get rid of SOCKTYPE and FDTYPE
[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, \
89                                                                      strlen ( \
90                                                                        s))); \
91     GNUNET_free (s); \
92 } while (0)
93
94   WRITE_BENCHMARK_OP (ecc_ecdh);
95   WRITE_BENCHMARK_OP (ecdh_eddsa);
96   WRITE_BENCHMARK_OP (ecdhe_key_create);
97   WRITE_BENCHMARK_OP (ecdhe_key_get_public);
98   WRITE_BENCHMARK_OP (ecdsa_ecdh);
99   WRITE_BENCHMARK_OP (ecdsa_key_create);
100   WRITE_BENCHMARK_OP (ecdsa_key_get_public);
101   WRITE_BENCHMARK_OP (ecdsa_sign);
102   WRITE_BENCHMARK_OP (ecdsa_verify);
103   WRITE_BENCHMARK_OP (eddsa_ecdh);
104   WRITE_BENCHMARK_OP (eddsa_key_create);
105   WRITE_BENCHMARK_OP (eddsa_key_get_public);
106   WRITE_BENCHMARK_OP (eddsa_sign);
107   WRITE_BENCHMARK_OP (eddsa_verify);
108   WRITE_BENCHMARK_OP (hash);
109   WRITE_BENCHMARK_OP (hash_context_finish);
110   WRITE_BENCHMARK_OP (hash_context_read);
111   WRITE_BENCHMARK_OP (hash_context_start);
112   WRITE_BENCHMARK_OP (hkdf);
113   WRITE_BENCHMARK_OP (rsa_blind);
114   WRITE_BENCHMARK_OP (rsa_private_key_create);
115   WRITE_BENCHMARK_OP (rsa_private_key_get_public);
116   WRITE_BENCHMARK_OP (rsa_sign_blinded);
117   WRITE_BENCHMARK_OP (rsa_unblind);
118   WRITE_BENCHMARK_OP (rsa_verify);
119
120 #undef WRITE_BENCHMARK_OP
121
122   GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh));
123
124   GNUNET_asprintf (&s, "%s/gnunet-benchmark-urls-%s-%llu-%llu.txt",
125                    benchmark_dir,
126                    (pid == tid) ? "main" : "thread",
127                    (unsigned long long) pid,
128                    (unsigned long long) tid);
129
130   fh = GNUNET_DISK_file_open (s,
131                               (GNUNET_DISK_OPEN_WRITE
132                                | GNUNET_DISK_OPEN_TRUNCATE
133                                | GNUNET_DISK_OPEN_CREATE),
134                               (GNUNET_DISK_PERM_USER_READ
135                                | GNUNET_DISK_PERM_USER_WRITE));
136   GNUNET_assert (NULL != fh);
137   GNUNET_free (s);
138
139   for (unsigned int i = 0; i < bd->urd_len; i++)
140   {
141     struct UrlRequestData *urd = &bd->urd[i];
142     GNUNET_asprintf (&s,
143                      "url %s status %u count %llu time_us %llu time_us_max %llu bytes_sent %llu bytes_received %llu\n",
144                      urd->request_url,
145                      urd->status,
146                      (unsigned long long) urd->count,
147                      (unsigned long long) urd->time.rel_value_us,
148                      (unsigned long long) urd->time_max.rel_value_us,
149                      (unsigned long long) urd->bytes_sent,
150                      (unsigned long long) urd->bytes_received);
151     GNUNET_assert (GNUNET_SYSERR != GNUNET_DISK_file_write_blocking (fh, s,
152                                                                      strlen (
153                                                                        s)));
154     GNUNET_free (s);
155   }
156
157   GNUNET_assert (GNUNET_OK == GNUNET_DISK_file_close (fh));
158 }
159
160
161 /**
162  * Called when the main thread exits and benchmark data for it was created.
163  */
164 static void
165 main_thread_destructor ()
166 {
167   struct BenchmarkData *bd;
168
169   bd = pthread_getspecific (key);
170   if (NULL != bd)
171     write_benchmark_data (bd);
172 }
173
174
175 /**
176  * Called when a thread exits and benchmark data for it was created.
177  *
178  * @param cls closure
179  */
180 static void
181 thread_destructor (void *cls)
182 {
183   struct BenchmarkData *bd = cls;
184
185   // main thread will be handled by atexit
186   if (getpid () == (pid_t) syscall (SYS_gettid))
187     return;
188
189   GNUNET_assert (NULL != bd);
190   write_benchmark_data (bd);
191 }
192
193
194 /**
195  * Initialize the thread-local variable key for benchmark data.
196  */
197 static void
198 make_key ()
199 {
200   (void) pthread_key_create (&key, &thread_destructor);
201 }
202
203
204 /**
205  * Acquire the benchmark data for the current thread, allocate if necessary.
206  * Installs handler to collect the benchmark data on thread termination.
207  *
208  * @return benchmark data for the current thread
209  */
210 struct BenchmarkData *
211 get_benchmark_data (void)
212 {
213   struct BenchmarkData *bd;
214
215   (void) pthread_once (&key_once, &make_key);
216
217   if (NULL == (bd = pthread_getspecific (key)))
218   {
219     bd = GNUNET_new (struct BenchmarkData);
220     (void) pthread_setspecific (key, bd);
221     if (getpid () == (pid_t) syscall (SYS_gettid))
222     {
223       // We're the main thread!
224       atexit (main_thread_destructor);
225     }
226   }
227   return bd;
228 }
229
230
231 /**
232  * Get benchmark data for a URL.  If the URL is too long, it's truncated
233  * before looking up the correspoding benchmark data.
234  *
235  * Statistics are bucketed by URL and status code.
236  *
237  * @param url url to get request data for
238  * @param status http status code
239  */
240 struct UrlRequestData *
241 get_url_benchmark_data (char *url, unsigned int status)
242 {
243   char trunc[MAX_BENCHMARK_URL_LEN];
244   struct BenchmarkData *bd;
245
246   if (NULL == url)
247   {
248     /* Should not happen unless curl barfs */
249     GNUNET_break (0);
250     url = "<empty>";
251   }
252
253   memcpy (trunc, url, MAX_BENCHMARK_URL_LEN);
254   trunc[MAX_BENCHMARK_URL_LEN - 1] = 0;
255
256   /* We're not interested in what's after the query string */
257   for (size_t i = 0; i < strlen (trunc); i++)
258   {
259     if (trunc[i] == '?')
260     {
261       trunc[i] = 0;
262       break;
263     }
264   }
265
266   bd = get_benchmark_data ();
267
268   GNUNET_assert (bd->urd_len <= bd->urd_capacity);
269
270   for (unsigned int i = 0; i < bd->urd_len; i++)
271   {
272     if ((0 == strcmp (trunc, bd->urd[i].request_url)) &&
273         (bd->urd[i].status == status))
274       return &bd->urd[i];
275   }
276
277   {
278     struct UrlRequestData urd = { 0 };
279
280     memcpy (&urd.request_url, trunc, MAX_BENCHMARK_URL_LEN);
281     urd.status = status;
282
283     if (bd->urd_len == bd->urd_capacity)
284     {
285       bd->urd_capacity = 2 * (bd->urd_capacity + 1);
286       bd->urd = GNUNET_realloc (bd->urd, bd->urd_capacity * sizeof(struct
287                                                                    UrlRequestData));
288     }
289
290     bd->urd[bd->urd_len++] = urd;
291     return &bd->urd[bd->urd_len - 1];
292   }
293 }