benchmark.c 8.2 KB

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