plibc: win32 related, socket
[oweals/gnunet.git] / src / util / benchmark.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2018 GNUnet e.V.
4
5      GNUnet is free software: you can redistribute it and/or modify it
6      under the terms of the GNU Affero General Public License as published
7      by the Free Software Foundation, either version 3 of the License,
8      or (at your option) any later version.
9
10      GNUnet is distributed in the hope that it will be useful, but
11      WITHOUT ANY WARRANTY; without even the implied warranty of
12      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13      Affero General Public License for more details.
14
15      You should have received a copy of the GNU Affero General Public License
16      along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
18      SPDX-License-Identifier: AGPL3.0-or-later
19  */
20
21 /**
22  * @file util/benchmark.c
23  * @brief benchmarking for various operations
24  * @author Florian Dold <flo@dold.me>
25  */
26
27 #include "platform.h"
28 #include "gnunet_util_lib.h"
29 #include "benchmark.h"
30 #include <pthread.h>
31 #include <sys/syscall.h>
32
33 /**
34  * Thread-local storage key for the benchmark data.
35  */
36 static pthread_key_t key;
37
38 /**
39  * One-time initialization marker for key.
40  */
41 static pthread_once_t key_once = PTHREAD_ONCE_INIT;
42
43
44 /**
45  * Write benchmark data to a file.
46  *
47  * @param bd the benchmark data
48  */
49 static void
50 write_benchmark_data(struct BenchmarkData *bd)
51 {
52   struct GNUNET_DISK_FileHandle *fh;
53   pid_t pid = getpid();
54   pid_t tid = syscall(SYS_gettid);
55   char *benchmark_dir;
56   char *s;
57
58   benchmark_dir = getenv("GNUNET_BENCHMARK_DIR");
59
60   if (NULL == benchmark_dir)
61     return;
62
63   if (GNUNET_OK != GNUNET_DISK_directory_create(benchmark_dir))
64     {
65       GNUNET_break(0);
66       return;
67     }
68
69   GNUNET_asprintf(&s, "%s/gnunet-benchmark-ops-%s-%llu-%llu.txt",
70                   benchmark_dir,
71                   (pid == tid) ? "main" : "thread",
72                   (unsigned long long)pid,
73                   (unsigned long long)tid);
74
75   fh = GNUNET_DISK_file_open(s,
76                              (GNUNET_DISK_OPEN_WRITE |
77                               GNUNET_DISK_OPEN_TRUNCATE |
78                               GNUNET_DISK_OPEN_CREATE),
79                              (GNUNET_DISK_PERM_USER_READ |
80                               GNUNET_DISK_PERM_USER_WRITE));
81   GNUNET_assert(NULL != fh);
82   GNUNET_free(s);
83
84 #define WRITE_BENCHMARK_OP(opname) do { \
85       GNUNET_asprintf(&s, "op " #opname " count %llu time_us %llu\n", \
86                       (unsigned long long)bd->opname ## _count, \
87                       (unsigned long long)bd->opname ## _time.rel_value_us); \
88       GNUNET_assert(GNUNET_SYSERR != GNUNET_DISK_file_write_blocking(fh, s, strlen(s))); \
89       GNUNET_free(s); \
90   } while (0)
91
92   WRITE_BENCHMARK_OP(ecc_ecdh);
93   WRITE_BENCHMARK_OP(ecdh_eddsa);
94   WRITE_BENCHMARK_OP(ecdhe_key_create);
95   WRITE_BENCHMARK_OP(ecdhe_key_get_public);
96   WRITE_BENCHMARK_OP(ecdsa_ecdh);
97   WRITE_BENCHMARK_OP(ecdsa_key_create);
98   WRITE_BENCHMARK_OP(ecdsa_key_get_public);
99   WRITE_BENCHMARK_OP(ecdsa_sign);
100   WRITE_BENCHMARK_OP(ecdsa_verify);
101   WRITE_BENCHMARK_OP(eddsa_ecdh);
102   WRITE_BENCHMARK_OP(eddsa_key_create);
103   WRITE_BENCHMARK_OP(eddsa_key_get_public);
104   WRITE_BENCHMARK_OP(eddsa_sign);
105   WRITE_BENCHMARK_OP(eddsa_verify);
106   WRITE_BENCHMARK_OP(hash);
107   WRITE_BENCHMARK_OP(hash_context_finish);
108   WRITE_BENCHMARK_OP(hash_context_read);
109   WRITE_BENCHMARK_OP(hash_context_start);
110   WRITE_BENCHMARK_OP(hkdf);
111   WRITE_BENCHMARK_OP(rsa_blind);
112   WRITE_BENCHMARK_OP(rsa_private_key_create);
113   WRITE_BENCHMARK_OP(rsa_private_key_get_public);
114   WRITE_BENCHMARK_OP(rsa_sign_blinded);
115   WRITE_BENCHMARK_OP(rsa_unblind);
116   WRITE_BENCHMARK_OP(rsa_verify);
117
118 #undef WRITE_BENCHMARK_OP
119
120   GNUNET_assert(GNUNET_OK == GNUNET_DISK_file_close(fh));
121
122   GNUNET_asprintf(&s, "%s/gnunet-benchmark-urls-%s-%llu-%llu.txt",
123                   benchmark_dir,
124                   (pid == tid) ? "main" : "thread",
125                   (unsigned long long)pid,
126                   (unsigned long long)tid);
127
128   fh = GNUNET_DISK_file_open(s,
129                              (GNUNET_DISK_OPEN_WRITE |
130                               GNUNET_DISK_OPEN_TRUNCATE |
131                               GNUNET_DISK_OPEN_CREATE),
132                              (GNUNET_DISK_PERM_USER_READ |
133                               GNUNET_DISK_PERM_USER_WRITE));
134   GNUNET_assert(NULL != fh);
135   GNUNET_free(s);
136
137   for (unsigned int i = 0; i < bd->urd_len; i++)
138     {
139       struct UrlRequestData *urd = &bd->urd[i];
140       GNUNET_asprintf(&s, "url %s status %u count %llu time_us %llu time_us_max %llu bytes_sent %llu bytes_received %llu\n",
141                       urd->request_url,
142                       urd->status,
143                       (unsigned long long)urd->count,
144                       (unsigned long long)urd->time.rel_value_us,
145                       (unsigned long long)urd->time_max.rel_value_us,
146                       (unsigned long long)urd->bytes_sent,
147                       (unsigned long long)urd->bytes_received);
148       GNUNET_assert(GNUNET_SYSERR != GNUNET_DISK_file_write_blocking(fh, s, strlen(s)));
149       GNUNET_free(s);
150     }
151
152   GNUNET_assert(GNUNET_OK == GNUNET_DISK_file_close(fh));
153 }
154
155
156 /**
157  * Called when the main thread exits and benchmark data for it was created.
158  */
159 static void
160 main_thread_destructor()
161 {
162   struct BenchmarkData *bd;
163
164   bd = pthread_getspecific(key);
165   if (NULL != bd)
166     write_benchmark_data(bd);
167 }
168
169
170 /**
171  * Called when a thread exits and benchmark data for it was created.
172  *
173  * @param cls closure
174  */
175 static void
176 thread_destructor(void *cls)
177 {
178   struct BenchmarkData *bd = cls;
179
180   // main thread will be handled by atexit
181   if (getpid() == (pid_t)syscall(SYS_gettid))
182     return;
183
184   GNUNET_assert(NULL != bd);
185   write_benchmark_data(bd);
186 }
187
188
189 /**
190  * Initialize the thread-local variable key for benchmark data.
191  */
192 static void
193 make_key()
194 {
195   (void)pthread_key_create(&key, &thread_destructor);
196 }
197
198
199 /**
200  * Acquire the benchmark data for the current thread, allocate if necessary.
201  * Installs handler to collect the benchmark data on thread termination.
202  *
203  * @return benchmark data for the current thread
204  */
205 struct BenchmarkData *
206 get_benchmark_data(void)
207 {
208   struct BenchmarkData *bd;
209
210   (void)pthread_once(&key_once, &make_key);
211
212   if (NULL == (bd = pthread_getspecific(key)))
213     {
214       bd = GNUNET_new(struct BenchmarkData);
215       (void)pthread_setspecific(key, bd);
216       if (getpid() == (pid_t)syscall(SYS_gettid))
217         {
218           // We're the main thread!
219           atexit(main_thread_destructor);
220         }
221     }
222   return bd;
223 }
224
225
226 /**
227  * Get benchmark data for a URL.  If the URL is too long, it's truncated
228  * before looking up the correspoding benchmark data.
229  *
230  * Statistics are bucketed by URL and status code.
231  *
232  * @param url url to get request data for
233  * @param status http status code
234  */
235 struct UrlRequestData *
236 get_url_benchmark_data(char *url, unsigned int status)
237 {
238   char trunc[MAX_BENCHMARK_URL_LEN];
239   struct BenchmarkData *bd;
240
241   if (NULL == url)
242     {
243       /* Should not happen unless curl barfs */
244       GNUNET_break(0);
245       url = "<empty>";
246     }
247
248   memcpy(trunc, url, MAX_BENCHMARK_URL_LEN);
249   trunc[MAX_BENCHMARK_URL_LEN - 1] = 0;
250
251   /* We're not interested in what's after the query string */
252   for (size_t i = 0; i < strlen(trunc); i++)
253     {
254       if (trunc[i] == '?')
255         {
256           trunc[i] = 0;
257           break;
258         }
259     }
260
261   bd = get_benchmark_data();
262
263   GNUNET_assert(bd->urd_len <= bd->urd_capacity);
264
265   for (unsigned int i = 0; i < bd->urd_len; i++)
266     {
267       if ((0 == strcmp(trunc, bd->urd[i].request_url)) &&
268           (bd->urd[i].status == status))
269         return &bd->urd[i];
270     }
271
272   {
273     struct UrlRequestData urd = { 0 };
274
275     memcpy(&urd.request_url, trunc, MAX_BENCHMARK_URL_LEN);
276     urd.status = status;
277
278     if (bd->urd_len == bd->urd_capacity)
279       {
280         bd->urd_capacity = 2 * (bd->urd_capacity + 1);
281         bd->urd = GNUNET_realloc(bd->urd, bd->urd_capacity * sizeof(struct UrlRequestData));
282       }
283
284     bd->urd[bd->urd_len++] = urd;
285     return &bd->urd[bd->urd_len - 1];
286   }
287 }