Cthulhu  0.2.10
Cthulhu compiler collection
arena.cpp
Go to the documentation of this file.
1 // SPDX-License-Identifier: GPL-3.0-only
2 #include "stdafx.hpp"
3 
6 
7 struct FrameInfo
8 {
9  std::string name;
10  std::string path;
11  std::uint_least32_t line = 0;
12 
13  void update(std::stacktrace_entry entry)
14  {
15  if (name.empty())
16  name = entry.description();
17 
18  if (path.empty())
19  path = entry.source_file();
20 
21  if (line == 0)
22  line = entry.source_line();
23  }
24 
25  const char *get_name() const
26  {
27  return (name.empty()) ? "???" : name.c_str();
28  }
29 
30  const char *get_path() const
31  {
32  return (path.empty()) ? "<unknown>" : path.c_str();
33  }
34 
35  std::uint_least32_t get_line() const
36  {
37  return line;
38  }
39 };
40 
41 static std::unordered_map<std::stacktrace_entry, FrameInfo> gTraceInfo;
42 static size_t gCounter = 0;
43 
44 const FrameInfo& get_frame_info(std::stacktrace_entry entry)
45 {
46  FrameInfo& info = gTraceInfo[entry];
47  info.update(entry);
48  return info;
49 }
50 
51 TraceArena::TraceArena(const char *id, Collect collect)
52  : IArena(id)
53  , collect(collect)
54 { }
55 
56 void *TraceArena::malloc(size_t size)
57 {
58  malloc_calls += 1;
59 
60  void *ptr = ::malloc(size);
61 
62  create_alloc(ptr, size);
63 
64  return ptr;
65 }
66 
67 void *TraceArena::realloc(void *ptr, size_t new_size, size_t)
68 {
69  realloc_calls += 1;
70 
71  Memory old_size = allocs[ptr].size;
72  if (old_size < new_size)
73  peak_memory_usage += (Memory::bytes(new_size) - old_size);
74 
75  void *new_ptr = ::realloc(ptr, new_size);
76 
77  create_alloc(new_ptr, new_size);
78  delete_alloc(ptr);
79 
80  return new_ptr;
81 }
82 
83 void TraceArena::free(void *ptr, size_t)
84 {
85  free_calls += 1;
86 
87  delete_alloc(ptr);
88 
89  ::free(ptr);
90 }
91 
92 void TraceArena::rename(const void *ptr, const char *new_name)
93 {
94  update_name(ptr, new_name);
95 }
96 
97 void TraceArena::reparent(const void *ptr, const void *new_parent)
98 {
99  update_parent(ptr, new_parent);
100 }
101 
103 {
104  malloc_calls = 0;
105  realloc_calls = 0;
106  free_calls = 0;
107  peak_memory_usage = 0;
108  live_memory_usage = 0;
109 
110  live_allocs.clear();
111  tree.clear();
112  allocs.clear();
113 }
114 
115 size_t TraceArena::add_stacktrace(const std::stacktrace& trace)
116 {
117  // TODO: lets hope these dont collide
118  size_t hash = std::hash<std::stacktrace>{}(trace);
119  stacktraces[hash] = trace;
120  return hash;
121 }
122 
123 void TraceArena::create_alloc(void *ptr, size_t size)
124 {
127  allocs[ptr].id = gCounter++;
128  allocs[ptr].size = size;
129 
131  allocs[ptr].timestamp = std::chrono::high_resolution_clock::now();
132 
134  allocs[ptr].trace = add_stacktrace(std::stacktrace::current());
135 
136  live_allocs.insert(ptr);
137 }
138 
140 {
141  remove_parents(ptr);
142 
143  auto iter = allocs.find(ptr);
144  if (iter == allocs.end()) return;
145 
146  live_memory_usage -= iter->second.size;
147 
148  live_allocs.erase(ptr);
149 }
150 
151 void TraceArena::update_parent(const void *ptr, const void *new_parent)
152 {
153  remove_parents(ptr);
154  allocs[ptr].parent = new_parent;
155 
156  // try and figure out if this points into an existing allocation
157  auto it = allocs.upper_bound(new_parent);
158  if (it == allocs.end() || it == allocs.begin())
159  {
160  // this is a new parent
161  tree[new_parent].push_back(ptr);
162  }
163  else
164  {
165  // find the best fit, this is the smallest parent that contains the new parent
166  const uint8_t *best = nullptr;
167  size_t smallest = SIZE_MAX;
168  while (it != allocs.begin())
169  {
170  --it;
171 
172  const uint8_t *value = reinterpret_cast<const uint8_t*>(it->first);
173  const AllocInfo& parent_info = it->second;
174 
175  if (value <= new_parent && value + parent_info.size.as_bytes() >= new_parent)
176  {
177  size_t size = parent_info.size.as_bytes();
178  if (size < smallest)
179  {
180  best = value;
181  smallest = size;
182  }
183  }
184  }
185 
186  if (best == nullptr)
187  {
188  // this is a new parent
189  tree[new_parent].push_back(ptr);
190  }
191  else
192  {
193  // this is a child
194  tree[best].push_back(ptr);
195  }
196  }
197 }
198 
199 void TraceArena::update_name(const void *ptr, const char *new_name)
200 {
201  allocs[ptr].name = new_name;
202 }
203 
204 void TraceArena::remove_parents(const void *ptr)
205 {
206  auto iter = allocs.find(ptr);
207  if (iter == allocs.end()) return;
208 
209  for (auto& [_, children] : tree)
210  {
211  auto it = std::find(children.begin(), children.end(), iter->first);
212  if (it != children.end())
213  {
214  children.erase(it);
215  break;
216  }
217  }
218 }
219 
220 bool TraceArena::has_children(const void *ptr) const
221 {
222  if (auto it = tree.find(ptr); it != tree.end())
223  {
224  return it->second.size() > 0;
225  }
226 
227  return false;
228 }
229 
230 bool TraceArena::has_parent(const void *ptr) const
231 {
232  if (auto it = allocs.find(ptr); it != allocs.end())
233  {
234  return it->second.parent != nullptr;
235  }
236 
237  return false;
238 }
239 
245 bool TraceArena::is_external(const void *ptr) const
246 {
247  return allocs.find(ptr) == allocs.end();
248 }
249 
250 static const ImGuiTableFlags kTraceTableFlags
251  = ImGuiTableFlags_BordersV
252  | ImGuiTableFlags_BordersOuterH
253  | ImGuiTableFlags_Resizable
254  | ImGuiTableFlags_RowBg
255  | ImGuiTableFlags_NoHostExtendX
256  | ImGuiTableFlags_NoBordersInBody;
257 
258 void TraceArenaWidget::draw_backtrace(const std::stacktrace& trace) const
259 {
260  if (ImGui::BeginTable("Trace", 3, kTraceTableFlags))
261  {
262  ImGui::TableSetupColumn("Address");
263  ImGui::TableSetupColumn("Symbol");
264  ImGui::TableSetupColumn("File");
265  ImGui::TableHeadersRow();
266 
267  for (auto frame : trace)
268  {
269  auto info = get_frame_info(frame);
270  ImGui::TableNextRow();
271  ImGui::TableNextColumn();
272  ImGui::Text("0x%p", reinterpret_cast<void*>(frame.native_handle()));
273  ImGui::TableNextColumn();
274  ImGui::Text("%s", info.get_name());
275  ImGui::TableNextColumn();
276  ImGui::Text("%s:%zu", info.get_path(), info.get_line());
277  }
278 
279  ImGui::EndTable();
280  }
281 }
282 
283 void TraceArenaWidget::draw_name(const AllocInfo& alloc) const
284 {
285  ed::ScopeID scope((int)alloc.id);
286  if (!alloc.name.empty())
287  {
288  ImGui::Text("%s", alloc.name.c_str());
289  }
290  else
291  {
292  ImGui::TextDisabled("---");
293  }
294 
295  if (ImGui::BeginPopupContextItem("TracePopup"))
296  {
297  const auto& trace = arena.stacktraces[alloc.trace];
298  if (!trace.empty())
299  {
300  draw_backtrace(trace);
301  }
302  else
303  {
304  ImGui::TextDisabled("Stacktrace not available");
305  }
306  ImGui::EndPopup();
307  }
308 }
309 
310 void TraceArenaWidget::draw_extern_name(const void *ptr) const
311 {
312  if (auto it = arena.allocs.find(ptr); it != arena.allocs.end())
313  {
314  const auto& alloc = it->second;
315  if (!alloc.name.empty())
316  {
317  ImGui::Text("%s (external)", alloc.name.c_str());
318  }
319  else
320  {
321  ImGui::TextDisabled("external");
322  }
323  }
324  else
325  {
326  ImGui::TextDisabled("external");
327  }
328 }
329 
330 static const ImGuiTreeNodeFlags kGroupNodeFlags
331  = ImGuiTreeNodeFlags_SpanAllColumns
332  | ImGuiTreeNodeFlags_AllowOverlap;
333 
334 static const ImGuiTreeNodeFlags kValueNodeFlags
335  = kGroupNodeFlags
336  | ImGuiTreeNodeFlags_Leaf
337  | ImGuiTreeNodeFlags_Bullet
338  | ImGuiTreeNodeFlags_NoTreePushOnOpen;
339 
340 void TraceArenaWidget::draw_tree_child(const void *ptr, const AllocInfo& alloc) const
341 {
342  ImGui::TreeNodeEx(ptr, kValueNodeFlags, "%p", ptr);
343 
344  ImGui::TableNextColumn();
345  ImGui::Text("%s", alloc.size.to_string().c_str());
346 
347  ImGui::TableNextColumn();
348  draw_name(alloc);
349 
350  if (ImGui::BeginItemTooltip())
351  {
352  ImGui::Text("parent: %p", alloc.parent);
353  ImGui::EndTooltip();
354  }
355 }
356 
357 void TraceArenaWidget::draw_tree_group(const void *ptr, const AllocInfo& alloc) const
358 {
359  bool is_open = ImGui::TreeNodeEx(ptr, kGroupNodeFlags, "%p", ptr);
360 
361  ImGui::TableNextColumn();
362  ImGui::Text("%s", alloc.size.to_string().c_str());
363 
364  ImGui::TableNextColumn();
365  draw_name(alloc);
366 
367  if (ImGui::BeginItemTooltip())
368  {
369  ImGui::Text("parent: %p", alloc.parent);
370  ImGui::EndTooltip();
371  }
372 
373  if (is_open)
374  {
375  for (const void *child : arena.tree.at(ptr))
376  {
377  draw_tree_node(child);
378  }
379 
380  ImGui::TreePop();
381  }
382 }
383 
384 void TraceArenaWidget::draw_tree_node_info(const void *ptr, const AllocInfo& alloc) const
385 {
386  ImGui::TableNextRow();
387  ImGui::TableNextColumn();
388  if (arena.has_children(ptr))
389  {
390  draw_tree_group(ptr, alloc);
391  }
392  else
393  {
394  draw_tree_child(ptr, alloc);
395  }
396 }
397 
398 void TraceArenaWidget::draw_tree_node(const void *ptr) const
399 {
400  if (auto it = arena.allocs.find(ptr); it != arena.allocs.end())
401  {
402  draw_tree_node_info(ptr, it->second);
403  }
404 }
405 
406 static const ImGuiTableFlags kMemoryTreeTableFlags
407  = ImGuiTableFlags_BordersV
408  | ImGuiTableFlags_BordersOuterH
409  | ImGuiTableFlags_Resizable
410  | ImGuiTableFlags_RowBg
411  | ImGuiTableFlags_NoHostExtendX
412  | ImGuiTableFlags_NoBordersInBody
413  | ImGuiTableFlags_ScrollY;
414 
415 void TraceArenaWidget::draw_tree() const
416 {
417  ImGui::SeparatorText("Memory tree view");
418 
419  if (ImGui::BeginTable("Allocations", 3, kMemoryTreeTableFlags))
420  {
421  ImGui::TableSetupColumn("Address");
422  ImGui::TableSetupColumn("Size");
423  ImGui::TableSetupColumn("Name");
424  ImGui::TableSetupScrollFreeze(0, 1);
425  ImGui::TableHeadersRow();
426 
427  // first draw all nodes that dont have parents but have children
428  for (auto& [root, children] : arena.tree)
429  {
430  if (arena.has_children(root) && !arena.has_parent(root))
431  {
432  draw_tree_node(root);
433  }
434 
435  if (arena.is_external(root) && !arena.has_parent(root))
436  {
437  AllocInfo it = { .name = "extern" };
438  draw_tree_node_info(root, it);
439  }
440  }
441 
442  // draw all nodes that don't have parents and don't have children
443  for (auto& [ptr, alloc] : arena.allocs)
444  {
445  if (alloc.parent != nullptr || arena.has_children(ptr)) continue;
446 
447  draw_tree_node(ptr);
448  }
449 
450  ImGui::EndTable();
451  }
452 }
453 
454 static const ImGuiTableFlags kMemoryFlatTableFlags
455  = ImGuiTableFlags_BordersV
456  | ImGuiTableFlags_BordersOuterH
457  | ImGuiTableFlags_Resizable
458  | ImGuiTableFlags_RowBg
459  | ImGuiTableFlags_NoHostExtendX
460  | ImGuiTableFlags_NoBordersInBody
461  | ImGuiTableFlags_ScrollY;
462 
463 void TraceArenaWidget::draw_flat() const
464 {
465  ImGui::SeparatorText("Memory view");
466 
467  if (ImGui::BeginTable("Allocations", 4, kMemoryFlatTableFlags))
468  {
469  ImGui::TableSetupColumn("Address");
470  ImGui::TableSetupColumn("Size");
471  ImGui::TableSetupColumn("Name");
472  ImGui::TableSetupColumn("Parent");
473  ImGui::TableSetupScrollFreeze(0, 1);
474  ImGui::TableHeadersRow();
475 
476  for (const auto& [ptr, alloc] : arena.allocs)
477  {
478  ImGui::TableNextRow();
479 
480  ImGui::TableNextColumn();
481  ImGui::Text("%p", ptr);
482 
483  ImGui::TableNextColumn();
484  ImGui::Text("%s", alloc.size.to_string().c_str());
485 
486  ImGui::TableNextColumn();
487  draw_name(alloc);
488 
489  ImGui::TableNextColumn();
490  if (alloc.parent)
491  {
492  ImGui::Text("%p", alloc.parent);
493  if (ImGui::BeginItemTooltip())
494  {
495  draw_extern_name(alloc.parent);
496  ImGui::EndTooltip();
497  }
498  }
499  else
500  {
501  ImGui::TextDisabled("null");
502  }
503  }
504 
505  ImGui::EndTable();
506  }
507 }
508 
510 {
511  if (ImGui::BeginMenuBar())
512  {
513  if (ImGui::BeginMenu("Options"))
514  {
515  if (ImGui::MenuItem("Collect Stack Trace", nullptr, arena.collect & TraceArena::eCollectStackTrace))
516  {
518  }
519 
520  if (ImGui::MenuItem("Collect Time Stamps", nullptr, arena.collect & TraceArena::eCollectTimeStamps))
521  {
523  }
524 
525  ImGui::EndMenu();
526  }
527  ImGui::EndMenuBar();
528  }
529 
530  ImGui::TextWrapped("Memory usage: (%zu mallocs, %zu reallocs, %zu frees, %s peak, %s live)", arena.malloc_calls, arena.realloc_calls, arena.free_calls, arena.peak_memory_usage.to_string().c_str(), arena.live_memory_usage.to_string().c_str());
531 
532  if (ImGui::Button("Reset Stats"))
533  {
534  arena.reset();
535  }
536  ImGui::SameLine();
537  ImGui::Text("Visualize as:");
538  ImGui::SameLine();
539 
540  // body
541  ImGui::RadioButton("Tree", &mode, eDrawTree);
542  ImGui::SameLine();
543  ImGui::RadioButton("Flat", &mode, eDrawFlat);
544 
545  if (mode == eDrawTree)
546  {
547  draw_tree();
548  }
549  else
550  {
551  draw_flat();
552  }
553 }
554 
556 {
557  if (!visible) return false;
558 
559  bool result = ImGui::Begin(get_title(), &visible, ImGuiWindowFlags_MenuBar);
560  if (result)
561  {
562  draw();
563  }
564  ImGui::End();
565 
566  return result;
567 }
Definition: arena.hpp:11
bool draw_window()
Definition: arena.cpp:555
@ eDrawFlat
draw the allocations as a flat list
Definition: arena.hpp:117
@ eDrawTree
draw the allocations as a tree using parent data
Definition: arena.hpp:114
const char * get_title() const
Definition: arena.hpp:127
void update_parent(const void *ptr, const void *parent)
Definition: arena.cpp:151
bool has_parent(const void *ptr) const
Definition: arena.cpp:230
void * malloc(size_t size) override
Definition: arena.cpp:56
size_t malloc_calls
Definition: arena.hpp:38
int collect
Definition: arena.hpp:55
void create_alloc(void *ptr, size_t size)
Definition: arena.cpp:123
void remove_parents(const void *ptr)
Definition: arena.cpp:204
void reset()
Definition: arena.cpp:102
size_t add_stacktrace(const std::stacktrace &trace)
Definition: arena.cpp:115
std::unordered_map< size_t, std::stacktrace > stacktraces
Definition: arena.hpp:51
size_t free_calls
Definition: arena.hpp:40
void delete_alloc(void *ptr)
Definition: arena.cpp:139
AllocMap allocs
Definition: arena.hpp:58
std::unordered_set< void * > live_allocs
Definition: arena.hpp:47
void reparent(const void *ptr, const void *parent) override
Definition: arena.cpp:97
void * realloc(void *ptr, size_t new_size, size_t size) override
Definition: arena.cpp:67
Memory live_memory_usage
Definition: arena.hpp:44
Memory peak_memory_usage
Definition: arena.hpp:43
void rename(const void *ptr, const char *new_name) override
Definition: arena.cpp:92
void update_name(const void *ptr, const char *new_name)
Definition: arena.cpp:199
TraceArena(const char *id, Collect collect)
Definition: arena.cpp:51
AllocTree tree
Definition: arena.hpp:61
void free(void *ptr, size_t size) override
Definition: arena.cpp:83
bool has_children(const void *ptr) const
Definition: arena.cpp:220
size_t realloc_calls
Definition: arena.hpp:39
bool is_external(const void *ptr) const
check if the given pointer was not allocated by this allocator
Definition: arena.cpp:245
@ eCollectStackTrace
Definition: arena.hpp:81
@ eCollectTimeStamps
Definition: arena.hpp:82
CT_NODISCARD size_t size
Definition: scan.h:128
const FrameInfo & get_frame_info(std::stacktrace_entry entry)
Definition: arena.cpp:44
size_t id
the id of the allocation
Definition: arena.hpp:13
std::string name
the name of the allocation
Definition: arena.hpp:18
size_t trace
hash of the stack trace
Definition: arena.hpp:16
Memory size
the size of the allocation
Definition: arena.hpp:14
const void * parent
the parent of the allocation
Definition: arena.hpp:19
std::uint_least32_t line
Definition: arena.cpp:11
const char * get_path() const
Definition: arena.cpp:30
std::string name
Definition: arena.cpp:9
std::uint_least32_t get_line() const
Definition: arena.cpp:35
void update(std::stacktrace_entry entry)
Definition: arena.cpp:13
std::string path
Definition: arena.cpp:10
const char * get_name() const
Definition: arena.cpp:25
Definition: memory.hpp:5
std::string to_string() const
Definition: memory.cpp:5
constexpr size_t as_bytes() const
Definition: memory.hpp:49
constexpr static Memory bytes(size_t bytes)
Definition: memory.hpp:37