aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEvgeny Grin (Karlson2k) <k2k@narod.ru>2023-07-03 11:25:15 +0300
committerEvgeny Grin (Karlson2k) <k2k@narod.ru>2023-07-03 18:31:47 +0300
commita5cf04b5d0cbaa4f51e54763e4a8e72c02751e48 (patch)
tree0b37129c2eebdccc34a5f434922af456c63e5af2
parent08fe527f036938911d94f528b9261aec8867e428 (diff)
downloadlibmicrohttpd-a5cf04b5d0cbaa4f51e54763e4a8e72c02751e48.tar.gz
libmicrohttpd-a5cf04b5d0cbaa4f51e54763e4a8e72c02751e48.zip
Added new tool perf_replies
Added new directory "tools" and one new tool in this directory.
-rw-r--r--configure.ac38
-rw-r--r--src/Makefile.am3
-rw-r--r--src/tools/.gitignore1
-rw-r--r--src/tools/Makefile.am41
-rw-r--r--src/tools/perf_replies.c1027
5 files changed, 1110 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac
index 8b7489c3..5d9ac157 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1745,6 +1745,12 @@ AC_ARG_ENABLE([[examples]],
1745test "x$enable_examples" = "xno" || enable_examples=yes 1745test "x$enable_examples" = "xno" || enable_examples=yes
1746AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$enable_examples" = "xyes"]) 1746AM_CONDITIONAL([BUILD_EXAMPLES], [test "x$enable_examples" = "xyes"])
1747 1747
1748AC_ARG_ENABLE([[tools]],
1749 [AS_HELP_STRING([[--disable-tools]], [do not build testing and demo tools])], ,
1750 [enable_tools=yes])
1751test "x$enable_tools" = "xyes" || enable_tools=no
1752AM_CONDITIONAL([BUILD_TOOLS], [test "x$enable_tools" = "xyes"])
1753
1748AC_ARG_ENABLE([[heavy-tests]], 1754AC_ARG_ENABLE([[heavy-tests]],
1749 [AS_HELP_STRING([[--enable-heavy-tests[=SCOPE]]], [use SCOPE of heavy tests in test-suite. WARNING:] 1755 [AS_HELP_STRING([[--enable-heavy-tests[=SCOPE]]], [use SCOPE of heavy tests in test-suite. WARNING:]
1750 [a dedicated host with minimal number of background processes and no network] 1756 [a dedicated host with minimal number of background processes and no network]
@@ -3895,6 +3901,34 @@ AS_VAR_IF([mhd_cv_ipv6_for_testing],["yes"],
3895 [AC_DEFINE([[USE_IPV6_TESTING]], [[1]], [Define to 1 if your kernel supports IPv6 and IPv6 is enabled and useful for testing.])] 3901 [AC_DEFINE([[USE_IPV6_TESTING]], [[1]], [Define to 1 if your kernel supports IPv6 and IPv6 is enabled and useful for testing.])]
3896) 3902)
3897 3903
3904AS_VAR_IF([enable_tools],["yes"],
3905 [
3906 MHD_CHECK_FUNC([pclose],[[#include <stdio.h>]],
3907 [[
3908 i][f (0 == pclose(NULL)) return 2;
3909 ]],
3910 [
3911 MHD_CHECK_FUNC([popen],[[#include <stdio.h>]],
3912 [[
3913 FILE *cmd_out = popen ("cat conftest.c", "r");
3914 i][f (NULL == cmd_out) return 2;
3915 i][f (0 != pclose(cmd_out)) return 3;
3916 ]]
3917 )
3918 ]
3919 )
3920 MATH_LIB=''
3921 MHD_FIND_LIB([sqrt],[#include <math.h>],
3922 [[
3923 double res = sqrt ((double) 4);
3924 i][f (((double) 2) != res) return 2;
3925 ]],[m],
3926 [AC_DEFINE([[HAVE_SQRT_FUNC]],[[1]],[Define to 1 i][f 'sqrt()' function is available.])
3927 ],[],[MATH_LIB]
3928 )
3929 AC_SUBST([MATH_LIB])
3930 ]
3931)
3898 3932
3899# Check for fork() and waitpid(). They are used for tests. 3933# Check for fork() and waitpid(). They are used for tests.
3900AC_MSG_CHECKING([[for fork()]]) 3934AC_MSG_CHECKING([[for fork()]])
@@ -3960,6 +3994,8 @@ AC_MSG_RESULT($use_gcov)
3960AM_CONDITIONAL([USE_COVERAGE], [test "x$use_gcov" = "xyes"]) 3994AM_CONDITIONAL([USE_COVERAGE], [test "x$use_gcov" = "xyes"])
3961 3995
3962AX_COUNT_CPUS 3996AX_COUNT_CPUS
3997AC_SUBST([MHD_REAL_CPU_COUNT],[${CPU_COUNT}])
3998AM_SUBST_NOTMAKE([MHD_REAL_CPU_COUNT])
3963AC_MSG_CHECKING([for number of CPU cores to use in tests]) 3999AC_MSG_CHECKING([for number of CPU cores to use in tests])
3964AS_VAR_IF([use_heavy_tests], ["yes"], 4000AS_VAR_IF([use_heavy_tests], ["yes"],
3965 [ 4001 [
@@ -5002,6 +5038,7 @@ src/lib/Makefile
5002src/microhttpd/Makefile 5038src/microhttpd/Makefile
5003src/microhttpd_ws/Makefile 5039src/microhttpd_ws/Makefile
5004src/examples/Makefile 5040src/examples/Makefile
5041src/tools/Makefile
5005src/testcurl/Makefile 5042src/testcurl/Makefile
5006src/testcurl/https/Makefile 5043src/testcurl/https/Makefile
5007src/testzzuf/Makefile]) 5044src/testzzuf/Makefile])
@@ -5077,6 +5114,7 @@ AC_MSG_NOTICE([GNU libmicrohttpd ${PACKAGE_VERSION} Configuration Summary:
5077 Use sanitizers: ${enabled_sanitizers:=no} 5114 Use sanitizers: ${enabled_sanitizers:=no}
5078 Build docs: ${enable_doc} 5115 Build docs: ${enable_doc}
5079 Build examples: ${enable_examples} 5116 Build examples: ${enable_examples}
5117 Build tools: ${enable_examples}
5080 Build static lib: ${enable_static} 5118 Build static lib: ${enable_static}
5081 Build shared lib: ${enable_shared} 5119 Build shared lib: ${enable_shared}
5082 Test with libcurl: ${MSG_CURL} 5120 Test with libcurl: ${MSG_CURL}
diff --git a/src/Makefile.am b/src/Makefile.am
index 85a10510..4673d0d8 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -18,6 +18,9 @@ if BUILD_EXAMPLES
18SUBDIRS += examples 18SUBDIRS += examples
19endif 19endif
20 20
21if BUILD_TOOLS
22SUBDIRS += tools
23endif
21 24
22EXTRA_DIST = \ 25EXTRA_DIST = \
23 datadir/cert-and-key.pem \ 26 datadir/cert-and-key.pem \
diff --git a/src/tools/.gitignore b/src/tools/.gitignore
new file mode 100644
index 00000000..8941024d
--- /dev/null
+++ b/src/tools/.gitignore
@@ -0,0 +1 @@
/perf_replies
diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am
new file mode 100644
index 00000000..d9461cd1
--- /dev/null
+++ b/src/tools/Makefile.am
@@ -0,0 +1,41 @@
1# This Makefile.am is in the public domain
2SUBDIRS = .
3
4AM_CPPFLAGS = \
5 -I$(top_srcdir)/src/include \
6 -DMHD_REAL_CPU_COUNT=@MHD_REAL_CPU_COUNT@ \
7 -DMHD_CPU_COUNT=$(CPU_COUNT) \
8 $(CPPFLAGS_ac) \
9 -DDATA_DIR=\"$(top_srcdir)/src/datadir/\"
10
11AM_CFLAGS = $(CFLAGS_ac) @LIBGCRYPT_CFLAGS@
12
13AM_LDFLAGS = $(LDFLAGS_ac)
14
15AM_TESTS_ENVIRONMENT = $(TESTS_ENVIRONMENT_ac)
16
17if USE_COVERAGE
18 AM_CFLAGS += --coverage
19endif
20
21LDADD = \
22 $(top_builddir)/src/microhttpd/libmicrohttpd.la
23
24$(top_builddir)/src/microhttpd/libmicrohttpd.la: $(top_builddir)/src/microhttpd/Makefile
25 @echo ' cd $(top_builddir)/src/microhttpd && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd.la'; \
26 $(am__cd) $(top_builddir)/src/microhttpd && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd.la
27
28
29# Tools
30noinst_PROGRAMS =
31
32if USE_THREADS
33noinst_PROGRAMS += \
34 perf_replies
35endif
36
37
38perf_replies_SOURCES = \
39 perf_replies.c
40perf_replies_LDADD = \
41 $(LDADD) $(MATH_LIB)
diff --git a/src/tools/perf_replies.c b/src/tools/perf_replies.c
new file mode 100644
index 00000000..fdfd6086
--- /dev/null
+++ b/src/tools/perf_replies.c
@@ -0,0 +1,1027 @@
1/*
2 This file is part of GNU libmicrohttpd
3 Copyright (C) 2023 Evgeny Grin (Karlson2k)
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are
7 met:
8
9 1. Redistributions of source code must retain the above copyright notice,
10 this list of conditions and the following disclaimer.
11
12 2. Redistributions in binary form must reproduce the above copyright notice,
13 this list of conditions and the following disclaimer in the documentation
14 and/or other materials provided with the distribution.
15
16 THIS SOFTWARE IS PROVIDED BY THE GNU LIBMICROHTTPD "AS IS" AND ANY EXPRESS
17 OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
19 NO EVENT SHALL THE LIBMICROHTTPD OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
20 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26*/
27
28/**
29 * @file examples/perf_replies.c
30 * @brief Implementation of HTTP server optimised for fast replies
31 * based on MHD.
32 * @author Karlson2k (Evgeny Grin)
33 */
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <string.h>
38#include <stdint.h>
39#include "mhd_options.h"
40#include "microhttpd.h"
41
42#if defined(MHD_REAL_CPU_COUNT)
43#if MHD_REAL_CPU_COUNT == 0
44#undef MHD_REAL_CPU_COUNT
45#endif /* MHD_REAL_CPU_COUNT == 0 */
46#endif /* MHD_REAL_CPU_COUNT */
47
48#define PERF_RPL_ERR_CODE_BAD_PARAM 65
49
50#ifndef MHD_STATICSTR_LEN_
51/**
52 * Determine length of static string / macro strings at compile time.
53 */
54#define MHD_STATICSTR_LEN_(macro) (sizeof(macro) / sizeof(char) - 1)
55#endif /* ! MHD_STATICSTR_LEN_ */
56
57/* Static constants */
58static const char *const tool_copyright =
59 "Copyright (C) 2023 Evgeny Grin (Karlson2k)";
60
61/* Package or build specific string, like
62 "Debian 1.2.3-4" or "RevX, built by MSYS2" */
63static const char *const build_revision = ""
64#ifdef MHD_BUILD_REV_STR
65 MHD_BUILD_REV_STR
66#endif /* MHD_BUILD_REV_STR */
67;
68
69#define PERF_REPL_PORT_FALLBACK 48080
70
71/* Dynamic variables */
72static char self_name[500] = "perf_replies";
73static uint16_t mhd_port = 0;
74static struct MHD_Response **resps = NULL;
75static unsigned int num_resps = 0;
76
77static void
78set_self_name (int argc, char *const *argv)
79{
80 if ((argc >= 1) && (NULL != argv[0]))
81 {
82 const char *last_dir_sep;
83 last_dir_sep = strrchr (argv[0], '/');
84#ifdef _WIN32
85 if (1)
86 {
87 const char *last_w32_dir_sep;
88 last_w32_dir_sep = strrchr (argv[0], '\\');
89 if ((NULL == last_dir_sep) ||
90 ((NULL != last_w32_dir_sep) && (last_w32_dir_sep > last_dir_sep)))
91 last_dir_sep = last_w32_dir_sep;
92 }
93#endif /* _WIN32 */
94 if (NULL != last_dir_sep)
95 {
96 size_t name_len;
97 name_len = strlen (last_dir_sep + 1);
98 if ((0 != name_len) && ((sizeof(self_name) / sizeof(char)) > name_len))
99 {
100 strcpy (self_name, last_dir_sep + 1);
101 return;
102 }
103 }
104 }
105 /* Set default name */
106 strcpy (self_name, "perf_replies");
107 return;
108}
109
110
111/**
112 * Convert decimal string to unsigned int.
113 * Function stops at the end of the string or on first non-digit character.
114 * @param str the string to convert
115 * @param[out] value the pointer to put the result
116 * @return return the number of digits converted or
117 * zero if no digits found or result would overflow the output
118 * variable (the output set to UINT_MAX in this case).
119 */
120static size_t
121str_to_uint (const char *str, unsigned int *value)
122{
123 size_t i;
124 unsigned int v = 0;
125 *value = 0;
126
127 for (i = 0; 0 != str[i]; ++i)
128 {
129 const char chr = str[i];
130 unsigned int digit;
131 if (('0' > chr) || ('9' < chr))
132 break;
133 digit = (unsigned char) (chr - '0');
134 if ((((0U - 1) / 10) < v) || ((v * 10 + digit) < v))
135 {
136 /* Overflow */
137 *value = 0U - 1;
138 return 0;
139 }
140 v *= 10;
141 v += digit;
142 }
143 *value = v;
144 return i;
145}
146
147
148#if defined (HAVE_POPEN) && defined(HAVE_PCLOSE)
149/**
150 * Read the command output as a number and return the number.
151 * Only positive decimal numbers are supported
152 * @param cmd the command to run
153 * @return zero or positive number read if success,
154 * negative number if any error occurs
155 */
156static int
157get_cmd_out_as_number (const char *cmd)
158{
159 FILE *cmd_out;
160 char buf[255];
161 int ret;
162 size_t len;
163
164 cmd_out = popen (cmd, "r"
165#ifdef _WIN32
166 "t"
167#endif /* _WIN32 */
168 );
169 if (NULL == cmd_out)
170 return -1;
171 ret = -1;
172 if (buf != fgets (buf, sizeof(buf), cmd_out))
173 len = 0;
174 else
175 len = strlen (buf);
176 if ((0 != len) && (sizeof(buf) > (len + 2)) && ! ferror (cmd_out))
177 {
178 size_t digits_found;
179 unsigned int out_value;
180 digits_found = str_to_uint (buf, &out_value);
181 if (0 != digits_found)
182 {
183 if ((0 == buf[digits_found])
184#ifdef _WIN32
185 || ('\r' == buf[digits_found])
186#endif /* _WIN32 */
187 || ('\n' == buf[digits_found]))
188 {
189 ret = (int) out_value; /* Possible negative cast result is interpreted as an error */
190 }
191 }
192 }
193 if (0 != pclose (cmd_out))
194 ret = -1;
195 return ret;
196}
197
198
199#else /* ! HAVE_POPEN || ! HAVE_PCLOSE */
200#define read_cmd_out_as_number(ignore) (-1)
201#endif /* ! HAVE_POPEN || ! HAVE_PCLOSE */
202
203static unsigned int
204detect_cpu_core_count (void)
205{
206 int sys_cpu_count = -1;
207#if ! defined(_WIN32) || defined(__CYGWIN__)
208 sys_cpu_count = get_cmd_out_as_number ("nproc 2>/dev/null");
209#endif /* ! _WIN32) || __CYGWIN__ */
210#ifdef _WIN32
211 if (0 >= sys_cpu_count)
212 sys_cpu_count = get_cmd_out_as_number ("echo %NUMBER_OF_PROCESSORS%");
213#endif /* _WIN32 */
214 if (0 >= sys_cpu_count)
215 {
216 fprintf (stderr, "Failed to detect the number of available CPU cores.\n");
217#ifdef MHD_REAL_CPU_COUNT
218 fprintf (stderr, "Hardcoded number is used as a fallback.\n");
219 sys_cpu_count = MHD_REAL_CPU_COUNT;
220#endif
221 if (0 >= sys_cpu_count)
222 sys_cpu_count = 1;
223 printf ("Assuming %d CPU cores.\n", sys_cpu_count);
224 }
225 else
226 {
227 printf ("Detected %d CPU cores.\n", sys_cpu_count);
228 }
229 return (unsigned int) sys_cpu_count;
230}
231
232
233static unsigned int
234get_cpu_core_count (void)
235{
236 static unsigned int num_cpu_cores = 0;
237 if (0 == num_cpu_cores)
238 num_cpu_cores = detect_cpu_core_count ();
239 return num_cpu_cores;
240}
241
242
243static unsigned int num_threads = 0;
244
245static unsigned int
246get_num_threads (void)
247{
248 static const unsigned int max_threads = 32;
249 if (0 == num_threads)
250 {
251 num_threads = get_cpu_core_count () / 2;
252 if (0 == num_threads)
253 num_threads = 1;
254 else
255 printf ("Using half of all available CPU cores, assuming the other half "
256 "is used by client / requests generator.\n");
257 }
258 if (max_threads < num_threads)
259 {
260 printf ("Number of threads are limited to %u as more threads "
261 "are unlikely to improve the performance.\n", max_threads);
262 num_threads = max_threads;
263 }
264
265 return num_threads;
266}
267
268
269/**
270 * The result of short parameters processing
271 */
272enum PerfRepl_param_result
273{
274 PERF_RPL_PARAM_ERROR, /**< Error processing parameter */
275 PERF_RPL_PARAM_ONE_CHAR, /**< Processed exactly one character */
276 PERF_RPL_PARAM_FULL_STR, /**< Processed current parameter completely */
277 PERF_RPL_PARAM_STR_PLUS_NEXT /**< Current parameter completely and next parameter processed */
278};
279
280/**
281 * Extract parameter value
282 * @param param_name the name of the parameter
283 * @param param_tail the pointer to the character after parameter name in
284 * the parameter string
285 * @param next_param the pointer to the next parameter (if any) or NULL
286 * @param[out] param_value the pointer where to store resulting value
287 * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by
288 * this function
289 */
290static enum PerfRepl_param_result
291get_param_value (const char *param_name, const char *param_tail,
292 const char *next_param, unsigned int *param_value)
293{
294 const char *value_str;
295 size_t digits;
296 if (0 != param_tail[0])
297 {
298 if ('=' != param_tail[0])
299 value_str = param_tail;
300 else
301 value_str = param_tail + 1;
302 }
303 else
304 value_str = next_param;
305
306 if (NULL != value_str)
307 digits = str_to_uint (value_str, param_value);
308 else
309 digits = 0;
310
311 if ((0 == digits) || (0 != value_str[digits]))
312 {
313 fprintf (stderr, "Parameter '%s' is not followed by valid number.\n",
314 param_name);
315 return PERF_RPL_PARAM_ERROR;
316 }
317
318 if (0 != param_tail[0])
319 return PERF_RPL_PARAM_FULL_STR;
320
321 return PERF_RPL_PARAM_STR_PLUS_NEXT;
322}
323
324
325static void
326show_help (void)
327{
328 printf ("Usage: %s [OPTIONS] [PORT_NUMBER]\n", self_name);
329 printf ("Start MHD-based web-server optimised for fast replies.\n");
330 printf ("\n");
331 printf ("Threads options (mutually exclusive):\n");
332 printf (" -A, --all-cpus use all available CPU cores (for \n"
333 " testing with remote client)\n");
334 printf (" -t NUM, --threads=NUM use NUM threads\n");
335 printf ("\n");
336 printf ("Force polling function (mutually exclusive):\n");
337 printf (" --epoll use 'epoll' functionality\n");
338 printf (" --poll use poll() function\n");
339 printf (" --select use select() function\n");
340 printf ("\n");
341 printf ("Other options:\n");
342 printf (" --help display this help and exit\n");
343 printf (" -V, --version output version information and exit\n");
344 printf ("\n");
345 printf ("This tool is part of GNU libmicrohttpd suite.\n");
346 printf ("%s\n", tool_copyright);
347}
348
349
350struct PerfRepl_parameters
351{
352 unsigned int port;
353 int all_cpus;
354 unsigned int threads;
355 int epoll;
356 int poll;
357 int select;
358 int help;
359 int version;
360};
361
362static struct PerfRepl_parameters tool_params = {
363 0,
364 0,
365 0,
366 0,
367 0,
368 0,
369 0,
370 0
371};
372
373/**
374 * Process parameter '-t' or '--threads'
375 * @param param_name the name of the parameter as specified in command line
376 * @param param_tail the pointer to the character after parameter name in
377 * the parameter string or NULL for "long" parameters
378 * @param next_param the pointer to the next parameter (if any) or NULL
379 * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by
380 * this function
381 */
382static enum PerfRepl_param_result
383process_param__threads (const char *param_name, const char *param_tail,
384 const char *next_param)
385{
386 unsigned int param_value;
387 enum PerfRepl_param_result value_res;
388
389 if (tool_params.all_cpus)
390 {
391 fprintf (stderr, "Parameter '%s' cannot be used together "
392 "with '-A' or '--all-cpus'.\n", param_name);
393 return PERF_RPL_PARAM_ERROR;
394 }
395 value_res = get_param_value (param_name, param_tail, next_param,
396 &param_value);
397 if (PERF_RPL_PARAM_ERROR == value_res)
398 return value_res;
399
400 if (0 == param_value)
401 {
402 fprintf (stderr, "'0' is not valid value for parameter '%s'.\n",
403 param_name);
404 return PERF_RPL_PARAM_ERROR;
405 }
406 tool_params.threads = param_value;
407 return value_res;
408}
409
410
411static enum PerfRepl_param_result
412process_param__all_cpus (const char *param_name)
413{
414 if (0 != tool_params.threads)
415 {
416 fprintf (stderr, "Parameter '%s' cannot be used together "
417 "with '-t' or '--threads'.\n", param_name);
418 return PERF_RPL_PARAM_ERROR;
419 }
420 tool_params.all_cpus = ! 0;
421 return '-' == param_name[1] ?
422 PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR;
423}
424
425
426static enum PerfRepl_param_result
427process_param__help (const char *param_name)
428{
429 /* Use only one of help | version */
430 if (! tool_params.version)
431 tool_params.help = ! 0;
432 return '-' == param_name[1] ?
433 PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR;
434}
435
436
437static enum PerfRepl_param_result
438process_param__version (const char *param_name)
439{
440 /* Use only one of help | version */
441 if (! tool_params.help)
442 tool_params.version = ! 0;
443 return '-' == param_name[1] ?
444 PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR;
445}
446
447
448static enum PerfRepl_param_result
449process_param__epoll (const char *param_name)
450{
451 if (tool_params.poll)
452 {
453 fprintf (stderr, "Parameter '%s' cannot be used together "
454 "with '--poll'.\n", param_name);
455 return PERF_RPL_PARAM_ERROR;
456 }
457 if (tool_params.select)
458 {
459 fprintf (stderr, "Parameter '%s' cannot be used together "
460 "with '--select'.\n", param_name);
461 return PERF_RPL_PARAM_ERROR;
462 }
463 tool_params.epoll = ! 0;
464 return '-' == param_name[1] ?
465 PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR;
466}
467
468
469static enum PerfRepl_param_result
470process_param__poll (const char *param_name)
471{
472 if (tool_params.epoll)
473 {
474 fprintf (stderr, "Parameter '%s' cannot be used together "
475 "with '--epoll'.\n", param_name);
476 return PERF_RPL_PARAM_ERROR;
477 }
478 if (tool_params.select)
479 {
480 fprintf (stderr, "Parameter '%s' cannot be used together "
481 "with '--select'.\n", param_name);
482 return PERF_RPL_PARAM_ERROR;
483 }
484 tool_params.poll = ! 0;
485 return '-' == param_name[1] ?
486 PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR;
487}
488
489
490static enum PerfRepl_param_result
491process_param__select (const char *param_name)
492{
493 if (tool_params.epoll)
494 {
495 fprintf (stderr, "Parameter '%s' cannot be used together "
496 "with '--epoll'.\n", param_name);
497 return PERF_RPL_PARAM_ERROR;
498 }
499 if (tool_params.poll)
500 {
501 fprintf (stderr, "Parameter '%s' cannot be used together "
502 "with '--poll'.\n", param_name);
503 return PERF_RPL_PARAM_ERROR;
504 }
505 tool_params.select = ! 0;
506 return '-' == param_name[1] ?
507 PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR;
508}
509
510
511/**
512 * Process "short" (one character) parameter.
513 * @param param the pointer to character after "-" or after another valid
514 * parameter
515 * @param next_param the pointer to the next parameter (if any) or
516 * NULL if no next parameter
517 * @return enum value with result
518 */
519static enum PerfRepl_param_result
520process_short_param (const char *param, const char *next_param)
521{
522 const char param_chr = param[0];
523 if ('A' == param_chr)
524 return process_param__all_cpus ("-A");
525 else if ('t' == param_chr)
526 return process_param__threads ("-t", param + 1, next_param);
527 else if ('V' == param_chr)
528 return process_param__version ("-V");
529
530 fprintf (stderr, "Unrecognised parameter: -%c.\n", param_chr);
531 return PERF_RPL_PARAM_ERROR;
532}
533
534
535/**
536 * Process string of "short" (one character) parameters.
537 * @param params_str the pointer to first character after "-"
538 * @param next_param the pointer to the next parameter (if any) or
539 * NULL if no next parameter
540 * @return enum value with result
541 */
542static enum PerfRepl_param_result
543process_short_params_str (const char *params_str, const char *next_param)
544{
545 if (0 == params_str[0])
546 {
547 fprintf (stderr, "Unrecognised parameter: -\n");
548 return PERF_RPL_PARAM_ERROR;
549 }
550 do
551 {
552 enum PerfRepl_param_result param_res;
553 param_res = process_short_param (params_str, next_param);
554 if (PERF_RPL_PARAM_ONE_CHAR != param_res)
555 return param_res;
556 } while (0 != (++params_str)[0]);
557 return PERF_RPL_PARAM_FULL_STR;
558}
559
560
561/**
562 * Process "long" (--something) parameters.
563 * @param param the pointer to first character after "--"
564 * @param next_param the pointer to the next parameter (if any) or
565 * NULL if no next parameter
566 * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by
567 * this function
568 */
569static enum PerfRepl_param_result
570process_long_param (const char *param, const char *next_param)
571{
572 const size_t param_len = strlen (param);
573
574 if ((MHD_STATICSTR_LEN_ ("all-cpus") == param_len) &&
575 (0 == memcmp (param, "all-cpus", MHD_STATICSTR_LEN_ ("all-cpus"))))
576 return process_param__all_cpus ("--all-cpus");
577 else if ((MHD_STATICSTR_LEN_ ("threads") <= param_len) &&
578 (0 == memcmp (param, "threads", MHD_STATICSTR_LEN_ ("threads"))))
579 return process_param__threads ("--threads",
580 param + MHD_STATICSTR_LEN_ ("threads"),
581 next_param);
582 else if ((MHD_STATICSTR_LEN_ ("epoll") == param_len) &&
583 (0 == memcmp (param, "epoll", MHD_STATICSTR_LEN_ ("epoll"))))
584 return process_param__epoll ("--epoll");
585 else if ((MHD_STATICSTR_LEN_ ("poll") == param_len) &&
586 (0 == memcmp (param, "poll", MHD_STATICSTR_LEN_ ("poll"))))
587 return process_param__poll ("--poll");
588 else if ((MHD_STATICSTR_LEN_ ("select") == param_len) &&
589 (0 == memcmp (param, "select", MHD_STATICSTR_LEN_ ("select"))))
590 return process_param__select ("--select");
591 else if ((MHD_STATICSTR_LEN_ ("help") == param_len) &&
592 (0 == memcmp (param, "help", MHD_STATICSTR_LEN_ ("help"))))
593 return process_param__help ("--help");
594 else if ((MHD_STATICSTR_LEN_ ("version") == param_len) &&
595 (0 == memcmp (param, "version", MHD_STATICSTR_LEN_ ("version"))))
596 return process_param__version ("--version");
597
598 fprintf (stderr, "Unrecognised parameter: --%s.\n", param);
599 return PERF_RPL_PARAM_ERROR;
600}
601
602
603static int
604process_params (int argc, char *const *argv)
605{
606 int proc_dash_param = ! 0;
607 int i;
608 for (i = 1; i < argc; ++i)
609 {
610 /**
611 * The currently processed argument
612 */
613 const char *const p = argv[i];
614 const char *const p_next = (argc == (i + 1)) ? NULL : (argv[i + 1]);
615 if (NULL == p)
616 {
617 fprintf (stderr, "The NULL in the parameter number %d. "
618 "The error in the C library?\n", i);
619 continue;
620 }
621 else if (0 == p[0])
622 continue; /* Empty */
623 else if (proc_dash_param && ('-' == p[0]))
624 {
625 enum PerfRepl_param_result param_res;
626 if ('-' == p[1])
627 {
628 if (0 == p[2])
629 {
630 proc_dash_param = 0; /* The '--' parameter */
631 continue;
632 }
633 param_res = process_long_param (p + 2, p_next);
634 }
635 else
636 param_res = process_short_params_str (p + 1, p_next);
637
638 if (PERF_RPL_PARAM_ERROR == param_res)
639 return PERF_RPL_ERR_CODE_BAD_PARAM;
640 if (PERF_RPL_PARAM_STR_PLUS_NEXT == param_res)
641 ++i;
642 else if (PERF_RPL_PARAM_ONE_CHAR == param_res)
643 abort ();
644 continue;
645 }
646 else if (('0' <= p[0]) && ('9' >= p[0]))
647 {
648 /* Process the port number */
649 unsigned int read_port;
650 size_t num_digits;
651 num_digits = str_to_uint (p, &read_port);
652 if (0 != p[num_digits])
653 {
654 fprintf (stderr, "Error in specified port number: %s\n", p);
655 return PERF_RPL_ERR_CODE_BAD_PARAM;
656 }
657 else if (65535 < read_port)
658 {
659 fprintf (stderr, "Wrong port number: %s\n", p);
660 return PERF_RPL_ERR_CODE_BAD_PARAM;
661 }
662 mhd_port = (uint16_t) read_port;
663 }
664 else
665 {
666 fprintf (stderr, "Unrecognised parameter: %s\n\n", p);
667 return PERF_RPL_ERR_CODE_BAD_PARAM;
668 }
669 }
670 return 0;
671}
672
673
674static void
675print_version (void)
676{
677 printf ("%s (GNU libmicrohttpd", self_name);
678 if (0 != build_revision[0])
679 printf ("; %s", build_revision);
680 printf (") %s\n", MHD_get_version ());
681 printf ("%s\n", tool_copyright);
682}
683
684
685static void
686print_all_cores_used (void)
687{
688 printf ("No CPU cores on this machine are left unused and available "
689 "for the client / requests generator. "
690 "Testing with remote client is recommended.\n");
691}
692
693
694/**
695 * Apply parameter '-t' or '--threads'
696 */
697static void
698check_apply_param__threads (void)
699{
700 if (0 == tool_params.threads)
701 return;
702
703 num_threads = tool_params.threads;
704 if (get_cpu_core_count () == num_threads)
705 {
706 printf ("The requested number of threads is equal to the number of "
707 "detected CPU cores.\n");
708 print_all_cores_used ();
709 }
710 else if (get_cpu_core_count () < num_threads)
711 {
712 fprintf (stderr, "WARNING: The requested number of threads (%u) is "
713 "higher than the number of detected CPU cores (%u).\n",
714 num_threads, get_cpu_core_count ());
715 fprintf (stderr, "This decreases the performance. "
716 "Consider using fewer threads.\n");
717 }
718}
719
720
721/**
722 * Apply parameter '-A' or '--all-cpus'
723 */
724static void
725check_apply_param__all_cpus (void)
726{
727 if (! tool_params.all_cpus)
728 return;
729
730 num_threads = get_cpu_core_count ();
731 printf ("Requested use of all available CPU cores for MHD threads.\n");
732 print_all_cores_used ();
733}
734
735
736static void
737check_param_port (void)
738{
739 if (0 != tool_params.port)
740 return;
741 if (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_AUTODETECT_BIND_PORT))
742 tool_params.port = PERF_REPL_PORT_FALLBACK;
743}
744
745
746/* non-zero - OK, zero - error */
747static int
748check_param__epoll (void)
749{
750 if (! tool_params.epoll)
751 return ! 0;
752 if (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_EPOLL))
753 {
754 fprintf (stderr, "'epoll' was requested, but this MHD build does not "
755 "support 'epoll' functionality.\n");
756 return 0;
757 }
758 return ! 0;
759}
760
761
762/* non-zero - OK, zero - error */
763static int
764check_param__poll (void)
765{
766 if (! tool_params.poll)
767 return ! 0;
768 if (MHD_NO == MHD_is_feature_supported (MHD_FEATURE_POLL))
769 {
770 fprintf (stderr, "poll() was requested, but this MHD build does not "
771 "support polling by poll().\n");
772 return 0;
773 }
774 return ! 0;
775}
776
777
778/**
779 * Apply decoded parameters
780 * @return 0 if success,
781 * positive error code if case of error,
782 * -1 to exit program with success (0) error code.
783 */
784static int
785check_apply_params (void)
786{
787 if (tool_params.help)
788 {
789 show_help ();
790 return -1;
791 }
792 else if (tool_params.version)
793 {
794 print_version ();
795 return -1;
796 }
797 if (! check_param__epoll ())
798 return PERF_RPL_ERR_CODE_BAD_PARAM;
799 if (! check_param__poll ())
800 return PERF_RPL_ERR_CODE_BAD_PARAM;
801 check_apply_param__threads ();
802 check_apply_param__all_cpus ();
803 return 0;
804}
805
806
807static int
808init_data (void)
809{
810 /* Use the same memory area to avoid multiple copies.
811 The system will keep it in cache. */
812 static const char tiny_body[] = "Hi!";
813 unsigned int i;
814 /* Use more responses to minimise waiting in threads to unlock
815 the response used by other thread. */
816 num_resps = 16 * get_num_threads ();
817 resps = (struct MHD_Response **)
818 malloc ((sizeof(struct MHD_Response *)) * num_resps);
819 if (NULL == resps)
820 return 25;
821 for (i = 0; i < num_resps; ++i)
822 {
823#if MHD_VERSION >= 0x00097701
824 resps[i] = MHD_create_response_from_buffer_static (sizeof(tiny_body) - 1,
825 tiny_body);
826#else /* MHD_VERSION < 0x00097701 */
827 resps[i] = MHD_create_response_from_buffer (sizeof(tiny_body) - 1,
828 (void *) tiny_body,
829 MHD_RESPMEM_PERSISTENT);
830#endif
831 if (NULL == resps[i])
832 {
833 fprintf (stderr, "Failed to create responses.\n");
834 break;
835 }
836 }
837 if (i == num_resps)
838 return 0; /* Success */
839
840 /* Cleanup */
841 while (--i < num_resps)
842 MHD_destroy_response (resps[i]);
843 free (resps);
844 resps = NULL;
845 num_resps = 0;
846 return 32;
847}
848
849
850static void
851deinit_data (void)
852{
853 unsigned int i;
854 for (i = 0; i < num_resps; ++i)
855 MHD_destroy_response (resps[i]);
856
857 free (resps);
858 resps = NULL;
859 num_resps = 0;
860}
861
862
863static enum MHD_Result
864answer_shared_response (void *cls,
865 struct MHD_Connection *connection,
866 const char *url,
867 const char *method,
868 const char *version,
869 const char *upload_data,
870 size_t *upload_data_size,
871 void **req_cls)
872{
873 static int marker = 0;
874 unsigned int resp_index;
875 static volatile unsigned int last_index = 0;
876 (void) cls; /* Unused */
877 (void) url; (void) version; /* Unused */
878 (void) upload_data; (void) upload_data_size; /* Unused */
879
880 if (NULL == *req_cls)
881 {
882 /* The fist call */
883 *req_cls = (void *) &marker;
884 /* Do not send reply yet. No error. */
885 return MHD_YES;
886 }
887 if ((0 != strcmp (method, MHD_HTTP_METHOD_GET)) &&
888 (0 != strcmp (method, MHD_HTTP_METHOD_HEAD)))
889 return MHD_NO; /* Unsupported method, close connection */
890
891 /* This kind of operation does not guarantee that numbers are not reused
892 in parallel threads, when processed simultaneously, but this should not
893 be a big problem, as it just slow down replies a bit due to
894 responses locking. */
895 resp_index = (last_index++) % num_resps;
896 return MHD_queue_response (connection, MHD_HTTP_OK, resps[resp_index]);
897}
898
899
900static int
901run_mhd (void)
902{
903 struct MHD_Daemon *d;
904 unsigned int flags = MHD_NO_FLAG;
905 struct MHD_OptionItem opt_arr[16];
906 size_t opt_count = 0;
907 const union MHD_DaemonInfo *d_info;
908 const char *poll_mode;
909 uint16_t port;
910
911#if defined (_DEBUG)
912 fprintf (stderr, "WARNING: Running with debug asserts enabled, "
913 "the performance is suboptimal.\n");
914#endif /* _DEBUG */
915#if defined(__GNUC__) && ! defined (__OPTIMIZE__)
916 fprintf (stderr, "WARNING: The tools is compiled without enabled compiler "
917 "optimisations, the performance is suboptimal.\n");
918#endif /* __GNUC__ && ! __OPTIMIZE__ */
919#if defined(__GNUC__) && defined (__OPTIMIZE_SIZE__)
920 fprintf (stderr, "WARNING: The tools is compiled with size-optimisations, "
921 "the performance is suboptimal.\n");
922#endif /* __GNUC__ && ! __OPTIMIZE__ */
923#if MHD_VERSION >= 0x00097703
924 if (MHD_NO != MHD_is_feature_supported (MHD_FEATURE_DEBUG_BUILD))
925 fprintf (stderr, "WARNING: The libmicrohttpd is compiled with "
926 "debug asserts enabled, the performance is suboptimal.\n");
927#endif /* MHD_VERSION >= 0x00097703 */
928 flags |= MHD_USE_ERROR_LOG;
929 flags |= MHD_USE_INTERNAL_POLLING_THREAD;
930 if (tool_params.epoll)
931 flags |= MHD_USE_EPOLL;
932 else if (tool_params.poll)
933 flags |= MHD_USE_POLL;
934 else if (tool_params.select)
935 (void) flags; /* No special additional flag */
936 else
937 flags |= MHD_USE_AUTO;
938 flags |= MHD_USE_SUPPRESS_DATE_NO_CLOCK;
939
940 if (0)
941 {
942 struct MHD_OptionItem option =
943 { MHD_OPTION_CONNECTION_LIMIT, 5, NULL };
944 opt_arr[opt_count++] = option;
945 }
946 if (1 < get_num_threads ())
947 {
948 struct MHD_OptionItem option =
949 { MHD_OPTION_THREAD_POOL_SIZE, (int) get_num_threads (), NULL };
950 opt_arr[opt_count++] = option;
951 }
952 if (1)
953 {
954 struct MHD_OptionItem option =
955 { MHD_OPTION_END, 0, NULL };
956 opt_arr[opt_count] = option;
957 if (opt_count >= (sizeof(opt_arr) / sizeof(opt_arr[0])))
958 abort ();
959 }
960 d = MHD_start_daemon (flags, mhd_port, NULL, NULL, &answer_shared_response,
961 NULL, MHD_OPTION_ARRAY, opt_arr, MHD_OPTION_END);
962 if (NULL == d)
963 {
964 fprintf (stderr, "Error starting MHD daemon.\n");
965 return 15;
966 }
967 d_info = MHD_get_daemon_info (d, MHD_DAEMON_INFO_FLAGS);
968 if (NULL == d_info)
969 abort ();
970 flags = (unsigned int) d_info->flags;
971 if (0 != (flags & MHD_USE_POLL))
972 poll_mode = "poll()";
973 else if (0 != (flags & MHD_USE_EPOLL))
974 poll_mode = "epoll";
975 else
976 poll_mode = "select()";
977 d_info = MHD_get_daemon_info (d, MHD_DAEMON_INFO_BIND_PORT);
978 if (NULL == d_info)
979 abort ();
980 port = d_info->port;
981 if (0 == port)
982 fprintf (stderr, "Cannot detect port number. Consider specifying "
983 "port number explicitly.\n");
984
985 printf ("\nMHD is running.\n");
986 printf (" Bind port: %u\n", (unsigned int) port);
987 printf (" Polling function: %s\n", poll_mode);
988 printf (" Threading: ");
989 if (1 == get_num_threads ())
990 printf ("one MHD thread\n");
991 else
992 printf ("%u MHD threads in thread pool\n", get_num_threads ());
993 printf ("To test with remote client use http://HOST_IP:%u/\n",
994 (unsigned int) port);
995 printf ("To test with client on the same host use "
996 "http://127.0.0.1:%u\n", (unsigned int) port);
997 printf ("\nPress ENTER to stop.\n");
998 if (1)
999 {
1000 char buf[10];
1001 (void) fgets (buf, sizeof(buf), stdin);
1002 }
1003 MHD_stop_daemon (d);
1004 return 0;
1005}
1006
1007
1008int
1009main (int argc, char *const *argv)
1010{
1011 int ret;
1012 set_self_name (argc, argv);
1013 ret = process_params (argc, argv);
1014 if (0 != ret)
1015 return ret;
1016 ret = check_apply_params ();
1017 if (0 > ret)
1018 return 0;
1019 if (0 != ret)
1020 return ret;
1021 ret = init_data ();
1022 if (0 != ret)
1023 return ret;
1024 ret = run_mhd ();
1025 deinit_data ();
1026 return ret;
1027}