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