Skip to content

Conversation

@esyr
Copy link
Member

@esyr esyr commented Sep 23, 2025

This patch set slightly updates the x509storeissuer test to actually populate the store with certificates, and provides additional modes of operation and measurement data (when requested).

Resolves: openssl/project#1529

td->q_data[quantiles - 1].count = count;
td->q_data[quantiles - 1].end_time = time;

err:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ci seems to be unhappy because of this. I would try to add return; or just empty statement ;. finge cross.

@esyr esyr force-pushed the esyr/x509storeissuer-update branch 2 times, most recently from d1454e0 to b327824 Compare October 15, 2025 14:30
@npajkovsky npajkovsky mentioned this pull request Oct 21, 2025
@esyr esyr force-pushed the esyr/x509storeissuer-update branch from b327824 to 1206f0b Compare October 22, 2025 14:55
@esyr esyr marked this pull request as ready for review October 22, 2025 14:56
@esyr esyr force-pushed the esyr/x509storeissuer-update branch from 1206f0b to 3001309 Compare October 22, 2025 15:01
@esyr esyr requested review from Sashan and npajkovsky October 22, 2025 15:01
Copy link
Contributor

@Sashan Sashan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May be I would move the recent changeset which populates store with certificates to separate PR. so we can get at least some changes in and fine tune new set of changes. that's just suggestions. but it feels like here we are just piling up more and more code.

*/

#include <stdlib.h>
#include <dirent.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid including dirent.h won't fly on windows.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, made it fly.

@esyr esyr force-pushed the esyr/x509storeissuer-update branch 3 times, most recently from ccbb791 to 4b335f7 Compare October 23, 2025 15:33
@esyr esyr requested a review from Sashan October 24, 2025 01:35
@npajkovsky npajkovsky requested a review from nhorman October 24, 2025 07:05
static unsigned char out[64];
size_t len;

len = rand() % (sizeof(out) - 2) + 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens here if rand() returns a value that is a multiple of sizeof(out) - 2? Don't we then just get an empty string for the issuer or subject name? Is that a problem?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why +1 at the end.

fprintf(stderr, "Added %zu generated certificates to the store\n",
num_store_gen_certs);

num_certs += read_certsdirs(argv + dirs_start, argc - dirs_start - 1,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I'm a bit confused here. Most of this code that targets x509storeissuer is about dynamically generating certificates at run time. Are we also allowing the loading of a certificate directory from disk? If we are allowing both, would it make more sense to move the dynamic generation to an external script in the repository, so that we can just support reading from disk in the code, and create any dynamic certs via the aforementioned script?

Copy link
Member Author

@esyr esyr Oct 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it makes sense. The idea behind generating them in the test was to have control over the contents of the certificates, but that can just as well be achieved by an external script.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loading has been reworked to include -l/-L/-E options, the certificate generation patches have been dropped.


#include <stdarg.h>
# if !defined(_WIN32)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we don't do new lines around the ifdefs

goto err;
}
if (cert == NULL)
errx(EXIT_FAILURE, "Failed to allocate cert path");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're missing goto err on every errx.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

errx() does exit() so goto won't be executed.


return ret;
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move it to libperf? It's used in other tests, and that should be fixed in other tests in another PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do a separate PR for that.

