Add linux powercap metrics collector (#15364)
Frank Riley committed
Jul 20, 2023 at 05:17 UTC
7766ec91a54f3ee7328d6187ae28255099b06ac8
9 files changed
+306
CMakeLists.txt
+1
@@ -512,6 +512,7 @@ set(DEBUGFS_PLUGIN_FILES
512
collectors/debugfs.plugin/debugfs_plugin.h
513
collectors/debugfs.plugin/debugfs_extfrag.c
514
collectors/debugfs.plugin/debugfs_zswap.c
515
+ collectors/debugfs.plugin/sys_devices_virtual_powercap.c
516
)
517
518
set(FREEBSD_PLUGIN_FILES
Makefile.am
+1
@@ -219,6 +219,7 @@ DEBUGFS_PLUGIN_FILES = \
219
collectors/debugfs.plugin/debugfs_plugin.h \
220
collectors/debugfs.plugin/debugfs_extfrag.c \
221
collectors/debugfs.plugin/debugfs_zswap.c \
222
+ collectors/debugfs.plugin/sys_devices_virtual_powercap.c \
223
$(LIBNETDATA_FILES) \
224
$(NULL)
225
build_external/scenarios/aclk-testing/agent_netdata.conf
+45
@@ -7108,3 +7108,48 @@
7108
# dim netdata algorithm = absolute
7109
# dim netdata multiplier = 1
7110
# dim netdata divisor = 1
7111
+
7112
+[system.power_consumption]
7113
+ history = 5
7114
+ # enabled = yes
7115
+ # cache directory = /var/cache/netdata/system.power_consumption
7116
+ # chart type = stacked
7117
+ # type = system
7118
+ # family = power_consumption
7119
+ # units = Watts
7120
+ # context = system.power_consumption
7121
+ # priority = 950
7122
+ # name = system.power_consumption
7123
+ # title = Power Consumption
7124
+ # dim HI name = HI
7125
+ # dim HI algorithm = incremental
7126
+ # dim HI multiplier = 1
7127
+ # dim HI divisor = 1
7128
+ # dim TIMER name = TIMER
7129
+ # dim TIMER algorithm = incremental
7130
+ # dim TIMER multiplier = 1
7131
+ # dim TIMER divisor = 1
7132
+ # dim NET_TX name = NET_TX
7133
+ # dim NET_TX algorithm = incremental
7134
+ # dim NET_TX multiplier = 1
7135
+ # dim NET_TX divisor = 1
7136
+ # dim NET_RX name = NET_RX
7137
+ # dim NET_RX algorithm = incremental
7138
+ # dim NET_RX multiplier = 1
7139
+ # dim NET_RX divisor = 1
7140
+ # dim TASKLET name = TASKLET
7141
+ # dim TASKLET algorithm = incremental
7142
+ # dim TASKLET multiplier = 1
7143
+ # dim TASKLET divisor = 1
7144
+ # dim SCHED name = SCHED
7145
+ # dim SCHED algorithm = incremental
7146
+ # dim SCHED multiplier = 1
7147
+ # dim SCHED divisor = 1
7148
+ # dim HRTIMER name = HRTIMER
7149
+ # dim HRTIMER algorithm = incremental
7150
+ # dim HRTIMER multiplier = 1
7151
+ # dim HRTIMER divisor = 1
7152
+ # dim RCU name = RCU
7153
+ # dim RCU algorithm = incremental
7154
+ # dim RCU multiplier = 1
7155
+ # dim RCU divisor = 1
build_external/scenarios/gaps_lo/mostly_off.conf
+3
@@ -961,3 +961,6 @@ enabled = no
961
962
[netfilter.netlink_expect]
963
enabled = no
964
+
965
+[system.power_consumption]
966
+enabled = no
collectors/all.h
+3
@@ -367,6 +367,9 @@
367
#define NETDATA_CHART_PRIO_POWER_SUPPLY_ENERGY 9502
368
#define NETDATA_CHART_PRIO_POWER_SUPPLY_VOLTAGE 9503
369
370
+// Linux powercap
371
+
372
+#define NETDATA_CHART_PRIO_POWERCAP 9600
373
374
// Wireless
375
collectors/debugfs.plugin/debugfs_plugin.c
+3
@@ -20,6 +20,9 @@ static struct debugfs_module {
20
.func = do_debugfs_extfrag},
21
{ .name = "/sys/kernel/debug/zswap", .enabled = CONFIG_BOOLEAN_YES,
22
.func = do_debugfs_zswap},
23
+ // Linux powercap metrics is here because it needs privilege to read each RAPL zone
24
+ { .name = "/sys/devices/virtual/powercap", .enabled = CONFIG_BOOLEAN_YES,
25
+ .func = do_sys_devices_virtual_powercap},
26
27
// The terminator
28
{ .name = NULL, .enabled = CONFIG_BOOLEAN_NO, .func = NULL}
collectors/debugfs.plugin/debugfs_plugin.h
+1
@@ -9,6 +9,7 @@
9
10
int do_debugfs_extfrag(int update_every, const char *name);
11
int do_debugfs_zswap(int update_every, const char *name);
12
+int do_sys_devices_virtual_powercap(int update_every, const char *name);
13
void debugfs2lower(char *name);
14
const char *debugfs_rrdset_type_name(RRDSET_TYPE chart_type);
15
const char *debugfs_rrd_algorithm_name(RRD_ALGORITHM algorithm);
collectors/debugfs.plugin/sys_devices_virtual_powercap.c
new
+241
@@ -0,0 +1,241 @@
1
+// SPDX-License-Identifier: GPL-3.0-or-later
2
+
3
+#include "debugfs_plugin.h"
4
+
5
+#define METRIC_ID "system.power_consumption"
6
+
7
+struct measurement_t {
8
+ unsigned long long energy_uj;
9
+ usec_t time_us;
10
+};
11
+
12
+struct zone_t {
13
+ char name[FILENAME_MAX + 1];
14
+ char path[FILENAME_MAX + 1];
15
+ unsigned long long max_energy_range_uj;
16
+ struct measurement_t measurement;
17
+};
18
+
19
+static int g_zones_count = 0;
20
+static struct zone_t *g_zones = NULL;
21
+
22
+static int get_measurement(const char *path, struct measurement_t *measurement)
23
+{
24
+ int result;
25
+ if (likely((result = read_single_number_file(path, &measurement->energy_uj)) == 0)) {
26
+ measurement->time_us = now_monotonic_high_precision_usec();
27
+ }
28
+ return result;
29
+}
30
+
31
+static double
32
+calculate_watts(unsigned long long max_energy_range_uj, struct measurement_t *before, struct measurement_t *after)
33
+{
34
+ unsigned long long energy_uj = 0;
35
+ usec_t delta_us = after->time_us - before->time_us;
36
+ if (unlikely(delta_us == 0)) {
37
+ return 0;
38
+ }
39
+
40
+ if (likely(after->energy_uj >= before->energy_uj)) {
41
+ energy_uj = after->energy_uj - before->energy_uj;
42
+ } else {
43
+ energy_uj = after->energy_uj + (max_energy_range_uj - before->energy_uj);
44
+ }
45
+ return (double)energy_uj / delta_us;
46
+}
47
+
48
+static struct zone_t *
49
+get_zone(const char *control_type, struct zone_t *parent, const char *dirname, struct zone_t **zones, size_t *count)
50
+{
51
+ char temp[FILENAME_MAX + 1];
52
+ snprintfz(temp, FILENAME_MAX, "%s/%s", dirname, "name");
53
+
54
+ char name[FILENAME_MAX + 1] = "";
55
+ if (read_file(temp, name, sizeof(name) - 1) != 0) {
56
+ return NULL;
57
+ }
58
+
59
+ char *trimmed = trim(name);
60
+ if (unlikely(trimmed == NULL || trimmed[0] == 0)) {
61
+ return NULL;
62
+ }
63
+
64
+ snprintfz(temp, FILENAME_MAX, "%s/%s", dirname, "max_energy_range_uj");
65
+ unsigned long long max_energy_range_uj = 0;
66
+ if (unlikely(read_single_number_file(temp, &max_energy_range_uj) != 0)) {
67
+ collector_error("Cannot read %s", temp);
68
+ return NULL;
69
+ }
70
+
71
+ snprintfz(temp, FILENAME_MAX, "%s/%s", dirname, "energy_uj");
72
+ struct measurement_t measurement;
73
+ if (unlikely(get_measurement(temp, &measurement) != 0)) {
74
+ collector_error("%s: Cannot read %s", trimmed, temp);
75
+ return NULL;
76
+ }
77
+
78
+ int parent_idx = -1;
79
+ if (parent != NULL) {
80
+ parent_idx = parent - *zones;
81
+ }
82
+
83
+ *zones = (struct zone_t *)reallocz(*zones, sizeof(struct zone_t) * (*count + 1));
84
+
85
+ struct zone_t *zone = &(*zones)[*count];
86
+ strcpy(zone->path, temp);
87
+ if (parent_idx >= 0) {
88
+ snprintfz(zone->name, FILENAME_MAX, "%s/%s", (*zones)[parent_idx].name, trimmed);
89
+ } else {
90
+ snprintfz(zone->name, FILENAME_MAX, "%s/%s", control_type, trimmed);
91
+ }
92
+ zone->max_energy_range_uj = max_energy_range_uj;
93
+ zone->measurement = measurement;
94
+
95
+ collector_info("Found zone: \"%s\"", zone->name);
96
+
97
+ ++(*count);
98
+
99
+ return zone;
100
+}
101
+
102
+static void
103
+look_for_zones(const char *control_type, struct zone_t *parent, const char *path, struct zone_t **zones, size_t *count)
104
+{
105
+ DIR *dir = opendir(path);
106
+ if (unlikely(dir == NULL)) {
107
+ return;
108
+ }
109
+
110
+ struct dirent *de = NULL;
111
+ while ((de = readdir(dir))) {
112
+ if (de->d_type != DT_DIR || de->d_name[0] == '.') {
113
+ continue;
114
+ }
115
+
116
+ char zone_path[FILENAME_MAX + 1];
117
+ snprintfz(zone_path, FILENAME_MAX, "%s/%s", path, de->d_name);
118
+
119
+ collector_info("Looking for zone in \"%s\"", zone_path);
120
+
121
+ struct zone_t *zone = get_zone(control_type, parent, zone_path, zones, count);
122
+ if (zone != NULL) {
123
+ look_for_zones(control_type, zone, zone_path, zones, count);
124
+ }
125
+ }
126
+
127
+ closedir(dir);
128
+}
129
+
130
+static int get_rapl_zones(struct zone_t **zones)
131
+{
132
+ *zones = NULL;
133
+
134
+ char dirname[FILENAME_MAX + 1];
135
+ snprintfz(dirname, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/virtual/powercap");
136
+
137
+ DIR *dir = opendir(dirname);
138
+ if (unlikely(dir == NULL)) {
139
+ return 0;
140
+ }
141
+
142
+ size_t count = 0;
143
+
144
+ struct dirent *de = NULL;
145
+ while ((de = readdir(dir))) {
146
+ if (de->d_type != DT_DIR || de->d_name[0] == '.') {
147
+ continue;
148
+ }
149
+
150
+ char control_type_path[FILENAME_MAX + 1];
151
+ snprintfz(control_type_path, FILENAME_MAX, "%s/%s", dirname, de->d_name);
152
+
153
+ collector_info("Looking at control type \"%s\"", de->d_name);
154
+ look_for_zones(de->d_name, NULL, control_type_path, zones, &count);
155
+ }
156
+ closedir(dir);
157
+
158
+ return count;
159
+}
160
+
161
+static void send_chart(int update_every, const char *name)
162
+{
163
+ fprintf(
164
+ stdout,
165
+ "CHART %s '' 'Power Consumption' 'Watts' 'power consumption' '' '%s' %d %d '%s' 'debugfs.plugin' ''\n",
166
+ METRIC_ID,
167
+ debugfs_rrdset_type_name(RRDSET_TYPE_LINE),
168
+ NETDATA_CHART_PRIO_POWERCAP,
169
+ update_every,
170
+ name);
171
+}
172
+
173
+static void send_dimension(const char *zone_name)
174
+{
175
+ fprintf(
176
+ stdout,
177
+ "DIMENSION '%s' '%s' %s 1 %d ''\n",
178
+ zone_name,
179
+ zone_name,
180
+ debugfs_rrd_algorithm_name(RRD_ALGORITHM_ABSOLUTE),
181
+ 1000);
182
+}
183
+
184
+static void send_begin()
185
+{
186
+ fprintf(stdout, "BEGIN %s\n", METRIC_ID);
187
+}
188
+
189
+static void send_set(const char *zone_name, collected_number value)
190
+{
191
+ fprintf(stdout, "SET %s = %lld\n", zone_name, value);
192
+}
193
+
194
+static void send_end_and_flush()
195
+{
196
+ fprintf(stdout, "END\n");
197
+ fflush(stdout);
198
+}
199
+
200
+int do_sys_devices_virtual_powercap(int update_every, const char *name)
201
+{
202
+ if (unlikely(g_zones_count <= 0)) {
203
+ g_zones_count = get_rapl_zones(&g_zones);
204
+ if (unlikely(g_zones_count < 0)) {
205
+ collector_error("Failed to find powercap zones.");
206
+ return 1;
207
+ } else if (unlikely(g_zones_count == 0)) {
208
+ collector_info("No powercap zones found.");
209
+ return 1;
210
+ }
211
+
212
+ send_chart(update_every, name);
213
+
214
+ for (int ii = 0; ii < g_zones_count; ++ii) {
215
+ send_dimension(g_zones[ii].name);
216
+ }
217
+
218
+ // This measurement is a derivative so start reporting on the next call
219
+ return 0;
220
+ }
221
+
222
+ send_begin();
223
+
224
+ for (int ii = 0; ii < g_zones_count; ++ii) {
225
+ struct measurement_t measurement;
226
+ struct zone_t *zone = &g_zones[ii];
227
+
228
+ if (unlikely(get_measurement(zone->path, &measurement) != 0)) {
229
+ collector_error("%s: Cannot read %s", zone->name, zone->path);
230
+ continue;
231
+ }
232
+ double watts = calculate_watts(zone->max_energy_range_uj, &zone->measurement, &measurement);
233
+ zone->measurement = measurement;
234
+
235
+ send_set(zone->name, (collected_number)(watts * 1000));
236
+ }
237
+
238
+ send_end_and_flush();
239
+
240
+ return 0;
241
+}
web/gui/dashboard_info.js
+8
@@ -1177,6 +1177,10 @@ netdataDashboard.submenu = {
1177
info: 'A connection is an established line of communication between a client and the PostgreSQL server. Each connection adds to the load on the PostgreSQL server. To guard against running out of memory or overloading the database the <i>max_connections</i> parameter (default = 100) defines the maximum number of concurrent connections to the database server. A separate parameter, <i>superuser_reserved_connections</i> (default = 3), defines the quota for superuser connections (so that superusers can connect even if all other connection slots are blocked).'
1178
},
1179
1180
+ 'system.power_consumption': {
1181
+ info: 'The current power consumption of the zones defined by the <a href="https://www.kernel.org/doc/html/next/power/powercap/powercap.html" target="_blank">power capping framework</a>.'
1182
+ }
1183
+
1184
};
1185
1186
// ----------------------------------------------------------------------------
@@ -1730,6 +1734,10 @@ netdataDashboard.context = {
1734
info: 'Difference between the number of calls to <a href="https://learn.netdata.cloud/docs/agent/collectors/ebpf.plugin#process-exit" target="_blank">functions</a> that close a task and release a task.'+ ebpfChartProvides
1735
},
1736
1737
+ 'system.power_consumption': {
1738
+ info: 'The current power consumption of the zones defined by the <a href="https://www.kernel.org/doc/html/next/power/powercap/powercap.html" target="_blank">power capping framework</a>.'
1739
+ },
1740
+
1741
// ------------------------------------------------------------------------
1742
// CPU charts
1743