Lumiera  0.pre.03
»edit your freedom«
tracking-allocator.cpp
Go to the documentation of this file.
1 /*
2  TrackingAllocator - custom allocator for memory management diagnostics
3 
4  Copyright (C)
5  2024, Hermann Vosseler <Ichthyostega@web.de>
6 
7   **Lumiera** is free software; you can redistribute it and/or modify it
8   under the terms of the GNU General Public License as published by the
9   Free Software Foundation; either version 2 of the License, or (at your
10   option) any later version. See the file COPYING for further details.
11 
12 * *****************************************************************/
13 
14 
35 #include "lib/iter-explorer.hpp"
36 #include "lib/depend.hpp"
37 #include "lib/util.hpp"
38 
39 
40 #include <string>
41 #include <unordered_map>
42 
43 using std::string;
44 using std::make_shared;
45 using std::make_pair;
46 using util::contains;
47 using util::joinDash;
48 using util::showAdr;
49 
50 
51 namespace lib {
52 namespace test{
53 
54 
58  class MemoryPool
60  {
61  // using the allocated memory location as key
62  using Location = void*;
63  struct LocationHash
64  {
65  HashVal operator() (Location const& loc) const { return HashVal(loc); }
66  };
67 
69  struct Allocation
71  {
73  size_t entryID{0};
74  };
75 
76  using AllocTab = std::unordered_map<const Location, Allocation, LocationHash>;
77 
78 
79  Literal poolID_;
80  AllocTab allocs_;
81  HashVal checksum_;
82  size_t entryNr_;
83 
84  public:
85  MemoryPool (Literal id)
86  : poolID_{id}
87  , allocs_{}
88  , checksum_{0}
89  , entryNr_{0}
90  { }
91 
92  Allocation& addAlloc (size_t bytes);
93  void discardAlloc (void* loc, size_t);
94 
95  Allocation const* findAlloc (Location) const;
96 
97  HashVal getChecksum() const;
98  size_t getAllocationCnt() const;
99  size_t calcAllocSize() const;
100 
101  Literal getPoolID() const { return poolID_; }
102  };
103 
104 
105  namespace { // implementation details for common memory management
106 
107  /* === Logging primitives === */
108 
109  inline EventLog& log() { return TrackingAllocator::log; }
110 
111  template<typename...XS>
112  inline void
113  logAlarm (XS const& ...xs)
114  {
115  log().error (joinDash(xs...));
116  }
117 
118  template<typename...ARGS>
119  inline void
120  logAlloc (Literal pool, string fun, ARGS const& ...args)
121  {
122  log().call (pool,fun,args...);
123  }
124 
125 
129  { // note: obsolete entries never discarded
130  using PoolTab = std::unordered_map<Literal, std::weak_ptr<MemoryPool>>;
131 
132  PoolTab pools_{};
133 
134  public:
135  static PoolHandle locate (Literal poolID);
136 
137  private:
138  PoolHandle fetch_or_create (Literal poolID);
139  };
140 
143  Depend<PoolRegistry> poolReg;
144 
145 
147  PoolHandle
148  PoolRegistry::locate (Literal poolID)
149  {
150  return poolReg().fetch_or_create (poolID);
151  }
152 
153 
154  PoolHandle
155  PoolRegistry::fetch_or_create (Literal poolID)
156  {
157  auto& entry = pools_[poolID];
158  auto handle = entry.lock();
159  if (handle) return handle;
160 
161  // create a new pool and enrol it for given ID
162  PoolHandle newPool = make_shared<MemoryPool> (poolID);
163  entry = newPool;
164  return newPool;
165  }
166  }//(End)Implementation memory pools and registration
167 
168 
169 
170 
171 
173  : mem_{PoolRegistry::locate(GLOBAL)}
174  { }
175 
177  : mem_{PoolRegistry::locate(id)}
178  { }
179 
180 
187  TrackingAllocator::Location
189  {
190  ENSURE (mem_);
191  return mem_->addAlloc (bytes)
192  .buff.front();
193  }
194 
200  void
201  TrackingAllocator::deallocate (Location loc, size_t bytes) noexcept
202  {
203  ENSURE (mem_);
204  mem_->discardAlloc (loc, bytes);
205  }
206 
208  MemoryPool::addAlloc (size_t bytes)
209  {
210  Allocation newAlloc;
211  newAlloc.buff.allocate (bytes);
212  Location loc = newAlloc.buff.front();
213  ASSERT (not contains (allocs_, loc));
214  newAlloc.entryID = ++entryNr_;
215  logAlloc (poolID_, "allocate", bytes, newAlloc.entryID, showAdr(loc));
216  checksum_ += newAlloc.entryID * bytes;
217  return allocs_.emplace (loc, move(newAlloc))
218  .first->second;
219  }
220 
221  void
222  MemoryPool::discardAlloc (Location loc, size_t bytes)
223  {
224  if (contains (allocs_, loc))
225  {
226  auto& entry = allocs_[loc];
227  ASSERT (entry.buff);
228  ASSERT (entry.buff.front() == loc);
229  if (entry.buff.size() != bytes) // *deliberately* tolerating wrong data, but log incident to support diagnostics
230  logAlarm ("SizeMismatch", bytes, "≠", entry.buff.size(), entry.entryID, showAdr(loc));
231 
232  logAlloc (poolID_, "deallocate", bytes, entry.entryID, bytes, showAdr(loc));
233  checksum_ -= entry.entryID * bytes; // Note: using the given size (if wrong ⟿ checksum mismatch)
234  allocs_.erase(loc);
235  }
236  else // deliberately no exception here (for better diagnostics)
237  logAlarm ("FreeUnknown", bytes, showAdr(loc));
238  }
239 
240 
242  MemoryPool::findAlloc (Location loc) const
243  {
244  return contains (allocs_, loc)? & allocs_.at(loc)
245  : nullptr;
246  }
247 
248 
249  HashVal
250  MemoryPool::getChecksum() const
251  {
252  return checksum_;
253  }
254 
255  size_t
256  MemoryPool::getAllocationCnt() const
257  {
258  return allocs_.size();
259  }
260 
261  size_t
262  MemoryPool::calcAllocSize() const
263  {
264  return explore(allocs_)
265  .transform ([](auto& it){ return it->second.buff.size(); })
266  .resultSum();
267  }
268 
269 
270 
271  /* ===== Diagnostics ===== */
272 
273  EventLog TrackingAllocator::log{"test::TrackingAllocator"};
274 
275 
277  HashVal
279  {
280  PoolHandle pool = PoolRegistry::locate (poolID);
281  return pool->getChecksum();
282  }
283 
285  size_t
287  {
288  PoolHandle pool = PoolRegistry::locate (poolID);
289  return pool.use_count() - 1;
290  }
291 
293  size_t
295  {
296  PoolHandle pool = PoolRegistry::locate (poolID);
297  return pool->getAllocationCnt();
298  }
299 
301  size_t
303  {
304  PoolHandle pool = PoolRegistry::locate (poolID);
305  return pool->calcAllocSize();
306  }
307 
308 
310  bool
311  TrackingAllocator::manages (Location memLoc) const
312  {
313  ENSURE (mem_);
314  return bool(mem_->findAlloc (memLoc));
315  }
316 
318  size_t
319  TrackingAllocator::getSize(Location memLoc) const
320  {
321  ENSURE (mem_);
322  auto* entry = mem_->findAlloc (memLoc);
323  return entry? entry->buff.size()
324  : 0;
325  }
326 
328  HashVal
329  TrackingAllocator::getID (Location memLoc) const
330  {
331  ENSURE (mem_);
332  auto* entry = mem_->findAlloc (memLoc);
333  return entry? entry->entryID
334  : 0;
335  }
336 
337  Literal
338  TrackingAllocator::poolID() const
339  {
340  ENSURE (mem_);
341  return mem_->getPoolID();
342  }
343 
344 
345 
346 }} // namespace lib::test
Depend< MemoryPool > globalPool
singleton for default pool
Location allocate(size_t n)
Allot a memory block of given size bytes.
static HashVal checksum(Literal pool=GLOBAL)
get Checksum for specific mem-pool
auto explore(IT &&srcSeq)
start building a IterExplorer by suitably wrapping the given iterable source.
size_t getSize(Location) const
retrieve the registered size of this allocation, if known.
AnyPair entry(Query< TY > const &query, typename WrapReturn< TY >::Wrapper &obj)
helper to simplify creating mock table entries, wrapped correctly
Definition: run.hpp:40
Any copy and copy construction prohibited.
Definition: nocopy.hpp:37
inline string literal This is a marker type to indicate that
Definition: symbol.hpp:76
Helper to log and verify the occurrence of events.
Definition: event-log.hpp:275
Types marked with this mix-in may be moved but not copied.
Definition: nocopy.hpp:49
static size_t numBytes(Literal pool=GLOBAL)
calculate currently allotted Bytes for mem-pool
bool manages(Location) const
probe if this allocator pool did allocate the given memory location
Access point to singletons and other kinds of dependencies designated by type.
Definition: depend.hpp:280
Implementation namespace for support and library code.
void deallocate(Location, size_t=0) noexcept
Discard and forget an allocation created through this allocator.
string joinDash(ARGS const &...args)
shortcut: join directly with dashes
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
EventLog & call(string target, string function)
Log occurrence of a function call with no arguments.
Definition: event-log.cpp:690
Unittest helper code: a custom allocator to track memory usage.
HashVal getID(Location) const
retrieve the internal registration ID for this allocation.
A raw memory block with proper alignment and array access.
Singleton services and Dependency Injection.
registration entry to maintain a single allocation
size_t HashVal
a STL compatible hash value
Definition: hash-value.h:52
static size_t numAlloc(Literal pool=GLOBAL)
get active allocation count for mem-pool
static size_t use_count(Literal pool=GLOBAL)
determine number of active front-end handles
TrackingAllocator()
can be default created to attach to a common pool
Building tree expanding and backtracking evaluations within hierarchical scopes.
bool contains(SEQ const &cont, typename SEQ::const_reference val)
shortcut for brute-force containment test in any sequential container
Definition: util.hpp:255
EventLog & error(string text)
Log an error note.
Definition: event-log.cpp:717