| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "debugfs_plugin.h" |
| 4 | |
| 5 | struct zone_t { |
| 6 | char *zone_chart_id; |
| 7 | char *subzone_chart_id; |
| 8 | char *name; |
| 9 | char *path; |
| 10 | |
| 11 | unsigned long long max_energy_range_uj; |
| 12 | unsigned long long energy_uj; |
| 13 | |
| 14 | struct zone_t *subzones; |
| 15 | |
| 16 | struct zone_t *prev, *next; |
| 17 | }; |
| 18 | |
| 19 | static struct zone_t *rapl_zones = NULL; |
| 20 | |
| 21 | static bool get_measurement(const char *path, unsigned long long *energy_uj) { |
| 22 | return read_single_number_file(path, energy_uj) == 0; |
| 23 | } |
| 24 | |
| 25 | static struct zone_t *get_rapl_zone(const char *control_type __maybe_unused, struct zone_t *parent __maybe_unused, const char *dirname) { |
| 26 | char temp[FILENAME_MAX + 1]; |
| 27 | snprintfz(temp, FILENAME_MAX, "%s/%s", dirname, "name"); |
| 28 | |
| 29 | char name[FILENAME_MAX + 1] = ""; |
| 30 | if (read_txt_file(temp, name, sizeof(name)) != 0) |
| 31 | return NULL; |
| 32 | |
| 33 | char *trimmed = trim(name); |
| 34 | if (unlikely(trimmed == NULL || trimmed[0] == 0)) |
| 35 | return NULL; |
| 36 | |
| 37 | snprintfz(temp, FILENAME_MAX, "%s/%s", dirname, "max_energy_range_uj"); |
| 38 | unsigned long long max_energy_range_uj = 0; |
| 39 | if (unlikely(read_single_number_file(temp, &max_energy_range_uj) != 0)) { |
| 40 | collector_error("Cannot read %s", temp); |
| 41 | return NULL; |
| 42 | } |
| 43 | |
| 44 | snprintfz(temp, FILENAME_MAX, "%s/%s", dirname, "energy_uj"); |
| 45 | unsigned long long energy_uj; |
| 46 | if (unlikely(!get_measurement(temp, &energy_uj))) { |
| 47 | collector_info("%s: Cannot read %s", trimmed, temp); |
| 48 | return NULL; |
| 49 | } |
| 50 | |
| 51 | struct zone_t *zone = callocz(1, sizeof(*zone)); |
| 52 | |
| 53 | zone->name = strdupz(trimmed); |
| 54 | zone->path = strdupz(temp); |
| 55 | |
| 56 | zone->max_energy_range_uj = max_energy_range_uj; |
| 57 | zone->energy_uj = energy_uj; |
| 58 | |
| 59 | collector_info("Found zone: \"%s\"", zone->name); |
| 60 | |
| 61 | return zone; |
| 62 | } |
| 63 | |
| 64 | static struct zone_t *look_for_rapl_zones(const char *control_type, struct zone_t *parent, const char *path, int depth) { |
| 65 | if(depth > 2) |
| 66 | return NULL; |
| 67 | |
| 68 | struct zone_t *base = NULL; |
| 69 | |
| 70 | DIR *dir = opendir(path); |
| 71 | if (unlikely(dir == NULL)) |
| 72 | return NULL; |
| 73 | |
| 74 | struct dirent *de = NULL; |
| 75 | while ((de = readdir(dir))) { |
| 76 | if (de->d_type != DT_DIR || de->d_name[0] == '.') |
| 77 | continue; |
| 78 | |
| 79 | if(strncmp(de->d_name, "intel-rapl:", 11) != 0) |
| 80 | continue; |
| 81 | |
| 82 | char zone_path[FILENAME_MAX + 1]; |
| 83 | snprintfz(zone_path, FILENAME_MAX, "%s/%s", path, de->d_name); |
| 84 | |
| 85 | struct zone_t *zone = get_rapl_zone(control_type, parent, zone_path); |
| 86 | if(zone) { |
| 87 | DOUBLE_LINKED_LIST_APPEND_ITEM_UNSAFE(base, zone, prev, next); |
| 88 | |
| 89 | if(!parent) |
| 90 | zone->subzones = look_for_rapl_zones(control_type, zone, zone_path, depth + 1); |
| 91 | } |
| 92 | } |
| 93 | |
| 94 | closedir(dir); |
| 95 | return base; |
| 96 | } |
| 97 | |
| 98 | static struct zone_t *get_main_rapl_zones(void) { |
| 99 | struct zone_t *base = NULL; |
| 100 | |
| 101 | char dirname[FILENAME_MAX + 1]; |
| 102 | snprintfz(dirname, FILENAME_MAX, "%s%s", netdata_configured_host_prefix, "/sys/devices/virtual/powercap"); |
| 103 | |
| 104 | DIR *dir = opendir(dirname); |
| 105 | if (unlikely(dir == NULL)) |
| 106 | return 0; |
| 107 | |
| 108 | struct dirent *de = NULL; |
| 109 | while ((de = readdir(dir))) { |
| 110 | if (de->d_type != DT_DIR || de->d_name[0] == '.') |
| 111 | continue; |
| 112 | |
| 113 | if(strncmp(de->d_name, "intel-rapl", 10) != 0) |
| 114 | continue; |
| 115 | |
| 116 | char control_type_path[FILENAME_MAX + 1]; |
| 117 | snprintfz(control_type_path, FILENAME_MAX, "%s/%s", dirname, de->d_name); |
| 118 | |
| 119 | collector_info("Looking at control type \"%s\"", de->d_name); |
| 120 | struct zone_t *zone = look_for_rapl_zones(de->d_name, NULL, control_type_path, 0); |
| 121 | if(zone) |
| 122 | DOUBLE_LINKED_LIST_APPEND_LIST_UNSAFE(base, zone, prev, next); |
| 123 | } |
| 124 | closedir(dir); |
| 125 | |
| 126 | return base; |
| 127 | } |
| 128 | |
| 129 | int do_module_devices_powercap(int update_every, const char *name __maybe_unused) { |
| 130 | |
| 131 | if (unlikely(!rapl_zones)) { |
| 132 | rapl_zones = get_main_rapl_zones(); |
| 133 | if (unlikely(!rapl_zones)) { |
| 134 | collector_info("Failed to find powercap zones."); |
| 135 | return 1; |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | netdata_mutex_lock(&stdout_mutex); |
| 140 | |
| 141 | for(struct zone_t *zone = rapl_zones; zone ; zone = zone->next) { |
| 142 | if(!zone->zone_chart_id) { |
| 143 | char id[1000 + 1]; |
| 144 | snprintf(id, 1000, "cpu.powercap_intel_rapl_zone_%s", zone->name); |
| 145 | zone->zone_chart_id = strdupz(id); |
| 146 | |
| 147 | printf(PLUGINSD_KEYWORD_CHART " '%s' '' 'Intel RAPL Zone Power Consumption' 'Watts' 'powercap' '%s' '%s' %d %d '' 'debugfs.plugin' 'intel_rapl'\n", |
| 148 | zone->zone_chart_id, |
| 149 | "cpu.powercap_intel_rapl_zone", |
| 150 | debugfs_rrdset_type_name(RRDSET_TYPE_LINE), |
| 151 | NETDATA_CHART_PRIO_POWERCAP, |
| 152 | update_every); |
| 153 | |
| 154 | printf(PLUGINSD_KEYWORD_CLABEL " 'zone' '%s' 1\n" |
| 155 | PLUGINSD_KEYWORD_CLABEL_COMMIT "\n", |
| 156 | zone->name); |
| 157 | |
| 158 | printf(PLUGINSD_KEYWORD_DIMENSION " 'power' '' %s 1 1000000 ''\n", |
| 159 | debugfs_rrd_algorithm_name(RRD_ALGORITHM_INCREMENTAL)); |
| 160 | |
| 161 | // the sub-zones |
| 162 | snprintf(id, 1000, "cpu.powercap_intel_rapl_subzones_%s", zone->name); |
| 163 | zone->subzone_chart_id = strdupz(id); |
| 164 | printf(PLUGINSD_KEYWORD_CHART " '%s' '' 'Intel RAPL Subzones Power Consumption' 'Watts' 'powercap' '%s' '%s' %d %d '' 'debugfs.plugin' 'intel_rapl'\n", |
| 165 | zone->subzone_chart_id, |
| 166 | "cpu.powercap_intel_rapl_subzones", |
| 167 | debugfs_rrdset_type_name(RRDSET_TYPE_LINE), |
| 168 | NETDATA_CHART_PRIO_POWERCAP + 1, |
| 169 | update_every); |
| 170 | |
| 171 | printf(PLUGINSD_KEYWORD_CLABEL " 'zone' '%s' 1\n" |
| 172 | PLUGINSD_KEYWORD_CLABEL_COMMIT "\n", |
| 173 | zone->name); |
| 174 | |
| 175 | for(struct zone_t *subzone = zone->subzones; subzone ; subzone = subzone->next) { |
| 176 | printf(PLUGINSD_KEYWORD_DIMENSION " '%s' '' %s 1 1000000 ''\n", |
| 177 | subzone->name, |
| 178 | debugfs_rrd_algorithm_name(RRD_ALGORITHM_INCREMENTAL)); |
| 179 | } |
| 180 | } |
| 181 | |
| 182 | if(get_measurement(zone->path, &zone->energy_uj)) { |
| 183 | printf(PLUGINSD_KEYWORD_BEGIN " '%s'\n" |
| 184 | PLUGINSD_KEYWORD_SET " power = %llu\n" |
| 185 | PLUGINSD_KEYWORD_END "\n" |
| 186 | , zone->zone_chart_id |
| 187 | , zone->energy_uj); |
| 188 | } |
| 189 | |
| 190 | if(zone->subzones) { |
| 191 | printf(PLUGINSD_KEYWORD_BEGIN " '%s'\n", |
| 192 | zone->subzone_chart_id); |
| 193 | |
| 194 | for (struct zone_t *subzone = zone->subzones; subzone; subzone = subzone->next) { |
| 195 | if(get_measurement(subzone->path, &subzone->energy_uj)) { |
| 196 | printf(PLUGINSD_KEYWORD_SET " '%s' = %llu\n", |
| 197 | subzone->name, |
| 198 | subzone->energy_uj); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | printf(PLUGINSD_KEYWORD_END "\n"); |
| 203 | } |
| 204 | |
| 205 | } |
| 206 | |
| 207 | fflush(stdout); |
| 208 | netdata_mutex_unlock(&stdout_mutex); |
| 209 | |
| 210 | return 0; |
| 211 | } |