#ifndef _MEM_POOL_H_ #define _MEM_POOL_H_ /** \defgroup MemPoolsAPI Memory Management (Memory Pool Allocator) \ingroup Components * *\par * MemPools are a pooled memory allocator running on top of malloc(). It's * purpose is to reduce memory fragmentation and provide detailed statistics * on memory consumption. * \par * Preferably all memory allocations in Squid should be done using MemPools * or one of the types built on top of it (i.e. cbdata). * \note Usually it is better to use cbdata types as these gives you additional * safeguards in references and typechecking. However, for high usage pools where * the cbdata functionality of cbdata is not required directly using a MemPool * might be the way to go. */ #include "util.h" #include "memMeter.h" #include "splay.h" #if HAVE_GNUMALLOC_H #include #elif HAVE_MALLOC_H #include #endif #if HAVE_MEMORY_H #include #endif #if !M_MMAP_MAX #if USE_DLMALLOC #define M_MMAP_MAX -4 #endif #endif /// \ingroup MemPoolsAPI #define MB ((size_t)1024*1024) /// \ingroup MemPoolsAPI #define toMB(size) ( ((double) size) / MB ) /// \ingroup MemPoolsAPI #define toKB(size) ( (size + 1024 - 1) / 1024 ) /// \ingroup MemPoolsAPI #define MEM_PAGE_SIZE 4096 /// \ingroup MemPoolsAPI #define MEM_CHUNK_SIZE 4096 * 4 /// \ingroup MemPoolsAPI #define MEM_CHUNK_MAX_SIZE 256 * 1024 /* 2MB */ /// \ingroup MemPoolsAPI #define MEM_MIN_FREE 32 /// \ingroup MemPoolsAPI #define MEM_MAX_FREE 65535 /* unsigned short is max number of items per chunk */ class MemImplementingAllocator; class MemPoolStats; /// \ingroup MemPoolsAPI /// \todo Kill this typedef for C++ typedef struct _MemPoolGlobalStats MemPoolGlobalStats; /// \ingroup MemPoolsAPI class MemPoolIterator { public: MemImplementingAllocator *pool; MemPoolIterator * next; }; /** \ingroup MemPoolsAPI * Object to track per-pool cumulative counters */ class mgb_t { public: mgb_t() : count(0), bytes(0) {} double count; double bytes; }; /** \ingroup MemPoolsAPI * Object to track per-pool memory usage (alloc = inuse+idle) */ class MemPoolMeter { public: MemPoolMeter(); void flush(); MemMeter alloc; MemMeter inuse; MemMeter idle; /** history Allocations */ mgb_t gb_allocated; mgb_t gb_oallocated; /** account Saved Allocations */ mgb_t gb_saved; /** account Free calls */ mgb_t gb_freed; }; class MemImplementingAllocator; /// \ingroup MemPoolsAPI class MemPools { public: static MemPools &GetInstance(); MemPools(); void init(); void flushMeters(); /** \param label Name for the pool. Displayed in stats. \param obj_size Size of elements in MemPool. */ MemImplementingAllocator * create(const char *label, size_t obj_size); /** * Sets upper limit in bytes to amount of free ram kept in pools. This is * not strict upper limit, but a hint. When MemPools are over this limit, * totally free chunks are immediately considered for release. Otherwise * only chunks that have not been referenced for a long time are checked. */ void setIdleLimit(ssize_t new_idle_limit); ssize_t idleLimit() const; /** \par * Main cleanup handler. For MemPools to stay within upper idle limits, * this function needs to be called periodically, preferrably at some * constant rate, eg. from Squid event. It looks through all pools and * chunks, cleans up internal states and checks for releasable chunks. * \par * Between the calls to this function objects are placed onto internal * cache instead of returning to their home chunks, mainly for speedup * purpose. During that time state of chunk is not known, it is not * known whether chunk is free or in use. This call returns all objects * to their chunks and restores consistency. * \par * Should be called relatively often, as it sorts chunks in suitable * order as to reduce free memory fragmentation and increase chunk * utilisation. * Suitable frequency for cleanup is in range of few tens of seconds to * few minutes, depending of memory activity. * \todo DOCS: Re-write this shorter! * \param maxage Release all totally idle chunks that * have not been referenced for maxage seconds. */ void clean(time_t maxage); void setDefaultPoolChunking(bool const &); MemImplementingAllocator *pools; ssize_t mem_idle_limit; int poolCount; bool defaultIsChunked; private: static MemPools *Instance; }; /** \ingroup MemPoolsAPI * a pool is a [growing] space for objects of the same size */ class MemAllocator { public: MemAllocator (char const *aLabel); virtual ~MemAllocator() {} /** \param stats Object to be filled with statistical data about pool. \retval Number of objects in use, ie. allocated. */ virtual int getStats(MemPoolStats * stats, int accumulate = 0) = 0; virtual MemPoolMeter const &getMeter() const = 0; /** * Allocate one element from the pool */ virtual void *alloc() = 0; /** * Free a element allocated by MemAllocator::alloc() */ virtual void freeOne(void *) = 0; virtual char const *objectType() const; virtual size_t objectSize() const = 0; virtual int getInUseCount() = 0; void zeroOnPush(bool doIt); int inUseCount(); /** * Allows you tune chunk size of pooling. Objects are allocated in chunks * instead of individually. This conserves memory, reduces fragmentation. * Because of that memory can be freed also only in chunks. Therefore * there is tradeoff between memory conservation due to chunking and free * memory fragmentation. * \note As a general guideline, increase chunk size only for pools that keep * very many items for relatively long time. */ virtual void setChunkSize(size_t chunksize) {} /** \param minSize Minimum size needed to be allocated. \retval n Smallest size divisible by sizeof(void*) */ static size_t RoundedSize(size_t minSize); protected: bool doZeroOnPush; private: const char *label; }; /** \ingroup MemPoolsAPI * Support late binding of pool type for allocator agnostic classes */ class MemAllocatorProxy { public: inline MemAllocatorProxy(char const *aLabel, size_t const &); /** * Allocate one element from the pool */ void *alloc(); /** * Free a element allocated by MemAllocatorProxy::alloc() */ void freeOne(void *); int inUseCount() const; size_t objectSize() const; MemPoolMeter const &getMeter() const; /** \param stats Object to be filled with statistical data about pool. \retval Number of objects in use, ie. allocated. */ int getStats(MemPoolStats * stats); char const * objectType() const; private: MemAllocator *getAllocator() const; const char *label; size_t size; mutable MemAllocator *theAllocator; }; /* help for classes */ /** \ingroup MemPoolsAPI \hideinitializer * * This macro is intended for use within the declaration of a class. */ #define MEMPROXY_CLASS(CLASS) \ inline void *operator new(size_t); \ inline void operator delete(void *); \ static inline MemAllocatorProxy &Pool() /** \ingroup MemPoolsAPI \hideinitializer * * This macro is intended for use within the .h or .cci of a class as appropriate. */ #define MEMPROXY_CLASS_INLINE(CLASS) \ MemAllocatorProxy& CLASS::Pool() \ { \ static MemAllocatorProxy thePool(#CLASS, sizeof (CLASS)); \ return thePool; \ } \ \ void * \ CLASS::operator new (size_t byteCount) \ { \ /* derived classes with different sizes must implement their own new */ \ assert (byteCount == sizeof (CLASS)); \ \ return Pool().alloc(); \ } \ \ void \ CLASS::operator delete (void *address) \ { \ Pool().freeOne(address); \ } /// \ingroup MemPoolsAPI class MemImplementingAllocator : public MemAllocator { public: MemImplementingAllocator(char const *aLabel, size_t aSize); virtual ~MemImplementingAllocator(); virtual MemPoolMeter const &getMeter() const; virtual MemPoolMeter &getMeter(); virtual void flushMetersFull(); virtual void flushMeters(); /** * Allocate one element from the pool */ virtual void *alloc(); /** * Free a element allocated by MemImplementingAllocator::alloc() */ virtual void freeOne(void *); virtual bool idleTrigger(int shift) const = 0; virtual void clean(time_t maxage) = 0; virtual size_t objectSize() const; virtual int getInUseCount() = 0; protected: virtual void *allocate() = 0; virtual void deallocate(void *, bool aggressive) = 0; MemPoolMeter meter; int memPID; public: MemImplementingAllocator *next; public: size_t alloc_calls; size_t free_calls; size_t saved_calls; size_t obj_size; }; /// \ingroup MemPoolsAPI class MemPoolStats { public: MemAllocator *pool; const char *label; MemPoolMeter *meter; int obj_size; int chunk_capacity; int chunk_size; int chunks_alloc; int chunks_inuse; int chunks_partial; int chunks_free; int items_alloc; int items_inuse; int items_idle; int overhead; }; /// \ingroup MemPoolsAPI /// \todo Classify and add constructor/destructor to initialize properly. struct _MemPoolGlobalStats { MemPoolMeter *TheMeter; int tot_pools_alloc; int tot_pools_inuse; int tot_pools_mempid; int tot_chunks_alloc; int tot_chunks_inuse; int tot_chunks_partial; int tot_chunks_free; int tot_items_alloc; int tot_items_inuse; int tot_items_idle; int tot_overhead; ssize_t mem_idle_limit; }; /// \ingroup MemPoolsAPI #define memPoolCreate MemPools::GetInstance().create /* Allocator API */ /** \ingroup MemPoolsAPI * Initialise iteration through all of the pools. \retval Iterator for use by memPoolIterateNext() and memPoolIterateDone() */ extern MemPoolIterator * memPoolIterate(void); /** \ingroup MemPoolsAPI * Get next pool pointer, until getting NULL pointer. */ extern MemImplementingAllocator * memPoolIterateNext(MemPoolIterator * iter); /** \ingroup MemPoolsAPI * Should be called after finished with iterating through all pools. */ extern void memPoolIterateDone(MemPoolIterator ** iter); /** \ingroup MemPoolsAPI \todo Stats API - not sured how to refactor yet * * Fills MemPoolGlobalStats with statistical data about overall * usage for all pools. * \retval Number of pools that have at least one object in use. * Ie. number of dirty pools. */ extern int memPoolGetGlobalStats(MemPoolGlobalStats * stats); /// \ingroup MemPoolsAPI extern int memPoolInUseCount(MemAllocator *); /// \ingroup MemPoolsAPI extern int memPoolsTotalAllocated(void); MemAllocatorProxy::MemAllocatorProxy(char const *aLabel, size_t const &aSize) : label (aLabel), size(aSize), theAllocator (NULL) { } #endif /* _MEM_POOL_H_ */