]>
Commit | Line | Data |
---|---|---|
d96ceb8e | 1 | #ifndef _MEM_POOLS_H_ |
2 | #define _MEM_POOLS_H_ | |
3 | ||
63be0a78 | 4 | /** |
5 | \defgroup MemPoolsAPI Memory Management (Memory Pool Allocator) | |
6 | \ingroup Components | |
7 | * | |
8 | *\par | |
9 | * MemPools are a pooled memory allocator running on top of malloc(). It's | |
10 | * purpose is to reduce memory fragmentation and provide detailed statistics | |
11 | * on memory consumption. | |
12 | * | |
13 | \par | |
14 | * Preferably all memory allocations in Squid should be done using MemPools | |
15 | * or one of the types built on top of it (i.e. cbdata). | |
16 | * | |
17 | \note Usually it is better to use cbdata types as these gives you additional | |
18 | * safeguards in references and typechecking. However, for high usage pools where | |
19 | * the cbdata functionality of cbdata is not required directly using a MemPool | |
20 | * might be the way to go. | |
21 | */ | |
22 | ||
d96ceb8e | 23 | #include "config.h" |
d96ceb8e | 24 | #include "util.h" |
83d8f9f4 | 25 | |
d96ceb8e | 26 | #include "memMeter.h" |
b001e822 | 27 | #include "splay.h" |
d96ceb8e | 28 | |
29 | #if HAVE_GNUMALLOC_H | |
30 | #include <gnumalloc.h> | |
482aa790 | 31 | #elif HAVE_MALLOC_H |
d96ceb8e | 32 | #include <malloc.h> |
33 | #endif | |
34 | ||
35 | #if HAVE_MEMORY_H | |
36 | #include <memory.h> | |
37 | #endif | |
38 | ||
39 | #if !M_MMAP_MAX | |
40 | #if USE_DLMALLOC | |
41 | #define M_MMAP_MAX -4 | |
42 | #endif | |
43 | #endif | |
44 | ||
63be0a78 | 45 | /// \ingroup MemPoolsAPI |
d96ceb8e | 46 | #define MB ((size_t)1024*1024) |
63be0a78 | 47 | /// \ingroup MemPoolsAPI |
d96ceb8e | 48 | #define mem_unlimited_size 2 * 1024 * MB |
63be0a78 | 49 | /// \ingroup MemPoolsAPI |
d96ceb8e | 50 | #define toMB(size) ( ((double) size) / MB ) |
63be0a78 | 51 | /// \ingroup MemPoolsAPI |
d96ceb8e | 52 | #define toKB(size) ( (size + 1024 - 1) / 1024 ) |
53 | ||
63be0a78 | 54 | /// \ingroup MemPoolsAPI |
d96ceb8e | 55 | #define MEM_PAGE_SIZE 4096 |
63be0a78 | 56 | /// \ingroup MemPoolsAPI |
d96ceb8e | 57 | #define MEM_CHUNK_SIZE 4096 * 4 |
63be0a78 | 58 | /// \ingroup MemPoolsAPI |
d96ceb8e | 59 | #define MEM_CHUNK_MAX_SIZE 256 * 1024 /* 2MB */ |
63be0a78 | 60 | /// \ingroup MemPoolsAPI |
d96ceb8e | 61 | #define MEM_MIN_FREE 32 |
63be0a78 | 62 | /// \ingroup MemPoolsAPI |
d96ceb8e | 63 | #define MEM_MAX_FREE 65535 /* ushort is max number of items per chunk */ |
64 | ||
b001e822 | 65 | class MemImplementingAllocator; |
66 | class MemChunk; | |
67 | class MemPoolStats; | |
83d8f9f4 | 68 | |
63be0a78 | 69 | /// \ingroup MemPoolsAPI |
70 | /// \todo Kill this typedef for C++ | |
d96ceb8e | 71 | typedef struct _MemPoolGlobalStats MemPoolGlobalStats; |
83d8f9f4 | 72 | |
63be0a78 | 73 | /// \ingroup MemPoolsAPI |
b001e822 | 74 | class MemPoolIterator |
83d8f9f4 | 75 | { |
c5dd4956 | 76 | public: |
b001e822 | 77 | MemImplementingAllocator *pool; |
d96ceb8e | 78 | MemPoolIterator * next; |
79 | }; | |
80 | ||
63be0a78 | 81 | /** |
82 | \ingroup MemPoolsAPI | |
83 | * Object to track per-pool cumulative counters | |
84 | */ | |
b001e822 | 85 | class mgb_t |
83d8f9f4 | 86 | { |
c5dd4956 AJ |
87 | public: |
88 | mgb_t() : count(0), bytes(0) {} | |
d96ceb8e | 89 | double count; |
90 | double bytes; | |
b001e822 | 91 | }; |
d96ceb8e | 92 | |
63be0a78 | 93 | /** |
94 | \ingroup MemPoolsAPI | |
95 | * Object to track per-pool memory usage (alloc = inuse+idle) | |
96 | */ | |
b001e822 | 97 | class MemPoolMeter |
83d8f9f4 | 98 | { |
c5dd4956 | 99 | public: |
b001e822 | 100 | void flush(); |
d96ceb8e | 101 | MemMeter alloc; |
102 | MemMeter inuse; | |
103 | MemMeter idle; | |
63be0a78 | 104 | |
105 | /** account Allocations */ | |
106 | mgb_t gb_saved; | |
107 | ||
108 | /** history Allocations */ | |
109 | mgb_t gb_osaved; | |
110 | ||
111 | /** account Free calls */ | |
112 | mgb_t gb_freed; | |
d96ceb8e | 113 | }; |
114 | ||
b001e822 | 115 | class MemImplementingAllocator; |
116 | ||
63be0a78 | 117 | /// \ingroup MemPoolsAPI |
c5dd4956 | 118 | class MemPools |
b001e822 | 119 | { |
c5dd4956 | 120 | public: |
b001e822 | 121 | static MemPools &GetInstance(); |
122 | MemPools(); | |
123 | void init(); | |
124 | void flushMeters(); | |
63be0a78 | 125 | |
126 | /** | |
127 | \param label Name for the pool. Displayed in stats. | |
128 | \param obj_size Size of elements in MemPool. | |
129 | */ | |
b001e822 | 130 | MemImplementingAllocator * create(const char *label, size_t obj_size); |
63be0a78 | 131 | |
132 | /** | |
133 | \param label Name for the pool. Displayed in stats. | |
134 | \param obj_size Size of elements in MemPool. | |
135 | \param chunked ?? | |
136 | */ | |
b001e822 | 137 | MemImplementingAllocator * create(const char *label, size_t obj_size, bool const chunked); |
63be0a78 | 138 | |
139 | /** | |
140 | * Sets upper limit in bytes to amount of free ram kept in pools. This is | |
141 | * not strict upper limit, but a hint. When MemPools are over this limit, | |
142 | * totally free chunks are immediately considered for release. Otherwise | |
143 | * only chunks that have not been referenced for a long time are checked. | |
144 | */ | |
b001e822 | 145 | void setIdleLimit(size_t new_idle_limit); |
63be0a78 | 146 | |
871899ca | 147 | size_t idleLimit() const; |
63be0a78 | 148 | |
149 | /** | |
150 | \par | |
151 | * Main cleanup handler. For MemPools to stay within upper idle limits, | |
152 | * this function needs to be called periodically, preferrably at some | |
153 | * constant rate, eg. from Squid event. It looks through all pools and | |
154 | * chunks, cleans up internal states and checks for releasable chunks. | |
155 | * | |
156 | \par | |
157 | * Between the calls to this function objects are placed onto internal | |
158 | * cache instead of returning to their home chunks, mainly for speedup | |
159 | * purpose. During that time state of chunk is not known, it is not | |
160 | * known whether chunk is free or in use. This call returns all objects | |
161 | * to their chunks and restores consistency. | |
162 | * | |
163 | \par | |
164 | * Should be called relatively often, as it sorts chunks in suitable | |
165 | * order as to reduce free memory fragmentation and increase chunk | |
166 | * utilisation. | |
167 | * Suitable frequency for cleanup is in range of few tens of seconds to | |
168 | * few minutes, depending of memory activity. | |
169 | * | |
170 | \todo DOCS: Re-write this shorter! | |
171 | * | |
172 | \param maxage Release all totally idle chunks that | |
173 | * have not been referenced for maxage seconds. | |
174 | */ | |
b001e822 | 175 | void clean(time_t maxage); |
63be0a78 | 176 | |
b001e822 | 177 | void setDefaultPoolChunking(bool const &); |
178 | MemImplementingAllocator *pools; | |
179 | int mem_idle_limit; | |
180 | int poolCount; | |
181 | bool defaultIsChunked; | |
c5dd4956 | 182 | private: |
b001e822 | 183 | static MemPools *Instance; |
184 | }; | |
185 | ||
63be0a78 | 186 | /** |
187 | \ingroup MemPoolsAPI | |
188 | * a pool is a [growing] space for objects of the same size | |
189 | */ | |
b001e822 | 190 | class MemAllocator |
191 | { | |
192 | public: | |
193 | MemAllocator (char const *aLabel); | |
194 | virtual ~MemAllocator() {} | |
63be0a78 | 195 | |
196 | /** | |
197 | \param stats Object to be filled with statistical data about pool. | |
198 | \retval Number of objects in use, ie. allocated. | |
199 | */ | |
b001e822 | 200 | virtual int getStats(MemPoolStats * stats) = 0; |
63be0a78 | 201 | |
b001e822 | 202 | virtual MemPoolMeter const &getMeter() const = 0; |
63be0a78 | 203 | |
204 | /** | |
205 | * Allocate one element from the pool | |
206 | */ | |
b001e822 | 207 | virtual void *alloc() = 0; |
63be0a78 | 208 | |
209 | /** | |
210 | * Free a element allocated by MemAllocator::alloc() | |
211 | */ | |
dc47f531 | 212 | virtual void freeOne(void *) = 0; |
63be0a78 | 213 | |
b001e822 | 214 | virtual char const *objectType() const; |
215 | virtual size_t objectSize() const = 0; | |
9f9e06f3 | 216 | virtual int getInUseCount() = 0; |
60da11d3 | 217 | void zeroOnPush(bool doIt); |
a3efa961 | 218 | int inUseCount(); |
63be0a78 | 219 | |
220 | /** | |
221 | * Allows you tune chunk size of pooling. Objects are allocated in chunks | |
222 | * instead of individually. This conserves memory, reduces fragmentation. | |
223 | * Because of that memory can be freed also only in chunks. Therefore | |
224 | * there is tradeoff between memory conservation due to chunking and free | |
225 | * memory fragmentation. | |
226 | * | |
227 | \note As a general guideline, increase chunk size only for pools that keep | |
228 | * very many items for relatively long time. | |
229 | */ | |
04eb0689 | 230 | virtual void setChunkSize(size_t chunksize) {} |
cb6d4984 | 231 | |
63be0a78 | 232 | /** |
233 | \param minSize Minimum size needed to be allocated. | |
234 | \retval n Smallest size divisible by sizeof(void*) | |
235 | */ | |
cb6d4984 | 236 | static size_t RoundedSize(size_t minSize); |
63be0a78 | 237 | |
60da11d3 | 238 | protected: |
239 | bool doZeroOnPush; | |
63be0a78 | 240 | |
b001e822 | 241 | private: |
242 | const char *label; | |
243 | }; | |
244 | ||
63be0a78 | 245 | /** |
246 | \ingroup MemPoolsAPI | |
247 | * Support late binding of pool type for allocator agnostic classes | |
248 | */ | |
b001e822 | 249 | class MemAllocatorProxy |
83d8f9f4 | 250 | { |
c5dd4956 | 251 | public: |
b001e822 | 252 | inline MemAllocatorProxy(char const *aLabel, size_t const &); |
63be0a78 | 253 | |
254 | /** | |
255 | * Allocate one element from the pool | |
256 | */ | |
b001e822 | 257 | void *alloc(); |
63be0a78 | 258 | |
259 | /** | |
260 | * Free a element allocated by MemAllocatorProxy::alloc() | |
261 | */ | |
dc47f531 | 262 | void freeOne(void *); |
63be0a78 | 263 | |
b001e822 | 264 | int inUseCount() const; |
265 | size_t objectSize() const; | |
266 | MemPoolMeter const &getMeter() const; | |
63be0a78 | 267 | |
268 | /** | |
269 | \param stats Object to be filled with statistical data about pool. | |
270 | \retval Number of objects in use, ie. allocated. | |
271 | */ | |
b001e822 | 272 | int getStats(MemPoolStats * stats); |
63be0a78 | 273 | |
b001e822 | 274 | char const * objectType() const; |
c5dd4956 | 275 | private: |
b001e822 | 276 | MemAllocator *getAllocator() const; |
d96ceb8e | 277 | const char *label; |
b001e822 | 278 | size_t size; |
279 | mutable MemAllocator *theAllocator; | |
280 | }; | |
63be0a78 | 281 | |
b001e822 | 282 | /* help for classes */ |
63be0a78 | 283 | |
284 | /** | |
285 | \ingroup MemPoolsAPI | |
286 | \hideinitializer | |
c5dd4956 | 287 | * |
63be0a78 | 288 | * This macro is intended for use within the declaration of a class. |
289 | */ | |
b001e822 | 290 | #define MEMPROXY_CLASS(CLASS) \ |
b001e822 | 291 | inline void *operator new(size_t); \ |
292 | inline void operator delete(void *); \ | |
293 | static inline MemAllocatorProxy &Pool() | |
294 | ||
63be0a78 | 295 | /** |
296 | \ingroup MemPoolsAPI | |
297 | \hideinitializer | |
c5dd4956 | 298 | * |
63be0a78 | 299 | * This macro is intended for use within the .h or .cci of a class as appropriate. |
300 | */ | |
b001e822 | 301 | #define MEMPROXY_CLASS_INLINE(CLASS) \ |
302 | MemAllocatorProxy& CLASS::Pool() \ | |
303 | { \ | |
304 | static MemAllocatorProxy thePool(#CLASS, sizeof (CLASS)); \ | |
305 | return thePool; \ | |
306 | } \ | |
307 | \ | |
308 | void * \ | |
309 | CLASS::operator new (size_t byteCount) \ | |
310 | { \ | |
311 | /* derived classes with different sizes must implement their own new */ \ | |
312 | assert (byteCount == sizeof (CLASS)); \ | |
313 | \ | |
314 | return Pool().alloc(); \ | |
315 | } \ | |
316 | \ | |
317 | void \ | |
318 | CLASS::operator delete (void *address) \ | |
319 | { \ | |
dc47f531 | 320 | Pool().freeOne(address); \ |
b001e822 | 321 | } |
322 | ||
63be0a78 | 323 | /// \ingroup MemPoolsAPI |
b001e822 | 324 | class MemImplementingAllocator : public MemAllocator |
325 | { | |
c5dd4956 | 326 | public: |
b001e822 | 327 | MemImplementingAllocator(char const *aLabel, size_t aSize); |
328 | virtual MemPoolMeter const &getMeter() const; | |
329 | virtual MemPoolMeter &getMeter(); | |
330 | virtual void flushMetersFull(); | |
331 | virtual void flushMeters(); | |
63be0a78 | 332 | |
333 | /** | |
334 | * Allocate one element from the pool | |
335 | */ | |
b001e822 | 336 | virtual void *alloc(); |
63be0a78 | 337 | |
338 | /** | |
339 | * Free a element allocated by MemImplementingAllocator::alloc() | |
340 | */ | |
dc47f531 | 341 | virtual void freeOne(void *); |
63be0a78 | 342 | |
b001e822 | 343 | virtual bool idleTrigger(int shift) const = 0; |
344 | virtual void clean(time_t maxage) = 0; | |
63be0a78 | 345 | /** Hint to the allocator - may be ignored */ |
b001e822 | 346 | virtual void setChunkSize(size_t chunksize) {} |
347 | virtual size_t objectSize() const; | |
9f9e06f3 | 348 | virtual int getInUseCount() = 0; |
c5dd4956 | 349 | protected: |
b001e822 | 350 | virtual void *allocate() = 0; |
351 | virtual void deallocate(void *) = 0; | |
c5dd4956 | 352 | private: |
b001e822 | 353 | MemPoolMeter meter; |
c5dd4956 | 354 | public: |
b001e822 | 355 | MemImplementingAllocator *next; |
c5dd4956 | 356 | public: |
b001e822 | 357 | size_t alloc_calls; |
358 | size_t free_calls; | |
d96ceb8e | 359 | size_t obj_size; |
b001e822 | 360 | }; |
361 | ||
63be0a78 | 362 | /// \ingroup MemPoolsAPI |
b001e822 | 363 | class MemPool : public MemImplementingAllocator |
364 | { | |
c5dd4956 | 365 | public: |
b001e822 | 366 | friend class MemChunk; |
367 | MemPool(const char *label, size_t obj_size); | |
368 | ~MemPool(); | |
369 | void convertFreeCacheToChunkFreeCache(); | |
370 | virtual void clean(time_t maxage); | |
63be0a78 | 371 | |
372 | /** | |
373 | \param stats Object to be filled with statistical data about pool. | |
374 | \retval Number of objects in use, ie. allocated. | |
375 | */ | |
b001e822 | 376 | virtual int getStats(MemPoolStats * stats); |
63be0a78 | 377 | |
b001e822 | 378 | void createChunk(); |
379 | void *get(); | |
380 | void push(void *obj); | |
9f9e06f3 | 381 | virtual int getInUseCount(); |
c5dd4956 | 382 | protected: |
b001e822 | 383 | virtual void *allocate(); |
384 | virtual void deallocate(void *); | |
c5dd4956 | 385 | public: |
63be0a78 | 386 | /** |
387 | * Allows you tune chunk size of pooling. Objects are allocated in chunks | |
388 | * instead of individually. This conserves memory, reduces fragmentation. | |
389 | * Because of that memory can be freed also only in chunks. Therefore | |
390 | * there is tradeoff between memory conservation due to chunking and free | |
391 | * memory fragmentation. | |
392 | * | |
393 | \note As a general guideline, increase chunk size only for pools that keep | |
394 | * very many items for relatively long time. | |
395 | */ | |
b001e822 | 396 | virtual void setChunkSize(size_t chunksize); |
63be0a78 | 397 | |
b001e822 | 398 | virtual bool idleTrigger(int shift) const; |
399 | ||
d96ceb8e | 400 | size_t chunk_size; |
401 | int chunk_capacity; | |
402 | int memPID; | |
403 | int chunkCount; | |
d96ceb8e | 404 | size_t inuse; |
405 | size_t idle; | |
406 | void *freeCache; | |
407 | MemChunk *nextFreeChunk; | |
408 | MemChunk *Chunks; | |
b001e822 | 409 | Splay<MemChunk *> allChunks; |
410 | }; | |
411 | ||
63be0a78 | 412 | /// \ingroup MemPoolsAPI |
b001e822 | 413 | class MemMalloc : public MemImplementingAllocator |
414 | { | |
c5dd4956 | 415 | public: |
b001e822 | 416 | MemMalloc(char const *label, size_t aSize); |
417 | virtual bool idleTrigger(int shift) const; | |
418 | virtual void clean(time_t maxage); | |
63be0a78 | 419 | |
420 | /** | |
421 | \param stats Object to be filled with statistical data about pool. | |
422 | \retval Number of objects in use, ie. allocated. | |
423 | */ | |
b001e822 | 424 | virtual int getStats(MemPoolStats * stats); |
63be0a78 | 425 | |
9f9e06f3 | 426 | virtual int getInUseCount(); |
c5dd4956 | 427 | protected: |
b001e822 | 428 | virtual void *allocate(); |
429 | virtual void deallocate(void *); | |
c5dd4956 | 430 | private: |
9f9e06f3 | 431 | int inuse; |
d96ceb8e | 432 | }; |
433 | ||
63be0a78 | 434 | /// \ingroup MemPoolsAPI |
b001e822 | 435 | class MemChunk |
83d8f9f4 | 436 | { |
c5dd4956 | 437 | public: |
b001e822 | 438 | MemChunk(MemPool *pool); |
439 | ~MemChunk(); | |
d96ceb8e | 440 | void *freeList; |
441 | void *objCache; | |
442 | int inuse_count; | |
443 | MemChunk *nextFreeChunk; | |
444 | MemChunk *next; | |
445 | time_t lastref; | |
b001e822 | 446 | MemPool *pool; |
d96ceb8e | 447 | }; |
448 | ||
63be0a78 | 449 | /// \ingroup MemPoolsAPI |
b001e822 | 450 | class MemPoolStats |
83d8f9f4 | 451 | { |
c5dd4956 | 452 | public: |
b001e822 | 453 | MemAllocator *pool; |
d96ceb8e | 454 | const char *label; |
455 | MemPoolMeter *meter; | |
456 | int obj_size; | |
457 | int chunk_capacity; | |
458 | int chunk_size; | |
459 | ||
460 | int chunks_alloc; | |
461 | int chunks_inuse; | |
462 | int chunks_partial; | |
463 | int chunks_free; | |
464 | ||
465 | int items_alloc; | |
466 | int items_inuse; | |
467 | int items_idle; | |
468 | ||
469 | int overhead; | |
470 | }; | |
471 | ||
63be0a78 | 472 | /// \ingroup MemPoolsAPI |
473 | /// \todo Classify and add constructor/destructor to initialize properly. | |
c5dd4956 | 474 | struct _MemPoolGlobalStats { |
d96ceb8e | 475 | MemPoolMeter *TheMeter; |
476 | ||
477 | int tot_pools_alloc; | |
478 | int tot_pools_inuse; | |
479 | int tot_pools_mempid; | |
480 | ||
481 | int tot_chunks_alloc; | |
482 | int tot_chunks_inuse; | |
483 | int tot_chunks_partial; | |
484 | int tot_chunks_free; | |
485 | ||
486 | int tot_items_alloc; | |
487 | int tot_items_inuse; | |
488 | int tot_items_idle; | |
489 | ||
490 | int tot_overhead; | |
491 | int mem_idle_limit; | |
492 | }; | |
493 | ||
63be0a78 | 494 | /// \ingroup MemPoolsAPI |
04eb0689 | 495 | #define memPoolCreate MemPools::GetInstance().create |
496 | ||
d96ceb8e | 497 | /* Allocator API */ |
63be0a78 | 498 | /** |
499 | \ingroup MemPoolsAPI | |
500 | * Initialise iteration through all of the pools. | |
501 | \retval Iterator for use by memPoolIterateNext() and memPoolIterateDone() | |
502 | */ | |
b001e822 | 503 | extern MemPoolIterator * memPoolIterate(void); |
63be0a78 | 504 | |
505 | /** | |
506 | \ingroup MemPoolsAPI | |
507 | * Get next pool pointer, until getting NULL pointer. | |
508 | */ | |
b001e822 | 509 | extern MemImplementingAllocator * memPoolIterateNext(MemPoolIterator * iter); |
63be0a78 | 510 | |
511 | /** | |
512 | \ingroup MemPoolsAPI | |
513 | * Should be called after finished with iterating through all pools. | |
514 | */ | |
b001e822 | 515 | extern void memPoolIterateDone(MemPoolIterator ** iter); |
d96ceb8e | 516 | |
63be0a78 | 517 | /** |
518 | \ingroup MemPoolsAPI | |
519 | \todo Stats API - not sured how to refactor yet | |
520 | * | |
521 | * Fills MemPoolGlobalStats with statistical data about overall | |
522 | * usage for all pools. | |
523 | * | |
524 | \retval Number of pools that have at least one object in use. | |
525 | * Ie. number of dirty pools. | |
526 | */ | |
b001e822 | 527 | extern int memPoolGetGlobalStats(MemPoolGlobalStats * stats); |
d96ceb8e | 528 | |
63be0a78 | 529 | /// \ingroup MemPoolsAPI |
b001e822 | 530 | extern int memPoolInUseCount(MemAllocator *); |
63be0a78 | 531 | /// \ingroup MemPoolsAPI |
b001e822 | 532 | extern int memPoolsTotalAllocated(void); |
d96ceb8e | 533 | |
b001e822 | 534 | MemAllocatorProxy::MemAllocatorProxy(char const *aLabel, size_t const &aSize) : label (aLabel), size(aSize), theAllocator (NULL) |
535 | { | |
536 | } | |
d96ceb8e | 537 | |
d96ceb8e | 538 | |
539 | #endif /* _MEM_POOLS_H_ */ |