CUBRID Engine  latest
resource_tracker.hpp
Go to the documentation of this file.
1 /*
2  * Copyright 2008 Search Solution Corporation
3  * Copyright 2016 CUBRID Corporation
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  * http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  *
17  */
18 
19 //
20 // resource_tracker.hpp - interface to track and debug resource usage (allocations, page fixes)
21 //
22 // the purpose of this interface is to track resource usage and detect possible issues:
23 //
24 // 1. resource leaks (at the end of the scope, used resources are not freed/retired)
25 // 2. resource over-usage (a resource exceeds a maximum allowed of usage).
26 // 3. invalid free/retire.
27 //
28 // how to use:
29 //
30 // 1. specialize your tracker based on resource type:
31 //
32 // using my_tracker_type = cubbase::resource_tracker<my_resource_type>;
33 //
34 // note - my_resource_type must be comparable; resource_tracker uses internally a std::map specialized by
35 // provided type and std::less of same type.
36 // todo - template for compare function
37 //
38 // 2. create an instance of your tracker.
39 //
40 // my_tracker_type my_tracker ("My Tracker", true, MAX_RESOURCES, "my res", MAX_REUSE);
41 //
42 // 3. start tracking:
43 //
44 // my_tracker.push_track ();
45 //
46 // 4. track resource; resource over-usage may be detected:
47 //
48 // my_tracker.increment (__FILE__, __LINE__, my_res_to_track, use_count /* default 1 */)
49 //
50 // 5. un-track resource; resource invalid free/retire may be detected:
51 //
52 // my_tracker.decrement (my_res_to_track, use_count /* default 1 */);
53 //
54 // 6. stop tracking; resource leaks may be detected:
55 //
56 // my_tracker.pop_track ();
57 //
58 
59 #ifndef _RESOURCE_TRACKER_HPP_
60 #define _RESOURCE_TRACKER_HPP_
61 
62 #include "fileline_location.hpp"
63 
64 #include <map>
65 #include <forward_list>
66 #include <sstream>
67 
68 #include <cassert>
69 
70 namespace cubbase
71 {
72  //
73  // resource_tracker_item - helper structure used by resource tracker to store information one resource.
74  //
76  {
77  public:
78 
79  // ctor - file, line, initial reuse count
80  resource_tracker_item (const char *fn_arg, int l_arg, unsigned reuse);
81 
82  fileline_location m_first_location; // first increment location
83  unsigned m_reuse_count; // current reuse count
84  };
85  // printing a resource_tracker_item to output stream
86  std::ostream &operator<< (std::ostream &os, const resource_tracker_item &item);
87 
88  //
89  // resource tracker - see more details on file description
90  //
91  // Res template is used to allow any resource specialization. It is restricted to comparable types.
92  //
93  template <typename Res>
95  {
96  public:
97 
98  // internal types
99  using res_type = Res;
100  using map_type = std::map<res_type, resource_tracker_item>;
101 
102  // ctor/dtor
103  resource_tracker (const char *name, bool enabled, std::size_t max_resources, const char *res_name,
104  unsigned max_reuse = 1);
105  ~resource_tracker (void);
106 
107  // using resources
108  // increment "uses" for given resource.
109  // if it is first use, file & line are saved.
110  // detects if maximum uses is exceeded.
111  void increment (const char *filename, const int line, const res_type &res, unsigned use_count = 1);
112  // decrement "uses" for given resource.
113  // if use count becomes 0, resource is removed from tracker
114  // detects invalid free/retire (existing use count is less than given count)
115  void decrement (const res_type &res, unsigned use_count = 1);
116 
117  // tracking; is stackable (allows nested tracking).
118  // push tracking; a new level of tracking is started
119  void push_track (void);
120  // pop tracking; finish last level of tacking. resource leaks are checked for last level
121  void pop_track (void);
122  // clear all tracks.
123  void clear_all (void);
124 
125  private:
126 
127  map_type &get_current_map (void); // get map for current level of tracking
128 
129  void abort (void); // abort tracking when maximum number of resources is exceeded
130  void dump (void) const; // dump all tracked resources on all levels
131  void dump_map (const map_type &map, std::ostream &out) const; // dump one tracker level
132  unsigned get_total_use_count (void) const; // compute and return total use count
133 
134  const char *m_name; // tracker name; used for dumping
135  const char *m_res_alias; // resource alias; used for dumping
136 
137  bool m_enabled; // if disabled, all tracker functions do nothing.
138  // it is used to avoid branching when calling tracker functions.
139  bool m_is_aborted; // set to true when tracking had to be aborted;
140  std::size_t m_max_resources; // maximum number of resources allowed to track; if exceeded, the tracker
141  // is aborted
142  std::size_t m_resource_count; // current resource count
143  unsigned m_max_reuse; // maximum reuse for a resource. by default is one.
144 
145  // a list of maps of tracked resources.
146  // each map represents one level in tracker
147  // list holds all levels
148  std::forward_list<map_type> m_tracked_stack;
149  };
150 
152  // other functions & stuff
154 
155  // functions used mainly to disable debug crashing and convert into an error; for unit testing
156  // todo - convert from global to instance
157 
158  // pop existing error
159  // returns true if error exists and false otherwise. error is reset.
160  bool restrack_pop_error (void);
161  // if set_error is true, error is set. if set_error is false, error remains as is.
162  void restrack_set_error (bool set_error);
163  // set assert mode
164  // if suppress is true, assert is suppressed and error is set instead; if false, assert is allowed.
165  void restrack_set_suppress_assert (bool suppress);
166  // get current assert mode
167  bool restrack_is_assert_suppressed (void);
168  // check condition to be true; based on assert mode, assert is hit or error is set
169  inline void restrack_assert (bool cond);
170 
171  void restrack_log (const std::string &str);
172 
174  // template/inline implementation
176 
177  template <typename Res>
178  resource_tracker<Res>::resource_tracker (const char *name, bool enabled, std::size_t max_resources,
179  const char *res_name, unsigned max_reuse /* = 1 */)
180  : m_name (name)
181  , m_res_alias (res_name)
182  , m_enabled (enabled)
183  , m_is_aborted (false)
184  , m_max_resources (max_resources)
185  , m_resource_count (0)
186  , m_max_reuse (max_reuse)
187  , m_tracked_stack ()
188  {
189  //
190  }
191 
192  template <typename Res>
194  {
195  // check no leaks
196  restrack_assert (m_tracked_stack.empty ());
198 
199  clear_all ();
200  }
201 
202  template <typename Res>
203  void
204  resource_tracker<Res>::increment (const char *filename, const int line, const res_type &res,
205  unsigned use_count /* = 1 */)
206  {
207  if (!m_enabled || m_is_aborted)
208  {
209  return;
210  }
211 
212  if (m_tracked_stack.empty ())
213  {
214  // not active
215  return;
216  }
217 
218  // given use_count cannot exceed max reuse
219  restrack_assert (use_count <= m_max_reuse);
220 
221  // get current level map
222  map_type &map = get_current_map ();
223 
224  // add resource to map or increment the use count of existing entry
225  //
226  // std::map::try_emplace returns a pair <it_insert_or_found, did_insert>
227  // it_insert_or_found = did_insert ? iterator to inserted : iterator to existing
228  // it_insert_or_found is an iterator to pair <res_type, resource_tracker_item>
229  //
230  // as of c++17 we could just do:
231  // auto inserted = map.try_emplace (res, filename, line, use_count);
232  //
233  // however, for now we have to use next piece of code I got from
234  // https://en.cppreference.com/w/cpp/container/map/emplace
235  auto inserted = map.emplace (std::piecewise_construct, std::forward_as_tuple (res),
236  std::forward_as_tuple (filename, line, use_count));
237  if (inserted.second)
238  {
239  // did insert
241  {
242  // max size reached. abort the tracker
243  abort ();
244  return;
245  }
246  }
247  else
248  {
249  // already exists
250  // increment use_count
251  inserted.first->second.m_reuse_count += use_count;
252 
253  restrack_assert (inserted.first->second.m_reuse_count <= m_max_reuse);
254  }
255  }
256 
257  template <typename Res>
258  void
259  resource_tracker<Res>::decrement (const res_type &res, unsigned use_count /* = 1 */)
260  {
261  if (!m_enabled || m_is_aborted)
262  {
263  return;
264  }
265 
266  if (m_tracked_stack.empty ())
267  {
268  // not active
269  return;
270  }
271 
272  map_type &map = get_current_map ();
273 
274  // note - currently resources from different levels cannot be mixed. one level cannot free resources from another
275  // level... if we want to do that, I don't think the stacking has any use anymore. we can just keep only
276  // one level and that's that.
277 
278  // std::map::find returns a pair to <res_type, resource_tracker_item>
279  auto tracked = map.find (res);
280  if (tracked == map.end ())
281  {
282  // not found; invalid free
283  restrack_assert (false);
284  }
285  else
286  {
287  // decrement
288  if (use_count > tracked->second.m_reuse_count)
289  {
290  // more than expected; invalid free
291  restrack_assert (false);
292  map.erase (tracked);
294  }
295  else
296  {
297  tracked->second.m_reuse_count -= use_count;
298  if (tracked->second.m_reuse_count == 0)
299  {
300  // remove from map
301  map.erase (tracked);
303  }
304  }
305  }
306  }
307 
308  template <typename Res>
309  unsigned
311  {
312  // iterate through all resources and collect use count
313  unsigned total = 0;
314  for (auto stack_it : m_tracked_stack)
315  {
316  for (auto map_it : stack_it)
317  {
318  total += map_it.second.m_reuse_count;
319  }
320  }
321  return total;
322  }
323 
324  template <typename Res>
327  {
328  return m_tracked_stack.front ();
329  }
330 
331  template <typename Res>
332  void
334  {
335  // clear stack
336  while (!m_tracked_stack.empty ())
337  {
338  pop_track ();
339  }
341  }
342 
343  template <typename Res>
344  void
346  {
347  m_is_aborted = true;
348  }
349 
350  template <typename Res>
351  void
353  {
354  std::stringstream out;
355 
356  out << std::endl;
357  out << " +--- " << m_name << std::endl;
358  out << " +--- amount = " << get_total_use_count () << " (threshold = " << m_max_resources << ")" << std::endl;
359 
360  std::size_t level = 0;
361  for (auto stack_it : m_tracked_stack)
362  {
363  out << " +--- stack_level = " << level << std::endl;
364  dump_map (stack_it, out);
365  level++;
366  }
367 
368  restrack_log (out.str ());
369  }
370 
371  template <typename Res>
372  void
373  resource_tracker<Res>::dump_map (const map_type &map, std::ostream &out) const
374  {
375  for (auto map_it : map)
376  {
377  out << " +--- tracked " << m_res_alias << "=" << map_it.first;
378  out << " " << map_it.second;
379  out << std::endl;
380  }
381  out << " +--- tracked " << m_res_alias << " count = " << map.size () << std::endl;
382  }
383 
384  template <typename Res>
385  void
387  {
388  if (!m_enabled)
389  {
390  return;
391  }
392 
393  if (m_tracked_stack.empty ())
394  {
395  // fresh start. set abort as false
396  m_resource_count = 0;
397  m_is_aborted = false;
398  }
399 
400  m_tracked_stack.emplace_front ();
401  }
402 
403  template <typename Res>
404  void
406  {
407  if (!m_enabled)
408  {
409  return;
410  }
411 
412  if (m_tracked_stack.empty ())
413  {
414  restrack_assert (false);
415  return;
416  }
417 
418  // get current level map
419  map_type &map = m_tracked_stack.front ();
420  if (!map.empty ())
421  {
422  if (!m_is_aborted)
423  {
424  // resources are leaked
425  dump ();
426  restrack_assert (false);
427  }
428  else
429  {
430  // tracker was aborted; we don't know if there are really leaks
431  }
432  // remove resources
433  m_resource_count -= map.size ();
434  }
435  // remove level from stack
436  m_tracked_stack.pop_front ();
437 
438  // if no levels, resource count should be zero
440  }
441 
443  // other
445  void
446  restrack_assert (bool cond)
447  {
448 #if !defined (NDEBUG)
450  {
451  restrack_set_error (!cond);
452  }
453  else
454  {
455  assert (cond);
456  }
457 #endif // NDEBUG
458  }
459 
460 } // namespace cubbase
461 
462 #endif // _RESOURCE_TRACKER_HPP_
resource_tracker_item(const char *fn_arg, int l_arg, unsigned reuse)
void restrack_set_suppress_assert(bool suppress)
void restrack_log(const std::string &str)
void dump_map(const map_type &map, std::ostream &out) const
std::map< res_type, resource_tracker_item > map_type
void restrack_set_error(bool error)
bool restrack_pop_error(void)
map_type & get_current_map(void)
void increment(const char *filename, const int line, const res_type &res, unsigned use_count=1)
resource_tracker(const char *name, bool enabled, std::size_t max_resources, const char *res_name, unsigned max_reuse=1)
#define assert(x)
unsigned get_total_use_count(void) const
void decrement(const res_type &res, unsigned use_count=1)
void restrack_assert(bool cond)
bool restrack_is_assert_suppressed(void)
std::ostream & operator<<(std::ostream &os, const fileline_location &fileline)
std::forward_list< map_type > m_tracked_stack