]>
Commit | Line | Data |
---|---|---|
5c193dec | 1 | /* |
77b1029d | 2 | * Copyright (C) 1996-2020 The Squid Software Foundation and contributors |
5c193dec AJ |
3 | * |
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. | |
7 | */ | |
8 | ||
8cfaefed HN |
9 | #ifndef _MEM_POOL_H_ |
10 | #define _MEM_POOL_H_ | |
d96ceb8e | 11 | |
63be0a78 | 12 | /** |
13 | \defgroup MemPoolsAPI Memory Management (Memory Pool Allocator) | |
14 | \ingroup Components | |
15 | * | |
16 | *\par | |
17 | * MemPools are a pooled memory allocator running on top of malloc(). It's | |
18 | * purpose is to reduce memory fragmentation and provide detailed statistics | |
19 | * on memory consumption. | |
20 | * | |
21 | \par | |
22 | * Preferably all memory allocations in Squid should be done using MemPools | |
23 | * or one of the types built on top of it (i.e. cbdata). | |
24 | * | |
25 | \note Usually it is better to use cbdata types as these gives you additional | |
26 | * safeguards in references and typechecking. However, for high usage pools where | |
27 | * the cbdata functionality of cbdata is not required directly using a MemPool | |
28 | * might be the way to go. | |
29 | */ | |
30 | ||
9663db1c | 31 | #include "mem/Meter.h" |
ed6e9fb9 | 32 | #include "util.h" |
d96ceb8e | 33 | |
34 | #if HAVE_GNUMALLOC_H | |
35 | #include <gnumalloc.h> | |
482aa790 | 36 | #elif HAVE_MALLOC_H |
d96ceb8e | 37 | #include <malloc.h> |
38 | #endif | |
d96ceb8e | 39 | #if HAVE_MEMORY_H |
40 | #include <memory.h> | |
41 | #endif | |
42 | ||
43 | #if !M_MMAP_MAX | |
44 | #if USE_DLMALLOC | |
45 | #define M_MMAP_MAX -4 | |
46 | #endif | |
47 | #endif | |
48 | ||
63be0a78 | 49 | /// \ingroup MemPoolsAPI |
c63f0322 | 50 | #define toMB(size) ( ((double) size) / ((double)(1024*1024)) ) |
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_MIN_FREE 32 |
63be0a78 | 58 | /// \ingroup MemPoolsAPI |
f53969cc | 59 | #define MEM_MAX_FREE 65535 /* unsigned short is max number of items per chunk */ |
d96ceb8e | 60 | |
b001e822 | 61 | class MemImplementingAllocator; |
b001e822 | 62 | class MemPoolStats; |
83d8f9f4 | 63 | |
63be0a78 | 64 | /// \ingroup MemPoolsAPI |
9837567d | 65 | /// TODO: Kill this typedef for C++ |
d96ceb8e | 66 | typedef struct _MemPoolGlobalStats MemPoolGlobalStats; |
83d8f9f4 | 67 | |
63be0a78 | 68 | /// \ingroup MemPoolsAPI |
b001e822 | 69 | class MemPoolIterator |
83d8f9f4 | 70 | { |
c5dd4956 | 71 | public: |
b001e822 | 72 | MemImplementingAllocator *pool; |
d96ceb8e | 73 | MemPoolIterator * next; |
74 | }; | |
75 | ||
63be0a78 | 76 | /** |
77 | \ingroup MemPoolsAPI | |
78 | * Object to track per-pool cumulative counters | |
79 | */ | |
b001e822 | 80 | class mgb_t |
83d8f9f4 | 81 | { |
c5dd4956 AJ |
82 | public: |
83 | mgb_t() : count(0), bytes(0) {} | |
d96ceb8e | 84 | double count; |
85 | double bytes; | |
b001e822 | 86 | }; |
d96ceb8e | 87 | |
63be0a78 | 88 | /** |
89 | \ingroup MemPoolsAPI | |
90 | * Object to track per-pool memory usage (alloc = inuse+idle) | |
91 | */ | |
b001e822 | 92 | class MemPoolMeter |
83d8f9f4 | 93 | { |
c5dd4956 | 94 | public: |
903a6eec | 95 | MemPoolMeter(); |
b001e822 | 96 | void flush(); |
9663db1c AJ |
97 | |
98 | Mem::Meter alloc; | |
99 | Mem::Meter inuse; | |
100 | Mem::Meter idle; | |
63be0a78 | 101 | |
63be0a78 | 102 | /** history Allocations */ |
903a6eec HN |
103 | mgb_t gb_allocated; |
104 | mgb_t gb_oallocated; | |
105 | ||
106 | /** account Saved Allocations */ | |
107 | mgb_t gb_saved; | |
63be0a78 | 108 | |
109 | /** account Free calls */ | |
110 | mgb_t gb_freed; | |
d96ceb8e | 111 | }; |
112 | ||
b001e822 | 113 | class MemImplementingAllocator; |
114 | ||
63be0a78 | 115 | /// \ingroup MemPoolsAPI |
c5dd4956 | 116 | class MemPools |
b001e822 | 117 | { |
c5dd4956 | 118 | public: |
b001e822 | 119 | static MemPools &GetInstance(); |
120 | MemPools(); | |
b001e822 | 121 | void flushMeters(); |
63be0a78 | 122 | |
123 | /** | |
f53969cc SM |
124 | \param label Name for the pool. Displayed in stats. |
125 | \param obj_size Size of elements in MemPool. | |
63be0a78 | 126 | */ |
b001e822 | 127 | MemImplementingAllocator * create(const char *label, size_t obj_size); |
63be0a78 | 128 | |
63be0a78 | 129 | /** |
130 | * Sets upper limit in bytes to amount of free ram kept in pools. This is | |
131 | * not strict upper limit, but a hint. When MemPools are over this limit, | |
132 | * totally free chunks are immediately considered for release. Otherwise | |
133 | * only chunks that have not been referenced for a long time are checked. | |
134 | */ | |
89646bd7 HN |
135 | void setIdleLimit(ssize_t new_idle_limit); |
136 | ssize_t idleLimit() const; | |
63be0a78 | 137 | |
138 | /** | |
139 | \par | |
140 | * Main cleanup handler. For MemPools to stay within upper idle limits, | |
2f8abb64 | 141 | * this function needs to be called periodically, preferably at some |
63be0a78 | 142 | * constant rate, eg. from Squid event. It looks through all pools and |
143 | * chunks, cleans up internal states and checks for releasable chunks. | |
144 | * | |
145 | \par | |
146 | * Between the calls to this function objects are placed onto internal | |
147 | * cache instead of returning to their home chunks, mainly for speedup | |
148 | * purpose. During that time state of chunk is not known, it is not | |
149 | * known whether chunk is free or in use. This call returns all objects | |
150 | * to their chunks and restores consistency. | |
151 | * | |
152 | \par | |
153 | * Should be called relatively often, as it sorts chunks in suitable | |
154 | * order as to reduce free memory fragmentation and increase chunk | |
155 | * utilisation. | |
156 | * Suitable frequency for cleanup is in range of few tens of seconds to | |
157 | * few minutes, depending of memory activity. | |
158 | * | |
9837567d | 159 | * TODO: DOCS: Re-write this shorter! |
63be0a78 | 160 | * |
161 | \param maxage Release all totally idle chunks that | |
162 | * have not been referenced for maxage seconds. | |
163 | */ | |
b001e822 | 164 | void clean(time_t maxage); |
63be0a78 | 165 | |
b001e822 | 166 | void setDefaultPoolChunking(bool const &); |
2414910d AJ |
167 | |
168 | MemImplementingAllocator *pools = nullptr; | |
169 | ssize_t mem_idle_limit = (2 << 20) /* 2MB */; | |
170 | int poolCount = 0; | |
171 | bool defaultIsChunked = false; | |
b001e822 | 172 | }; |
173 | ||
63be0a78 | 174 | /** |
175 | \ingroup MemPoolsAPI | |
176 | * a pool is a [growing] space for objects of the same size | |
177 | */ | |
b001e822 | 178 | class MemAllocator |
179 | { | |
180 | public: | |
181 | MemAllocator (char const *aLabel); | |
182 | virtual ~MemAllocator() {} | |
63be0a78 | 183 | |
184 | /** | |
f53969cc SM |
185 | \param stats Object to be filled with statistical data about pool. |
186 | \retval Number of objects in use, ie. allocated. | |
63be0a78 | 187 | */ |
8cfaefed | 188 | virtual int getStats(MemPoolStats * stats, int accumulate = 0) = 0; |
63be0a78 | 189 | |
b001e822 | 190 | virtual MemPoolMeter const &getMeter() const = 0; |
63be0a78 | 191 | |
192 | /** | |
193 | * Allocate one element from the pool | |
194 | */ | |
b001e822 | 195 | virtual void *alloc() = 0; |
63be0a78 | 196 | |
197 | /** | |
198 | * Free a element allocated by MemAllocator::alloc() | |
199 | */ | |
dc47f531 | 200 | virtual void freeOne(void *) = 0; |
63be0a78 | 201 | |
b001e822 | 202 | virtual char const *objectType() const; |
203 | virtual size_t objectSize() const = 0; | |
9f9e06f3 | 204 | virtual int getInUseCount() = 0; |
3b08e399 | 205 | void zeroBlocks(bool doIt) {doZero = doIt;} |
a3efa961 | 206 | int inUseCount(); |
63be0a78 | 207 | |
208 | /** | |
209 | * Allows you tune chunk size of pooling. Objects are allocated in chunks | |
210 | * instead of individually. This conserves memory, reduces fragmentation. | |
211 | * Because of that memory can be freed also only in chunks. Therefore | |
212 | * there is tradeoff between memory conservation due to chunking and free | |
213 | * memory fragmentation. | |
214 | * | |
215 | \note As a general guideline, increase chunk size only for pools that keep | |
216 | * very many items for relatively long time. | |
217 | */ | |
ced8def3 | 218 | virtual void setChunkSize(size_t) {} |
cb6d4984 | 219 | |
63be0a78 | 220 | /** |
f53969cc | 221 | \param minSize Minimum size needed to be allocated. |
63be0a78 | 222 | \retval n Smallest size divisible by sizeof(void*) |
223 | */ | |
cb6d4984 | 224 | static size_t RoundedSize(size_t minSize); |
63be0a78 | 225 | |
60da11d3 | 226 | protected: |
3b08e399 AJ |
227 | /** Whether to zero memory on initial allocation and on return to the pool. |
228 | * | |
229 | * We do this on some pools because many object constructors are/were incomplete | |
230 | * and we are afraid some code may use the object after free. | |
231 | * These probems are becoming less common, so when possible set this to false. | |
232 | */ | |
233 | bool doZero; | |
63be0a78 | 234 | |
b001e822 | 235 | private: |
236 | const char *label; | |
237 | }; | |
238 | ||
63be0a78 | 239 | /// \ingroup MemPoolsAPI |
b001e822 | 240 | class MemImplementingAllocator : public MemAllocator |
241 | { | |
c5dd4956 | 242 | public: |
b001e822 | 243 | MemImplementingAllocator(char const *aLabel, size_t aSize); |
2be7332c | 244 | virtual ~MemImplementingAllocator(); |
b001e822 | 245 | virtual MemPoolMeter const &getMeter() const; |
246 | virtual MemPoolMeter &getMeter(); | |
247 | virtual void flushMetersFull(); | |
248 | virtual void flushMeters(); | |
63be0a78 | 249 | |
250 | /** | |
251 | * Allocate one element from the pool | |
252 | */ | |
b001e822 | 253 | virtual void *alloc(); |
63be0a78 | 254 | |
255 | /** | |
256 | * Free a element allocated by MemImplementingAllocator::alloc() | |
257 | */ | |
dc47f531 | 258 | virtual void freeOne(void *); |
63be0a78 | 259 | |
b001e822 | 260 | virtual bool idleTrigger(int shift) const = 0; |
261 | virtual void clean(time_t maxage) = 0; | |
b001e822 | 262 | virtual size_t objectSize() const; |
9f9e06f3 | 263 | virtual int getInUseCount() = 0; |
c5dd4956 | 264 | protected: |
b001e822 | 265 | virtual void *allocate() = 0; |
2be7332c | 266 | virtual void deallocate(void *, bool aggressive) = 0; |
b001e822 | 267 | MemPoolMeter meter; |
8cfaefed | 268 | int memPID; |
c5dd4956 | 269 | public: |
b001e822 | 270 | MemImplementingAllocator *next; |
c5dd4956 | 271 | public: |
b001e822 | 272 | size_t alloc_calls; |
273 | size_t free_calls; | |
903a6eec | 274 | size_t saved_calls; |
d96ceb8e | 275 | size_t obj_size; |
b001e822 | 276 | }; |
277 | ||
63be0a78 | 278 | /// \ingroup MemPoolsAPI |
b001e822 | 279 | class MemPoolStats |
83d8f9f4 | 280 | { |
c5dd4956 | 281 | public: |
b001e822 | 282 | MemAllocator *pool; |
d96ceb8e | 283 | const char *label; |
284 | MemPoolMeter *meter; | |
285 | int obj_size; | |
286 | int chunk_capacity; | |
287 | int chunk_size; | |
288 | ||
289 | int chunks_alloc; | |
290 | int chunks_inuse; | |
291 | int chunks_partial; | |
292 | int chunks_free; | |
293 | ||
294 | int items_alloc; | |
295 | int items_inuse; | |
296 | int items_idle; | |
297 | ||
298 | int overhead; | |
299 | }; | |
300 | ||
63be0a78 | 301 | /// \ingroup MemPoolsAPI |
9837567d | 302 | /// TODO: Classify and add constructor/destructor to initialize properly. |
c5dd4956 | 303 | struct _MemPoolGlobalStats { |
d96ceb8e | 304 | MemPoolMeter *TheMeter; |
305 | ||
306 | int tot_pools_alloc; | |
307 | int tot_pools_inuse; | |
308 | int tot_pools_mempid; | |
309 | ||
310 | int tot_chunks_alloc; | |
311 | int tot_chunks_inuse; | |
312 | int tot_chunks_partial; | |
313 | int tot_chunks_free; | |
314 | ||
315 | int tot_items_alloc; | |
316 | int tot_items_inuse; | |
317 | int tot_items_idle; | |
318 | ||
319 | int tot_overhead; | |
89646bd7 | 320 | ssize_t mem_idle_limit; |
d96ceb8e | 321 | }; |
322 | ||
63be0a78 | 323 | /// \ingroup MemPoolsAPI |
a8ef0b3f | 324 | /// Creates a named MemPool of elements with the given size |
04eb0689 | 325 | #define memPoolCreate MemPools::GetInstance().create |
326 | ||
d96ceb8e | 327 | /* Allocator API */ |
63be0a78 | 328 | /** |
329 | \ingroup MemPoolsAPI | |
330 | * Initialise iteration through all of the pools. | |
a8ef0b3f | 331 | * \returns Iterator for use by memPoolIterateNext() and memPoolIterateDone() |
63be0a78 | 332 | */ |
b001e822 | 333 | extern MemPoolIterator * memPoolIterate(void); |
63be0a78 | 334 | |
335 | /** | |
336 | \ingroup MemPoolsAPI | |
337 | * Get next pool pointer, until getting NULL pointer. | |
338 | */ | |
b001e822 | 339 | extern MemImplementingAllocator * memPoolIterateNext(MemPoolIterator * iter); |
63be0a78 | 340 | |
341 | /** | |
342 | \ingroup MemPoolsAPI | |
343 | * Should be called after finished with iterating through all pools. | |
344 | */ | |
b001e822 | 345 | extern void memPoolIterateDone(MemPoolIterator ** iter); |
d96ceb8e | 346 | |
63be0a78 | 347 | /** |
348 | \ingroup MemPoolsAPI | |
63be0a78 | 349 | * |
a8ef0b3f | 350 | * Fills a MemPoolGlobalStats with statistical data about overall |
63be0a78 | 351 | * usage for all pools. |
352 | * | |
a8ef0b3f AJ |
353 | * \param stats Object to be filled with statistical data. |
354 | * | |
355 | * \return Number of pools that have at least one object in use. | |
63be0a78 | 356 | * Ie. number of dirty pools. |
357 | */ | |
b001e822 | 358 | extern int memPoolGetGlobalStats(MemPoolGlobalStats * stats); |
d96ceb8e | 359 | |
63be0a78 | 360 | /// \ingroup MemPoolsAPI |
b001e822 | 361 | extern int memPoolsTotalAllocated(void); |
d96ceb8e | 362 | |
8cfaefed | 363 | #endif /* _MEM_POOL_H_ */ |
f53969cc | 364 |