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