| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #define PULSE_INTERNALS 1 |
| 4 | #include "pulse-daemon-memory.h" |
| 5 | #include "streaming/stream-replication-sender.h" |
| 6 | |
| 7 | #if defined(HAVE_C_MALLOC_INFO) || defined(HAVE_C_MALLINFO2) |
| 8 | #include <malloc.h> |
| 9 | #endif |
| 10 | |
| 11 | #ifdef HAVE_C_MALLOC_INFO |
| 12 | // Helper function to find the last occurrence of a substring in a string |
| 13 | static char *find_last(const char *haystack, const char *needle, size_t *found) { |
| 14 | *found = 0; |
| 15 | |
| 16 | char *last = NULL; |
| 17 | char *current = strstr(haystack, needle); |
| 18 | while (current) { |
| 19 | (*found)++; |
| 20 | last = current; |
| 21 | current = strstr(current + 1, needle); |
| 22 | } |
| 23 | return last; |
| 24 | } |
| 25 | |
| 26 | static bool parse_malloc_info(size_t *arenas, size_t *allocated_arena, size_t *unused_fast, size_t *unused_rest, size_t *allocated_mmap) { |
| 27 | int found = 0; |
| 28 | |
| 29 | *arenas = 0; |
| 30 | *allocated_arena = 0; |
| 31 | *unused_fast = 0; |
| 32 | *unused_rest = 0; |
| 33 | *allocated_mmap = 0; |
| 34 | |
| 35 | // Buffer for malloc_info XML |
| 36 | char *buffer = NULL; |
| 37 | size_t size = 0; |
| 38 | FILE *meminfo = open_memstream(&buffer, &size); |
| 39 | if (!meminfo) |
| 40 | goto cleanup; |
| 41 | |
| 42 | char *t = malloc(1024); |
| 43 | |
| 44 | // Generate malloc_info XML |
| 45 | if(malloc_info(0, meminfo) != 0) { |
| 46 | free(t); |
| 47 | goto cleanup; |
| 48 | } |
| 49 | |
| 50 | fflush(meminfo); |
| 51 | fclose(meminfo); |
| 52 | meminfo = NULL; |
| 53 | |
| 54 | free(t); |
| 55 | |
| 56 | if(!size || !buffer) |
| 57 | goto cleanup; |
| 58 | |
| 59 | // make sure it is terminated |
| 60 | buffer[size - 1] = '\0'; |
| 61 | |
| 62 | // Find the last </heap> |
| 63 | char *last_heap_end = find_last(buffer, "</heap>", arenas); |
| 64 | if (!last_heap_end) |
| 65 | goto cleanup; |
| 66 | |
| 67 | // Move past the last </heap> |
| 68 | char *summary_section = last_heap_end + strlen("</heap>"); |
| 69 | |
| 70 | // Parse the summary section using strstr |
| 71 | const char *fast_key = "<total type=\"fast\""; |
| 72 | const char *rest_key = "<total type=\"rest\""; |
| 73 | const char *mmap_key = "<total type=\"mmap\""; |
| 74 | const char *system_key = "<system type=\"current\""; |
| 75 | char *size_pos; |
| 76 | |
| 77 | char *fast_pos = strstr(summary_section, fast_key); |
| 78 | if(!fast_pos || !(size_pos = strstr(fast_pos, "size=\""))) |
| 79 | goto cleanup; |
| 80 | |
| 81 | *unused_fast = strtoull(size_pos + 6, NULL, 10); |
| 82 | found++; |
| 83 | |
| 84 | char *rest_pos = strstr(summary_section, rest_key); |
| 85 | if (!rest_pos || !(size_pos = strstr(rest_pos, "size=\""))) |
| 86 | goto cleanup; |
| 87 | |
| 88 | *unused_rest = strtoull(size_pos + 6, NULL, 10); |
| 89 | found++; |
| 90 | |
| 91 | char *mmap_pos = strstr(summary_section, mmap_key); |
| 92 | if (!mmap_pos || !(size_pos = strstr(mmap_pos, "size=\""))) |
| 93 | goto cleanup; |
| 94 | |
| 95 | *allocated_mmap = strtoull(size_pos + 6, NULL, 10); |
| 96 | found++; |
| 97 | |
| 98 | char *system_pos = strstr(summary_section, system_key); |
| 99 | if (!system_pos || !(size_pos = strstr(system_pos, "size=\""))) |
| 100 | goto cleanup; |
| 101 | |
| 102 | *allocated_arena = strtoull(size_pos + 6, NULL, 10); |
| 103 | found++; |
| 104 | |
| 105 | cleanup: |
| 106 | if(meminfo) fclose(meminfo); |
| 107 | if(buffer) free(buffer); |
| 108 | return found == 4; |
| 109 | } |
| 110 | #endif // HAVE_C_MALLOC_INFO |
| 111 | |
| 112 | void pulse_daemon_memory_system_do(bool extended) { |
| 113 | if(!extended) return; |
| 114 | |
| 115 | size_t glibc_mmaps = 0; |
| 116 | bool have_mallinfo = false; (void)have_mallinfo; |
| 117 | |
| 118 | #ifdef HAVE_C_MALLINFO2 |
| 119 | struct mallinfo2 mi = mallinfo2(); |
| 120 | glibc_mmaps = mi.hblks; |
| 121 | if(mi.hblkhd || mi.fordblks) { |
| 122 | static RRDSET *st_mallinfo = NULL; |
| 123 | static RRDDIM *rd_used_mmap = NULL; |
| 124 | static RRDDIM *rd_used_arena = NULL; |
| 125 | static RRDDIM *rd_unused_fragments = NULL; |
| 126 | static RRDDIM *rd_unused_releasable = NULL; |
| 127 | |
| 128 | if (unlikely(!st_mallinfo)) { |
| 129 | st_mallinfo = rrdset_create_localhost( |
| 130 | "netdata", |
| 131 | "glibc_mallinfo2", |
| 132 | NULL, |
| 133 | "Memory Usage", |
| 134 | NULL, |
| 135 | "Glibc Mallinfo2 Memory Distribution", |
| 136 | "bytes", |
| 137 | "netdata", |
| 138 | "pulse", |
| 139 | 130130, |
| 140 | localhost->rrd_update_every, |
| 141 | RRDSET_TYPE_STACKED); |
| 142 | |
| 143 | rd_unused_releasable = rrddim_add(st_mallinfo, "unused releasable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 144 | rd_unused_fragments = rrddim_add(st_mallinfo, "unused fragments", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 145 | rd_used_arena = rrddim_add(st_mallinfo, "used arena", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 146 | rd_used_mmap = rrddim_add(st_mallinfo, "used mmap", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 147 | } |
| 148 | |
| 149 | // size_t total = mi.uordblks; |
| 150 | size_t used_mmap = mi.hblkhd; |
| 151 | size_t used_arena = (mi.arena > mi.fordblks) ? mi.arena - mi.fordblks : 0; |
| 152 | |
| 153 | size_t unused_total = mi.fordblks; |
| 154 | size_t unused_releasable = mi.keepcost; |
| 155 | // size_t unused_fast = mi.fsmblks; |
| 156 | size_t unused_fragments = (unused_total > unused_releasable) ? unused_total - unused_releasable : 0; |
| 157 | |
| 158 | rrddim_set_by_pointer(st_mallinfo, rd_unused_releasable, (collected_number)unused_releasable); |
| 159 | rrddim_set_by_pointer(st_mallinfo, rd_unused_fragments, (collected_number)unused_fragments); |
| 160 | rrddim_set_by_pointer(st_mallinfo, rd_used_arena, (collected_number)used_arena); |
| 161 | rrddim_set_by_pointer(st_mallinfo, rd_used_mmap, (collected_number)used_mmap); |
| 162 | |
| 163 | rrdset_done(st_mallinfo); |
| 164 | have_mallinfo = true; |
| 165 | } |
| 166 | #endif // HAVE_C_MALLINFO2 |
| 167 | |
| 168 | #ifdef HAVE_C_MALLOC_INFO |
| 169 | size_t glibc_arenas, glibc_allocated_arenas, glibc_unused_fast, glibc_unused_rest, glibc_allocated_mmap; |
| 170 | if(!have_mallinfo && parse_malloc_info(&glibc_arenas, &glibc_allocated_arenas, &glibc_unused_fast, &glibc_unused_rest, &glibc_allocated_mmap)) { |
| 171 | if (glibc_arenas) { |
| 172 | static RRDSET *st_arenas = NULL; |
| 173 | static RRDDIM *rd_arenas = NULL; |
| 174 | |
| 175 | if (unlikely(!st_arenas)) { |
| 176 | st_arenas = rrdset_create_localhost( |
| 177 | "netdata", |
| 178 | "glibc_arenas", |
| 179 | NULL, |
| 180 | "Memory Usage", |
| 181 | NULL, |
| 182 | "Glibc Memory Arenas", |
| 183 | "arenas", |
| 184 | "netdata", |
| 185 | "pulse", |
| 186 | 130120, |
| 187 | localhost->rrd_update_every, |
| 188 | RRDSET_TYPE_LINE); |
| 189 | |
| 190 | rd_arenas = rrddim_add(st_arenas, "arenas", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 191 | } |
| 192 | |
| 193 | rrddim_set_by_pointer(st_arenas, rd_arenas, (collected_number)glibc_arenas); |
| 194 | rrdset_done(st_arenas); |
| 195 | } |
| 196 | |
| 197 | if (glibc_allocated_arenas || glibc_allocated_mmap) { |
| 198 | static RRDSET *st_malloc = NULL; |
| 199 | static RRDDIM *rd_unused_fast = NULL; |
| 200 | static RRDDIM *rd_unused_rest = NULL; |
| 201 | static RRDDIM *rd_used_arena = NULL; |
| 202 | static RRDDIM *rd_used_mmap = NULL; |
| 203 | |
| 204 | if (unlikely(!st_malloc)) { |
| 205 | st_malloc = rrdset_create_localhost( |
| 206 | "netdata", |
| 207 | "glibc_malloc_info", |
| 208 | NULL, |
| 209 | "Memory Usage", |
| 210 | NULL, |
| 211 | "Glibc Malloc Info", |
| 212 | "bytes", |
| 213 | "netdata", |
| 214 | "pulse", |
| 215 | 130121, |
| 216 | localhost->rrd_update_every, |
| 217 | RRDSET_TYPE_STACKED); |
| 218 | |
| 219 | rd_unused_fast = rrddim_add(st_malloc, "unused fast", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 220 | rd_unused_rest = rrddim_add(st_malloc, "unused rest", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 221 | rd_used_arena = rrddim_add(st_malloc, "used arena", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 222 | rd_used_mmap = rrddim_add(st_malloc, "used mmap", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 223 | } |
| 224 | |
| 225 | size_t unused = glibc_unused_fast + glibc_unused_rest; |
| 226 | size_t used_arena = (glibc_allocated_arenas > unused) ? glibc_allocated_arenas - unused : 0; |
| 227 | |
| 228 | rrddim_set_by_pointer(st_malloc, rd_unused_fast, (collected_number)glibc_unused_fast); |
| 229 | rrddim_set_by_pointer(st_malloc, rd_unused_rest, (collected_number)glibc_unused_rest); |
| 230 | rrddim_set_by_pointer(st_malloc, rd_used_arena, (collected_number)used_arena); |
| 231 | rrddim_set_by_pointer(st_malloc, rd_used_mmap, (collected_number)glibc_allocated_mmap); |
| 232 | rrdset_done(st_malloc); |
| 233 | } |
| 234 | } |
| 235 | #endif |
| 236 | |
| 237 | size_t netdata_mmaps = __atomic_load_n(&nd_mmap_count, __ATOMIC_RELAXED); |
| 238 | size_t total_mmaps = netdata_mmaps + glibc_mmaps; |
| 239 | { |
| 240 | static RRDSET *st_maps = NULL; |
| 241 | static RRDDIM *rd_netdata = NULL; |
| 242 | static RRDDIM *rd_glibc = NULL; |
| 243 | |
| 244 | if (unlikely(!st_maps)) { |
| 245 | st_maps = rrdset_create_localhost( |
| 246 | "netdata", |
| 247 | "memory_maps", |
| 248 | NULL, |
| 249 | "Memory Usage", |
| 250 | NULL, |
| 251 | "Netdata Memory Maps", |
| 252 | "maps", |
| 253 | "netdata", |
| 254 | "pulse", |
| 255 | 130105, |
| 256 | localhost->rrd_update_every, |
| 257 | RRDSET_TYPE_LINE); |
| 258 | |
| 259 | rd_netdata = rrddim_add(st_maps, "netdata", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 260 | rd_glibc = rrddim_add(st_maps, "glibc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE); |
| 261 | } |
| 262 | |
| 263 | rrddim_set_by_pointer(st_maps, rd_glibc, (collected_number)glibc_mmaps); |
| 264 | rrddim_set_by_pointer(st_maps, rd_netdata, (collected_number)netdata_mmaps); |
| 265 | |
| 266 | rrdset_done(st_maps); |
| 267 | } |
| 268 | |
| 269 | static unsigned long long max_map_count = 0; |
| 270 | static usec_t last_read_ut = 0; |
| 271 | usec_t now_ut = now_monotonic_usec(); |
| 272 | if(now_ut - last_read_ut >= 60 * USEC_PER_SEC) { |
| 273 | // read this file once per minute |
| 274 | read_single_number_file("/proc/sys/vm/max_map_count", &max_map_count); |
| 275 | last_read_ut = now_ut; |
| 276 | } |
| 277 | |
| 278 | if (max_map_count && total_mmaps) { |
| 279 | static RRDSET *st_maps_percent = NULL; |
| 280 | static RRDDIM *rd_used = NULL; |
| 281 | |
| 282 | if (unlikely(!st_maps_percent)) { |
| 283 | st_maps_percent = rrdset_create_localhost( |
| 284 | "netdata", |
| 285 | "memory_maps_limit", |
| 286 | NULL, |
| 287 | "Memory Usage", |
| 288 | NULL, |
| 289 | "Netdata Memory Maps Limit", |
| 290 | "%", |
| 291 | "netdata", |
| 292 | "pulse", |
| 293 | 130106, |
| 294 | localhost->rrd_update_every, |
| 295 | RRDSET_TYPE_AREA); |
| 296 | |
| 297 | rd_used = rrddim_add(st_maps_percent, "used", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE); |
| 298 | } |
| 299 | |
| 300 | double percent = (double)total_mmaps * 100.0 / (double)max_map_count; |
| 301 | |
| 302 | rrddim_set_by_pointer(st_maps_percent, rd_used, (collected_number)round(percent * 1000.0)); |
| 303 | rrdset_done(st_maps_percent); |
| 304 | } |
| 305 | } |