constructor_stats.h revision 14299
1#pragma once 2/* 3 tests/constructor_stats.h -- framework for printing and tracking object 4 instance lifetimes in example/test code. 5 6 Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca> 7 8 All rights reserved. Use of this source code is governed by a 9 BSD-style license that can be found in the LICENSE file. 10 11This header provides a few useful tools for writing examples or tests that want to check and/or 12display object instance lifetimes. It requires that you include this header and add the following 13function calls to constructors: 14 15 class MyClass { 16 MyClass() { ...; print_default_created(this); } 17 ~MyClass() { ...; print_destroyed(this); } 18 MyClass(const MyClass &c) { ...; print_copy_created(this); } 19 MyClass(MyClass &&c) { ...; print_move_created(this); } 20 MyClass(int a, int b) { ...; print_created(this, a, b); } 21 MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); } 22 MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); } 23 24 ... 25 } 26 27You can find various examples of these in several of the existing testing .cpp files. (Of course 28you don't need to add any of the above constructors/operators that you don't actually have, except 29for the destructor). 30 31Each of these will print an appropriate message such as: 32 33 ### MyClass @ 0x2801910 created via default constructor 34 ### MyClass @ 0x27fa780 created 100 200 35 ### MyClass @ 0x2801910 destroyed 36 ### MyClass @ 0x27fa780 destroyed 37 38You can also include extra arguments (such as the 100, 200 in the output above, coming from the 39value constructor) for all of the above methods which will be included in the output. 40 41For testing, each of these also keeps track the created instances and allows you to check how many 42of the various constructors have been invoked from the Python side via code such as: 43 44 from pybind11_tests import ConstructorStats 45 cstats = ConstructorStats.get(MyClass) 46 print(cstats.alive()) 47 print(cstats.default_constructions) 48 49Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage 50collector to actually destroy objects that aren't yet referenced. 51 52For everything except copy and move constructors and destructors, any extra values given to the 53print_...() function is stored in a class-specific values list which you can retrieve and inspect 54from the ConstructorStats instance `.values()` method. 55 56In some cases, when you need to track instances of a C++ class not registered with pybind11, you 57need to add a function returning the ConstructorStats for the C++ class; this can be done with: 58 59 m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference) 60 61Finally, you can suppress the output messages, but keep the constructor tracking (for 62inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. 63`track_copy_created(this)`). 64 65*/ 66 67#include "pybind11_tests.h" 68#include <unordered_map> 69#include <list> 70#include <typeindex> 71#include <sstream> 72 73class ConstructorStats { 74protected: 75 std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents 76 std::list<std::string> _values; // Used to track values (e.g. of value constructors) 77public: 78 int default_constructions = 0; 79 int copy_constructions = 0; 80 int move_constructions = 0; 81 int copy_assignments = 0; 82 int move_assignments = 0; 83 84 void copy_created(void *inst) { 85 created(inst); 86 copy_constructions++; 87 } 88 89 void move_created(void *inst) { 90 created(inst); 91 move_constructions++; 92 } 93 94 void default_created(void *inst) { 95 created(inst); 96 default_constructions++; 97 } 98 99 void created(void *inst) { 100 ++_instances[inst]; 101 } 102 103 void destroyed(void *inst) { 104 if (--_instances[inst] < 0) 105 throw std::runtime_error("cstats.destroyed() called with unknown " 106 "instance; potential double-destruction " 107 "or a missing cstats.created()"); 108 } 109 110 static void gc() { 111 // Force garbage collection to ensure any pending destructors are invoked: 112#if defined(PYPY_VERSION) 113 PyObject *globals = PyEval_GetGlobals(); 114 PyObject *result = PyRun_String( 115 "import gc\n" 116 "for i in range(2):" 117 " gc.collect()\n", 118 Py_file_input, globals, globals); 119 if (result == nullptr) 120 throw py::error_already_set(); 121 Py_DECREF(result); 122#else 123 py::module::import("gc").attr("collect")(); 124#endif 125 } 126 127 int alive() { 128 gc(); 129 int total = 0; 130 for (const auto &p : _instances) 131 if (p.second > 0) 132 total += p.second; 133 return total; 134 } 135 136 void value() {} // Recursion terminator 137 // Takes one or more values, converts them to strings, then stores them. 138 template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) { 139 std::ostringstream oss; 140 oss << v; 141 _values.push_back(oss.str()); 142 value(std::forward<Tmore>(args)...); 143 } 144 145 // Move out stored values 146 py::list values() { 147 py::list l; 148 for (const auto &v : _values) l.append(py::cast(v)); 149 _values.clear(); 150 return l; 151 } 152 153 // Gets constructor stats from a C++ type index 154 static ConstructorStats& get(std::type_index type) { 155 static std::unordered_map<std::type_index, ConstructorStats> all_cstats; 156 return all_cstats[type]; 157 } 158 159 // Gets constructor stats from a C++ type 160 template <typename T> static ConstructorStats& get() { 161#if defined(PYPY_VERSION) 162 gc(); 163#endif 164 return get(typeid(T)); 165 } 166 167 // Gets constructor stats from a Python class 168 static ConstructorStats& get(py::object class_) { 169 auto &internals = py::detail::get_internals(); 170 const std::type_index *t1 = nullptr, *t2 = nullptr; 171 try { 172 auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0); 173 for (auto &p : internals.registered_types_cpp) { 174 if (p.second == type_info) { 175 if (t1) { 176 t2 = &p.first; 177 break; 178 } 179 t1 = &p.first; 180 } 181 } 182 } 183 catch (const std::out_of_range &) {} 184 if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); 185 auto &cs1 = get(*t1); 186 // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever 187 // has more constructions (typically one or the other will be 0) 188 if (t2) { 189 auto &cs2 = get(*t2); 190 int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size(); 191 int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size(); 192 if (cs2_total > cs1_total) return cs2; 193 } 194 return cs1; 195 } 196}; 197 198// To track construction/destruction, you need to call these methods from the various 199// constructors/operators. The ones that take extra values record the given values in the 200// constructor stats values for later inspection. 201template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } 202template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } 203template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) { 204 auto &cst = ConstructorStats::get<T>(); 205 cst.copy_assignments++; 206 cst.value(std::forward<Values>(values)...); 207} 208template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) { 209 auto &cst = ConstructorStats::get<T>(); 210 cst.move_assignments++; 211 cst.value(std::forward<Values>(values)...); 212} 213template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) { 214 auto &cst = ConstructorStats::get<T>(); 215 cst.default_created(inst); 216 cst.value(std::forward<Values>(values)...); 217} 218template <class T, typename... Values> void track_created(T *inst, Values &&...values) { 219 auto &cst = ConstructorStats::get<T>(); 220 cst.created(inst); 221 cst.value(std::forward<Values>(values)...); 222} 223template <class T, typename... Values> void track_destroyed(T *inst) { 224 ConstructorStats::get<T>().destroyed(inst); 225} 226template <class T, typename... Values> void track_values(T *, Values &&...values) { 227 ConstructorStats::get<T>().value(std::forward<Values>(values)...); 228} 229 230/// Don't cast pointers to Python, print them as strings 231inline const char *format_ptrs(const char *p) { return p; } 232template <typename T> 233py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } 234template <typename T> 235auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } 236 237template <class T, typename... Output> 238void print_constr_details(T *inst, const std::string &action, Output &&...output) { 239 py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action, 240 format_ptrs(std::forward<Output>(output))...); 241} 242 243// Verbose versions of the above: 244template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 245 print_constr_details(inst, "created via copy constructor", values...); 246 track_copy_created(inst); 247} 248template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 249 print_constr_details(inst, "created via move constructor", values...); 250 track_move_created(inst); 251} 252template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) { 253 print_constr_details(inst, "assigned via copy assignment", values...); 254 track_copy_assigned(inst, values...); 255} 256template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) { 257 print_constr_details(inst, "assigned via move assignment", values...); 258 track_move_assigned(inst, values...); 259} 260template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) { 261 print_constr_details(inst, "created via default constructor", values...); 262 track_default_created(inst, values...); 263} 264template <class T, typename... Values> void print_created(T *inst, Values &&...values) { 265 print_constr_details(inst, "created", values...); 266 track_created(inst, values...); 267} 268template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values 269 print_constr_details(inst, "destroyed", values...); 270 track_destroyed(inst); 271} 272template <class T, typename... Values> void print_values(T *inst, Values &&...values) { 273 print_constr_details(inst, ":", values...); 274 track_values(inst, values...); 275} 276 277