Skip to content

Commit

Permalink
added mmap count charts (netdata#19384)
Browse files Browse the repository at this point in the history
* added mmap count charts

* buffers should not be extended

* read max_map_count once per minute
  • Loading branch information
ktsaou authored Jan 12, 2025
1 parent 3b3e821 commit 21a545a
Show file tree
Hide file tree
Showing 5 changed files with 311 additions and 234 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1152,6 +1152,7 @@ set(DAEMON_FILES
src/daemon/config/netdata-conf-cloud.h
src/daemon/config/netdata-conf-profile.c
src/daemon/config/netdata-conf-profile.h
src/daemon/pulse/pulse-daemon-memory-system.c
)

set(H2O_FILES
Expand Down
300 changes: 300 additions & 0 deletions src/daemon/pulse/pulse-daemon-memory-system.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
// SPDX-License-Identifier: GPL-3.0-or-later

#define PULSE_INTERNALS 1
#include "pulse-daemon-memory.h"
#include "streaming/stream-replication-sender.h"

#if defined(HAVE_C_MALLOC_INFO) || defined(HAVE_C_MALLINFO2)
#include <malloc.h>
#endif

#ifdef HAVE_C_MALLOC_INFO
// Helper function to find the last occurrence of a substring in a string
static char *find_last(const char *haystack, const char *needle, size_t *found) {
*found = 0;

char *last = NULL;
char *current = strstr(haystack, needle);
while (current) {
(*found)++;
last = current;
current = strstr(current + 1, needle);
}
return last;
}

static bool parse_malloc_info(size_t *arenas, size_t *allocated_arena, size_t *unused_fast, size_t *unused_rest, size_t *allocated_mmap) {
int found = 0;

*arenas = 0;
*allocated_arena = 0;
*unused_fast = 0;
*unused_rest = 0;
*allocated_mmap = 0;

// Buffer for malloc_info XML
char *buffer = NULL;
size_t size = 0;
FILE *meminfo = open_memstream(&buffer, &size);
if (!meminfo)
goto cleanup;

char *t = malloc(1024);

// Generate malloc_info XML
if(malloc_info(0, meminfo) != 0)
goto cleanup;

fflush(meminfo);
fclose(meminfo);
meminfo = NULL;

free(t);

if(!size || !buffer)
goto cleanup;

// make sure it is terminated
buffer[size - 1] = '\0';

// Find the last </heap>
char *last_heap_end = find_last(buffer, "</heap>", arenas);
if (!last_heap_end)
goto cleanup;

// Move past the last </heap>
char *summary_section = last_heap_end + strlen("</heap>");

// Parse the summary section using strstr
const char *fast_key = "<total type=\"fast\"";
const char *rest_key = "<total type=\"rest\"";
const char *mmap_key = "<total type=\"mmap\"";
const char *system_key = "<system type=\"current\"";
char *size_pos;

char *fast_pos = strstr(summary_section, fast_key);
if(!fast_pos || !(size_pos = strstr(fast_pos, "size=\"")))
goto cleanup;

*unused_fast = strtoull(size_pos + 6, NULL, 10);
found++;

char *rest_pos = strstr(summary_section, rest_key);
if (!rest_pos || !(size_pos = strstr(rest_pos, "size=\"")))
goto cleanup;

*unused_rest = strtoull(size_pos + 6, NULL, 10);
found++;

char *mmap_pos = strstr(summary_section, mmap_key);
if (!mmap_pos || !(size_pos = strstr(mmap_pos, "size=\"")))
goto cleanup;

*allocated_mmap = strtoull(size_pos + 6, NULL, 10);
found++;

char *system_pos = strstr(summary_section, system_key);
if (!system_pos || !(size_pos = strstr(system_pos, "size=\"")))
goto cleanup;

*allocated_arena = strtoull(size_pos + 6, NULL, 10);
found++;

cleanup:
if(meminfo) fclose(meminfo);
if(buffer) free(buffer);
return found == 4;
}
#endif // HAVE_C_MALLOC_INFO

void pulse_daemon_memory_system_do(bool extended) {

#ifdef HAVE_C_MALLOC_INFO
size_t glibc_arenas, glibc_allocated_arenas, glibc_unused_fast, glibc_unused_rest, glibc_allocated_mmap;
if(extended && parse_malloc_info(&glibc_arenas, &glibc_allocated_arenas, &glibc_unused_fast, &glibc_unused_rest, &glibc_allocated_mmap)) {
if (glibc_arenas) {
static RRDSET *st_arenas = NULL;
static RRDDIM *rd_arenas = NULL;

if (unlikely(!st_arenas)) {
st_arenas = rrdset_create_localhost(
"netdata",
"glibc_arenas",
NULL,
"Memory Usage",
NULL,
"Glibc Memory Arenas",
"arenas",
"netdata",
"pulse",
130120,
localhost->rrd_update_every,
RRDSET_TYPE_LINE);

rd_arenas = rrddim_add(st_arenas, "arenas", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
}

rrddim_set_by_pointer(st_arenas, rd_arenas, (collected_number)glibc_arenas);
rrdset_done(st_arenas);
}

if (glibc_allocated_arenas || glibc_allocated_mmap) {
static RRDSET *st_malloc = NULL;
static RRDDIM *rd_unused_fast = NULL;
static RRDDIM *rd_unused_rest = NULL;
static RRDDIM *rd_used_arena = NULL;
static RRDDIM *rd_used_mmap = NULL;

if (unlikely(!st_malloc)) {
st_malloc = rrdset_create_localhost(
"netdata",
"glibc_malloc_info",
NULL,
"Memory Usage",
NULL,
"Glibc Malloc Info",
"bytes",
"netdata",
"pulse",
130121,
localhost->rrd_update_every,
RRDSET_TYPE_STACKED);

rd_unused_fast = rrddim_add(st_malloc, "unused fast", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_unused_rest = rrddim_add(st_malloc, "unused rest", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_used_arena = rrddim_add(st_malloc, "used arena", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_used_mmap = rrddim_add(st_malloc, "used mmap", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
}

size_t unused = glibc_unused_fast + glibc_unused_rest;
size_t used_arena = (glibc_allocated_arenas > unused) ? glibc_allocated_arenas - unused : 0;

rrddim_set_by_pointer(st_malloc, rd_unused_fast, (collected_number)glibc_unused_fast);
rrddim_set_by_pointer(st_malloc, rd_unused_rest, (collected_number)glibc_unused_rest);
rrddim_set_by_pointer(st_malloc, rd_used_arena, (collected_number)used_arena);
rrddim_set_by_pointer(st_malloc, rd_used_mmap, (collected_number)glibc_allocated_mmap);
rrdset_done(st_malloc);
}
}
#endif

size_t glibc_mmaps = 0;

#ifdef HAVE_C_MALLINFO2
struct mallinfo2 mi = mallinfo2();
glibc_mmaps = mi.hblks;
if(extended && (mi.hblkhd || mi.fordblks)) {
static RRDSET *st_mallinfo = NULL;
static RRDDIM *rd_used_mmap = NULL;
static RRDDIM *rd_used_arena = NULL;
static RRDDIM *rd_unused_fragments = NULL;
static RRDDIM *rd_unused_releasable = NULL;

if (unlikely(!st_mallinfo)) {
st_mallinfo = rrdset_create_localhost(
"netdata",
"glibc_mallinfo2",
NULL,
"Memory Usage",
NULL,
"Glibc Mallinfo2 Memory Distribution",
"bytes",
"netdata",
"pulse",
130130,
localhost->rrd_update_every,
RRDSET_TYPE_STACKED);

rd_unused_releasable = rrddim_add(st_mallinfo, "unused releasable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_unused_fragments = rrddim_add(st_mallinfo, "unused fragments", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_used_arena = rrddim_add(st_mallinfo, "used arena", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_used_mmap = rrddim_add(st_mallinfo, "used mmap", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
}

// size_t total = mi.uordblks;
size_t used_mmap = mi.hblkhd;
size_t used_arena = (mi.arena > mi.fordblks) ? mi.arena - mi.fordblks : 0;

size_t unused_total = mi.fordblks;
size_t unused_releasable = mi.keepcost;
// size_t unused_fast = mi.fsmblks;
size_t unused_fragments = (unused_total > unused_releasable) ? unused_total - unused_releasable : 0;

rrddim_set_by_pointer(st_mallinfo, rd_unused_releasable, (collected_number)unused_releasable);
rrddim_set_by_pointer(st_mallinfo, rd_unused_fragments, (collected_number)unused_fragments);
rrddim_set_by_pointer(st_mallinfo, rd_used_arena, (collected_number)used_arena);
rrddim_set_by_pointer(st_mallinfo, rd_used_mmap, (collected_number)used_mmap);

rrdset_done(st_mallinfo);
}
#endif // HAVE_C_MALLINFO2

size_t netdata_mmaps = __atomic_load_n(&netdata_mmap_count, __ATOMIC_RELAXED);
size_t total_mmaps = netdata_mmaps + glibc_mmaps;
{
static RRDSET *st_maps = NULL;
static RRDDIM *rd_netdata = NULL;
static RRDDIM *rd_glibc = NULL;

if (unlikely(!st_maps)) {
st_maps = rrdset_create_localhost(
"netdata",
"memory_maps",
NULL,
"Memory Usage",
NULL,
"Netdata Memory Maps",
"maps",
"netdata",
"pulse",
130105,
localhost->rrd_update_every,
RRDSET_TYPE_LINE);

rd_netdata = rrddim_add(st_maps, "netdata", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
rd_glibc = rrddim_add(st_maps, "glibc", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
}

rrddim_set_by_pointer(st_maps, rd_glibc, (collected_number)netdata_mmaps);
rrddim_set_by_pointer(st_maps, rd_netdata, (collected_number)glibc_mmaps);

rrdset_done(st_maps);
}

static unsigned long long max_map_count = 0;
static usec_t last_read_ut = 0;
usec_t now_ut = now_monotonic_usec();
if(now_ut - last_read_ut >= 60 * USEC_PER_SEC) {
// read this file once per minute
read_single_number_file("/proc/sys/vm/max_map_count", &max_map_count);
last_read_ut = now_ut;
}

if (max_map_count && total_mmaps) {
static RRDSET *st_maps_percent = NULL;
static RRDDIM *rd_used = NULL;

if (unlikely(!st_maps_percent)) {
st_maps_percent = rrdset_create_localhost(
"netdata",
"memory_maps_limit",
NULL,
"Memory Usage",
NULL,
"Netdata Memory Maps Limit",
"%",
"netdata",
"pulse",
130106,
localhost->rrd_update_every,
RRDSET_TYPE_AREA);

rd_used = rrddim_add(st_maps_percent, "used", NULL, 1, 1000, RRD_ALGORITHM_ABSOLUTE);
}

double percent = (double)total_mmaps * 100.0 / (double)max_map_count;

rrddim_set_by_pointer(st_maps_percent, rd_used, (collected_number)round(percent * 1000.0));
rrdset_done(st_maps_percent);
}
}
Loading

0 comments on commit 21a545a

Please sign in to comment.