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