2929#include "perflib/err.h"
3030#include "perflib/perflib.h"
3131
32+ #define W_PROBABILITY 50
33+ #define MAX_WRITERS 0
3234#define RUN_TIME 5
3335#define QUANTILES 5
3436#define NONCE_CFG "file:servercert.pem"
3840
3941static size_t timeout_us = RUN_TIME * 1000000 ;
4042static size_t quantiles = QUANTILES ;
43+ static size_t max_writers = MAX_WRITERS ;
44+ static size_t w_probability = W_PROBABILITY * 65536 / 100 ;
4145
4246enum verbosity {
4347 VERBOSITY_TERSE ,
@@ -49,6 +53,11 @@ enum verbosity {
4953 VERBOSITY_MAX__
5054};
5155
56+ static enum mode {
57+ MODE_R ,
58+ MODE_RW , /* "MODE_W" is just MODE_RW with 100% write probability */
59+ } mode = MODE_R ;
60+
5261enum nonce_type {
5362 NONCE_PATH ,
5463};
@@ -57,6 +66,7 @@ struct call_times {
5766 uint64_t duration ;
5867 uint64_t total_count ;
5968 uint64_t total_found ;
69+ uint64_t total_added ;
6070 uint64_t min_count ;
6171 uint64_t max_count ;
6272 double avg ;
@@ -95,8 +105,11 @@ ALIGN64 struct thread_data {
95105 struct {
96106 uint64_t count ;
97107 uint64_t found ;
108+ uint64_t added_certs ;
98109 OSSL_TIME end_time ;
99110 } * q_data ;
111+ size_t cert_count ;
112+ X509 * * certs ;
100113 X509_STORE_CTX * ctx ;
101114} * thread_data ;
102115
@@ -112,6 +125,28 @@ OSSL_TIME max_time;
112125#define OSSL_MIN (p , q ) ((p) < (q) ? (p) : (q))
113126#define OSSL_MAX (p , q ) ((p) > (q) ? (p) : (q))
114127
128+ #if defined(__STDC_VERSION__ ) && __STDC_VERSION__ >= 202311L && \
129+ !defined(__cplusplus )
130+ # define ossl_thread thread_local
131+ #elif defined(__STDC_VERSION__ ) && __STDC_VERSION__ >= 201112L && \
132+ !defined(__cplusplus )
133+ # define ossl_thread _Thread_local
134+ #elif defined(__GNUC__ )
135+ # define ossl_thread __thread
136+ #elif defined(_MSC_VER )
137+ # define ossl_thread __declspec(thread)
138+ #else
139+ # define ossl_thread
140+ #endif
141+
142+ static ossl_thread uint32_t prng_seed ;
143+
144+ static uint32_t prng (void )
145+ {
146+ /* Taken from FreeBSD's lib/libc/stdlib/rand.c */
147+ return (prng_seed = prng_seed * 1103515245U + 12345U );
148+ }
149+
115150/**
116151 * Extend the x509_pool, by the increment, if inc is 0, the increment size
117152 * is decided automatically.
@@ -482,30 +517,46 @@ do_x509storeissuer(size_t num)
482517 OSSL_TIME q_end ;
483518 size_t q = 0 ;
484519 size_t count = 0 ;
520+ size_t add = 0 ;
485521 size_t found = 0 ;
486522
523+ prng_seed = num ;
487524 td -> start_time = ossl_time_now ();
488525 duration .t = max_time .t - td -> start_time .t ;
489526 q_end .t = duration .t / quantiles + td -> start_time .t ;
490527
491528 do {
492- if (X509_STORE_CTX_get1_issuer (& issuer , td -> ctx , x509_nonce ) != 0 ) {
493- found ++ ;
494- X509_free (issuer );
529+ if (td -> cert_count > 0 && (prng () % 65536 < w_probability )) {
530+ size_t cert_id = add % td -> cert_count ;
531+
532+ if (!X509_STORE_add_cert (store , td -> certs [cert_id ])) {
533+ warnx ("thread %zu: Failed to add generated certificate %zu"
534+ " to the store" , num , cert_id );
535+ } else {
536+ add ++ ;
537+ }
538+ } else {
539+ if (X509_STORE_CTX_get1_issuer (& issuer , td -> ctx , x509_nonce ) != 0 ) {
540+ found ++ ;
541+ X509_free (issuer );
542+ }
543+ issuer = NULL ;
495544 }
496- issuer = NULL ;
545+
497546 count ++ ;
498547 time = ossl_time_now ();
499548 if (time .t >= q_end .t ) {
500549 td -> q_data [q ].count = count ;
501550 td -> q_data [q ].found = found ;
551+ td -> q_data [q ].added_certs = add ;
502552 td -> q_data [q ].end_time = time ;
503553 q_end .t = (duration .t * (++ q + 1 )) / quantiles + td -> start_time .t ;
504554 }
505555 } while (time .t < max_time .t );
506556
507557 td -> q_data [quantiles - 1 ].count = count ;
508558 td -> q_data [quantiles - 1 ].found = found ;
559+ td -> q_data [quantiles - 1 ].added_certs = add ;
509560 td -> q_data [quantiles - 1 ].end_time = time ;
510561}
511562
@@ -552,17 +603,21 @@ get_calltimes(struct call_times *times, int verbosity)
552603 (q ? thread_data [i ].q_data [q - 1 ].count : 0 );
553604 uint64_t found = thread_data [i ].q_data [q ].found -
554605 (q ? thread_data [i ].q_data [q - 1 ].found : 0 );
606+ uint64_t add = thread_data [i ].q_data [q ].added_certs -
607+ (q ? thread_data [i ].q_data [q - 1 ].added_certs : 0 );
555608
556609 times [q ].duration += thread_data [i ].q_data [q ].end_time .t - start_t ;
557610 times [q ].total_count += count ;
558611 times [q ].total_found += found ;
612+ times [q ].total_added += add ;
559613 }
560614 }
561615
562616 for (size_t q = 0 ; q < quantiles ; q ++ ) {
563617 times [quantiles ].duration += times [q ].duration ;
564618 times [quantiles ].total_count += times [q ].total_count ;
565619 times [quantiles ].total_found += times [q ].total_found ;
620+ times [quantiles ].total_added += times [q ].total_added ;
566621 }
567622
568623 for (size_t q = (quantiles == 1 ); q <= quantiles ; q ++ )
@@ -647,14 +702,18 @@ report_result(int verbosity)
647702 printf (": avg: %9.3lf us, median: %9.3lf us"
648703 ", min: %9.3lf us @thread %3zu, max: %9.3lf us @thread %3zu"
649704 ", stddev: %9.3lf us (%8.4lf%%)"
650- ", hits %9" PRIu64 " of %9" PRIu64 " (%8.4lf%%)\n" ,
705+ ", hits %9" PRIu64 " of %9" PRIu64 " (%8.4lf%%)"
706+ ", added certs: %" PRIu64 "\n" ,
651707 times [i ].avg , times [i ].median ,
652708 times [i ].min , times [i ].min_idx ,
653709 times [i ].max , times [i ].max_idx ,
654710 times [i ].stddev ,
655711 100.0 * times [i ].stddev / times [i ].avg ,
656- times [i ].total_found , times [i ].total_count ,
657- 100.0 * times [i ].total_found / (times [i ].total_count ));
712+ times [i ].total_found ,
713+ (times [i ].total_count - times [i ].total_added ),
714+ 100.0 * times [i ].total_found
715+ / (times [i ].total_count - times [i ].total_added ),
716+ times [i ].total_added );
658717 }
659718 break ;
660719 }
@@ -667,6 +726,7 @@ usage(char * const argv[])
667726{
668727 fprintf (stderr ,
669728 "Usage: %s [-t] [-v] [-q N] [-T time] [-n nonce_type:type_args]"
729+ " [-m mode] [-w writer_threads] [-W percentage]"
670730 " [-l max_certs] [-L max_cert_dirs] [-E] [-C threads]"
671731 " [-V] certsdir [certsdir...] threadcount\n"
672732 "\t-t\tTerse output\n"
@@ -681,6 +741,13 @@ usage(char * const argv[])
681741 "\t\t\tfile:PATH - load nonce certificate from PATH;\n"
682742 "\t\t\tif PATH is relative, the provided certsdir's are searched.\n"
683743 "\t\tDefault: " NONCE_CFG "\n"
744+ "\t-m\tTest mode, can be one of r, rw. Default: r\n"
745+ "\t-w\tMaximum number of threads that attempt addition\n"
746+ "\t\tof the new certificates to the store in rw mode,\n"
747+ "\t\t0 is unlimited. Default: " OPENSSL_MSTR (MAX_WRITERS ) "\n"
748+ "\t-W\tProbability of a certificate being written\n"
749+ "\t\tto the store, instead of being queried,\n"
750+ "\t\tin percents. Default: " OPENSSL_MSTR (W_PROBABILITY ) "\n"
684751 "\t-l\tLimit on the number of initially loaded certificates.\n"
685752 "\t\tDefault: " OPENSSL_MSTR (MAX_LOAD_CERTS ) "\n"
686753 "\t-L\tLimit on the number of initially loaded certificate\n"
@@ -710,8 +777,23 @@ parse_timeout(const char * const optarg)
710777 return (size_t )(timeout_s * 1e6 );
711778}
712779
780+ static double
781+ parse_probability (const char * const optarg )
782+ {
783+ char * endptr = NULL ;
784+ double prob ;
785+
786+ prob = strtod (optarg , & endptr );
787+
788+ if (endptr == NULL || * endptr != '\0' || prob < 0 || prob > 100 )
789+ errx (EXIT_FAILURE , "incorrect probability value: \"%s\"" , optarg );
790+
791+ return prob ;
792+ }
793+
713794/**
714795 * Parse nonce configuration string. Currently supported formats:
796+ * * "gen" - generate a nonce certificate
715797 * * "file:PATH" - where PATH is either a relative path (that will be then
716798 * checked against the list of directories provided),
717799 * or an absolute one.
@@ -765,7 +847,7 @@ main(int argc, char *argv[])
765847
766848 parse_nonce_cfg (NONCE_CFG , & nonce_cfg );
767849
768- while ((opt = getopt (argc , argv , "tvq:T:n:l:L:EC:V" )) != -1 ) {
850+ while ((opt = getopt (argc , argv , "tvq:T:n:m:w:W: l:L:EC:V" )) != -1 ) {
769851 switch (opt ) {
770852 case 't' : /* terse */
771853 verbosity = VERBOSITY_TERSE ;
@@ -788,6 +870,22 @@ main(int argc, char *argv[])
788870 case 'n' : /* nonce */
789871 parse_nonce_cfg (optarg , & nonce_cfg );
790872 break ;
873+ case 'm' : /* mode */
874+ if (strcasecmp (optarg , "r" ) == 0 ) {
875+ mode = MODE_R ;
876+ } else if (strcasecmp (optarg , "rw" ) == 0 ) {
877+ mode = MODE_RW ;
878+ } else {
879+ errx (EXIT_FAILURE , "Unknown mode: \"%s\"" , optarg );
880+ }
881+ break ;
882+ case 'w' : /* maximum writers */
883+ max_writers = parse_int (optarg , 0 , INT_MAX ,
884+ "maximum number of writers" );
885+ break ;
886+ case 'W' : /* percent of writes */
887+ w_probability = (size_t ) (parse_probability (optarg ) * 65536 / 100 );
888+ break ;
791889 case 'l' : /* max certs to load */
792890 max_load_certs = parse_int (optarg , 0 , INT_MAX ,
793891 "maximum certificate load count" );
@@ -855,7 +953,8 @@ main(int argc, char *argv[])
855953
856954 num_certs += read_certsdirs (argv + dirs_start , argc - dirs_start - 1 ,
857955 max_load_certs , max_load_cert_dirs ,
858- store , & cert_pool , & total_certs_read );
956+ store , mode == MODE_RW ? & cert_pool : NULL ,
957+ & total_certs_read );
859958
860959 if (verbosity >= VERBOSITY_DEBUG_STATS )
861960 fprintf (stderr , "Added %zu certificates to the store"
@@ -881,6 +980,22 @@ main(int argc, char *argv[])
881980 }
882981 }
883982
983+ if (mode == MODE_RW && cert_pool .size > 0 ) {
984+ const size_t writers = max_writers ? OSSL_MIN (max_writers , threadcount )
985+ : threadcount ;
986+
987+ /*
988+ * Point each writer thread at the part of the generated certs
989+ * array it uses for store population.
990+ */
991+ for (size_t i = 0 ; i < writers ; i ++ ) {
992+ size_t offs = (cert_pool .size * i ) / writers ;
993+
994+ thread_data [i ].certs = cert_pool .entries + offs ;
995+ thread_data [i ].cert_count = OSSL_MAX (cert_pool .size / writers , 1 );
996+ }
997+ }
998+
884999 max_time = ossl_time_add (ossl_time_now (), ossl_us2time (timeout_us ));
8851000
8861001 if (!perflib_run_multi_thread_test (do_x509storeissuer , threadcount , & duration ))
@@ -889,6 +1004,9 @@ main(int argc, char *argv[])
8891004 if (error )
8901005 errx (EXIT_FAILURE , "Error during test" );
8911006
1007+ if (mode == MODE_RW )
1008+ report_store_size (store , "after the test run" , verbosity );
1009+
8921010 report_result (verbosity );
8931011
8941012 ret = EXIT_SUCCESS ;
0 commit comments