diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-25 17:33:56 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-11-25 17:34:10 +0000 |
commit | 83ba6762cc43d9db581b979bb5e3445669e46cc2 (patch) | |
tree | 2e69833b43f791ed253a7a20318b767ebe56cdb8 /src/libnetdata/os | |
parent | Releasing debian version 1.47.5-1. (diff) | |
download | netdata-83ba6762cc43d9db581b979bb5e3445669e46cc2.tar.xz netdata-83ba6762cc43d9db581b979bb5e3445669e46cc2.zip |
Merging upstream version 2.0.3+dfsg (Closes: #923993, #1042533, #1045145).
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libnetdata/os')
37 files changed, 3065 insertions, 64 deletions
diff --git a/src/libnetdata/os/close_range.c b/src/libnetdata/os/close_range.c index 56d5c2527..2ee5837ee 100644 --- a/src/libnetdata/os/close_range.c +++ b/src/libnetdata/os/close_range.c @@ -7,6 +7,12 @@ static int fd_is_valid(int fd) { return fcntl(fd, F_GETFD) != -1 || errno != EBADF; } +static void setcloexec(int fd) { + int flags = fcntl(fd, F_GETFD); + if (flags != -1) + (void) fcntl(fd, F_SETFD, flags | FD_CLOEXEC); +} + int os_get_fd_open_max(void) { static int fd_open_max = CLOSE_RANGE_FD_MAX; @@ -33,9 +39,9 @@ int os_get_fd_open_max(void) { return fd_open_max; } -void os_close_range(int first, int last) { +void os_close_range(int first, int last, int flags) { #if defined(HAVE_CLOSE_RANGE) - if(close_range(first, last, 0) == 0) return; + if(close_range(first, last, flags) == 0) return; #endif #if defined(OS_LINUX) @@ -44,8 +50,12 @@ void os_close_range(int first, int last) { struct dirent *entry; while ((entry = readdir(dir)) != NULL) { int fd = str2i(entry->d_name); - if (fd >= first && (last == CLOSE_RANGE_FD_MAX || fd <= last) && fd_is_valid(fd)) - (void)close(fd); + if (fd >= first && (last == CLOSE_RANGE_FD_MAX || fd <= last) && fd_is_valid(fd)) { + if(flags & CLOSE_RANGE_CLOEXEC) + setcloexec(fd); + else + (void)close(fd); + } } closedir(dir); return; @@ -57,7 +67,12 @@ void os_close_range(int first, int last) { last = os_get_fd_open_max(); for (int fd = first; fd <= last; fd++) { - if (fd_is_valid(fd)) (void)close(fd); + if (fd_is_valid(fd)) { + if(flags & CLOSE_RANGE_CLOEXEC) + setcloexec(fd); + else + (void)close(fd); + } } } @@ -67,9 +82,9 @@ static int compare_ints(const void *a, const void *b) { return (int_a > int_b) - (int_a < int_b); } -void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num) { +void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num, int flags) { if (fds_num == 0 || fds == NULL) { - os_close_range(STDERR_FILENO + 1, CLOSE_RANGE_FD_MAX); + os_close_range(STDERR_FILENO + 1, CLOSE_RANGE_FD_MAX, flags); return; } @@ -89,10 +104,10 @@ void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num) { // call os_close_range() as many times as needed for (; i < fds_num; i++) { if (fds_copy[i] > start) - os_close_range(start, fds_copy[i] - 1); + os_close_range(start, fds_copy[i] - 1, flags); start = fds_copy[i] + 1; } - os_close_range(start, CLOSE_RANGE_FD_MAX); + os_close_range(start, CLOSE_RANGE_FD_MAX, flags); } diff --git a/src/libnetdata/os/close_range.h b/src/libnetdata/os/close_range.h index e3cb93798..7914ac3f6 100644 --- a/src/libnetdata/os/close_range.h +++ b/src/libnetdata/os/close_range.h @@ -5,8 +5,16 @@ #define CLOSE_RANGE_FD_MAX (int)(~0U) +#ifndef CLOSE_RANGE_UNSHARE +#define CLOSE_RANGE_UNSHARE (1U << 1) +#endif + +#ifndef CLOSE_RANGE_CLOEXEC +#define CLOSE_RANGE_CLOEXEC (1U << 2) +#endif + int os_get_fd_open_max(void); -void os_close_range(int first, int last); -void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num); +void os_close_range(int first, int last, int flags); +void os_close_all_non_std_open_fds_except(const int fds[], size_t fds_num, int flags); #endif //CLOSE_RANGE_H diff --git a/src/libnetdata/os/get_system_cpus.c b/src/libnetdata/os/get_system_cpus.c index 5a76d8aa5..f8234d8bc 100644 --- a/src/libnetdata/os/get_system_cpus.c +++ b/src/libnetdata/os/get_system_cpus.c @@ -2,10 +2,6 @@ #include "../libnetdata.h" -#if defined(OS_WINDOWS) -#include <windows.h> -#endif - #define CPUS_FOR_COLLECTORS 0 #define CPUS_FOR_NETDATA 1 @@ -82,7 +78,14 @@ long os_get_system_cpus_cached(bool cache, bool for_netdata) { SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); - return (long) sysInfo.dwNumberOfProcessors; + processors[index] = sysInfo.dwNumberOfProcessors; + + if(processors[index] < 1) { + processors[index] = 1; + netdata_log_error("Assuming system has %ld processors.", processors[index]); + } + + return processors[index]; #else diff --git a/src/libnetdata/os/gettid.c b/src/libnetdata/os/gettid.c index 273c428f8..d61819445 100644 --- a/src/libnetdata/os/gettid.c +++ b/src/libnetdata/os/gettid.c @@ -2,10 +2,6 @@ #include "../libnetdata.h" -#if defined(OS_WINDOWS) -#include <windows.h> -#endif - pid_t os_gettid(void) { #if defined(HAVE_GETTID) return gettid(); @@ -30,4 +26,9 @@ pid_t gettid_cached(void) { gettid_cached_tid = os_gettid(); return gettid_cached_tid; -}
\ No newline at end of file +} + +pid_t gettid_uncached(void) { + gettid_cached_tid = 0; + return gettid_cached(); +} diff --git a/src/libnetdata/os/gettid.h b/src/libnetdata/os/gettid.h index f04d9c365..6debfd928 100644 --- a/src/libnetdata/os/gettid.h +++ b/src/libnetdata/os/gettid.h @@ -7,5 +7,6 @@ pid_t os_gettid(void); pid_t gettid_cached(void); +pid_t gettid_uncached(void); #endif //NETDATA_GETTID_H diff --git a/src/libnetdata/os/os-windows-wrappers.c b/src/libnetdata/os/os-windows-wrappers.c index 64076eae2..a79ae41f2 100644 --- a/src/libnetdata/os/os-windows-wrappers.c +++ b/src/libnetdata/os/os-windows-wrappers.c @@ -3,8 +3,6 @@ #include "../libnetdata.h" #if defined(OS_WINDOWS) -#include <windows.h> - long netdata_registry_get_dword_from_open_key(unsigned int *out, void *lKey, char *name) { DWORD length = 260; @@ -58,4 +56,42 @@ bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, cha return status; } +bool EnableWindowsPrivilege(const char *privilegeName) { + HANDLE hToken; + LUID luid; + TOKEN_PRIVILEGES tkp; + + // Open the process token with appropriate access rights + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken)) + return false; + + // Lookup the LUID for the specified privilege + if (!LookupPrivilegeValue(NULL, privilegeName, &luid)) { + CloseHandle(hToken); // Close the token handle before returning + return false; + } + + // Set up the TOKEN_PRIVILEGES structure + tkp.PrivilegeCount = 1; + tkp.Privileges[0].Luid = luid; + tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + // Adjust the token's privileges + if (!AdjustTokenPrivileges(hToken, FALSE, &tkp, sizeof(tkp), NULL, NULL)) { + CloseHandle(hToken); // Close the token handle before returning + return false; + } + + // Check if AdjustTokenPrivileges succeeded + if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { + CloseHandle(hToken); // Close the token handle before returning + return false; + } + + // Close the handle to the token after success + CloseHandle(hToken); + + return true; +} + #endif diff --git a/src/libnetdata/os/os-windows-wrappers.h b/src/libnetdata/os/os-windows-wrappers.h index 5ae73043a..30e1fc50d 100644 --- a/src/libnetdata/os/os-windows-wrappers.h +++ b/src/libnetdata/os/os-windows-wrappers.h @@ -14,5 +14,7 @@ bool netdata_registry_get_dword(unsigned int *out, void *hKey, char *subKey, cha long netdata_registry_get_string_from_open_key(char *out, unsigned int length, void *lKey, char *name); bool netdata_registry_get_string(char *out, unsigned int length, void *hKey, char *subKey, char *name); +bool EnableWindowsPrivilege(const char *privilegeName); + #endif // OS_WINDOWS #endif //NETDATA_OS_WINDOWS_WRAPPERS_H diff --git a/src/libnetdata/os/os.c b/src/libnetdata/os/os.c index 1caa25f85..780801fa1 100644 --- a/src/libnetdata/os/os.c +++ b/src/libnetdata/os/os.c @@ -6,12 +6,13 @@ // system functions // to retrieve settings of the system -unsigned int system_hz; +unsigned int system_hz = 100; void os_get_system_HZ(void) { long ticks; if ((ticks = sysconf(_SC_CLK_TCK)) == -1) { netdata_log_error("Cannot get system clock ticks"); + ticks = 100; } system_hz = (unsigned int) ticks; diff --git a/src/libnetdata/os/os.h b/src/libnetdata/os/os.h index 15e74faa7..1846afb6d 100644 --- a/src/libnetdata/os/os.h +++ b/src/libnetdata/os/os.h @@ -7,6 +7,8 @@ #include <sys/syscall.h> #endif +#include "random.h" +#include "timestamps.h" #include "setproctitle.h" #include "close_range.h" #include "setresuid.h" @@ -16,12 +18,21 @@ #include "gettid.h" #include "get_pid_max.h" #include "get_system_cpus.h" -#include "tinysleep.h" +#include "sleep.h" #include "uuid_generate.h" #include "setenv.h" #include "os-freebsd-wrappers.h" #include "os-macos-wrappers.h" #include "os-windows-wrappers.h" +#include "system-maps/cached-uid-username.h" +#include "system-maps/cached-gid-groupname.h" +#include "system-maps/cache-host-users-and-groups.h" +#include "system-maps/cached-sid-username.h" +#include "windows-perflib/perflib.h" + +// this includes windows.h to the whole of netdata +// so various conflicts arise +// #include "windows-wmi/windows-wmi.h" // ===================================================================================================================== // common defs for Apple/FreeBSD/Linux diff --git a/src/libnetdata/os/random.c b/src/libnetdata/os/random.c new file mode 100644 index 000000000..125e1cdb5 --- /dev/null +++ b/src/libnetdata/os/random.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +#if !defined(HAVE_ARC4RANDOM_BUF) && !defined(HAVE_RAND_S) +static SPINLOCK random_lock = NETDATA_SPINLOCK_INITIALIZER; +static __attribute__((constructor)) void random_seed() { + // Use current time and process ID to create a high-entropy seed + struct timeval tv; + gettimeofday(&tv, NULL); + + uint32_t seed = (uint32_t)(tv.tv_sec ^ tv.tv_usec ^ getpid()); + + // Seed the random number generator + srandom(seed); +} + +static inline void random_bytes(void *buf, size_t bytes) { + spinlock_lock(&random_lock); + while (bytes > 0) { + if (bytes >= sizeof(uint32_t)) { + // Generate 4 bytes at a time + uint32_t temp = random(); + memcpy(buf, &temp, sizeof(uint32_t)); + buf = (uint8_t *)buf + sizeof(uint32_t); + bytes -= sizeof(uint32_t); + } else if (bytes >= sizeof(uint16_t)) { + // Generate 2 bytes at a time + uint16_t temp = random(); + memcpy(buf, &temp, sizeof(uint16_t)); + buf = (uint8_t *)buf + sizeof(uint16_t); + bytes -= sizeof(uint16_t); + } else { + // Generate remaining bytes + uint32_t temp = random(); + for (size_t i = 0; i < bytes; i++) { + ((uint8_t *)buf)[i] = temp & 0xFF; + temp >>= 8; + } + bytes = 0; + } + } + spinlock_unlock(&random_lock); +} + +#if defined(HAVE_GETRANDOM) +#include <sys/random.h> +static inline void getrandom_bytes(void *buf, size_t bytes) { + ssize_t result; + while (bytes > 0) { + result = getrandom(buf, bytes, 0); + if (result == -1) { + if (errno == EINTR) { + // Interrupted, retry + continue; + } else if (errno == EAGAIN) { + // Insufficient entropy; wait and retry + tinysleep(); + continue; + } else { + // fallback to RAND_bytes + random_bytes(buf, bytes); + return; + } + } + buf = (uint8_t *)buf + result; + bytes -= result; + } +} +#endif // HAVE_GETRANDOM +#endif // !HAVE_ARC4RANDOM_BUF && !HAVE_RAND_S + +#if defined(HAVE_RAND_S) +static inline void rand_s_bytes(void *buf, size_t bytes) { + while (bytes > 0) { + if (bytes >= sizeof(unsigned int)) { + unsigned int temp; + rand_s(&temp); + memcpy(buf, &temp, sizeof(unsigned int)); + buf = (uint8_t *)buf + sizeof(unsigned int); + bytes -= sizeof(unsigned int); + } else if (bytes >= sizeof(uint16_t)) { + // Generate 2 bytes at a time + unsigned int t; + rand_s(&t); + uint16_t temp = t; + memcpy(buf, &temp, sizeof(uint16_t)); + buf = (uint8_t *)buf + sizeof(uint16_t); + bytes -= sizeof(uint16_t); + } else { + // Generate remaining bytes + unsigned int temp; + rand_s(&temp); + for (size_t i = 0; i < sizeof(temp) && i < bytes; i++) { + ((uint8_t *)buf)[0] = temp & 0xFF; + temp >>= 8; + buf = (uint8_t *)buf + 1; + bytes--; + } + } + } +} +#endif + +inline void os_random_bytes(void *buf, size_t bytes) { +#if defined(HAVE_ARC4RANDOM_BUF) + arc4random_buf(buf, bytes); +#else + + if(RAND_bytes((unsigned char *)buf, bytes) == 1) + return; + +#if defined(HAVE_GETRANDOM) + getrandom_bytes(buf, bytes); +#elif defined(HAVE_RAND_S) + rand_s_bytes(buf, bytes); +#else + random_bytes(buf, bytes); +#endif +#endif +} + +// Generate an 8-bit random number +uint8_t os_random8(void) { + uint8_t value; + os_random_bytes(&value, sizeof(value)); + return value; +} + +// Generate a 16-bit random number +uint16_t os_random16(void) { + uint16_t value; + os_random_bytes(&value, sizeof(value)); + return value; +} + +// Generate a 32-bit random number +uint32_t os_random32(void) { + uint32_t value; + os_random_bytes(&value, sizeof(value)); + return value; +} + +// Generate a 64-bit random number +uint64_t os_random64(void) { + uint64_t value; + os_random_bytes(&value, sizeof(value)); + return value; +} + +/* + * Rejection Sampling + * To reduce bias, we can use rejection sampling without creating an infinite loop. + * This technique works by discarding values that would introduce bias, but limiting + * the number of retries to avoid infinite loops. +*/ + +// Calculate an upper limit so that the range evenly divides into max. +// Any values greater than this limit would introduce bias, so we discard them. +#define MAX_RETRIES 10 +#define os_random_rejection_sampling_X(type, type_max, func, max) \ + ({ \ + size_t retries = 0; \ + type value, upper_limit = type_max - (type_max % (max)); \ + while ((value = func()) >= upper_limit && retries++ < MAX_RETRIES); \ + value % (max); \ + }) + +uint64_t os_random(uint64_t max) { + if (max <= 1) return 0; + +#if defined(HAVE_ARC4RANDOM_UNIFORM) + if(max <= UINT32_MAX) + // this is not biased + return arc4random_uniform(max); +#endif + + if ((max & (max - 1)) == 0) { + // max is a power of 2 + // use bitmasking to directly generate an unbiased random number + + if (max <= UINT8_MAX) + return os_random8() & (max - 1); + else if (max <= UINT16_MAX) + return os_random16() & (max - 1); + else if (max <= UINT32_MAX) + return os_random32() & (max - 1); + else + return os_random64() & (max - 1); + } + + if (max <= UINT8_MAX) + return os_random_rejection_sampling_X(uint8_t, UINT8_MAX, os_random8, max); + else if (max <= UINT16_MAX) + return os_random_rejection_sampling_X(uint16_t, UINT16_MAX, os_random16, max); + else if (max <= UINT32_MAX) + return os_random_rejection_sampling_X(uint32_t, UINT32_MAX, os_random32, max); + else + return os_random_rejection_sampling_X(uint64_t, UINT64_MAX, os_random64, max); +} diff --git a/src/libnetdata/os/random.h b/src/libnetdata/os/random.h new file mode 100644 index 000000000..d09cee5ea --- /dev/null +++ b/src/libnetdata/os/random.h @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_RANDOM_H +#define NETDATA_RANDOM_H + +#include "libnetdata/common.h" + +// fill a buffer with random bytes +void os_random_bytes(void *buf, size_t bytes); + +// return a random number 0 to max - 1 +uint64_t os_random(uint64_t max); + +uint8_t os_random8(void); +uint16_t os_random16(void); +uint32_t os_random32(void); +uint64_t os_random64(void); + +#endif //NETDATA_RANDOM_H diff --git a/src/libnetdata/os/setenv.c b/src/libnetdata/os/setenv.c index 5aa4302b8..c0de1b4b6 100644 --- a/src/libnetdata/os/setenv.c +++ b/src/libnetdata/os/setenv.c @@ -1,13 +1,8 @@ // SPDX-License-Identifier: GPL-3.0-or-later
-#include "config.h"
+#include "libnetdata/libnetdata.h"
#ifndef HAVE_SETENV
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
int os_setenv(const char *name, const char *value, int overwrite) {
char *env_var;
int result;
@@ -28,3 +23,21 @@ int os_setenv(const char *name, const char *value, int overwrite) { }
#endif
+
+void nd_setenv(const char *name, const char *value, int overwrite) {
+#if defined(OS_WINDOWS)
+ if(overwrite)
+ SetEnvironmentVariable(name, value);
+ else {
+ char buf[1024];
+ if(GetEnvironmentVariable(name, buf, sizeof(buf)) == 0)
+ SetEnvironmentVariable(name, value);
+ }
+#endif
+
+#ifdef HAVE_SETENV
+ setenv(name, value, overwrite);
+#else
+ os_setenv(name, value, overwite);
+#endif
+}
diff --git a/src/libnetdata/os/setenv.h b/src/libnetdata/os/setenv.h index 3ed63714c..78e7224de 100644 --- a/src/libnetdata/os/setenv.h +++ b/src/libnetdata/os/setenv.h @@ -10,4 +10,6 @@ int os_setenv(const char *name, const char *value, int overwrite); #define setenv(name, value, overwrite) os_setenv(name, value, overwrite)
#endif
+void nd_setenv(const char *name, const char *value, int overwrite);
+
#endif //NETDATA_SETENV_H
diff --git a/src/libnetdata/os/sleep.c b/src/libnetdata/os/sleep.c new file mode 100644 index 000000000..131b47c44 --- /dev/null +++ b/src/libnetdata/os/sleep.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../libnetdata.h" + +#ifdef OS_WINDOWS +void tinysleep(void) { + Sleep(1); +} +#else +void tinysleep(void) { + static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 }; + nanosleep(&ns, NULL); +} +#endif + +#ifdef OS_WINDOWS +void microsleep(usec_t ut) { + size_t ms = ut / USEC_PER_MS + ((ut == 0 || (ut % USEC_PER_MS)) ? 1 : 0); + Sleep(ms); +} +#else +void microsleep(usec_t ut) { + time_t secs = (time_t)(ut / USEC_PER_SEC); + nsec_t nsec = (ut % USEC_PER_SEC) * NSEC_PER_USEC + ((ut == 0) ? 1 : 0); + + struct timespec remaining = { + .tv_sec = secs, + .tv_nsec = nsec, + }; + + errno_clear(); + while (nanosleep(&remaining, &remaining) == -1 && errno == EINTR && (remaining.tv_sec || remaining.tv_nsec)) { + // Loop continues if interrupted by a signal + } +} +#endif diff --git a/src/libnetdata/os/sleep.h b/src/libnetdata/os/sleep.h new file mode 100644 index 000000000..358238762 --- /dev/null +++ b/src/libnetdata/os/sleep.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef NETDATA_SLEEP_H
+#define NETDATA_SLEEP_H
+
+void tinysleep(void);
+void microsleep(usec_t ut);
+
+#endif //NETDATA_SLEEP_H
diff --git a/src/libnetdata/os/system-maps/cache-host-users-and-groups.c b/src/libnetdata/os/system-maps/cache-host-users-and-groups.c new file mode 100644 index 000000000..53825fd35 --- /dev/null +++ b/src/libnetdata/os/system-maps/cache-host-users-and-groups.c @@ -0,0 +1,101 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" + +static bool file_changed(const struct stat *statbuf __maybe_unused, struct timespec *last_modification_time __maybe_unused) { +#if defined(OS_MACOS) || defined(OS_WINDOWS) + return false; +#else + if(likely(statbuf->st_mtim.tv_sec == last_modification_time->tv_sec && + statbuf->st_mtim.tv_nsec == last_modification_time->tv_nsec)) return false; + + last_modification_time->tv_sec = statbuf->st_mtim.tv_sec; + last_modification_time->tv_nsec = statbuf->st_mtim.tv_nsec; + + return true; +#endif +} + +static size_t read_passwd_or_group(const char *filename, struct timespec *last_modification_time, void (*cb)(uint32_t gid, const char *name, uint32_t version), uint32_t version) { + struct stat statbuf; + if(unlikely(stat(filename, &statbuf) || !file_changed(&statbuf, last_modification_time))) + return 0; + + procfile *ff = procfile_open(filename, " :\t", PROCFILE_FLAG_DEFAULT); + if(unlikely(!ff)) return 0; + + ff = procfile_readall(ff); + if(unlikely(!ff)) return 0; + + size_t line, lines = procfile_lines(ff); + + size_t added = 0; + for(line = 0; line < lines ;line++) { + size_t words = procfile_linewords(ff, line); + if(unlikely(words < 3)) continue; + + char *name = procfile_lineword(ff, line, 0); + if(unlikely(!name || !*name)) continue; + + char *id_string = procfile_lineword(ff, line, 2); + if(unlikely(!id_string || !*id_string)) continue; + + uint32_t id = str2ull(id_string, NULL); + + cb(id, name, version); + added++; + } + + procfile_close(ff); + return added; +} + +void update_cached_host_users(void) { + if(!netdata_configured_host_prefix || !*netdata_configured_host_prefix) return; + + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; + if(!spinlock_trylock(&spinlock)) return; + + char filename[FILENAME_MAX]; + static bool initialized = false; + + size_t added = 0; + + if(!initialized) { + initialized = true; + cached_usernames_init(); + } + + static uint32_t passwd_version = 0; + static struct timespec passwd_ts = { 0 }; + snprintfz(filename, FILENAME_MAX, "%s/etc/passwd", netdata_configured_host_prefix); + added = read_passwd_or_group(filename, &passwd_ts, cached_username_populate_by_uid, ++passwd_version); + if(added) cached_usernames_delete_old_versions(passwd_version); + + spinlock_unlock(&spinlock); +} + +void update_cached_host_groups(void) { + if(!netdata_configured_host_prefix || !*netdata_configured_host_prefix) return; + + static SPINLOCK spinlock = NETDATA_SPINLOCK_INITIALIZER; + if(!spinlock_trylock(&spinlock)) return; + + char filename[FILENAME_MAX]; + static bool initialized = false; + + size_t added = 0; + + if(!initialized) { + initialized = true; + cached_groupnames_init(); + } + + static uint32_t group_version = 0; + static struct timespec group_ts = { 0 }; + snprintfz(filename, FILENAME_MAX, "%s/etc/group", netdata_configured_host_prefix); + added = read_passwd_or_group(filename, &group_ts, cached_groupname_populate_by_gid, ++group_version); + if(added) cached_groupnames_delete_old_versions(group_version); + + spinlock_unlock(&spinlock); +} diff --git a/src/libnetdata/os/system-maps/cache-host-users-and-groups.h b/src/libnetdata/os/system-maps/cache-host-users-and-groups.h new file mode 100644 index 000000000..7a84bcadf --- /dev/null +++ b/src/libnetdata/os/system-maps/cache-host-users-and-groups.h @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_CACHE_HOST_USERS_AND_GROUPS_H +#define NETDATA_CACHE_HOST_USERS_AND_GROUPS_H + +void update_cached_host_users(void); +void update_cached_host_groups(void); + +#endif //NETDATA_CACHE_HOST_USERS_AND_GROUPS_H diff --git a/src/libnetdata/os/system-maps/cached-gid-groupname.c b/src/libnetdata/os/system-maps/cached-gid-groupname.c new file mode 100644 index 000000000..3fabe94a2 --- /dev/null +++ b/src/libnetdata/os/system-maps/cached-gid-groupname.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "cached-gid-groupname.h" + +// -------------------------------------------------------------------------------------------------------------------- +// hashtable for caching gid to groupname mappings +// key is the gid, value is groupname (STRING) + +#define SIMPLE_HASHTABLE_KEY_TYPE gid_t +#define SIMPLE_HASHTABLE_VALUE_TYPE CACHED_GROUPNAME +#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION cached_groupname_to_gid_ptr +#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION compar_gid_ptr +#define SIMPLE_HASHTABLE_NAME _GROUPNAMES_CACHE +#include "libnetdata/simple_hashtable/simple_hashtable.h" + +static struct { + bool initialized; + SPINLOCK spinlock; + SIMPLE_HASHTABLE_GROUPNAMES_CACHE ht; +} group_cache = { + .initialized = false, + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + .ht = { 0 }, +}; + +static gid_t *cached_groupname_to_gid_ptr(CACHED_GROUPNAME *cu) { + return &cu->gid; +} + +static bool compar_gid_ptr(gid_t *a, gid_t *b) { + return *a == *b; +} + +void cached_groupname_populate_by_gid(gid_t gid, const char *groupname, uint32_t version) { + internal_fatal(!group_cache.initialized, "system-users cache needs to be initialized"); + if(!groupname || !*groupname) return; + + spinlock_lock(&group_cache.spinlock); + + XXH64_hash_t hash = XXH3_64bits(&gid, sizeof(gid)); + SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_get_slot_GROUPNAMES_CACHE(&group_cache.ht, hash, &gid, true); + CACHED_GROUPNAME *cg = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(!cg || (cg->version && version > cg->version)) { + internal_fatal(cg && cg->gid != gid, "invalid gid matched from cache"); + + if(cg) + string_freez(cg->groupname); + else + cg = callocz(1, sizeof(*cg)); + + cg->version = version; + cg->gid = gid; + cg->groupname = string_strdupz(groupname); + simple_hashtable_set_slot_GROUPNAMES_CACHE(&group_cache.ht, sl, hash, cg); + } + + spinlock_unlock(&group_cache.spinlock); +} + +CACHED_GROUPNAME cached_groupname_get_by_gid(gid_t gid) { + internal_fatal(!group_cache.initialized, "system-users cache needs to be initialized"); + + spinlock_lock(&group_cache.spinlock); + + XXH64_hash_t hash = XXH3_64bits(&gid, sizeof(gid)); + SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_get_slot_GROUPNAMES_CACHE(&group_cache.ht, hash, &gid, true); + CACHED_GROUPNAME *cg = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(!cg) { + cg = callocz(1, sizeof(*cg)); + + static char tmp[1024]; // we are inside a global spinlock - it is ok to be static + struct group gr, *result = NULL; + + if (getgrgid_r(gid, &gr, tmp, sizeof(tmp), &result) != 0 || !result || !gr.gr_name || !(*gr.gr_name)) { + char name[UINT64_MAX_LENGTH]; + print_uint64(name, gid); + cg->groupname = string_strdupz(name); + } + else + cg->groupname = string_strdupz(gr.gr_name); + + cg->gid = gid; + simple_hashtable_set_slot_GROUPNAMES_CACHE(&group_cache.ht, sl, hash, cg); + } + + internal_fatal(cg->gid != gid, "invalid gid matched from cache"); + + CACHED_GROUPNAME rc = { + .version = cg->version, + .gid = cg->gid, + .groupname = string_dup(cg->groupname), + }; + + spinlock_unlock(&group_cache.spinlock); + return rc; +} + +void cached_groupname_release(CACHED_GROUPNAME cg) { + string_freez(cg.groupname); +} + +void cached_groupnames_init(void) { + if(group_cache.initialized) return; + group_cache.initialized = true; + + spinlock_init(&group_cache.spinlock); + simple_hashtable_init_GROUPNAMES_CACHE(&group_cache.ht, 100); +} + +void cached_groupnames_destroy(void) { + if(!group_cache.initialized) return; + + spinlock_lock(&group_cache.spinlock); + + for(SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_first_read_only_GROUPNAMES_CACHE(&group_cache.ht); + sl; + sl = simple_hashtable_next_read_only_GROUPNAMES_CACHE(&group_cache.ht, sl)) { + CACHED_GROUPNAME *u = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(u) { + string_freez(u->groupname); + freez(u); + // simple_hashtable_del_slot_GROUPNAMES_CACHE(&uc.ht, sl); + } + } + + simple_hashtable_destroy_GROUPNAMES_CACHE(&group_cache.ht); + group_cache.initialized = false; + + spinlock_unlock(&group_cache.spinlock); +} + +void cached_groupnames_delete_old_versions(uint32_t version) { + if(!group_cache.initialized) return; + + spinlock_lock(&group_cache.spinlock); + + for(SIMPLE_HASHTABLE_SLOT_GROUPNAMES_CACHE *sl = simple_hashtable_first_read_only_GROUPNAMES_CACHE(&group_cache.ht); + sl; + sl = simple_hashtable_next_read_only_GROUPNAMES_CACHE(&group_cache.ht, sl)) { + CACHED_GROUPNAME *cg = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(cg && cg->version && cg->version < version) { + string_freez(cg->groupname); + freez(cg); + simple_hashtable_del_slot_GROUPNAMES_CACHE(&group_cache.ht, sl); + } + } + + spinlock_unlock(&group_cache.spinlock); +} diff --git a/src/libnetdata/os/system-maps/cached-gid-groupname.h b/src/libnetdata/os/system-maps/cached-gid-groupname.h new file mode 100644 index 000000000..81a62523e --- /dev/null +++ b/src/libnetdata/os/system-maps/cached-gid-groupname.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_CACHED_UID_GROUPNAME_H +#define NETDATA_CACHED_UID_GROUPNAME_H + +#include "libnetdata/libnetdata.h" + +struct netdata_string; + +typedef struct { + uint32_t version; + gid_t gid; + struct netdata_string *groupname; +} CACHED_GROUPNAME; + +void cached_groupname_populate_by_gid(gid_t gid, const char *groupname, uint32_t version); +CACHED_GROUPNAME cached_groupname_get_by_gid(gid_t gid); +void cached_groupname_release(CACHED_GROUPNAME cg); +void cached_groupnames_delete_old_versions(uint32_t version); + +void cached_groupnames_init(void); +void cached_groupnames_destroy(void); + +#endif //NETDATA_CACHED_UID_GROUPNAME_H diff --git a/src/libnetdata/os/system-maps/cached-sid-username.c b/src/libnetdata/os/system-maps/cached-sid-username.c new file mode 100644 index 000000000..a0f90c546 --- /dev/null +++ b/src/libnetdata/os/system-maps/cached-sid-username.c @@ -0,0 +1,200 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "../../libnetdata.h" + +#if defined(OS_WINDOWS) +#include "cached-sid-username.h" + +typedef struct { + size_t len; + uint8_t sid[]; +} SID_KEY; + +typedef struct { + // IMPORTANT: + // This is malloc'd ! You have to manually set fields to zero. + + STRING *account; + STRING *domain; + STRING *full; + STRING *sid_str; + + // this needs to be last, because of its variable size + SID_KEY key; +} SID_VALUE; + +#define SIMPLE_HASHTABLE_NAME _SID +#define SIMPLE_HASHTABLE_VALUE_TYPE SID_VALUE +#define SIMPLE_HASHTABLE_KEY_TYPE SID_KEY +#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION sid_value_to_key +#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION sid_cache_compar +#define SIMPLE_HASHTABLE_SAMPLE_IMPLEMENTATION 1 +#include "libnetdata/simple_hashtable/simple_hashtable.h" + +static struct { + SPINLOCK spinlock; + struct simple_hashtable_SID hashtable; +} sid_globals = { + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + .hashtable = { 0 }, +}; + +static inline SID_KEY *sid_value_to_key(SID_VALUE *s) { + return &s->key; +} + +static inline bool sid_cache_compar(SID_KEY *a, SID_KEY *b) { + return a->len == b->len && memcmp(&a->sid, &b->sid, a->len) == 0; +} + +void cached_sid_username_init(void) { + simple_hashtable_init_SID(&sid_globals.hashtable, 100); +} + +static char *account2utf8(const wchar_t *user) { + static __thread char buffer[256]; + if(utf16_to_utf8(buffer, sizeof(buffer), user, -1, NULL) == 0) + buffer[0] = '\0'; + return buffer; +} + +static char *domain2utf8(const wchar_t *domain) { + static __thread char buffer[256]; + if(utf16_to_utf8(buffer, sizeof(buffer), domain, -1, NULL) == 0) + buffer[0] = '\0'; + return buffer; +} + +static void lookup_user_in_system(SID_VALUE *sv) { + static __thread wchar_t account_unicode[256]; + static __thread wchar_t domain_unicode[256]; + static __thread char tmp[512 + 2]; + + DWORD account_name_size = sizeof(account_unicode) / sizeof(account_unicode[0]); + DWORD domain_name_size = sizeof(domain_unicode) / sizeof(domain_unicode[0]); + SID_NAME_USE sid_type; + + if (LookupAccountSidW(NULL, sv->key.sid, account_unicode, &account_name_size, domain_unicode, &domain_name_size, &sid_type)) { + const char *account = account2utf8(account_unicode); + const char *domain = domain2utf8(domain_unicode); + snprintfz(tmp, sizeof(tmp), "%s\\%s", domain, account); + sv->domain = string_strdupz(domain); + sv->account = string_strdupz(account); + sv->full = string_strdupz(tmp); + } + else { + sv->domain = NULL; + sv->account = NULL; + sv->full = NULL; + } + + wchar_t *sid_string = NULL; + if (ConvertSidToStringSidW(sv->key.sid, &sid_string)) + sv->sid_str = string_strdupz(account2utf8(sid_string)); + else + sv->sid_str = NULL; +} + +static SID_VALUE *lookup_or_convert_user_id_to_name_lookup(PSID sid) { + if(!sid || !IsValidSid(sid)) + return NULL; + + size_t size = GetLengthSid(sid); + + size_t tmp_size = sizeof(SID_VALUE) + size; + size_t tmp_key_size = sizeof(SID_KEY) + size; + uint8_t buf[tmp_size]; + SID_VALUE *tmp = (SID_VALUE *)&buf; + memcpy(&tmp->key.sid, sid, size); + tmp->key.len = size; + + spinlock_lock(&sid_globals.spinlock); + SID_VALUE *found = simple_hashtable_get_SID(&sid_globals.hashtable, &tmp->key, tmp_key_size); + spinlock_unlock(&sid_globals.spinlock); + if(found) return found; + + // allocate the SID_VALUE + found = mallocz(tmp_size); + memcpy(found, buf, tmp_size); + + lookup_user_in_system(found); + + // add it to the cache + spinlock_lock(&sid_globals.spinlock); + simple_hashtable_set_SID(&sid_globals.hashtable, &found->key, tmp_key_size, found); + spinlock_unlock(&sid_globals.spinlock); + + return found; +} + +bool cached_sid_to_account_domain_sidstr(PSID sid, TXT_UTF8 *dst_account, TXT_UTF8 *dst_domain, TXT_UTF8 *dst_sid_str) { + SID_VALUE *found = lookup_or_convert_user_id_to_name_lookup(sid); + + if(found) { + if (found->account) { + txt_utf8_resize(dst_account, string_strlen(found->account) + 1, false); + memcpy(dst_account->data, string2str(found->account), string_strlen(found->account) + 1); + dst_account->used = string_strlen(found->account) + 1; + } + else + txt_utf8_empty(dst_account); + + if (found->domain) { + txt_utf8_resize(dst_domain, string_strlen(found->domain) + 1, false); + memcpy(dst_domain->data, string2str(found->domain), string_strlen(found->domain) + 1); + dst_domain->used = string_strlen(found->domain) + 1; + } + else + txt_utf8_empty(dst_domain); + + if (found->sid_str) { + txt_utf8_resize(dst_sid_str, string_strlen(found->sid_str) + 1, false); + memcpy(dst_sid_str->data, string2str(found->sid_str), string_strlen(found->sid_str) + 1); + dst_sid_str->used = string_strlen(found->sid_str) + 1; + } + else + txt_utf8_empty(dst_sid_str); + + return true; + } + + txt_utf8_empty(dst_account); + txt_utf8_empty(dst_domain); + txt_utf8_empty(dst_sid_str); + return false; +} + +bool cached_sid_to_buffer_append(PSID sid, BUFFER *dst, const char *prefix) { + SID_VALUE *found = lookup_or_convert_user_id_to_name_lookup(sid); + size_t added = 0; + + if(found) { + if (found->full) { + if (prefix && *prefix) + buffer_strcat(dst, prefix); + + buffer_fast_strcat(dst, string2str(found->full), string_strlen(found->full)); + added++; + } + if (found->sid_str) { + if (prefix && *prefix) + buffer_strcat(dst, prefix); + + buffer_fast_strcat(dst, string2str(found->sid_str), string_strlen(found->sid_str)); + added++; + } + } + + return added > 0; +} + +STRING *cached_sid_fullname_or_sid_str(PSID sid) { + SID_VALUE *found = lookup_or_convert_user_id_to_name_lookup(sid); + if(found) { + if(found->full) return string_dup(found->full); + return string_dup(found->sid_str); + } + return NULL; +} + +#endif
\ No newline at end of file diff --git a/src/libnetdata/os/system-maps/cached-sid-username.h b/src/libnetdata/os/system-maps/cached-sid-username.h new file mode 100644 index 000000000..4077cad11 --- /dev/null +++ b/src/libnetdata/os/system-maps/cached-sid-username.h @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_CACHED_SID_USERNAME_H +#define NETDATA_CACHED_SID_USERNAME_H + +#include "../../libnetdata.h" + +#if defined(OS_WINDOWS) +#include "../../string/utf8.h" + +bool cached_sid_to_account_domain_sidstr(void *sid, TXT_UTF8 *dst_account, TXT_UTF8 *dst_domain, TXT_UTF8 *dst_sid_str); +bool cached_sid_to_buffer_append(void *sid, BUFFER *dst, const char *prefix); +void cached_sid_username_init(void); +STRING *cached_sid_fullname_or_sid_str(void *sid); +#endif + +#endif //NETDATA_CACHED_SID_USERNAME_H diff --git a/src/libnetdata/os/system-maps/cached-uid-username.c b/src/libnetdata/os/system-maps/cached-uid-username.c new file mode 100644 index 000000000..35d93f2f0 --- /dev/null +++ b/src/libnetdata/os/system-maps/cached-uid-username.c @@ -0,0 +1,149 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "cached-uid-username.h" + +// -------------------------------------------------------------------------------------------------------------------- +// hashtable for caching uid to username mappings +// key is the uid, value is username (STRING) + +#define SIMPLE_HASHTABLE_KEY_TYPE uid_t +#define SIMPLE_HASHTABLE_VALUE_TYPE CACHED_USERNAME +#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION cached_username_to_uid_ptr +#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION compar_uid_ptr +#define SIMPLE_HASHTABLE_NAME _USERNAMES_CACHE +#include "libnetdata/simple_hashtable/simple_hashtable.h" + +static struct { + bool initialized; + SPINLOCK spinlock; + SIMPLE_HASHTABLE_USERNAMES_CACHE ht; +} user_cache = { + .initialized = false, + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + .ht = { 0 }, +}; + +static uid_t *cached_username_to_uid_ptr(CACHED_USERNAME *cu) { + return &cu->uid; +} + +static bool compar_uid_ptr(uid_t *a, uid_t *b) { + return *a == *b; +} + +void cached_username_populate_by_uid(uid_t uid, const char *username, uint32_t version) { + internal_fatal(!user_cache.initialized, "system-users cache needs to be initialized"); + if(!username || !*username) return; + + spinlock_lock(&user_cache.spinlock); + + XXH64_hash_t hash = XXH3_64bits(&uid, sizeof(uid)); + SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_get_slot_USERNAMES_CACHE(&user_cache.ht, hash, &uid, true); + CACHED_USERNAME *cu = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(!cu || (cu->version && version > cu->version)) { + internal_fatal(cu && cu->uid != uid, "invalid uid matched from cache"); + + if(cu) + string_freez(cu->username); + else + cu = callocz(1, sizeof(*cu)); + + cu->version = version; + cu->uid = uid; + cu->username = string_strdupz(username); + simple_hashtable_set_slot_USERNAMES_CACHE(&user_cache.ht, sl, hash, cu); + } + + spinlock_unlock(&user_cache.spinlock); +} + +CACHED_USERNAME cached_username_get_by_uid(uid_t uid) { + internal_fatal(!user_cache.initialized, "system-users cache needs to be initialized"); + + spinlock_lock(&user_cache.spinlock); + + XXH64_hash_t hash = XXH3_64bits(&uid, sizeof(uid)); + SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_get_slot_USERNAMES_CACHE(&user_cache.ht, hash, &uid, true); + CACHED_USERNAME *cu = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(!cu) { + cu = callocz(1, sizeof(*cu)); + + static char tmp[1024]; // we are inside a global spinlock - it is ok to be static + struct passwd pw, *result = NULL; + + if (getpwuid_r(uid, &pw, tmp, sizeof(tmp), &result) != 0 || !result || !pw.pw_name || !(*pw.pw_name)) { + char name[UINT64_MAX_LENGTH]; + print_uint64(name, uid); + cu->username = string_strdupz(name); + } + else + cu->username = string_strdupz(pw.pw_name); + + cu->uid = uid; + simple_hashtable_set_slot_USERNAMES_CACHE(&user_cache.ht, sl, hash, cu); + } + + internal_fatal(cu->uid != uid, "invalid uid matched from cache"); + + CACHED_USERNAME rc = { + .version = cu->version, + .uid = cu->uid, + .username = string_dup(cu->username), + }; + + spinlock_unlock(&user_cache.spinlock); + return rc; +} + +void cached_username_release(CACHED_USERNAME cu) { + string_freez(cu.username); +} + +void cached_usernames_init(void) { + if(user_cache.initialized) return; + user_cache.initialized = true; + + spinlock_init(&user_cache.spinlock); + simple_hashtable_init_USERNAMES_CACHE(&user_cache.ht, 100); +} + +void cached_usernames_destroy(void) { + if(!user_cache.initialized) return; + + spinlock_lock(&user_cache.spinlock); + + for(SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_first_read_only_USERNAMES_CACHE(&user_cache.ht); + sl; + sl = simple_hashtable_next_read_only_USERNAMES_CACHE(&user_cache.ht, sl)) { + CACHED_USERNAME *u = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(u) { + string_freez(u->username); + freez(u); + // simple_hashtable_del_slot_USERNAMES_CACHE(&uc.ht, sl); + } + } + + simple_hashtable_destroy_USERNAMES_CACHE(&user_cache.ht); + user_cache.initialized = false; + + spinlock_unlock(&user_cache.spinlock); +} + +void cached_usernames_delete_old_versions(uint32_t version) { + if(!user_cache.initialized) return; + + spinlock_lock(&user_cache.spinlock); + + for(SIMPLE_HASHTABLE_SLOT_USERNAMES_CACHE *sl = simple_hashtable_first_read_only_USERNAMES_CACHE(&user_cache.ht); + sl; + sl = simple_hashtable_next_read_only_USERNAMES_CACHE(&user_cache.ht, sl)) { + CACHED_USERNAME *cu = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(cu && cu->version && cu->version < version) { + string_freez(cu->username); + freez(cu); + simple_hashtable_del_slot_USERNAMES_CACHE(&user_cache.ht, sl); + } + } + + spinlock_unlock(&user_cache.spinlock); +} diff --git a/src/libnetdata/os/system-maps/cached-uid-username.h b/src/libnetdata/os/system-maps/cached-uid-username.h new file mode 100644 index 000000000..b7c52c7c4 --- /dev/null +++ b/src/libnetdata/os/system-maps/cached-uid-username.h @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_CACHED_UID_USERNAME_H +#define NETDATA_CACHED_UID_USERNAME_H + +#include "libnetdata/libnetdata.h" + +struct netdata_string; + +typedef struct { + uint32_t version; + uid_t uid; + struct netdata_string *username; +} CACHED_USERNAME; + +void cached_username_populate_by_uid(uid_t uid, const char *username, uint32_t version); +CACHED_USERNAME cached_username_get_by_uid(uid_t uid); +void cached_username_release(CACHED_USERNAME cu); + +void cached_usernames_init(void); +void cached_usernames_destroy(void); +void cached_usernames_delete_old_versions(uint32_t version); + +#endif //NETDATA_CACHED_UID_USERNAME_H diff --git a/src/libnetdata/os/system-maps/system-services.h b/src/libnetdata/os/system-maps/system-services.h new file mode 100644 index 000000000..5d3592bbf --- /dev/null +++ b/src/libnetdata/os/system-maps/system-services.h @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_SYSTEM_SERVICES_H +#define NETDATA_SYSTEM_SERVICES_H + +#include "libnetdata/libnetdata.h" +#include <netdb.h> + +// -------------------------------------------------------------------------------------------------------------------- +// hashtable for caching port and protocol to service name mappings +// key is the combination of protocol and port packed into an uint64_t, value is service name (STRING) + +#define SIMPLE_HASHTABLE_VALUE_TYPE STRING +#define SIMPLE_HASHTABLE_NAME _SERVICENAMES_CACHE +#include "libnetdata/simple_hashtable/simple_hashtable.h" + +typedef struct servicenames_cache { + SPINLOCK spinlock; + SIMPLE_HASHTABLE_SERVICENAMES_CACHE ht; +} SERVICENAMES_CACHE; + +static inline const char *system_servicenames_ipproto2str(uint16_t ipproto) { + return (ipproto == IPPROTO_TCP) ? "tcp" : "udp"; +} + +static inline const char *static_portnames(uint16_t port, uint16_t ipproto) { + if(port == 19999 && ipproto == IPPROTO_TCP) + return "netdata"; + + if(port == 8125) + return "statsd"; + + return NULL; +} + +static inline STRING *system_servicenames_cache_lookup(SERVICENAMES_CACHE *sc, uint16_t port, uint16_t ipproto) { + struct { + uint16_t ipproto; + uint16_t port; + } key = { + .ipproto = ipproto, + .port = port, + }; + XXH64_hash_t hash = XXH3_64bits(&key, sizeof(key)); + + spinlock_lock(&sc->spinlock); + + SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_get_slot_SERVICENAMES_CACHE(&sc->ht, hash, &key, true); + STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if (!s) { + const char *st = static_portnames(port, ipproto); + if(st) { + s = string_strdupz(st); + } + else { + struct servent *se = getservbyport(htons(port), system_servicenames_ipproto2str(ipproto)); + + if (!se || !se->s_name) { + char name[50]; + snprintfz(name, sizeof(name), "%u/%s", port, system_servicenames_ipproto2str(ipproto)); + s = string_strdupz(name); + } + else + s = string_strdupz(se->s_name); + } + + simple_hashtable_set_slot_SERVICENAMES_CACHE(&sc->ht, sl, hash, s); + } + + s = string_dup(s); + spinlock_unlock(&sc->spinlock); + return s; +} + +static inline SERVICENAMES_CACHE *system_servicenames_cache_init(void) { + SERVICENAMES_CACHE *sc = callocz(1, sizeof(*sc)); + spinlock_init(&sc->spinlock); + simple_hashtable_init_SERVICENAMES_CACHE(&sc->ht, 100); + return sc; +} + +static inline void system_servicenames_cache_destroy(SERVICENAMES_CACHE *sc) { + spinlock_lock(&sc->spinlock); + + for (SIMPLE_HASHTABLE_SLOT_SERVICENAMES_CACHE *sl = simple_hashtable_first_read_only_SERVICENAMES_CACHE(&sc->ht); + sl; + sl = simple_hashtable_next_read_only_SERVICENAMES_CACHE(&sc->ht, sl)) { + STRING *s = SIMPLE_HASHTABLE_SLOT_DATA(sl); + string_freez(s); + } + + simple_hashtable_destroy_SERVICENAMES_CACHE(&sc->ht); + freez(sc); +} + +#endif //NETDATA_SYSTEM_SERVICES_H diff --git a/src/libnetdata/os/timestamps.c b/src/libnetdata/os/timestamps.c new file mode 100644 index 000000000..602899d34 --- /dev/null +++ b/src/libnetdata/os/timestamps.c @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "libnetdata/libnetdata.h" +#include "timestamps.h" diff --git a/src/libnetdata/os/timestamps.h b/src/libnetdata/os/timestamps.h new file mode 100644 index 000000000..3737a4f40 --- /dev/null +++ b/src/libnetdata/os/timestamps.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef LIBNETDATA_OS_TIMESTAMPS_H +#define LIBNETDATA_OS_TIMESTAMPS_H + +// Windows file time starts on January 1, 1601, Unix epoch starts on January 1, 1970 +// Difference in 100-nanosecond intervals between these two dates is 116444736000000000ULL + +// Convert Windows file time (in 100-nanosecond intervals) to Unix epoch in nanoseconds +#define os_windows_ulonglong_to_unix_epoch_ns(ft) (((uint64_t)(ft) - 116444736000000000ULL) * 100ULL) + +// Convert Unix epoch time (in nanoseconds) to Windows file time (in 100-nanosecond intervals) +#define os_unix_epoch_ns_to_windows_ulonglong(ns) (((uint64_t)(ns) / 100ULL) + 116444736000000000ULL) + +#if defined(OS_WINDOWS) +// Convert FILETIME to Unix epoch in nanoseconds +#define os_filetime_to_unix_epoch_ns(ft) \ + ((((uint64_t)(ft).dwHighDateTime << 32 | (ft).dwLowDateTime) - 116444736000000000ULL) * 100ULL) + +// Convert Unix epoch in nanoseconds to FILETIME (returns FILETIME) +#define os_unix_epoch_ns_to_filetime(ns) \ + ({ \ + uint64_t temp = ((uint64_t)(ns) / 100ULL) + 116444736000000000ULL; \ + FILETIME ft; \ + ft.dwLowDateTime = (uint32_t)(temp & 0xFFFFFFFF); \ + ft.dwHighDateTime = (uint32_t)(temp >> 32); \ + ft; \ + }) + +// Convert Unix epoch in microseconds to FILETIME (returns FILETIME) +#define os_unix_epoch_ut_to_filetime(ns) \ + ({ \ + uint64_t temp = ((uint64_t)(ns) * 10ULL) + 116444736000000000ULL; \ + FILETIME ft; \ + ft.dwLowDateTime = (uint32_t)(temp & 0xFFFFFFFF); \ + ft.dwHighDateTime = (uint32_t)(temp >> 32); \ + ft; \ + }) + +#endif //OS_WINDOWS + +#endif //LIBNETDATA_OS_TIMESTAMPS_H diff --git a/src/libnetdata/os/tinysleep.c b/src/libnetdata/os/tinysleep.c deleted file mode 100644 index f04cbdadc..000000000 --- a/src/libnetdata/os/tinysleep.c +++ /dev/null @@ -1,23 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "../libnetdata.h" - -#ifdef OS_WINDOWS -#include <windows.h> - -void tinysleep(void) { - // Improve the system timer resolution to 1 ms - timeBeginPeriod(1); - - // Sleep for the desired duration - Sleep(1); - - // Reset the system timer resolution - timeEndPeriod(1); -} -#else -void tinysleep(void) { - static const struct timespec ns = { .tv_sec = 0, .tv_nsec = 1 }; - nanosleep(&ns, NULL); -} -#endif diff --git a/src/libnetdata/os/tinysleep.h b/src/libnetdata/os/tinysleep.h deleted file mode 100644 index 480575a3a..000000000 --- a/src/libnetdata/os/tinysleep.h +++ /dev/null @@ -1,8 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later
-
-#ifndef NETDATA_TINYSLEEP_H
-#define NETDATA_TINYSLEEP_H
-
-void tinysleep(void);
-
-#endif //NETDATA_TINYSLEEP_H
diff --git a/src/libnetdata/os/uuid_generate.c b/src/libnetdata/os/uuid_generate.c index 4a7a9b6bc..6019f8844 100644 --- a/src/libnetdata/os/uuid_generate.c +++ b/src/libnetdata/os/uuid_generate.c @@ -6,8 +6,6 @@ #undef uuid_generate_time #ifdef OS_WINDOWS -#include <windows.h> - void os_uuid_generate(void *out) { RPC_STATUS status = UuidCreate(out); while (status != RPC_S_OK && status != RPC_S_UUID_LOCAL_ONLY) { diff --git a/src/libnetdata/os/windows-perflib/perflib-dump.c b/src/libnetdata/os/windows-perflib/perflib-dump.c new file mode 100644 index 000000000..eaccb7827 --- /dev/null +++ b/src/libnetdata/os/windows-perflib/perflib-dump.c @@ -0,0 +1,531 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "perflib.h" + +#if defined(OS_WINDOWS) +static const char *getCounterType(DWORD CounterType) { + switch (CounterType) { + case PERF_COUNTER_COUNTER: + return "PERF_COUNTER_COUNTER"; + + case PERF_COUNTER_TIMER: + return "PERF_COUNTER_TIMER"; + + case PERF_COUNTER_QUEUELEN_TYPE: + return "PERF_COUNTER_QUEUELEN_TYPE"; + + case PERF_COUNTER_LARGE_QUEUELEN_TYPE: + return "PERF_COUNTER_LARGE_QUEUELEN_TYPE"; + + case PERF_COUNTER_100NS_QUEUELEN_TYPE: + return "PERF_COUNTER_100NS_QUEUELEN_TYPE"; + + case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE: + return "PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE"; + + case PERF_COUNTER_BULK_COUNT: + return "PERF_COUNTER_BULK_COUNT"; + + case PERF_COUNTER_TEXT: + return "PERF_COUNTER_TEXT"; + + case PERF_COUNTER_RAWCOUNT: + return "PERF_COUNTER_RAWCOUNT"; + + case PERF_COUNTER_LARGE_RAWCOUNT: + return "PERF_COUNTER_LARGE_RAWCOUNT"; + + case PERF_COUNTER_RAWCOUNT_HEX: + return "PERF_COUNTER_RAWCOUNT_HEX"; + + case PERF_COUNTER_LARGE_RAWCOUNT_HEX: + return "PERF_COUNTER_LARGE_RAWCOUNT_HEX"; + + case PERF_SAMPLE_FRACTION: + return "PERF_SAMPLE_FRACTION"; + + case PERF_SAMPLE_COUNTER: + return "PERF_SAMPLE_COUNTER"; + + case PERF_COUNTER_NODATA: + return "PERF_COUNTER_NODATA"; + + case PERF_COUNTER_TIMER_INV: + return "PERF_COUNTER_TIMER_INV"; + + case PERF_SAMPLE_BASE: + return "PERF_SAMPLE_BASE"; + + case PERF_AVERAGE_TIMER: + return "PERF_AVERAGE_TIMER"; + + case PERF_AVERAGE_BASE: + return "PERF_AVERAGE_BASE"; + + case PERF_AVERAGE_BULK: + return "PERF_AVERAGE_BULK"; + + case PERF_OBJ_TIME_TIMER: + return "PERF_OBJ_TIME_TIMER"; + + case PERF_100NSEC_TIMER: + return "PERF_100NSEC_TIMER"; + + case PERF_100NSEC_TIMER_INV: + return "PERF_100NSEC_TIMER_INV"; + + case PERF_COUNTER_MULTI_TIMER: + return "PERF_COUNTER_MULTI_TIMER"; + + case PERF_COUNTER_MULTI_TIMER_INV: + return "PERF_COUNTER_MULTI_TIMER_INV"; + + case PERF_COUNTER_MULTI_BASE: + return "PERF_COUNTER_MULTI_BASE"; + + case PERF_100NSEC_MULTI_TIMER: + return "PERF_100NSEC_MULTI_TIMER"; + + case PERF_100NSEC_MULTI_TIMER_INV: + return "PERF_100NSEC_MULTI_TIMER_INV"; + + case PERF_RAW_FRACTION: + return "PERF_RAW_FRACTION"; + + case PERF_LARGE_RAW_FRACTION: + return "PERF_LARGE_RAW_FRACTION"; + + case PERF_RAW_BASE: + return "PERF_RAW_BASE"; + + case PERF_LARGE_RAW_BASE: + return "PERF_LARGE_RAW_BASE"; + + case PERF_ELAPSED_TIME: + return "PERF_ELAPSED_TIME"; + + case PERF_COUNTER_HISTOGRAM_TYPE: + return "PERF_COUNTER_HISTOGRAM_TYPE"; + + case PERF_COUNTER_DELTA: + return "PERF_COUNTER_DELTA"; + + case PERF_COUNTER_LARGE_DELTA: + return "PERF_COUNTER_LARGE_DELTA"; + + case PERF_PRECISION_SYSTEM_TIMER: + return "PERF_PRECISION_SYSTEM_TIMER"; + + case PERF_PRECISION_100NS_TIMER: + return "PERF_PRECISION_100NS_TIMER"; + + case PERF_PRECISION_OBJECT_TIMER: + return "PERF_PRECISION_OBJECT_TIMER"; + + default: + return "UNKNOWN_COUNTER_TYPE"; + } +} + +static const char *getCounterDescription(DWORD CounterType) { + switch (CounterType) { + case PERF_COUNTER_COUNTER: + return "32-bit Counter. Divide delta by delta time. Display suffix: \"/sec\""; + + case PERF_COUNTER_TIMER: + return "64-bit Timer. Divide delta by delta time. Display suffix: \"%\""; + + case PERF_COUNTER_QUEUELEN_TYPE: + case PERF_COUNTER_LARGE_QUEUELEN_TYPE: + return "Queue Length Space-Time Product. Divide delta by delta time. No Display Suffix"; + + case PERF_COUNTER_100NS_QUEUELEN_TYPE: + return "Queue Length Space-Time Product using 100 Ns timebase. Divide delta by delta time. No Display Suffix"; + + case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE: + return "Queue Length Space-Time Product using Object specific timebase. Divide delta by delta time. No Display Suffix."; + + case PERF_COUNTER_BULK_COUNT: + return "64-bit Counter. Divide delta by delta time. Display Suffix: \"/sec\""; + + case PERF_COUNTER_TEXT: + return "Unicode text Display as text."; + + case PERF_COUNTER_RAWCOUNT: + case PERF_COUNTER_LARGE_RAWCOUNT: + return "A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."; + + case PERF_COUNTER_RAWCOUNT_HEX: + case PERF_COUNTER_LARGE_RAWCOUNT_HEX: + return "Special case for RAWCOUNT which should be displayed in hex. A counter which should not be time averaged on display (such as an error counter on a serial line). Display as is. No Display Suffix."; + + case PERF_SAMPLE_FRACTION: + return "A count which is either 1 or 0 on each sampling interrupt (% busy). Divide delta by delta base. Display Suffix: \"%\""; + + case PERF_SAMPLE_COUNTER: + return "A count which is sampled on each sampling interrupt (queue length). Divide delta by delta time. No Display Suffix."; + + case PERF_COUNTER_NODATA: + return "A label: no data is associated with this counter (it has 0 length). Do not display."; + + case PERF_COUNTER_TIMER_INV: + return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 - delta divided by delta time. Display suffix: \"%\""; + + case PERF_SAMPLE_BASE: + return "The divisor for a sample, used with the previous counter to form a sampled %. You must check for >0 before dividing by this! This counter will directly follow the numerator counter. It should not be displayed to the user."; + + case PERF_AVERAGE_TIMER: + return "A timer which, when divided by an average base, produces a time in seconds which is the average time of some operation. This timer times total operations, and the base is the number of operations. Display Suffix: \"sec\""; + + case PERF_AVERAGE_BASE: + return "Used as the denominator in the computation of time or count averages. Must directly follow the numerator counter. Not displayed to the user."; + + case PERF_AVERAGE_BULK: + return "A bulk count which, when divided (typically) by the number of operations, gives (typically) the number of bytes per operation. No Display Suffix."; + + case PERF_OBJ_TIME_TIMER: + return "64-bit Timer in object specific units. Display delta divided by delta time as returned in the object type header structure. Display suffix: \"%\""; + + case PERF_100NSEC_TIMER: + return "64-bit Timer in 100 nsec units. Display delta divided by delta time. Display suffix: \"%\""; + + case PERF_100NSEC_TIMER_INV: + return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 - delta divided by delta time. Display suffix: \"%\""; + + case PERF_COUNTER_MULTI_TIMER: + return "64-bit Timer. Divide delta by delta time. Display suffix: \"%\". Timer for multiple instances, so result can exceed 100%."; + + case PERF_COUNTER_MULTI_TIMER_INV: + return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 * _MULTI_BASE - delta divided by delta time. Display suffix: \"%\" Timer for multiple instances, so result can exceed 100%. Followed by a counter of type _MULTI_BASE."; + + case PERF_COUNTER_MULTI_BASE: + return "Number of instances to which the preceding _MULTI_..._INV counter applies. Used as a factor to get the percentage."; + + case PERF_100NSEC_MULTI_TIMER: + return "64-bit Timer in 100 nsec units. Display delta divided by delta time. Display suffix: \"%\" Timer for multiple instances, so result can exceed 100%."; + + case PERF_100NSEC_MULTI_TIMER_INV: + return "64-bit Timer inverse (e.g., idle is measured, but display busy %). Display 100 * _MULTI_BASE - delta divided by delta time. Display suffix: \"%\" Timer for multiple instances, so result can exceed 100%. Followed by a counter of type _MULTI_BASE."; + + case PERF_LARGE_RAW_FRACTION: + case PERF_RAW_FRACTION: + return "Indicates the data is a fraction of the following counter which should not be time averaged on display (such as free space over total space.) Display as is. Display the quotient as \"%\""; + + case PERF_RAW_BASE: + case PERF_LARGE_RAW_BASE: + return "Indicates the data is a base for the preceding counter which should not be time averaged on display (such as free space over total space.)"; + + case PERF_ELAPSED_TIME: + return "The data collected in this counter is actually the start time of the item being measured. For display, this data is subtracted from the sample time to yield the elapsed time as the difference between the two. In the definition below, the PerfTime field of the Object contains the sample time as indicated by the PERF_OBJECT_TIMER bit and the difference is scaled by the PerfFreq of the Object to convert the time units into seconds."; + + case PERF_COUNTER_HISTOGRAM_TYPE: + return "Counter type can be used with the preceding types to define a range of values to be displayed in a histogram."; + + case PERF_COUNTER_DELTA: + case PERF_COUNTER_LARGE_DELTA: + return "This counter is used to display the difference from one sample to the next. The counter value is a constantly increasing number and the value displayed is the difference between the current value and the previous value. Negative numbers are not allowed which shouldn't be a problem as long as the counter value is increasing or unchanged."; + + case PERF_PRECISION_SYSTEM_TIMER: + return "The precision counters are timers that consist of two counter values:\r\n\t1) the count of elapsed time of the event being monitored\r\n\t2) the \"clock\" time in the same units\r\nthe precision timers are used where the standard system timers are not precise enough for accurate readings. It's assumed that the service providing the data is also providing a timestamp at the same time which will eliminate any error that may occur since some small and variable time elapses between the time the system timestamp is captured and when the data is collected from the performance DLL. Only in extreme cases has this been observed to be problematic.\r\nwhen using this type of timer, the definition of the PERF_PRECISION_TIMESTAMP counter must immediately follow the definition of the PERF_PRECISION_*_TIMER in the Object header\r\nThe timer used has the same frequency as the System Performance Timer"; + + case PERF_PRECISION_100NS_TIMER: + return "The precision counters are timers that consist of two counter values:\r\n\t1) the count of elapsed time of the event being monitored\r\n\t2) the \"clock\" time in the same units\r\nthe precision timers are used where the standard system timers are not precise enough for accurate readings. It's assumed that the service providing the data is also providing a timestamp at the same time which will eliminate any error that may occur since some small and variable time elapses between the time the system timestamp is captured and when the data is collected from the performance DLL. Only in extreme cases has this been observed to be problematic.\r\nwhen using this type of timer, the definition of the PERF_PRECISION_TIMESTAMP counter must immediately follow the definition of the PERF_PRECISION_*_TIMER in the Object header\r\nThe timer used has the same frequency as the 100 NanoSecond Timer"; + + case PERF_PRECISION_OBJECT_TIMER: + return "The precision counters are timers that consist of two counter values:\r\n\t1) the count of elapsed time of the event being monitored\r\n\t2) the \"clock\" time in the same units\r\nthe precision timers are used where the standard system timers are not precise enough for accurate readings. It's assumed that the service providing the data is also providing a timestamp at the same time which will eliminate any error that may occur since some small and variable time elapses between the time the system timestamp is captured and when the data is collected from the performance DLL. Only in extreme cases has this been observed to be problematic.\r\nwhen using this type of timer, the definition of the PERF_PRECISION_TIMESTAMP counter must immediately follow the definition of the PERF_PRECISION_*_TIMER in the Object header\r\nThe timer used is of the frequency specified in the Object header's. PerfFreq field (PerfTime is ignored)"; + + default: + return ""; + } +} + +static const char *getCounterAlgorithm(DWORD CounterType) { + switch (CounterType) + { + case PERF_COUNTER_COUNTER: + case PERF_SAMPLE_COUNTER: + case PERF_COUNTER_BULK_COUNT: + return "(data1 - data0) / ((time1 - time0) / frequency)"; + + case PERF_COUNTER_QUEUELEN_TYPE: + case PERF_COUNTER_100NS_QUEUELEN_TYPE: + case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE: + case PERF_COUNTER_LARGE_QUEUELEN_TYPE: + case PERF_AVERAGE_BULK: // normally not displayed + return "(data1 - data0) / (time1 - time0)"; + + case PERF_OBJ_TIME_TIMER: + case PERF_COUNTER_TIMER: + case PERF_100NSEC_TIMER: + case PERF_PRECISION_SYSTEM_TIMER: + case PERF_PRECISION_100NS_TIMER: + case PERF_PRECISION_OBJECT_TIMER: + case PERF_SAMPLE_FRACTION: + return "100 * (data1 - data0) / (time1 - time0)"; + + case PERF_COUNTER_TIMER_INV: + return "100 * (1 - ((data1 - data0) / (time1 - time0)))"; + + case PERF_100NSEC_TIMER_INV: + return "100 * (1- (data1 - data0) / (time1 - time0))"; + + case PERF_COUNTER_MULTI_TIMER: + return "100 * ((data1 - data0) / ((time1 - time0) / frequency1)) / multi1"; + + case PERF_100NSEC_MULTI_TIMER: + return "100 * ((data1 - data0) / (time1 - time0)) / multi1"; + + case PERF_COUNTER_MULTI_TIMER_INV: + case PERF_100NSEC_MULTI_TIMER_INV: + return "100 * (multi1 - ((data1 - data0) / (time1 - time0)))"; + + case PERF_COUNTER_RAWCOUNT: + case PERF_COUNTER_LARGE_RAWCOUNT: + return "data0"; + + case PERF_COUNTER_RAWCOUNT_HEX: + case PERF_COUNTER_LARGE_RAWCOUNT_HEX: + return "hex(data0)"; + + case PERF_COUNTER_DELTA: + case PERF_COUNTER_LARGE_DELTA: + return "data1 - data0"; + + case PERF_RAW_FRACTION: + case PERF_LARGE_RAW_FRACTION: + return "100 * data0 / time0"; + + case PERF_AVERAGE_TIMER: + return "((data1 - data0) / frequency1) / (time1 - time0)"; + + case PERF_ELAPSED_TIME: + return "(time0 - data0) / frequency0"; + + case PERF_COUNTER_TEXT: + case PERF_SAMPLE_BASE: + case PERF_AVERAGE_BASE: + case PERF_COUNTER_MULTI_BASE: + case PERF_RAW_BASE: + case PERF_COUNTER_NODATA: + case PERF_PRECISION_TIMESTAMP: + default: + return ""; + } +} + +void dumpSystemTime(BUFFER *wb, SYSTEMTIME *st) { + buffer_json_member_add_uint64(wb, "Year", st->wYear); + buffer_json_member_add_uint64(wb, "Month", st->wMonth); + buffer_json_member_add_uint64(wb, "DayOfWeek", st->wDayOfWeek); + buffer_json_member_add_uint64(wb, "Day", st->wDay); + buffer_json_member_add_uint64(wb, "Hour", st->wHour); + buffer_json_member_add_uint64(wb, "Minute", st->wMinute); + buffer_json_member_add_uint64(wb, "Second", st->wSecond); + buffer_json_member_add_uint64(wb, "Milliseconds", st->wMilliseconds); +} + +bool dumpDataCb(PERF_DATA_BLOCK *pDataBlock, void *data) { + char name[4096]; + if(!getSystemName(pDataBlock, name, sizeof(name))) + strncpyz(name, "[failed]", sizeof(name) - 1); + + BUFFER *wb = data; + buffer_json_member_add_string(wb, "SystemName", name); + + // Number of types of objects being reported + // Type: DWORD + buffer_json_member_add_int64(wb, "NumObjectTypes", pDataBlock->NumObjectTypes); + + buffer_json_member_add_int64(wb, "LittleEndian", pDataBlock->LittleEndian); + + // Version and Revision of these data structures. + // Version starts at 1. + // Revision starts at 0 for each Version. + // Type: DWORD + buffer_json_member_add_int64(wb, "Version", pDataBlock->Version); + buffer_json_member_add_int64(wb, "Revision", pDataBlock->Revision); + + // Object Title Index of default object to display when data from this system is retrieved + // (-1 = none, but this is not expected to be used) + // Type: LONG + buffer_json_member_add_int64(wb, "DefaultObject", pDataBlock->DefaultObject); + + // Performance counter frequency at the system under measurement + // Type: LARGE_INTEGER + buffer_json_member_add_int64(wb, "PerfFreq", pDataBlock->PerfFreq.QuadPart); + + // Performance counter value at the system under measurement + // Type: LARGE_INTEGER + buffer_json_member_add_int64(wb, "PerfTime", pDataBlock->PerfTime.QuadPart); + + // Performance counter time in 100 nsec units at the system under measurement + // Type: LARGE_INTEGER + buffer_json_member_add_int64(wb, "PerfTime100nSec", pDataBlock->PerfTime100nSec.QuadPart); + + // Time at the system under measurement in UTC + // Type: SYSTEMTIME + buffer_json_member_add_object(wb, "SystemTime"); + dumpSystemTime(wb, &pDataBlock->SystemTime); + buffer_json_object_close(wb); + + if(pDataBlock->NumObjectTypes) + buffer_json_member_add_array(wb, "Objects"); + + return true; +} + +static const char *GetDetailLevel(DWORD num) { + switch (num) { + case 100: + return "Novice (100)"; + case 200: + return "Advanced (200)"; + case 300: + return "Expert (300)"; + case 400: + return "Wizard (400)"; + + default: + return "Unknown"; + } +} + +bool dumpObjectCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, void *data) { + (void)pDataBlock; + BUFFER *wb = data; + if(!pObjectType) { + buffer_json_array_close(wb); // instances or counters + buffer_json_object_close(wb); // objectType + return true; + } + + buffer_json_add_array_item_object(wb); // objectType + buffer_json_member_add_int64(wb, "NameId", pObjectType->ObjectNameTitleIndex); + buffer_json_member_add_string(wb, "Name", RegistryFindNameByID(pObjectType->ObjectNameTitleIndex)); + buffer_json_member_add_int64(wb, "HelpId", pObjectType->ObjectHelpTitleIndex); + buffer_json_member_add_string(wb, "Help", RegistryFindHelpByID(pObjectType->ObjectHelpTitleIndex)); + buffer_json_member_add_int64(wb, "NumInstances", pObjectType->NumInstances); + buffer_json_member_add_int64(wb, "NumCounters", pObjectType->NumCounters); + buffer_json_member_add_int64(wb, "PerfTime", pObjectType->PerfTime.QuadPart); + buffer_json_member_add_int64(wb, "PerfFreq", pObjectType->PerfFreq.QuadPart); + buffer_json_member_add_int64(wb, "CodePage", pObjectType->CodePage); + buffer_json_member_add_int64(wb, "DefaultCounter", pObjectType->DefaultCounter); + buffer_json_member_add_string(wb, "DetailLevel", GetDetailLevel(pObjectType->DetailLevel)); + + if(ObjectTypeHasInstances(pDataBlock, pObjectType)) + buffer_json_member_add_array(wb, "Instances"); + else + buffer_json_member_add_array(wb, "Counters"); + + return true; +} + +bool dumpInstanceCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, void *data) { + (void)pDataBlock; + BUFFER *wb = data; + if(!pInstance) { + buffer_json_array_close(wb); // counters + buffer_json_object_close(wb); // instance + return true; + } + + char name[4096]; + if(!getInstanceName(pDataBlock, pObjectType, pInstance, name, sizeof(name))) + strncpyz(name, "[failed]", sizeof(name) - 1); + + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "Instance", name); + buffer_json_member_add_int64(wb, "UniqueID", pInstance->UniqueID); + buffer_json_member_add_array(wb, "Labels"); + { + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "key", RegistryFindNameByID(pObjectType->ObjectNameTitleIndex)); + buffer_json_member_add_string(wb, "value", name); + } + buffer_json_object_close(wb); + + if(pInstance->ParentObjectTitleIndex) { + PERF_INSTANCE_DEFINITION *pi = pInstance; + while(pi->ParentObjectTitleIndex) { + PERF_OBJECT_TYPE *po = getObjectTypeByIndex(pDataBlock, pInstance->ParentObjectTitleIndex); + pi = getInstanceByPosition(pDataBlock, po, pi->ParentObjectInstance); + + if(!getInstanceName(pDataBlock, po, pi, name, sizeof(name))) + strncpyz(name, "[failed]", sizeof(name) - 1); + + buffer_json_add_array_item_object(wb); + { + buffer_json_member_add_string(wb, "key", RegistryFindNameByID(po->ObjectNameTitleIndex)); + buffer_json_member_add_string(wb, "value", name); + } + buffer_json_object_close(wb); + } + } + } + buffer_json_array_close(wb); // rrdlabels + + buffer_json_member_add_array(wb, "Counters"); + return true; +} + +void dumpSample(BUFFER *wb, RAW_DATA *d) { + buffer_json_member_add_object(wb, "Value"); + buffer_json_member_add_uint64(wb, "data", d->Data); + buffer_json_member_add_int64(wb, "time", d->Time); + buffer_json_member_add_uint64(wb, "type", d->CounterType); + buffer_json_member_add_int64(wb, "multi", d->MultiCounterData); + buffer_json_member_add_int64(wb, "frequency", d->Frequency); + buffer_json_object_close(wb); +} + +bool dumpCounterCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_COUNTER_DEFINITION *pCounter, RAW_DATA *sample, void *data) { + (void)pDataBlock; + (void)pObjectType; + BUFFER *wb = data; + buffer_json_add_array_item_object(wb); + buffer_json_member_add_string(wb, "Counter", RegistryFindNameByID(pCounter->CounterNameTitleIndex)); + dumpSample(wb, sample); + buffer_json_member_add_string(wb, "Help", RegistryFindHelpByID(pCounter->CounterHelpTitleIndex)); + buffer_json_member_add_string(wb, "Type", getCounterType(pCounter->CounterType)); + buffer_json_member_add_string(wb, "Algorithm", getCounterAlgorithm(pCounter->CounterType)); + buffer_json_member_add_string(wb, "Description", getCounterDescription(pCounter->CounterType)); + buffer_json_object_close(wb); + return true; +} + +bool dumpInstanceCounterCb(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, PERF_COUNTER_DEFINITION *pCounter, RAW_DATA *sample, void *data) { + (void)pInstance; + return dumpCounterCb(pDataBlock, pObjectType, pCounter, sample, data); +} + + +int windows_perflib_dump(const char *key) { + if(key && !*key) + key = NULL; + + PerflibNamesRegistryInitialize(); + + DWORD id = 0; + if(key) { + id = RegistryFindIDByName(key); + if(id == PERFLIB_REGISTRY_NAME_NOT_FOUND) { + fprintf(stderr, "Cannot find key '%s' in Windows Performance Counters Registry.\n", key); + exit(1); + } + } + + CLEAN_BUFFER *wb = buffer_create(0, NULL); + buffer_json_initialize(wb, "\"", "\"", 0, true, BUFFER_JSON_OPTIONS_MINIFY); + + perflibQueryAndTraverse(id, dumpDataCb, dumpObjectCb, dumpInstanceCb, dumpInstanceCounterCb, dumpCounterCb, wb); + + buffer_json_finalize(wb); + printf("\n%s\n", buffer_tostring(wb)); + + perflibFreePerformanceData(); + + return 0; +} + +#endif // OS_WINDOWS
\ No newline at end of file diff --git a/src/libnetdata/os/windows-perflib/perflib-names.c b/src/libnetdata/os/windows-perflib/perflib-names.c new file mode 100644 index 000000000..18ff2af65 --- /dev/null +++ b/src/libnetdata/os/windows-perflib/perflib-names.c @@ -0,0 +1,245 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "perflib.h" + +#if defined(OS_WINDOWS) +#define REGISTRY_KEY "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Perflib\\009" + +typedef struct perflib_registry { + DWORD id; + char *key; + char *help; +} perfLibRegistryEntry; + +static inline bool compare_perfLibRegistryEntry(const char *k1, const char *k2) { + return strcmp(k1, k2) == 0; +} + +static inline const char *value2key_perfLibRegistryEntry(perfLibRegistryEntry *entry) { + return entry->key; +} + +#define SIMPLE_HASHTABLE_COMPARE_KEYS_FUNCTION compare_perfLibRegistryEntry +#define SIMPLE_HASHTABLE_VALUE2KEY_FUNCTION value2key_perfLibRegistryEntry +#define SIMPLE_HASHTABLE_KEY_TYPE const char +#define SIMPLE_HASHTABLE_VALUE_TYPE perfLibRegistryEntry +#define SIMPLE_HASHTABLE_NAME _PERFLIB +#include "libnetdata/simple_hashtable/simple_hashtable.h" + +static struct { + SPINLOCK spinlock; + size_t size; + perfLibRegistryEntry **array; + struct simple_hashtable_PERFLIB hashtable; + FILETIME lastWriteTime; +} names_globals = { + .spinlock = NETDATA_SPINLOCK_INITIALIZER, + .size = 0, + .array = NULL, +}; + +DWORD RegistryFindIDByName(const char *name) { + DWORD rc = PERFLIB_REGISTRY_NAME_NOT_FOUND; + + spinlock_lock(&names_globals.spinlock); + XXH64_hash_t hash = XXH3_64bits((void *)name, strlen(name)); + SIMPLE_HASHTABLE_SLOT_PERFLIB *sl = simple_hashtable_get_slot_PERFLIB(&names_globals.hashtable, hash, name, false); + perfLibRegistryEntry *e = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(e) rc = e->id; + spinlock_unlock(&names_globals.spinlock); + + return rc; +} + +static inline void RegistryAddToHashTable_unsafe(perfLibRegistryEntry *entry) { + XXH64_hash_t hash = XXH3_64bits((void *)entry->key, strlen(entry->key)); + SIMPLE_HASHTABLE_SLOT_PERFLIB *sl = simple_hashtable_get_slot_PERFLIB(&names_globals.hashtable, hash, entry->key, true); + perfLibRegistryEntry *e = SIMPLE_HASHTABLE_SLOT_DATA(sl); + if(!e || e->id > entry->id) + simple_hashtable_set_slot_PERFLIB(&names_globals.hashtable, sl, hash, entry); +} + +static void RegistrySetData_unsafe(DWORD id, const char *key, const char *help) { + if(id >= names_globals.size) { + // increase the size of the array + + size_t old_size = names_globals.size; + + if(!names_globals.size) + names_globals.size = 20000; + else + names_globals.size *= 2; + + names_globals.array = reallocz(names_globals.array, names_globals.size * sizeof(perfLibRegistryEntry *)); + + memset(names_globals.array + old_size, 0, (names_globals.size - old_size) * sizeof(perfLibRegistryEntry *)); + } + + perfLibRegistryEntry *entry = names_globals.array[id]; + if(!entry) + entry = names_globals.array[id] = (perfLibRegistryEntry *)calloc(1, sizeof(perfLibRegistryEntry)); + + bool add_to_hash = false; + if(key && !entry->key) { + entry->key = strdup(key); + add_to_hash = true; + } + + if(help && !entry->help) + entry->help = strdup(help); + + entry->id = id; + + if(add_to_hash) + RegistryAddToHashTable_unsafe(entry); +} + +const char *RegistryFindNameByID(DWORD id) { + const char *s = ""; + spinlock_lock(&names_globals.spinlock); + + if(id < names_globals.size) { + perfLibRegistryEntry *titleEntry = names_globals.array[id]; + if(titleEntry && titleEntry->key) + s = titleEntry->key; + } + + spinlock_unlock(&names_globals.spinlock); + return s; +} + +const char *RegistryFindHelpByID(DWORD id) { + const char *s = ""; + spinlock_lock(&names_globals.spinlock); + + if(id < names_globals.size) { + perfLibRegistryEntry *titleEntry = names_globals.array[id]; + if(titleEntry && titleEntry->help) + s = titleEntry->help; + } + + spinlock_unlock(&names_globals.spinlock); + return s; +} + +// ---------------------------------------------------------- + +static inline void readRegistryKeys_unsafe(BOOL helps) { + TCHAR *pData = NULL; + + HKEY hKey; + DWORD dwType; + DWORD dwSize = 0; + LONG lStatus; + + LPCSTR valueName; + if(helps) + valueName = TEXT("help"); + else + valueName = TEXT("CounterDefinition"); + + // Open the key for the English counters + lStatus = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(REGISTRY_KEY), 0, KEY_READ, &hKey); + if (lStatus != ERROR_SUCCESS) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "Failed to open registry key HKEY_LOCAL_MACHINE, subkey '%s', error %ld\n", REGISTRY_KEY, (long)lStatus); + return; + } + + // Get the size of the 'Counters' data + lStatus = RegQueryValueEx(hKey, valueName, NULL, &dwType, NULL, &dwSize); + if (lStatus != ERROR_SUCCESS) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "Failed to get registry key HKEY_LOCAL_MACHINE, subkey '%s', value '%s', size of data, error %ld\n", + REGISTRY_KEY, (const char *)valueName, (long)lStatus); + goto cleanup; + } + + // Allocate memory for the data + pData = mallocz(dwSize); + + // Read the 'Counters' data + lStatus = RegQueryValueEx(hKey, valueName, NULL, &dwType, (LPBYTE)pData, &dwSize); + if (lStatus != ERROR_SUCCESS || dwType != REG_MULTI_SZ) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "Failed to get registry key HKEY_LOCAL_MACHINE, subkey '%s', value '%s', data, error %ld\n", + REGISTRY_KEY, (const char *)valueName, (long)lStatus); + goto cleanup; + } + + // Process the counter data + TCHAR *ptr = pData; + while (*ptr) { + TCHAR *sid = ptr; // First string is the ID + ptr += lstrlen(ptr) + 1; // Move to the next string + TCHAR *name = ptr; // Second string is the name + ptr += lstrlen(ptr) + 1; // Move to the next pair + + DWORD id = strtoul(sid, NULL, 10); + + if(helps) + RegistrySetData_unsafe(id, NULL, name); + else + RegistrySetData_unsafe(id, name, NULL); + } + +cleanup: + if(pData) freez(pData); + RegCloseKey(hKey); +} + +static BOOL RegistryKeyModification(FILETIME *lastWriteTime) { + HKEY hKey; + LONG lResult; + BOOL ret = FALSE; + + // Open the registry key + lResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE, TEXT(REGISTRY_KEY), 0, KEY_READ, &hKey); + if (lResult != ERROR_SUCCESS) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "Failed to open registry key HKEY_LOCAL_MACHINE, subkey '%s', error %ld\n", REGISTRY_KEY, (long)lResult); + return FALSE; + } + + // Get the last write time + lResult = RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, lastWriteTime); + if (lResult != ERROR_SUCCESS) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "Failed to query registry key HKEY_LOCAL_MACHINE, subkey '%s', last write time, error %ld\n", REGISTRY_KEY, (long)lResult); + ret = FALSE; + } + else + ret = TRUE; + + RegCloseKey(hKey); + return ret; +} + +static inline void RegistryFetchAll_unsafe(void) { + readRegistryKeys_unsafe(FALSE); + readRegistryKeys_unsafe(TRUE); +} + +void PerflibNamesRegistryInitialize(void) { + spinlock_lock(&names_globals.spinlock); + simple_hashtable_init_PERFLIB(&names_globals.hashtable, 20000); + RegistryKeyModification(&names_globals.lastWriteTime); + RegistryFetchAll_unsafe(); + spinlock_unlock(&names_globals.spinlock); +} + +void PerflibNamesRegistryUpdate(void) { + FILETIME lastWriteTime = { 0 }; + RegistryKeyModification(&lastWriteTime); + + if(CompareFileTime(&lastWriteTime, &names_globals.lastWriteTime) > 0) { + spinlock_lock(&names_globals.spinlock); + if(CompareFileTime(&lastWriteTime, &names_globals.lastWriteTime) > 0) { + names_globals.lastWriteTime = lastWriteTime; + RegistryFetchAll_unsafe(); + } + spinlock_unlock(&names_globals.spinlock); + } +} + +#endif // OS_WINDOWS diff --git a/src/libnetdata/os/windows-perflib/perflib.c b/src/libnetdata/os/windows-perflib/perflib.c new file mode 100644 index 000000000..413a202fa --- /dev/null +++ b/src/libnetdata/os/windows-perflib/perflib.c @@ -0,0 +1,687 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "perflib.h" + +#if defined(OS_WINDOWS) +// -------------------------------------------------------------------------------- + +// Retrieve a buffer that contains the specified performance data. +// The pwszSource parameter determines the data that GetRegistryBuffer returns. +// +// Typically, when calling RegQueryValueEx, you can specify zero for the size of the buffer +// and the RegQueryValueEx will set your size variable to the required buffer size. However, +// if the source is "Global" or one or more object index values, you will need to increment +// the buffer size in a loop until RegQueryValueEx does not return ERROR_MORE_DATA. +static LPBYTE getPerformanceData(const char *pwszSource) { + static __thread DWORD size = 0; + static __thread LPBYTE buffer = NULL; + + if(pwszSource == (const char *)0x01) { + freez(buffer); + buffer = NULL; + size = 0; + return NULL; + } + + if(!size) { + size = 32 * 1024; + buffer = mallocz(size); + } + + LONG status = ERROR_SUCCESS; + while ((status = RegQueryValueEx(HKEY_PERFORMANCE_DATA, pwszSource, + NULL, NULL, buffer, &size)) == ERROR_MORE_DATA) { + size *= 2; + buffer = reallocz(buffer, size); + } + + if (status != ERROR_SUCCESS) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "RegQueryValueEx failed with 0x%x.\n", status); + return NULL; + } + + return buffer; +} + +void perflibFreePerformanceData(void) { + getPerformanceData((const char *)0x01); +} + +// -------------------------------------------------------------------------------------------------------------------- + +// Retrieve the raw counter value and any supporting data needed to calculate +// a displayable counter value. Use the counter type to determine the information +// needed to calculate the value. + +static BOOL getCounterData( + PERF_DATA_BLOCK *pDataBlock, + PERF_OBJECT_TYPE* pObject, + PERF_COUNTER_DEFINITION* pCounter, + PERF_COUNTER_BLOCK* pCounterDataBlock, + PRAW_DATA pRawData) +{ + PVOID pData = NULL; + UNALIGNED ULONGLONG* pullData = NULL; + PERF_COUNTER_DEFINITION* pBaseCounter = NULL; + BOOL fSuccess = TRUE; + + //Point to the raw counter data. + pData = (PVOID)((LPBYTE)pCounterDataBlock + pCounter->CounterOffset); + + //Now use the PERF_COUNTER_DEFINITION.CounterType value to figure out what + //other information you need to calculate a displayable value. + switch (pCounter->CounterType) { + + case PERF_COUNTER_COUNTER: + case PERF_COUNTER_QUEUELEN_TYPE: + case PERF_SAMPLE_COUNTER: + pRawData->Data = (ULONGLONG)(*(DWORD*)pData); + pRawData->Time = pDataBlock->PerfTime.QuadPart; + if (PERF_COUNTER_COUNTER == pCounter->CounterType || PERF_SAMPLE_COUNTER == pCounter->CounterType) + pRawData->Frequency = pDataBlock->PerfFreq.QuadPart; + break; + + case PERF_OBJ_TIME_TIMER: + pRawData->Data = (ULONGLONG)(*(DWORD*)pData); + pRawData->Time = pObject->PerfTime.QuadPart; + break; + + case PERF_COUNTER_100NS_QUEUELEN_TYPE: + pRawData->Data = *(UNALIGNED ULONGLONG *)pData; + pRawData->Time = pDataBlock->PerfTime100nSec.QuadPart; + break; + + case PERF_COUNTER_OBJ_TIME_QUEUELEN_TYPE: + pRawData->Data = *(UNALIGNED ULONGLONG *)pData; + pRawData->Time = pObject->PerfTime.QuadPart; + break; + + case PERF_COUNTER_TIMER: + case PERF_COUNTER_TIMER_INV: + case PERF_COUNTER_BULK_COUNT: + case PERF_COUNTER_LARGE_QUEUELEN_TYPE: + pullData = (UNALIGNED ULONGLONG *)pData; + pRawData->Data = *pullData; + pRawData->Time = pDataBlock->PerfTime.QuadPart; + if (pCounter->CounterType == PERF_COUNTER_BULK_COUNT) + pRawData->Frequency = pDataBlock->PerfFreq.QuadPart; + break; + + case PERF_COUNTER_MULTI_TIMER: + case PERF_COUNTER_MULTI_TIMER_INV: + pullData = (UNALIGNED ULONGLONG *)pData; + pRawData->Data = *pullData; + pRawData->Frequency = pDataBlock->PerfFreq.QuadPart; + pRawData->Time = pDataBlock->PerfTime.QuadPart; + + //These counter types have a second counter value that is adjacent to + //this counter value in the counter data block. The value is needed for + //the calculation. + if ((pCounter->CounterType & PERF_MULTI_COUNTER) == PERF_MULTI_COUNTER) { + ++pullData; + pRawData->MultiCounterData = *(DWORD*)pullData; + } + break; + + //These counters do not use any time reference. + case PERF_COUNTER_RAWCOUNT: + case PERF_COUNTER_RAWCOUNT_HEX: + case PERF_COUNTER_DELTA: + // some counters in these categories, have CounterSize = sizeof(ULONGLONG) + // but the official documentation always uses them as sizeof(DWORD) + pRawData->Data = (ULONGLONG)(*(DWORD*)pData); + pRawData->Time = 0; + break; + + case PERF_COUNTER_LARGE_RAWCOUNT: + case PERF_COUNTER_LARGE_RAWCOUNT_HEX: + case PERF_COUNTER_LARGE_DELTA: + pRawData->Data = *(UNALIGNED ULONGLONG*)pData; + pRawData->Time = 0; + break; + + //These counters use the 100ns time base in their calculation. + case PERF_100NSEC_TIMER: + case PERF_100NSEC_TIMER_INV: + case PERF_100NSEC_MULTI_TIMER: + case PERF_100NSEC_MULTI_TIMER_INV: + pullData = (UNALIGNED ULONGLONG*)pData; + pRawData->Data = *pullData; + pRawData->Time = pDataBlock->PerfTime100nSec.QuadPart; + + //These counter types have a second counter value that is adjacent to + //this counter value in the counter data block. The value is needed for + //the calculation. + if ((pCounter->CounterType & PERF_MULTI_COUNTER) == PERF_MULTI_COUNTER) { + ++pullData; + pRawData->MultiCounterData = *(DWORD*)pullData; + } + break; + + //These counters use two data points, this value and one from this counter's + //base counter. The base counter should be the next counter in the object's + //list of counters. + case PERF_SAMPLE_FRACTION: + case PERF_RAW_FRACTION: + pRawData->Data = (ULONGLONG)(*(DWORD*)pData); + pBaseCounter = pCounter + 1; //Get base counter + if ((pBaseCounter->CounterType & PERF_COUNTER_BASE) == PERF_COUNTER_BASE) { + pData = (PVOID)((LPBYTE)pCounterDataBlock + pBaseCounter->CounterOffset); + pRawData->Time = (LONGLONG)(*(DWORD*)pData); + } + else + fSuccess = FALSE; + break; + + case PERF_LARGE_RAW_FRACTION: + case PERF_PRECISION_SYSTEM_TIMER: + case PERF_PRECISION_100NS_TIMER: + case PERF_PRECISION_OBJECT_TIMER: + pRawData->Data = *(UNALIGNED ULONGLONG*)pData; + pBaseCounter = pCounter + 1; + if ((pBaseCounter->CounterType & PERF_COUNTER_BASE) == PERF_COUNTER_BASE) { + pData = (PVOID)((LPBYTE)pCounterDataBlock + pBaseCounter->CounterOffset); + pRawData->Time = *(LONGLONG*)pData; + } + else + fSuccess = FALSE; + break; + + case PERF_AVERAGE_TIMER: + case PERF_AVERAGE_BULK: + pRawData->Data = *(UNALIGNED ULONGLONG*)pData; + pBaseCounter = pCounter+1; + if ((pBaseCounter->CounterType & PERF_COUNTER_BASE) == PERF_COUNTER_BASE) { + pData = (PVOID)((LPBYTE)pCounterDataBlock + pBaseCounter->CounterOffset); + pRawData->Time = *(DWORD*)pData; + } + else + fSuccess = FALSE; + + if (pCounter->CounterType == PERF_AVERAGE_TIMER) + pRawData->Frequency = pDataBlock->PerfFreq.QuadPart; + break; + + //These are base counters and are used in calculations for other counters. + //This case should never be entered. + case PERF_SAMPLE_BASE: + case PERF_AVERAGE_BASE: + case PERF_COUNTER_MULTI_BASE: + case PERF_RAW_BASE: + case PERF_LARGE_RAW_BASE: + pRawData->Data = 0; + pRawData->Time = 0; + fSuccess = FALSE; + break; + + case PERF_ELAPSED_TIME: + pRawData->Data = *(UNALIGNED ULONGLONG*)pData; + pRawData->Time = pObject->PerfTime.QuadPart; + pRawData->Frequency = pObject->PerfFreq.QuadPart; + break; + + //These counters are currently not supported. + case PERF_COUNTER_TEXT: + case PERF_COUNTER_NODATA: + case PERF_COUNTER_HISTOGRAM_TYPE: + default: // unknown counter types + pRawData->Data = 0; + pRawData->Time = 0; + fSuccess = FALSE; + break; + } + + return fSuccess; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline BOOL isValidPointer(PERF_DATA_BLOCK *pDataBlock __maybe_unused, void *ptr __maybe_unused) { +#ifdef NETDATA_INTERNAL_CHECKS + return (PBYTE)ptr >= (PBYTE)pDataBlock + pDataBlock->TotalByteLength ? FALSE : TRUE; +#else + return TRUE; +#endif +} + +static inline BOOL isValidStructure(PERF_DATA_BLOCK *pDataBlock __maybe_unused, void *ptr __maybe_unused, size_t length __maybe_unused) { +#ifdef NETDATA_INTERNAL_CHECKS + return (PBYTE)ptr + length > (PBYTE)pDataBlock + pDataBlock->TotalByteLength ? FALSE : TRUE; +#else + return TRUE; +#endif +} + +static inline PERF_DATA_BLOCK *getDataBlock(BYTE *pBuffer) { + PERF_DATA_BLOCK *pDataBlock = (PERF_DATA_BLOCK *)pBuffer; + + static WCHAR signature[] = { 'P', 'E', 'R', 'F' }; + + if(memcmp(pDataBlock->Signature, signature, sizeof(signature)) != 0) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Invalid data block signature."); + return NULL; + } + + if(!isValidPointer(pDataBlock, (PBYTE)pDataBlock + pDataBlock->SystemNameOffset) || + !isValidStructure(pDataBlock, (PBYTE)pDataBlock + pDataBlock->SystemNameOffset, pDataBlock->SystemNameLength)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Invalid system name array."); + return NULL; + } + + return pDataBlock; +} + +static inline PERF_OBJECT_TYPE *getObjectType(PERF_DATA_BLOCK* pDataBlock, PERF_OBJECT_TYPE *lastObjectType) { + PERF_OBJECT_TYPE* pObjectType = NULL; + + if(!lastObjectType) + pObjectType = (PERF_OBJECT_TYPE *)((PBYTE)pDataBlock + pDataBlock->HeaderLength); + else if (lastObjectType->TotalByteLength != 0) + pObjectType = (PERF_OBJECT_TYPE *)((PBYTE)lastObjectType + lastObjectType->TotalByteLength); + + if(pObjectType && (!isValidPointer(pDataBlock, pObjectType) || !isValidStructure(pDataBlock, pObjectType, pObjectType->TotalByteLength))) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Invalid ObjectType!"); + pObjectType = NULL; + } + + return pObjectType; +} + +inline PERF_OBJECT_TYPE *getObjectTypeByIndex(PERF_DATA_BLOCK *pDataBlock, DWORD ObjectNameTitleIndex) { + PERF_OBJECT_TYPE *po = NULL; + for(DWORD o = 0; o < pDataBlock->NumObjectTypes ; o++) { + po = getObjectType(pDataBlock, po); + if(po->ObjectNameTitleIndex == ObjectNameTitleIndex) + return po; + } + + return NULL; +} + +static inline PERF_INSTANCE_DEFINITION *getInstance( + PERF_DATA_BLOCK *pDataBlock, + PERF_OBJECT_TYPE *pObjectType, + PERF_COUNTER_BLOCK *lastCounterBlock +) { + PERF_INSTANCE_DEFINITION *pInstance; + + if(!lastCounterBlock) + pInstance = (PERF_INSTANCE_DEFINITION *)((PBYTE)pObjectType + pObjectType->DefinitionLength); + else + pInstance = (PERF_INSTANCE_DEFINITION *)((PBYTE)lastCounterBlock + lastCounterBlock->ByteLength); + + if(pInstance && (!isValidPointer(pDataBlock, pInstance) || !isValidStructure(pDataBlock, pInstance, pInstance->ByteLength))) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Invalid Instance Definition!"); + pInstance = NULL; + } + + return pInstance; +} + +static inline PERF_COUNTER_BLOCK *getObjectTypeCounterBlock( + PERF_DATA_BLOCK *pDataBlock, + PERF_OBJECT_TYPE *pObjectType +) { + PERF_COUNTER_BLOCK *pCounterBlock = (PERF_COUNTER_BLOCK *)((PBYTE)pObjectType + pObjectType->DefinitionLength); + + if(pCounterBlock && (!isValidPointer(pDataBlock, pCounterBlock) || !isValidStructure(pDataBlock, pCounterBlock, pCounterBlock->ByteLength))) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Invalid ObjectType CounterBlock!"); + pCounterBlock = NULL; + } + + return pCounterBlock; +} + +static inline PERF_COUNTER_BLOCK *getInstanceCounterBlock( + PERF_DATA_BLOCK *pDataBlock, + PERF_OBJECT_TYPE *pObjectType, + PERF_INSTANCE_DEFINITION *pInstance +) { + (void)pObjectType; + PERF_COUNTER_BLOCK *pCounterBlock = (PERF_COUNTER_BLOCK *)((PBYTE)pInstance + pInstance->ByteLength); + + if(pCounterBlock && (!isValidPointer(pDataBlock, pCounterBlock) || !isValidStructure(pDataBlock, pCounterBlock, pCounterBlock->ByteLength))) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Invalid Instance CounterBlock!"); + pCounterBlock = NULL; + } + + return pCounterBlock; +} + +inline PERF_INSTANCE_DEFINITION *getInstanceByPosition(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, DWORD instancePosition) { + PERF_INSTANCE_DEFINITION *pi = NULL; + PERF_COUNTER_BLOCK *pc = NULL; + for(DWORD i = 0; i <= instancePosition ;i++) { + pi = getInstance(pDataBlock, pObjectType, pc); + pc = getInstanceCounterBlock(pDataBlock, pObjectType, pi); + } + return pi; +} + +static inline PERF_COUNTER_DEFINITION *getCounterDefinition(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_COUNTER_DEFINITION *lastCounterDefinition) { + PERF_COUNTER_DEFINITION *pCounterDefinition = NULL; + + if(!lastCounterDefinition) + pCounterDefinition = (PERF_COUNTER_DEFINITION *)((PBYTE)pObjectType + pObjectType->HeaderLength); + else + pCounterDefinition = (PERF_COUNTER_DEFINITION *)((PBYTE)lastCounterDefinition + lastCounterDefinition->ByteLength); + + if(pCounterDefinition && (!isValidPointer(pDataBlock, pCounterDefinition) || !isValidStructure(pDataBlock, pCounterDefinition, pCounterDefinition->ByteLength))) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Invalid Counter Definition!"); + pCounterDefinition = NULL; + } + + return pCounterDefinition; +} + +// -------------------------------------------------------------------------------------------------------------------- + +static inline BOOL getEncodedStringToUTF8(char *dst, size_t dst_len, DWORD CodePage, char *start, DWORD length) { + static __thread wchar_t unicode[PERFLIB_MAX_NAME_LENGTH]; + + WCHAR *tempBuffer; // Temporary buffer for Unicode data + DWORD charsCopied = 0; + + if (CodePage == 0) { + // Input is already Unicode (UTF-16) + tempBuffer = (WCHAR *)start; + charsCopied = length / sizeof(WCHAR); // Convert byte length to number of WCHARs + } + else { + tempBuffer = unicode; + charsCopied = any_to_utf16(CodePage, unicode, _countof(unicode), start, (int)length, NULL); + if(!charsCopied) return FALSE; + } + + // Now convert from Unicode (UTF-16) to UTF-8 + int bytesCopied = WideCharToMultiByte(CP_UTF8, 0, tempBuffer, (int)charsCopied, dst, (int)dst_len, NULL, NULL); + if (bytesCopied == 0) { + dst[0] = '\0'; // Ensure the buffer is null-terminated even on failure + return FALSE; + } + + dst[bytesCopied - 1] = '\0'; // Ensure buffer is null-terminated + return TRUE; +} + +inline BOOL getInstanceName(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, + char *buffer, size_t bufferLen) { + (void)pDataBlock; + if (!pInstance || !buffer || !bufferLen) return FALSE; + + return getEncodedStringToUTF8(buffer, bufferLen, pObjectType->CodePage, + ((char *)pInstance + pInstance->NameOffset), pInstance->NameLength); +} + +inline BOOL getSystemName(PERF_DATA_BLOCK *pDataBlock, char *buffer, size_t bufferLen) { + return getEncodedStringToUTF8(buffer, bufferLen, 0, + ((char *)pDataBlock + pDataBlock->SystemNameOffset), pDataBlock->SystemNameLength); +} + +inline bool ObjectTypeHasInstances(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType) { + (void)pDataBlock; + return pObjectType->NumInstances != PERF_NO_INSTANCES && pObjectType->NumInstances > 0; +} + +PERF_OBJECT_TYPE *perflibFindObjectTypeByName(PERF_DATA_BLOCK *pDataBlock, const char *name) { + PERF_OBJECT_TYPE* pObjectType = NULL; + for(DWORD o = 0; o < pDataBlock->NumObjectTypes; o++) { + pObjectType = getObjectType(pDataBlock, pObjectType); + if(strcmp(name, RegistryFindNameByID(pObjectType->ObjectNameTitleIndex)) == 0) + return pObjectType; + } + + return NULL; +} + +PERF_INSTANCE_DEFINITION *perflibForEachInstance(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *lastInstance) { + if(!ObjectTypeHasInstances(pDataBlock, pObjectType)) + return NULL; + + return getInstance(pDataBlock, pObjectType, + lastInstance ? + getInstanceCounterBlock(pDataBlock, pObjectType, lastInstance) : + NULL ); +} + +bool perflibGetInstanceCounter(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, COUNTER_DATA *cd) { + DWORD id = cd->id; + const char *key = cd->key; + internal_fatal(key == NULL, "You have to set a key for this call."); + + if(unlikely(cd->failures >= PERFLIB_MAX_FAILURES_TO_FIND_METRIC)) { + // we don't want to lookup and compare strings all the time + // when a metric is not there, so we try to find it for + // XX times, and then we give up. + + if(cd->failures == PERFLIB_MAX_FAILURES_TO_FIND_METRIC) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Giving up on metric '%s' (tried to find it %u times).", + cd->key, cd->failures); + + cd->failures++; // increment it once, so that we will not log this again + } + + goto failed; + } + + PERF_COUNTER_DEFINITION *pCounterDefinition = NULL; + for(DWORD c = 0; c < pObjectType->NumCounters ;c++) { + pCounterDefinition = getCounterDefinition(pDataBlock, pObjectType, pCounterDefinition); + if(!pCounterDefinition) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Cannot read counter definition No %u (out of %u)", + c, pObjectType->NumCounters); + break; + } + + if(id) { + if(id != pCounterDefinition->CounterNameTitleIndex) + continue; + } + else { + const char *name = RegistryFindNameByID(pCounterDefinition->CounterNameTitleIndex); + if(strcmp(name, key) != 0) + continue; + + cd->id = pCounterDefinition->CounterNameTitleIndex; + } + + cd->current.CounterType = cd->OverwriteCounterType ? cd->OverwriteCounterType : pCounterDefinition->CounterType; + PERF_COUNTER_BLOCK *pCounterBlock = getInstanceCounterBlock(pDataBlock, pObjectType, pInstance); + + cd->previous = cd->current; + if(likely(getCounterData(pDataBlock, pObjectType, pCounterDefinition, pCounterBlock, &cd->current))) { + cd->updated = true; + cd->failures = 0; + return true; + } + } + + cd->failures++; + +failed: + cd->previous = cd->current; + cd->current = RAW_DATA_EMPTY; + cd->updated = false; + return false; +} + +bool perflibGetObjectCounter(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, COUNTER_DATA *cd) { + PERF_COUNTER_DEFINITION *pCounterDefinition = NULL; + for(DWORD c = 0; c < pObjectType->NumCounters ;c++) { + pCounterDefinition = getCounterDefinition(pDataBlock, pObjectType, pCounterDefinition); + if(!pCounterDefinition) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Cannot read counter definition No %u (out of %u)", + c, pObjectType->NumCounters); + break; + } + + if(cd->id) { + if(cd->id != pCounterDefinition->CounterNameTitleIndex) + continue; + } + else { + if(strcmp(RegistryFindNameByID(pCounterDefinition->CounterNameTitleIndex), cd->key) != 0) + continue; + + cd->id = pCounterDefinition->CounterNameTitleIndex; + } + + cd->current.CounterType = cd->OverwriteCounterType ? cd->OverwriteCounterType : pCounterDefinition->CounterType; + PERF_COUNTER_BLOCK *pCounterBlock = getObjectTypeCounterBlock(pDataBlock, pObjectType); + + cd->previous = cd->current; + cd->updated = getCounterData(pDataBlock, pObjectType, pCounterDefinition, pCounterBlock, &cd->current); + return cd->updated; + } + + cd->previous = cd->current; + cd->current = RAW_DATA_EMPTY; + cd->updated = false; + return false; +} + +PERF_DATA_BLOCK *perflibGetPerformanceData(DWORD id) { + char source[24]; + snprintfz(source, sizeof(source), "%u", id); + + LPBYTE pData = (LPBYTE)getPerformanceData((id > 0) ? source : NULL); + if (!pData) return NULL; + + PERF_DATA_BLOCK *pDataBlock = getDataBlock(pData); + if(!pDataBlock) return NULL; + + return pDataBlock; +} + +int perflibQueryAndTraverse(DWORD id, + perflib_data_cb dataCb, + perflib_object_cb objectCb, + perflib_instance_cb instanceCb, + perflib_instance_counter_cb instanceCounterCb, + perflib_counter_cb counterCb, + void *data) { + int counters = -1; + + PERF_DATA_BLOCK *pDataBlock = perflibGetPerformanceData(id); + if(!pDataBlock) goto cleanup; + + bool do_data = true; + if(dataCb) + do_data = dataCb(pDataBlock, data); + + PERF_OBJECT_TYPE* pObjectType = NULL; + for(DWORD o = 0; do_data && o < pDataBlock->NumObjectTypes; o++) { + pObjectType = getObjectType(pDataBlock, pObjectType); + if(!pObjectType) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Cannot read object type No %d (out of %d)", + o, pDataBlock->NumObjectTypes); + break; + } + + bool do_object = true; + if(objectCb) + do_object = objectCb(pDataBlock, pObjectType, data); + + if(!do_object) + continue; + + if(ObjectTypeHasInstances(pDataBlock, pObjectType)) { + PERF_INSTANCE_DEFINITION *pInstance = NULL; + PERF_COUNTER_BLOCK *pCounterBlock = NULL; + for(LONG i = 0; i < pObjectType->NumInstances ;i++) { + pInstance = getInstance(pDataBlock, pObjectType, pCounterBlock); + if(!pInstance) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Cannot read Instance No %d (out of %d)", + i, pObjectType->NumInstances); + break; + } + + pCounterBlock = getInstanceCounterBlock(pDataBlock, pObjectType, pInstance); + if(!pCounterBlock) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Cannot read CounterBlock of instance No %d (out of %d)", + i, pObjectType->NumInstances); + break; + } + + bool do_instance = true; + if(instanceCb) + do_instance = instanceCb(pDataBlock, pObjectType, pInstance, data); + + if(!do_instance) + continue; + + PERF_COUNTER_DEFINITION *pCounterDefinition = NULL; + for(DWORD c = 0; c < pObjectType->NumCounters ;c++) { + pCounterDefinition = getCounterDefinition(pDataBlock, pObjectType, pCounterDefinition); + if(!pCounterDefinition) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Cannot read counter definition No %u (out of %u)", + c, pObjectType->NumCounters); + break; + } + + RAW_DATA sample = { + .CounterType = pCounterDefinition->CounterType, + }; + if(getCounterData(pDataBlock, pObjectType, pCounterDefinition, pCounterBlock, &sample)) { + // DisplayCalculatedValue(&sample, &sample); + + if(instanceCounterCb) { + instanceCounterCb(pDataBlock, pObjectType, pInstance, pCounterDefinition, &sample, data); + counters++; + } + } + } + + if(instanceCb) + instanceCb(pDataBlock, pObjectType, NULL, data); + } + } + else { + PERF_COUNTER_BLOCK *pCounterBlock = getObjectTypeCounterBlock(pDataBlock, pObjectType); + PERF_COUNTER_DEFINITION *pCounterDefinition = NULL; + for(DWORD c = 0; c < pObjectType->NumCounters ;c++) { + pCounterDefinition = getCounterDefinition(pDataBlock, pObjectType, pCounterDefinition); + if(!pCounterDefinition) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, + "WINDOWS: PERFLIB: Cannot read counter definition No %u (out of %u)", + c, pObjectType->NumCounters); + break; + } + + RAW_DATA sample = { + .CounterType = pCounterDefinition->CounterType, + }; + if(getCounterData(pDataBlock, pObjectType, pCounterDefinition, pCounterBlock, &sample)) { + // DisplayCalculatedValue(&sample, &sample); + + if(counterCb) { + counterCb(pDataBlock, pObjectType, pCounterDefinition, &sample, data); + counters++; + } + } + } + } + + if(objectCb) + objectCb(pDataBlock, NULL, data); + } + +cleanup: + return counters; +} + +#endif // OS_WINDOWS
\ No newline at end of file diff --git a/src/libnetdata/os/windows-perflib/perflib.h b/src/libnetdata/os/windows-perflib/perflib.h new file mode 100644 index 000000000..650e5503b --- /dev/null +++ b/src/libnetdata/os/windows-perflib/perflib.h @@ -0,0 +1,92 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_PERFLIB_H +#define NETDATA_PERFLIB_H + +#include "libnetdata/libnetdata.h" + +#if defined(OS_WINDOWS) + +typedef uint32_t DWORD; +typedef long long LONGLONG; +typedef unsigned long long ULONGLONG; +typedef int BOOL; + +struct _PERF_DATA_BLOCK; +typedef struct _PERF_DATA_BLOCK PERF_DATA_BLOCK; +struct _PERF_OBJECT_TYPE; +typedef struct _PERF_OBJECT_TYPE PERF_OBJECT_TYPE; +struct _PERF_INSTANCE_DEFINITION; +typedef struct _PERF_INSTANCE_DEFINITION PERF_INSTANCE_DEFINITION; +struct _PERF_COUNTER_DEFINITION; +typedef struct _PERF_COUNTER_DEFINITION PERF_COUNTER_DEFINITION; + +const char *RegistryFindNameByID(DWORD id); +const char *RegistryFindHelpByID(DWORD id); +DWORD RegistryFindIDByName(const char *name); +#define PERFLIB_REGISTRY_NAME_NOT_FOUND (DWORD)-1 +#define PERFLIB_MAX_NAME_LENGTH 1024 + +PERF_DATA_BLOCK *perflibGetPerformanceData(DWORD id); +void perflibFreePerformanceData(void); +PERF_OBJECT_TYPE *perflibFindObjectTypeByName(PERF_DATA_BLOCK *pDataBlock, const char *name); +PERF_INSTANCE_DEFINITION *perflibForEachInstance(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *lastInstance); + +typedef struct _rawdata { + DWORD CounterType; + DWORD MultiCounterData; // Second raw counter value for multi-valued counters + ULONGLONG Data; // Raw counter data + LONGLONG Time; // Is a time value or a base value + LONGLONG Frequency; +} RAW_DATA, *PRAW_DATA; + +typedef struct _counterdata { + DWORD id; + bool updated; + uint8_t failures; // counts the number of failures to find this key + const char *key; + DWORD OverwriteCounterType; // if set, the counter type will be overwritten once read + RAW_DATA current; + RAW_DATA previous; +} COUNTER_DATA; + +#define PERFLIB_MAX_FAILURES_TO_FIND_METRIC 10 + +#define RAW_DATA_EMPTY (RAW_DATA){ 0 } + +bool perflibGetInstanceCounter(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, COUNTER_DATA *cd); +bool perflibGetObjectCounter(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, COUNTER_DATA *cd); + +typedef bool (*perflib_data_cb)(PERF_DATA_BLOCK *pDataBlock, void *data); +typedef bool (*perflib_object_cb)(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, void *data); +typedef bool (*perflib_instance_cb)(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, void *data); +typedef bool (*perflib_instance_counter_cb)(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, PERF_COUNTER_DEFINITION *pCounter, RAW_DATA *sample, void *data); +typedef bool (*perflib_counter_cb)(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_COUNTER_DEFINITION *pCounter, RAW_DATA *sample, void *data); + +int perflibQueryAndTraverse(DWORD id, + perflib_data_cb dataCb, + perflib_object_cb objectCb, + perflib_instance_cb instanceCb, + perflib_instance_counter_cb instanceCounterCb, + perflib_counter_cb counterCb, + void *data); + +bool ObjectTypeHasInstances(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType); + +BOOL getInstanceName(PERF_DATA_BLOCK *pDataBlock, PERF_OBJECT_TYPE *pObjectType, PERF_INSTANCE_DEFINITION *pInstance, + char *buffer, size_t bufferLen); + +BOOL getSystemName(PERF_DATA_BLOCK *pDataBlock, char *buffer, size_t bufferLen); + +PERF_OBJECT_TYPE *getObjectTypeByIndex(PERF_DATA_BLOCK *pDataBlock, DWORD ObjectNameTitleIndex); + +PERF_INSTANCE_DEFINITION *getInstanceByPosition( + PERF_DATA_BLOCK *pDataBlock, + PERF_OBJECT_TYPE *pObjectType, + DWORD instancePosition); + +void PerflibNamesRegistryInitialize(void); +void PerflibNamesRegistryUpdate(void); + +#endif // OS_WINDOWS +#endif //NETDATA_PERFLIB_H diff --git a/src/libnetdata/os/windows-wmi/windows-wmi-GetDiskDriveInfo.c b/src/libnetdata/os/windows-wmi/windows-wmi-GetDiskDriveInfo.c new file mode 100644 index 000000000..283c6f09e --- /dev/null +++ b/src/libnetdata/os/windows-wmi/windows-wmi-GetDiskDriveInfo.c @@ -0,0 +1,144 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "windows-wmi-GetDiskDriveInfo.h" + +#if defined(OS_WINDOWS) + +size_t GetDiskDriveInfo(DiskDriveInfoWMI *diskInfoArray, size_t array_size) { + if (InitializeWMI() != S_OK) return 0; + + HRESULT hr; + IEnumWbemClassObject* pEnumerator = NULL; + + // Execute the query, including new properties + BSTR query = SysAllocString(L"SELECT DeviceID, Model, Caption, Name, Partitions, Size, Status, Availability, Index, Manufacturer, InstallDate, MediaType, NeedsCleaning FROM WIN32_DiskDrive"); + BSTR wql = SysAllocString(L"WQL"); + hr = nd_wmi.pSvc->lpVtbl->ExecQuery( + nd_wmi.pSvc, + wql, + query, + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, + NULL, + &pEnumerator + ); + SysFreeString(query); + SysFreeString(wql); + + if (FAILED(hr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "GetDiskDriveInfo() WMI query failed. Error code = 0x%X", hr); + return 0; + } + + // Iterate through the results + IWbemClassObject *pclsObj = NULL; + ULONG uReturn = 0; + size_t index = 0; + while (pEnumerator && index < array_size) { + hr = pEnumerator->lpVtbl->Next(pEnumerator, WBEM_INFINITE, 1, &pclsObj, &uReturn); + if (0 == uReturn) break; + + VARIANT vtProp; + + // Extract DeviceID + hr = pclsObj->lpVtbl->Get(pclsObj, L"DeviceID", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].DeviceID, vtProp.bstrVal, sizeof(diskInfoArray[index].DeviceID)); + } + VariantClear(&vtProp); + + // Extract Model + hr = pclsObj->lpVtbl->Get(pclsObj, L"Model", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].Model, vtProp.bstrVal, sizeof(diskInfoArray[index].Model)); + } + VariantClear(&vtProp); + + // Extract Caption + hr = pclsObj->lpVtbl->Get(pclsObj, L"Caption", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].Caption, vtProp.bstrVal, sizeof(diskInfoArray[index].Caption)); + } + VariantClear(&vtProp); + + // Extract Name + hr = pclsObj->lpVtbl->Get(pclsObj, L"Name", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].Name, vtProp.bstrVal, sizeof(diskInfoArray[index].Name)); + } + VariantClear(&vtProp); + + // Extract Partitions + hr = pclsObj->lpVtbl->Get(pclsObj, L"Partitions", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && (vtProp.vt == VT_I4 || vtProp.vt == VT_UI4)) { + diskInfoArray[index].Partitions = vtProp.intVal; + } + VariantClear(&vtProp); + + // Extract Size (convert BSTR to uint64) + hr = pclsObj->lpVtbl->Get(pclsObj, L"Size", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + char sizeStr[64]; + wcstombs(sizeStr, vtProp.bstrVal, sizeof(sizeStr)); + diskInfoArray[index].Size = strtoull(sizeStr, NULL, 10); + } + VariantClear(&vtProp); + + // Extract Status + hr = pclsObj->lpVtbl->Get(pclsObj, L"Status", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].Status, vtProp.bstrVal, sizeof(diskInfoArray[index].Status)); + } + VariantClear(&vtProp); + + // Extract Availability + hr = pclsObj->lpVtbl->Get(pclsObj, L"Availability", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && (vtProp.vt == VT_I4 || vtProp.vt == VT_UI4)) { + diskInfoArray[index].Availability = vtProp.intVal; + } + VariantClear(&vtProp); + + // Extract Index + hr = pclsObj->lpVtbl->Get(pclsObj, L"Index", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && (vtProp.vt == VT_I4 || vtProp.vt == VT_UI4)) { + diskInfoArray[index].Index = vtProp.intVal; + } + VariantClear(&vtProp); + + // Extract Manufacturer + hr = pclsObj->lpVtbl->Get(pclsObj, L"Manufacturer", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].Manufacturer, vtProp.bstrVal, sizeof(diskInfoArray[index].Manufacturer)); + } + VariantClear(&vtProp); + + // Extract InstallDate + hr = pclsObj->lpVtbl->Get(pclsObj, L"InstallDate", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].InstallDate, vtProp.bstrVal, sizeof(diskInfoArray[index].InstallDate)); + } + VariantClear(&vtProp); + + // Extract MediaType + hr = pclsObj->lpVtbl->Get(pclsObj, L"MediaType", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && vtProp.vt == VT_BSTR) { + wcstombs(diskInfoArray[index].MediaType, vtProp.bstrVal, sizeof(diskInfoArray[index].MediaType)); + } + VariantClear(&vtProp); + + // Extract NeedsCleaning + hr = pclsObj->lpVtbl->Get(pclsObj, L"NeedsCleaning", 0, &vtProp, 0, 0); + if (SUCCEEDED(hr) && (vtProp.vt == VT_BOOL)) { + diskInfoArray[index].NeedsCleaning = vtProp.boolVal; + } + VariantClear(&vtProp); + + pclsObj->lpVtbl->Release(pclsObj); + index++; + } + + pEnumerator->lpVtbl->Release(pEnumerator); + + return index; +} + +#endif
\ No newline at end of file diff --git a/src/libnetdata/os/windows-wmi/windows-wmi-GetDiskDriveInfo.h b/src/libnetdata/os/windows-wmi/windows-wmi-GetDiskDriveInfo.h new file mode 100644 index 000000000..cc9b46067 --- /dev/null +++ b/src/libnetdata/os/windows-wmi/windows-wmi-GetDiskDriveInfo.h @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_WINDOWS_WMI_GETDISKDRIVEINFO_H +#define NETDATA_WINDOWS_WMI_GETDISKDRIVEINFO_H + +#include "windows-wmi.h" + +#if defined(OS_WINDOWS) + +typedef struct { + char DeviceID[256]; + char Model[256]; + char Caption[256]; + char Name[256]; + int Partitions; + unsigned long long Size; + char Status[64]; + int Availability; + int Index; + char Manufacturer[256]; + char InstallDate[64]; + char MediaType[128]; + bool NeedsCleaning; +} DiskDriveInfoWMI; + +size_t GetDiskDriveInfo(DiskDriveInfoWMI *diskInfoArray, size_t array_size); + +#endif + +#endif //NETDATA_WINDOWS_WMI_GETDISKDRIVEINFO_H diff --git a/src/libnetdata/os/windows-wmi/windows-wmi.c b/src/libnetdata/os/windows-wmi/windows-wmi.c new file mode 100644 index 000000000..02d3faa7c --- /dev/null +++ b/src/libnetdata/os/windows-wmi/windows-wmi.c @@ -0,0 +1,110 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "windows-wmi.h" + +#if defined(OS_WINDOWS) + +__thread ND_WMI nd_wmi = { 0 }; + +HRESULT InitializeWMI(void) { + if(nd_wmi.pLoc && nd_wmi.pSvc) return S_OK; + CleanupWMI(); + + IWbemLocator **pLoc = &nd_wmi.pLoc; + IWbemServices **pSvc = &nd_wmi.pSvc; + + HRESULT hr; + + // Initialize COM + hr = CoInitializeEx(0, COINIT_MULTITHREADED); + if (FAILED(hr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Failed to initialize COM library. Error code = 0x%X", hr); + CleanupWMI(); + return hr; + } + + // Set COM security levels + hr = CoInitializeSecurity( + NULL, + -1, + NULL, + NULL, + RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE, + NULL + ); + if (FAILED(hr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Failed to initialize security. Error code = 0x%X", hr); + CleanupWMI(); + return hr; + } + + // Obtain the initial locator to WMI + hr = CoCreateInstance( + &CLSID_WbemLocator, 0, + CLSCTX_INPROC_SERVER, + &IID_IWbemLocator, (LPVOID *)pLoc + ); + if (FAILED(hr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Failed to create IWbemLocator object. Error code = 0x%X", hr); + CleanupWMI(); + return hr; + } + + // Connect to WMI + BSTR namespacePath = SysAllocString(L"ROOT\\CIMV2"); + hr = (*pLoc)->lpVtbl->ConnectServer( + *pLoc, + namespacePath, + NULL, + NULL, + 0, + 0, + 0, + 0, + pSvc + ); + SysFreeString(namespacePath); + + if (FAILED(hr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Could not connect to WMI server. Error code = 0x%X", hr); + CleanupWMI(); + return hr; + } + + // Set security levels on the proxy + hr = CoSetProxyBlanket( + (IUnknown *)*pSvc, + RPC_C_AUTHN_WINNT, + RPC_C_AUTHZ_NONE, + NULL, + RPC_C_AUTHN_LEVEL_CALL, + RPC_C_IMP_LEVEL_IMPERSONATE, + NULL, + EOAC_NONE + ); + if (FAILED(hr)) { + nd_log(NDLS_COLLECTORS, NDLP_ERR, "Could not set proxy blanket. Error code = 0x%X", hr); + CleanupWMI(); + return hr; + } + + return S_OK; +} + +void CleanupWMI(void) { + if(nd_wmi.pLoc) + nd_wmi.pLoc->lpVtbl->Release(nd_wmi.pLoc); + + if (nd_wmi.pSvc) + nd_wmi.pSvc->lpVtbl->Release(nd_wmi.pSvc); + + nd_wmi.pLoc = NULL; + nd_wmi.pSvc = NULL; + + CoUninitialize(); +} + +#endif
\ No newline at end of file diff --git a/src/libnetdata/os/windows-wmi/windows-wmi.h b/src/libnetdata/os/windows-wmi/windows-wmi.h new file mode 100644 index 000000000..69d7244aa --- /dev/null +++ b/src/libnetdata/os/windows-wmi/windows-wmi.h @@ -0,0 +1,23 @@ +// SPDX-License-Identifier: GPL-3.0-or-later + +#ifndef NETDATA_WINDOWS_WMI_H +#define NETDATA_WINDOWS_WMI_H + +#include "../../libnetdata.h" + +#if defined(OS_WINDOWS) +typedef struct { + IWbemLocator *pLoc; + IWbemServices *pSvc; +} ND_WMI; + +extern __thread ND_WMI nd_wmi; + +HRESULT InitializeWMI(void); +void CleanupWMI(void); + +#include "windows-wmi-GetDiskDriveInfo.h" + +#endif + +#endif //NETDATA_WINDOWS_WMI_H |