2 * Copyright (C) 1996-2022 The Squid Software Foundation and contributors
4 * Squid software is distributed under GPLv2+ license and includes
5 * contributions from numerous individuals and organizations.
6 * Please see the COPYING and CONTRIBUTORS files for details.
9 /* DEBUG: section 13 High Level Memory Pool Management */
12 #include "base/PackableStream.h"
13 #include "ClientInfo.h"
17 #include "icmp/net_db.h"
19 #include "mem/forward.h"
20 #include "mem/Meter.h"
23 #include "mgr/Registration.h"
24 #include "SquidConfig.h"
29 /* forward declarations */
30 static void memFree2K(void *);
31 static void memFree4K(void *);
32 static void memFree8K(void *);
33 static void memFree16K(void *);
34 static void memFree32K(void *);
35 static void memFree64K(void *);
37 /* local prototypes */
38 static void memStringStats(std::ostream
&);
41 static double xm_time
= 0;
42 static double xm_deltat
= 0;
45 #define mem_str_pool_count 6
52 static Mem::Meter StrCountMeter
;
53 static Mem::Meter StrVolumeMeter
;
55 static Mem::Meter HugeBufCountMeter
;
56 static Mem::Meter HugeBufVolumeMeter
;
60 // XXX: refactor objects using these pools to use MEMPROXY classes instead
61 // then remove this function entirely
62 static MemAllocator
*&
65 static MemAllocator
*pools
[MEM_MAX
];
66 static bool initialized
= false;
69 memset(pools
, '\0', sizeof(pools
));
71 // Mem::Init() makes use of GetPool(type) to initialize
72 // the actual pools. So must come after the flag is true
80 GetStrPool(size_t type
)
82 static MemAllocator
*strPools
[mem_str_pool_count
];
83 static bool initialized
= false;
85 static const PoolMeta PoolAttrs
[mem_str_pool_count
] = {
86 {"Short Strings", MemAllocator::RoundedSize(36)}, /* to fit rfc1123 and similar */
87 {"Medium Strings", MemAllocator::RoundedSize(128)}, /* to fit most urls */
88 {"Long Strings", MemAllocator::RoundedSize(512)},
89 {"1KB Strings", MemAllocator::RoundedSize(1024)},
90 {"4KB Strings", MemAllocator::RoundedSize(4*1024)},
91 {"16KB Strings", MemAllocator::RoundedSize(16*1024)}
95 memset(strPools
, '\0', sizeof(strPools
));
97 /** Lastly init the string pools. */
98 for (int i
= 0; i
< mem_str_pool_count
; ++i
) {
99 strPools
[i
] = memPoolCreate(PoolAttrs
[i
].name
, PoolAttrs
[i
].obj_size
);
100 strPools
[i
]->zeroBlocks(false);
102 if (strPools
[i
]->objectSize() != PoolAttrs
[i
].obj_size
)
103 debugs(13, DBG_IMPORTANT
, "WARNING: " << PoolAttrs
[i
].name
<<
104 " is " << strPools
[i
]->objectSize() <<
105 " bytes instead of requested " <<
106 PoolAttrs
[i
].obj_size
<< " bytes");
112 return *strPools
[type
];
115 /// \returns the best-fit string pool or nil
116 static MemAllocator
*
117 memFindStringPool(size_t net_size
, bool fuzzy
)
119 for (unsigned int i
= 0; i
< mem_str_pool_count
; ++i
) {
120 auto &pool
= GetStrPool(i
);
121 if (fuzzy
&& net_size
< pool
.objectSize())
123 if (net_size
== pool
.objectSize())
130 memStringStats(std::ostream
&stream
)
133 int pooled_count
= 0;
134 size_t pooled_volume
= 0;
136 stream
<< "String Pool\t Impact\t\t\n \t (%strings)\t (%volume)\n";
139 for (i
= 0; i
< mem_str_pool_count
; ++i
) {
140 const auto &pool
= GetStrPool(i
);
141 const auto plevel
= pool
.getMeter().inuse
.currentLevel();
142 stream
<< std::setw(20) << std::left
<< pool
.objectType();
143 stream
<< std::right
<< "\t " << xpercentInt(plevel
, StrCountMeter
.currentLevel());
144 stream
<< "\t " << xpercentInt(plevel
* pool
.objectSize(), StrVolumeMeter
.currentLevel()) << "\n";
145 pooled_count
+= plevel
;
146 pooled_volume
+= plevel
* pool
.objectSize();
150 stream
<< std::setw(20) << std::left
<< "Other Strings";
151 stream
<< std::right
<< "\t ";
152 stream
<< xpercentInt(StrCountMeter
.currentLevel() - pooled_count
, StrCountMeter
.currentLevel()) << "\t ";
153 stream
<< xpercentInt(StrVolumeMeter
.currentLevel() - pooled_volume
, StrVolumeMeter
.currentLevel()) << "\n\n";
157 memBufStats(std::ostream
& stream
)
159 stream
<< "Large buffers: " <<
160 HugeBufCountMeter
.currentLevel() << " (" <<
161 HugeBufVolumeMeter
.currentLevel() / 1024 << " KB)\n";
165 Mem::Stats(StoreEntry
* sentry
)
167 PackableStream
stream(*sentry
);
169 memStringStats(stream
);
172 if (RUNNING_ON_VALGRIND
) {
173 long int leaked
= 0, dubious
= 0, reachable
= 0, suppressed
= 0;
174 stream
<< "Valgrind Report:\n";
175 stream
<< "Type\tAmount\n";
176 debugs(13, DBG_IMPORTANT
, "Asking valgrind for memleaks");
177 VALGRIND_DO_LEAK_CHECK
;
178 debugs(13, DBG_IMPORTANT
, "Getting valgrind statistics");
179 VALGRIND_COUNT_LEAKS(leaked
, dubious
, reachable
, suppressed
);
180 stream
<< "Leaked\t" << leaked
<< "\n";
181 stream
<< "Dubious\t" << dubious
<< "\n";
182 stream
<< "Reachable\t" << reachable
<< "\n";
183 stream
<< "Suppressed\t" << suppressed
<< "\n";
194 * we have a limit on _total_ amount of idle memory so we ignore max_pages for now.
195 * Will ignore repeated calls for the same pool type.
197 * Relies on Mem::Init() having been called beforehand.
200 memDataInit(mem_type type
, const char *name
, size_t size
, int, bool doZero
)
202 assert(name
&& size
);
204 if (GetPool(type
) != nullptr)
207 GetPool(type
) = memPoolCreate(name
, size
);
208 GetPool(type
)->zeroBlocks(doZero
);
211 /* find appropriate pool and use it (pools always init buffer with 0s) */
213 memAllocate(mem_type type
)
215 assert(GetPool(type
));
216 return GetPool(type
)->alloc();
219 /* give memory back to the pool */
221 memFree(void *p
, int type
)
223 assert(GetPool(type
));
224 GetPool(type
)->freeOne(p
);
227 /* allocate a variable size buffer using best-fit string pool */
229 memAllocString(size_t net_size
, size_t * gross_size
)
233 if (const auto pool
= memFindStringPool(net_size
, true)) {
234 *gross_size
= pool
->objectSize();
235 assert(*gross_size
>= net_size
);
237 StrVolumeMeter
+= *gross_size
;
238 return pool
->alloc();
241 *gross_size
= net_size
;
243 StrVolumeMeter
+= *gross_size
;
244 return xcalloc(1, net_size
);
248 memAllocRigid(size_t net_size
)
250 // TODO: Use memAllocString() instead (after it stops zeroing memory).
252 if (const auto pool
= memFindStringPool(net_size
, true)) {
254 StrVolumeMeter
+= pool
->objectSize();
255 return pool
->alloc();
259 StrVolumeMeter
+= net_size
;
260 return xmalloc(net_size
);
268 for (int counter
= 0; counter
< mem_str_pool_count
; ++counter
)
269 result
+= GetStrPool(counter
).inUseCount();
274 /* free buffer allocated with memAllocString() */
276 memFreeString(size_t size
, void *buf
)
280 if (const auto pool
= memFindStringPool(size
, false))
286 StrVolumeMeter
-= size
;
290 memFreeRigid(void *buf
, size_t net_size
)
292 // TODO: Use memFreeString() instead (after removing fuzzy=false pool search).
294 if (const auto pool
= memFindStringPool(net_size
, true)) {
296 StrVolumeMeter
-= pool
->objectSize();
302 StrVolumeMeter
-= net_size
;
306 /* Find the best fit MEM_X_BUF type */
308 memFindBufSizeType(size_t net_size
, size_t * gross_size
)
313 if (net_size
<= 2 * 1024) {
316 } else if (net_size
<= 4 * 1024) {
319 } else if (net_size
<= 8 * 1024) {
322 } else if (net_size
<= 16 * 1024) {
325 } else if (net_size
<= 32 * 1024) {
328 } else if (net_size
<= 64 * 1024) {
342 /* allocate a variable size buffer using best-fit pool */
344 memAllocBuf(size_t net_size
, size_t * gross_size
)
346 mem_type type
= memFindBufSizeType(net_size
, gross_size
);
348 if (type
!= MEM_NONE
)
349 return memAllocate(type
);
352 HugeBufVolumeMeter
+= *gross_size
;
353 return xcalloc(1, net_size
);
357 /* resize a variable sized buffer using best-fit pool */
359 memReallocBuf(void *oldbuf
, size_t net_size
, size_t * gross_size
)
361 /* XXX This can be optimized on very large buffers to use realloc() */
362 /* TODO: if the existing gross size is >= new gross size, do nothing */
363 size_t new_gross_size
;
364 void *newbuf
= memAllocBuf(net_size
, &new_gross_size
);
367 size_t data_size
= *gross_size
;
369 if (data_size
> net_size
)
370 data_size
= net_size
;
372 memcpy(newbuf
, oldbuf
, data_size
);
374 memFreeBuf(*gross_size
, oldbuf
);
377 *gross_size
= new_gross_size
;
381 /* free buffer allocated with memAllocBuf() */
383 memFreeBuf(size_t size
, void *buf
)
385 mem_type type
= memFindBufSizeType(size
, nullptr);
387 if (type
!= MEM_NONE
)
392 HugeBufVolumeMeter
-= size
;
396 static double clean_interval
= 15.0; /* time to live of idle chunk before release */
399 Mem::CleanIdlePools(void *)
401 MemPools::GetInstance().clean(static_cast<time_t>(clean_interval
));
402 eventAdd("memPoolCleanIdlePools", CleanIdlePools
, nullptr, clean_interval
, 1);
408 int64_t new_pool_limit
;
410 /** Set to configured value first */
411 if (!Config
.onoff
.mem_pools
)
413 else if (Config
.MemPools
.limit
> 0)
414 new_pool_limit
= Config
.MemPools
.limit
;
416 if (Config
.MemPools
.limit
== 0)
417 debugs(13, DBG_IMPORTANT
, "memory_pools_limit 0 has been chagned to memory_pools_limit none. Please update your config");
421 MemPools::GetInstance().setIdleLimit(new_pool_limit
);
427 /* all pools are ready to be used */
428 static bool MemIsInitialized
= false;
429 if (MemIsInitialized
)
433 * Then initialize all pools.
435 * Starting with generic 2kB - 64kB buffr pools, then specific object types.
437 * It does not hurt much to have a lot of pools since sizeof(MemPool) is
438 * small; someday we will figure out what to do with all the entries here
439 * that are never used or used only once; perhaps we should simply use
440 * malloc() for those? @?@
442 memDataInit(MEM_2K_BUF
, "2K Buffer", 2048, 10, false);
443 memDataInit(MEM_4K_BUF
, "4K Buffer", 4096, 10, false);
444 memDataInit(MEM_8K_BUF
, "8K Buffer", 8192, 10, false);
445 memDataInit(MEM_16K_BUF
, "16K Buffer", 16384, 10, false);
446 memDataInit(MEM_32K_BUF
, "32K Buffer", 32768, 10, false);
447 memDataInit(MEM_64K_BUF
, "64K Buffer", 65536, 10, false);
448 memDataInit(MEM_DREAD_CTRL
, "dread_ctrl", sizeof(dread_ctrl
), 0);
449 memDataInit(MEM_DWRITE_Q
, "dwrite_q", sizeof(dwrite_q
), 0);
450 memDataInit(MEM_MD5_DIGEST
, "MD5 digest", SQUID_MD5_DIGEST_LENGTH
, 0);
451 GetPool(MEM_MD5_DIGEST
)->setChunkSize(512 * 1024);
453 MemIsInitialized
= true;
455 // finally register with the cache manager
456 Mgr::RegisterAction("mem", "Memory Utilization", Mem::Stats
, 0, 1);
462 debugs(13, 3, "Memory pools are '" <<
463 (Config
.onoff
.mem_pools
? "on" : "off") << "'; limit: " <<
464 std::setprecision(3) << toMB(MemPools::GetInstance().idleLimit()) <<
469 operator++(mem_type
&aMem
)
472 aMem
= (mem_type
)(++tmp
);
477 * Test that all entries are initialized
482 mem_type t
= MEM_NONE
;
484 while (++t
< MEM_MAX
) {
486 * If you hit this assertion, then you forgot to add a
487 * memDataInit() line for type 't'.
496 MemPoolGlobalStats stats
;
497 if (Config
.MemPools
.limit
> 0) // do not reset if disabled or same
498 MemPools::GetInstance().setIdleLimit(0);
499 MemPools::GetInstance().clean(0);
500 memPoolGetGlobalStats(&stats
);
502 if (stats
.tot_items_inuse
)
503 debugs(13, 2, "memCleanModule: " << stats
.tot_items_inuse
<<
504 " items in " << stats
.tot_chunks_inuse
<< " chunks and " <<
505 stats
.tot_pools_inuse
<< " pools are left dirty");
509 memInUse(mem_type type
)
511 return GetPool(type
)->inUseCount();
519 memFree(p
, MEM_2K_BUF
);
525 memFree(p
, MEM_4K_BUF
);
531 memFree(p
, MEM_8K_BUF
);
537 memFree(p
, MEM_16K_BUF
);
543 memFree(p
, MEM_32K_BUF
);
549 memFree(p
, MEM_64K_BUF
);
553 cxx_xfree(void * ptr
)
559 memFreeBufFunc(size_t size
)
583 HugeBufVolumeMeter
-= size
;
591 Mem::PoolReport(const MemPoolStats
* mp_st
, const MemPoolMeter
* AllMeter
, std::ostream
&stream
)
595 MemPoolMeter
*pm
= mp_st
->meter
;
596 const char *delim
= "\t ";
598 stream
.setf(std::ios_base::fixed
);
599 stream
<< std::setw(20) << std::left
<< mp_st
->label
<< delim
;
600 stream
<< std::setw(4) << std::right
<< mp_st
->obj_size
<< delim
;
603 if (mp_st
->chunk_capacity
) {
604 stream
<< std::setw(4) << toKB(mp_st
->obj_size
* mp_st
->chunk_capacity
) << delim
;
605 stream
<< std::setw(4) << mp_st
->chunk_capacity
<< delim
;
607 needed
= mp_st
->items_inuse
/ mp_st
->chunk_capacity
;
609 if (mp_st
->items_inuse
% mp_st
->chunk_capacity
)
612 excess
= mp_st
->chunks_inuse
- needed
;
614 stream
<< std::setw(4) << mp_st
->chunks_alloc
<< delim
;
615 stream
<< std::setw(4) << mp_st
->chunks_inuse
<< delim
;
616 stream
<< std::setw(4) << mp_st
->chunks_free
<< delim
;
617 stream
<< std::setw(4) << mp_st
->chunks_partial
<< delim
;
618 stream
<< std::setprecision(3) << xpercent(excess
, needed
) << delim
;
629 * Fragmentation calculation:
630 * needed = inuse.currentLevel() / chunk_capacity
631 * excess = used - needed
632 * fragmentation = excess / needed * 100%
634 * Fragm = (alloced - (inuse / obj_ch) ) / alloced
637 stream
<< mp_st
->items_alloc
<< delim
;
638 stream
<< toKB(mp_st
->obj_size
* pm
->alloc
.currentLevel()) << delim
;
639 stream
<< toKB(mp_st
->obj_size
* pm
->alloc
.peak()) << delim
;
640 stream
<< std::setprecision(2) << ((squid_curtime
- pm
->alloc
.peakTime()) / 3600.) << delim
;
641 stream
<< std::setprecision(3) << xpercent(mp_st
->obj_size
* pm
->alloc
.currentLevel(), AllMeter
->alloc
.currentLevel()) << delim
;
643 stream
<< mp_st
->items_inuse
<< delim
;
644 stream
<< toKB(mp_st
->obj_size
* pm
->inuse
.currentLevel()) << delim
;
645 stream
<< toKB(mp_st
->obj_size
* pm
->inuse
.peak()) << delim
;
646 stream
<< std::setprecision(2) << ((squid_curtime
- pm
->inuse
.peakTime()) / 3600.) << delim
;
647 stream
<< std::setprecision(3) << xpercent(pm
->inuse
.currentLevel(), pm
->alloc
.currentLevel()) << delim
;
649 stream
<< mp_st
->items_idle
<< delim
;
650 stream
<< toKB(mp_st
->obj_size
* pm
->idle
.currentLevel()) << delim
;
651 stream
<< toKB(mp_st
->obj_size
* pm
->idle
.peak()) << delim
;
653 stream
<< (int)pm
->gb_saved
.count
<< delim
;
654 stream
<< std::setprecision(3) << xpercent(pm
->gb_saved
.count
, AllMeter
->gb_allocated
.count
) << delim
;
655 stream
<< std::setprecision(3) << xpercent(pm
->gb_saved
.bytes
, AllMeter
->gb_allocated
.bytes
) << delim
;
656 stream
<< std::setprecision(3) << xdiv(pm
->gb_allocated
.count
- pm
->gb_oallocated
.count
, xm_deltat
) << "\n";
657 pm
->gb_oallocated
.count
= pm
->gb_allocated
.count
;
661 MemPoolReportSorter(const void *a
, const void *b
)
663 const MemPoolStats
*A
= (MemPoolStats
*) a
;
664 const MemPoolStats
*B
= (MemPoolStats
*) b
;
666 // use this to sort on %Total Allocated
668 double pa
= (double) A
->obj_size
* A
->meter
->alloc
.currentLevel();
669 double pb
= (double) B
->obj_size
* B
->meter
->alloc
.currentLevel();
681 Mem::Report(std::ostream
&stream
)
684 static MemPoolStats mp_stats
;
685 static MemPoolGlobalStats mp_total
;
687 MemPoolIterator
*iter
;
691 stream
<< "Current memory usage:\n";
693 stream
<< "Pool\t Obj Size\t"
694 "Chunks\t\t\t\t\t\t\t"
695 "Allocated\t\t\t\t\t"
698 "Allocations Saved\t\t\t"
703 "(#)\t used\t free\t part\t %Frag\t "
704 "(#)\t (KB)\t high (KB)\t high (hrs)\t %Tot\t"
705 "(#)\t (KB)\t high (KB)\t high (hrs)\t %alloc\t"
706 "(#)\t (KB)\t high (KB)\t"
707 "(#)\t %cnt\t %vol\t"
710 xm_deltat
= current_dtime
- xm_time
;
711 xm_time
= current_dtime
;
713 /* Get stats for Totals report line */
714 memPoolGetGlobalStats(&mp_total
);
716 MemPoolStats
*sortme
= (MemPoolStats
*) xcalloc(mp_total
.tot_pools_alloc
,sizeof(*sortme
));
720 iter
= memPoolIterate();
722 while ((pool
= memPoolIterateNext(iter
))) {
723 pool
->getStats(&mp_stats
);
725 if (!mp_stats
.pool
) /* pool destroyed */
728 if (mp_stats
.pool
->getMeter().gb_allocated
.count
> 0) {
729 /* this pool has been used */
730 sortme
[npools
] = mp_stats
;
737 memPoolIterateDone(&iter
);
739 qsort(sortme
, npools
, sizeof(*sortme
), MemPoolReportSorter
);
741 for (int i
= 0; i
< npools
; ++i
) {
742 PoolReport(&sortme
[i
], mp_total
.TheMeter
, stream
);
747 mp_stats
.pool
= nullptr;
748 mp_stats
.label
= "Total";
749 mp_stats
.meter
= mp_total
.TheMeter
;
750 mp_stats
.obj_size
= 1;
751 mp_stats
.chunk_capacity
= 0;
752 mp_stats
.chunk_size
= 0;
753 mp_stats
.chunks_alloc
= mp_total
.tot_chunks_alloc
;
754 mp_stats
.chunks_inuse
= mp_total
.tot_chunks_inuse
;
755 mp_stats
.chunks_partial
= mp_total
.tot_chunks_partial
;
756 mp_stats
.chunks_free
= mp_total
.tot_chunks_free
;
757 mp_stats
.items_alloc
= mp_total
.tot_items_alloc
;
758 mp_stats
.items_inuse
= mp_total
.tot_items_inuse
;
759 mp_stats
.items_idle
= mp_total
.tot_items_idle
;
760 mp_stats
.overhead
= mp_total
.tot_overhead
;
762 PoolReport(&mp_stats
, mp_total
.TheMeter
, stream
);
765 stream
<< "Cumulative allocated volume: "<< double_to_str(buf
, 64, mp_total
.TheMeter
->gb_allocated
.bytes
) << "\n";
767 stream
<< "Current overhead: " << mp_total
.tot_overhead
<< " bytes (" <<
768 std::setprecision(3) << xpercent(mp_total
.tot_overhead
, mp_total
.TheMeter
->inuse
.currentLevel()) << "%)\n";
770 if (mp_total
.mem_idle_limit
>= 0)
771 stream
<< "Idle pool limit: " << std::setprecision(2) << toMB(mp_total
.mem_idle_limit
) << " MB\n";
773 stream
<< "Total Pools created: " << mp_total
.tot_pools_alloc
<< "\n";
774 stream
<< "Pools ever used: " << mp_total
.tot_pools_alloc
- not_used
<< " (shown above)\n";
775 stream
<< "Currently in use: " << mp_total
.tot_pools_inuse
<< "\n";