Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 1 | #pragma once |
| 2 | /* |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 3 | tests/constructor_stats.h -- framework for printing and tracking object |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 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 | |
| 11 | This header provides a few useful tools for writing examples or tests that want to check and/or |
| 12 | display object instance lifetimes. It requires that you include this header and add the following |
| 13 | function 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 | |
Jason Rhinelander | 1249452 | 2017-01-31 11:28:29 -0500 | [diff] [blame] | 27 | You can find various examples of these in several of the existing testing .cpp files. (Of course |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 28 | you don't need to add any of the above constructors/operators that you don't actually have, except |
| 29 | for the destructor). |
| 30 | |
| 31 | Each 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 | |
| 38 | You can also include extra arguments (such as the 100, 200 in the output above, coming from the |
| 39 | value constructor) for all of the above methods which will be included in the output. |
| 40 | |
| 41 | For testing, each of these also keeps track the created instances and allows you to check how many |
| 42 | of the various constructors have been invoked from the Python side via code such as: |
| 43 | |
Jason Rhinelander | 1249452 | 2017-01-31 11:28:29 -0500 | [diff] [blame] | 44 | from pybind11_tests import ConstructorStats |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 45 | cstats = ConstructorStats.get(MyClass) |
| 46 | print(cstats.alive()) |
| 47 | print(cstats.default_constructions) |
| 48 | |
| 49 | Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage |
| 50 | collector to actually destroy objects that aren't yet referenced. |
| 51 | |
| 52 | For everything except copy and move constructors and destructors, any extra values given to the |
| 53 | print_...() function is stored in a class-specific values list which you can retrieve and inspect |
| 54 | from the ConstructorStats instance `.values()` method. |
| 55 | |
| 56 | In some cases, when you need to track instances of a C++ class not registered with pybind11, you |
| 57 | need to add a function returning the ConstructorStats for the C++ class; this can be done with: |
| 58 | |
Jason Rhinelander | c07ec31 | 2016-11-06 13:12:48 -0500 | [diff] [blame] | 59 | m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference) |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 60 | |
| 61 | Finally, you can suppress the output messages, but keep the constructor tracking (for |
| 62 | inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. |
| 63 | `track_copy_created(this)`). |
| 64 | |
| 65 | */ |
| 66 | |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 67 | #include "pybind11_tests.h" |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 68 | #include <unordered_map> |
| 69 | #include <list> |
| 70 | #include <typeindex> |
| 71 | #include <sstream> |
| 72 | |
| 73 | class ConstructorStats { |
| 74 | protected: |
| 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) |
| 77 | public: |
| 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 | } |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 88 | |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 89 | void move_created(void *inst) { |
| 90 | created(inst); |
| 91 | move_constructions++; |
| 92 | } |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 93 | |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 94 | void default_created(void *inst) { |
| 95 | created(inst); |
| 96 | default_constructions++; |
| 97 | } |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 98 | |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 99 | void created(void *inst) { |
| 100 | ++_instances[inst]; |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 101 | } |
| 102 | |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 103 | void destroyed(void *inst) { |
| 104 | if (--_instances[inst] < 0) |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 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 |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 125 | } |
| 126 | |
| 127 | int alive() { |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 128 | gc(); |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 129 | int total = 0; |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 130 | for (const auto &p : _instances) |
| 131 | if (p.second > 0) |
| 132 | total += p.second; |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 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 | } |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 144 | |
| 145 | // Move out stored values |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 146 | py::list values() { |
| 147 | py::list l; |
| 148 | for (const auto &v : _values) l.append(py::cast(v)); |
Dean Moldovan | a0c1ccf | 2016-08-12 13:50:00 +0200 | [diff] [blame] | 149 | _values.clear(); |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 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() { |
Wenzel Jakob | 1d1f81b | 2016-12-16 15:00:46 +0100 | [diff] [blame] | 161 | #if defined(PYPY_VERSION) |
| 162 | gc(); |
| 163 | #endif |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 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 { |
Jason Rhinelander | e45c211 | 2017-02-22 21:36:09 -0500 | [diff] [blame^] | 172 | auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0); |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 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 (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. |
| 201 | template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } |
| 202 | template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } |
| 203 | template <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 | } |
| 208 | template <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 | } |
| 213 | template <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 | } |
| 218 | template <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 | } |
| 223 | template <class T, typename... Values> void track_destroyed(T *inst) { |
| 224 | ConstructorStats::get<T>().destroyed(inst); |
| 225 | } |
| 226 | template <class T, typename... Values> void track_values(T *, Values &&...values) { |
| 227 | ConstructorStats::get<T>().value(std::forward<Values>(values)...); |
| 228 | } |
| 229 | |
Dean Moldovan | 81511be | 2016-09-07 00:50:10 +0200 | [diff] [blame] | 230 | /// Don't cast pointers to Python, print them as strings |
| 231 | inline const char *format_ptrs(const char *p) { return p; } |
| 232 | template <typename T> |
| 233 | py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } |
| 234 | template <typename T> |
| 235 | auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } |
| 236 | |
| 237 | template <class T, typename... Output> |
| 238 | void 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))...); |
Jason Rhinelander | 3f58937 | 2016-08-07 13:05:26 -0400 | [diff] [blame] | 241 | } |
| 242 | |
| 243 | // Verbose versions of the above: |
| 244 | template <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 | } |
| 248 | template <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 | } |
| 252 | template <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 | } |
| 256 | template <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 | } |
| 260 | template <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 | } |
| 264 | template <class T, typename... Values> void print_created(T *inst, Values &&...values) { |
| 265 | print_constr_details(inst, "created", values...); |
| 266 | track_created(inst, values...); |
| 267 | } |
| 268 | template <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 | } |
| 272 | template <class T, typename... Values> void print_values(T *inst, Values &&...values) { |
| 273 | print_constr_details(inst, ":", values...); |
| 274 | track_values(inst, values...); |
| 275 | } |
| 276 | |