From e99da8b64b1588a4ccf19f89953ee83c49584bb7 Mon Sep 17 00:00:00 2001 From: vkalintiris Date: Tue, 2 Jul 2024 12:19:20 +0300 Subject: [PATCH] Run the agent as a Windows service. (#17766) * Run the agent as a Windows service. This commit contains the boilerplate code for running the agent as a Windows service. We start the agent's main as a separate thread, although this is not strictly required based on my experiments. We need similar logic for calling netdata's exit function when someone wants to stop the agent. However, at this point we need to resolve the issue of gaps when running the agent as a service. It seems that sleeping for one second with `sleep(1)`, actually sleeps for 2 to 4 seconds on my setup. Once we resolve this, the work that remains concerns packaging: ie. installing the binaries at the proper places so that the relevant DLLs are found. To test this PR you need to: - Build the agent: ./packaging/utils/compile-on-windows.sh - Install the files: `ninja -C build/ install` - Copy the main binary: `cp ./build/netdata /usr/bin/` - (Only once) Create the netdata service: `sc.exe config Netdata binPath="C:\msys64\usr\bin\netdata"` - Start the service: `sc.exe start Netdata` A couple notes: - The health and the spawn client have been disabled for the time being. They will be re-enabled once we finish the agent-as-service issue and the packaging. - Last time I checked, the agent crashes after a while when using dbengine. In order to have something that works correctly, you should specify memory-mode ram in your netdata.conf. * Add windows version for sleep_usec_with_now * Split install prefix from runtime prefix These paths are always the same for non-Windows systems. On Windows, RFS is the top-level installation path. With the current setup, Netdata will be installed at C:\msys64\opt\netdata at packaging time. However, the layout of the application means that when the agent starts, it'll look as if everything was installed at /. * Do not use mold linker on Windows. * Use modern UI for installer. * Make the service delayed-auto * Use mutexes instead of spinlocks. * Update service handling logic. * Add proper ifdefs for spinlock implementation. * Initialize analytics spinlock * Add a macro to build the agent as regular cli tool. * Add makensis dependency * Let installer know it's installing Netdata. * Disable pluginsd on Windows When pluginsd is enabled, the agent freezes approximately 20% of the time during startup. * Add service description. * Return pthread_join result * Print tag when we fail to join a thread. * Do not use mutexes instead of spinlocks. * Assorted changes to service/main code. * Rework service functions. With the current implementation we are not getting any MUTEX_LOCK errors and thread joining succeeds. The only case where joining fails is the parallel initialization of dbengine threads, which we can easily avoid by serializing the initialization step. * Rework main functions This will allow someone to run the agent either as a service or as a command-line tool. * Change runtime prefix only when building for packaging. * Install binaries and dlls. * Make netdata claiming through UI work correctly. * Fix netdata path --- .gitignore | 3 + CMakeLists.txt | 128 ++++++++++--- packaging/utils/compile-on-windows.sh | 7 +- packaging/utils/find-dll-deps.sh | 16 ++ packaging/utils/installer.nsi | 64 +++++-- src/daemon/analytics.c | 8 +- src/daemon/analytics.h | 1 + src/daemon/main.c | 41 +++-- src/daemon/static_threads.c | 6 +- src/daemon/winsvc.cc | 252 ++++++++++++++++++++++++++ src/database/rrdhost.c | 6 + src/libnetdata/clocks/clocks.c | 30 +++ src/libnetdata/libnetdata.h | 5 + src/libnetdata/locks/locks.c | 82 +++++++-- src/libnetdata/locks/locks.h | 31 +++- src/libnetdata/string/string.c | 5 + src/libnetdata/string/string.h | 2 + src/libnetdata/threads/threads.c | 12 +- src/libnetdata/threads/threads.h | 2 +- 19 files changed, 615 insertions(+), 86 deletions(-) create mode 100644 packaging/utils/find-dll-deps.sh create mode 100644 src/daemon/winsvc.cc diff --git a/.gitignore b/.gitignore index 54878dc7558ffb..4348244c5d569b 100644 --- a/.gitignore +++ b/.gitignore @@ -199,3 +199,6 @@ src/go/collectors/go.d.plugin/mocks/springboot2/.gradle/ src/go/collectors/go.d.plugin/mocks/tmp/* !src/go/collectors/go.d.plugin/mocks/tmp/.gitkeep src/go/collectors/go.d.plugin/vendor + +# ignore nsis installer +packaging/utils/netdata-installer.exe diff --git a/CMakeLists.txt b/CMakeLists.txt index a8a9105b5e5286..79ea95a606c9ff 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -92,6 +92,9 @@ set(OS_MACOS False) set(OS_WINDOWS False) set(ALLOW_PLATFORM_SENSITIVE_OPTIONS True) +set(NETDATA_RUNTIME_PREFIX "${CMAKE_INSTALL_PREFIX}") +set(BINDIR usr/sbin) + if("${CMAKE_SYSTEM_NAME}" STREQUAL "Darwin") set(OS_MACOS True) find_library(IOKIT IOKit) @@ -111,6 +114,16 @@ elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "Linux") endif() elseif("${CMAKE_SYSTEM_NAME}" STREQUAL "CYGWIN" OR "${CMAKE_SYSTEM_NAME}" STREQUAL "MSYS" OR "${CMAKE_SYSTEM_NAME}" STREQUAL "Windows") set(OS_WINDOWS True) + + if(NOT "${CMAKE_INSTALL_PREFIX}" STREQUAL "/opt/netdata") + message(FATAL_ERROR "CMAKE_INSTALL_PREFIX must be set to /opt/netdata, but it is set to ${CMAKE_INSTALL_PREFIX}") + endif() + + if(BUILD_FOR_PACKAGING) + set(NETDATA_RUNTIME_PREFIX "/") + endif() + + set(BINDIR usr/bin) add_definitions(-D_GNU_SOURCE) if($ENV{CLION_IDE}) @@ -1434,6 +1447,7 @@ elseif(OS_FREEBSD) elseif(OS_WINDOWS) list(APPEND NETDATA_FILES src/daemon/static_threads_windows.c + src/daemon/winsvc.cc ${WINDOWS_PLUGIN_FILES} ${INTERNAL_COLLECTORS_FILES} ) @@ -2279,7 +2293,7 @@ target_link_libraries(systemd-cat-native libnetdata) install(TARGETS systemd-cat-native COMPONENT netdata - DESTINATION usr/sbin) + DESTINATION "${BINDIR}") # # build log2journal @@ -2314,7 +2328,7 @@ if(PCRE2_FOUND) install(TARGETS log2journal COMPONENT netdata - DESTINATION usr/sbin) + DESTINATION "${BINDIR}") install(DIRECTORY src/collectors/log2journal/log2journal.d COMPONENT netdata @@ -2337,7 +2351,7 @@ target_link_libraries(netdatacli libnetdata) install(TARGETS netdatacli COMPONENT netdata - DESTINATION usr/sbin) + DESTINATION "${BINDIR}") # # Build go.d.plugin @@ -2354,17 +2368,16 @@ endif() # # Generate config file # - -if(NOT CMAKE_INSTALL_PREFIX STREQUAL "") - string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") +if(NOT NETDATA_RUNTIME_PREFIX STREQUAL "") + string(REGEX REPLACE "/$" "" NETDATA_RUNTIME_PREFIX "${NETDATA_RUNTIME_PREFIX}") endif() -set(CACHE_DIR "${CMAKE_INSTALL_PREFIX}/var/cache/netdata") -set(CONFIG_DIR "${CMAKE_INSTALL_PREFIX}/etc/netdata") -set(LIBCONFIG_DIR "${CMAKE_INSTALL_PREFIX}/usr/lib/netdata/conf.d") -set(LOG_DIR "${CMAKE_INSTALL_PREFIX}/var/log/netdata") -set(PLUGINS_DIR "${CMAKE_INSTALL_PREFIX}/usr/libexec/netdata/plugins.d") -set(VARLIB_DIR "${CMAKE_INSTALL_PREFIX}/var/lib/netdata") +set(CACHE_DIR "${NETDATA_RUNTIME_PREFIX}/var/cache/netdata") +set(CONFIG_DIR "${NETDATA_RUNTIME_PREFIX}/etc/netdata") +set(LIBCONFIG_DIR "${NETDATA_RUNTIME_PREFIX}/usr/lib/netdata/conf.d") +set(LOG_DIR "${NETDATA_RUNTIME_PREFIX}/var/log/netdata") +set(PLUGINS_DIR "${NETDATA_RUNTIME_PREFIX}/usr/libexec/netdata/plugins.d") +set(VARLIB_DIR "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata") # A non-default value is only used when building Debian packages (/var/lib/netdata/www) if(NOT DEFINED WEB_DIR) @@ -2373,7 +2386,7 @@ else() string(REGEX REPLACE "^/" "" WEB_DIR "${WEB_DIR}") endif() set(WEB_DEST "${WEB_DIR}") -set(WEB_DIR "${CMAKE_INSTALL_PREFIX}/${WEB_DEST}") +set(WEB_DIR "${NETDATA_RUNTIME_PREFIX}/${WEB_DEST}") set(CONFIGURE_COMMAND "dummy-configure-command") @@ -2387,7 +2400,7 @@ configure_file(packaging/cmake/config.cmake.h.in config.h) # install # -install(TARGETS netdata COMPONENT netdata DESTINATION usr/sbin) +install(TARGETS netdata COMPONENT netdata DESTINATION "${BINDIR}") install(DIRECTORY COMPONENT netdata DESTINATION var/cache/netdata) install(DIRECTORY COMPONENT netdata DESTINATION var/log/netdata) @@ -2406,15 +2419,15 @@ install(DIRECTORY COMPONENT netdata DESTINATION usr/lib/netdata/conf.d/schema.d) install(DIRECTORY COMPONENT netdata DESTINATION usr/libexec/netdata/plugins.d) install(DIRECTORY COMPONENT netdata DESTINATION ${WEB_DEST}) -set(libsysdir_POST "${CMAKE_INSTALL_PREFIX}/usr/lib/netdata/system") -set(pkglibexecdir_POST "${CMAKE_INSTALL_PREFIX}/usr/libexec/netdata") -set(localstatedir_POST "${CMAKE_INSTALL_PREFIX}/var") -set(sbindir_POST "${CMAKE_INSTALL_PREFIX}/usr/sbin") -set(configdir_POST "${CMAKE_INSTALL_PREFIX}/etc/netdata") -set(libconfigdir_POST "${CMAKE_INSTALL_PREFIX}/usr/lib/netdata/conf.d") -set(cachedir_POST "${CMAKE_INSTALL_PREFIX}/var/cache/netdata") -set(registrydir_POST "${CMAKE_INSTALL_PREFIX}/var/lib/netdata/registry") -set(varlibdir_POST "${CMAKE_INSTALL_PREFIX}/var/lib/netdata") +set(libsysdir_POST "${NETDATA_RUNTIME_PREFIX}/usr/lib/netdata/system") +set(pkglibexecdir_POST "${NETDATA_RUNTIME_PREFIX}/usr/libexec/netdata") +set(localstatedir_POST "${NETDATA_RUNTIME_PREFIX}/var") +set(sbindir_POST "${NETDATA_RUNTIME_PREFIX}/${BINDIR}") +set(configdir_POST "${NETDATA_RUNTIME_PREFIX}/etc/netdata") +set(libconfigdir_POST "${NETDATA_RUNTIME_PREFIX}/usr/lib/netdata/conf.d") +set(cachedir_POST "${NETDATA_RUNTIME_PREFIX}/var/cache/netdata") +set(registrydir_POST "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata/registry") +set(varlibdir_POST "${NETDATA_RUNTIME_PREFIX}/var/lib/netdata") set(netdata_user_POST "${NETDATA_USER}") # netdata-claim.sh @@ -2434,7 +2447,7 @@ configure_file(src/claim/netdata-claim.sh.in src/claim/netdata-claim.sh @ONLY) install(PROGRAMS ${CMAKE_BINARY_DIR}/src/claim/netdata-claim.sh COMPONENT netdata - DESTINATION usr/sbin) + DESTINATION "${BINDIR}") # # We don't check ENABLE_PLUGIN_CGROUP_NETWORK because rpm builds assume @@ -3026,4 +3039,71 @@ if(NOT OS_WINDOWS) DESTINATION ${WEB_DEST}/v0) endif() +if(OS_WINDOWS) + install(FILES /usr/bin/awk.exe + /usr/bin/bash.exe + /usr/bin/cat.exe + /usr/bin/chown.exe + /usr/bin/curl.exe + /usr/bin/env.exe + /usr/bin/grep.exe + /usr/bin/mkdir.exe + /usr/bin/openssl.exe + /usr/bin/rm.exe + /usr/bin/sed.exe + /usr/bin/sh.exe + /usr/bin/tail.exe + /usr/bin/tr.exe + /usr/bin/uuidgen.exe + /usr/bin/whoami.exe + DESTINATION "${BINDIR}") + + install(FILES /usr/bin/msys-2.0.dll + /usr/bin/msys-asn1-8.dll + /usr/bin/msys-brotlicommon-1.dll + /usr/bin/msys-brotlidec-1.dll + /usr/bin/msys-brotlienc-1.dll + /usr/bin/msys-com_err-1.dll + /usr/bin/msys-crypt-2.dll + /usr/bin/msys-crypto-3.dll + /usr/bin/msys-curl-4.dll + /usr/bin/msys-gcc_s-seh-1.dll + /usr/bin/msys-gmp-10.dll + /usr/bin/msys-gssapi-3.dll + /usr/bin/msys-hcrypto-4.dll + /usr/bin/msys-heimbase-1.dll + /usr/bin/msys-heimntlm-0.dll + /usr/bin/msys-hx509-5.dll + /usr/bin/msys-iconv-2.dll + /usr/bin/msys-idn2-0.dll + /usr/bin/msys-intl-8.dll + /usr/bin/msys-krb5-26.dll + /usr/bin/msys-lz4-1.dll + /usr/bin/msys-mpfr-6.dll + /usr/bin/msys-ncursesw6.dll + /usr/bin/msys-nghttp2-14.dll + /usr/bin/msys-pcre-1.dll + /usr/bin/msys-protobuf-32.dll + /usr/bin/msys-psl-5.dll + /usr/bin/msys-readline8.dll + /usr/bin/msys-roken-18.dll + /usr/bin/msys-sqlite3-0.dll + /usr/bin/msys-ssh2-1.dll + /usr/bin/msys-ssl-3.dll + /usr/bin/msys-stdc++-6.dll + /usr/bin/msys-unistring-5.dll + /usr/bin/msys-uuid-1.dll + /usr/bin/msys-uv-1.dll + /usr/bin/msys-wind-0.dll + /usr/bin/msys-z.dll + /usr/bin/msys-zstd-1.dll + DESTINATION "${BINDIR}") + + # Make bash & netdata happy + install(DIRECTORY DESTINATION tmp) + + # Make curl work with ssl + install(DIRECTORY /usr/ssl DESTINATION usr) +endif() + include(Packaging) diff --git a/packaging/utils/compile-on-windows.sh b/packaging/utils/compile-on-windows.sh index aa44771b8ef4dc..103052be497a40 100644 --- a/packaging/utils/compile-on-windows.sh +++ b/packaging/utils/compile-on-windows.sh @@ -16,7 +16,8 @@ install_dependencies() { msys/pcre2-devel mingw64/mingw-w64-x86_64-pcre2 ucrt64/mingw-w64-ucrt-x86_64-pcre2 \ msys/brotli-devel mingw64/mingw-w64-x86_64-brotli ucrt64/mingw-w64-ucrt-x86_64-brotli \ msys/ccache ucrt64/mingw-w64-ucrt-x86_64-ccache mingw64/mingw-w64-x86_64-ccache \ - mingw64/mingw-w64-x86_64-go ucrt64/mingw-w64-ucrt-x86_64-go + mingw64/mingw-w64-x86_64-go ucrt64/mingw-w64-ucrt-x86_64-go \ + mingw64/mingw-w64-x86_64-nsis } if [ "${1}" = "install" ] @@ -52,7 +53,9 @@ fi -G Ninja \ -DCMAKE_INSTALL_PREFIX="/opt/netdata" \ -DCMAKE_BUILD_TYPE="${BUILD_TYPE}" \ - -DCMAKE_C_FLAGS="-O0 -ggdb -Wall -Wextra -Wno-char-subscripts -Wa,-mbig-obj -pipe -DNETDATA_INTERNAL_CHECKS=1 -D_FILE_OFFSET_BITS=64 -D__USE_MINGW_ANSI_STDIO=1" \ + -DCMAKE_C_FLAGS="-fstack-protector-all -O0 -ggdb -Wall -Wextra -Wno-char-subscripts -Wa,-mbig-obj -pipe -DNETDATA_INTERNAL_CHECKS=1 -D_FILE_OFFSET_BITS=64 -D__USE_MINGW_ANSI_STDIO=1" \ + -DBUILD_FOR_PACKAGING=On \ + -DUSE_MOLD=Off \ -DNETDATA_USER="${USER}" \ -DDEFAULT_FEATURE_STATE=Off \ -DENABLE_H2O=Off \ diff --git a/packaging/utils/find-dll-deps.sh b/packaging/utils/find-dll-deps.sh new file mode 100644 index 00000000000000..9f4fe384753d0b --- /dev/null +++ b/packaging/utils/find-dll-deps.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +if [ "$#" -lt 1 ]; then + echo "Usage: $0 ... " + exit 1 +fi + +results=() + +for arg in "$@"; do + while IFS= read -r line; do + results+=("$line") + done < <(ldd "$arg" | grep /usr/bin | awk '{ print $3 }') +done + +printf "%s\n" "${results[@]}" | sort | uniq diff --git a/packaging/utils/installer.nsi b/packaging/utils/installer.nsi index b78f52ffe2337e..10600f15ebf0a9 100644 --- a/packaging/utils/installer.nsi +++ b/packaging/utils/installer.nsi @@ -1,34 +1,58 @@ -Outfile "netdata-installer.exe" -InstallDir "C:\netdata" +!include "MUI2.nsh" +Name "Netdata" +Outfile "netdata-installer.exe" +InstallDir "$PROGRAMFILES\netdata" RequestExecutionLevel admin -Section - SetOutPath $INSTDIR - WriteUninstaller $INSTDIR\uninstaller.exe -SectionEnd +!define MUI_ABORTWARNING +!define MUI_UNABORTWARNING -Section "Install MSYS2 environment" - SetOutPath $TEMP +!insertmacro MUI_PAGE_WELCOME +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH - SetCompress off - File "C:\msys64\msys2-installer.exe" - nsExec::ExecToLog 'cmd.exe /C "$TEMP\msys2-installer.exe" in --confirm-command --accept-messages --root $INSTDIR' +!insertmacro MUI_UNPAGE_CONFIRM +!insertmacro MUI_UNPAGE_INSTFILES +!insertmacro MUI_UNPAGE_FINISH - Delete "$TEMP\msys2-installer.exe" -SectionEnd - -Section "Install MSYS2 packages" - ExecWait '"$INSTDIR\usr\bin\bash.exe" -lc "pacman -S --noconfirm msys/libuv msys/protobuf"' -SectionEnd +!insertmacro MUI_LANGUAGE "English" Section "Install Netdata" - SetOutPath $INSTDIR\opt\netdata - + SetOutPath $INSTDIR SetCompress off + File /r "C:\msys64\opt\netdata\*.*" + + ClearErrors + ExecWait '"$SYSDIR\sc.exe" create Netdata binPath= "$INSTDIR\usr\bin\netdata.exe" start= delayed-auto' + IfErrors 0 +2 + DetailPrint "Warning: Failed to create Netdata service." + + ClearErrors + ExecWait '"$SYSDIR\sc.exe" description Netdata "Real-time system monitoring service"' + IfErrors 0 +2 + DetailPrint "Warning: Failed to add Netdata service description." + + ClearErrors + ExecWait '"$SYSDIR\sc.exe" start Netdata' + IfErrors 0 +2 + DetailPrint "Warning: Failed to start Netdata service." + + WriteUninstaller "$INSTDIR\Uninstall.exe" SectionEnd Section "Uninstall" - nsExec::ExecToLog 'cmd.exe /C "$INSTDIR\uninstall.exe" pr --confirm-command' + ClearErrors + ExecWait '"$SYSDIR\sc.exe" stop Netdata' + IfErrors 0 +2 + DetailPrint "Warning: Failed to stop Netdata service." + + ClearErrors + ExecWait '"$SYSDIR\sc.exe" delete Netdata' + IfErrors 0 +2 + DetailPrint "Warning: Failed to delete Netdata service." + + RMDir /r "$INSTDIR" SectionEnd diff --git a/src/daemon/analytics.c b/src/daemon/analytics.c index 33f6f357f68496..e9abf11c88a910 100644 --- a/src/daemon/analytics.c +++ b/src/daemon/analytics.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later +#include "analytics.h" #include "common.h" #include "buildinfo.h" @@ -470,8 +471,6 @@ void analytics_alarms(void) */ void analytics_misc(void) { - spinlock_init(&analytics_data.spinlock); - #ifdef ENABLE_ACLK analytics_set_data(&analytics_data.netdata_host_cloud_available, "true"); analytics_set_data_str(&analytics_data.netdata_host_aclk_implementation, "Next Generation"); @@ -1081,3 +1080,8 @@ void analytics_statistic_send(const analytics_statistic_t *statistic) { freez(command_to_run); } + +void analytics_init(void) +{ + spinlock_init(&analytics_data.spinlock); +} diff --git a/src/daemon/analytics.h b/src/daemon/analytics.h index 501eb7b555f4ae..747cf6070ef3a7 100644 --- a/src/daemon/analytics.h +++ b/src/daemon/analytics.h @@ -86,6 +86,7 @@ void analytics_log_dashboard(void); void analytics_gather_mutable_meta_data(void); void analytics_report_oom_score(long long int score); void get_system_timezone(void); +void analytics_init(void); typedef struct { const char *action; diff --git a/src/daemon/main.c b/src/daemon/main.c index 2678d955c3ccaa..75fa13356e1794 100644 --- a/src/daemon/main.c +++ b/src/daemon/main.c @@ -496,6 +496,10 @@ void netdata_cleanup_and_exit(int ret, const char *action, const char *action_re watcher_shutdown_end(); watcher_thread_stop(); +#ifdef OS_WINDOWS + return; +#endif + #ifdef ENABLE_SENTRY if (ret) abort(); @@ -1460,7 +1464,11 @@ int unittest_prepare_rrd(char **user) { return 0; } -int main(int argc, char **argv) { +int netdata_main(int argc, char **argv) +{ + analytics_init(); + string_init(); + // initialize the system clocks clocks_init(); netdata_start_time = now_realtime_sec(); @@ -1471,11 +1479,16 @@ int main(int argc, char **argv) { int i; int config_loaded = 0; - int dont_fork = 0; bool close_open_fds = true; size_t default_stacksize; char *user = NULL; +#ifdef OS_WINDOWS + int dont_fork = 1; +#else + int dont_fork = 0; +#endif + static_threads = static_threads_get(); netdata_ready = false; @@ -2214,7 +2227,10 @@ int main(int argc, char **argv) { // fork the spawn server delta_startup_time("fork the spawn server"); + +#ifndef OS_WINDOWS spawn_init(); +#endif /* * Libuv uv_spawn() uses SIGCHLD internally: @@ -2357,22 +2373,21 @@ int main(int argc, char **argv) { } #endif - // ------------------------------------------------------------------------ - // initialize WebRTC - webrtc_initialize(); - // ------------------------------------------------------------------------ - // unblock signals - signals_unblock(); - // ------------------------------------------------------------------------ - // Handle signals + return 10; +} - signals_handle(); +#ifndef OS_WINDOWS +int main(int argc, char *argv[]) +{ + int rc = netdata_main(argc, argv); + if (rc != 10) + return rc; - // should never reach this point - // but we need it for rpmlint #2752 + signals_handle(); return 1; } +#endif diff --git a/src/daemon/static_threads.c b/src/daemon/static_threads.c index 2667c0fb5cee13..1a5f82f7f2dfe2 100644 --- a/src/daemon/static_threads.c +++ b/src/daemon/static_threads.c @@ -30,7 +30,7 @@ const struct netdata_static_thread static_threads_common[] = { .name = "HEALTH", .config_section = NULL, .config_name = NULL, - .enabled = 1, + .enabled = 0, .thread = NULL, .init_routine = NULL, .start_routine = health_main @@ -70,7 +70,11 @@ const struct netdata_static_thread static_threads_common[] = { .name = "PLUGINSD", .config_section = NULL, .config_name = NULL, +#ifdef OS_WINDOWS + .enabled = 0, +#else .enabled = 1, +#endif .thread = NULL, .init_routine = NULL, .start_routine = pluginsd_main diff --git a/src/daemon/winsvc.cc b/src/daemon/winsvc.cc new file mode 100644 index 00000000000000..cdf503256c88fe --- /dev/null +++ b/src/daemon/winsvc.cc @@ -0,0 +1,252 @@ +extern "C" { + +#include "daemon.h" +#include "libnetdata/libnetdata.h" + +int netdata_main(int argc, char *argv[]); +void signals_handle(void); + +} + +#include + +__attribute__((format(printf, 1, 2))) +static void netdata_service_log(const char *fmt, ...) +{ + char path[FILENAME_MAX + 1]; + snprintfz(path, FILENAME_MAX, "%s/service.log", LOG_DIR); + + FILE *fp = fopen(path, "a"); + if (fp == NULL) { + return; + } + + SYSTEMTIME time; + GetSystemTime(&time); + fprintf(fp, "%d:%d:%d - ", time.wHour, time.wMinute, time.wSecond); + + va_list args; + va_start(args, fmt); + vfprintf(fp, fmt, args); + va_end(args); + + fprintf(fp, "\n"); + + fflush(fp); + fclose(fp); +} + +static SERVICE_STATUS_HANDLE svc_status_handle = nullptr; +static SERVICE_STATUS svc_status = {}; + +static HANDLE svc_stop_event_handle = nullptr; + +static ND_THREAD *cleanup_thread = nullptr; + +static bool ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint, DWORD dwControlsAccepted) +{ + static DWORD dwCheckPoint = 1; + svc_status.dwCurrentState = dwCurrentState; + svc_status.dwWin32ExitCode = dwWin32ExitCode; + svc_status.dwWaitHint = dwWaitHint; + svc_status.dwControlsAccepted = dwControlsAccepted; + + if (dwCurrentState == SERVICE_RUNNING || dwCurrentState == SERVICE_STOPPED) + { + svc_status.dwCheckPoint = 0; + } + else + { + svc_status.dwCheckPoint = dwCheckPoint++; + } + + if (!SetServiceStatus(svc_status_handle, &svc_status)) { + netdata_service_log("@ReportSvcStatus: SetServiceStatusFailed (%d)", GetLastError()); + return false; + } + + return true; +} + +static HANDLE CreateEventHandle(const char *msg) +{ + HANDLE h = CreateEvent(NULL, TRUE, FALSE, NULL); + + if (!h) + { + netdata_service_log(msg); + + if (!ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 1000, 0)) + { + netdata_service_log("Failed to set service status to stopped."); + } + + return NULL; + } + + return h; +} + +static void *call_netdata_cleanup(void *arg) +{ + UNUSED(arg); + + // Wait until we have to stop the service + netdata_service_log("Cleanup thread waiting for stop event..."); + WaitForSingleObject(svc_stop_event_handle, INFINITE); + + // Stop the agent + netdata_service_log("Running netdata cleanup..."); + netdata_cleanup_and_exit(0, NULL, NULL, NULL); + + // Close event handle + netdata_service_log("Closing stop event handle..."); + CloseHandle(svc_stop_event_handle); + + // Set status to stopped + netdata_service_log("Reporting the service as stopped..."); + ReportSvcStatus(SERVICE_STOPPED, 0, 0, 0); + + return nullptr; +} + +static void WINAPI ServiceControlHandler(DWORD controlCode) +{ + switch (controlCode) + { + case SERVICE_CONTROL_STOP: + { + if (svc_status.dwCurrentState != SERVICE_RUNNING) + return; + + // Set service status to stop-pending + netdata_service_log("Setting service status to stop-pending..."); + if (!ReportSvcStatus(SERVICE_STOP_PENDING, 0, 5000, 0)) + return; + + // Create cleanup thread + netdata_service_log("Creating cleanup thread..."); + char tag[NETDATA_THREAD_TAG_MAX + 1]; + snprintfz(tag, NETDATA_THREAD_TAG_MAX, "%s", "CLEANUP"); + cleanup_thread = nd_thread_create(tag, NETDATA_THREAD_OPTION_JOINABLE, call_netdata_cleanup, NULL); + + // Signal the stop request + netdata_service_log("Signalling the cleanup thread..."); + SetEvent(svc_stop_event_handle); + break; + } + case SERVICE_CONTROL_INTERROGATE: + { + ReportSvcStatus(svc_status.dwCurrentState, svc_status.dwWin32ExitCode, svc_status.dwWaitHint, svc_status.dwControlsAccepted); + break; + } + default: + break; + } +} + +void WINAPI ServiceMain(DWORD argc, LPSTR* argv) +{ + UNUSED(argc); + UNUSED(argv); + + // Create service status handle + netdata_service_log("Creating service status handle..."); + svc_status_handle = RegisterServiceCtrlHandler("Netdata", ServiceControlHandler); + if (!svc_status_handle) + { + netdata_service_log("@ServiceMain() - RegisterServiceCtrlHandler() failed..."); + return; + } + + // Set status to start-pending + netdata_service_log("Setting service status to start-pending..."); + svc_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + svc_status.dwServiceSpecificExitCode = 0; + svc_status.dwCheckPoint = 0; + if (!ReportSvcStatus(SERVICE_START_PENDING, 0, 5000, 0)) + { + netdata_service_log("Failed to set service status to start pending."); + return; + } + + // Create stop service event handle + netdata_service_log("Creating stop service event handle..."); + svc_stop_event_handle = CreateEventHandle("Failed to create stop event handle"); + if (!svc_stop_event_handle) + return; + + // Set status to running + netdata_service_log("Setting service status to running..."); + if (!ReportSvcStatus(SERVICE_RUNNING, 0, 5000, SERVICE_ACCEPT_STOP)) + { + netdata_service_log("Failed to set service status to running."); + return; + } + + // Run the agent + netdata_service_log("Running the agent..."); + netdata_main(argc, argv); + + netdata_service_log("Agent has been started..."); +} + +static bool update_path() { + const char *old_path = getenv("PATH"); + + if (!old_path) { + if (setenv("PATH", "/usr/bin", 1) != 0) { + netdata_service_log("Failed to set PATH to /usr/bin"); + return false; + } + + return true; + } + + size_t new_path_length = strlen(old_path) + strlen("/usr/bin") + 2; + char *new_path = (char *) callocz(new_path_length, sizeof(char)); + snprintfz(new_path, new_path_length, "/usr/bin:%s", old_path); + + if (setenv("PATH", new_path, 1) != 0) { + netdata_service_log("Failed to add /usr/bin to PATH"); + freez(new_path); + return false; + } + + freez(new_path); + return true; +} + +int main(int argc, char *argv[]) +{ + bool tty = isatty(fileno(stdout)) == 1; + + if (!update_path()) { + return 1; + } + + if (tty) + { + int rc = netdata_main(argc, argv); + if (rc != 10) + return rc; + + signals_handle(); + return 1; + } + else + { + SERVICE_TABLE_ENTRY serviceTable[] = { + { strdupz("Netdata"), ServiceMain }, + { nullptr, nullptr } + }; + + if (!StartServiceCtrlDispatcher(serviceTable)) + { + netdata_service_log("@main() - StartServiceCtrlDispatcher() failed..."); + return 1; + } + + return 0; + } +} diff --git a/src/database/rrdhost.c b/src/database/rrdhost.c index 6bf2c25518a6be..dd5f2a43e221db 100644 --- a/src/database/rrdhost.c +++ b/src/database/rrdhost.c @@ -935,7 +935,13 @@ void dbengine_init(char *hostname) { config_set_number(CONFIG_SECTION_DB, "dbengine tier 0 disk space MB", default_multidb_disk_quota_mb); } +#ifdef OS_WINDOWS + // FIXME: for whatever reason joining the initialization threads + // fails on Windows. + bool parallel_initialization = false; +#else bool parallel_initialization = (storage_tiers <= (size_t)get_netdata_cpus()) ? true : false; +#endif struct dbengine_initialization tiers_init[RRD_STORAGE_TIERS] = {}; diff --git a/src/libnetdata/clocks/clocks.c b/src/libnetdata/clocks/clocks.c index e1a3e64cb3610b..a6816896238583 100644 --- a/src/libnetdata/clocks/clocks.c +++ b/src/libnetdata/clocks/clocks.c @@ -368,6 +368,35 @@ usec_t heartbeat_next(heartbeat_t *hb, usec_t tick) { return dt; } +#ifdef OS_WINDOWS + +#include "windows.h" + +void sleep_usec_with_now(usec_t usec, usec_t started_ut) +{ + if (!started_ut) + started_ut = now_realtime_usec(); + + usec_t end_ut = started_ut + usec; + usec_t remaining_ut = usec; + + timeBeginPeriod(1); + + while (remaining_ut >= 1000) + { + DWORD sleep_ms = (DWORD) (remaining_ut / USEC_PER_MS); + Sleep(sleep_ms); + + usec_t now_ut = now_realtime_usec(); + if (now_ut >= end_ut) + break; + + remaining_ut = end_ut - now_ut; + } + + timeEndPeriod(1); +} +#else void sleep_usec_with_now(usec_t usec, usec_t started_ut) { // we expect microseconds (1.000.000 per second) // but timespec is nanoseconds (1.000.000.000 per second) @@ -411,6 +440,7 @@ void sleep_usec_with_now(usec_t usec, usec_t started_ut) { } } } +#endif static inline collected_number uptime_from_boottime(void) { #ifdef CLOCK_BOOTTIME_IS_AVAILABLE diff --git a/src/libnetdata/libnetdata.h b/src/libnetdata/libnetdata.h index 859f54cc33d32c..1c72e54106883c 100644 --- a/src/libnetdata/libnetdata.h +++ b/src/libnetdata/libnetdata.h @@ -451,7 +451,12 @@ typedef enum { } OPEN_FD_EXCLUDE; void for_each_open_fd(OPEN_FD_ACTION action, OPEN_FD_EXCLUDE excluded_fds); +#ifdef OS_WINDOWS +void netdata_cleanup_and_exit(int ret, const char *action, const char *action_result, const char *action_data); +#else void netdata_cleanup_and_exit(int ret, const char *action, const char *action_result, const char *action_data) NORETURN; +#endif + extern char *netdata_configured_host_prefix; #include "os/os.h" diff --git a/src/libnetdata/locks/locks.c b/src/libnetdata/locks/locks.c index d01ee29f143851..424b86ce9526f5 100644 --- a/src/libnetdata/locks/locks.c +++ b/src/libnetdata/locks/locks.c @@ -224,14 +224,24 @@ int __netdata_rwlock_trywrlock(netdata_rwlock_t *rwlock) { // spinlock implementation // https://www.youtube.com/watch?v=rmGJc9PXpuE&t=41s -void spinlock_init(SPINLOCK *spinlock) { +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_init(SPINLOCK *spinlock) +{ + netdata_mutex_init(&spinlock->inner); +} +#else +void spinlock_init(SPINLOCK *spinlock) +{ memset(spinlock, 0, sizeof(SPINLOCK)); } +#endif -static inline void spinlock_lock_internal(SPINLOCK *spinlock) { -#ifdef NETDATA_INTERNAL_CHECKS +#ifndef SPINLOCK_IMPL_WITH_MUTEX +static inline void spinlock_lock_internal(SPINLOCK *spinlock) +{ + #ifdef NETDATA_INTERNAL_CHECKS size_t spins = 0; -#endif + #endif for(int i = 1; __atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) || @@ -239,9 +249,10 @@ static inline void spinlock_lock_internal(SPINLOCK *spinlock) { ; i++ ) { -#ifdef NETDATA_INTERNAL_CHECKS + #ifdef NETDATA_INTERNAL_CHECKS spins++; -#endif + #endif + if(unlikely(i == 8)) { i = 0; tinysleep(); @@ -250,23 +261,29 @@ static inline void spinlock_lock_internal(SPINLOCK *spinlock) { // we have the lock -#ifdef NETDATA_INTERNAL_CHECKS + #ifdef NETDATA_INTERNAL_CHECKS spinlock->spins += spins; spinlock->locker_pid = gettid_cached(); -#endif + #endif nd_thread_spinlock_locked(); } +#endif // SPINLOCK_IMPL_WITH_MUTEX -static inline void spinlock_unlock_internal(SPINLOCK *spinlock) { -#ifdef NETDATA_INTERNAL_CHECKS +#ifndef SPINLOCK_IMPL_WITH_MUTEX +static inline void spinlock_unlock_internal(SPINLOCK *spinlock) +{ + #ifdef NETDATA_INTERNAL_CHECKS spinlock->locker_pid = 0; -#endif + #endif + __atomic_clear(&spinlock->locked, __ATOMIC_RELEASE); nd_thread_spinlock_unlocked(); } +#endif // SPINLOCK_IMPL_WITH_MUTEX +#ifndef SPINLOCK_IMPL_WITH_MUTEX static inline bool spinlock_trylock_internal(SPINLOCK *spinlock) { if(!__atomic_load_n(&spinlock->locked, __ATOMIC_RELAXED) && !__atomic_test_and_set(&spinlock->locked, __ATOMIC_ACQUIRE)) { @@ -277,36 +294,79 @@ static inline bool spinlock_trylock_internal(SPINLOCK *spinlock) { return false; } +#endif // SPINLOCK_IMPL_WITH_MUTEX +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_lock(SPINLOCK *spinlock) +{ + netdata_mutex_lock(&spinlock->inner); +} +#else void spinlock_lock(SPINLOCK *spinlock) { spinlock_lock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_unlock(SPINLOCK *spinlock) +{ + netdata_mutex_unlock(&spinlock->inner); +} +#else void spinlock_unlock(SPINLOCK *spinlock) { spinlock_unlock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +bool spinlock_trylock(SPINLOCK *spinlock) +{ + return netdata_mutex_trylock(&spinlock->inner) == 0; +} +#else bool spinlock_trylock(SPINLOCK *spinlock) { return spinlock_trylock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_lock_cancelable(SPINLOCK *spinlock) +{ + netdata_mutex_lock(&spinlock->inner); +} +#else void spinlock_lock_cancelable(SPINLOCK *spinlock) { spinlock_lock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +void spinlock_unlock_cancelable(SPINLOCK *spinlock) +{ + netdata_mutex_unlock(&spinlock->inner); +} +#else void spinlock_unlock_cancelable(SPINLOCK *spinlock) { spinlock_unlock_internal(spinlock); } +#endif +#ifdef SPINLOCK_IMPL_WITH_MUTEX +bool spinlock_trylock_cancelable(SPINLOCK *spinlock) +{ + return netdata_mutex_trylock(&spinlock->inner) == 0; +} +#else bool spinlock_trylock_cancelable(SPINLOCK *spinlock) { return spinlock_trylock_internal(spinlock); } +#endif // ---------------------------------------------------------------------------- // rw_spinlock implementation diff --git a/src/libnetdata/locks/locks.h b/src/libnetdata/locks/locks.h index d3873c295204af..c05c65fe2d5e4a 100644 --- a/src/libnetdata/locks/locks.h +++ b/src/libnetdata/locks/locks.h @@ -6,19 +6,34 @@ #include "../libnetdata.h" #include "../clocks/clocks.h" +// #ifdef OS_WINDOWS +// #define SPINLOCK_IMPL_WITH_MUTEX +// #endif + typedef pthread_mutex_t netdata_mutex_t; #define NETDATA_MUTEX_INITIALIZER PTHREAD_MUTEX_INITIALIZER -typedef struct netdata_spinlock { - bool locked; -#ifdef NETDATA_INTERNAL_CHECKS - pid_t locker_pid; - size_t spins; +#ifdef SPINLOCK_IMPL_WITH_MUTEX + typedef struct netdata_spinlock + { + netdata_mutex_t inner; + } SPINLOCK; +#else + typedef struct netdata_spinlock + { + bool locked; + #ifdef NETDATA_INTERNAL_CHECKS + pid_t locker_pid; + size_t spins; + #endif + } SPINLOCK; #endif -} SPINLOCK; -#define NETDATA_SPINLOCK_INITIALIZER \ - { .locked = false } +#ifdef SPINLOCK_IMPL_WITH_MUTEX +#define NETDATA_SPINLOCK_INITIALIZER { .inner = PTHREAD_MUTEX_INITIALIZER } +#else +#define NETDATA_SPINLOCK_INITIALIZER { .locked = false } +#endif void spinlock_init(SPINLOCK *spinlock); void spinlock_lock(SPINLOCK *spinlock); diff --git a/src/libnetdata/string/string.c b/src/libnetdata/string/string.c index 94c11f4b9a2eb8..257a3cc4b91fbf 100644 --- a/src/libnetdata/string/string.c +++ b/src/libnetdata/string/string.c @@ -702,3 +702,8 @@ int string_unittest(size_t entries) { fprintf(stderr, "\n%zu errors found\n", errors); return errors ? 1 : 0; } + +void string_init(void) { + for (size_t i = 0; i != STRING_PARTITIONS; i++) + rw_spinlock_init(&string_base[i].spinlock); +} diff --git a/src/libnetdata/string/string.h b/src/libnetdata/string/string.h index f2ff9666ca548d..c44696be2561b7 100644 --- a/src/libnetdata/string/string.h +++ b/src/libnetdata/string/string.h @@ -34,4 +34,6 @@ void string_statistics(size_t *inserts, size_t *deletes, size_t *searches, size_ int string_unittest(size_t entries); +void string_init(void); + #endif diff --git a/src/libnetdata/threads/threads.c b/src/libnetdata/threads/threads.c index 0e12d173ec67b5..36c63f4e0fec68 100644 --- a/src/libnetdata/threads/threads.c +++ b/src/libnetdata/threads/threads.c @@ -418,12 +418,14 @@ bool nd_thread_signaled_to_cancel(void) { // ---------------------------------------------------------------------------- // nd_thread_join -void nd_thread_join(ND_THREAD *nti) { - if(!nti) return; +int nd_thread_join(ND_THREAD *nti) { + if(!nti) + return ESRCH; int ret = pthread_join(nti->thread, NULL); - if(ret != 0) - nd_log(NDLS_DAEMON, NDLP_WARNING, "cannot join thread. pthread_join() failed with code %d.", ret); + if(ret != 0) { + nd_log(NDLS_DAEMON, NDLP_WARNING, "cannot join thread. pthread_join() failed with code %d. (tag=%s)", ret, nti->tag); + } else { nd_thread_status_set(nti, NETDATA_THREAD_STATUS_JOINED); @@ -434,4 +436,6 @@ void nd_thread_join(ND_THREAD *nti) { freez(nti); } + + return ret; } diff --git a/src/libnetdata/threads/threads.h b/src/libnetdata/threads/threads.h index a7204e2a2ffbe4..0b54a5fc0b634c 100644 --- a/src/libnetdata/threads/threads.h +++ b/src/libnetdata/threads/threads.h @@ -70,7 +70,7 @@ void netdata_threads_init_after_fork(size_t stacksize); void netdata_threads_init_for_external_plugins(size_t stacksize); ND_THREAD *nd_thread_create(const char *tag, NETDATA_THREAD_OPTIONS options, void *(*start_routine) (void *), void *arg); -void nd_thread_join(ND_THREAD * nti); +int nd_thread_join(ND_THREAD * nti); ND_THREAD *nd_thread_self(void); bool nd_thread_is_me(ND_THREAD *nti);