fprintf(stderr,
"Usage: %s [-t] [-v] [-T time] [-n nonce_type:type_args] "
"certsdir threadcount\n"
"certsdir [certsdir...] threadcount\n"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need another certsdir?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially it was in order to avoid the need to combine the cert dirs outside the test, now it's more to get ability to point to a generated certs dir in addition to initially loaded ones.

counts[num]++;
time = ossl_time_now();
if ((count & 0x3f) == 0)
time = ossl_time_now();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please keep it aligned with other tests. If there is an issue, create a new ticket where we can discuss it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since the were no observed benefit on MacOS after all, the patch has been dropped.

Copy link
Contributor

@Sashan Sashan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good in general. I could spot few nits which I think are worth to address.

if (ctx == NULL || !X509_STORE_CTX_init(ctx, store, x509, NULL)) {
printf("Failed to initialise X509_STORE_CTX\n");
err = 1;
alg = p ? *alg_storage = OPENSSL_strndup(algstr, p - algstr) : algstr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if there should also be OPENSSL_strdup(algstr) in false branch.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The offending code has been dropped after the latest changes.

return ctx;

err:
EVP_PKEY_CTX_free(ctx);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we do

OPENSSL_free(alg);
*alg_storage = NULL;

it does not matter much now because program is going to terminate when failure happens here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here.

static unsigned char out[64];
size_t len;

len = rand() % (sizeof(out) - 2) + 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we care if rand() returns 0 here making the function to yield an empty string?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here.

int error = 1;

cert = X509_new();
if (!cert) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: in function make_pkey_ctx() we use == NULL / != NULL to compare pointers. either way is fine with me as long as it is used consistently. Well I have slight preference to use == NULL over !x
thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here.

X509 *x509_nonce = X509_new();
X509_NAME *x509_name_nonce = NULL;

if (!x509_nonce)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

== NULL ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto here.

#if defined(_WIN32)
/*
* So, we don't try to concatenate the provided path with the directory
* paths if the path start with the following:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it should be ...the path starts

{
X509 *x509 = NULL;
char *path = NULL;
size_t ret = 1;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ret should be initialized to 0 here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct, fixed.

static void
do_x509storeissuer(size_t num)
{
struct thread_data *td = thread_data + num;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to do td = &thread_data[num]; thanks.

@npajkovsky
Copy link

openssl head 0ab2ece0a2025ebdea1f94744424ef89ffb9a2b0

./Configure --banner=Configured --strict-warnings

Main branch

$ ./x509storeissuer -t /Users/npajkovsky/openssl/openssl/test/certs 8
1.981555

Your branch

$ ./x509storeissuer -t /Users/npajkovsky/openssl/openssl/test/certs 8
4.663879

There is something that changes the behaviour of the test that we already have publicly available.

@Sashan
Copy link
Contributor

Sashan commented Oct 27, 2025

hmm, this is actually a very good point. I think the whole statistics machinery deserves some explanation in comment.

I see the difference, but otherway round:

lifty$ ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.sashan/test/certs/ 8
10.218173
lifty$ cd ../build-main/
lifty$ ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.sashan/test/certs/ 8
25.938117

issuer = NULL;
}

count++;
Copy link
Contributor

@Sashan Sashan Oct 27, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found the code block which starts at 769 as pretty dense. it perhaps deserves some comment here. my understanding is that the whole idea is to divide the collection of statistics to evenly distributed samples (quantiles). The current setting is there are 5 quantiles for the whole duration of test which is 5 secs.

the line here just makes we check the elapsed time only once in a while (count 0x3f). If time collection interval ellapses, then we save a sample (quantile) for elapsed interval.

and one important detail to mention here is that when tool is running with -t option, terse mode there is just one quantile (quantile = 1) which covers the whole test duration. no split to evenly distributed intervals happens.

td->q_data[q].found = found;
td->q_data[q].added_certs = add;
td->q_data[q].end_time = time;
q_end.t = (duration.t * (++q + 1)) / quantiles + td->start_time.t;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here we calculate the time when to collect the next sample. ++q + 1 prevents 0 (which might be interpreted as now in current logic at line 771.

@nhorman
Copy link
Contributor

nhorman commented Oct 27, 2025

@npajkovsky given that this change introduces a population of the X509_STORE that we are testing (i.e. with this change we're actually iterating over lots of certificates. vs no certificates), I would expect some level of run time increase (though a 4x slowdown is odd). Weather or not we should set the default NUM_KEYS value to 16 vs 0 is probably the question to answer here.

@esyr
Copy link
Member Author

esyr commented Oct 27, 2025

@npajkovsky given that this change introduces a population of the X509_STORE that we are testing (i.e. with this change we're actually iterating over lots of certificates. vs no certificates), I would expect some level of run time increase (though a 4x slowdown is odd). Weather or not we should set the default NUM_KEYS value to 16 vs 0 is probably the question to answer here.

I'm going to change the defaults so it still measures the time against an empty store, so the additional measurements can be added separately.

@npajkovsky
Copy link

@npajkovsky given that this change introduces a population of the X509_STORE that we are testing (i.e. with this change we're actually iterating over lots of certificates. vs no certificates), I would expect some level of run time increase (though a 4x slowdown is odd). Weather or not we should set the default NUM_KEYS value to 16 vs 0 is probably the question to answer here.

Number of certificates in the store should not matter much. X509_STORE is using hastable which maps certificate's subject to STACK_OF(X509_OBJECTS). Hence, the only way how to iterate over certificates is to have plenty of certificates with the same subject. I don't think we have this case right now.

@npajkovsky
Copy link

npajkovsky commented Oct 27, 2025

The difference between performance is because the original code didn't find the certificate, while @esyr code finds it. That triggers a bit of different code paths which we can measure.

do_x509storeissuer should really be named do_x509storeissuer_cache_miss. Your implementation should be a cache hit.

@npajkovsky
Copy link

npajkovsky commented Oct 29, 2025

Also, there are two leaks.

diff --git a/source/x509storeissuer.c b/source/x509storeissuer.c
index 3acce441e2a2..6c737bf578c7 100644
--- a/source/x509storeissuer.c
+++ b/source/x509storeissuer.c
@@ -714,6 +714,8 @@ read_certsdir(char * const dir, X509_STORE * const store)
         cnt += read_cert(dir, e->d_name, store);
     }

+    closedir(d);
+
     return cnt;
 }
 #endif /* defined(_WIN32) */
@@ -941,6 +943,8 @@ report_result(int verbosity)
         }
         break;
     }
+
+    OPENSSL_free(times);
 }

