constructor_stats.h revision 11986
12SN/A#pragma once 21762SN/A/* 32SN/A tests/constructor_stats.h -- framework for printing and tracking object 42SN/A instance lifetimes in example/test code. 52SN/A 62SN/A Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca> 72SN/A 82SN/A All rights reserved. Use of this source code is governed by a 92SN/A BSD-style license that can be found in the LICENSE file. 102SN/A 112SN/AThis header provides a few useful tools for writing examples or tests that want to check and/or 122SN/Adisplay object instance lifetimes. It requires that you include this header and add the following 132SN/Afunction calls to constructors: 142SN/A 152SN/A class MyClass { 162SN/A MyClass() { ...; print_default_created(this); } 172SN/A ~MyClass() { ...; print_destroyed(this); } 182SN/A MyClass(const MyClass &c) { ...; print_copy_created(this); } 192SN/A MyClass(MyClass &&c) { ...; print_move_created(this); } 202SN/A MyClass(int a, int b) { ...; print_created(this, a, b); } 212SN/A MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); } 222SN/A MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); } 232SN/A 242SN/A ... 252SN/A } 262SN/A 272665Ssaidi@eecs.umich.eduYou can find various examples of these in several of the existing example .cpp files. (Of course 282665Ssaidi@eecs.umich.eduyou don't need to add any of the above constructors/operators that you don't actually have, except 292665Ssaidi@eecs.umich.edufor the destructor). 302SN/A 312SN/AEach of these will print an appropriate message such as: 322SN/A 332SN/A ### MyClass @ 0x2801910 created via default constructor 342SN/A ### MyClass @ 0x27fa780 created 100 200 352147SN/A ### MyClass @ 0x2801910 destroyed 367678Sgblack@eecs.umich.edu ### MyClass @ 0x27fa780 destroyed 378229Snate@binkert.org 387878Sgblack@eecs.umich.eduYou can also include extra arguments (such as the 100, 200 in the output above, coming from the 392147SN/Avalue constructor) for all of the above methods which will be included in the output. 402147SN/A 412680Sktlim@umich.eduFor testing, each of these also keeps track the created instances and allows you to check how many 422132SN/Aof the various constructors have been invoked from the Python side via code such as: 432147SN/A 445999Snate@binkert.org from example import ConstructorStats 452147SN/A cstats = ConstructorStats.get(MyClass) 462147SN/A print(cstats.alive()) 472147SN/A print(cstats.default_constructions) 482147SN/A 492147SN/ANote that `.alive()` should usually be the first thing you call as it invokes Python's garbage 502147SN/Acollector to actually destroy objects that aren't yet referenced. 512147SN/A 522147SN/AFor everything except copy and move constructors and destructors, any extra values given to the 532147SN/Aprint_...() function is stored in a class-specific values list which you can retrieve and inspect 542090SN/Afrom the ConstructorStats instance `.values()` method. 552147SN/A 564695Sgblack@eecs.umich.eduIn some cases, when you need to track instances of a C++ class not registered with pybind11, you 5710417Sandreas.hansson@arm.comneed to add a function returning the ConstructorStats for the C++ class; this can be done with: 5810417Sandreas.hansson@arm.com 592SN/A m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference) 602SN/A 612612SN/AFinally, you can suppress the output messages, but keep the constructor tracking (for 622612SN/Ainspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. 632612SN/A`track_copy_created(this)`). 642612SN/A 652612SN/A*/ 662612SN/A 672612SN/A#include "pybind11_tests.h" 682612SN/A#include <unordered_map> 692612SN/A#include <list> 704695Sgblack@eecs.umich.edu#include <typeindex> 7110417Sandreas.hansson@arm.com#include <sstream> 7210417Sandreas.hansson@arm.com 732612SN/Aclass ConstructorStats { 742612SN/Aprotected: 758545Ssaidi@eecs.umich.edu std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents 768545Ssaidi@eecs.umich.edu std::list<std::string> _values; // Used to track values (e.g. of value constructors) 778545Ssaidi@eecs.umich.edupublic: 788545Ssaidi@eecs.umich.edu int default_constructions = 0; 798545Ssaidi@eecs.umich.edu int copy_constructions = 0; 8010417Sandreas.hansson@arm.com int move_constructions = 0; 8110417Sandreas.hansson@arm.com int copy_assignments = 0; 828545Ssaidi@eecs.umich.edu int move_assignments = 0; 838545Ssaidi@eecs.umich.edu 845004Sgblack@eecs.umich.edu void copy_created(void *inst) { 854183Sgblack@eecs.umich.edu created(inst); 864183Sgblack@eecs.umich.edu copy_constructions++; 874183Sgblack@eecs.umich.edu } 884183Sgblack@eecs.umich.edu void move_created(void *inst) { 895004Sgblack@eecs.umich.edu created(inst); 905004Sgblack@eecs.umich.edu move_constructions++; 9110417Sandreas.hansson@arm.com } 9210417Sandreas.hansson@arm.com void default_created(void *inst) { 935004Sgblack@eecs.umich.edu created(inst); 945004Sgblack@eecs.umich.edu default_constructions++; 955004Sgblack@eecs.umich.edu } 965004Sgblack@eecs.umich.edu void created(void *inst) { 975004Sgblack@eecs.umich.edu ++_instances[inst]; 985004Sgblack@eecs.umich.edu }; 995004Sgblack@eecs.umich.edu void destroyed(void *inst) { 1005004Sgblack@eecs.umich.edu if (--_instances[inst] < 0) 1015004Sgblack@eecs.umich.edu throw std::runtime_error("cstats.destroyed() called with unknown instance; potential double-destruction or a missing cstats.created()"); 10210417Sandreas.hansson@arm.com } 10310417Sandreas.hansson@arm.com 1044183Sgblack@eecs.umich.edu int alive() { 1054183Sgblack@eecs.umich.edu // Force garbage collection to ensure any pending destructors are invoked: 1062SN/A py::module::import("gc").attr("collect")(); 107 int total = 0; 108 for (const auto &p : _instances) if (p.second > 0) total += p.second; 109 return total; 110 } 111 112 void value() {} // Recursion terminator 113 // Takes one or more values, converts them to strings, then stores them. 114 template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) { 115 std::ostringstream oss; 116 oss << v; 117 _values.push_back(oss.str()); 118 value(std::forward<Tmore>(args)...); 119 } 120 121 // Move out stored values 122 py::list values() { 123 py::list l; 124 for (const auto &v : _values) l.append(py::cast(v)); 125 _values.clear(); 126 return l; 127 } 128 129 // Gets constructor stats from a C++ type index 130 static ConstructorStats& get(std::type_index type) { 131 static std::unordered_map<std::type_index, ConstructorStats> all_cstats; 132 return all_cstats[type]; 133 } 134 135 // Gets constructor stats from a C++ type 136 template <typename T> static ConstructorStats& get() { 137 return get(typeid(T)); 138 } 139 140 // Gets constructor stats from a Python class 141 static ConstructorStats& get(py::object class_) { 142 auto &internals = py::detail::get_internals(); 143 const std::type_index *t1 = nullptr, *t2 = nullptr; 144 try { 145 auto *type_info = internals.registered_types_py.at(class_.ptr()); 146 for (auto &p : internals.registered_types_cpp) { 147 if (p.second == type_info) { 148 if (t1) { 149 t2 = &p.first; 150 break; 151 } 152 t1 = &p.first; 153 } 154 } 155 } 156 catch (std::out_of_range) {} 157 if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); 158 auto &cs1 = get(*t1); 159 // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever 160 // has more constructions (typically one or the other will be 0) 161 if (t2) { 162 auto &cs2 = get(*t2); 163 int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size(); 164 int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size(); 165 if (cs2_total > cs1_total) return cs2; 166 } 167 return cs1; 168 } 169}; 170 171// To track construction/destruction, you need to call these methods from the various 172// constructors/operators. The ones that take extra values record the given values in the 173// constructor stats values for later inspection. 174template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } 175template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } 176template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) { 177 auto &cst = ConstructorStats::get<T>(); 178 cst.copy_assignments++; 179 cst.value(std::forward<Values>(values)...); 180} 181template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) { 182 auto &cst = ConstructorStats::get<T>(); 183 cst.move_assignments++; 184 cst.value(std::forward<Values>(values)...); 185} 186template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) { 187 auto &cst = ConstructorStats::get<T>(); 188 cst.default_created(inst); 189 cst.value(std::forward<Values>(values)...); 190} 191template <class T, typename... Values> void track_created(T *inst, Values &&...values) { 192 auto &cst = ConstructorStats::get<T>(); 193 cst.created(inst); 194 cst.value(std::forward<Values>(values)...); 195} 196template <class T, typename... Values> void track_destroyed(T *inst) { 197 ConstructorStats::get<T>().destroyed(inst); 198} 199template <class T, typename... Values> void track_values(T *, Values &&...values) { 200 ConstructorStats::get<T>().value(std::forward<Values>(values)...); 201} 202 203/// Don't cast pointers to Python, print them as strings 204inline const char *format_ptrs(const char *p) { return p; } 205template <typename T> 206py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } 207template <typename T> 208auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } 209 210template <class T, typename... Output> 211void print_constr_details(T *inst, const std::string &action, Output &&...output) { 212 py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action, 213 format_ptrs(std::forward<Output>(output))...); 214} 215 216// Verbose versions of the above: 217template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 218 print_constr_details(inst, "created via copy constructor", values...); 219 track_copy_created(inst); 220} 221template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 222 print_constr_details(inst, "created via move constructor", values...); 223 track_move_created(inst); 224} 225template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) { 226 print_constr_details(inst, "assigned via copy assignment", values...); 227 track_copy_assigned(inst, values...); 228} 229template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) { 230 print_constr_details(inst, "assigned via move assignment", values...); 231 track_move_assigned(inst, values...); 232} 233template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) { 234 print_constr_details(inst, "created via default constructor", values...); 235 track_default_created(inst, values...); 236} 237template <class T, typename... Values> void print_created(T *inst, Values &&...values) { 238 print_constr_details(inst, "created", values...); 239 track_created(inst, values...); 240} 241template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values 242 print_constr_details(inst, "destroyed", values...); 243 track_destroyed(inst); 244} 245template <class T, typename... Values> void print_values(T *inst, Values &&...values) { 246 print_constr_details(inst, ":", values...); 247 track_values(inst, values...); 248} 249 250