libmicrohttpd2

HTTP server C library (MHD 2.x, alpha)
Log | Files | Refs | README | LICENSE

commit 2e6777febaa8b08306329af28201b47a9949f2e0
parent 546a9e4de9f8e21bd1315aa4e279041f17902c2f
Author: Evgeny Grin (Karlson2k) <k2k@drgrin.dev>
Date:   Fri, 30 May 2025 09:58:29 +0200

Implemented perf_replies tool for benchmarking

Diffstat:
Mconfigure.ac | 1+
Msrc/Makefile.am | 4++++
Asrc/tools/.gitignore | 1+
Asrc/tools/Makefile.am | 39+++++++++++++++++++++++++++++++++++++++
Asrc/tools/mhdtl_get_cpu_count.c | 940+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tools/mhdtl_get_cpu_count.h | 55+++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tools/mhdtl_str_to_uint.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/tools/perf_replies.c | 1907+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
8 files changed, 3019 insertions(+), 0 deletions(-)

diff --git a/configure.ac b/configure.ac @@ -7547,6 +7547,7 @@ src/incl_priv/Makefile src/incl_priv/config/Makefile src/include/Makefile src/mhd2/Makefile +src/tools/Makefile src/tests/Makefile src/tests/basic/Makefile src/tests/upgrade/Makefile diff --git a/src/Makefile.am b/src/Makefile.am @@ -4,6 +4,10 @@ examples_DIR = examples2 SUBDIRS = incl_priv include mhd2 +if BUILD_TOOLS +SUBDIRS += tools +endif + if BUILD_EXAMPLES SUBDIRS += $(examples_DIR) endif diff --git a/src/tools/.gitignore b/src/tools/.gitignore @@ -0,0 +1 @@ +/perf_replies diff --git a/src/tools/Makefile.am b/src/tools/Makefile.am @@ -0,0 +1,39 @@ +# This Makefile.am is in the public domain +SUBDIRS = . + +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/incl_priv \ + -I$(top_srcdir)/src/include \ + -DMHD_REAL_CPU_COUNT=@MHD_REAL_CPU_COUNT@ \ + -DMHD_CPU_COUNT=$(CPU_COUNT) \ + $(CPPFLAGS_ac) + +AM_CFLAGS = $(CFLAGS_ac) + +AM_LDFLAGS = $(LDFLAGS_ac) + +AM_TESTS_ENVIRONMENT = $(TESTS_ENVIRONMENT_ac) + +if USE_COVERAGE + AM_CFLAGS += -fprofile-arcs -ftest-coverage +endif + +LDADD = $(top_builddir)/src/mhd2/libmicrohttpd2.la + +$(top_builddir)/src/mhd2/libmicrohttpd2.la: $(top_builddir)/src/mhd2/Makefile + @echo ' cd $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la'; \ + $(am__cd) $(top_builddir)/src/mhd2 && $(MAKE) $(AM_MAKEFLAGS) libmicrohttpd2.la + + +# Tools +noinst_PROGRAMS = + +if MHD_SUPPORT_THREADS +noinst_PROGRAMS += \ + perf_replies +endif + + +perf_replies_SOURCES = \ + perf_replies.c mhdtl_str_to_uint.h \ + mhdtl_get_cpu_count.h mhdtl_get_cpu_count.c diff --git a/src/tools/mhdtl_get_cpu_count.c b/src/tools/mhdtl_get_cpu_count.c @@ -0,0 +1,940 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2023-2025 Evgeny Grin (Karlson2k) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file src/tools/mhdtl_get_cpu_count.c + * @brief Implementation of functions to detect the number of available + * CPU cores. + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include "mhdtl_get_cpu_count.h" +#ifdef HAVE_SYS_TYPES_H +# include <sys/types.h> +#endif /* HAVE_SYS_TYPES_H */ +#ifdef HAVE_SYS_PARAM_H +# include <sys/param.h> +#endif /* HAVE_SYS_PARAM_H */ +#ifdef HAVE_SYS__CPUSET_H +# include <sys/_cpuset.h> +#endif /* HAVE_SYS_PARAM_H */ +#ifdef HAVE_STDDEF_H +# include <stddef.h> +#endif /* HAVE_STDDEF_H */ +#ifdef HAVE_STRING_H +# include <string.h> +#endif /* HAVE_STRING_H */ +#ifdef HAVE_SYS_SYSCTL_H +# include <sys/sysctl.h> +#endif /* HAVE_SYS_SYSCTL_H */ +#ifdef HAVE_FEATURES_H +# include <features.h> +#endif /* HAVE_FEATURES_H */ +#ifdef HAVE_UNISTD_H +# include <unistd.h> +#endif /* HAVE_UNISTD_H */ +#ifdef HAVE_VXCPULIB_H +# include <vxCpuLib.h> +#endif +#ifdef HAVE_WINDOWS_H +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN 1 +# endif /* ! WIN32_LEAN_AND_MEAN */ +# include <windows.h> +# ifndef ALL_PROCESSOR_GROUPS +# define ALL_PROCESSOR_GROUPS 0xFFFFu +# endif /* ALL_PROCESSOR_GROUPS */ +#elif defined(_WIN32) && ! defined (__CYGWIN__) +# error Windows headers are required for Windows build +#endif /* HAVE_WINDOWS_H */ +#ifdef HAVE_SCHED_H +# include <sched.h> +#endif /* HAVE_SCHED_H */ +#ifdef HAVE_SYS_CPUSET_H +# include <sys/cpuset.h> +#endif /* HAVE_SYS_CPUSET_H */ +#ifdef HAVE_STDBOOL_H +# include <stdbool.h> +#endif /* HAVE_STDBOOL_H */ + +#if ! defined(HAS_DECL_CPU_SETSIZE) && ! defined(CPU_SETSIZE) +# define CPU_SETSIZE (1024) +# define CPU_SETSIZE_SAFE (64) +#else /* HAS_DECL_CPU_SETSIZE || CPU_SETSIZE */ +# define CPU_SETSIZE_SAFE CPU_SETSIZE +#endif /* HAS_DECL_CPU_SETSIZE || CPU_SETSIZE */ + +/* Check and fix possible missing macros */ +#if ! defined(HAS_DECL_CTL_HW) && defined(CTL_HW) +# define HAS_DECL_CTL_HW 1 +#endif /* ! HAS_DECL_CTL_HW && CTL_HW */ + +#if ! defined(HAS_DECL_HW_NCPUONLINE) && defined(HW_NCPUONLINE) +# define HAS_DECL_HW_NCPUONLINE 1 +#endif /* ! HAS_DECL_HW_NCPUONLINE && HW_NCPUONLINE */ + +#if ! defined(HAS_DECL_HW_AVAILCPU) && defined(HW_AVAILCPU) +# define HAS_DECL_HW_AVAILCPU 1 +#endif /* ! HAS_DECL_HW_AVAILCPU && HW_AVAILCPU */ + +#if ! defined(HAS_DECL_HW_NCPU) && defined(HW_NCPU) +# define HAS_DECL_HW_NCPU 1 +#endif /* ! HAS_DECL_HW_NCPU && HW_NCPU */ + +#if ! defined(HAS_DECL__SC_NPROCESSORS_ONLN) && defined(_SC_NPROCESSORS_ONLN) +# define HAS_DECL__SC_NPROCESSORS_ONLN 1 +#endif /* ! HAS_DECL__SC_NPROCESSORS_ONLN && _SC_NPROCESSORS_ONLN */ + +#if ! defined(HAS_DECL__SC_NPROC_ONLN) && defined(_SC_NPROC_ONLN) +# define HAS_DECL__SC_NPROC_ONLN 1 +#endif /* ! HAS_DECL__SC_NPROC_ONLN && _SC_NPROC_ONLN */ + +#if ! defined(HAS_DECL__SC_CRAY_NCPU) && defined(_SC_CRAY_NCPU) +# define HAS_DECL__SC_CRAY_NCPU 1 +#endif /* ! HAS_DECL__SC_CRAY_NCPU && _SC_CRAY_NCPU */ + +#if ! defined(HAS_DECL__SC_NPROCESSORS_CONF) && defined(_SC_NPROCESSORS_CONF) +# define HAS_DECL__SC_NPROCESSORS_CONF 1 +#endif /* ! HAVE_DECL__SC_NPROCESSORS_CONF && _SC_NPROCESSORS_CONF */ + +/* Forward declarations */ + +static int +mhdtl_get_sys_cpu_count_sysctl_ (void); + +/** + * Detect the number of logical CPU cores available for the process by + * sched_getaffinity() (and related) function. + * @return the number of detected logical CPU cores or + * -1 if failed to detect (or this function unavailable). + */ +static int +mhdtl_get_proc_cpu_count_sched_getaffinity_ (void) +{ + int ret = -1; +#if defined(HAVE_SCHED_GETAFFINITY) && defined(HAVE_GETPID) + /* Glibc style */ + if (0 >= ret) + { + cpu_set_t cur_set; + if (0 == sched_getaffinity (getpid (), sizeof (cur_set), &cur_set)) + { +#ifdef HAVE_CPU_COUNT + ret = CPU_COUNT (&cur_set); +#else /* ! HAVE_CPU_COUNT */ + unsigned int i; + ret = 0; + for (i = 0; i < CPU_SETSIZE_SAFE; ++i) + { + if (CPU_ISSET (i, &cur_set)) + ++ret; + } + if (0 == ret) + ret = -1; +#endif /* ! HAVE_CPU_COUNT */ + } + } +#ifdef HAVE_CPU_COUNT_S + if (0 >= ret) + { + /* Use 256 times larger size than size for default maximum CPU number. + Hopefully it would be enough even for exotic situations. */ + static const unsigned int set_size_cpus = 256 * CPU_SETSIZE; + const size_t set_size_bytes = CPU_ALLOC_SIZE (set_size_cpus); + cpu_set_t *p_set; + + p_set = CPU_ALLOC (set_size_cpus); + if (NULL != p_set) + { + if (0 == sched_getaffinity (getpid (), set_size_bytes, p_set)) + { +#ifndef MHD_FUNC_CPU_COUNT_S_GETS_CPUS + ret = CPU_COUNT_S (set_size_bytes, p_set); +#else /* MHD_FUNC_CPU_COUNT_S_GETS_CPUS */ + ret = CPU_COUNT_S (set_size_cpus, p_set); +#endif /* MHD_FUNC_CPU_COUNT_S_GETS_CPUS */ + } + CPU_FREE (p_set); + } + } +#endif /* HAVE_CPU_COUNT_S */ +#endif /* HAVE_SCHED_GETAFFINITY && HAVE_GETPID */ + if (0 >= ret) + return -1; + return ret; +} + + +/** + * Detect the number of logical CPU cores available for the process by + * cpuset_getaffinity() function. + * @return the number of detected logical CPU cores or + * -1 if failed to detect (or this function unavailable). + */ +static int +mhdtl_get_proc_cpu_count_cpuset_getaffinity_ (void) +{ + int ret = -1; +#if defined(HAVE_CPUSET_GETAFFINITY) + /* FreeBSD style */ + if (0 >= ret) + { + cpuset_t cur_mask; + /* The should get "anonymous" mask/set. The anonymous mask is always + a subset of the assigned set (which is a subset of the root set). */ + if (0 == cpuset_getaffinity (CPU_LEVEL_WHICH, CPU_WHICH_PID, (id_t) -1, + sizeof (cur_mask), &cur_mask)) + { +#ifdef HAVE_CPU_COUNT + ret = CPU_COUNT (&cur_mask); +#else /* ! HAVE_CPU_COUNT */ + unsigned int i; + ret = 0; + for (i = 0; i < CPU_SETSIZE_SAFE; ++i) + { + if (CPU_ISSET (i, &cur_mask)) + ++ret; + } + if (0 == ret) + ret = -1; +#endif /* ! HAVE_CPU_COUNT */ + } + } +#ifdef HAVE_CPU_COUNT_S + if (0 >= ret) + { + /* Use 256 times larger size than size for default maximum CPU number. + Hopefully it would be enough even for exotic situations. */ + static const unsigned int mask_size_cpus = 256 * CPU_SETSIZE; + const size_t mask_size_bytes = CPU_ALLOC_SIZE (mask_size_cpus); + cpuset_t *p_mask; + + p_mask = CPU_ALLOC (mask_size_cpus); + if (NULL != p_mask) + { + if (0 == cpuset_getaffinity (CPU_LEVEL_WHICH, CPU_WHICH_PID, (id_t) -1, + mask_size_bytes, p_mask)) + { +#ifndef MHD_FUNC_CPU_COUNT_S_GETS_CPUS + ret = CPU_COUNT_S (mask_size_bytes, p_mask); +#else /* MHD_FUNC_CPU_COUNT_S_GETS_CPUS */ + ret = CPU_COUNT_S (mask_size_cpus, p_mask); +#endif /* MHD_FUNC_CPU_COUNT_S_GETS_CPUS */ + } + CPU_FREE (p_mask); + } + } +#endif /* HAVE_CPU_COUNT_S */ +#endif /* HAVE_CPUSET_GETAFFINITY */ + if (0 >= ret) + return -1; + return ret; +} + + +/** + * Detect the number of logical CPU cores available for the process by + * sched_getaffinity_np() (and related) function. + * @return the number of detected logical CPU cores or + * -1 if failed to detect (or this function unavailable). + */ +static int +mhdtl_get_proc_cpu_count_sched_getaffinity_np_ (void) +{ + int ret = -1; +#if defined(HAVE_SCHED_GETAFFINITY_NP) && defined(HAVE_GETPID) + /* NetBSD style */ + cpuset_t *cpuset_ptr; + cpuset_ptr = cpuset_create (); + if (NULL != cpuset_ptr) + { + if (0 == sched_getaffinity_np (getpid (), cpuset_size (cpuset_ptr), + cpuset_ptr)) + { + cpuid_t cpu_num; +#if defined(HAVE_SYSCONF) && defined(HAVE_DECL__SC_NPROCESSORS_CONF) + unsigned int max_num = 0; + long sc_value; + sc_value = sysconf (_SC_NPROCESSORS_ONLN); + if (0 < sc_value) + max_num = (unsigned int) sc_value; + if (0 < max_num) + { + ret = 0; + for (cpu_num = 0; cpu_num < max_num; ++cpu_num) + if (0 < cpuset_isset (cpu_num, cpuset_ptr)) + ++ret; + } + else /* Combined with the next 'if' */ +#endif /* HAVE_SYSCONF && HAVE_DECL__SC_NPROCESSORS_CONF */ + if (1) + { + int res; + cpu_num = 0; + ret = 0; + do + { + res = cpuset_isset (cpu_num++, cpuset_ptr); + if (0 < res) + ++ret; + } while (0 <= res); + } +#ifdef __NetBSD__ + if (0 == ret) + { + /* On NetBSD "unset" affinity (exactly zero CPUs) means + "all CPUs are available". */ + ret = mhdtl_get_sys_cpu_count_sysctl_ (); + } +#endif /* __NetBSD__ */ + } + cpuset_destroy (cpuset_ptr); + } +#endif /* HAVE_SCHED_GETAFFINITY_NP && HAVE_GETPID */ + if (0 >= ret) + return -1; + return ret; +} + + +/** + * Detect the number of logical CPU cores available for the process by + * W32 API functions. + * @return the number of detected logical CPU cores or + * -1 if failed to detect (or this function unavailable). + */ +static int +mhdtl_get_proc_cpu_count_w32_ (void) +{ + int ret = -1; +#if defined(_WIN32) && ! defined(__CYGWIN__) + /* W32 Native */ + /** + * Maximum used number of CPU groups. + * Improvement: Implement dynamic allocation when it would be reasonable + */ +#define MHDT_MAX_GROUP_COUNT 128 + /** + * The count of logical CPUs as returned by GetProcessAffinityMask() + */ + int count_by_proc_aff_mask; + count_by_proc_aff_mask = -1; + if (1) + { + DWORD_PTR proc_aff; + DWORD_PTR sys_aff; + + if (GetProcessAffinityMask (GetCurrentProcess (), &proc_aff, &sys_aff)) + { + /* Count all set bits */ + for (count_by_proc_aff_mask = 0; 0 != proc_aff; proc_aff &= proc_aff - 1) + ++count_by_proc_aff_mask; + } + } + if (0 < count_by_proc_aff_mask) + { + HMODULE k32hndl; + k32hndl = LoadLibraryA ("kernel32.dll"); + if (NULL != k32hndl) + { + typedef BOOL (WINAPI *GPGA_PTR)(HANDLE hProcess, + PUSHORT GroupCount, + PUSHORT GroupArray); + GPGA_PTR ptrGetProcessGroupAffinity; + ptrGetProcessGroupAffinity = + (GPGA_PTR) (void *) GetProcAddress (k32hndl, + "GetProcessGroupAffinity"); + if (NULL == ptrGetProcessGroupAffinity) + { + /* Windows version before Win7 */ + /* No processor groups supported, the process affinity mask gives full picture */ + ret = count_by_proc_aff_mask; + } + else + { + /* Windows version Win7 or later */ + /* Processor groups are supported */ + USHORT arr_elements = MHDT_MAX_GROUP_COUNT; + USHORT groups_arr[MHDT_MAX_GROUP_COUNT]; /* Hopefully should be enough */ + /* Improvement: Implement dynamic allocation when it would be reasonable */ + /** + * Exactly one processor group is assigned to the process + */ + bool single_cpu_group_assigned; /**< Exactly one processor group is assigned to the process */ + struct mhdt_GR_AFFINITY + { + KAFFINITY Mask; + WORD Group; + WORD Reserved[3]; + }; + typedef BOOL (WINAPI *GPDCSM_PTR)(HANDLE Process, + struct mhdt_GR_AFFINITY *CpuSetMasks, + USHORT CpuSetMaskCount, + USHORT *RequiredMaskCount); + GPDCSM_PTR ptrGetProcessDefaultCpuSetMasks; + bool win_fe_or_later; + bool cpu_set_mask_assigned; + + single_cpu_group_assigned = false; + if (ptrGetProcessGroupAffinity (GetCurrentProcess (), &arr_elements, + groups_arr)) + { + if (1 == arr_elements) + { + /* Exactly one processor group assigned to the process */ + single_cpu_group_assigned = true; +#if 0 /* Disabled code */ + /* The value returned by GetThreadGroupAffinity() is not relevant as + for the new threads the process affinity mask is used. */ + ULONG_PTR proc_aff2; + typedef BOOL (WINAPI *GTGA_PTR)(HANDLE hThread, + struct mhdt_GR_AFFINITY * + GroupAffinity); + GTGA_PTR ptrGetThreadGroupAffinity; + ptrGetThreadGroupAffinity = + (GTGA_PTR) (void *) GetProcAddress (k32hndl, + "GetThreadGroupAffinity"); + if (NULL != ptrGetThreadGroupAffinity) + { + struct mhdt_GR_AFFINITY thr_gr_aff; + if (ptrGetThreadGroupAffinity (GetCurrentThread (), &thr_gr_aff)) + proc_aff2 = (ULONG_PTR) thr_gr_aff.Mask; + } +#endif /* Disabled code */ + } + } + ptrGetProcessDefaultCpuSetMasks = + (GPDCSM_PTR) (void *) GetProcAddress (k32hndl, + "GetProcessDefaultCpuSetMasks"); + if (NULL != ptrGetProcessDefaultCpuSetMasks) + { + /* This is Iron Release / Codename Fe + (also know as Windows 11 and Windows Server 2022) + or later version */ + struct mhdt_GR_AFFINITY gr_affs[MHDT_MAX_GROUP_COUNT]; /* Hopefully should be enough */ + /* Improvement: Implement dynamic allocation when it would be reasonable */ + USHORT num_elm; + + win_fe_or_later = true; + + if (ptrGetProcessDefaultCpuSetMasks (GetCurrentProcess (), gr_affs, + sizeof (gr_affs) + / sizeof (gr_affs[0]), &num_elm)) + { + if (0 == num_elm) + { + /* No group mask set */ + cpu_set_mask_assigned = false; + } + else + cpu_set_mask_assigned = true; + } + else + cpu_set_mask_assigned = true; /* Assume the worst case */ + } + else + { + win_fe_or_later = false; + cpu_set_mask_assigned = false; + } + if (! win_fe_or_later) + { + /* The OS is not capable of distributing threads across different + processor groups. Results reported by GetProcessAffinityMask() + are relevant for the main processor group for the process. */ + ret = count_by_proc_aff_mask; + } + else + { + /* The of is capable of automatic threads distribution across + processor groups. */ + if (cpu_set_mask_assigned) + { + /* Assigned Default CpuSet Masks combines with "classic" + affinity in the not fully clear way. The combination + is not documented and this functionality could be changed + any moment. */ + ret = -1; + } + else + { + if (! single_cpu_group_assigned) + { + /* This is a multi processor group process on Win11 (or later). + Each processor group may have different affinity and + the OS has not API to get it. + For example, affinity to the main processor group could be + assigned by SetProcessAffinityMask() function, which converts + the process to the single-processor-group type, but if + SetThreadGroupAffinity() is called later and bind the thread + to another processor group, the process becomes multi-processor- + group again, however the initial affinity mask is still used + for the initial (main) processor group. There is no API to read + it. + It is also possible that processor groups have different number + of processors. */ + ret = -1; + } + else + { + /* Single-processor-group process on Win11 (or later) without + assigned Default CpuSet Masks. */ + ret = count_by_proc_aff_mask; + } + } + } + } + FreeLibrary (k32hndl); + } + } +#endif /* _WIN32 && ! __CYGWIN__ */ + if (0 >= ret) + return -1; + return ret; +} + + +/** + * Detect the number of logical CPU cores available for the process. + * The number of cores available for this process could be different from + * value of cores available on the system. The OS may have limit on number + * assigned/allowed cores for single process and process may have limited + * CPU affinity. + * @return the number of logical CPU cores available for the process or + * -1 if failed to detect + */ +int +mhdtl_get_proc_cpu_count (void) +{ + int res; + +#if defined(__linux__) || defined(__GLIBC__) + /* On Linux kernel try first 'sched_getaffinity()' as it should be + the native API. + Also try it first on other kernels if Glibc is used. */ + res = mhdtl_get_proc_cpu_count_sched_getaffinity_ (); + if (0 < res) + return res; + + res = mhdtl_get_proc_cpu_count_cpuset_getaffinity_ (); + if (0 < res) + return res; +#else /* ! __linux__ && ! __GLIBC__ */ + /* On non-Linux kernels 'cpuset_getaffinity()' could be the native API, + while 'sched_getaffinity()' could be implemented in compatibility layer. */ + res = mhdtl_get_proc_cpu_count_cpuset_getaffinity_ (); + if (0 < res) + return res; + + res = mhdtl_get_proc_cpu_count_sched_getaffinity_ (); + if (0 < res) + return res; +#endif /* ! __linux__ && ! __GLIBC__ */ + + res = mhdtl_get_proc_cpu_count_sched_getaffinity_np_ (); + if (0 < res) + return res; + + res = mhdtl_get_proc_cpu_count_w32_ (); + if (0 < res) + return res; + + return -1; +} + + +/** + * Detect the number of processors by special API functions + * @return number of processors as returned by special API functions or + * -1 in case of error or special API functions unavailable + */ +static int +mhdtl_get_sys_cpu_count_special_api_ (void) +{ + int ret = -1; +#ifdef HAVE_PSTAT_GETDYNAMIC + if (0 >= ret) + { + /* HP-UX things */ + struct pst_dynamic psd_data; + memset ((void *) &psd_data, 0, sizeof (psd_data)); + if (1 == pstat_getdynamic (&psd_data, sizeof (psd_data), (size_t) 1, 0)) + { + if (0 < psd_data.psd_proc_cnt) + ret = (int) psd_data.psd_proc_cnt; + } + } +#endif /* HAVE_PSTAT_GETDYNAMIC */ +#ifdef HAVE_VXCPUENABLEDGET + if (0 >= ret) + { + /* VxWorks */ + cpuset_t enb_set; + enb_set = vxCpuEnabledGet (); + /* Count set bits */ + for (ret = 0; 0 != enb_set; enb_set &= enb_set - 1) + ++ret; + } +#endif /* HAVE_VXCPUENABLEDGET */ +#if defined(_WIN32) && ! defined (__CYGWIN__) + if (0 >= ret) + { + /* Native W32 */ + HMODULE k32hndl; + k32hndl = LoadLibraryA ("kernel32.dll"); + if (NULL != k32hndl) + { + typedef DWORD (WINAPI *GAPC_PTR)(WORD GroupNumber); + GAPC_PTR ptrGetActiveProcessorCount; + /* Available on W7 or later */ + ptrGetActiveProcessorCount = + (GAPC_PTR) (void *) GetProcAddress (k32hndl, "GetActiveProcessorCount"); + if (NULL != ptrGetActiveProcessorCount) + { + DWORD res; + res = ptrGetActiveProcessorCount (ALL_PROCESSOR_GROUPS); + ret = (int) res; + if (res != (DWORD) ret) + ret = -1; /* Overflow */ + } + } + if ((0 >= ret) && (NULL != k32hndl)) + { + typedef void (WINAPI *GNSI_PTR)(SYSTEM_INFO *pSysInfo); + GNSI_PTR ptrGetNativeSystemInfo; + /* May give incorrect (low) result on versions from W7 to W11 + when more then 64 CPUs are available */ + ptrGetNativeSystemInfo = + (GNSI_PTR) (void *) GetProcAddress (k32hndl, "GetNativeSystemInfo"); + if (NULL != ptrGetNativeSystemInfo) + { + SYSTEM_INFO sysInfo; + + memset ((void *) &sysInfo, 0, sizeof (sysInfo)); + ptrGetNativeSystemInfo (&sysInfo); + ret = (int) sysInfo.dwNumberOfProcessors; + if (sysInfo.dwNumberOfProcessors != (DWORD) ret) + ret = -1; /* Overflow */ + } + } + if (NULL != k32hndl) + FreeLibrary (k32hndl); + } + if (0 >= ret) + { + /* May give incorrect (low) result on versions from W7 to W11 + when more then 64 CPUs are available */ + SYSTEM_INFO sysInfo; + memset ((void *) &sysInfo, 0, sizeof (sysInfo)); + GetSystemInfo (&sysInfo); + ret = (int) sysInfo.dwNumberOfProcessors; + if (sysInfo.dwNumberOfProcessors != (DWORD) ret) + ret = -1; /* Overflow */ + } +#endif /* _WIN32 && ! __CYGWIN__ */ + if (0 >= ret) + return -1; + return ret; +} + + +/** + * Detect the number of processors by sysctl*() functions in reliable way. + * + * This function uses reliable identificators that coreponds to actual + * number of CPU cores online currently. + * @return number of processors as returned by 'sysctl*' functions or + * -1 in case of error or the number cannot be detected + * by these functions + */ +static int +mhdtl_get_sys_cpu_count_sysctl_ (void) +{ + int ret = -1; + /* Do not use sysctl() function on GNU/Linux even if + sysctl() is available */ +#ifndef __linux__ +#ifdef HAVE_SYSCTLBYNAME + if (0 >= ret) + { + size_t value_size = sizeof (ret); + /* Darwin: The number of available logical CPUs */ + if ((0 != sysctlbyname ("hw.logicalcpu", &ret, &value_size, + NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } + if (0 >= ret) + { + size_t value_size = sizeof (ret); + /* FreeBSD: The number of online CPUs */ + if ((0 != sysctlbyname ("kern.smp.cpus", &ret, &value_size, + NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } + if (0 >= ret) + { + size_t value_size = sizeof (ret); + /* Darwin: The current number of CPUs available to run threads */ + if ((0 != sysctlbyname ("hw.activecpu", &ret, &value_size, + NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } + if (0 >= ret) + { + size_t value_size = sizeof (ret); + /* OpenBSD, NetBSD: The number of online CPUs */ + if ((0 != sysctlbyname ("hw.ncpuonline", &ret, &value_size, + NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } + if (0 >= ret) + { + size_t value_size = sizeof (ret); + /* Darwin: The old/alternative name for "hw.activecpu" */ + if ((0 != sysctlbyname ("hw.availcpu", &ret, &value_size, + NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } +#endif /* HAVE_SYSCTLBYNAME */ +#if defined(HAVE_SYSCTL) && \ + defined(HAS_DECL_CTL_HW) && \ + defined(HAS_DECL_HW_NCPUONLINE) + if (0 >= ret) + { + /* OpenBSD, NetBSD: The number of online CPUs */ + int mib[2] = {CTL_HW, HW_NCPUONLINE}; + size_t value_size = sizeof (ret); + if ((0 != sysctl (mib, 2, &ret, &value_size, NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } +#endif /* HAVE_SYSCTL && HAS_DECL_CTL_HW && HAS_DECL_HW_NCPUONLINE */ +#if defined(HAVE_SYSCTL) && \ + defined(HAS_DECL_CTL_HW) && \ + defined(HAS_DECL_HW_AVAILCPU) + if (0 >= ret) + { + /* Darwin: The MIB name for "hw.activecpu" */ + int mib[2] = {CTL_HW, HW_AVAILCPU}; + size_t value_size = sizeof (ret); + if ((0 != sysctl (mib, 2, &ret, &value_size, NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } +#endif /* HAVE_SYSCTL && HAS_DECL_CTL_HW && HAS_DECL_HW_AVAILCPU */ +#endif /* ! __linux__ */ + if (0 >= ret) + return -1; + return ret; +} + + +/** + * Detect the number of processors by sysctl*() functions, using fallback way. + * + * This function uses less reliable (compared to + * #mhd_tool_get_sys_cpu_count_sysctl_()) ways to detect the number of + * available CPU cores and may return values corresponding to the number of + * physically available (but possibly not used by the kernel) CPU logical cores. + * @return number of processors as returned by 'sysctl*' functions or + * -1 in case of error or the number cannot be detected + * by these functions + */ +static int +mhdtl_get_sys_cpu_count_sysctl_fallback_ (void) +{ + int ret = -1; + /* Do not use sysctl() function on GNU/Linux even if + sysctl() is available */ +#ifndef __linux__ +#ifdef HAVE_SYSCTLBYNAME + if (0 >= ret) + { + size_t value_size = sizeof (ret); + /* FreeBSD, OpenBSD, NetBSD, Darwin (and others?): The number of CPUs */ + if ((0 != sysctlbyname ("hw.ncpu", &ret, &value_size, + NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } +#endif /* HAVE_SYSCTLBYNAME */ +#if defined(HAVE_SYSCTL) && \ + defined(HAS_DECL_CTL_HW) && \ + defined(HAS_DECL_HW_NCPU) + if (0 >= ret) + { + /* FreeBSD, OpenBSD, NetBSD, Darwin (and others?): The number of CPUs */ + int mib[2] = {CTL_HW, HW_NCPU}; + size_t value_size = sizeof (ret); + if ((0 != sysctl (mib, 2, &ret, &value_size, NULL, 0)) + || (sizeof (ret) != value_size)) + ret = -1; + } +#endif /* HAVE_SYSCTL && HAS_DECL_CTL_HW && HAS_DECL_HW_NCPU */ +#endif /* ! __linux__ */ + if (0 >= ret) + return -1; + return ret; +} + + +/** + * Detect the number of processors by sysconf() function in reliable way. + * + * This function uses reliable identificators that coreponds to actual + * number of CPU cores online currently. + * @return number of processors as returned by 'sysconf' function or + * -1 in case of error or 'sysconf' unavailable + */ +static int +mhdtl_get_sys_cpu_count_sysconf_ (void) +{ + int ret = -1; +#if defined(HAVE_SYSCONF) && \ + (defined(HAS_DECL__SC_NPROCESSORS_ONLN) || defined(HAS_DECL__SC_NPROC_ONLN)) + long value = -1; +#ifdef HAS_DECL__SC_NPROCESSORS_ONLN + if (0 >= value) + value = sysconf (_SC_NPROCESSORS_ONLN); +#endif /* HAS_DECL__SC_NPROCESSORS_ONLN */ +#ifdef HAS_DECL__SC_NPROC_ONLN + if (0 >= value) + value = sysconf (_SC_NPROC_ONLN); +#endif /* HAS_DECL__SC_NPROC_ONLN */ + if (0 >= value) + return -1; + ret = (int) value; + if ((long) ret != value) + return -1; /* Overflow */ +#endif /* HAVE_SYSCONF && + (HAS_DECL__SC_NPROCESSORS_ONLN || HAS_DECL__SC_NPROC_ONLN) */ + return ret; +} + + +/** + * Detect the number of processors by sysconf() function, using fallback way. + * + * This function uses less reliable (compared to + * #mhd_tool_get_sys_cpu_count_sysconf_()) ways to detect the number of + * available CPU cores and may return values corresponding to the number of + * physically available (but possibly not used by the kernel) CPU logical cores. + * @return number of processors as returned by 'sysconf' function or + * -1 in case of error or 'sysconf' unavailable + */ +static int +mhdtl_get_sys_cpu_count_sysconf_fallback_ (void) +{ + int ret = -1; +#if defined(HAVE_SYSCONF) && \ + (defined(HAS_DECL__SC_CRAY_NCPU) || defined(HAS_DECL__SC_NPROCESSORS_CONF)) + long value = -1; +#ifdef HAS_DECL__SC_CRAY_NCPU + if (0 >= value) + value = sysconf (_SC_CRAY_NCPU); +#endif /* HAS_DECL__SC_CRAY_NCPU */ +#ifdef HAS_DECL__SC_NPROCESSORS_CONF + if (0 >= value) + value = sysconf (_SC_NPROCESSORS_CONF); +#endif /* HAS_DECL__SC_NPROCESSORS_CONF */ + if (0 >= value) + return -1; + ret = (int) value; + if ((long) ret != value) + return -1; /* Overflow */ +#endif /* HAVE_SYSCONF && + (HAS_DECL__SC_CRAY_NCPU || HAS_DECL__SC_NPROCESSORS_CONF) */ + return ret; +} + + +/** + * Try to detect the number of logical CPU cores available for the system. + * The number of available logical CPU cores could be changed any time due to + * CPU hotplug. + * @return the number of logical CPU cores available, + * -1 if failed to detect. + */ +int +mhdtl_get_system_cpu_count (void) +{ + int res; + + /* Try specialised APIs first */ + res = mhdtl_get_sys_cpu_count_special_api_ (); + if (0 < res) + return res; + + /* Try sysctl*(). This is typically a direct interface to + kernel values. */ + res = mhdtl_get_sys_cpu_count_sysctl_ (); + if (0 < res) + return res; + + /* Try sysconf() as the last resort as this is a generic interface + which can be implemented by parsing system files. */ + res = mhdtl_get_sys_cpu_count_sysconf_ (); +#if ! defined(__linux__) && ! defined(__GLIBC__) + if (0 < res) + return res; +#else /* __linux__ || __GLIBC__ */ + if (2 < res) + return res; + if (0 < res) + { + /* '1' or '2' could a be fallback number. + * See get_nprocs_fallback() in glibc + sysdeps/unix/sysv/linux/getsysstats.c */ + + int proc_cpu_count; + + proc_cpu_count = mhdtl_get_proc_cpu_count (); + if (proc_cpu_count == res) + { + /* The detected number of CPUs available for the process + is equal to the detected number of system CPUs. + Assume detected number is correct. */ + return res; + } + } +#endif /* __linux__ || __GLIBC__ */ + + /* Try available fallbacks */ + + res = mhdtl_get_sys_cpu_count_sysctl_fallback_ (); + if (0 < res) + return res; + + res = mhdtl_get_sys_cpu_count_sysconf_fallback_ (); +#if ! defined(__linux__) && ! defined(__GLIBC__) + if (0 < res) + return res; +#else /* __linux__ || __GLIBC__ */ + if (2 < res) + return res; +#endif /* __linux__ || __GLIBC__ */ + + return -1; /* Cannot detect */ +} diff --git a/src/tools/mhdtl_get_cpu_count.h b/src/tools/mhdtl_get_cpu_count.h @@ -0,0 +1,55 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2023-2025 Evgeny Grin (Karlson2k) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file src/tools/mhdtl_get_cpu_count.h + * @brief Declaration of functions to detect the number of available + * CPU cores. + * @author Karlson2k (Evgeny Grin) + */ + + +#ifndef MHDTL_GET_CPU_COUNT_H_ +#define MHDTL_GET_CPU_COUNT_H_ 1 + + +/** + * Detect the number of logical CPU cores available for the process. + * The number of cores available for this process could be different from + * value of cores available on the system. The OS may have limit on number + * assigned/allowed cores for single process and process may have limited + * CPU affinity. + * @return the number of logical CPU cores available for the process or + * -1 if failed to detect + */ +int +mhdtl_get_proc_cpu_count (void); + + +/** + * Try to detect the number of logical CPU cores available for the system. + * The number of available logical CPU cores could be changed any time due to + * CPU hotplug. + * @return the number of logical CPU cores available, + * -1 if failed to detect. + */ +int +mhdtl_get_system_cpu_count (void); + +#endif /* MHDTL_GET_CPU_COUNT_H_ */ diff --git a/src/tools/mhdtl_str_to_uint.h b/src/tools/mhdtl_str_to_uint.h @@ -0,0 +1,72 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2023-2025 Evgeny Grin (Karlson2k) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @file src/tools/mhdtl_str_to_uint.h + * @brief Function to decode the value of decimal string number. + * @author Karlson2k (Evgeny Grin) + */ + +#ifndef MHDTL_STR_TO_UINT_H_ +#define MHDTL_STR_TO_UINT_H_ 1 + +#include <stddef.h> + +/** + * Convert decimal string to unsigned int. + * Function stops at the end of the string or on first non-digit character. + * @param str the string to convert + * @param[out] value the pointer to put the result + * @return return the number of digits converted or + * zero if no digits found or result would overflow the output + * variable (the output set to UINT_MAX in this case). + */ +static inline size_t +mhdtl_str_to_uint (const char *str, unsigned int *value) +{ + size_t i; + unsigned int v = 0; + + for (i = 0; 0 != str[i]; ++i) + { + const char chr = str[i]; + unsigned int digit; + unsigned int prev_v; + if (('0' > chr) || ('9' < chr)) + break; + digit = (unsigned char) (chr - '0'); + prev_v = v; + v *= 10; + if (v/10 == prev_v) + { + prev_v = v; + v += digit; + if (v >= prev_v) + continue; + } + /* Overflow */ + *value = 0U - 1; + return 0; + } + *value = v; + return i; +} + + +#endif /* MHDTL_STR_TO_UINT_H_ */ diff --git a/src/tools/perf_replies.c b/src/tools/perf_replies.c @@ -0,0 +1,1907 @@ +/* + This file is part of GNU libmicrohttpd + Copyright (C) 2023-2025 Evgeny Grin (Karlson2k) + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions + are met: + 1. Redistributions of source code must retain the above copyright + notice unmodified, this list of conditions and the following + disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + + THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR + IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/** + * @file tools/perf_replies.c + * @brief Implementation of HTTP server optimised for fast replies + * based on MHD2. + * @author Karlson2k (Evgeny Grin) + */ + +#include "mhd_sys_options.h" +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdint.h> +#include "microhttpd2.h" + +#include "mhdtl_get_cpu_count.h" +#include "mhdtl_str_to_uint.h" + +#if defined(MHD_REAL_CPU_COUNT) +# if MHD_REAL_CPU_COUNT == 0 +# undef MHD_REAL_CPU_COUNT +# endif /* MHD_REAL_CPU_COUNT == 0 */ +#endif /* MHD_REAL_CPU_COUNT */ + +#define PERF_RPL_ERR_CODE_BAD_PARAM 65 + +#ifndef mhd_SSTR_LEN +/** + * Determine length of static string / macro strings at compile time. + */ +#define mhd_SSTR_LEN(macro) (sizeof(macro) / sizeof(char) - 1) +#endif /* ! mhd_SSTR_LEN */ + +/* Static constants */ +static const char *const tool_copyright = + "Copyright (C) 2023-2025 Evgeny Grin (Karlson2k)"; + +/* Package or build specific string, like + "Debian 1.2.3-4" or "RevX, built by MSYS2" */ +static const char *const build_revision = "" +#ifdef MHD_BUILD_REV_STR + MHD_BUILD_REV_STR +#endif /* MHD_BUILD_REV_STR */ +; + +#define PERF_REPL_PORT_FALLBACK 48080 + +/* Dynamic variables */ +static char self_name[500] = "perf_replies"; +static uint_least16_t mhd_port = 0; + +static void +set_self_name (int argc, char *const *argv) +{ + if ((argc >= 1) && (NULL != argv[0])) + { + const char *last_dir_sep; + last_dir_sep = strrchr (argv[0], '/'); +#ifdef _WIN32 + if (1) + { + const char *last_w32_dir_sep; + last_w32_dir_sep = strrchr (argv[0], '\\'); + if ((NULL == last_dir_sep) || + ((NULL != last_w32_dir_sep) && (last_w32_dir_sep > last_dir_sep))) + last_dir_sep = last_w32_dir_sep; + } +#endif /* _WIN32 */ + if (NULL != last_dir_sep) + { + size_t name_len; + name_len = strlen (last_dir_sep + 1); + if ((0 != name_len) && ((sizeof(self_name) / sizeof(char)) > name_len)) + { + strcpy (self_name, last_dir_sep + 1); + return; + } + } + } + /* Set default name */ + strcpy (self_name, "perf_replies"); + return; +} + + +static unsigned int +detect_cpu_core_count (void) +{ + int sys_cpu_count; + sys_cpu_count = mhdtl_get_system_cpu_count (); + if (0 >= sys_cpu_count) + { + int proc_cpu_count; + fprintf (stderr, "Failed to detect the number of logical CPU cores " + "available on the system.\n"); + proc_cpu_count = mhdtl_get_proc_cpu_count (); + if (0 < proc_cpu_count) + { + fprintf (stderr, "The number of CPU cores available for this process " + "is used as a fallback.\n"); + sys_cpu_count = proc_cpu_count; + } +#ifdef MHD_REAL_CPU_COUNT + if (0 >= sys_cpu_count) + { + fprintf (stderr, "configure-detected hardcoded number is used " + "as a fallback.\n"); + sys_cpu_count = MHD_REAL_CPU_COUNT; + } +#endif + if (0 >= sys_cpu_count) + sys_cpu_count = 1; + printf ("Assuming %d logical CPU core%s on this system.\n", sys_cpu_count, + (1 == sys_cpu_count) ? "" : "s"); + } + else + { + printf ("Detected %d logical CPU core%s on this system.\n", sys_cpu_count, + (1 == sys_cpu_count) ? "" : "s"); + } + return (unsigned int) sys_cpu_count; +} + + +static unsigned int +get_cpu_core_count (void) +{ + static unsigned int num_cpu_cores = 0; + if (0 == num_cpu_cores) + num_cpu_cores = detect_cpu_core_count (); + return num_cpu_cores; +} + + +static unsigned int +detect_process_cpu_core_count (void) +{ + unsigned int num_proc_cpu_cores; + unsigned int sys_cpu_cores; + int res; + + sys_cpu_cores = get_cpu_core_count (); + res = mhdtl_get_proc_cpu_count (); + if (0 > res) + { + fprintf (stderr, "Cannot detect the number of logical CPU cores available " + "for this process.\n"); + if (1 != sys_cpu_cores) + printf ("Assuming all %u system logical CPU cores are available to run " + "threads of this process.\n", sys_cpu_cores); + else + printf ("Assuming single logical CPU core available for this process.\n"); + num_proc_cpu_cores = sys_cpu_cores; + } + else + { + printf ("Detected %d logical CPU core%s available to run threads " + "of this process.\n", res, (1 == res) ? "" : "s"); + num_proc_cpu_cores = (unsigned int) res; + } + if (num_proc_cpu_cores > sys_cpu_cores) + { + fprintf (stderr, "WARNING: Detected number of CPU cores available " + "for this process (%u) is larger than detected number " + "of CPU cores on the system (%u).\n", + num_proc_cpu_cores, sys_cpu_cores); + num_proc_cpu_cores = sys_cpu_cores; + fprintf (stderr, "Using %u as the number of logical CPU cores available " + "for this process.\n", num_proc_cpu_cores); + } + return num_proc_cpu_cores; +} + + +static unsigned int +get_process_cpu_core_count (void) +{ + static unsigned int proc_num_cpu_cores = 0; + if (0 == proc_num_cpu_cores) + proc_num_cpu_cores = detect_process_cpu_core_count (); + return proc_num_cpu_cores; +} + + +static unsigned int num_threads = 0; + +static unsigned int +get_num_threads (void) +{ + unsigned int sys_cpu_core_count_half; + if (0 < num_threads) + return num_threads; + + sys_cpu_core_count_half = get_cpu_core_count () / 2; + if (0 == sys_cpu_core_count_half) + num_threads = 1; + else + { + unsigned int num_proc_cpus; + num_proc_cpus = get_process_cpu_core_count (); + if (sys_cpu_core_count_half > num_proc_cpus) + { + printf ("Using all CPU cores available for this process as more than " + "half of CPU cores on this system are still available for use " + "by client / requests generator.\n"); + num_threads = num_proc_cpus; + } + else + { + printf ("Using half of all available CPU cores, assuming the other half " + "is used by client / requests generator.\n"); + num_threads = sys_cpu_core_count_half; + } + } +#if 0 /* Disabled code */ + if (1) + { + static const unsigned int max_threads = 32; + if (max_threads < num_threads) + { + printf ("Number of threads are limited to %u as more threads " + "are unlikely to improve the performance.\n", max_threads); + num_threads = max_threads; + } + } +#endif /* Disabled code */ + + return num_threads; +} + + +/** + * The result of short parameters processing + */ +enum PerfRepl_param_result +{ + PERF_RPL_PARAM_ERROR, /**< Error processing parameter */ + PERF_RPL_PARAM_ONE_CHAR, /**< Processed exactly one character */ + PERF_RPL_PARAM_FULL_STR, /**< Processed current parameter completely */ + PERF_RPL_PARAM_STR_PLUS_NEXT /**< Current parameter completely and next parameter processed */ +}; + +/** + * Extract parameter value + * @param param_name the name of the parameter + * @param param_tail the pointer to the character after parameter name in + * the parameter string + * @param next_param the pointer to the next parameter (if any) or NULL + * @param[out] param_value the pointer where to store resulting value + * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by + * this function + */ +static enum PerfRepl_param_result +get_param_value (const char *param_name, const char *param_tail, + const char *next_param, unsigned int *param_value) +{ + const char *value_str; + size_t digits; + if (0 != param_tail[0]) + { + if ('=' != param_tail[0]) + value_str = param_tail; + else + value_str = param_tail + 1; + } + else + value_str = next_param; + + if (NULL != value_str) + digits = mhdtl_str_to_uint (value_str, param_value); + else + digits = 0; + + if ((0 == digits) || (0 != value_str[digits])) + { + fprintf (stderr, "Parameter '%s' is not followed by valid number.\n", + param_name); + return PERF_RPL_PARAM_ERROR; + } + + if (0 != param_tail[0]) + return PERF_RPL_PARAM_FULL_STR; + + return PERF_RPL_PARAM_STR_PLUS_NEXT; +} + + +static void +show_help (void) +{ + union MHD_LibInfoFixedData mhdl_info; + (void) MHD_lib_get_info_fixed (MHD_LIB_INFO_FIXED_TYPES_SOCKETS_POLLING, + &mhdl_info); + printf ("Usage: %s [OPTIONS] [PORT_NUMBER]\n", self_name); + printf ("Start MHD2-based web-server optimised for fast replies.\n"); + printf ("\n"); + printf ("Threads options (mutually exclusive):\n"); + printf (" -A, --all-cpus use all available CPU cores (for \n" + " testing with remote client)\n"); + printf (" -t NUM, --threads=NUM use NUM threads\n"); +#if 0 /* disabled code */ + printf (" -P, --thread-per-conn use thread-per-connection mode,\n" + " the number of threads are limited only\n" + " by the number of connection\n"); +#endif /* disabled code */ + printf ("\n"); + printf ("Force polling function (mutually exclusive):\n"); + if (MHD_NO != mhdl_info.v_types_sockets_polling.tech_epoll) + printf (" -e, --epoll use 'epoll' functionality\n"); + if (MHD_NO != mhdl_info.v_types_sockets_polling.func_poll) + printf (" -p, --poll use poll() function\n"); + if (MHD_NO != mhdl_info.v_types_sockets_polling.func_select) + printf (" -s, --select use select() function\n"); + printf ("\n"); + printf ("Response body size options (mutually exclusive):\n"); + printf (" -E, --empty empty response, 0 bytes\n"); + printf (" -T, --tiny tiny response, 3 bytes (default)\n"); + printf (" -M, --medium medium response, 8 KB\n"); + printf (" -L, --large large response, 1 MB\n"); + printf ("\n"); + printf ("Response use options (mutually exclusive):\n"); + printf (" -S, --shared pool of pre-generated shared response\n" + " objects (default)\n"); + printf (" -I, --single single pre-generated response object\n" + " used for all requests\n"); + printf (" -U, --unique response object generated for every\n" + " request and used one time only\n"); + printf ("\n"); + printf ("Other options:\n"); + printf (" -c NUM, --connections=NUM reject more than NUM client \n" + " connections\n"); + printf (" -O NUM, --timeout=NUM set connection timeout to NUM seconds,\n" + " zero means no timeout\n"); + printf (" --date-header use the 'Date:' header in every\n" + " reply\n"); + printf (" --help display this help and exit\n"); + printf (" -V, --version output version information and exit\n"); + printf ("\n"); + printf ("This tool is part of GNU libmicrohttpd suite.\n"); + printf ("%s\n", tool_copyright); + if ((MHD_NO == mhdl_info.v_types_sockets_polling.func_select) && + (MHD_NO == mhdl_info.v_types_sockets_polling.func_poll) && + (MHD_NO == mhdl_info.v_types_sockets_polling.tech_epoll)) + fprintf (stderr, "No internal sockets polling function available!\n"); +} + + +struct PerfRepl_parameters +{ + unsigned int port; + int all_cpus; + unsigned int threads; + int thread_per_conn; + int epoll; + int poll; + int select; + int empty; + int tiny; + int medium; + int large; + int shared; + int single; + int unique; + unsigned int connections; + unsigned int timeout; + int date_header; + int help; + int version; +}; + +static struct PerfRepl_parameters tool_params = { + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 +}; + + +static enum PerfRepl_param_result +process_param__all_cpus (const char *param_name) +{ + if (0 != tool_params.threads) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-t' or '--threads'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.thread_per_conn) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-P' or '--thread-per-conn'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.all_cpus = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +/** + * Process parameter '-t' or '--threads' + * @param param_name the name of the parameter as specified in command line + * @param param_tail the pointer to the character after parameter name in + * the parameter string + * @param next_param the pointer to the next parameter (if any) or NULL + * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by + * this function + */ +static enum PerfRepl_param_result +process_param__threads (const char *param_name, const char *param_tail, + const char *next_param) +{ + unsigned int param_value; + enum PerfRepl_param_result value_res; + + if (tool_params.all_cpus) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-A' or '--all-cpus'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.thread_per_conn) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-P' or '--thread-per-conn'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + value_res = get_param_value (param_name, param_tail, next_param, + &param_value); + if (PERF_RPL_PARAM_ERROR == value_res) + return value_res; + + if (0 == param_value) + { + fprintf (stderr, "'0' is not valid value for parameter '%s'.\n", + param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.threads = param_value; + return value_res; +} + + +#if 0 /* disabled code */ +static enum PerfRepl_param_result +process_param__thread_per_conn (const char *param_name) +{ + if (tool_params.all_cpus) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-A' or '--all-cpus'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (0 != tool_params.threads) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-t' or '--threads'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.thread_per_conn = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} +#endif /* disabled code */ + + +static enum PerfRepl_param_result +process_param__epoll (const char *param_name) +{ + if (tool_params.poll) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-p' or '--poll'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.select) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-s' or '--select'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.epoll = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__poll (const char *param_name) +{ + if (tool_params.epoll) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-e' or '--epoll'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.select) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-s' or '--select'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.poll = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__select (const char *param_name) +{ + if (tool_params.epoll) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-e' or '--epoll'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.poll) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-p' or '--poll'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.select = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__empty (const char *param_name) +{ + if (tool_params.tiny) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-T' or '--tiny'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.medium) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-M' or '--medium'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.large) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-L' or '--large'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.empty = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__tiny (const char *param_name) +{ + if (tool_params.empty) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-E' or '--empty'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.medium) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-M' or '--medium'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.large) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-L' or '--large'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.tiny = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__medium (const char *param_name) +{ + if (tool_params.empty) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-E' or '--empty'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.tiny) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-T' or '--tiny'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.large) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-L' or '--large'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.medium = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__large (const char *param_name) +{ + if (tool_params.empty) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-E' or '--empty'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.tiny) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-T' or '--tiny'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.medium) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-M' or '--medium'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.large = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__shared (const char *param_name) +{ + if (tool_params.single) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-I' or '--single'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.unique) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-U' or '--unique'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.shared = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__single (const char *param_name) +{ + if (tool_params.shared) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-S' or '--shared'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.unique) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-U' or '--unique'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.single = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__unique (const char *param_name) +{ + if (tool_params.shared) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-S' or '--shared'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + if (tool_params.single) + { + fprintf (stderr, "Parameter '%s' cannot be used together " + "with '-I' or '--single'.\n", param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.unique = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +/** + * Process parameter '-c' or '--connections' + * @param param_name the name of the parameter as specified in command line + * @param param_tail the pointer to the character after parameter name in + * the parameter string + * @param next_param the pointer to the next parameter (if any) or NULL + * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by + * this function + */ +static enum PerfRepl_param_result +process_param__connections (const char *param_name, const char *param_tail, + const char *next_param) +{ + unsigned int param_value; + enum PerfRepl_param_result value_res; + + value_res = get_param_value (param_name, param_tail, next_param, + &param_value); + if (PERF_RPL_PARAM_ERROR == value_res) + return value_res; + + if (0 == param_value) + { + fprintf (stderr, "'0' is not valid value for parameter '%s'.\n", + param_name); + return PERF_RPL_PARAM_ERROR; + } + tool_params.connections = param_value; + return value_res; +} + + +/** + * Process parameter '-O' or '--timeout' + * @param param_name the name of the parameter as specified in command line + * @param param_tail the pointer to the character after parameter name in + * the parameter string + * @param next_param the pointer to the next parameter (if any) or NULL + * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by + * this function + */ +static enum PerfRepl_param_result +process_param__timeout (const char *param_name, const char *param_tail, + const char *next_param) +{ + unsigned int param_value; + enum PerfRepl_param_result value_res; + + value_res = get_param_value (param_name, param_tail, next_param, + &param_value); + if (PERF_RPL_PARAM_ERROR == value_res) + return value_res; + + tool_params.timeout = param_value; + return value_res; +} + + +static enum PerfRepl_param_result +process_param__date_header (const char *param_name) +{ + tool_params.date_header = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__help (const char *param_name) +{ + /* Use only one of help | version */ + if (! tool_params.version) + tool_params.help = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +static enum PerfRepl_param_result +process_param__version (const char *param_name) +{ + /* Use only one of help | version */ + if (! tool_params.help) + tool_params.version = ! 0; + return '-' == param_name[1] ? + PERF_RPL_PARAM_FULL_STR :PERF_RPL_PARAM_ONE_CHAR; +} + + +/** + * Process "short" parameter (like "-d"). + * @param param the pointer to character after "-" or after another valid + * parameter + * @param next_param the pointer to the next parameter (if any) or + * NULL if no next parameter + * @return enum value with result + */ +static enum PerfRepl_param_result +process_short_param (const char *param, const char *next_param) +{ + const char param_chr = param[0]; + if ('A' == param_chr) + return process_param__all_cpus ("-A"); + else if ('t' == param_chr) + return process_param__threads ("-t", param + 1, next_param); +#if 0 /* disabled code */ + else if ('P' == param_chr) + return process_param__thread_per_conn ("-P"); +#endif /* disabled code */ + else if ('e' == param_chr) + return process_param__epoll ("-e"); + else if ('p' == param_chr) + return process_param__poll ("-p"); + else if ('s' == param_chr) + return process_param__select ("-s"); + else if ('E' == param_chr) + return process_param__empty ("-E"); + else if ('T' == param_chr) + return process_param__tiny ("-T"); + else if ('M' == param_chr) + return process_param__medium ("-M"); + else if ('L' == param_chr) + return process_param__large ("-L"); + else if ('S' == param_chr) + return process_param__shared ("-S"); + else if ('I' == param_chr) + return process_param__single ("-I"); + else if ('U' == param_chr) + return process_param__unique ("-U"); + else if ('c' == param_chr) + return process_param__connections ("-c", param + 1, next_param); + else if ('O' == param_chr) + return process_param__timeout ("-O", param + 1, next_param); + else if ('V' == param_chr) + return process_param__version ("-V"); + + fprintf (stderr, "Unrecognised parameter: -%c.\n", param_chr); + return PERF_RPL_PARAM_ERROR; +} + + +/** + * Process string of "short" (one character) parameters. + * @param params_str the pointer to first character after "-" + * @param next_param the pointer to the next parameter (if any) or + * NULL if no next parameter + * @return enum value with result + */ +static enum PerfRepl_param_result +process_short_params_str (const char *params_str, const char *next_param) +{ + if (0 == params_str[0]) + { + fprintf (stderr, "Unrecognised parameter: -\n"); + return PERF_RPL_PARAM_ERROR; + } + do + { + enum PerfRepl_param_result param_res; + param_res = process_short_param (params_str, next_param); + if (PERF_RPL_PARAM_ONE_CHAR != param_res) + return param_res; + } while (0 != (++params_str)[0]); + return PERF_RPL_PARAM_FULL_STR; +} + + +/** + * Process "long" parameters (like "--something"). + * @param param the pointer to first character after "--" + * @param next_param the pointer to the next parameter (if any) or + * NULL if no next parameter + * @return enum value, the PERF_PERPL_SPARAM_ONE_CHAR is not used by + * this function + */ +static enum PerfRepl_param_result +process_long_param (const char *param, const char *next_param) +{ + const size_t param_len = strlen (param); + + if ((mhd_SSTR_LEN ("all-cpus") == param_len) && + (0 == memcmp (param, "all-cpus", mhd_SSTR_LEN ("all-cpus")))) + return process_param__all_cpus ("--all-cpus"); + else if ((mhd_SSTR_LEN ("threads") <= param_len) && + (0 == memcmp (param, "threads", mhd_SSTR_LEN ("threads")))) + return process_param__threads ("--threads", + param + mhd_SSTR_LEN ("threads"), + next_param); +#if 0 /* disabled code */ + else if ((mhd_SSTR_LEN ("thread-per-conn") == param_len) && + (0 == memcmp (param, "thread-per-conn", + mhd_SSTR_LEN ("thread-per-conn")))) + return process_param__thread_per_conn ("--thread-per-conn"); +#endif /* disabled code */ + else if ((mhd_SSTR_LEN ("epoll") == param_len) && + (0 == memcmp (param, "epoll", mhd_SSTR_LEN ("epoll")))) + return process_param__epoll ("--epoll"); + else if ((mhd_SSTR_LEN ("poll") == param_len) && + (0 == memcmp (param, "poll", mhd_SSTR_LEN ("poll")))) + return process_param__poll ("--poll"); + else if ((mhd_SSTR_LEN ("select") == param_len) && + (0 == memcmp (param, "select", mhd_SSTR_LEN ("select")))) + return process_param__select ("--select"); + else if ((mhd_SSTR_LEN ("empty") == param_len) && + (0 == memcmp (param, "empty", mhd_SSTR_LEN ("empty")))) + return process_param__empty ("--empty"); + else if ((mhd_SSTR_LEN ("tiny") == param_len) && + (0 == memcmp (param, "tiny", mhd_SSTR_LEN ("tiny")))) + return process_param__tiny ("--tiny"); + else if ((mhd_SSTR_LEN ("medium") == param_len) && + (0 == memcmp (param, "medium", mhd_SSTR_LEN ("medium")))) + return process_param__medium ("--medium"); + else if ((mhd_SSTR_LEN ("large") == param_len) && + (0 == memcmp (param, "large", mhd_SSTR_LEN ("large")))) + return process_param__large ("--large"); + else if ((mhd_SSTR_LEN ("shared") == param_len) && + (0 == memcmp (param, "shared", mhd_SSTR_LEN ("shared")))) + return process_param__shared ("--shared"); + else if ((mhd_SSTR_LEN ("single") == param_len) && + (0 == memcmp (param, "single", mhd_SSTR_LEN ("single")))) + return process_param__single ("--single"); + else if ((mhd_SSTR_LEN ("unique") == param_len) && + (0 == memcmp (param, "unique", mhd_SSTR_LEN ("unique")))) + return process_param__unique ("--unique"); + else if ((mhd_SSTR_LEN ("connections") <= param_len) && + (0 == memcmp (param, "connections", + mhd_SSTR_LEN ("connections")))) + return process_param__connections ("--connections", + param + + mhd_SSTR_LEN ("connections"), + next_param); + else if ((mhd_SSTR_LEN ("timeout") <= param_len) && + (0 == memcmp (param, "timeout", + mhd_SSTR_LEN ("timeout")))) + return process_param__timeout ("--timeout", + param + mhd_SSTR_LEN ("timeout"), + next_param); + else if ((mhd_SSTR_LEN ("date-header") == param_len) && + (0 == memcmp (param, "date-header", + mhd_SSTR_LEN ("date-header")))) + return process_param__date_header ("--date-header"); + else if ((mhd_SSTR_LEN ("help") == param_len) && + (0 == memcmp (param, "help", mhd_SSTR_LEN ("help")))) + return process_param__help ("--help"); + else if ((mhd_SSTR_LEN ("version") == param_len) && + (0 == memcmp (param, "version", mhd_SSTR_LEN ("version")))) + return process_param__version ("--version"); + + fprintf (stderr, "Unrecognised parameter: --%s.\n", param); + return PERF_RPL_PARAM_ERROR; +} + + +static int +process_params (int argc, char *const *argv) +{ + int proc_dash_param = ! 0; + int i; + for (i = 1; i < argc; ++i) + { + /** + * The currently processed argument + */ + const char *const p = argv[i]; + const char *const p_next = (argc == (i + 1)) ? NULL : (argv[i + 1]); + if (NULL == p) + { + fprintf (stderr, "The NULL in the parameter number %d. " + "The error in the C library?\n", i); + continue; + } + else if (0 == p[0]) + continue; /* Empty */ + else if (proc_dash_param && ('-' == p[0])) + { + enum PerfRepl_param_result param_res; + if ('-' == p[1]) + { + if (0 == p[2]) + { + proc_dash_param = 0; /* The '--' parameter */ + continue; + } + param_res = process_long_param (p + 2, p_next); + } + else + param_res = process_short_params_str (p + 1, p_next); + + if (PERF_RPL_PARAM_ERROR == param_res) + return PERF_RPL_ERR_CODE_BAD_PARAM; + if (PERF_RPL_PARAM_STR_PLUS_NEXT == param_res) + ++i; + else if (PERF_RPL_PARAM_ONE_CHAR == param_res) + abort (); + continue; + } + else if (('0' <= p[0]) && ('9' >= p[0])) + { + /* Process the port number */ + unsigned int read_port; + size_t num_digits; + num_digits = mhdtl_str_to_uint (p, &read_port); + if (0 != p[num_digits]) + { + fprintf (stderr, "Error in specified port number: %s\n", p); + return PERF_RPL_ERR_CODE_BAD_PARAM; + } + else if (65535 < read_port) + { + fprintf (stderr, "Wrong port number: %s\n", p); + return PERF_RPL_ERR_CODE_BAD_PARAM; + } + mhd_port = (uint_least16_t) read_port; + } + else + { + fprintf (stderr, "Unrecognised parameter: %s\n\n", p); + return PERF_RPL_ERR_CODE_BAD_PARAM; + } + } + return 0; +} + + +static void +print_version (void) +{ + union MHD_LibInfoFixedData mhdl_data; + (void) MHD_lib_get_info_fixed (MHD_LIB_INFO_FIXED_VERSION_STRING, + &mhdl_data); + printf ("%s (GNU libmicrohttpd2", self_name); + if (0 != build_revision[0]) + printf ("; %s", build_revision); + printf (") %s\n", mhdl_data.v_version_string.cstr); + printf ("%s\n", tool_copyright); +} + + +static void +print_all_cores_used (void) +{ + printf ("No CPU cores on this machine are left unused and available " + "for the client / requests generator. " + "Testing with remote client is recommended.\n"); +} + + +/** + * Apply parameter '-A' or '--all-cpus' + */ +static void +check_apply_param__all_cpus (void) +{ + if (! tool_params.all_cpus) + return; + + num_threads = get_process_cpu_core_count (); + printf ("Requested use of all available CPU cores for MHD threads.\n"); + if (get_cpu_core_count () == num_threads) + print_all_cores_used (); +} + + +/** + * Apply parameter '-t' or '--threads' + */ +static void +check_apply_param__threads (void) +{ + if (0 == tool_params.threads) + return; + + num_threads = tool_params.threads; + + if (get_process_cpu_core_count () < num_threads) + { + fprintf (stderr, "WARNING: The requested number of threads (%u) is " + "higher than the number of detected available CPU cores (%u).\n", + num_threads, get_process_cpu_core_count ()); + fprintf (stderr, "This decreases the performance. " + "Consider using fewer threads.\n"); + } + if (get_cpu_core_count () == num_threads) + { + printf ("The requested number of threads is equal to the number of " + "detected CPU cores.\n"); + print_all_cores_used (); + } +} + +#if 0 /* disabled code */ + +/** + * Apply parameter '-P' or '--thread-per-conn' + * @return non-zero - OK, zero - error + */ +static int +check_apply_param__thread_per_conn (void) +{ + if (! tool_params.thread_per_conn) + return ! 0; + + if (tool_params.epoll) + { + fprintf (stderr, "'Thread-per-connection' mode cannot be used together " + "with 'epoll'.\n"); + return 0; + } + num_threads = 1; + + return ! 0; +} +#endif /* disabled code */ + + +/* non-zero - OK, zero - error */ +static int +check_param__epoll (void) +{ + union MHD_LibInfoFixedData mhdl_info; + if (! tool_params.epoll) + return ! 0; + (void) MHD_lib_get_info_fixed (MHD_LIB_INFO_FIXED_TYPES_SOCKETS_POLLING, + &mhdl_info); + if (MHD_NO == mhdl_info.v_types_sockets_polling.tech_epoll) + { + fprintf (stderr, "'epoll' was requested, but this MHD build does not " + "support 'epoll' functionality.\n"); + return 0; + } + return ! 0; +} + + +/* non-zero - OK, zero - error */ +static int +check_param__poll (void) +{ + union MHD_LibInfoFixedData mhdl_info; + if (! tool_params.poll) + return ! 0; + (void) MHD_lib_get_info_fixed (MHD_LIB_INFO_FIXED_TYPES_SOCKETS_POLLING, + &mhdl_info); + if (MHD_NO == mhdl_info.v_types_sockets_polling.func_poll) + { + fprintf (stderr, "poll() was requested, but this MHD build does not " + "support polling by poll().\n"); + return 0; + } + return ! 0; +} + + +/* non-zero - OK, zero - error */ +static int +check_param__select (void) +{ + union MHD_LibInfoFixedData mhdl_info; + if (! tool_params.poll) + return ! 0; + (void) MHD_lib_get_info_fixed (MHD_LIB_INFO_FIXED_TYPES_SOCKETS_POLLING, + &mhdl_info); + if (MHD_NO == mhdl_info.v_types_sockets_polling.func_select) + { + fprintf (stderr, "select() was requested, but this MHD build does not " + "support polling by select().\n"); + return 0; + } + return ! 0; +} + + +static void +check_param__empty_tiny_medium_large (void) +{ + if (0 == (tool_params.empty | tool_params.tiny | tool_params.medium + | tool_params.large)) + tool_params.tiny = ! 0; +} + + +static void +check_param__shared_single_unique (void) +{ + if (0 == (tool_params.shared | tool_params.single | tool_params.unique)) + tool_params.shared = ! 0; +} + + +/* Must be called after 'check_apply_param__threads()' and + 'check_apply_param__all_cpus()' */ +/* non-zero - OK, zero - error */ +static int +check_param__connections (void) +{ + if (0 == tool_params.connections) + return ! 0; + if (get_num_threads () > tool_params.connections) + { + fprintf (stderr, "The connections number limit (%u) is less than number " + "of threads used (%u). Use higher value for connections limit.\n", + tool_params.connections, get_num_threads ()); + return 0; + } + return ! 0; +} + + +/** + * Apply decoded parameters + * @return 0 if success, + * positive error code if case of error, + * -1 to exit program with success (0) error code. + */ +static int +check_apply_params (void) +{ + if (tool_params.help) + { + show_help (); + return -1; + } + else if (tool_params.version) + { + print_version (); + return -1; + } + check_apply_param__all_cpus (); + check_apply_param__threads (); +#if 0 /* disabled code */ + if (! check_apply_param__thread_per_conn ()) + return PERF_RPL_ERR_CODE_BAD_PARAM; +#endif /* disabled code */ + if (! check_param__epoll ()) + return PERF_RPL_ERR_CODE_BAD_PARAM; + if (! check_param__poll ()) + return PERF_RPL_ERR_CODE_BAD_PARAM; + if (! check_param__select ()) + return PERF_RPL_ERR_CODE_BAD_PARAM; + check_param__empty_tiny_medium_large (); + check_param__shared_single_unique (); + if (! check_param__connections ()) + return PERF_RPL_ERR_CODE_BAD_PARAM; + return 0; +} + + +/* The pool of shared responses */ +static struct MHD_Response **resps = NULL; +static unsigned int num_resps = 0; +/* The single response */ +static struct MHD_Response *resp_single = NULL; + +/* Use the same memory area to avoid multiple copies. + The system will keep it in cache. */ +static const char tiny_body[] = "Hi!"; +static char *body_dyn = NULL; /* Non-static body data */ +static size_t body_dyn_size; + +/* Non-zero - success, zero - failure */ +static int +init_response_body_data (void) +{ + if (0 != body_dyn_size) + { + body_dyn = (char *) malloc (body_dyn_size); + if (NULL == body_dyn) + { + fprintf (stderr, "Failed to allocate memory.\n"); + return 0; + } + if (tool_params.medium) + { + /* Fill the body with HTML-like content */ + size_t pos; + size_t filler_pos; + static const char body_header[] = + "<html>\n" + "<head>\n<title>Sample page title</title>\n<head>\n" + "<body>\n"; + static const char body_filler[] = + "The quick brown fox jumps over the lazy dog.<br>\n"; + static const char body_footer[] = + "</body>\n" + "</html>\n"; + pos = 0; + memcpy (body_dyn + pos, body_header, mhd_SSTR_LEN (body_header)); + pos += mhd_SSTR_LEN (body_header); + for (filler_pos = 0; + filler_pos < (body_dyn_size - (mhd_SSTR_LEN (body_header) + + mhd_SSTR_LEN (body_footer))); + ++filler_pos) + { + body_dyn[pos + filler_pos] = + body_filler[filler_pos % mhd_SSTR_LEN (body_filler)]; + } + pos += filler_pos; + memcpy (body_dyn + pos, body_footer, mhd_SSTR_LEN (body_footer)); + } + else + { + /* Fill the body with binary-like content */ + size_t pos; + for (pos = 0; pos < body_dyn_size; ++pos) + body_dyn[pos] = (char) (unsigned char) (255U - pos % 256U); + } + } + return ! 0; +} + + +static struct MHD_Response * +create_reusable_response_object (void) +{ + struct MHD_Response *r; + if (NULL != body_dyn) + r = MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK, + body_dyn_size, + body_dyn); + else if (tool_params.empty) + r = MHD_response_from_empty (MHD_HTTP_STATUS_OK); + else + r = MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK, + mhd_SSTR_LEN (tiny_body), + tiny_body); + if (NULL != r) + { + if (MHD_SC_OK != + MHD_RESPONSE_SET_OPTIONS(r, + MHD_R_OPTION_REUSABLE (MHD_YES))) + { + MHD_response_destroy (r); + r = NULL; + } + } + return r; +} + + +static int +init_data (void) +{ + unsigned int i; + + if (tool_params.medium) + body_dyn_size = 8U * 1024U; + else if (tool_params.large) + body_dyn_size = 1024U * 1024U; + else + body_dyn_size = 0; + + if (! init_response_body_data ()) + return 25; + + if (tool_params.unique) + return 0; /* Responses are generated on-fly */ + + if (tool_params.single) + { + resp_single = create_reusable_response_object (); + if (NULL == resp_single) + { + fprintf (stderr, "Failed to create response.\n"); + return 25; + } + return 0; + } + + /* Use more responses to minimise waiting in threads while the response + used by other thread. */ + if (! tool_params.thread_per_conn) + num_resps = 16 * get_num_threads (); + else + num_resps = 16 * get_cpu_core_count (); + + resps = (struct MHD_Response **) + malloc ((sizeof(struct MHD_Response *)) * num_resps); + if (NULL == resps) + { + if (NULL != body_dyn) + { + free (body_dyn); + body_dyn = NULL; + } + fprintf (stderr, "Failed to allocate memory.\n"); + return 25; + } + for (i = 0; i < num_resps; ++i) + { + resps[i] = create_reusable_response_object (); + if (NULL == resps[i]) + { + fprintf (stderr, "Failed to create responses.\n"); + break; + } + } + if (i == num_resps) + return 0; /* Success */ + + /* Cleanup */ + while (--i < num_resps) + MHD_response_destroy (resps[i]); + free (resps); + resps = NULL; + num_resps = 0; + if (NULL != body_dyn) + free (body_dyn); + body_dyn = NULL; + return 32; +} + + +static void +deinit_data (void) +{ + if (NULL != resp_single) + MHD_response_destroy (resp_single); + resp_single = NULL; + if (NULL != resps) + { + unsigned int i; + for (i = 0; i < num_resps; ++i) + MHD_response_destroy (resps[i]); + num_resps = 0; + free (resps); + } + resps = NULL; + if (NULL != body_dyn) + free (body_dyn); + body_dyn = NULL; +} + + +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) +static const struct MHD_Action * +answer_shared_response (void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + unsigned int resp_index; + static volatile unsigned int last_index = 0; + (void) cls; /* Unused */ + (void) path; /* Unused */ + (void) upload_size; /* Unused */ + + if ((MHD_HTTP_METHOD_GET != method) && + (MHD_HTTP_METHOD_HEAD != method)) + return MHD_action_abort_request (request); /* Unsupported method, close connection */ + + /* This kind of operation does not guarantee that numbers are not reused + in parallel threads, when processed simultaneously, but this should not + be a big problem, as all responses are valid anyways. */ + resp_index = (last_index++) % num_resps; + return MHD_action_from_response (request, + resps[resp_index]); +} + + +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) +static const struct MHD_Action * +answer_single_response (void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + (void) cls; /* Unused */ + (void) path; /* Unused */ + (void) upload_size; /* Unused */ + + if ((MHD_HTTP_METHOD_GET != method) && + (MHD_HTTP_METHOD_HEAD != method)) + return MHD_action_abort_request (request); /* Unsupported method, close connection */ + + return MHD_action_from_response (request, + resp_single); +} + + +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) +static const struct MHD_Action * +answer_unique_empty_response (void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + (void) cls; /* Unused */ + (void) path; /* Unused */ + (void) upload_size; /* Unused */ + + if ((MHD_HTTP_METHOD_GET != method) && + (MHD_HTTP_METHOD_HEAD != method)) + return MHD_action_abort_request (request); /* Unsupported method, close connection */ + + return + MHD_action_from_response (request, + MHD_response_from_empty (MHD_HTTP_STATUS_OK)); +} + + +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) +static const struct MHD_Action * +answer_unique_tiny_response (void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + (void) cls; /* Unused */ + (void) path; /* Unused */ + (void) upload_size; /* Unused */ + + if ((MHD_HTTP_METHOD_GET != method) && + (MHD_HTTP_METHOD_HEAD != method)) + return MHD_action_abort_request (request); /* Unsupported method, close connection */ + + return + MHD_action_from_response ( + request, + MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK, + mhd_SSTR_LEN (tiny_body), + tiny_body)); +} + + +MHD_FN_PAR_NONNULL_ (2) MHD_FN_PAR_NONNULL_ (3) +static const struct MHD_Action * +answer_unique_dyn_response (void *cls, + struct MHD_Request *MHD_RESTRICT request, + const struct MHD_String *MHD_RESTRICT path, + enum MHD_HTTP_Method method, + uint_fast64_t upload_size) +{ + (void) cls; /* Unused */ + (void) path; /* Unused */ + (void) upload_size; /* Unused */ + + if ((MHD_HTTP_METHOD_GET != method) && + (MHD_HTTP_METHOD_HEAD != method)) + return MHD_action_abort_request (request); /* Unsupported method, close connection */ + + return + MHD_action_from_response ( + request, + MHD_response_from_buffer_static (MHD_HTTP_STATUS_OK, + body_dyn_size, + body_dyn)); +} + + +static void +print_perf_warnings (void) +{ + int newline_needed = 0; +#if defined (_DEBUG) + fprintf (stderr, "WARNING: Running with debug asserts enabled, " + "the performance is suboptimal.\n"); + newline_needed |= ! 0; +#endif /* _DEBUG */ +#if defined(__GNUC__) && ! defined (__OPTIMIZE__) + fprintf (stderr, "WARNING: The tools is compiled without compiler " + "optimisations enabled, the performance is suboptimal.\n"); + newline_needed |= ! 0; +#endif /* __GNUC__ && ! __OPTIMIZE__ */ +#if defined(__GNUC__) && defined (__OPTIMIZE_SIZE__) + fprintf (stderr, "WARNING: The tools is compiled with size-optimisations, " + "the performance is suboptimal.\n"); + newline_needed |= ! 0; +#endif /* __GNUC__ && ! __OPTIMIZE__ */ + if (1) + { + union MHD_LibInfoFixedData mhdl_info; + (void) MHD_lib_get_info_fixed (MHD_LIB_INFO_FIXED_IS_NON_DEBUG, + &mhdl_info); + if (MHD_NO == mhdl_info.v_is_non_debug_bool) + { + fprintf (stderr, "WARNING: The libmicrohttpd2 library is compiled with " + "debug asserts enabled, the performance is suboptimal.\n"); + newline_needed |= ! 0; + } + } + if (newline_needed) + printf ("\n"); +} + + + +static const char * +get_mhd_poll_func_name (struct MHD_Daemon *d) +{ + union MHD_DaemonInfoFixedData d_info; + d_info.v_global_connection_limit_uint = 0; + if (MHD_SC_OK != + MHD_daemon_get_info_fixed (d, + MHD_DAEMON_INFO_FIXED_POLL_SYSCALL, + &d_info)) + abort (); + + switch (d_info.v_poll_syscall) + { + case MHD_SPS_SELECT: + return "select()"; + case MHD_SPS_POLL: + return "poll()"; + case MHD_SPS_EPOLL: + return "epoll"; + case MHD_SPS_AUTO: + default: + break; + } + return "[unkonwn]"; +} + + +static uint_least16_t +get_mhd_bind_port (struct MHD_Daemon *d) +{ + union MHD_DaemonInfoFixedData d_info; + enum MHD_StatusCode res; + res = MHD_daemon_get_info_fixed (d, + MHD_DAEMON_INFO_FIXED_BIND_PORT, + &d_info); + if (MHD_SC_INFO_GET_TYPE_UNOBTAINABLE == res) + return 0; + if (MHD_SC_OK != res) + abort (); + return d_info.v_bind_port_uint16; +} + + +static unsigned int +get_mhd_num_threads (struct MHD_Daemon *d) +{ + union MHD_DaemonInfoFixedData d_info; + if (MHD_SC_OK != + MHD_daemon_get_info_fixed (d, + MHD_DAEMON_INFO_FIXED_NUM_WORK_THREADS, + &d_info)) + abort (); + return d_info.v_num_work_threads_uint; +} + + +static unsigned int +get_mhd_conn_limit (struct MHD_Daemon *d) +{ + union MHD_DaemonInfoFixedData d_info; + if (MHD_SC_OK != + MHD_daemon_get_info_fixed (d, + MHD_DAEMON_INFO_FIXED_GLOBAL_CONNECTION_LIMIT, + &d_info)) + abort (); + return d_info.v_global_connection_limit_uint; +} + + +static unsigned int +get_mhd_def_timeout (struct MHD_Daemon *d) +{ + union MHD_DaemonInfoFixedData d_info; + if (MHD_SC_OK != + MHD_daemon_get_info_fixed (d, + MHD_DAEMON_INFO_FIXED_DEFAULT_TIMEOUT, + &d_info)) + abort (); + return d_info.v_default_timeout_uint; +} + + +static int +get_mhd_suppr_date (struct MHD_Daemon *d) +{ + union MHD_DaemonInfoFixedData d_info; + if (MHD_SC_OK != + MHD_daemon_get_info_fixed (d, + MHD_DAEMON_INFO_FIXED_SUPPRESS_DATE_HEADER, + &d_info)) + abort (); + return (int) d_info.v_suppress_date_header_bool; +} + + +static const char * +get_mhd_response_size (void) +{ + if (tool_params.empty) + return "0 bytes (empty)"; + else if (tool_params.tiny) + return "3 bytes (tiny)"; + else if (tool_params.medium) + return "8 KB (medium)"; + else if (tool_params.large) + return "1 MB (large)"; + return "!!internal error!!"; +} + + +static int +run_mhd (void) +{ + MHD_RequestCallback reply_func; + struct MHD_Daemon *d; + struct MHD_DaemonOptionAndValue opt_arr[16]; + size_t opt_count = 0; + uint_least16_t port; + unsigned int num_requsted_threads = 0; + unsigned int num_used_threads; + + /* Make sure that the detection message is printed already */ +#if 0 /* Disabled code */ + if (! tool_params.thread_per_conn) +#endif /* Disabled code */ + num_requsted_threads = get_num_threads (); + + printf ("\n"); + + print_perf_warnings (); + + printf ("Responses:\n"); + printf (" Sharing: "); + if (tool_params.shared) + { + reply_func = &answer_shared_response; + printf ("pre-generated shared pool with %u objects\n", num_resps); + } + else if (tool_params.single) + { + reply_func = &answer_single_response; + printf ("single pre-generated reused response object\n"); + } + else + { + /* Unique responses */ + if (tool_params.empty) + reply_func = &answer_unique_empty_response; + else if (tool_params.tiny) + reply_func = &answer_unique_tiny_response; + else + reply_func = &answer_unique_dyn_response; + printf ("one-time response object generated for every request\n"); + } + printf (" Body size: %s\n", + get_mhd_response_size ()); + + opt_arr[opt_count++] = MHD_D_OPTION_BIND_PORT (MHD_AF_AUTO, mhd_port); + if (tool_params.epoll) + opt_arr[opt_count++] = MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_EPOLL); + else if (tool_params.poll) + opt_arr[opt_count++] = MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_POLL); + else if (tool_params.select) + opt_arr[opt_count++] = MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_SELECT); + else + opt_arr[opt_count++] = MHD_D_OPTION_POLL_SYSCALL (MHD_SPS_AUTO); + +#if 0 /* Disabled code */ + if (tool_params.thread_per_conn) + opt_arr[opt_count++] = MHD_D_OPTION_WM_THREAD_PER_CONNECTION (); + else +#endif /* Disabled code */ + opt_arr[opt_count++] = MHD_D_OPTION_WM_WORKER_THREADS (num_requsted_threads); + + if (! tool_params.date_header) + opt_arr[opt_count++] = MHD_D_OPTION_SUPPRESS_DATE_HEADER(MHD_YES); + + if (0 != tool_params.connections) + opt_arr[opt_count++] = + MHD_D_OPTION_GLOBAL_CONNECTION_LIMIT (tool_params.connections); + + opt_arr[opt_count++] = MHD_D_OPTION_DEFAULT_TIMEOUT (tool_params.timeout); + + if (opt_count > (sizeof(opt_arr) / sizeof(opt_arr[0]))) + abort (); + + d = MHD_daemon_create (reply_func, + NULL); + if (NULL == d) + { + fprintf (stderr, "Error creating MHD2 daemon.\n"); + return 15; + } + + if (MHD_SC_OK != MHD_daemon_set_options (d, + opt_arr, + opt_count)) + { + fprintf (stderr, "Error setting daemon options.\n"); + MHD_daemon_destroy (d); + return 15; + } + if (MHD_SC_OK != MHD_daemon_start (d)) + { + fprintf (stderr, "Error starting MHD2 daemon.\n"); + MHD_daemon_destroy (d); + return 15; + } + port = get_mhd_bind_port (d); + if (0 == port) + port = mhd_port; + if (0 == port) + fprintf (stderr, "Cannot detect port number. Consider specifying " + "port number explicitly.\n"); + num_used_threads = get_mhd_num_threads (d); + + printf ("MHD2 daemon is running.\n"); + printf (" Bind port: %u\n", (unsigned int) port); + printf (" Polling function: %s\n", get_mhd_poll_func_name (d)); + printf (" Threading: "); +#if 0 /* Disabled code */ + if (MHD_USE_THREAD_PER_CONNECTION == (flags & MHD_USE_THREAD_PER_CONNECTION)) + printf ("thread per connection\n"); + else +#endif /* Disabled code */ + if (1 == num_used_threads) + printf ("one MHD thread\n"); + else + printf ("%u MHD threads in thread pool\n", num_used_threads); + printf (" Connections limit: %u\n", get_mhd_conn_limit (d)); + if (1) + { + unsigned int def_timeout = get_mhd_def_timeout (d); + printf (" Connection timeout: %u%s\n", def_timeout, + 0 == def_timeout ? " (no timeout)" : ""); + } + printf (" 'Date:' header: %s\n", + (! get_mhd_suppr_date(d)) ? "Yes" : "No"); + if (0 != port) + { + printf ("To test with remote client use\n" + " http://HOST_IP:%u/\n", (unsigned int) port); + printf ("To test with client on the same host use\n" + " http://localhost:%u/\n", (unsigned int) port); + printf ("\nPress ENTER to stop.\n"); + } + if (1) + { + char buf[10]; + (void) fgets (buf, sizeof(buf), stdin); + } + MHD_daemon_destroy (d); + return 0; +} + + +int +main (int argc, char *const *argv) +{ + int ret; + set_self_name (argc, argv); + ret = process_params (argc, argv); + if (0 != ret) + return ret; + ret = check_apply_params (); + if (0 > ret) + return 0; + if (0 != ret) + return ret; + ret = init_data (); + if (0 != ret) + return ret; + ret = run_mhd (); + deinit_data (); + return ret; +}