@cryptotaxi247 / netdata-1 / commits / 361f62191

daemon status 27b (#20076)

* use native method to collect process memory, instead of relying on getrusage * remove IPs from agent-events backend * remove detection of bios mode and secure boot from dmi and status file * use ecc memory, ipmi and number of cpu sockets to influence if a system is server * add disk footprint to status file * added dir_size library calls * added metrics for database size

Costa Tsaousis committed Apr 7, 2025 at 23:59 UTC 361f6219133416e481874263036febc292df2fe0
21 files changed +1078 -253
CMakeLists.txt
+6
@@ -1086,6 +1086,10 @@ set(LIBNETDATA_FILES
1086 src/libnetdata/signals/signals.h
1087 src/libnetdata/os/machine_id.c
1088 src/libnetdata/os/machine_id.h
1089 + src/libnetdata/os/process_memory.c
1090 + src/libnetdata/os/process_memory.h
1091 + src/libnetdata/os/dir_size.c
1092 + src/libnetdata/os/dir_size.h
1093 src/libnetdata/signals/signal-code.c
1094 src/libnetdata/signals/signal-code.h
1095 )
@@ -1584,6 +1588,8 @@ set(RRD_PLUGIN_FILES
1588 src/database/rrdlabels.c
1589 src/database/rrd.c
1590 src/database/rrd.h
1591 + src/database/rrd-metadata.c
1592 + src/database/rrd-metadata.h
1593 src/database/rrdset.c
1594 src/database/storage-engine.c
1595 src/database/storage-engine.h
packaging/tools/agent-events/server.go
+15 -3
@@ -303,11 +303,23 @@ func handler(w http.ResponseWriter, r *http.Request) {
303
304 bytesReceived.Add(ctx, int64(len(body)), metric.WithAttributes(attribute.String("status", "success")))
305
306 - // Add Cloudflare Headers to all requests
306 + // Add Cloudflare Headers to all requests, excluding IP addresses for GDPR compliance
307 cfHeaders := make(map[string]string)
308 - cfHeaderPrefixes := []string{"CF-IPCountry", "CF-Ray", "CF-Connecting-IP", "CF-IPCity", "CF-IPContinent", "CF-IPLatitude", "CF-IPLongitude", "CF-IPRegion", "CF-IPTimeZone", "CF-Visitor", "CF-IPCOLO"}
308 + cfHeaderPrefixes := []string{"CF-IPCountry", "CF-Ray", "CF-IPCity", "CF-IPContinent", "CF-IPRegion", "CF-IPTimeZone", "CF-IPCOLO"}
309 + // Explicitly excluding IP-related headers: CF-Connecting-IP, CF-IPLatitude, CF-IPLongitude, CF-Visitor
310 for _, name := range cfHeaderPrefixes { if value := r.Header.Get(name); value != "" { key := strings.TrimPrefix(name, "CF-"); cfHeaders[key] = value } }
310 - for name, values := range r.Header { if strings.HasPrefix(name, "CF-") && len(values) > 0 { key := strings.TrimPrefix(name, "CF-"); if _, exists := cfHeaders[key]; !exists { cfHeaders[key] = values[0] } } }
311 + for name, values := range r.Header {
312 + if strings.HasPrefix(name, "CF-") && len(values) > 0 {
313 + // Skip IP-related headers for GDPR compliance
314 + if name == "CF-Connecting-IP" || name == "CF-IPLatitude" || name == "CF-IPLongitude" || name == "CF-Visitor" {
315 + continue
316 + }
317 + key := strings.TrimPrefix(name, "CF-");
318 + if _, exists := cfHeaders[key]; !exists {
319 + cfHeaders[key] = values[0]
320 + }
321 + }
322 + }
323 if len(cfHeaders) > 0 { fullData["cf"] = cfHeaders; slog.Debug("added cloudflare headers", "count", len(cfHeaders)) }
324
325 // Deduplication Logic
packaging/tools/agent-events/server_test.go
+11 -2
@@ -157,12 +157,21 @@ func TestHandler(t *testing.T) {
157 t.Cleanup(resetDedupState)
158 jsonBody := `{"id": "uuid-cf", "data": "value-cf"}`
159 req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader(jsonBody))
160 - req.Header.Set("CF-IPCountry", "US"); req.Header.Set("CF-Ray", "123"); req.Header.Set("CF-Connecting-IP", "1.2.3.4"); req.Header.Set("CF-IPCity", "Testville")
160 + // Set allowed headers (non-IP related)
161 + req.Header.Set("CF-IPCountry", "US"); req.Header.Set("CF-Ray", "123"); req.Header.Set("CF-IPCity", "Testville")
162 + // Set IP-related headers that should be excluded for GDPR compliance
163 + req.Header.Set("CF-Connecting-IP", "1.2.3.4"); req.Header.Set("CF-IPLatitude", "12.34"); req.Header.Set("CF-IPLongitude", "-56.78"); req.Header.Set("CF-Visitor", "{\"ip\":\"1.2.3.4\"}")
164 +
165 rr := httptest.NewRecorder(); stdout, _ := captureOutput(t, func() { handler(rr, req) })
166 if status := rr.Code; status != http.StatusOK { t.Errorf("status: got %v want %v", status, http.StatusOK) }
167 if !strings.Contains(stdout, `"cf":`) { t.Errorf("stdout missing cf object") }
168 if !strings.Contains(stdout, `"IPCountry":"US"`) { t.Errorf("stdout missing cf header IPCountry") }
165 - if !strings.Contains(stdout, `"Connecting-IP":"1.2.3.4"`) { t.Errorf("stdout missing cf header Connecting-IP") }
169 +
170 + // Verify IP-related headers are excluded for GDPR compliance
171 + if strings.Contains(stdout, `"Connecting-IP"`) { t.Errorf("stdout should not contain IP address: Connecting-IP") }
172 + if strings.Contains(stdout, `"IPLatitude"`) { t.Errorf("stdout should not contain IP geolocation: IPLatitude") }
173 + if strings.Contains(stdout, `"IPLongitude"`) { t.Errorf("stdout should not contain IP geolocation: IPLongitude") }
174 + if strings.Contains(stdout, `"Visitor"`) { t.Errorf("stdout should not contain Visitor which includes IP") }
175 })
176
177 t.Run("MethodNotAllowed", func(t *testing.T) {
src/daemon/status-file-dmi.c
-149
@@ -171,42 +171,6 @@ void os_dmi_info_get(DMI_INFO *dmi) {
171
172 linux_get_dmi_field("chassis_type", NULL, dmi->chassis.type, sizeof(dmi->chassis.type));
173
174 - // Check if running in UEFI mode - just file existence, no external command
175 - bool is_uefi = access("/sys/firmware/efi", F_OK) == 0;
176 - if (is_uefi) {
177 - safecpy(dmi->bios.mode, "UEFI");
178 -
179 - // Check EFI variable for secure boot - direct file access, no external command
180 - int fd = open("/sys/firmware/efi/efivars/SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c", O_RDONLY);
181 - if (fd != -1) {
182 - // Skip the first 4 bytes which contain the EFI variable attributes
183 - unsigned char value[5] = {0};
184 - ssize_t bytes_read = read(fd, value, sizeof(value));
185 - if (bytes_read == sizeof(value)) {
186 - // The 5th byte (index 4) contains the secure boot status
187 - dmi->bios.secure_boot = (value[4] == 1);
188 - }
189 - close(fd);
190 - }
191 -
192 - // Alternative check through securelevel - direct file access, no external command
193 - if (!dmi->bios.secure_boot) {
194 - fd = open("/sys/kernel/security/securelevel", O_RDONLY);
195 - if (fd != -1) {
196 - char level[10] = {0};
197 - ssize_t bytes_read = read(fd, level, sizeof(level) - 1);
198 - if (bytes_read > 0) {
199 - level[bytes_read] = '\0'; // Ensure null termination
200 - // If securelevel is > 0, usually means secure boot is enabled
201 - int securelevel = atoi(level);
202 - dmi->bios.secure_boot = (securelevel > 0);
203 - }
204 - close(fd);
205 - }
206 - }
207 - } else {
208 - safecpy(dmi->bios.mode, "Legacy");
209 - }
174 }
175 #elif defined(OS_MACOS)
176
@@ -561,50 +525,6 @@ void os_dmi_info_get(DMI_INFO *dmi) {
525 if (!dmi->chassis.type[0])
526 safecpy(dmi->chassis.type, "3"); // Desktop
527
564 - // Check boot mode (UEFI vs Legacy) on macOS
565 - io_registry_entry_t options = IORegistryEntryFromPath(kIOMasterPortDefault, "IODeviceTree:/options");
566 - if (options) {
567 - bool found_efi = false;
568 - CFTypeRef property = IORegistryEntryCreateCFProperty(options, CFSTR("efi-boot-device"), kCFAllocatorDefault, 0);
569 - if (property) {
570 - found_efi = true;
571 - CFRelease(property);
572 - }
573 -
574 - if (found_efi) {
575 - safecpy(dmi->bios.mode, "UEFI");
576 -
577 - // Check secure boot status
578 - CFTypeRef secure_boot_prop = IORegistryEntryCreateCFProperty(options,
579 - CFSTR("SecureBootLevel"),
580 - kCFAllocatorDefault, 0);
581 - if (secure_boot_prop) {
582 - if (CFGetTypeID(secure_boot_prop) == CFNumberGetTypeID()) {
583 - int level;
584 - if (CFNumberGetValue((CFNumberRef)secure_boot_prop, kCFNumberIntType, &level)) {
585 - // Any positive value indicates secure boot is enabled
586 - dmi->bios.secure_boot = (level > 0);
587 - }
588 - }
589 - CFRelease(secure_boot_prop);
590 - }
591 - } else {
592 - safecpy(dmi->bios.mode, "Legacy");
593 - }
594 -
595 - IOObjectRelease(options);
596 - } else {
597 - safecpy(dmi->bios.mode, "Unknown");
598 - }
599 -
600 - // Check for Apple T2 security chip (similar to TPM but without using external commands)
601 - // This uses only IOKit APIs which are safe and built-in
602 - io_registry_entry_t chip = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/AppleACPIPlatformExpert/SMC/AppleT2:");
603 - if (chip) {
604 - // If T2 chip is present, secure boot is likely enabled
605 - dmi->bios.secure_boot = true;
606 - IOObjectRelease(chip);
607 - }
528 }
529
530 #elif defined(OS_FREEBSD)
@@ -682,34 +602,6 @@ void os_dmi_info_get(DMI_INFO *dmi) {
602 // Get UUID
603 freebsd_get_kenv_str("smbios.system.uuid", dmi->sys.uuid, sizeof(dmi->sys.uuid));
604
685 - // Check for UEFI boot mode using sysctl (no external commands)
686 - char bootmethod[64] = {0};
687 - size_t bootmethod_size = sizeof(bootmethod) - 1;
688 - if (sysctlbyname("kern.bootmethod", bootmethod, &bootmethod_size, NULL, 0) == 0) {
689 - if (strncmp(bootmethod, "UEFI", 4) == 0) {
690 - safecpy(dmi->bios.mode, "UEFI");
691 -
692 - // Check secure boot status - FreeBSD stores this in sysctl (no external commands)
693 - int secure_boot_enabled = 0;
694 - size_t secboot_size = sizeof(secure_boot_enabled);
695 - if (sysctlbyname("kern.secureboot.enable", &secure_boot_enabled, &secboot_size, NULL, 0) == 0) {
696 - dmi->bios.secure_boot = (secure_boot_enabled != 0);
697 - }
698 - } else {
699 - safecpy(dmi->bios.mode, "Legacy");
700 - }
701 - } else {
702 - // Fallback: check for EFI presence (older FreeBSD versions) - no external commands
703 - int efi_present = 0;
704 - size_t efi_size = sizeof(efi_present);
705 - if (sysctlbyname("kern.efi.runtime", &efi_present, &efi_size, NULL, 0) == 0) {
706 - if (efi_present) {
707 - safecpy(dmi->bios.mode, "UEFI");
708 - } else {
709 - safecpy(dmi->bios.mode, "Legacy");
710 - }
711 - }
712 - }
605 }
606
607 #elif defined(OS_WINDOWS)
@@ -1279,33 +1171,6 @@ static void windows_get_registry_info(DMI_INFO *dmi) {
1171 sizeof(dmi->chassis.serial)
1172 );
1173
1282 - // Get BIOS boot mode (UEFI or Legacy)
1283 - DWORD firmware_type = 0;
1284 - if (GetFirmwareType(&firmware_type)) {
1285 - switch (firmware_type) {
1286 - case 1: // FirmwareTypeBios
1287 - safecpy(dmi->bios.mode, "Legacy");
1288 - break;
1289 - case 2: // FirmwareTypeUefi
1290 - safecpy(dmi->bios.mode, "UEFI");
1291 - break;
1292 - default:
1293 - safecpy(dmi->bios.mode, "Unknown");
1294 - }
1295 - }
1296 -
1297 - // Check if secure boot is enabled
1298 - HKEY key;
1299 - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State",
1300 - 0, KEY_READ, &key) == ERROR_SUCCESS) {
1301 - DWORD secure_boot_value = 0;
1302 - DWORD size = sizeof(secure_boot_value);
1303 - if (RegQueryValueExA(key, "UEFISecureBootEnabled", NULL, NULL,
1304 - (LPBYTE)&secure_boot_value, &size) == ERROR_SUCCESS) {
1305 - dmi->bios.secure_boot = (secure_boot_value != 0);
1306 - }
1307 - RegCloseKey(key);
1308 - }
1174 }
1175
1176 // Main function to get hardware information
@@ -1349,18 +1214,6 @@ void os_dmi_info_get(DMI_INFO *dmi) {
1214 );
1215 }
1216
1352 - // Check for Secure Boot - Read directly from registry, no external commands
1353 - HKEY secureBootKey;
1354 - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\SecureBoot\\State",
1355 - 0, KEY_READ, &secureBootKey) == ERROR_SUCCESS) {
1356 - DWORD secure_boot = 0;
1357 - DWORD size = sizeof(secure_boot);
1358 - if (RegQueryValueExA(secureBootKey, "UEFISecureBootEnabled", NULL, NULL,
1359 - (LPBYTE)&secure_boot, &size) == ERROR_SUCCESS) {
1360 - dmi->bios.secure_boot = (secure_boot != 0);
1361 - }
1362 - RegCloseKey(secureBootKey);
1363 - }
1217
1218 // If chassis type is not set or not a valid number, set a default
1219 if (!dmi->chassis.type[0] || atoi(dmi->chassis.type) <= 0) {
@@ -1417,8 +1270,6 @@ void dmi_info_init(DMI_INFO *dmi) {
1270 dmi->bios.version[0] = '\0';
1271 dmi->bios.date[0] = '\0';
1272 dmi->bios.release[0] = '\0';
1420 - dmi->bios.mode[0] = '\0';
1421 - dmi->bios.secure_boot = false;
1273
1274 // Chassis information
1275 dmi->chassis.vendor[0] = '\0';
src/daemon/status-file-dmi.h
-2
@@ -48,8 +48,6 @@ typedef struct dmi_info {
48 char release[64];
49 char version[64];
50 char vendor[64];
51 - char mode[16]; // Boot mode (UEFI/Legacy)
52 - bool secure_boot; // Secure boot status (true/false)
51 } bios;
52 } DMI_INFO;
53
src/daemon/status-file-product.c
+104
@@ -266,6 +266,108 @@ static const char *dmi_chassis_type_to_string(int chassis_type) {
266 }
267 }
268
269 +// Check for active ECC memory controllers
270 +static bool has_ecc_memory(void) {
271 +#if defined(OS_LINUX)
272 + char edac_path[FILENAME_MAX + 1];
273 + snprintfz(edac_path, FILENAME_MAX, "%s/sys/devices/system/edac/mc", netdata_configured_host_prefix ? netdata_configured_host_prefix : "");
274 +
275 + DIR *dir = opendir(edac_path);
276 + if (!dir)
277 + return false;
278 +
279 + struct dirent *entry;
280 + bool found_mc = false;
281 +
282 + while ((entry = readdir(dir))) {
283 + // Look for "mc0", "mc1", etc. directories
284 + if (strncmp(entry->d_name, "mc", 2) == 0 && isdigit(entry->d_name[2])) {
285 + // Check for presence of ECC-related files in this mc directory
286 + char mc_path[FILENAME_MAX + 1];
287 + snprintfz(mc_path, FILENAME_MAX, "%s/%s/ce_count", edac_path, entry->d_name);
288 +
289 + struct stat st;
290 + if (stat(mc_path, &st) == 0) {
291 + found_mc = true;
292 + break;
293 + }
294 + }
295 + }
296 +
297 + closedir(dir);
298 + return found_mc;
299 +#else
300 + return false; // Not implemented for this OS
301 +#endif
302 +}
303 +
304 +// Check for IPMI device
305 +static bool has_ipmi(void) {
306 +#if defined(OS_LINUX)
307 + char ipmi_dev_path[FILENAME_MAX + 1];
308 + snprintfz(ipmi_dev_path, FILENAME_MAX, "%s/dev/ipmi0", netdata_configured_host_prefix ? netdata_configured_host_prefix : "");
309 + struct stat ipmi_stat;
310 + return (stat(ipmi_dev_path, &ipmi_stat) == 0);
311 +#else
312 + return false; // Not implemented for this OS
313 +#endif
314 +}
315 +
316 +// Check for multiple CPU sockets
317 +static bool has_multiple_cpu_sockets(void) {
318 +#if defined(OS_LINUX)
319 + char cpu_path[FILENAME_MAX + 1];
320 + snprintfz(cpu_path, FILENAME_MAX, "%s/sys/devices/system/cpu", netdata_configured_host_prefix ? netdata_configured_host_prefix : "");
321 +
322 + DIR *dir = opendir(cpu_path);
323 + if (!dir)
324 + return false;
325 +
326 + DICTIONARY *physical_ids = dictionary_create(DICT_OPTION_SINGLE_THREADED);
327 + struct dirent *entry;
328 +
329 + while ((entry = readdir(dir))) {
330 + // Check for cpu directories (cpu0, cpu1, etc.)
331 + if (strncmp(entry->d_name, "cpu", 3) == 0 && isdigit(entry->d_name[3])) {
332 + char topology_path[FILENAME_MAX + 1];
333 + char physical_id[64];
334 +
335 + // Read the physical_package_id file for this CPU
336 + snprintfz(topology_path, FILENAME_MAX, "%s/%s/topology/physical_package_id",
337 + cpu_path, entry->d_name);
338 +
339 + if (read_txt_file(topology_path, physical_id, sizeof(physical_id)) == 0) {
340 + dictionary_set(physical_ids, physical_id, NULL, 0);
341 + }
342 + }
343 + }
344 +
345 + closedir(dir);
346 +
347 + unsigned int socket_count = dictionary_entries(physical_ids);
348 + dictionary_destroy(physical_ids);
349 +
350 + return (socket_count > 1);
351 +#else
352 + return false; // Not implemented for this OS
353 +#endif
354 +}
355 +
356 +// Main function to check for server hardware indicators
357 +static bool is_server_hardware(void) {
358 + // Check each server indicator
359 + if (has_ecc_memory())
360 + return true;
361 +
362 + if (has_ipmi())
363 + return true;
364 +
365 + if (has_multiple_cpu_sockets())
366 + return true;
367 +
368 + return false; // No server indicators found
369 +}
370 +
371 void product_name_vendor_type(DAEMON_STATUS_FILE *ds) {
372 char *force_type = NULL;
373
@@ -378,6 +480,8 @@ void product_name_vendor_type(DAEMON_STATUS_FILE *ds) {
480 safecpy(ds->product.type, force_type);
481 else if(dmi_is_virtual_machine(&ds->hw))
482 safecpy(ds->product.type, "vm");
483 + else if(is_server_hardware())
484 + safecpy(ds->product.type, "server");
485 else {
486 char *end = NULL;
487 int type = (int)strtol(ds->hw.chassis.type, &end, 10);
src/daemon/status-file.c
+118 -6
@@ -163,6 +163,41 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
163 }
164 buffer_json_object_close(wb);
165
166 + // Add metrics stats as a top-level node
167 + buffer_json_member_add_object(wb, "metrics");
168 + {
169 + buffer_json_member_add_object(wb, "nodes");
170 + {
171 + buffer_json_member_add_uint64(wb, "total", ds->metrics_metadata.nodes.total);
172 + buffer_json_member_add_uint64(wb, "receiving", ds->metrics_metadata.nodes.receiving);
173 + buffer_json_member_add_uint64(wb, "sending", ds->metrics_metadata.nodes.sending);
174 + buffer_json_member_add_uint64(wb, "archived", ds->metrics_metadata.nodes.archived);
175 + }
176 + buffer_json_object_close(wb);
177 +
178 + buffer_json_member_add_object(wb, "metrics");
179 + {
180 + buffer_json_member_add_uint64(wb, "collected", ds->metrics_metadata.metrics.collected);
181 + buffer_json_member_add_uint64(wb, "available", ds->metrics_metadata.metrics.available);
182 + }
183 + buffer_json_object_close(wb);
184 +
185 + buffer_json_member_add_object(wb, "instances");
186 + {
187 + buffer_json_member_add_uint64(wb, "collected", ds->metrics_metadata.instances.collected);
188 + buffer_json_member_add_uint64(wb, "available", ds->metrics_metadata.instances.available);
189 + }
190 + buffer_json_object_close(wb);
191 +
192 + buffer_json_member_add_object(wb, "contexts");
193 + {
194 + buffer_json_member_add_uint64(wb, "collected", ds->metrics_metadata.contexts.collected);
195 + buffer_json_member_add_uint64(wb, "available", ds->metrics_metadata.contexts.available);
196 + }
197 + buffer_json_object_close(wb);
198 + }
199 + buffer_json_object_close(wb);
200 +
201 buffer_json_member_add_object(wb, "host");
202 {
203 buffer_json_member_add_uuid_compact(wb, "id", ds->machine_id.uuid);
@@ -207,11 +242,18 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
242 buffer_json_member_add_boolean(wb, "read_only", ds->var_cache.is_read_only);
243 }
244 buffer_json_object_close(wb);
245 +
246 + buffer_json_member_add_object(wb, "netdata");
247 + buffer_json_member_add_uint64(wb, "dbengine", ds->disk_footprint.dbengine);
248 + buffer_json_member_add_uint64(wb, "sqlite", ds->disk_footprint.sqlite);
249 + buffer_json_member_add_uint64(wb, "other", ds->disk_footprint.other);
250 + buffer_json_member_add_datetime_rfc3339(wb, "last_updated", ds->disk_footprint.last_updated_ut, true);
251 + buffer_json_object_close(wb);
252 }
253 buffer_json_object_close(wb);
254 }
255 buffer_json_object_close(wb);
214 -
256 +
257 buffer_json_member_add_object(wb, "os");
258 {
259 buffer_json_member_add_string(wb, "type", DAEMON_OS_TYPE_2str(ds->os_type));
@@ -269,8 +311,6 @@ static void daemon_status_file_to_json(BUFFER *wb, DAEMON_STATUS_FILE *ds) {
311 buffer_json_member_add_string(wb, "release", ds->hw.bios.release);
312 buffer_json_member_add_string(wb, "version", ds->hw.bios.version);
313 buffer_json_member_add_string(wb, "vendor", ds->hw.bios.vendor);
272 - buffer_json_member_add_string(wb, "mode", ds->hw.bios.mode);
273 - buffer_json_member_add_boolean(wb, "secure_boot", ds->hw.bios.secure_boot);
314 }
315 buffer_json_object_close(wb);
316 }
@@ -464,6 +504,14 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
504 if(!OS_SYSTEM_DISK_SPACE_OK(ds->var_cache))
505 ds->var_cache = OS_SYSTEM_DISK_SPACE_EMPTY;
506 });
507 +
508 + JSONC_PARSE_SUBOBJECT(jobj, path, "netdata", error, required_v27, {
509 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "dbengine", ds->disk_footprint.dbengine, error, required_v27);
510 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "sqlite", ds->disk_footprint.sqlite, error, required_v27);
511 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "other", ds->disk_footprint.other, error, required_v27);
512 + JSONC_PARSE_TXT2RFC3339_USEC_OR_ERROR_AND_RETURN(jobj, path, "last_updated", ds->disk_footprint.last_updated_ut, error, required_v27);
513 + // Don't reset if not OK since this is a new field
514 + });
515 });
516
517 if(version >= 20) {
@@ -473,6 +521,31 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
521 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "cloud_region", ds->cloud_instance_region, error, required_v20);
522 }
523 });
524 +
525 + // Parse metrics metadata
526 + JSONC_PARSE_SUBOBJECT(jobj, path, "metrics", error, required_v27, {
527 + JSONC_PARSE_SUBOBJECT(jobj, path, "nodes", error, required_v27, {
528 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "total", ds->metrics_metadata.nodes.total, error, required_v27);
529 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "receiving", ds->metrics_metadata.nodes.receiving, error, required_v27);
530 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "sending", ds->metrics_metadata.nodes.sending, error, required_v27);
531 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "archived", ds->metrics_metadata.nodes.archived, error, required_v27);
532 + });
533 +
534 + JSONC_PARSE_SUBOBJECT(jobj, path, "metrics", error, required_v27, {
535 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "collected", ds->metrics_metadata.metrics.collected, error, required_v27);
536 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "available", ds->metrics_metadata.metrics.available, error, required_v27);
537 + });
538 +
539 + JSONC_PARSE_SUBOBJECT(jobj, path, "instances", error, required_v27, {
540 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "collected", ds->metrics_metadata.instances.collected, error, required_v27);
541 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "available", ds->metrics_metadata.instances.available, error, required_v27);
542 + });
543 +
544 + JSONC_PARSE_SUBOBJECT(jobj, path, "contexts", error, required_v27, {
545 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "collected", ds->metrics_metadata.contexts.collected, error, required_v27);
546 + JSONC_PARSE_UINT64_OR_ERROR_AND_RETURN(jobj, path, "available", ds->metrics_metadata.contexts.available, error, required_v27);
547 + });
548 + });
549
550 // Parse os object
551 JSONC_PARSE_SUBOBJECT(jobj, path, "os", error, required_v1, {
@@ -521,8 +594,6 @@ static bool daemon_status_file_from_json(json_object *jobj, void *data, BUFFER *
594 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "release", ds->hw.bios.release, error, required_v25);
595 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "version", ds->hw.bios.version, error, required_v25);
596 JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "vendor", ds->hw.bios.vendor, error, required_v25);
524 - JSONC_PARSE_TXT2CHAR_OR_ERROR_AND_RETURN(jobj, path, "mode", ds->hw.bios.mode, error, required_v27);
525 - JSONC_PARSE_BOOL_OR_ERROR_AND_RETURN(jobj, path, "secure_boot", ds->hw.bios.secure_boot, error, required_v27);
597 });
598 });
599
@@ -692,7 +763,10 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
763 session_status.cloud_status = cs;
764
765 session_status.oom_protection = dbengine_out_of_memory_protection;
695 - session_status.netdata_max_rss = process_max_rss();
766 +
767 + OS_PROCESS_MEMORY proc_mem = os_process_memory(0);
768 + if(OS_PROCESS_MEMORY_OK(proc_mem))
769 + session_status.netdata_max_rss = proc_mem.max_rss;
770
771 session_status.claim_id = claim_id_get_uuid();
772
@@ -718,6 +792,44 @@ static void daemon_status_file_refresh(DAEMON_STATUS status) {
792
793 session_status.memory = os_system_memory(true);
794 session_status.var_cache = os_disk_space(netdata_configured_cache_dir);
795 +
796 + // Collect metrics metadata statistics
797 + session_status.metrics_metadata = rrdstats_metadata_collect();
798 +
799 + // Update disk footprint at most once every 10 minutes (600 seconds)
800 + if ((now_ut - session_status.disk_footprint.last_updated_ut) >= 600 * USEC_PER_SEC ||
801 + session_status.disk_footprint.last_updated_ut == 0) {
802 + // Calculate disk footprint by categories
803 + const char *dirs_to_measure[] = {
804 + netdata_configured_varlib_dir,
805 + netdata_configured_cache_dir
806 + };
807 +
808 + // Create patterns for different file types
809 + SIMPLE_PATTERN *dbengine_pattern = simple_pattern_create("*dbengine*/*.ndf *dbengine*/*.njf*", " ", SIMPLE_PATTERN_EXACT, false);
810 + SIMPLE_PATTERN *sqlite_pattern = simple_pattern_create("*.db *.wal *.shm", " ", SIMPLE_PATTERN_EXACT, false);
811 +
812 + // Get total size first
813 + DIR_SIZE total_size = dir_size_multiple(dirs_to_measure, 2, NULL, 0);
814 +
815 + // Get DBEngine files size
816 + DIR_SIZE dbengine_size = dir_size_multiple(dirs_to_measure, 2, dbengine_pattern, 0);
817 + session_status.disk_footprint.dbengine = dbengine_size.bytes;
818 +
819 + // Get SQLite files size
820 + DIR_SIZE sqlite_size = dir_size_multiple(dirs_to_measure, 2, sqlite_pattern, 0);
821 + session_status.disk_footprint.sqlite = sqlite_size.bytes;
822 +
823 + // Calculate other files (total - dbengine - sqlite)
824 + session_status.disk_footprint.other = total_size.bytes - dbengine_size.bytes - sqlite_size.bytes;
825 +
826 + // Update last updated timestamp
827 + session_status.disk_footprint.last_updated_ut = now_ut;
828 +
829 + // Clean up patterns
830 + simple_pattern_free(dbengine_pattern);
831 + simple_pattern_free(sqlite_pattern);
832 + }
833
834 spinlock_unlock(&session_status.spinlock);
835 dsf_release(session_status);
src/daemon/status-file.h
+11
@@ -6,6 +6,7 @@
6 #include "libnetdata/libnetdata.h"
7 #include "daemon/config/netdata-conf-profile.h"
8 #include "database/rrd-database-mode.h"
9 +#include "database/rrd-metadata.h"
10 #include "claim/cloud-status.h"
11 #include "machine-guid.h"
12 #include "status-file-dmi.h"
@@ -73,6 +74,16 @@ typedef struct daemon_status_file {
74 uint64_t netdata_max_rss;
75 OS_SYSTEM_MEMORY memory;
76 OS_SYSTEM_DISK_SPACE var_cache;
77 +
78 + struct {
79 + uint64_t dbengine; // Size of dbengine files
80 + uint64_t sqlite; // Size of sqlite files
81 + uint64_t other; // Size of other files (total - dbengine - sqlite)
82 + usec_t last_updated_ut; // Last time the footprint was updated (microseconds)
83 + } disk_footprint;
84 +
85 + // Metrics statistics
86 + RRDSTATS_METADATA metrics_metadata;
87
88 char install_type[32];
89 char architecture[32]; // ECS: host.architecture
src/database/contexts/api_v2_contexts_agents.c
+13 -41
@@ -2,6 +2,7 @@
2
3 #include "api_v2_contexts.h"
4 #include "aclk/aclk_capas.h"
5 +#include "database/rrd-metadata.h"
6
7 void build_info_to_json_object(BUFFER *b);
8
@@ -42,65 +43,36 @@ void buffer_json_agents_v2(BUFFER *wb, struct query_timings *timings, time_t now
43
44 buffer_json_cloud_status(wb, now_s);
45
45 - size_t collected_metrics = 0;
46 - size_t collected_instances = 0;
47 - size_t collected_contexts = 0;
48 - size_t available_metrics = 0;
49 - size_t available_instances = 0;
50 - size_t available_contexts = 0;
46 + // Get metrics metadata using our reusable function
47 + RRDSTATS_METADATA metadata = rrdstats_metadata_collect();
48
49 buffer_json_member_add_object(wb, "nodes");
50 {
54 - size_t receiving = 0, archived = 0, sending = 0, total = 0;
55 - RRDHOST *host;
56 - dfe_start_read(rrdhost_root_index, host) {
57 - total++;
58 -
59 - available_metrics += __atomic_load_n(&host->rrdctx.metrics_count, __ATOMIC_RELAXED);
60 - available_instances += __atomic_load_n(&host->rrdctx.instances_count, __ATOMIC_RELAXED);
61 - available_contexts += __atomic_load_n(&host->rrdctx.contexts_count, __ATOMIC_RELAXED);
62 -
63 - if(rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_CONNECTED))
64 - sending++;
65 -
66 - if (rrdhost_is_online(host)) {
67 - collected_metrics += __atomic_load_n(&host->collected.metrics_count, __ATOMIC_RELAXED);
68 - collected_instances += __atomic_load_n(&host->collected.instances_count, __ATOMIC_RELAXED);
69 - collected_contexts += __atomic_load_n(&host->collected.contexts_count, __ATOMIC_RELAXED);
70 -
71 - if(host != localhost)
72 - receiving++;
73 - }
74 - else
75 - archived++;
76 - }
77 - dfe_done(host);
78 -
79 - buffer_json_member_add_uint64(wb, "total", total);
80 - buffer_json_member_add_uint64(wb, "receiving", receiving);
81 - buffer_json_member_add_uint64(wb, "sending", sending);
82 - buffer_json_member_add_uint64(wb, "archived", archived);
51 + buffer_json_member_add_uint64(wb, "total", metadata.nodes.total);
52 + buffer_json_member_add_uint64(wb, "receiving", metadata.nodes.receiving);
53 + buffer_json_member_add_uint64(wb, "sending", metadata.nodes.sending);
54 + buffer_json_member_add_uint64(wb, "archived", metadata.nodes.archived);
55 }
56 buffer_json_object_close(wb); // nodes
57
58 buffer_json_member_add_object(wb, "metrics");
59 {
88 - buffer_json_member_add_uint64(wb, "collected", collected_metrics);
89 - buffer_json_member_add_uint64(wb, "available", available_metrics);
60 + buffer_json_member_add_uint64(wb, "collected", metadata.metrics.collected);
61 + buffer_json_member_add_uint64(wb, "available", metadata.metrics.available);
62 }
63 buffer_json_object_close(wb);
64
65 buffer_json_member_add_object(wb, "instances");
66 {
95 - buffer_json_member_add_uint64(wb, "collected", collected_instances);
96 - buffer_json_member_add_uint64(wb, "available", available_instances);
67 + buffer_json_member_add_uint64(wb, "collected", metadata.instances.collected);
68 + buffer_json_member_add_uint64(wb, "available", metadata.instances.available);
69 }
70 buffer_json_object_close(wb);
71
72 buffer_json_member_add_object(wb, "contexts");
73 {
102 - buffer_json_member_add_uint64(wb, "collected", collected_contexts);
103 - buffer_json_member_add_uint64(wb, "available", available_contexts);
74 + buffer_json_member_add_uint64(wb, "collected", metadata.contexts.collected);
75 + buffer_json_member_add_uint64(wb, "available", metadata.contexts.available);
76 }
77 buffer_json_object_close(wb);
78
src/database/rrd-metadata.c new
+50
@@ -0,0 +1,50 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#define RRDHOST_INTERNALS
4 +#include "rrd.h"
5 +#include "rrd-metadata.h"
6 +
7 +// Collect metrics metadata from all hosts
8 +RRDSTATS_METADATA rrdstats_metadata_collect(void) {
9 + RRDSTATS_METADATA metadata = {
10 + .nodes = { .total = 0, .receiving = 0, .sending = 0, .archived = 0 },
11 + .metrics = { .collected = 0, .available = 0 },
12 + .instances = { .collected = 0, .available = 0 },
13 + .contexts = { .collected = 0, .available = 0 }
14 + };
15 +
16 + rrd_rdlock();
17 +
18 + if(!rrdhost_root_index) {
19 + rrd_rdunlock();
20 + return metadata;
21 + }
22 +
23 + RRDHOST *host;
24 + dfe_start_read(rrdhost_root_index, host) {
25 + metadata.nodes.total++;
26 +
27 + metadata.metrics.available += __atomic_load_n(&host->rrdctx.metrics_count, __ATOMIC_RELAXED);
28 + metadata.instances.available += __atomic_load_n(&host->rrdctx.instances_count, __ATOMIC_RELAXED);
29 + metadata.contexts.available += __atomic_load_n(&host->rrdctx.contexts_count, __ATOMIC_RELAXED);
30 +
31 + if(rrdhost_flag_check(host, RRDHOST_FLAG_STREAM_SENDER_CONNECTED))
32 + metadata.nodes.sending++;
33 +
34 + if (rrdhost_is_online(host)) {
35 + metadata.metrics.collected += __atomic_load_n(&host->collected.metrics_count, __ATOMIC_RELAXED);
36 + metadata.instances.collected += __atomic_load_n(&host->collected.instances_count, __ATOMIC_RELAXED);
37 + metadata.contexts.collected += __atomic_load_n(&host->collected.contexts_count, __ATOMIC_RELAXED);
38 +
39 + if(host != localhost)
40 + metadata.nodes.receiving++;
41 + }
42 + else
43 + metadata.nodes.archived++;
44 + }
45 + dfe_done(host);
46 +
47 + rrd_rdunlock();
48 +
49 + return metadata;
50 +}
\ No newline at end of file
src/database/rrd-metadata.h new
+36
@@ -0,0 +1,36 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_RRD_METADATA_H
4 +#define NETDATA_RRD_METADATA_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +
8 +// struct to hold node, metrics, instances and contexts statistics
9 +typedef struct rrdstats_metadata {
10 + struct {
11 + size_t total;
12 + size_t receiving;
13 + size_t sending;
14 + size_t archived;
15 + } nodes;
16 +
17 + struct {
18 + size_t collected;
19 + size_t available;
20 + } metrics;
21 +
22 + struct {
23 + size_t collected;
24 + size_t available;
25 + } instances;
26 +
27 + struct {
28 + size_t collected;
29 + size_t available;
30 + } contexts;
31 +} RRDSTATS_METADATA;
32 +
33 +// Function to collect all metadata statistics
34 +RRDSTATS_METADATA rrdstats_metadata_collect(void);
35 +
36 +#endif // NETDATA_RRD_METADATA_H
\ No newline at end of file
src/database/rrd.h
+1
@@ -8,6 +8,7 @@ extern "C" {
8 #endif
9
10 #include "libnetdata/libnetdata.h"
11 +#include "rrd-metadata.h"
12
13 // --------------------------------------------------------------------------------------------------------------------
14
src/libnetdata/inlined.h
+102 -35
@@ -116,16 +116,23 @@ static inline uint64_t murmur64(uint64_t k) {
116 return k;
117 }
118
119 -static inline unsigned int str2u(const char *s) {
120 - unsigned int n = 0;
119 +ALWAYS_INLINE
120 +static unsigned int str2u(const char *s) {
121 + while(isspace((uint8_t)*s))
122 + s++;
123
124 + unsigned int n = 0;
125 while(*s >= '0' && *s <= '9')
126 n = n * 10 + (*s++ - '0');
127
128 return n;
129 }
130
128 -static inline int str2i(const char *s) {
131 +ALWAYS_INLINE
132 +static int str2i(const char *s) {
133 + while(isspace((uint8_t)*s))
134 + s++;
135 +
136 if(unlikely(*s == '-')) {
137 s++;
138 return -(int) str2u(s);
@@ -136,16 +143,23 @@ static inline int str2i(const char *s) {
143 }
144 }
145
139 -static inline unsigned long str2ul(const char *s) {
140 - unsigned long n = 0;
146 +ALWAYS_INLINE
147 +static unsigned long str2ul(const char *s) {
148 + while(isspace((uint8_t)*s))
149 + s++;
150
151 + unsigned long n = 0;
152 while(*s >= '0' && *s <= '9')
153 n = n * 10 + (*s++ - '0');
154
155 return n;
156 }
157
148 -static inline long str2l(const char *s) {
158 +ALWAYS_INLINE
159 +static long str2l(const char *s) {
160 + while(isspace((uint8_t)*s))
161 + s++;
162 +
163 if(unlikely(*s == '-')) {
164 s++;
165 return -(long) str2ul(s);
@@ -156,7 +170,11 @@ static inline long str2l(const char *s) {
170 }
171 }
172
159 -static inline uint32_t str2uint32_t(const char *s, char **endptr) {
173 +ALWAYS_INLINE
174 +static uint32_t str2uint32_t(const char *s, char **endptr) {
175 + while(isspace((uint8_t)*s))
176 + s++;
177 +
178 uint32_t n = 0;
179
180 while(*s >= '0' && *s <= '9')
@@ -168,7 +186,11 @@ static inline uint32_t str2uint32_t(const char *s, char **endptr) {
186 return n;
187 }
188
171 -static inline uint64_t str2uint64_t(const char *s, char **endptr) {
189 +ALWAYS_INLINE
190 +static uint64_t str2uint64_t(const char *s, char **endptr) {
191 + while(isspace((uint8_t)*s))
192 + s++;
193 +
194 uint64_t n = 0;
195
196 #ifdef ENV32BIT
@@ -188,11 +210,16 @@ static inline uint64_t str2uint64_t(const char *s, char **endptr) {
210 return n;
211 }
212
191 -static inline unsigned long long int str2ull(const char *s, char **endptr) {
213 +ALWAYS_INLINE
214 +static unsigned long long int str2ull(const char *s, char **endptr) {
215 return str2uint64_t(s, endptr);
216 }
217
195 -static inline long long str2ll(const char *s, char **endptr) {
218 +ALWAYS_INLINE
219 +static long long str2ll(const char *s, char **endptr) {
220 + while(isspace((uint8_t)*s))
221 + s++;
222 +
223 if(unlikely(*s == '-')) {
224 s++;
225 return -(long long) str2uint64_t(s, endptr);
@@ -203,7 +230,11 @@ static inline long long str2ll(const char *s, char **endptr) {
230 }
231 }
232
206 -static inline uint32_t str2uint32_hex(const char *src, char **endptr) {
233 +ALWAYS_INLINE
234 +static uint32_t str2uint32_hex(const char *src, char **endptr) {
235 + while(isspace((uint8_t)*src))
236 + src++;
237 +
238 uint32_t num = 0;
239 const unsigned char *s = (const unsigned char *)src;
240 unsigned char c;
@@ -219,7 +250,11 @@ static inline uint32_t str2uint32_hex(const char *src, char **endptr) {
250 return num;
251 }
252
222 -static inline uint64_t str2uint64_hex(const char *src, char **endptr) {
253 +ALWAYS_INLINE
254 +static uint64_t str2uint64_hex(const char *src, char **endptr) {
255 + while(isspace((uint8_t)*src))
256 + src++;
257 +
258 uint64_t num = 0;
259 const unsigned char *s = (const unsigned char *)src;
260 unsigned char c;
@@ -235,7 +270,11 @@ static inline uint64_t str2uint64_hex(const char *src, char **endptr) {
270 return num;
271 }
272
238 -static inline uint64_t str2uint64_base64(const char *src, char **endptr) {
273 +ALWAYS_INLINE
274 +static uint64_t str2uint64_base64(const char *src, char **endptr) {
275 + while(isspace((uint8_t)*src))
276 + src++;
277 +
278 uint64_t num = 0;
279 const unsigned char *s = (const unsigned char *)src;
280 unsigned char c;
@@ -251,7 +290,11 @@ static inline uint64_t str2uint64_base64(const char *src, char **endptr) {
290 return num;
291 }
292
254 -static inline NETDATA_DOUBLE str2ndd_parse_double_decimal_digits_internal(const char *src, int *digits) {
293 +ALWAYS_INLINE
294 +static NETDATA_DOUBLE str2ndd_parse_double_decimal_digits_internal(const char *src, int *digits) {
295 + while(isspace((uint8_t)*src))
296 + src++;
297 +
298 const char *s = src;
299 NETDATA_DOUBLE n = 0.0;
300
@@ -272,7 +315,11 @@ static inline NETDATA_DOUBLE str2ndd_parse_double_decimal_digits_internal(const
315 return n;
316 }
317
275 -static inline NETDATA_DOUBLE str2ndd(const char *src, char **endptr) {
318 +ALWAYS_INLINE
319 +static NETDATA_DOUBLE str2ndd(const char *src, char **endptr) {
320 + while(isspace((uint8_t)*src))
321 + src++;
322 +
323 const char *s = src;
324
325 NETDATA_DOUBLE sign = 1.0;
@@ -361,7 +408,8 @@ static inline NETDATA_DOUBLE str2ndd(const char *src, char **endptr) {
408 return sign * result;
409 }
410
364 -static ALWAYS_INLINE unsigned long long str2ull_encoded(const char *s) {
411 +ALWAYS_INLINE
412 +static unsigned long long str2ull_encoded(const char *s) {
413 if(*s == IEEE754_UINT64_B64_PREFIX[0])
414 return str2uint64_base64(s + sizeof(IEEE754_UINT64_B64_PREFIX) - 1, NULL);
415
@@ -371,14 +419,16 @@ static ALWAYS_INLINE unsigned long long str2ull_encoded(const char *s) {
419 return str2uint64_t(s, NULL);
420 }
421
374 -static ALWAYS_INLINE long long str2ll_encoded(const char *s) {
422 +ALWAYS_INLINE
423 +static long long str2ll_encoded(const char *s) {
424 if(*s == '-')
425 return -(long long) str2ull_encoded(&s[1]);
426 else
427 return (long long) str2ull_encoded(s);
428 }
429
381 -static ALWAYS_INLINE NETDATA_DOUBLE str2ndd_encoded(const char *src, char **endptr) {
430 +ALWAYS_INLINE
431 +static NETDATA_DOUBLE str2ndd_encoded(const char *src, char **endptr) {
432 if (*src == IEEE754_DOUBLE_B64_PREFIX[0]) {
433 // double parsing from base64
434 uint64_t n = str2uint64_base64(src + sizeof(IEEE754_DOUBLE_B64_PREFIX) - 1, endptr);
@@ -409,7 +459,8 @@ static ALWAYS_INLINE NETDATA_DOUBLE str2ndd_encoded(const char *src, char **endp
459 return str2ndd(src, endptr) * sign;
460 }
461
412 -static inline char *strncpyz(char *dst, const char *src, size_t dst_size_minus_1) {
462 +ALWAYS_INLINE
463 +static char *strncpyz(char *dst, const char *src, size_t dst_size_minus_1) {
464 char *p = dst;
465
466 while (*src && dst_size_minus_1--)
@@ -422,7 +473,8 @@ static inline char *strncpyz(char *dst, const char *src, size_t dst_size_minus_1
473
474 // append src to dst, but only if there is space for it
475 // dst is always null terminated
425 -static inline size_t strcatz(char *dst, size_t len, const char *src, size_t size) {
476 +ALWAYS_INLINE
477 +static size_t strcatz(char *dst, size_t len, const char *src, size_t size) {
478 // If starting offset is out of bounds, do nothing.
479 if (unlikely(len >= size)) {
480 if(size > 0)
@@ -457,7 +509,8 @@ static inline size_t strcatz(char *dst, size_t len, const char *src, size_t size
509 return len + (initial_space - space);
510 }
511
460 -static inline void sanitize_json_string(char *dst, const char *src, size_t dst_size) {
512 +ALWAYS_INLINE
513 +static void sanitize_json_string(char *dst, const char *src, size_t dst_size) {
514 while (*src != '\0' && dst_size > 1) {
515 if (*src < 0x1F) {
516 *dst++ = '_';
@@ -477,7 +530,8 @@ static inline void sanitize_json_string(char *dst, const char *src, size_t dst_s
530 *dst = '\0';
531 }
532
480 -static inline bool sanitize_command_argument_string(char *dst, const char *src, size_t dst_size) {
533 +ALWAYS_INLINE
534 +static bool sanitize_command_argument_string(char *dst, const char *src, size_t dst_size) {
535 if(dst_size)
536 *dst = '\0';
537
@@ -521,7 +575,8 @@ static inline bool sanitize_command_argument_string(char *dst, const char *src,
575 return true;
576 }
577
524 -static inline int read_txt_file(const char *filename, char *buffer, size_t size) {
578 +ALWAYS_INLINE
579 +static int read_txt_file(const char *filename, char *buffer, size_t size) {
580 if(unlikely(!size)) return 3;
581
582 int fd = open(filename, O_RDONLY | O_CLOEXEC, 0666);
@@ -542,7 +597,8 @@ static inline int read_txt_file(const char *filename, char *buffer, size_t size)
597 return 0;
598 }
599
545 -static inline bool read_txt_file_to_buffer(const char *filename, BUFFER *wb, size_t max_size) {
600 +ALWAYS_INLINE
601 +static bool read_txt_file_to_buffer(const char *filename, BUFFER *wb, size_t max_size) {
602 // Open the file
603 int fd = open(filename, O_RDONLY | O_CLOEXEC);
604 if (fd == -1)
@@ -579,7 +635,8 @@ static inline bool read_txt_file_to_buffer(const char *filename, BUFFER *wb, siz
635 return true; // Success
636 }
637
582 -static inline int read_proc_cmdline(const char *filename, char *buffer, size_t size) {
638 +ALWAYS_INLINE
639 +static int read_proc_cmdline(const char *filename, char *buffer, size_t size) {
640 if (unlikely(!size)) return 3;
641
642 int fd = open(filename, O_RDONLY | O_CLOEXEC, 0666);
@@ -612,7 +669,8 @@ static inline int read_proc_cmdline(const char *filename, char *buffer, size_t s
669 return 0;
670 }
671
615 -static inline int read_single_number_file(const char *filename, unsigned long long *result) {
672 +ALWAYS_INLINE
673 +static int read_single_number_file(const char *filename, unsigned long long *result) {
674 char buffer[30 + 1];
675
676 int ret = read_txt_file(filename, buffer, sizeof(buffer));
@@ -626,7 +684,8 @@ static inline int read_single_number_file(const char *filename, unsigned long lo
684 return 0;
685 }
686
629 -static inline int read_single_signed_number_file(const char *filename, long long *result) {
687 +ALWAYS_INLINE
688 +static int read_single_signed_number_file(const char *filename, long long *result) {
689 char buffer[30 + 1];
690
691 int ret = read_txt_file(filename, buffer, sizeof(buffer));
@@ -640,7 +699,8 @@ static inline int read_single_signed_number_file(const char *filename, long long
699 return 0;
700 }
701
643 -static inline int read_single_base64_or_hex_number_file(const char *filename, unsigned long long *result) {
702 +ALWAYS_INLINE
703 +static int read_single_base64_or_hex_number_file(const char *filename, unsigned long long *result) {
704 char buffer[30 + 1];
705
706 int ret = read_txt_file(filename, buffer, sizeof(buffer));
@@ -661,14 +721,16 @@ static inline int read_single_base64_or_hex_number_file(const char *filename, un
721 }
722 }
723
664 -static inline char *strsep_skip_consecutive_separators(char **ptr, char *s) {
724 +ALWAYS_INLINE
725 +static char *strsep_skip_consecutive_separators(char **ptr, char *s) {
726 char *p = (char *)"";
727 while (p && !p[0] && *ptr) p = strsep(ptr, s);
728 return (p);
729 }
730
731 // remove leading and trailing spaces; may return NULL
671 -static inline char *trim(char *s) {
732 +ALWAYS_INLINE
733 +static char *trim(char *s) {
734 char *buf = s;
735
736 // skip leading spaces
@@ -696,7 +758,8 @@ static inline char *trim(char *s) {
758 }
759
760 // like trim(), but also remove duplicate spaces inside the string
699 -static inline char *trim_all(char *buffer) {
761 +ALWAYS_INLINE
762 +static char *trim_all(char *buffer) {
763 char *d = buffer, *s = buffer;
764
765 // skip spaces
@@ -728,7 +791,8 @@ static inline char *trim_all(char *buffer) {
791 return buffer;
792 }
793
731 -static inline bool streq(const char *a, const char *b) {
794 +ALWAYS_INLINE
795 +static bool streq(const char *a, const char *b) {
796 if (a == b)
797 return true;
798
@@ -738,7 +802,8 @@ static inline bool streq(const char *a, const char *b) {
802 return strcmp(a, b) == 0;
803 }
804
741 -static inline bool strstartswith(const char *string, const char *prefix) {
805 +ALWAYS_INLINE
806 +static bool strstartswith(const char *string, const char *prefix) {
807 if (string == NULL || prefix == NULL)
808 return false;
809
@@ -751,7 +816,8 @@ static inline bool strstartswith(const char *string, const char *prefix) {
816 return strncmp(string, prefix, prefix_len) == 0;
817 }
818
754 -static inline bool strendswith(const char *string, const char *suffix) {
819 +ALWAYS_INLINE
820 +static bool strendswith(const char *string, const char *suffix) {
821 if (string == NULL || suffix == NULL)
822 return false;
823
@@ -764,7 +830,8 @@ static inline bool strendswith(const char *string, const char *suffix) {
830 return strcmp(string + string_len - suffix_len, suffix) == 0;
831 }
832
767 -static inline bool strendswith_lengths(const char *string, size_t string_len, const char *suffix, size_t suffix_len) {
833 +ALWAYS_INLINE
834 +static bool strendswith_lengths(const char *string, size_t string_len, const char *suffix, size_t suffix_len) {
835 if (string == NULL || suffix == NULL)
836 return false;
837
src/libnetdata/memory/nd-mallocz.c
+2 -14
@@ -7,19 +7,6 @@ void mallocz_register_out_of_memory_cb(out_of_memory_cb cb) {
7 out_of_memory_callback = cb;
8 }
9
10 -uint64_t process_max_rss(void) {
11 -#if defined(OS_LINUX) || defined(OS_WINDOWS)
12 - int rss_multiplier = 1024;
13 -#else
14 - int rss_multiplier = 1;
15 -#endif
16 -
17 - struct rusage usage = { 0 };
18 - if(getrusage(RUSAGE_SELF, &usage) != 0)
19 - return 0;
20 -
21 - return usage.ru_maxrss * rss_multiplier;
22 -}
10
11 ALWAYS_INLINE NORETURN
12 void out_of_memory(const char *call, size_t size, const char *details) {
@@ -29,7 +16,8 @@ void out_of_memory(const char *call, size_t size, const char *details) {
16 if(out_of_memory_callback)
17 out_of_memory_callback();
18
32 - uint64_t max_rss = process_max_rss();
19 + OS_PROCESS_MEMORY proc_mem = os_process_memory(0);
20 + uint64_t max_rss = OS_PROCESS_MEMORY_OK(proc_mem) ? proc_mem.max_rss : 0;
21
22 char mem_available[64];
23 char rss_used[64];
src/libnetdata/memory/nd-mallocz.h
-1
@@ -69,6 +69,5 @@ void mallocz_register_out_of_memory_cb(out_of_memory_cb cb);
69
70 NORETURN
71 void out_of_memory(const char *call, size_t size, const char *details);
72 -uint64_t process_max_rss(void);
72
73 #endif //NETDATA_ND_MALLOCZ_H
src/libnetdata/os/dir_size.c new
+202
@@ -0,0 +1,202 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "libnetdata/libnetdata.h"
4 +#include "dir_size.h"
5 +
6 +#include <dirent.h>
7 +#include <sys/stat.h>
8 +#include <unistd.h>
9 +#include <string.h>
10 +#include <errno.h>
11 +
12 +// Hash table to keep track of visited inodes to avoid cycles
13 +typedef struct {
14 + ino_t inode; // Inode number
15 + dev_t device; // Device ID
16 +} INODE_DEVICE_PAIR;
17 +
18 +// Internal function to recursively calculate directory size
19 +static void calc_dir_size_recursive(const char *base_path, const char *rel_path,
20 + SIMPLE_PATTERN *pattern, size_t max_depth, size_t current_depth,
21 + DIR_SIZE *result, DICTIONARY *visited_inodes) {
22 +
23 + char path[FILENAME_MAX + 1];
24 + struct stat statbuf;
25 + struct dirent *entry;
26 + DIR *dir;
27 +
28 + // Check max depth
29 + if (max_depth > 0 && current_depth > max_depth)
30 + return;
31 +
32 + // Update max depth found
33 + if (current_depth > result->depth)
34 + result->depth = current_depth;
35 +
36 + // Construct full path (avoid double slashes)
37 + if (rel_path && *rel_path) {
38 + if (base_path[strlen(base_path) - 1] == '/')
39 + snprintfz(path, FILENAME_MAX, "%s%s", base_path, rel_path);
40 + else
41 + snprintfz(path, FILENAME_MAX, "%s/%s", base_path, rel_path);
42 + } else {
43 + snprintfz(path, FILENAME_MAX, "%s", base_path);
44 + }
45 +
46 + // Get file/directory stats
47 + if (lstat(path, &statbuf) != 0) {
48 + result->errors++;
49 + return;
50 + }
51 +
52 + // Create inode-device pair to detect loops
53 + INODE_DEVICE_PAIR id_pair = {
54 + .inode = statbuf.st_ino,
55 + .device = statbuf.st_dev
56 + };
57 +
58 + // Use string representation as the dictionary name
59 + char name[sizeof(INODE_DEVICE_PAIR) * 2 + 1];
60 + snprintfz(name, sizeof(name), "%lu_%lu", (unsigned long)id_pair.inode, (unsigned long)id_pair.device);
61 +
62 + // Check if we've seen this inode-device pair before (for symlink loop detection)
63 + if (dictionary_get(visited_inodes, name))
64 + return;
65 +
66 + // Add to visited inodes
67 + dictionary_set(visited_inodes, name, NULL, sizeof(void *));
68 +
69 + // Handle different file types
70 + if (S_ISDIR(statbuf.st_mode)) {
71 + result->directories++;
72 +
73 + // Open directory
74 + dir = opendir(path);
75 + if (!dir) {
76 + result->errors++;
77 + return;
78 + }
79 +
80 + // Iterate through directory entries
81 + while ((entry = readdir(dir)) != NULL) {
82 + // Skip "." and ".."
83 + if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
84 + continue;
85 +
86 + // Build relative path (this is the path relative to base_path)
87 + char next_rel_path[FILENAME_MAX + 1];
88 + if (rel_path[0] == '\0')
89 + snprintfz(next_rel_path, FILENAME_MAX, "%s", entry->d_name);
90 + else
91 + snprintfz(next_rel_path, FILENAME_MAX, "%s/%s", rel_path, entry->d_name);
92 +
93 + // Build full path to check file type (avoid double slashes)
94 + char full_path[FILENAME_MAX + 1];
95 + if (path[strlen(path) - 1] == '/')
96 + snprintfz(full_path, FILENAME_MAX, "%s%s", path, entry->d_name);
97 + else
98 + snprintfz(full_path, FILENAME_MAX, "%s/%s", path, entry->d_name);
99 +
100 + struct stat entry_stat;
101 + if (lstat(full_path, &entry_stat) != 0) {
102 + result->errors++;
103 + continue;
104 + }
105 +
106 + if (S_ISDIR(entry_stat.st_mode)) {
107 + // Always recurse on directories regardless of pattern
108 + calc_dir_size_recursive(base_path, next_rel_path, pattern, max_depth,
109 + current_depth + 1, result, visited_inodes);
110 + }
111 + else if (S_ISREG(entry_stat.st_mode)) {
112 + // For files, apply pattern filtering if specified
113 + if (pattern && !simple_pattern_matches(pattern, next_rel_path))
114 + continue;
115 +
116 + // Count the file
117 + result->files++;
118 + result->bytes += entry_stat.st_size;
119 + }
120 + // Other file types (symlinks, etc.) are not counted
121 + }
122 +
123 + closedir(dir);
124 + }
125 + else if (S_ISREG(statbuf.st_mode)) {
126 + // For individual files (when dir_size is called directly on a file)
127 + // Apply pattern filtering if specified
128 + if (pattern && !simple_pattern_matches(pattern, rel_path))
129 + return;
130 +
131 + // Count the file
132 + result->files++;
133 + result->bytes += statbuf.st_size;
134 + }
135 + // Other file types (symlinks, etc.) are not counted in size calculation
136 +}
137 +
138 +DIR_SIZE dir_size(const char *path, SIMPLE_PATTERN *pattern, size_t max_depth) {
139 + DIR_SIZE result = DIR_SIZE_EMPTY;
140 +
141 + if (!path || !*path)
142 + return result;
143 +
144 + // Create dictionary to track visited inodes
145 + DICTIONARY *visited_inodes = dictionary_create(DICT_OPTION_SINGLE_THREADED);
146 +
147 + // Check if path exists and get initial stats
148 + struct stat statbuf;
149 +
150 + if (stat(path, &statbuf) != 0) {
151 + result.errors++;
152 + dictionary_destroy(visited_inodes);
153 + return result;
154 + }
155 +
156 + if (S_ISDIR(statbuf.st_mode)) {
157 + // Start recursion from the base path for directories
158 + calc_dir_size_recursive(path, "", pattern, max_depth, 0, &result, visited_inodes);
159 + } else if (S_ISREG(statbuf.st_mode)) {
160 + // Single file case
161 + // Extract the filename for pattern matching
162 + const char *filename = strrchr(path, '/');
163 + filename = filename ? filename + 1 : path;
164 +
165 + // Apply pattern filtering if specified
166 + if (pattern && !simple_pattern_matches(pattern, filename))
167 + return result;
168 +
169 + result.files = 1;
170 + result.bytes = statbuf.st_size;
171 + }
172 +
173 + dictionary_destroy(visited_inodes);
174 + return result;
175 +}
176 +
177 +DIR_SIZE dir_size_multiple(const char **paths, int num_paths, SIMPLE_PATTERN *pattern, size_t max_depth) {
178 + DIR_SIZE result = DIR_SIZE_EMPTY;
179 +
180 + if (!paths || num_paths <= 0)
181 + return result;
182 +
183 + // Calculate size for each path and combine results
184 + for (int i = 0; i < num_paths; i++) {
185 + if (!paths[i] || !*paths[i])
186 + continue;
187 +
188 + DIR_SIZE path_result = dir_size(paths[i], pattern, max_depth);
189 +
190 + // Combine results
191 + result.bytes += path_result.bytes;
192 + result.files += path_result.files;
193 + result.directories += path_result.directories;
194 + result.errors += path_result.errors;
195 +
196 + // Take the maximum depth found
197 + if (path_result.depth > result.depth)
198 + result.depth = path_result.depth;
199 + }
200 +
201 + return result;
202 +}
\ No newline at end of file
src/libnetdata/os/dir_size.h new
+47
@@ -0,0 +1,47 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_DIR_SIZE_H
4 +#define NETDATA_DIR_SIZE_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +#include "libnetdata/simple_pattern/simple_pattern.h"
8 +
9 +typedef struct {
10 + uint64_t bytes; // Total size in bytes
11 + size_t files; // Number of files
12 + size_t directories; // Number of directories (including root directory)
13 + size_t depth; // Maximum depth found
14 + size_t errors; // Number of errors encountered during calculation
15 +} DIR_SIZE;
16 +
17 +#define DIR_SIZE_EMPTY (DIR_SIZE){ 0 }
18 +#define DIR_SIZE_OK(ds) ((ds).bytes > 0 && (ds).errors == 0)
19 +
20 +/**
21 + * Calculate the total size of a directory and its contents
22 + *
23 + * @param path Path to the directory
24 + * @param pattern Simple pattern to filter files (NULL to include all files)
25 + * @param max_depth Maximum recursion depth (0 for unlimited)
26 + * @return DIR_SIZE structure with the size information
27 + *
28 + * This function recursively traverses the directory structure starting from
29 + * the given path and calculates the total size in bytes, counting files and
30 + * directories. It safely handles symbolic links to avoid infinite loops.
31 + */
32 +DIR_SIZE dir_size(const char *path, SIMPLE_PATTERN *pattern, size_t max_depth);
33 +
34 +/**
35 + * Calculate multiple directory sizes in one pass
36 + *
37 + * @param paths Array of directory paths to calculate
38 + * @param num_paths Number of paths in the array
39 + * @param pattern Simple pattern to filter files (NULL to include all files)
40 + * @param max_depth Maximum recursion depth (0 for unlimited)
41 + * @return DIR_SIZE structure with the combined size information
42 + *
43 + * This function calculates sizes for multiple directories and returns their combined total.
44 + */
45 +DIR_SIZE dir_size_multiple(const char **paths, int num_paths, SIMPLE_PATTERN *pattern, size_t max_depth);
46 +
47 +#endif //NETDATA_DIR_SIZE_H
\ No newline at end of file
src/libnetdata/os/machine_id.c
+10
@@ -69,7 +69,11 @@ static ND_UUID get_machine_id(void) {
69 ND_UUID machine_id = { 0 };
70
71 // First try to get the platform UUID
72 +#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
73 + io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMainPortDefault, "IOService:/");
74 +#else
75 io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
76 +#endif
77 if (ioRegistryRoot) {
78 CFStringRef uuidCf = (CFStringRef) IORegistryEntryCreateCFProperty(
79 ioRegistryRoot,
@@ -92,9 +96,15 @@ static ND_UUID get_machine_id(void) {
96 }
97
98 // Fallback to IOPlatformExpertDevice's IOPlatformSerialNumber
99 +#if defined(MAC_OS_VERSION_12_0) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
100 + io_service_t platformExpert = IOServiceGetMatchingService(
101 + kIOMainPortDefault,
102 + IOServiceMatching("IOPlatformExpertDevice"));
103 +#else
104 io_service_t platformExpert = IOServiceGetMatchingService(
105 kIOMasterPortDefault,
106 IOServiceMatching("IOPlatformExpertDevice"));
107 +#endif
108
109 if (platformExpert) {
110 CFStringRef serialNumberCf = (CFStringRef) IORegistryEntryCreateCFProperty(
src/libnetdata/os/os.h
+2
@@ -41,6 +41,8 @@
41 #include "file_lock.h"
42 #include "mmap_limit.h"
43 #include "machine_id.h"
44 +#include "process_memory.h"
45 +#include "dir_size.h"
46
47 // this includes windows.h to the whole of netdata
48 // so various conflicts arise
src/libnetdata/os/process_memory.c new
+305
@@ -0,0 +1,305 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#include "process_memory.h"
4 +#include "libnetdata/libnetdata.h"
5 +
6 +static OS_PROCESS_MEMORY last_process_memory_info = OS_PROCESS_MEMORY_EMPTY;
7 +
8 +#if defined(OS_LINUX)
9 +/**
10 + * Get process memory information for Linux
11 + *
12 + * Uses /proc/<pid>/statm and /proc/<pid>/status to collect memory information.
13 + */
14 +OS_PROCESS_MEMORY os_process_memory(pid_t pid) {
15 + OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY;
16 + char filename[FILENAME_MAX + 1];
17 + char buffer[4096 + 1];
18 + long page_size = sysconf(_SC_PAGESIZE);
19 +
20 + if (pid == 0)
21 + pid = getpid();
22 +
23 + // Get statm information (for RSS, total size, shared, text, data)
24 + size_t len = 0;
25 + len = strcatz(filename, len, "/proc/", sizeof(filename));
26 + len += print_uint64(&filename[len], pid);
27 + len = strcatz(filename, len, "/statm", sizeof(filename));
28 + if (read_txt_file(filename, buffer, sizeof(buffer)) == 0) {
29 + unsigned long size = 0, resident = 0, shared = 0, text = 0, lib = 0, data = 0;
30 + char *pos = buffer;
31 + size = str2ull(pos, &pos);
32 + resident = str2ull(pos, &pos);
33 + shared = str2ull(pos, &pos);
34 + text = str2ull(pos, &pos);
35 + lib = str2ull(pos, &pos);
36 + data = str2ull(pos, &pos);
37 + (void)lib;
38 + (void)data;
39 +
40 + proc_mem.virtual_size = size * page_size;
41 + proc_mem.rss = resident * page_size;
42 + proc_mem.shared = shared * page_size;
43 + proc_mem.text = text * page_size;
44 + proc_mem.data = data * page_size;
45 + }
46 +
47 + // Get status information (for peak resident set size)
48 + len = 0;
49 + len = strcatz(filename, len, "/proc/", sizeof(filename));
50 + len += print_uint64(&filename[len], pid);
51 + len = strcatz(filename, len, "/status", sizeof(filename));
52 + if (read_txt_file(filename, buffer, sizeof(buffer)) == 0) {
53 + char *s = strstr(buffer, "VmHWM:");
54 + if (s) {
55 + s += 6; // Skip "VmHWM:"
56 + while (isspace((uint8_t)*s)) s++; // Skip spaces
57 + proc_mem.max_rss = str2ull(s, NULL) * 1024; // VmHWM is in kB, convert to bytes
58 + }
59 + }
60 +
61 + if (OS_PROCESS_MEMORY_OK(proc_mem))
62 + last_process_memory_info = proc_mem;
63 +
64 + return proc_mem;
65 +}
66 +#endif
67 +
68 +#if defined(OS_FREEBSD)
69 +#include <sys/param.h>
70 +#include <sys/user.h>
71 +#include <sys/sysctl.h>
72 +
73 +/**
74 + * Get process memory information for FreeBSD
75 + *
76 + * Uses the sysctl API to collect memory information.
77 + */
78 +OS_PROCESS_MEMORY os_process_memory(pid_t pid) {
79 + OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY;
80 + int mib[4];
81 + struct kinfo_proc proc;
82 + size_t len = sizeof(proc);
83 +
84 + if (pid == 0)
85 + pid = getpid();
86 +
87 + // Use direct sysctl call instead of higher-level functions
88 + mib[0] = CTL_KERN;
89 + mib[1] = KERN_PROC;
90 + mib[2] = KERN_PROC_PID;
91 + mib[3] = pid;
92 +
93 + if (sysctl(mib, 4, &proc, &len, NULL, 0) == 0) {
94 + int page_size = getpagesize();
95 +
96 + proc_mem.rss = proc.ki_rssize * page_size;
97 + proc_mem.virtual_size = proc.ki_size;
98 +
99 + // Get maximum RSS without using getrusage
100 + unsigned long maxrss = 0;
101 + size_t maxrss_len = sizeof(maxrss);
102 + char maxrss_name[128];
103 + size_t maxrss_name_len = 0;
104 + maxrss_name[0] = '\0';
105 + maxrss_name_len = strcatz(maxrss_name, maxrss_name_len, "kern.proc.", sizeof(maxrss_name));
106 + maxrss_name_len += print_uint64(&maxrss_name[maxrss_name_len], KERN_PROC_PID);
107 + maxrss_name_len = strcatz(maxrss_name, maxrss_name_len, ".rusage.", sizeof(maxrss_name));
108 + maxrss_name_len += print_uint64(&maxrss_name[maxrss_name_len], pid);
109 + maxrss_name_len = strcatz(maxrss_name, maxrss_name_len, ".maxrss", sizeof(maxrss_name));
110 + if (sysctlbyname(maxrss_name, &maxrss, &maxrss_len, NULL, 0) == 0)
111 + proc_mem.max_rss = maxrss * 1024; // maxrss is in KB
112 + else
113 + proc_mem.max_rss = proc_mem.rss; // Fall back to current RSS if peak not available
114 +
115 + // Get memory map information for shared memory approximation
116 + // This approach still uses syscall directly, which is already low-level
117 + size_t shared_pages_len = 0;
118 + int mib_shared[] = {CTL_KERN, KERN_PROC, KERN_PROC_VMMAP, pid};
119 +
120 + // First call to get size
121 + if (sysctl(mib_shared, 4, NULL, &shared_pages_len, NULL, 0) == 0 && shared_pages_len > 0) {
122 + // Allocate memory for the vmmap data
123 + void *shared_info = mallocz(shared_pages_len);
124 + if (sysctl(mib_shared, 4, shared_info, &shared_pages_len, NULL, 0) == 0) {
125 + // For now, use a simple approximation
126 + // A more accurate calculation would involve parsing the vm_map_entries
127 + proc_mem.shared = proc_mem.rss / 4; // rough estimate
128 + }
129 + freez(shared_info);
130 + }
131 + }
132 +
133 + if (OS_PROCESS_MEMORY_OK(proc_mem))
134 + last_process_memory_info = proc_mem;
135 +
136 + return proc_mem;
137 +}
138 +#endif
139 +
140 +#if defined(OS_MACOS)
141 +#include <mach/mach.h>
142 +#include <mach/task.h>
143 +#include <mach/mach_init.h>
144 +
145 +/**
146 + * Get process memory information for macOS
147 + *
148 + * Uses the Mach task API to collect memory information.
149 + * Mach API is already a low-level API for accessing process information on macOS.
150 + */
151 +OS_PROCESS_MEMORY os_process_memory(pid_t pid) {
152 + OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY;
153 + task_t task;
154 + kern_return_t kr;
155 +
156 + if (pid == 0)
157 + pid = getpid();
158 +
159 + // Get the Mach task for the process - this is a low-level system call
160 + kr = task_for_pid(mach_task_self(), pid, &task);
161 + if (kr != KERN_SUCCESS) {
162 + if (pid == getpid()) {
163 + // Always works for current process
164 + task = mach_task_self();
165 + } else {
166 + // Can't get info for other processes without privileges
167 + return proc_mem;
168 + }
169 + }
170 +
171 + // Get basic task info (RSS and virtual size) - direct Mach API call
172 + struct task_basic_info_64 task_basic_info;
173 + mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
174 + kr = task_info(task, TASK_BASIC_INFO_64, (task_info_t)&task_basic_info, &count);
175 +
176 + if (kr == KERN_SUCCESS) {
177 + proc_mem.rss = task_basic_info.resident_size;
178 + proc_mem.virtual_size = task_basic_info.virtual_size;
179 +
180 + // Get page-in information to estimate max RSS - direct Mach API call
181 + // This avoids using getrusage().ru_maxrss
182 + task_events_info_data_t events_info;
183 + mach_msg_type_number_t events_info_count = TASK_EVENTS_INFO_COUNT;
184 + kr = task_info(task, TASK_EVENTS_INFO, (task_info_t)&events_info, &events_info_count);
185 +
186 + if (kr == KERN_SUCCESS && events_info.pageins > 0) {
187 + // If we have page-ins, we can use a more accurate calculation
188 + vm_size_t page_size = 0; // Changed from mach_vm_size_t to vm_size_t
189 + host_page_size(mach_host_self(), &page_size);
190 + proc_mem.max_rss = task_basic_info.resident_size + (events_info.pageins * page_size);
191 + } else {
192 + // Otherwise, just use current RSS as a fallback
193 + proc_mem.max_rss = task_basic_info.resident_size;
194 + }
195 +
196 + // On macOS, use a simpler approach without vm_region
197 + // Just estimate shared, text, and data based on total memory
198 + if (task_basic_info.resident_size > 0) {
199 + // Simple heuristic estimates based on typical process memory layout
200 + proc_mem.shared = task_basic_info.resident_size / 5; // ~20% shared libraries
201 + proc_mem.text = task_basic_info.resident_size / 5; // ~20% code
202 + proc_mem.data = task_basic_info.resident_size - proc_mem.shared - proc_mem.text; // remaining is data
203 + }
204 + }
205 +
206 + // Release the task port if we obtained it - low-level resource management
207 + if (task != mach_task_self()) {
208 + mach_port_deallocate(mach_task_self(), task);
209 + }
210 +
211 + if (OS_PROCESS_MEMORY_OK(proc_mem))
212 + last_process_memory_info = proc_mem;
213 +
214 + return proc_mem;
215 +}
216 +#endif
217 +
218 +#if defined(OS_WINDOWS)
219 +#include <windows.h>
220 +#include <psapi.h>
221 +#include <tlhelp32.h>
222 +
223 +/**
224 + * Get process memory information for Windows
225 + *
226 + * Uses Windows APIs to collect memory information.
227 + * Windows API is inherently lower-level compared to standard C library functions.
228 + */
229 +OS_PROCESS_MEMORY os_process_memory(pid_t pid) {
230 + OS_PROCESS_MEMORY proc_mem = OS_PROCESS_MEMORY_EMPTY;
231 + HANDLE hProcess;
232 + DWORD process_id = pid;
233 +
234 + if (process_id == 0)
235 + process_id = GetCurrentProcessId();
236 +
237 + // OpenProcess is a direct Windows API call - low level access to process
238 + hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, process_id);
239 + if (hProcess) {
240 + // GetProcessMemoryInfo is a direct Windows API call to get memory info
241 + PROCESS_MEMORY_COUNTERS_EX pmc;
242 + ZeroMemory(&pmc, sizeof(pmc));
243 + pmc.cb = sizeof(pmc);
244 +
245 + if (GetProcessMemoryInfo(hProcess, (PROCESS_MEMORY_COUNTERS*)&pmc, sizeof(pmc))) {
246 + proc_mem.rss = pmc.WorkingSetSize;
247 + proc_mem.max_rss = pmc.PeakWorkingSetSize;
248 + proc_mem.virtual_size = pmc.PagefileUsage + pmc.WorkingSetSize;
249 +
250 + // Get module information to determine text and shared memory
251 + // CreateToolhelp32Snapshot is a low-level Windows API call
252 + HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE | TH32CS_SNAPMODULE32, process_id);
253 + if (hSnapshot != INVALID_HANDLE_VALUE) {
254 + MODULEENTRY32 me;
255 + ZeroMemory(&me, sizeof(me));
256 + me.dwSize = sizeof(MODULEENTRY32);
257 +
258 + // Module32First/Next are direct API calls to examine loaded modules
259 + if (Module32First(hSnapshot, &me)) {
260 + // The first module is the executable itself
261 + proc_mem.text = me.modBaseSize;
262 +
263 + // Sum all other modules as shared code
264 + while (Module32Next(hSnapshot, &me)) {
265 + proc_mem.shared += me.modBaseSize;
266 + }
267 + }
268 + CloseHandle(hSnapshot);
269 + }
270 +
271 + // Get virtual memory information for more detailed breakdown
272 + MEMORY_BASIC_INFORMATION mbi;
273 + ZeroMemory(&mbi, sizeof(mbi));
274 + SIZE_T address = 0;
275 +
276 + // VirtualQueryEx is a low-level API to get memory region info
277 + while (VirtualQueryEx(hProcess, (LPCVOID)address, &mbi, sizeof(mbi)) == sizeof(mbi)) {
278 + if (mbi.State == MEM_COMMIT) {
279 + if (mbi.Type == MEM_PRIVATE && !(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ | PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))) {
280 + // Private data memory
281 + proc_mem.data += mbi.RegionSize;
282 + }
283 + }
284 +
285 + // Move to the next region
286 + address = (SIZE_T)mbi.BaseAddress + mbi.RegionSize;
287 +
288 + // Avoid potential infinite loop on 64-bit systems
289 + if (address < (SIZE_T)mbi.BaseAddress)
290 + break;
291 + }
292 +
293 + // If data counting failed, fall back to estimation
294 + if (proc_mem.data == 0)
295 + proc_mem.data = pmc.PrivateUsage - proc_mem.text;
296 + }
297 + CloseHandle(hProcess);
298 + }
299 +
300 + if (OS_PROCESS_MEMORY_OK(proc_mem))
301 + last_process_memory_info = proc_mem;
302 +
303 + return proc_mem;
304 +}
305 +#endif
\ No newline at end of file
src/libnetdata/os/process_memory.h new
+43
@@ -0,0 +1,43 @@
1 +// SPDX-License-Identifier: GPL-3.0-or-later
2 +
3 +#ifndef NETDATA_PROCESS_MEMORY_H
4 +#define NETDATA_PROCESS_MEMORY_H
5 +
6 +#include "libnetdata/libnetdata.h"
7 +
8 +/**
9 + * Process memory information
10 + *
11 + * This structure contains memory usage information for a process.
12 + */
13 +typedef struct {
14 + uint64_t rss; // Resident Set Size in bytes
15 + uint64_t virtual_size; // Virtual memory size in bytes
16 + uint64_t shared; // Shared memory in bytes
17 + uint64_t text; // Text (code) size in bytes
18 + uint64_t data; // Data size in bytes
19 + uint64_t max_rss; // Peak resident set size in bytes
20 +} OS_PROCESS_MEMORY;
21 +
22 +/**
23 + * Check if the process memory information is valid
24 + */
25 +#define OS_PROCESS_MEMORY_OK(proc_mem) ((proc_mem).rss > 0)
26 +
27 +/**
28 + * Empty process memory structure
29 + */
30 +#define OS_PROCESS_MEMORY_EMPTY (OS_PROCESS_MEMORY){ 0 }
31 +
32 +/**
33 + * Get process memory information
34 + *
35 + * Returns memory information for the specified process, or the current
36 + * process if pid is 0.
37 + *
38 + * @param pid The process ID or 0 for current process
39 + * @return OS_PROCESS_MEMORY The memory information
40 + */
41 +OS_PROCESS_MEMORY os_process_memory(pid_t pid);
42 +
43 +#endif // NETDATA_PROCESS_MEMORY_H
\ No newline at end of file