@esyr esyr force-pushed the esyr/x509storeissuer-update branch 2 times, most recently from 0b96651 to 44e0c66 Compare November 5, 2025 15:12
@esyr esyr requested a review from Sashan November 5, 2025 15:12
Copy link
Contributor

@Sashan Sashan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

few finishing touches are still needed. thanks.

path);
}

if (pool_cnt != NULL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the ; should not be here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I wonder how it hasn't been caught, as it indeed should be called with pool_cnt == NULL if mode == MODE_R.

timeout_s = strtod(optarg, &endptr);

if (endptr == NULL || *endptr != '\0' || timeout_s < 0)
errx(EXIT_FAILURE, "incorrect timeout value: \"%s\"");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

there is %s in format string. shall we add ``...", timeout_s); here?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I wonder why hasn't it been caught by the compiler, __attribute__((format(printf))) and/or -Wmissing-format-attribute are missing.

printf(": avg: %9.3lf us, median: %9.3lf us"
", min: %9.3lf us @thread %3zu, max: %9.3lf us @thread %3zu"
", stddev: %9.3lf us (%8.4lf%%)"
", hits %9zu of %9zu (%8.4lf%%)"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the types are uint64_t the format string should %llu

 681                    ", hits %9llu of %9llu (%8.4lf%%)"
 682                    ", added certs: %llu\n",

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, the proper format specifier for uint64_t is "%s" PRIu64, however.

OSSL_TIME max_time;

int err = 0;
int error = 0;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I rememer correctly the change was prompted by thread affinity which is gone. so perhaps we can drop this changeset from this PR too.

@esyr
Copy link
Member Author

esyr commented Nov 7, 2025

hmm, this is actually a very good point. I think the whole statistics machinery deserves some explanation in comment.

I see the difference, but otherway round:

lifty$ ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.sashan/test/certs/ 8
10.218173
lifty$ cd ../build-main/
lifty$ ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.sashan/test/certs/ 8
25.938117

Interesting, does OpenBSD use vdso for gettimeofday() calls? Otherwise, while being apparently useless for MacOS, it might make sense to reduce the rate of ossl_time_now() calls, after all. May I ask you to try to use different values other than 0x3f in that old version ([1], I guess?) to see if it has an impact for you?

[1] https://github.com/openssl/perftools/commits/ccbb791

@Sashan
Copy link
Contributor

Sashan commented Nov 7, 2025

Interesting, does OpenBSD use vdso for gettimeofday() calls? Otherwise, while being apparently useless for MacOS, it

If I understand vdso description right then OpenBSD uses very similar way to implement gettime*() functions. The magic happens in libc on OpenBSD details can be found in this email thread

might make sense to reduce the rate of ossl_time_now() calls, after all. May I ask you to try to use different values other than 0x3f in that old version ([1], I guess?) to see if it has an impact for you?

[1] https://github.com/openssl/perftools/commits/ccbb791

this what I get for the main branch s found on github:

lifty$ ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.addr_validation/test/certs/ 8  
25.113781
lifty$ time ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.addr_validation/test/certs/ 8 
24.919960
    0m05.05s real     0m04.65s user     0m34.88s system

This is what I get for the old branch with 0x3f value

lifty$ time ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.addr_validation/test/certs/ 8 
8.745155
    0m06.17s real     0m38.15s user     0m01.29s system

this what I get for 0x3ff

lifty$ time ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.addr_validation/test/certs/ 8 
8.749818
    0m06.05s real     0m37.40s user     0m01.51s system

this result is with 0x3ffff

lifty$ time ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.addr_validation/test/certs/ 8 
8.428343
    0m07.04s real     0m42.01s user     0m00.92s system

this is with value 0

lifty$ time ./x509storeissuer -t /home/sashan/work.openssl/QUIC/openssl.addr_validation/test/certs/ 8 
8.952559
    0m06.17s real     0m38.21s user     0m01.05s system

@esyr
Copy link
Member Author

esyr commented Nov 7, 2025

Well, looking at these, I'd say it's almost definitely the case that ossl_time_now() calls eat significant part of the run time (over 70%, judging by the ratio between user and system time and changes in the measured time) and taint the measurements.

esyr added 2 commits November 10, 2025 15:48
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
@esyr esyr force-pushed the esyr/x509storeissuer-update branch from dee1a75 to 76a9d0a Compare November 10, 2025 14:54
@esyr esyr requested a review from Sashan November 10, 2025 14:55
@esyr esyr force-pushed the esyr/x509storeissuer-update branch from 76a9d0a to 61c0d21 Compare November 10, 2025 14:58
esyr added 20 commits November 10, 2025 17:31
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
… function

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
…ration

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
…mmering the common counts array's cache line

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
…nt and report successful calls

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
…e store

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
…bosity level is DEBUG_STATS or higher

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
…the run

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
…t run matrix

Signed-off-by: Eugene Syromiatnikov <esyr@openssl.org>
@esyr esyr force-pushed the esyr/x509storeissuer-update branch from 61c0d21 to 45e4a5f Compare November 10, 2025 16:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add a new/extend x509 perf test

4 participants