@cryptotaxi247 / netdata-1 / commits / 4c2efd402

limit the glibc unused memory (#19380)

* limit the glibc unused memory * call malloc_trim() more frequently * use mallinfo2 to provide glibc fragmentation statistics

Costa Tsaousis committed Jan 12, 2025 at 13:15 UTC 4c2efd40288c89b5edf58f5279d2c1b79597c597
7 files changed +93 -25
CMakeLists.txt
+8
@@ -492,6 +492,14 @@ int main() {
492 }
493 " HAVE_C_MALLOC_INFO)
494
495 +check_c_source_compiles("
496 +#include <malloc.h>
497 +int main() {
498 + struct mallinfo2 m = mallinfo2();
499 + return 0;
500 +}
501 +" HAVE_C_MALLINFO2)
502 +
503 check_c_source_compiles("
504 #define _GNU_SOURCE
505 #include <stdio.h>
packaging/cmake/config.cmake.h.in
+1
@@ -99,6 +99,7 @@
99 #cmakedefine HAVE_C_MALLOPT
100 #cmakedefine HAVE_C_MALLOC_TRIM
101 #cmakedefine HAVE_C_MALLOC_INFO
102 +#cmakedefine HAVE_C_MALLINFO2
103 #cmakedefine HAVE_SETNS
104 #cmakedefine HAVE_STRNDUP
105 #cmakedefine SSL_HAS_PENDING
src/daemon/config/netdata-conf-profile.c
+2 -2
@@ -118,8 +118,8 @@ void nd_profile_setup(void) {
118 else if(netdata_conf_is_parent()) {
119 nd_profile.storage_tiers = 3;
120 nd_profile.update_every = 1;
121 - nd_profile.malloc_arenas = os_get_system_cpus_cached(true);
122 - nd_profile.malloc_trim = 256 * 1024;
121 + nd_profile.malloc_arenas = 1;
122 + nd_profile.malloc_trim = 128 * 1024;
123 nd_profile.stream_sender_compression = ND_COMPRESSION_FASTEST;
124 // web server threads = dynamic
125 // aclk query threads = dynamic
src/daemon/pulse/pulse-daemon-memory.c
+60 -4
@@ -9,9 +9,11 @@
9
10 struct netdata_buffers_statistics netdata_buffers_statistics = { 0 };
11
12 -#ifdef HAVE_C_MALLOC_INFO
12 +#if defined(HAVE_C_MALLOC_INFO) || defined(HAVE_C_MALLINFO2)
13 #include <malloc.h>
14 +#endif
15
16 +#ifdef HAVE_C_MALLOC_INFO
17 // Helper function to find the last occurrence of a substring in a string
18 static char *find_last(const char *haystack, const char *needle, size_t *found) {
19 *found = 0;
@@ -410,7 +412,7 @@ void pulse_daemon_memory_do(bool extended) {
412 "Memory Usage",
413 NULL,
414 "Glibc Memory Arenas",
413 - "bytes",
415 + "arenas",
416 "netdata",
417 "pulse",
418 130104,
@@ -435,11 +437,11 @@ void pulse_daemon_memory_do(bool extended) {
437 if (unlikely(!st_malloc)) {
438 st_malloc = rrdset_create_localhost(
439 "netdata",
438 - "glibc_memory",
440 + "glibc_malloc_info",
441 NULL,
442 "Memory Usage",
443 NULL,
442 - "Glibc Memory Usage",
444 + "Glibc Malloc Info",
445 "bytes",
446 "netdata",
447 "pulse",
@@ -463,4 +465,58 @@ void pulse_daemon_memory_do(bool extended) {
465 }
466 #endif
467
468 +#ifdef HAVE_C_MALLINFO2
469 + {
470 + static RRDSET *st_mallinfo = NULL;
471 + static RRDDIM *rd_used_mmap = NULL;
472 + static RRDDIM *rd_used_arena = NULL;
473 + static RRDDIM *rd_unused_fragments = NULL;
474 + static RRDDIM *rd_unused_releasable = NULL;
475 +
476 + if (unlikely(!st_mallinfo)) {
477 + st_mallinfo = rrdset_create_localhost(
478 + "netdata",
479 + "glibc_mallinfo2",
480 + NULL,
481 + "Memory Usage",
482 + NULL,
483 + "Glibc Mallinfo2 Memory Distribution",
484 + "bytes",
485 + "netdata",
486 + "pulse",
487 + 130106,
488 + localhost->rrd_update_every,
489 + RRDSET_TYPE_STACKED);
490 +
491 + rd_unused_releasable = rrddim_add(st_mallinfo, "unused releasable", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
492 + rd_unused_fragments = rrddim_add(st_mallinfo, "unused fragments", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
493 + rd_used_arena = rrddim_add(st_mallinfo, "used arena", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
494 + rd_used_mmap = rrddim_add(st_mallinfo, "used mmap", NULL, 1, 1, RRD_ALGORITHM_ABSOLUTE);
495 + }
496 +
497 + struct mallinfo2 mi = mallinfo2();
498 +
499 + // Memory used in mmapped regions
500 + size_t used_mmap = mi.hblkhd;
501 +
502 + // Memory used in arena (non-mmapped allocations)
503 + // uordblks includes both arena and mmap allocations, so we subtract mmap
504 + size_t used_arena = (mi.uordblks > mi.hblkhd) ? mi.uordblks - mi.hblkhd : 0;
505 +
506 + // Releasable memory (can be released via malloc_trim())
507 + size_t unused_releasable = mi.keepcost;
508 +
509 + // Fragmentation (remaining free space that's not easily releasable)
510 + // This includes free chunks (fordblks) minus the releasable space
511 + size_t unused_fragments = (mi.fordblks > mi.keepcost) ? mi.fordblks - mi.keepcost : 0;
512 +
513 + rrddim_set_by_pointer(st_mallinfo, rd_unused_releasable, (collected_number)unused_releasable);
514 + rrddim_set_by_pointer(st_mallinfo, rd_unused_fragments, (collected_number)unused_fragments);
515 + rrddim_set_by_pointer(st_mallinfo, rd_used_arena, (collected_number)used_arena);
516 + rrddim_set_by_pointer(st_mallinfo, rd_used_mmap, (collected_number)used_mmap);
517 +
518 + rrdset_done(st_mallinfo);
519 + }
520 +#endif // HAVE_C_MALLINFO2
521 +
522 }
src/database/engine/cache.c
+12 -9
@@ -1982,14 +1982,16 @@ void free_all_unreferenced_clean_pages(PGC *cache) {
1982 }
1983
1984 static void *pgc_evict_thread(void *ptr) {
1985 + static usec_t last_malloc_release_ut = 0;
1986 +
1987 PGC *cache = ptr;
1988
1989 worker_register("PGCEVICT");
1990 worker_register_job_name(0, "signaled");
1991 worker_register_job_name(1, "scheduled");
1992 + worker_register_job_name(2, "cleanup");
1993
1994 unsigned job_id = 0;
1992 - usec_t last_malloc_release_ut = 0;
1995
1996 while (true) {
1997 worker_is_idle();
@@ -2002,18 +2004,19 @@ static void *pgc_evict_thread(void *ptr) {
2004 if (nd_thread_signaled_to_cancel())
2005 break;
2006
2005 - evict_pages(cache, 0, 0, true, false);
2006 -
2007 size_t size_to_evict = 0;
2008 - if(cache_usage_per1000(cache, &size_to_evict) > cache->config.severe_pressure_per1000) {
2009 - usec_t now_ut = now_monotonic_usec();
2008 + bool system_cleanup = false;
2009 + if(cache_usage_per1000(cache, &size_to_evict) > cache->config.aggressive_evict_per1000)
2010 + system_cleanup = true;
2011
2011 - if(last_malloc_release_ut + USEC_PER_SEC < now_ut) {
2012 - last_malloc_release_ut = now_ut;
2012 + evict_pages(cache, 0, 0, true, false);
2013
2014 - // so, we tried 100 times to reduce memory, and a second has passed,
2015 - // but it is still severe!
2014 + if(system_cleanup) {
2015 + usec_t now_ut = now_monotonic_usec();
2016
2017 + if(__atomic_load_n(&last_malloc_release_ut, __ATOMIC_RELAXED) + USEC_PER_SEC <= now_ut) {
2018 + __atomic_store_n(&last_malloc_release_ut, now_ut, __ATOMIC_RELAXED);
2019 + worker_is_busy(2);
2020 mallocz_release_as_much_memory_to_the_system();
2021 }
2022 }
src/database/engine/page.c
+6 -5
@@ -101,11 +101,12 @@ static size_t aral_sizes[] = {
101 sizeof(PGD),
102
103 512, 1024, 1536, 2048, 5 * 512, 6 * 512, 7 * 512,
104 - 1 * 4096, 2 * 4096, 3 * 4096, 4 * 4096, 5 * 4096,
105 - 6 * 4096, 7 * 4096, 8 * 4096, 9 * 4096, 10 * 4096,
106 - 11 * 4096, 12 * 4096, 13 * 4096, 14 * 4096, 15 * 4096,
107 - 16 * 4096, 17 * 4096, 18 * 4096, 19 * 4096, 20 * 4096,
108 - 21 * 4096, 22 * 4096, 23 * 4096, 24 * 4096, 25 * 4096,
104 + 4 * 1024, 8 * 1024, 12 * 1024, 16 * 1024, 20 * 1024,
105 + 24 * 1024, 28 * 1024, 32 * 1024, 36 * 1024, 40 * 1024,
106 + 44 * 1024, 48 * 1024, 52 * 1024, 56 * 1024, 60 * 1024,
107 + 64 * 1024, 68 * 1024, 72 * 1024, 76 * 1024, 80 * 1024,
108 + 84 * 1024, 88 * 1024, 92 * 1024, 96 * 1024, 100 * 1024,
109 + 104 * 1024, 108 * 1024, 112 * 1024, 116 * 1024, 120 * 1024,
110 };
111 static ARAL **arals = NULL;
112
src/libnetdata/libnetdata.c
+4 -5
@@ -466,11 +466,10 @@ void posix_memfree(void *ptr) {
466 void mallocz_release_as_much_memory_to_the_system(void) {
467 #if defined(HAVE_C_MALLOC_TRIM)
468 static SPINLOCK spinlock = SPINLOCK_INITIALIZER;
469 - spinlock_lock(&spinlock);
470 -
471 - malloc_trim(0);
472 -
473 - spinlock_unlock(&spinlock);
469 + if(spinlock_trylock(&spinlock)) {
470 + malloc_trim(0);
471 + spinlock_unlock(&spinlock);
472 + }
473 #endif
474 }
475