1/*
2 * Copyright (c) 2006 Apple Inc.  All Rights Reserved.
3 *
4 * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. The rights granted to you under the License
10 * may not be used to create, or enable the creation or redistribution of,
11 * unlawful or unlicensed copies of an Apple operating system, or to
12 * circumvent, violate, or enable the circumvention or violation of, any
13 * terms of an Apple operating system software license agreement.
14 *
15 * Please obtain a copy of the License at
16 * http://www.opensource.apple.com/apsl/ and read it before using this file.
17 *
18 * The Original Code and all software distributed under the License are
19 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23 * Please see the License for the specific language governing rights and
24 * limitations under the License.
25 *
26 * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27 */
28
29#include <unistd.h>
30#include <stdlib.h>
31#include <stdio.h>
32#include <stdbool.h>
33#include <string.h>
34
35// add additional headers needed here.
36
37#include "../libmicro.h"
38#include <CoreFoundation/CFArray.h>
39#include <CoreFoundation/CFString.h>
40#include <CoreFoundation/CFDictionary.h>
41#include <OpenDirectory/OpenDirectory.h>
42#include <DirectoryService/DirectoryService.h>
43
44#if DEBUG
45# define debug(fmt, args...)    (void) fprintf(stderr, fmt , ##args)
46// # define debug(fmt, args...)    (void) fprintf(stderr, fmt "\n" , ##args)
47#else
48# define debug(fmt, args...)
49#endif
50
51
52// Correct use case
53//
54//    od_query_create_with_node -E  -L -S -W -B 200 -C 10 -c 100 -r 300
55//
56//      libMicro default benchmark run options are "-E -C 200 -L -S -W"
57//
58// -B is batch size: loop iteration per each benchmark run. Needs to match # of
59//                   real lookups. This is total number of lookups to issue.
60// -C is min sample number: how many benchmark needs to run to get proper sample
61//                          1 is mimumum, but you get at least 3 benchmark run
62//                          samples. Do not set to zero. Default is 200 for most
63//                          runs in libMicro.
64// -r is the number of total records.
65// -c is the cache hit rate for lookup. set to 10%, you need -c 10.
66//                ie. -B 100 -c 50 -r 1000 -C 200 (out of 1000 records, I want 50%
67//                     lookup, and batch size is 100.
68//                     To get 50% cache hit rate, you need 500 record lookups.
69//                     Batch size will be adjusted to 500 to get 500 record
70//                     lookup in each benchmark. If -r size is smaller than -B,
71//                     then -B will not be adjusted.
72
73// Defining prefix for user and group name
74// make sure that these match the ones in LDAP records
75// ie. local_test_1 , od_test_4525, od_test_group_43, od_test_host_63
76#define LOCAL_U_PREFIX     CFSTR("local_test_")
77#define OD_U_PREFIX        CFSTR("od_test_")
78#define LOCAL_G_PREFIX     CFSTR("local_test_group_")
79#define OD_G_PREFIX        CFSTR("od_test_group_")
80#define LOCAL_H_PREFIX     CFSTR("local_test_host_")
81#define OD_H_PREFIX        CFSTR("od_test_host_")
82
83/*
84 *    Your state variables should live in the tsd_t struct below
85 */
86typedef struct {
87    ODNodeRef    node;
88} tsd_t;
89
90// dsRecTypeStandard type dictionary
91enum {rectype_users=0, rectype_groups, rectype_hosts};
92CFStringRef rectype_dict[] = { CFSTR(kDSStdRecordTypeUsers),
93                               CFSTR(kDSStdRecordTypeGroups),
94                               CFSTR(kDSStdRecordTypeHosts) };
95
96// the number of record lookup to issue is covered by standard option optB
97static int  optRecords =    100;  // the number of total records
98static int  optCachehit =   100;  // specify cache hit rate (% of record re-lookup)
99static bool optNodeLocal =  1;    // which node to search. Local node is default
100static int  optType =       rectype_users;    // dsRecType to search for. "Users"" is the default
101static const char *nodename = "/LDAPv3/127.0.0.1";
102
103static CFStringRef *key;                // username array
104
105// parse -t option and return enum type: user, group, and host
106// called by benchmark_optswitch()
107int
108ds_rec_type(char *name)
109{
110    if (strcasecmp("u", name) == 0) {
111        return (rectype_users);
112    } else if (strcasecmp("g", name) == 0) {
113        return (rectype_groups);
114    } else if (strcasecmp("h", name) == 0) {
115        return (rectype_hosts);
116    }
117
118    return (-1);
119}
120
121int
122benchmark_init()
123{
124    debug("benchmark_init");
125    (void) sprintf(lm_optstr,  "c:n:r:t:");
126
127    lm_tsdsize = sizeof (tsd_t);
128    lm_defB = 1000;
129
130    (void) sprintf(lm_usage,
131                "\n       ------- od_query_create_with_node specific options (default: *)\n"
132                "       [-c hitrate%% (100%%*)]\n"
133                "       [-r total number of records (100*)]\n"
134                "       [-n nodename] node name to use for test\n"
135                "       [-t record type: 'u'sers, 'g'roups, 'h'osts]\n"
136                "       use -B option to specify total number of record lookups to issue"
137                "\n" );
138    return (0);
139}
140
141/*
142 * This is where you parse your lower-case arguments.
143 */
144int
145benchmark_optswitch(int opt, char *optarg)
146{
147    debug("benchmark_optswitch");
148
149    switch (opt) {
150    case 'c':    // cache hit rate. 100% means lookup the same records over and over
151        optCachehit = atoi(optarg);
152        debug("optCachehit = %d\n", optCachehit);
153        if (optCachehit > 100 || optCachehit < 0) {
154            printf("cache hit rate should be in between 0%% and 100%%");
155            return (-1);
156        }
157        break;
158
159    case 'r':    // total number of records. default is 100
160        optRecords = atoi(optarg);
161        debug("optRecords = %d\n", optRecords);
162        break;
163
164    case 'n':    // node
165        nodename = optarg;
166        break;
167
168    case 't':    // dsRecType: user, group, hots
169        optType = ds_rec_type(optarg);
170        debug("optType = %d\n", optType);
171
172        if (optType == -1) {
173            printf("wrong -t record type option\n");
174            return (-1);
175        }
176        break;
177
178    default:
179        return (-1);
180    }
181
182    return (0);
183}
184
185
186int
187benchmark_initrun()
188{
189    int i;
190    CFStringRef prefix;              // local user is default
191
192    debug("benchmark_initrun\n");
193
194    // Adjust # of record lookups to reflect cache hit rate
195    if (optCachehit < 100) {
196        optRecords  = (int) ((float) optRecords * ((float) optCachehit / 100));
197        debug("# of records adjusted to %d for cache hit rate %d%%\n", optRecords, optCachehit);
198    }
199
200    // if batch size (one benchmark run) is less than the number records, adjust
201    // it to match the number record lookups in one batch run
202    if (lm_optB < optRecords) {
203        lm_optB = optRecords;
204        debug("Adjusting batch size to %d to match the lookups required in benchmark run\n", lm_optB);
205    }
206
207    switch (optType) {
208        case rectype_users:
209            prefix = (optNodeLocal) ? LOCAL_U_PREFIX : OD_U_PREFIX;
210            break;
211        case rectype_groups:
212            prefix = (optNodeLocal) ? LOCAL_G_PREFIX : OD_G_PREFIX;
213            break;
214        case rectype_hosts:
215            prefix = (optNodeLocal) ? LOCAL_H_PREFIX : OD_H_PREFIX;
216            break;
217    }
218    // create an array of usernames to use in benchmark before their use
219    // realtime generation in benchmark effects performance measurements
220
221    key = malloc(sizeof(CFStringRef) * optRecords);
222
223    // user, group, hosts key to lookup
224    switch (optType) {
225
226    case rectype_users:     // users query
227    case rectype_groups:    // groups query
228    case rectype_hosts:     // hosts query
229        for (i = 0; i < optRecords; i++) {
230            key[i] = CFStringCreateWithFormat( kCFAllocatorDefault,
231                                               NULL,
232                                               CFSTR("%@%d"),
233                                               prefix,
234                                               i+1);
235            // CFShow(key[i]);  // print user name to check
236        }
237        break;
238    }
239
240    return (0);
241}
242
243
244// Initialize all structures that will be used in benchmark()
245// 1. make local or network node for OD query
246// 2. create user key
247int
248benchmark_initworker(void *tsd)
249{
250    CFErrorRef    error;
251    tsd_t *ts = (tsd_t *)tsd;
252
253    debug("benchmark_initworker: %s", (optNodeLocal) ? "local" : "network");
254
255
256    // create OD node for local or OD query
257    if (optNodeLocal) {
258        ts->node = ODNodeCreateWithNodeType(NULL, kODSessionDefault, kODNodeTypeLocalNodes, &error);
259    }
260    else {
261        CFStringRef nodenameStr = CFStringCreateWithCString(kCFAllocatorDefault, nodename, kCFStringEncodingUTF8);
262        ts->node = ODNodeCreateWithName(NULL, kODSessionDefault, nodenameStr, &error);
263        CFRelease(nodenameStr);
264    }
265
266    if (!ts->node) {
267        debug("error calling ODNodeCreateWithNodeType\n");
268        exit(1);
269    }
270
271    CFRetain (ts->node);
272
273    debug("benchmark_initworker: ODNodeRef = 0x%lx\n", ts->node);
274    return (0);
275}
276
277int
278benchmark(void *tsd, result_t *res)
279{
280
281    tsd_t        *ts = (tsd_t *)tsd;
282    int          i;
283    ODNodeRef    node;
284    CFErrorRef   error;
285    CFArrayRef   results;
286    ODQueryRef   query;
287
288   res->re_errors = 0;
289    node = ts->node;
290
291    debug("in to benchmark - optB = %i, node = 0x%lx \n", lm_optB, node);
292    for (i = 0; i < lm_optB; i++) {
293
294        debug("loop %d: querying\n", i);
295        query = ODQueryCreateWithNode(NULL,
296                        node,                        // inNode
297                        rectype_dict[optType],       // inRecordTypeOrList
298                        CFSTR(kDSNAttrRecordName),   // inAttribute
299                        kODMatchInsensitiveEqualTo,  // inMatchType
300                        key[i % optRecords],                      // inQueryValueOrList
301                        NULL,                        // inReturnAttributeOrList
302                        1,                           // inMaxResults
303                        &error);
304
305        if (query) {
306            // we do not want to factually fetch the result in benchmark run
307            // debug("loop %d: calling ODQueryCopyResults\n", i);
308            results = ODQueryCopyResults(query, FALSE, &error);
309            CFRelease(query);
310            if (results) {
311#if DEBUG
312                int c;
313                c = CFArrayGetCount(results);
314                if (c > 0) {
315                    debug("Successful run: %d results, ", c);
316                }
317                else {
318                    debug("no result for ");
319                }
320                CFShow (key[i % optRecords]);
321                debug("\n");
322#endif
323                CFRelease(results);
324            }
325            else {
326                debug("loop %d: ODQueryCopyResults returned empty result for ", i);
327                res->re_errors++;
328                CFShow (key[i % optRecords]);
329                debug("\n");
330            } // if (results)
331
332        } // if (query)
333        else {
334            res->re_errors++;
335        }
336    }
337    res->re_count = i;
338
339    return (0);
340}
341
342
343// We need to release all the structures we allocated in benchmark_initworker()
344int
345benchmark_finiworker(void *tsd)
346{
347    tsd_t    *ts = (tsd_t *)tsd;
348
349    debug("benchmark_result: deallocating structures\n");
350
351    // free the node
352    if (ts->node)
353        CFRelease (ts->node);
354    ts->node = NULL;
355
356    return (0);
357}
358
359int
360benchmark_finirun()
361{
362    int i;
363
364    for (i = 0; i < optRecords; i++){
365        CFRelease(key[i]);
366    }
367
368    free(key);
369
370    return (0);
371}
372
373char *
374benchmark_result()
375{
376    static char    result = '\0';
377    debug("\n\n# of records adjusted to %d for cache hit rate %d%%\n", optRecords, optCachehit);
378    debug("benchmark_result\n");
379    return (&result);
380}
381
382