apps.plugin: add PSS-based memory estimation (#21199)
Co-authored-by: Ilya Mashchenko <ilya@netdata.cloud>
Costa Tsaousis committed
Oct 30, 2025 at 14:02 UTC
c8ef9410dde146b82fbdc638b05831350855d226
16 files changed
+632
-62
docs/functions/processes.md
+14
-2
@@ -70,6 +70,10 @@ The `processes` function answers these questions by showing:
70
| **iCtxSwitch** | Rate | Involuntary context switches per second | ✓ | ✓ | - | Linux only |
71
| **Memory** | Percentage | Memory usage as percentage of total system RAM | ✓ | ✓ | - | All |
72
| **Resident** | MiB | Resident Set Size (physical memory) | ✓ | ✓ | - | All |
73
+| **Estimated** | MiB | Estimated memory using PSS scaling (visible by default when enabled) | ✓ | ✓ | - | Linux 4.14+ (with PSS) |
74
+| **Pss** | MiB | Proportional Set Size (hidden by default) | ✓ | ✓ | - | Linux 4.14+ (with PSS) |
75
+| **PssAge** | Seconds | Time since last smaps sample (hidden by default) | ✓ | ✓ | - | Linux 4.14+ (with PSS) |
76
+| **SharedRatio** | Percentage | Shared memory ratio from PSS (hidden by default) | ✓ | ✓ | - | Linux 4.14+ (with PSS) |
77
| **Shared** | MiB | Shared memory pages | ✓ | ✓ | - | Linux only |
78
| **Virtual** | MiB | Virtual memory size | ✓ | ✓ | - | All |
79
| **Swap** | MiB | Swap memory usage | ✓ | ✓ | - | Linux, Windows |
@@ -102,8 +106,9 @@ The `processes` function answers these questions by showing:
106
### Platform-Specific Field Notes
107
108
- **Linux**: The most comprehensive data with all metrics including physical I/O, detailed file descriptors, child process accumulation, and resource limits
109
+ - **PSS Memory Estimation** (kernel 4.14+): When enabled (default), provides `Estimated`, `Pss`, `PssAge`, and `SharedRatio` fields for more accurate memory accounting in shared-memory workloads. The plugin uses adaptive sampling that prioritizes the largest memory consumers and processes with significant memory changes, refreshing them within seconds of detection. All processes are guaranteed to be refreshed within 2× the configured PSS refresh period (default: 600 seconds). Disable with `--pss 0` to remove these fields and use traditional RSS measurements.
110
- **macOS**: Full process data except physical I/O, children accumulation, and some advanced metrics
106
-- **FreeBSD**: Similar to macOS but includes children CPU accumulation
111
+- **FreeBSD**: Similar to macOS but includes children CPU accumulation
112
- **Windows**: Different approach using handles instead of file descriptors, includes I/O operations but lacks user/group ownership and command line access
113
114
## Drill-Down Workflow
@@ -142,6 +147,8 @@ Filter by `category:[name]` and sort by `CPU` descending to find the exact proce
147
#### Find memory-consuming processes in application groups
148
Filter by specific categories and sort by `Resident` or `Memory` percentage to identify which processes within an application group consume the most RAM. Compare `Virtual` vs `Resident` to understand memory allocation patterns and potential over-provisioning.
149
150
+On Linux 4.14+ with PSS enabled (default), use `Estimated` instead of `Resident` for more accurate memory accounting in shared-memory workloads (databases, cache servers, etc.). The `Estimated` field scales shared memory using PSS ratios to show true proportional memory usage. Check `SharedRatio` to see the scaling factor - values significantly below 100% indicate heavy shared memory usage where `Resident` would overstate consumption. The `PssAge` field shows seconds since the last PSS sample - expect low values (under 10s) for large memory consumers due to adaptive prioritization, while smaller processes may show higher ages (up to 600s by default) as they are refreshed less frequently.
151
+
152
#### Locate I/O-heavy processes causing disk bottlenecks
153
Sort by `PReads + PWrites` for physical I/O or `LReads + LWrites` for logical I/O to find processes generating the most disk activity. Filter by category to drill down from chart-level I/O metrics to specific process-level I/O patterns.
154
@@ -150,7 +157,7 @@ Sort by `PReads + PWrites` for physical I/O or `LReads + LWrites` for logical I/
157
The processes function excels at identifying various types of resource leaks by correlating resource usage with process uptime.
158
159
#### Memory leak detection in long-running processes
153
-Filter processes with `Uptime > 3600` (one hour) and sort by `Resident` memory descending. Look for processes where memory consumption is disproportionately high relative to their uptime. Track specific PIDs over time to observe continuously growing memory usage patterns.
160
+Filter processes with `Uptime > 3600` (one hour) and sort by `Resident` (or `Estimated` on Linux with PSS enabled) memory descending. Look for processes where memory consumption is disproportionately high relative to their uptime. Track specific PIDs over time to observe continuously growing memory usage patterns. On shared-memory workloads, use `Estimated` to avoid false positives from shared pages that aren't actually leaking. Note that PSS samples for large memory consumers are refreshed within seconds, providing near real-time leak detection.
161
162
#### File descriptor leak identification
163
Sort by `FDs` count or filter for `FDsLimitPercent > 50` to find processes approaching their file descriptor limits. Examine the breakdown of descriptor types (`Files`, `Sockets`, `Pipes`, etc.) to understand what type of resources are leaking. Correlate high FD counts with process uptime to identify gradual leaks.
@@ -193,6 +200,11 @@ Use full-text search in `CmdLine` to find processes launched with specific param
200
## Special Features
201
202
- **Child Process Accumulation**: Uniquely captures resources from exited children - critical for accurate measurement of shell scripts and applications that spawn many short-lived processes (even 100+ commands/second)
203
+- **PSS Memory Estimation** (Linux 4.14+): Provides accurate memory accounting for shared-memory workloads by using Proportional Set Size (PSS) to scale shared pages. Enabled by default with adaptive sampling to minimize overhead while ensuring rapid response to memory changes. The plugin alternates between two prioritization strategies each iteration:
204
+ - **Delta-based strategy**: Prioritizes processes with the largest memory changes, ensuring rapid detection and response to memory growth (typically within seconds)
205
+ - **Age-based strategy**: Prioritizes processes that haven't been updated longest, ensuring eventual consistency for all processes
206
+
207
+ Both strategies sort candidates by priority and refresh the top N processes within the configured budget. This approach ensures that the biggest memory consumers (databases, cache servers, etc.) are refreshed within seconds of significant changes, while guaranteeing that even the smallest processes are refreshed within 2× the configured PSS refresh period (default: 600 seconds). Shows true memory consumption vs inflated RSS values for shared-memory workloads.
208
- **Category Correlation**: The `Category` field directly matches the instance names in `apps.plugin` charts, enabling drill-down from chart to process level
209
- **Intelligent Grouping**: Understands spawn managers (systemd, containerd, init) and groups by top-most parent to create manageable categories
210
- **Normalized Metrics**: All per-process usage is normalized to accurately match total system resource usage
src/collectors/apps.plugin/README.md
+60
@@ -18,6 +18,24 @@
18
This is particularly valuable for scenarios where processes spawn numerous short-lived subprocesses, such as shell scripts that fork hundreds or thousands of times per second.
19
Even though these subprocesses may have a brief lifespan, `apps.plugin` effectively aggregates their resource utilization, providing a comprehensive overview of how resources are shared among all processes within the system.
20
21
+## PSS Memory Estimation
22
+
23
+On Linux systems with kernel 4.14 or later, `apps.plugin` uses Proportional Set Size (PSS) data to provide more accurate memory usage estimates for processes that use shared memory.
24
+
25
+PSS is an expensive kernel operation that requires scanning all shared memory segments of a process to determine which memory pages are shared with other processes, and then proportionally dividing the shared memory among them to calculate each process's actual memory footprint. Since PSS for any process can change due to actions by other processes (such as mapping or unmapping the same files, or processes exiting), maintaining accurate real-time PSS data for all processes would be prohibitively expensive.
26
+
27
+To balance accuracy with performance, `apps.plugin` uses a **ratio-based estimation approach**: it periodically samples PSS values to calculate a PSS/RSS ratio for each process, then applies this cached ratio to the current RSS values **every second** to estimate memory usage. This means that estimated memory values are updated every second based on current RSS, while the ratio itself is recalibrated adaptively based on process priority.
28
+
29
+The plugin implements an **adaptive sampling strategy** designed to prioritize the largest memory consumers and processes with significant memory changes, refreshing them within seconds of detection, while guaranteeing that all processes are eventually sampled within **twice the configured interval** (10 minutes by default for a 5-minute interval). The plugin alternates between two complementary prioritization strategies each iteration:
30
+
31
+1. **Delta-Based Strategy**: Prioritizes processes with the largest changes in shared memory, ensuring rapid detection and response to memory growth. Large memory consumers (databases, cache servers, etc.) are typically refreshed within seconds when their memory footprint changes significantly.
32
+
33
+2. **Age-Based Strategy**: Prioritizes processes that haven't been sampled longest, ensuring eventual consistency for all processes. Even the smallest processes are guaranteed to be refreshed within twice the configured interval.
34
+
35
+Both strategies sort candidates by priority and refresh the top N processes within the configured budget each iteration. By alternating between these strategies, the plugin ensures responsive tracking of significant memory changes while maintaining bounded staleness for all processes.
36
+
37
+Additionally, on the first iteration after startup, the plugin samples all processes to establish accurate initial estimates before switching to the adaptive sampling strategy.
38
+
39
## Charts
40
41
`apps.plugin` offers a set of charts for three groups within the **System->Processes** section of the Netdata dashboard: **Apps**, **Users**, and **Groups**.
@@ -28,6 +46,8 @@ Each of these sections presents the same number of charts:
46
- Total CPU usage
47
- User/system CPU usage
48
- Memory
49
+ - Estimated Memory Usage (RSS with PSS scaling, default on Linux 4.14+)
50
+ - Memory RSS Usage
51
- Real Memory Used (non-shared)
52
- Virtual Memory Allocated
53
- Minor page faults (i.e. memory activity)
@@ -251,6 +271,46 @@ For example, to disable user and user group charts you would set:
271
command options = without-users without-groups
272
```
273
274
+### Memory Estimation with PSS Sampling
275
+
276
+On Linux systems with kernel 4.14 or later, `apps.plugin` uses Proportional Set Size (PSS) data from `/proc/<pid>/smaps_rollup` to provide more accurate memory usage estimates for processes that heavily use shared memory (e.g., databases, shared memory applications).
277
+
278
+**By default, PSS sampling is enabled with a 5-minute refresh interval.** This provides better accuracy than raw RSS (Resident Set Size), which can overstate memory usage for processes sharing memory pages. The plugin periodically samples PSS values and uses them to scale the shared memory portion of RSS, providing a more accurate estimate without the overhead of reading smaps on every iteration.
279
+
280
+#### Configuration
281
+
282
+The `--pss` option controls PSS sampling behavior:
283
+
284
+```text
285
+[plugin:apps]
286
+ command options = --pss 5m
287
+```
288
+
289
+**Valid values:**
290
+- Duration (e.g., `5m`, `300s`, `10m`): Sets the refresh interval for PSS sampling. Lower values provide more accurate estimates but increase CPU overhead.
291
+- `off` or `0`: Completely disables PSS sampling. Memory charts will show traditional RSS-based measurements.
292
+
293
+**Default:** `5m` (5 minutes)
294
+
295
+**How it works:**
296
+- `apps.plugin` uses adaptive sampling that alternates between two strategies each iteration:
297
+ - **Delta-based**: Prioritizes processes with largest shared memory changes (refreshes big memory consumers within seconds)
298
+ - **Age-based**: Prioritizes processes with oldest samples (ensures all processes refreshed within 2× the interval)
299
+- The sampled PSS/RSS ratio is cached and applied to subsequent RSS readings to estimate current memory usage
300
+- This approach ensures rapid response to significant memory changes while guaranteeing bounded staleness for all processes
301
+- When disabled (`--pss 0` or `--pss off`), no PSS sampling occurs and estimated memory charts are not shown
302
+
303
+**Performance considerations:**
304
+- Reading `/proc/<pid>/smaps_rollup` is more expensive than reading `/proc/<pid>/status`
305
+- Shorter refresh periods provide more accurate estimates but increase CPU usage
306
+- On systems with thousands of processes, consider increasing the refresh period (e.g., `10m` or `15m`)
307
+- For systems without significant shared memory usage, disabling PSS sampling (`--pss off`) reduces overhead
308
+
309
+**Chart behavior:**
310
+- **Default (PSS enabled):** Shows both "Estimated memory usage (RSS with shared scaling)" and "Memory RSS usage" charts
311
+- **When disabled (`--pss 0`):** Shows only "Memory RSS usage" charts
312
+- The `processes` function API exposes additional columns (PSS, PssAge, SharedRatio) when PSS is enabled
313
+
314
### Integration with eBPF
315
316
If you don't see charts under the **eBPF syscall** or **eBPF net** sections, you should edit your
src/collectors/apps.plugin/apps_aggregations.c
+39
-1
@@ -15,6 +15,10 @@ static size_t zero_all_targets(struct target *root) {
15
for(size_t f = 0; f < PDF_MAX ;f++)
16
w->values[f] = 0;
17
18
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
19
+ w->needs_smaps_update = false;
20
+#endif
21
+
22
w->uptime_min = 0;
23
w->uptime_max = 0;
24
@@ -68,6 +72,16 @@ static inline void aggregate_pid_on_target(struct target *w, struct pid_stat *p,
72
w->max_open_files_percent = p->openfds_limits_percent;
73
#endif
74
75
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
76
+ kernel_uint_t shared = p->values[PDF_VMSHARED];
77
+ if(shared > 0) {
78
+ if(o && o != w)
79
+ o->needs_smaps_update = true;
80
+ if(!o || o != w)
81
+ w->needs_smaps_update = true;
82
+ }
83
+#endif
84
+
85
for(size_t f = 0; f < PDF_MAX ;f++)
86
w->values[f] += p->values[f];
87
@@ -90,6 +104,13 @@ static inline void cleanup_exited_pids(void) {
104
if(unlikely(debug_enabled && (p->keep || p->keeploops)))
105
debug_log(" > CLEANUP cannot keep exited process %d (%s) anymore - removing it.", p->pid, pid_stat_comm(p));
106
107
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
108
+ if(p->values[PDF_VMSHARED] > 0) {
109
+ if(p->target)
110
+ p->target->needs_smaps_update = true;
111
+ }
112
+#endif
113
+
114
#if (PROCESSES_HAVE_FDS == 1)
115
for(size_t c = 0; c < p->fds_size; c++)
116
if(p->fds[c].fd > 0) {
@@ -183,7 +204,13 @@ void aggregate_processes_to_targets(void) {
204
// --------------------------------------------------------------------
205
// apps_groups and tree target
206
186
- aggregate_pid_on_target(p->target, p, NULL);
207
+ aggregate_pid_on_target(p->target, p,
208
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
209
+ p->prev_target
210
+#else
211
+ NULL
212
+#endif
213
+ );
214
215
216
// --------------------------------------------------------------------
@@ -244,7 +271,18 @@ void aggregate_processes_to_targets(void) {
271
if(enable_file_charts)
272
aggregate_pid_fds_on_targets(p);
273
#endif
274
+
275
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
276
+ p->prev_target = p->target;
277
+#endif
278
}
279
280
cleanup_exited_pids();
281
+
282
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
283
+ for(struct pid_stat *p = root_of_pids(); p ; p = p->next) {
284
+ if(p->target && p->target->needs_smaps_update && p->values[PDF_VMSHARED] > 0)
285
+ p->vmshared_delta = p->values[PDF_VMSHARED];
286
+ }
287
+#endif
288
}
src/collectors/apps.plugin/apps_functions.c
+50
-1
@@ -199,6 +199,12 @@ void function_processes(const char *transaction, char *function,
199
, CPU_max = 0.0
200
, VMSize_max = 0.0
201
, RSS_max = 0.0
202
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
203
+ , Estimated_max = 0.0
204
+ , Pss_max = 0.0
205
+ , PssAge_max = 0.0
206
+ , SharedRatio_max = 0.0
207
+#endif
208
#if (PROCESSES_HAVE_VMSHARED == 1)
209
, Shared_max = 0.0
210
#endif
@@ -387,7 +393,22 @@ void function_processes(const char *transaction, char *function,
393
add_value_field_ndd_with_max(wb, Memory, (NETDATA_DOUBLE)p->values[PDF_VMRSS] * 100.0 / (NETDATA_DOUBLE)total_memory_bytes);
394
395
add_value_field_ndd_with_max(wb, RSS, (NETDATA_DOUBLE)p->values[PDF_VMRSS] / memory_divisor);
390
-
396
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
397
+ if(pss_refresh_period > 0) {
398
+ add_value_field_ndd_with_max(wb, Estimated, (NETDATA_DOUBLE)p->values[PDF_MEM_ESTIMATED] / memory_divisor);
399
+ add_value_field_ndd_with_max(wb, Pss, (NETDATA_DOUBLE)p->values[PDF_PSS] / memory_divisor);
400
+ NETDATA_DOUBLE pss_age;
401
+ if(p->last_pss_iteration == 0)
402
+ pss_age = NAN;
403
+ else if(global_iterations_counter >= p->last_pss_iteration)
404
+ pss_age = (NETDATA_DOUBLE)(global_iterations_counter - p->last_pss_iteration) * update_every;
405
+ else
406
+ pss_age = NAN;
407
+ add_value_field_ndd_with_max(wb, PssAge, pss_age);
408
+ NETDATA_DOUBLE shared_ratio = p->pss_total_ratio * 100.0;
409
+ add_value_field_ndd_with_max(wb, SharedRatio, shared_ratio);
410
+ }
411
+#endif
412
#if (PROCESSES_HAVE_VMSHARED == 1)
413
add_value_field_ndd_with_max(wb, Shared, (NETDATA_DOUBLE)p->values[PDF_VMSHARED] / memory_divisor);
414
#endif
@@ -614,6 +635,34 @@ void function_processes(const char *transaction, char *function,
635
2, "MiB", RSS_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
636
RRDF_FIELD_FILTER_RANGE,
637
RRDF_FIELD_OPTS_VISIBLE, NULL);
638
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
639
+ if(pss_refresh_period > 0) {
640
+ buffer_rrdf_table_add_field(wb, field_id++, "Estimated", "Estimated Memory Usage", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
641
+ RRDF_FIELD_VISUAL_BAR,
642
+ RRDF_FIELD_TRANSFORM_NUMBER,
643
+ 2, "MiB", Estimated_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
644
+ RRDF_FIELD_FILTER_RANGE,
645
+ RRDF_FIELD_OPTS_VISIBLE, NULL);
646
+ buffer_rrdf_table_add_field(wb, field_id++, "Pss", "Proportional Set Size", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
647
+ RRDF_FIELD_VISUAL_BAR,
648
+ RRDF_FIELD_TRANSFORM_NUMBER,
649
+ 2, "MiB", Pss_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_SUM,
650
+ RRDF_FIELD_FILTER_RANGE,
651
+ RRDF_FIELD_OPTS_NONE, NULL);
652
+ buffer_rrdf_table_add_field(wb, field_id++, "PssAge", "Time since last smaps sample", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
653
+ RRDF_FIELD_VISUAL_BAR,
654
+ RRDF_FIELD_TRANSFORM_NUMBER,
655
+ 2, "s", PssAge_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_MAX,
656
+ RRDF_FIELD_FILTER_RANGE,
657
+ RRDF_FIELD_OPTS_NONE, NULL);
658
+ buffer_rrdf_table_add_field(wb, field_id++, "SharedRatio", "Shared Memory Ratio", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
659
+ RRDF_FIELD_VISUAL_BAR,
660
+ RRDF_FIELD_TRANSFORM_NUMBER,
661
+ 2, "%", SharedRatio_max, RRDF_FIELD_SORT_DESCENDING, NULL, RRDF_FIELD_SUMMARY_MEAN,
662
+ RRDF_FIELD_FILTER_RANGE,
663
+ RRDF_FIELD_OPTS_NONE, NULL);
664
+ }
665
+#endif
666
#if (PROCESSES_HAVE_VMSHARED == 1)
667
buffer_rrdf_table_add_field(wb, field_id++, "Shared", "Shared Pages", RRDF_FIELD_TYPE_BAR_WITH_INTEGER,
668
RRDF_FIELD_VISUAL_BAR, RRDF_FIELD_TRANSFORM_NUMBER, 2,
src/collectors/apps.plugin/apps_incremental_collection.c
+9
@@ -50,6 +50,15 @@ bool managed_log(struct pid_stat *p, PID_LOG log, bool status) {
50
netdata_log_error("Cannot process %s/proc/%d/limits (command '%s')", netdata_configured_host_prefix, p->pid, pid_stat_comm(p));
51
#endif
52
53
+ case PID_LOG_LIMITS_DETAIL:
54
+ break;
55
+
56
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
57
+ case PID_LOG_SMAPS:
58
+ netdata_log_error("Cannot process %s/proc/%d/smaps_rollup (command '%s')", netdata_configured_host_prefix, p->pid, pid_stat_comm(p));
59
+ break;
60
+#endif
61
+
62
case PID_LOG_STAT:
63
break;
64
src/collectors/apps.plugin/apps_os_linux.c
+288
-1
@@ -1,6 +1,8 @@
1
// SPDX-License-Identifier: GPL-3.0-or-later
2
3
#include "apps_plugin.h"
4
+#include <limits.h>
5
+#include <unistd.h>
6
7
#if defined(OS_LINUX)
8
@@ -23,6 +25,269 @@ struct arl_callback_ptr {
25
size_t line;
26
};
27
28
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
29
+
30
+struct arl_callback_smaps_ptr {
31
+ struct pid_stat *p;
32
+ procfile *ff;
33
+ size_t line;
34
+};
35
+
36
+static procfile *smaps_rollup_ff = NULL;
37
+static struct arl_callback_smaps_ptr smaps_rollup_ctx;
38
+static bool smaps_rollup_checked = false;
39
+static bool smaps_rollup_available = false;
40
+static bool smaps_rollup_warned = false;
41
+
42
+struct smaps_candidate {
43
+ struct pid_stat *p;
44
+ kernel_uint_t delta; // for delta-based refresh strategy
45
+ size_t age; // for age-based refresh strategy
46
+ kernel_uint_t vmshared; // for age-based refresh strategy (tiebreaker)
47
+};
48
+
49
+static int compare_smaps_delta_desc(const void *a, const void *b) {
50
+ const struct smaps_candidate *A = (const struct smaps_candidate *)a;
51
+ const struct smaps_candidate *B = (const struct smaps_candidate *)b;
52
+ if(A->delta < B->delta) return 1;
53
+ if(A->delta > B->delta) return -1;
54
+ return 0;
55
+}
56
+
57
+static int compare_smaps_age_desc(const void *a, const void *b) {
58
+ const struct smaps_candidate *A = (const struct smaps_candidate *)a;
59
+ const struct smaps_candidate *B = (const struct smaps_candidate *)b;
60
+ if(A->age < B->age) return 1;
61
+ if(A->age > B->age) return -1;
62
+ if(A->vmshared < B->vmshared) return 1;
63
+ if(A->vmshared > B->vmshared) return -1;
64
+ return 0;
65
+}
66
+
67
+static inline void pid_update_estimated_memory(struct pid_stat *p) {
68
+ kernel_uint_t vmrss = p->values[PDF_VMRSS];
69
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
70
+ p->values[PDF_PSS] = p->pss_bytes;
71
+#endif
72
+ if(vmrss == 0) {
73
+ p->values[PDF_MEM_ESTIMATED] = 0;
74
+ return;
75
+ }
76
+
77
+ NETDATA_DOUBLE ratio = p->pss_total_ratio;
78
+ if(unlikely(ratio < 0.0)) ratio = 0.0;
79
+ if(unlikely(ratio > 1.0)) ratio = 1.0;
80
+
81
+ NETDATA_DOUBLE scaled = (NETDATA_DOUBLE)vmrss * ratio;
82
+ if(unlikely(scaled > (NETDATA_DOUBLE)UINT64_MAX))
83
+ p->values[PDF_MEM_ESTIMATED] = vmrss;
84
+ else
85
+ p->values[PDF_MEM_ESTIMATED] = (kernel_uint_t)(scaled + 0.5);
86
+}
87
+
88
+static inline kernel_uint_t smaps_value_to_bytes(const char *value) {
89
+ return str2kernel_uint_t(value) * 1024ULL;
90
+}
91
+
92
+static void arl_callback_smaps_pss(const char *name __maybe_unused, uint32_t hash __maybe_unused, const char *value __maybe_unused, void *dst) {
93
+ struct arl_callback_smaps_ptr *ctx = (struct arl_callback_smaps_ptr *)dst;
94
+ if(unlikely(procfile_linewords(ctx->ff, ctx->line) < 2))
95
+ return;
96
+
97
+ ctx->p->values[PDF_PSS] = smaps_value_to_bytes(procfile_lineword(ctx->ff, ctx->line, 1));
98
+}
99
+
100
+bool apps_os_have_smaps_rollup_linux(void) {
101
+ if(likely(smaps_rollup_checked))
102
+ return smaps_rollup_available;
103
+
104
+ char filename[FILENAME_MAX + 1];
105
+ snprintfz(filename, FILENAME_MAX, "%s/proc/self/smaps_rollup", netdata_configured_host_prefix);
106
+
107
+ if(access(filename, R_OK) == 0) {
108
+ smaps_rollup_available = true;
109
+ }
110
+ else {
111
+ if(!smaps_rollup_warned) {
112
+ netdata_log_info("apps.plugin: /proc/*/smaps_rollup is not available on this kernel. PSS metrics will be disabled.");
113
+ smaps_rollup_warned = true;
114
+ }
115
+ smaps_rollup_available = false;
116
+ }
117
+
118
+ smaps_rollup_checked = true;
119
+ return smaps_rollup_available;
120
+}
121
+
122
+bool apps_os_read_pid_smaps_rollup_linux(struct pid_stat *p, void *ptr __maybe_unused) {
123
+ if(unlikely(!apps_os_have_smaps_rollup_linux()))
124
+ return false;
125
+
126
+ if(unlikely(!p->smaps_rollup_arl)) {
127
+ p->smaps_rollup_arl = arl_create("/proc/pid/smaps_rollup", NULL, 60);
128
+ arl_expect_custom(p->smaps_rollup_arl, "Pss", arl_callback_smaps_pss, &smaps_rollup_ctx);
129
+ }
130
+
131
+ if(unlikely(!p->smaps_rollup_filename)) {
132
+ char filename[FILENAME_MAX + 1];
133
+ snprintfz(filename, FILENAME_MAX, "%s/proc/%d/smaps_rollup", netdata_configured_host_prefix, p->pid);
134
+ p->smaps_rollup_filename = strdupz(filename);
135
+ }
136
+
137
+ smaps_rollup_ff = procfile_reopen(smaps_rollup_ff, p->smaps_rollup_filename, (!smaps_rollup_ff) ? " \t:" : NULL, PROCFILE_FLAG_NO_ERROR_ON_FILE_IO);
138
+ if(unlikely(!smaps_rollup_ff)) {
139
+ if(errno == EINVAL)
140
+ errno = ENOENT;
141
+ return false;
142
+ }
143
+
144
+ smaps_rollup_ff = procfile_readall(smaps_rollup_ff);
145
+ if(unlikely(!smaps_rollup_ff)) {
146
+ if(errno == EINVAL)
147
+ errno = ENOENT;
148
+ return false;
149
+ }
150
+
151
+ calls_counter++;
152
+
153
+ smaps_rollup_ctx.p = p;
154
+ smaps_rollup_ctx.ff = smaps_rollup_ff;
155
+
156
+ size_t lines = procfile_lines(smaps_rollup_ff);
157
+ arl_begin(p->smaps_rollup_arl);
158
+
159
+ for(size_t l = 0; l < lines; l++) {
160
+ smaps_rollup_ctx.line = l;
161
+ if(unlikely(arl_check(p->smaps_rollup_arl,
162
+ procfile_lineword(smaps_rollup_ff, l, 0),
163
+ procfile_lineword(smaps_rollup_ff, l, 1))))
164
+ break;
165
+ }
166
+
167
+ kernel_uint_t vmrss = p->values[PDF_VMRSS];
168
+ kernel_uint_t pss = p->values[PDF_PSS];
169
+ NETDATA_DOUBLE ratio = 1.0;
170
+ if(vmrss > 0)
171
+ ratio = (NETDATA_DOUBLE)pss / (NETDATA_DOUBLE)vmrss;
172
+
173
+ if(ratio < 0.0) ratio = 0.0;
174
+ if(ratio > 1.0) ratio = 1.0;
175
+
176
+ p->pss_total_ratio = ratio;
177
+ p->pss_bytes = pss;
178
+ pid_update_estimated_memory(p);
179
+ p->vmshared_delta = 0;
180
+ p->last_pss_iteration = global_iterations_counter;
181
+
182
+ return true;
183
+}
184
+
185
+#define PSS_REFRESH_MIN 1
186
+
187
+static void apps_handle_smaps_updates(void) {
188
+ if(pss_refresh_period <= 0)
189
+ return;
190
+
191
+ if(unlikely(!apps_os_have_smaps_rollup()))
192
+ return;
193
+
194
+ struct pid_stat *p;
195
+
196
+ // On first iteration, scan all processes to get accurate initial estimates
197
+ if(unlikely(global_iterations_counter == 1)) {
198
+ for(p = root_of_pids(); p ; p = p->next) {
199
+ if(p->values[PDF_VMSHARED] > 0)
200
+ OS_FUNCTION(apps_os_read_pid_smaps_rollup)(p, NULL);
201
+ }
202
+ return;
203
+ }
204
+
205
+ // Alternate between delta-based and age-based refresh strategies
206
+ static bool refresh_by_delta = true;
207
+
208
+ size_t total_pids = 0;
209
+
210
+ for(p = root_of_pids(); p ; p = p->next)
211
+ total_pids++;
212
+
213
+ if(unlikely(total_pids == 0))
214
+ return;
215
+
216
+ int divisor = pss_refresh_period / update_every;
217
+ if(divisor < 1)
218
+ divisor = 1;
219
+
220
+ size_t budget = total_pids / (size_t)divisor;
221
+ if(budget < PSS_REFRESH_MIN)
222
+ budget = PSS_REFRESH_MIN;
223
+
224
+ struct smaps_candidate *candidates = mallocz(sizeof(*candidates) * total_pids);
225
+ size_t candidate_count = 0;
226
+
227
+ // Populate candidates based on current strategy
228
+ if(refresh_by_delta) {
229
+ // Delta-based: prioritize processes with largest memory changes
230
+ for(p = root_of_pids(); p ; p = p->next) {
231
+ kernel_uint_t vmshared = p->values[PDF_VMSHARED];
232
+
233
+ if(vmshared > 0 && p->vmshared_delta > 0) {
234
+ candidates[candidate_count].p = p;
235
+ candidates[candidate_count].delta = p->vmshared_delta;
236
+ candidate_count++;
237
+ }
238
+ }
239
+
240
+ // Sort by delta (descending)
241
+ if(candidate_count > 1)
242
+ qsort(candidates, candidate_count, sizeof(*candidates), compare_smaps_delta_desc);
243
+ }
244
+ else {
245
+ // Age-based: prioritize processes that haven't been updated longest
246
+ for(p = root_of_pids(); p ; p = p->next) {
247
+ kernel_uint_t vmshared = p->values[PDF_VMSHARED];
248
+
249
+ if(vmshared == 0)
250
+ continue;
251
+
252
+ size_t age;
253
+ if(p->last_pss_iteration == 0)
254
+ age = SIZE_MAX;
255
+ else if(global_iterations_counter >= p->last_pss_iteration)
256
+ age = global_iterations_counter - p->last_pss_iteration;
257
+ else
258
+ age = SIZE_MAX;
259
+
260
+ candidates[candidate_count].p = p;
261
+ candidates[candidate_count].age = age;
262
+ candidates[candidate_count].vmshared = vmshared;
263
+ candidate_count++;
264
+ }
265
+
266
+ // Sort by age (descending), with vmshared as tiebreaker
267
+ if(candidate_count > 1)
268
+ qsort(candidates, candidate_count, sizeof(*candidates), compare_smaps_age_desc);
269
+ }
270
+
271
+ // Refresh top priority processes within budget
272
+ size_t to_refresh = candidate_count;
273
+ if(to_refresh > budget)
274
+ to_refresh = budget;
275
+
276
+ for(size_t i = 0; i < to_refresh; i++) {
277
+ struct pid_stat *pid_entry = candidates[i].p;
278
+ if(!pid_entry)
279
+ continue;
280
+ OS_FUNCTION(apps_os_read_pid_smaps_rollup)(pid_entry, NULL);
281
+ }
282
+
283
+ freez(candidates);
284
+
285
+ // Toggle strategy for next iteration
286
+ refresh_by_delta = !refresh_by_delta;
287
+}
288
+
289
+#endif // PROCESSES_HAVE_SMAPS_ROLLUP
290
+
291
bool apps_os_read_pid_fds_linux(struct pid_stat *p, void *ptr __maybe_unused) {
292
if(unlikely(!p->fds_dirname)) {
293
char dirname[FILENAME_MAX+1];
@@ -452,7 +717,24 @@ void arl_callback_status_rssshmem(const char *name, uint32_t hash, const char *v
717
struct arl_callback_ptr *aptr = (struct arl_callback_ptr *)dst;
718
if(unlikely(procfile_linewords(aptr->ff, aptr->line) < 3)) return;
719
455
- aptr->p->values[PDF_RSSSHMEM] = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)) * 1024;
720
+ struct pid_stat *p = aptr->p;
721
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
722
+ kernel_uint_t old_shared = p->values[PDF_VMSHARED];
723
+#endif
724
+
725
+ p->values[PDF_RSSSHMEM] = str2kernel_uint_t(procfile_lineword(aptr->ff, aptr->line, 1)) * 1024;
726
+ p->values[PDF_VMSHARED] = p->values[PDF_RSSFILE] + p->values[PDF_RSSSHMEM];
727
+
728
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
729
+ if(old_shared > p->values[PDF_VMSHARED])
730
+ p->vmshared_delta += old_shared - p->values[PDF_VMSHARED];
731
+ else
732
+ p->vmshared_delta += p->values[PDF_VMSHARED] - old_shared;
733
+
734
+ p->values[PDF_PSS] = p->pss_bytes;
735
+
736
+ pid_update_estimated_memory(p);
737
+#endif
738
}
739
740
void arl_callback_status_voluntary_ctxt_switches(const char *name, uint32_t hash, const char *value, void *dst) {
@@ -765,6 +1047,11 @@ bool apps_os_collect_all_pids_linux(void) {
1047
}
1048
closedir(dir);
1049
1050
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
1051
+ apps_handle_smaps_updates();
1052
+#endif
1053
+
1054
return true;
1055
}
1056
+
1057
#endif
src/collectors/apps.plugin/apps_output.c
+18
-1
@@ -159,6 +159,14 @@ void send_collected_data_to_netdata(struct target *root, const char *type, usec_
159
send_END();
160
#endif
161
162
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
163
+ if(pss_refresh_period > 0) {
164
+ send_BEGIN(type, string2str(w->clean_name), "estimated_mem_usage", dt);
165
+ send_SET("mem", w->values[PDF_MEM_ESTIMATED]);
166
+ send_END();
167
+ }
168
+#endif
169
+
170
send_BEGIN(type, string2str(w->clean_name), "mem_usage", dt);
171
send_SET("rss", w->values[PDF_VMRSS]);
172
send_END();
@@ -335,6 +343,16 @@ void send_charts_updates_to_netdata(struct target *root, const char *type, const
343
#endif
344
#endif
345
346
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
347
+ if(pss_refresh_period > 0) {
348
+ fprintf(stdout, "CHART %s.%s_estimated_mem_usage '' '%s estimated memory usage (RSS with shared scaling)' 'MiB' mem %s.estimated_mem_usage area 20055 %d\n",
349
+ type, string2str(w->clean_name), title, type, update_every);
350
+ fprintf(stdout, "CLABEL '%s' '%s' 1\n", lbl_name, string2str(w->name));
351
+ fprintf(stdout, "CLABEL_COMMIT\n");
352
+ fprintf(stdout, "DIMENSION mem '' absolute %ld %ld\n", 1L, 1024L * 1024L);
353
+ }
354
+#endif
355
+
356
fprintf(stdout, "CHART %s.%s_mem_usage '' '%s memory RSS usage' 'MiB' mem %s.mem_usage area 20055 %d\n",
357
type, string2str(w->clean_name), title, type, update_every);
358
fprintf(stdout, "CLABEL '%s' '%s' 1\n", lbl_name, string2str(w->name));
@@ -439,4 +457,3 @@ void send_proc_states_count(usec_t dt __maybe_unused) {
457
send_END();
458
}
459
#endif
442
-
src/collectors/apps.plugin/apps_pid.c
+11
@@ -90,6 +90,11 @@ struct pid_stat *get_or_allocate_pid_entry(pid_t pid) {
90
91
p->pid = pid;
92
p->values[PDF_PROCESSES] = 1;
93
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
94
+ p->pss_total_ratio = 1.0;
95
+ p->last_pss_iteration = 0;
96
+ p->pss_bytes = 0;
97
+#endif
98
99
DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(pids.all_pids.root, p, prev, next);
100
simple_hashtable_set_slot_PID(&pids.all_pids.ht, sl, hash, p);
@@ -123,6 +128,9 @@ void del_pid_entry(pid_t pid) {
128
}
129
130
arl_free(p->status_arl);
131
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
132
+ arl_free(p->smaps_rollup_arl);
133
+#endif
134
135
freez(p->fds_dirname);
136
freez(p->stat_filename);
@@ -130,6 +138,9 @@ void del_pid_entry(pid_t pid) {
138
freez(p->limits_filename);
139
freez(p->io_filename);
140
freez(p->cmdline_filename);
141
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
142
+ freez(p->smaps_rollup_filename);
143
+#endif
144
#endif
145
146
#if (PROCESSES_HAVE_FDS == 1)
src/collectors/apps.plugin/apps_plugin.c
+34
@@ -2,6 +2,7 @@
2
3
#include "apps_plugin.h"
4
#include "libnetdata/required_dummies.h"
5
+#include "libnetdata/parsers/duration.h"
6
7
#define APPS_PLUGIN_FUNCTIONS() do { \
8
fprintf(stdout, PLUGINSD_KEYWORD_FUNCTION " \"processes\" %d \"%s\" \"top\" "HTTP_ACCESS_FORMAT" %d\n", \
@@ -379,6 +380,9 @@ cleanup:
380
381
static bool profile_speed = false;
382
static bool print_tree_and_exit = false;
383
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
384
+int pss_refresh_period = 300; // seconds
385
+#endif
386
387
static void parse_args(int argc, char **argv)
388
{
@@ -438,6 +442,30 @@ static void parse_args(int argc, char **argv)
442
if(max_fds_cache_seconds < 0) max_fds_cache_seconds = 0;
443
continue;
444
}
445
+
446
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
447
+ if(strcmp("--pss", argv[i]) == 0) {
448
+ if(argc <= i + 1) {
449
+ fprintf(stderr, "Parameter '--pss' requires a duration (e.g. 5m, 300s) or 'off'.\n");
450
+ exit(1);
451
+ }
452
+ i++;
453
+ int64_t seconds = 0;
454
+ if(!duration_parse(argv[i], &seconds, "s", "s")) {
455
+ fprintf(stderr, "Cannot parse '--pss' value '%s'.\n", argv[i]);
456
+ exit(1);
457
+ }
458
+ if(seconds <= 0) {
459
+ pss_refresh_period = 0; // disabled
460
+ }
461
+ else {
462
+ pss_refresh_period = (int)seconds;
463
+ if(pss_refresh_period < 1)
464
+ pss_refresh_period = 1;
465
+ }
466
+ continue;
467
+ }
468
+#endif
469
#endif
470
471
#if (PROCESSES_HAVE_CPU_CHILDREN_TIME == 1) || (PROCESSES_HAVE_CHILDREN_FLTS == 1)
@@ -557,6 +585,12 @@ static void parse_args(int argc, char **argv)
585
" max given)\n"
586
" (default is %d seconds)\n"
587
"\n"
588
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
589
+ " --pss TIME estimated memory interval (e.g. 5m, 300s)\n"
590
+ " use 'off' to disable smaps sampling\n"
591
+ " (default is 5 minutes)\n"
592
+ "\n"
593
+#endif
594
#endif
595
" version or -v or -V print program version and exit\n"
596
"\n"
src/collectors/apps.plugin/apps_plugin.h
+34
@@ -76,6 +76,7 @@ struct pid_info {
76
#define PROCESSES_HAVE_VMSHARED 0
77
#define PROCESSES_HAVE_RSSFILE 0
78
#define PROCESSES_HAVE_RSSSHMEM 0
79
+#define PROCESSES_HAVE_SMAPS_ROLLUP 0
80
#define PROCESSES_HAVE_FDS 1
81
#define PROCESSES_HAVE_HANDLES 0
82
#define PROCESSES_HAVE_CMDLINE 1
@@ -106,6 +107,7 @@ struct pid_info {
107
#define PROCESSES_HAVE_VMSHARED 0
108
#define PROCESSES_HAVE_RSSFILE 0
109
#define PROCESSES_HAVE_RSSSHMEM 0
110
+#define PROCESSES_HAVE_SMAPS_ROLLUP 0
111
#define PROCESSES_HAVE_FDS 0
112
#define PROCESSES_HAVE_HANDLES 1
113
#define PROCESSES_HAVE_CMDLINE 0
@@ -136,6 +138,7 @@ struct pid_info {
138
#define PROCESSES_HAVE_VMSHARED 1
139
#define PROCESSES_HAVE_RSSFILE 1
140
#define PROCESSES_HAVE_RSSSHMEM 1
141
+#define PROCESSES_HAVE_SMAPS_ROLLUP 1
142
#define PROCESSES_HAVE_FDS 1
143
#define PROCESSES_HAVE_HANDLES 0
144
#define PROCESSES_HAVE_CMDLINE 1
@@ -329,6 +332,11 @@ typedef enum __attribute__((packed)) {
332
PDF_VMSHARED, // the shared memory used by the process, in bytes
333
#endif
334
335
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
336
+ PDF_MEM_ESTIMATED, // estimated memory usage using smaps ratios, in bytes
337
+ PDF_PSS, // proportional set size, in bytes
338
+#endif
339
+
340
#if (PROCESSES_HAVE_RSSFILE == 1)
341
PDF_RSSFILE, // unit: bytes
342
#endif
@@ -413,6 +421,7 @@ struct target {
421
#endif
422
423
bool exposed:1; // if set, we have sent this to netdata
424
+ bool needs_smaps_update:1;
425
426
struct pid_on_target *root_pid; // list of aggregated pids for target debugging
427
@@ -434,6 +443,9 @@ typedef enum __attribute__((packed)) {
443
PID_LOG_STAT = (1 << 4),
444
PID_LOG_LIMITS = (1 << 5),
445
PID_LOG_LIMITS_DETAIL = (1 << 6),
446
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
447
+ PID_LOG_SMAPS = (1 << 7),
448
+#endif
449
} PID_LOG;
450
451
// --------------------------------------------------------------------------------------------------------------------
@@ -493,6 +505,7 @@ struct pid_stat {
505
struct pid_stat *prev;
506
507
struct target *target; // app_groups.conf/tree targets
508
+ struct target *prev_target;
509
510
#if (PROCESSES_HAVE_UID == 1)
511
struct target *uid_target; // uid based targets
@@ -584,6 +597,14 @@ struct pid_stat {
597
char *io_filename;
598
char *cmdline_filename;
599
char *limits_filename;
600
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
601
+ char *smaps_rollup_filename;
602
+ ARL_BASE *smaps_rollup_arl;
603
+ kernel_uint_t vmshared_delta;
604
+ NETDATA_DOUBLE pss_total_ratio;
605
+ size_t last_pss_iteration;
606
+ kernel_uint_t pss_bytes;
607
+#endif
608
#endif
609
};
610
@@ -756,6 +777,19 @@ bool OS_FUNCTION(apps_os_get_pid_cmdline)(struct pid_stat *p, char *cmdline, siz
777
bool OS_FUNCTION(apps_os_read_pid_fds)(struct pid_stat *p, void *ptr);
778
#endif
779
780
+#if (PROCESSES_HAVE_SMAPS_ROLLUP == 1)
781
+bool OS_FUNCTION(apps_os_read_pid_smaps_rollup)(struct pid_stat *p, void *ptr);
782
+bool OS_FUNCTION(apps_os_have_smaps_rollup)(void);
783
+static inline bool apps_os_have_smaps_rollup(void) {
784
+ return OS_FUNCTION(apps_os_have_smaps_rollup)();
785
+}
786
+extern int pss_refresh_period;
787
+#else
788
+static inline bool OS_FUNCTION(apps_os_read_pid_smaps_rollup)(struct pid_stat *p __maybe_unused, void *ptr __maybe_unused) { return false; }
789
+static inline bool OS_FUNCTION(apps_os_have_smaps_rollup)(void) { return false; }
790
+static inline bool apps_os_have_smaps_rollup(void) { return false; }
791
+#endif
792
+
793
#if (ALL_PIDS_ARE_READ_INSTANTLY == 0)
794
bool OS_FUNCTION(apps_os_read_global_cpu_utilization)(void);
795
#endif
src/collectors/apps.plugin/integrations/applications.md
+1
-2
@@ -69,6 +69,7 @@ Metrics:
69
| app.cpu_utilization | user, system | percentage |
70
| app.cpu_guest_utilization | guest | percentage |
71
| app.cpu_context_switches | voluntary, involuntary | switches/s |
72
+| app.estimated_mem_usage | mem | MiB |
73
| app.mem_usage | rss | MiB |
74
| app.mem_private_usage | mem | MiB |
75
| app.vmem_usage | vmem | MiB |
@@ -113,5 +114,3 @@ There is no configuration file.
114
115
##### Examples
116
There are no configuration examples.
116
-
117
-
src/collectors/apps.plugin/integrations/user_groups.md
+1
-2
@@ -69,6 +69,7 @@ Metrics:
69
| usergroup.cpu_utilization | user, system | percentage |
70
| usergroup.cpu_guest_utilization | guest | percentage |
71
| usergroup.cpu_context_switches | voluntary, involuntary | switches/s |
72
+| usergroup.estimated_mem_usage | mem | MiB |
73
| usergroup.mem_usage | rss | MiB |
74
| usergroup.mem_private_usage | mem | MiB |
75
| usergroup.vmem_usage | vmem | MiB |
@@ -113,5 +114,3 @@ There is no configuration file.
114
115
##### Examples
116
There are no configuration examples.
116
-
117
-
src/collectors/apps.plugin/integrations/users.md
+1
-2
@@ -69,6 +69,7 @@ Metrics:
69
| user.cpu_utilization | user, system | percentage |
70
| user.cpu_guest_utilization | guest | percentage |
71
| user.cpu_context_switches | voluntary, involuntary | switches/s |
72
+| user.estimated_mem_usage | mem | MiB |
73
| user.mem_usage | rss | MiB |
74
| user.mem_private_usage | mem | MiB |
75
| user.vmem_usage | vmem | MiB |
@@ -113,5 +114,3 @@ There is no configuration file.
114
115
##### Examples
116
There are no configuration examples.
116
-
117
-
src/collectors/apps.plugin/metadata.yaml
+18
@@ -93,6 +93,12 @@ modules:
93
dimensions:
94
- name: voluntary
95
- name: involuntary
96
+ - name: app.estimated_mem_usage
97
+ description: Apps estimated memory usage (RSS with shared scaling). Requires kernel 4.14+. Enabled by default, disable with --pss 0.
98
+ unit: MiB
99
+ chart_type: line
100
+ dimensions:
101
+ - name: mem
102
- name: app.mem_usage
103
description: Apps memory RSS usage
104
unit: MiB
@@ -278,6 +284,12 @@ modules:
284
dimensions:
285
- name: voluntary
286
- name: involuntary
287
+ - name: usergroup.estimated_mem_usage
288
+ description: User Groups estimated memory usage (RSS with shared scaling). Requires kernel 4.14+. Enabled by default, disable with --pss 0.
289
+ unit: MiB
290
+ chart_type: area
291
+ dimensions:
292
+ - name: mem
293
- name: usergroup.mem_usage
294
description: User Groups memory RSS usage
295
unit: MiB
@@ -461,6 +473,12 @@ modules:
473
dimensions:
474
- name: voluntary
475
- name: involuntary
476
+ - name: user.estimated_mem_usage
477
+ description: User memory estimated usage (RSS with shared scaling). Requires kernel 4.14+. Enabled by default, disable with --pss 0.
478
+ unit: MiB
479
+ chart_type: area
480
+ dimensions:
481
+ - name: mem
482
- name: user.mem_usage
483
description: User memory RSS usage
484
unit: MiB
src/libnetdata/adaptive_resortable_list/adaptive_resortable_list.c
+53
@@ -193,6 +193,7 @@ ARL_ENTRY *arl_expect_custom(ARL_BASE *base, const char *keyword, void (*process
193
return e;
194
}
195
196
+ALWAYS_INLINE
197
int arl_find_or_create_and_relink(ARL_BASE *base, const char *s, const char *value) {
198
ARL_ENTRY *e;
199
@@ -278,3 +279,55 @@ int arl_find_or_create_and_relink(ARL_BASE *base, const char *s, const char *val
279
280
return 0;
281
}
282
+
283
+// check a keyword against the ARL
284
+// this is to be called for each keyword read from source data
285
+// s = the keyword, as collected
286
+// src = the src data to be passed to the processor
287
+// it is defined in the header file in order to be inlined
288
+ALWAYS_INLINE
289
+int arl_check(ARL_BASE *base, const char *keyword, const char *value) {
290
+ ARL_ENTRY *e = base->next_keyword;
291
+
292
+#ifdef NETDATA_INTERNAL_CHECKS
293
+ if(unlikely((base->fast + base->slow) % (base->expected + base->allocated) == 0 && (base->fast + base->slow) > (base->expected + base->allocated) * base->iteration))
294
+ netdata_log_info("ARL '%s': Did you forget to call arl_begin()?", base->name);
295
+#endif
296
+
297
+ // it should be the first entry (pointed by base->next_keyword)
298
+ if(likely(!strcmp(keyword, e->name))) {
299
+ // it is
300
+
301
+#ifdef NETDATA_INTERNAL_CHECKS
302
+ base->fast++;
303
+#endif
304
+
305
+ e->flags |= ARL_ENTRY_FLAG_FOUND;
306
+
307
+ // execute the processor
308
+ if(unlikely(e->dst)) {
309
+ e->processor(e->name, e->hash, value, e->dst);
310
+ base->found++;
311
+ }
312
+
313
+ // be prepared for the next iteration
314
+ base->next_keyword = e->next;
315
+ if(unlikely(!base->next_keyword))
316
+ base->next_keyword = base->head;
317
+
318
+ // stop if we collected all the values for this iteration
319
+ if(unlikely(base->found == base->wanted)) {
320
+ // fprintf(stderr, "FOUND ALL WANTED 2: found = %zu, wanted = %zu, expected %zu\n", base->found, base->wanted, base->expected);
321
+ return 1;
322
+ }
323
+
324
+ return 0;
325
+ }
326
+
327
+#ifdef NETDATA_INTERNAL_CHECKS
328
+ base->slow++;
329
+#endif
330
+
331
+ // we read from source, a not-expected keyword
332
+ return arl_find_or_create_and_relink(base, keyword, value);
333
+}
src/libnetdata/adaptive_resortable_list/adaptive_resortable_list.h
+1
-50
@@ -84,55 +84,6 @@ void arl_callback_str2ull(const char *name, uint32_t hash, const char *value, vo
84
void arl_callback_str2kernel_uint_t(const char *name, uint32_t hash, const char *value, void *dst);
85
void arl_callback_ssize_t(const char *name, uint32_t hash, const char *value, void *dst);
86
87
-// check a keyword against the ARL
88
-// this is to be called for each keyword read from source data
89
-// s = the keyword, as collected
90
-// src = the src data to be passed to the processor
91
-// it is defined in the header file in order to be inlined
92
-static inline int arl_check(ARL_BASE *base, const char *keyword, const char *value) {
93
- ARL_ENTRY *e = base->next_keyword;
94
-
95
-#ifdef NETDATA_INTERNAL_CHECKS
96
- if(unlikely((base->fast + base->slow) % (base->expected + base->allocated) == 0 && (base->fast + base->slow) > (base->expected + base->allocated) * base->iteration))
97
- netdata_log_info("ARL '%s': Did you forget to call arl_begin()?", base->name);
98
-#endif
99
-
100
- // it should be the first entry (pointed by base->next_keyword)
101
- if(likely(!strcmp(keyword, e->name))) {
102
- // it is
103
-
104
-#ifdef NETDATA_INTERNAL_CHECKS
105
- base->fast++;
106
-#endif
107
-
108
- e->flags |= ARL_ENTRY_FLAG_FOUND;
109
-
110
- // execute the processor
111
- if(unlikely(e->dst)) {
112
- e->processor(e->name, e->hash, value, e->dst);
113
- base->found++;
114
- }
115
-
116
- // be prepared for the next iteration
117
- base->next_keyword = e->next;
118
- if(unlikely(!base->next_keyword))
119
- base->next_keyword = base->head;
120
-
121
- // stop if we collected all the values for this iteration
122
- if(unlikely(base->found == base->wanted)) {
123
- // fprintf(stderr, "FOUND ALL WANTED 2: found = %zu, wanted = %zu, expected %zu\n", base->found, base->wanted, base->expected);
124
- return 1;
125
- }
126
-
127
- return 0;
128
- }
129
-
130
-#ifdef NETDATA_INTERNAL_CHECKS
131
- base->slow++;
132
-#endif
133
-
134
- // we read from source, a not-expected keyword
135
- return arl_find_or_create_and_relink(base, keyword, value);
136
-}
87
+int arl_check(ARL_BASE *base, const char *keyword, const char *value);
88
89
#endif //NETDATA_ADAPTIVE_RESORTABLE_LIST_H