xref: /freebsd/crypto/krb5/src/tests/threads/gss-perf.c (revision 7f2fe78b9dd5f51c821d771b63d2e096f6fd49e9)
1 /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /* tests/threads/gss-perf.c */
3 /*
4  * Copyright (C) 2009 by the Massachusetts Institute of Technology.
5  * All rights reserved.
6  *
7  * Export of this software from the United States of America may
8  *   require a specific license from the United States Government.
9  *   It is the responsibility of any person or organization contemplating
10  *   export to obtain such a license before exporting.
11  *
12  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
13  * distribute this software and its documentation for any purpose and
14  * without fee is hereby granted, provided that the above copyright
15  * notice appear in all copies and that both that copyright notice and
16  * this permission notice appear in supporting documentation, and that
17  * the name of M.I.T. not be used in advertising or publicity pertaining
18  * to distribution of the software without specific, written prior
19  * permission.  Furthermore if you modify this software you must label
20  * your software as modified software and not distribute it in such a
21  * fashion that it might be confused with the original M.I.T. software.
22  * M.I.T. makes no representations about the suitability of
23  * this software for any purpose.  It is provided "as is" without express
24  * or implied warranty.
25  */
26 
27 /*
28  * GSSAPI performance testing
29  * initially contributed by Ken Raeburn
30  */
31 /*
32  * Possible to-do items:
33  * - init-mutual testing (process msg back from accept)
34  * - wrap/unwrap testing (one init/accept per thread, loop on wrap/unwrap)
35  * - wrap/unwrap MT testing (one init/accept for process) ?
36  * - init+accept with replay cache
37  * - default to target "host@localhostname"
38  * - input ccache option?
39  *
40  * Also, perhaps try to simulate certain application patterns, like
41  * init/accept, exchange N messages with wrap/unwrap, destroy context,
42  * all in a loop in M parallel threads.
43  */
44 
45 #include <stdio.h>
46 #include <string.h>
47 #include <stdlib.h>
48 #include <limits.h>
49 #include <assert.h>
50 #include <unistd.h>
51 #include <pthread.h>
52 #include <krb5.h>
53 #include <gssapi/gssapi.h>
54 
55 #include <sys/time.h>
56 #include <sys/resource.h>
57 
58 #define N_THREADS 2
59 #define ITER_COUNT 10000
60 static int init_krb5_first = 0;
61 
62 struct resource_info {
63     struct timeval start_time, end_time;
64 };
65 struct thread_info {
66     pthread_t tid;
67     struct resource_info r;
68 };
69 
70 static gss_name_t target;
71 static char *prog, *target_name;
72 static unsigned int n_threads = N_THREADS;
73 static int iter_count = ITER_COUNT;
74 static int do_pause, do_mutual;
75 static int test_init, test_accept;
76 
77 static void usage (void) __attribute__((noreturn));
78 static void set_target (char *);
79 
80 static void
usage()81 usage ()
82 {
83     fprintf (stderr, "usage: %s [ options ] service-name\n", prog);
84     fprintf (stderr, "  service-name\tGSSAPI host-based service name (e.g., 'host@FQDN')\n");
85     fprintf (stderr, "options:\n");
86     fprintf (stderr, "\t-I\ttest gss_init_sec_context\n");
87     fprintf (stderr, "\t-A\ttest gss_accept_sec_context\n");
88     fprintf (stderr, "\t-k K\tspecify keytab (remember FILE: or other prefix!)\n");
89     fprintf (stderr, "\t-t N\tspecify number of threads (default %d)\n",
90              N_THREADS);
91     fprintf (stderr, "\t-i N\tset iteration count (default %d)\n",
92              ITER_COUNT);
93     fprintf (stderr, "\t-m\tenable mutual authentication flag (but don't do the additional calls)\n");
94     fprintf (stderr, "\t-K\tinitialize a krb5_context for the duration\n");
95     fprintf (stderr, "\t-P\tpause briefly after starting, to allow attaching dtrace/strace/etc\n");
96     exit (1);
97 }
98 
99 static int
numarg(char * arg)100 numarg (char *arg)
101 {
102     char *end;
103     long val;
104 
105     val = strtol (arg, &end, 10);
106     if (*arg == 0 || *end != 0) {
107         fprintf (stderr, "invalid numeric argument '%s'\n", arg);
108         usage ();
109     }
110     if (val >= 1 && val <= INT_MAX)
111         return val;
112     fprintf (stderr, "out of range numeric value %ld (1..%d)\n",
113              val, INT_MAX);
114     usage ();
115 }
116 
117 static char optstring[] = "k:t:i:KPmIA";
118 
119 static void
process_options(int argc,char * argv[])120 process_options (int argc, char *argv[])
121 {
122     int c;
123 
124     prog = strrchr (argv[0], '/');
125     if (prog)
126         prog++;
127     else
128         prog = argv[0];
129     while ((c = getopt (argc, argv, optstring)) != -1) {
130         switch (c) {
131         case '?':
132         case ':':
133             usage ();
134             break;
135 
136         case 'k':
137             setenv ("KRB5_KTNAME", optarg, 1);
138             break;
139 
140         case 't':
141             n_threads = numarg (optarg);
142             if (n_threads >= SIZE_MAX / sizeof (struct thread_info)) {
143                 n_threads = SIZE_MAX / sizeof (struct thread_info);
144                 fprintf (stderr, "limiting n_threads to %u\n", n_threads);
145             }
146             break;
147 
148         case 'i':
149             iter_count = numarg (optarg);
150             break;
151 
152         case 'K':
153             init_krb5_first = 1;
154             break;
155 
156         case 'P':
157             do_pause = 1;
158             break;
159 
160         case 'I':
161             test_init = 1;
162             break;
163         case 'A':
164             test_accept = 1;
165             break;
166         }
167     }
168     if (argc == optind + 1)
169         set_target (argv[optind]);
170     else
171         usage ();
172 
173     if (test_init && test_accept) {
174         fprintf (stderr, "-I and -A are mutually exclusive\n");
175         usage ();
176     }
177     if (test_init == 0 && test_accept == 0)
178         test_init = 1;
179 }
180 
181 static void
display_a_status(const char * s_type,OM_uint32 type,OM_uint32 val)182 display_a_status (const char *s_type, OM_uint32 type, OM_uint32 val)
183 {
184     OM_uint32 mctx = 0;
185     OM_uint32 maj_stat, min_stat;
186     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
187 
188     do {
189         maj_stat = gss_display_status (&min_stat,
190                                        val,
191                                        type,
192                                        GSS_C_NO_OID,
193                                        &mctx,
194                                        &msg);
195         if (maj_stat != GSS_S_COMPLETE) {
196             fprintf (stderr,
197                      "error getting display form of %s status code %#lx\n",
198                      s_type, (unsigned long) val);
199             exit (1);
200         }
201         fprintf (stderr, " %s: %.*s\n", s_type,
202                  (int) msg.length, (char *) msg.value);
203         gss_release_buffer (&min_stat, &msg);
204     } while (mctx != 0);
205 }
206 
207 static void
gss_error(const char * where,OM_uint32 maj_stat,OM_uint32 min_stat)208 gss_error(const char *where, OM_uint32 maj_stat, OM_uint32 min_stat)
209 {
210     fprintf (stderr, "%s: %s:\n", prog, where);
211     display_a_status ("major", GSS_C_GSS_CODE, maj_stat);
212     display_a_status ("minor", GSS_C_MECH_CODE, min_stat);
213     exit (1);
214 }
215 
216 static void
do_accept(gss_buffer_desc * msg,int iter)217 do_accept (gss_buffer_desc *msg, int iter)
218 {
219     OM_uint32 maj_stat, min_stat;
220     gss_name_t client = GSS_C_NO_NAME;
221     gss_buffer_desc reply = GSS_C_EMPTY_BUFFER;
222     gss_OID oid = GSS_C_NO_OID;
223     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
224     OM_uint32 flags = do_mutual ? GSS_C_MUTUAL_FLAG : 0;
225 
226     reply.value = NULL;
227     reply.length = 0;
228     maj_stat = gss_accept_sec_context (&min_stat,
229                                        &ctx,
230                                        GSS_C_NO_CREDENTIAL,
231                                        msg,
232                                        GSS_C_NO_CHANNEL_BINDINGS,
233                                        &client,
234                                        &oid,
235                                        &reply,
236                                        &flags,
237                                        NULL, /* time_rec */
238                                        NULL); /* del_cred_handle */
239     if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) {
240         fprintf (stderr, "pid %lu thread %#lx failing in iteration %d\n",
241                  (unsigned long) getpid (), (unsigned long) pthread_self (),
242                  iter);
243         gss_error ("accepting context", maj_stat, min_stat);
244     }
245     gss_release_buffer (&min_stat, &reply);
246     if (ctx != GSS_C_NO_CONTEXT)
247         gss_delete_sec_context (&min_stat, &ctx, GSS_C_NO_BUFFER);
248     gss_release_name (&min_stat, &client);
249 }
250 
251 static gss_buffer_desc
do_init()252 do_init ()
253 {
254     OM_uint32 maj_stat, min_stat;
255     gss_ctx_id_t ctx = GSS_C_NO_CONTEXT;
256     OM_uint32 flags = 0, ret_flags = 0;
257     gss_buffer_desc msg = GSS_C_EMPTY_BUFFER;
258 
259     if (do_mutual)
260         flags |= GSS_C_MUTUAL_FLAG;
261 
262     msg.value = NULL;
263     msg.length = 0;
264     maj_stat = gss_init_sec_context (&min_stat,
265                                      GSS_C_NO_CREDENTIAL,
266                                      &ctx,
267                                      target,
268                                      GSS_C_NO_OID,
269                                      flags,
270                                      0,
271                                      NULL, /* no channel bindings */
272                                      NULL, /* no previous token */
273                                      NULL, /* ignore mech type */
274                                      &msg,
275                                      &ret_flags,
276                                      NULL); /* time_rec */
277     if (maj_stat != GSS_S_COMPLETE && maj_stat != GSS_S_CONTINUE_NEEDED) {
278         gss_error ("initiating", maj_stat, min_stat);
279     }
280     if (ctx != GSS_C_NO_CONTEXT)
281         gss_delete_sec_context (&min_stat, &ctx, GSS_C_NO_BUFFER);
282     return msg;
283 }
284 
285 static void
set_target(char * name)286 set_target (char *name)
287 {
288     OM_uint32 maj_stat, min_stat;
289     gss_buffer_desc namebuf;
290 
291     target_name = name;
292     namebuf.value = name;
293     namebuf.length = strlen (name);
294     maj_stat = gss_import_name (&min_stat,
295                                 &namebuf,
296                                 GSS_C_NT_HOSTBASED_SERVICE,
297                                 &target);
298     if (maj_stat != GSS_S_COMPLETE)
299         gss_error ("importing target name", maj_stat, min_stat);
300 }
301 
302 static long double
tvsub(struct timeval t1,struct timeval t2)303 tvsub (struct timeval t1, struct timeval t2)
304 {
305     /* POSIX says .tv_usec is signed.  */
306     return (t1.tv_sec - t2.tv_sec
307             + (long double) 1.0e-6 * (t1.tv_usec - t2.tv_usec));
308 }
309 
310 static struct timeval
now(void)311 now (void)
312 {
313     struct timeval tv;
314     if (gettimeofday (&tv, NULL) < 0) {
315         perror ("gettimeofday");
316         exit (1);
317     }
318     return tv;
319 }
320 
321 static gss_buffer_desc init_msg;
322 
run_iterations(struct resource_info * r)323 static void run_iterations (struct resource_info *r)
324 {
325     int i;
326     OM_uint32 min_stat;
327 
328     r->start_time = now ();
329     for (i = 0; i < iter_count; i++) {
330         if (test_init) {
331             gss_buffer_desc msg = do_init ();
332             gss_release_buffer (&min_stat, &msg);
333         } else if (test_accept) {
334             do_accept (&init_msg, i);
335         } else
336             assert (test_init || test_accept);
337     }
338     r->end_time = now ();
339 }
340 
341 static void *
thread_proc(void * p)342 thread_proc (void *p)
343 {
344     run_iterations (p);
345     return 0;
346 }
347 
348 static struct thread_info *tinfo;
349 
350 static krb5_context kctx;
351 static struct rusage start, finish;
352 static struct timeval start_time, finish_time;
353 
354 int
main(int argc,char * argv[])355 main (int argc, char *argv[])
356 {
357     long double user, sys, wallclock, total;
358     unsigned int i;
359 
360     /* Probably should have a command-line option controlling this,
361        but if a replay cache is used, we can't do just one
362        init_sec_context and easily time just the accept_sec_context
363        side.  */
364     setenv ("KRB5RCACHETYPE", "none", 1);
365 
366     process_options (argc, argv);
367 
368     /*
369      * Some places in the krb5 library cache data globally.
370      * This option allows you to test the effect of that.
371      */
372     if (init_krb5_first && krb5_init_context (&kctx) != 0) {
373         fprintf (stderr, "krb5_init_context error\n");
374         exit (1);
375     }
376     tinfo = calloc (n_threads, sizeof (*tinfo));
377     if (tinfo == NULL) {
378         perror ("calloc");
379         exit (1);
380     }
381     printf ("Test: %s  threads: %d  iterations: %d  target: %s\n",
382             test_init ? "init" : "accept", n_threads, iter_count,
383             target_name ? target_name : "(NONE)");
384     if (do_pause) {
385         printf ("pid %lu napping...\n", (unsigned long) getpid ());
386         sleep (10);
387     }
388     /*
389      * Some tests use one message and process it over and over.  Even
390      * if not, this sort of "primes" things by fetching any needed
391      * tickets just once.
392      */
393     init_msg = do_init ();
394     printf ("starting...\n");
395     /* And *now* we start measuring the performance.  */
396     if (getrusage (RUSAGE_SELF, &start) < 0) {
397         perror ("getrusage");
398         exit (1);
399     }
400     start_time = now ();
401 #define foreach_thread(IDXVAR) for (IDXVAR = 0; IDXVAR < n_threads; IDXVAR++)
402     foreach_thread (i) {
403         int err;
404 
405         err = pthread_create (&tinfo[i].tid, NULL, thread_proc, &tinfo[i].r);
406         if (err) {
407             fprintf (stderr, "pthread_create: %s\n", strerror (err));
408             exit (1);
409         }
410     }
411     foreach_thread (i) {
412         int err;
413         void *val;
414 
415         err = pthread_join (tinfo[i].tid, &val);
416         if (err) {
417             fprintf (stderr, "pthread_join: %s\n", strerror (err));
418             exit (1);
419         }
420     }
421     finish_time = now ();
422     if (getrusage (RUSAGE_SELF, &finish) < 0) {
423         perror ("getrusage");
424         exit (1);
425     }
426     if (init_krb5_first)
427         krb5_free_context (kctx);
428     foreach_thread (i) {
429         printf ("Thread %2d: elapsed time %Lfs\n", i,
430                 tvsub (tinfo[i].r.end_time, tinfo[i].r.start_time));
431     }
432     wallclock = tvsub (finish_time, start_time);
433     /*
434      * Report on elapsed time and CPU usage.  Depending what
435      * performance issue you're chasing down, different values may be
436      * of particular interest, so report all the info we've got.
437      */
438     printf ("Overall run time with %d threads = %Lfs, %Lfms per iteration.\n",
439             n_threads, wallclock, 1000 * wallclock / iter_count);
440     user = tvsub (finish.ru_utime, start.ru_utime);
441     sys = tvsub (finish.ru_stime, start.ru_stime);
442     total = user + sys;
443     printf ("CPU usage:   user=%Lfs sys=%Lfs total=%Lfs.\n", user, sys, total);
444     printf ("Utilization: user=%5.1Lf%% sys=%5.1Lf%% total=%5.1Lf%%\n",
445             100 * user / wallclock,
446             100 * sys / wallclock,
447             100 * total / wallclock);
448     printf ("Util/thread: user=%5.1Lf%% sys=%5.1Lf%% total=%5.1Lf%%\n",
449             100 * user / wallclock / n_threads,
450             100 * sys / wallclock / n_threads,
451             100 * total / wallclock / n_threads);
452     printf ("Total CPU use per iteration per thread: %Lfms\n",
453             1000 * total / n_threads / iter_count);
454     return 0;
455 }
456