constructor_stats.h revision 12037
11689SN/A#pragma once 210333Smitch.hayenga@arm.com/* 39920Syasuko.eckert@amd.com tests/constructor_stats.h -- framework for printing and tracking object 47944SGiacomo.Gabrielli@arm.com instance lifetimes in example/test code. 57944SGiacomo.Gabrielli@arm.com 67944SGiacomo.Gabrielli@arm.com Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca> 77944SGiacomo.Gabrielli@arm.com 87944SGiacomo.Gabrielli@arm.com All rights reserved. Use of this source code is governed by a 97944SGiacomo.Gabrielli@arm.com BSD-style license that can be found in the LICENSE file. 107944SGiacomo.Gabrielli@arm.com 117944SGiacomo.Gabrielli@arm.comThis header provides a few useful tools for writing examples or tests that want to check and/or 127944SGiacomo.Gabrielli@arm.comdisplay object instance lifetimes. It requires that you include this header and add the following 137944SGiacomo.Gabrielli@arm.comfunction calls to constructors: 147944SGiacomo.Gabrielli@arm.com 152326SN/A class MyClass { 161689SN/A MyClass() { ...; print_default_created(this); } 171689SN/A ~MyClass() { ...; print_destroyed(this); } 181689SN/A MyClass(const MyClass &c) { ...; print_copy_created(this); } 191689SN/A MyClass(MyClass &&c) { ...; print_move_created(this); } 201689SN/A MyClass(int a, int b) { ...; print_created(this, a, b); } 211689SN/A MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); } 221689SN/A MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); } 231689SN/A 241689SN/A ... 251689SN/A } 261689SN/A 271689SN/AYou can find various examples of these in several of the existing testing .cpp files. (Of course 281689SN/Ayou don't need to add any of the above constructors/operators that you don't actually have, except 291689SN/Afor the destructor). 301689SN/A 311689SN/AEach of these will print an appropriate message such as: 321689SN/A 331689SN/A ### MyClass @ 0x2801910 created via default constructor 341689SN/A ### MyClass @ 0x27fa780 created 100 200 351689SN/A ### MyClass @ 0x2801910 destroyed 361689SN/A ### MyClass @ 0x27fa780 destroyed 371689SN/A 381689SN/AYou can also include extra arguments (such as the 100, 200 in the output above, coming from the 391689SN/Avalue constructor) for all of the above methods which will be included in the output. 402665Ssaidi@eecs.umich.edu 412665Ssaidi@eecs.umich.eduFor testing, each of these also keeps track the created instances and allows you to check how many 422831Sksewell@umich.eduof the various constructors have been invoked from the Python side via code such as: 431689SN/A 441689SN/A from pybind11_tests import ConstructorStats 459944Smatt.horsnell@ARM.com cstats = ConstructorStats.get(MyClass) 469944Smatt.horsnell@ARM.com print(cstats.alive()) 479944Smatt.horsnell@ARM.com print(cstats.default_constructions) 482064SN/A 491060SN/ANote that `.alive()` should usually be the first thing you call as it invokes Python's garbage 501060SN/Acollector to actually destroy objects that aren't yet referenced. 512292SN/A 521717SN/AFor everything except copy and move constructors and destructors, any extra values given to the 538232Snate@binkert.orgprint_...() function is stored in a class-specific values list which you can retrieve and inspect 544762Snate@binkert.orgfrom the ConstructorStats instance `.values()` method. 556221Snate@binkert.org 564762Snate@binkert.orgIn some cases, when you need to track instances of a C++ class not registered with pybind11, you 571060SN/Aneed to add a function returning the ConstructorStats for the C++ class; this can be done with: 588737Skoansin.tan@gmail.com 598737Skoansin.tan@gmail.com m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference) 608737Skoansin.tan@gmail.com 615529Snate@binkert.orgFinally, you can suppress the output messages, but keep the constructor tracking (for 621061SN/Ainspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. 632292SN/A`track_copy_created(this)`). 645606Snate@binkert.org 658581Ssteve.reinhardt@amd.com*/ 668581Ssteve.reinhardt@amd.com 671060SN/A#include "pybind11_tests.h" 682292SN/A#include <unordered_map> 692292SN/A#include <list> 702292SN/A#include <typeindex> 712292SN/A#include <sstream> 722292SN/A 732292SN/Aclass ConstructorStats { 742326SN/Aprotected: 752292SN/A std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents 762292SN/A std::list<std::string> _values; // Used to track values (e.g. of value constructors) 772292SN/Apublic: 782292SN/A int default_constructions = 0; 792292SN/A int copy_constructions = 0; 802292SN/A int move_constructions = 0; 815336Shines@cs.fsu.edu int copy_assignments = 0; 822292SN/A int move_assignments = 0; 834873Sstever@eecs.umich.edu 842292SN/A void copy_created(void *inst) { 852292SN/A created(inst); 862292SN/A copy_constructions++; 874329Sktlim@umich.edu } 885529Snate@binkert.org 894329Sktlim@umich.edu void move_created(void *inst) { 904329Sktlim@umich.edu created(inst); 914329Sktlim@umich.edu move_constructions++; 922292SN/A } 932292SN/A 942292SN/A void default_created(void *inst) { 952292SN/A created(inst); 962292SN/A default_constructions++; 972292SN/A } 985529Snate@binkert.org 991060SN/A void created(void *inst) { 1009920Syasuko.eckert@amd.com ++_instances[inst]; 1019920Syasuko.eckert@amd.com } 10210935Snilay@cs.wisc.edu 1031060SN/A void destroyed(void *inst) { 1041060SN/A if (--_instances[inst] < 0) 1051060SN/A throw std::runtime_error("cstats.destroyed() called with unknown " 1062326SN/A "instance; potential double-destruction " 1071060SN/A "or a missing cstats.created()"); 1081060SN/A } 1091060SN/A 1101060SN/A static void gc() { 1112292SN/A // Force garbage collection to ensure any pending destructors are invoked: 1126221Snate@binkert.org#if defined(PYPY_VERSION) 1136221Snate@binkert.org PyObject *globals = PyEval_GetGlobals(); 1146221Snate@binkert.org PyObject *result = PyRun_String( 1151060SN/A "import gc\n" 1161060SN/A "for i in range(2):" 1172307SN/A " gc.collect()\n", 1182292SN/A Py_file_input, globals, globals); 1192980Sgblack@eecs.umich.edu if (result == nullptr) 1202292SN/A throw py::error_already_set(); 1212292SN/A Py_DECREF(result); 1222292SN/A#else 1232292SN/A py::module::import("gc").attr("collect")(); 1242292SN/A#endif 1252292SN/A } 1262292SN/A 1272292SN/A int alive() { 1282292SN/A gc(); 1292292SN/A int total = 0; 1306221Snate@binkert.org for (const auto &p : _instances) 1316221Snate@binkert.org if (p.second > 0) 1322292SN/A total += p.second; 1332292SN/A return total; 1342292SN/A } 1352292SN/A 1362292SN/A void value() {} // Recursion terminator 1372292SN/A // Takes one or more values, converts them to strings, then stores them. 1382292SN/A template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) { 1392292SN/A std::ostringstream oss; 1402292SN/A oss << v; 1416221Snate@binkert.org _values.push_back(oss.str()); 1426221Snate@binkert.org value(std::forward<Tmore>(args)...); 1432292SN/A } 1442292SN/A 1452831Sksewell@umich.edu // Move out stored values 1462292SN/A py::list values() { 1472292SN/A py::list l; 1482292SN/A for (const auto &v : _values) l.append(py::cast(v)); 1492292SN/A _values.clear(); 1502292SN/A return l; 1512292SN/A } 1522292SN/A 1532292SN/A // Gets constructor stats from a C++ type index 1542292SN/A static ConstructorStats& get(std::type_index type) { 1556221Snate@binkert.org static std::unordered_map<std::type_index, ConstructorStats> all_cstats; 1566221Snate@binkert.org return all_cstats[type]; 1572292SN/A } 1582292SN/A 1592831Sksewell@umich.edu // Gets constructor stats from a C++ type 1602292SN/A template <typename T> static ConstructorStats& get() { 1612292SN/A#if defined(PYPY_VERSION) 1622292SN/A gc(); 1632292SN/A#endif 1642292SN/A return get(typeid(T)); 1652292SN/A } 1662292SN/A 1672292SN/A // Gets constructor stats from a Python class 1682292SN/A static ConstructorStats& get(py::object class_) { 1692292SN/A auto &internals = py::detail::get_internals(); 1702326SN/A const std::type_index *t1 = nullptr, *t2 = nullptr; 1712348SN/A try { 1722326SN/A auto *type_info = internals.registered_types_py.at(class_.ptr()); 1732326SN/A for (auto &p : internals.registered_types_cpp) { 1742348SN/A if (p.second == type_info) { 1752292SN/A if (t1) { 1762292SN/A t2 = &p.first; 1772292SN/A break; 1782292SN/A } 1792292SN/A t1 = &p.first; 1802292SN/A } 1812292SN/A } 1821060SN/A } 1831060SN/A catch (std::out_of_range) {} 1841061SN/A if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); 1851060SN/A auto &cs1 = get(*t1); 1861062SN/A // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever 1871062SN/A // has more constructions (typically one or the other will be 0) 1882301SN/A if (t2) { 1891062SN/A auto &cs2 = get(*t2); 1901062SN/A int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size(); 1911062SN/A int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size(); 1921062SN/A if (cs2_total > cs1_total) return cs2; 1931062SN/A } 1941062SN/A return cs1; 1951062SN/A } 1961062SN/A}; 1971062SN/A 1981062SN/A// To track construction/destruction, you need to call these methods from the various 1992301SN/A// constructors/operators. The ones that take extra values record the given values in the 2002301SN/A// constructor stats values for later inspection. 2012301SN/Atemplate <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } 2022301SN/Atemplate <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } 2031062SN/Atemplate <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) { 2041062SN/A auto &cst = ConstructorStats::get<T>(); 2051062SN/A cst.copy_assignments++; 2061062SN/A cst.value(std::forward<Values>(values)...); 2071062SN/A} 2081062SN/Atemplate <class T, typename... Values> void track_move_assigned(T *, Values &&...values) { 2091062SN/A auto &cst = ConstructorStats::get<T>(); 2101062SN/A cst.move_assignments++; 2111062SN/A cst.value(std::forward<Values>(values)...); 2121062SN/A} 2131062SN/Atemplate <class T, typename... Values> void track_default_created(T *inst, Values &&...values) { 2141062SN/A auto &cst = ConstructorStats::get<T>(); 2151062SN/A cst.default_created(inst); 2161062SN/A cst.value(std::forward<Values>(values)...); 2171062SN/A} 2181062SN/Atemplate <class T, typename... Values> void track_created(T *inst, Values &&...values) { 2191062SN/A auto &cst = ConstructorStats::get<T>(); 2201062SN/A cst.created(inst); 2211062SN/A cst.value(std::forward<Values>(values)...); 2221062SN/A} 2231062SN/Atemplate <class T, typename... Values> void track_destroyed(T *inst) { 2241062SN/A ConstructorStats::get<T>().destroyed(inst); 2251062SN/A} 2261062SN/Atemplate <class T, typename... Values> void track_values(T *, Values &&...values) { 2271062SN/A ConstructorStats::get<T>().value(std::forward<Values>(values)...); 2281062SN/A} 2291062SN/A 2301062SN/A/// Don't cast pointers to Python, print them as strings 2311062SN/Ainline const char *format_ptrs(const char *p) { return p; } 2321062SN/Atemplate <typename T> 2331062SN/Apy::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } 2341062SN/Atemplate <typename T> 2351062SN/Aauto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } 2361062SN/A 2371062SN/Atemplate <class T, typename... Output> 2381062SN/Avoid print_constr_details(T *inst, const std::string &action, Output &&...output) { 2391062SN/A py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action, 2401062SN/A format_ptrs(std::forward<Output>(output))...); 2411062SN/A} 2421062SN/A 2431062SN/A// Verbose versions of the above: 2441062SN/Atemplate <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 2451062SN/A print_constr_details(inst, "created via copy constructor", values...); 2461062SN/A track_copy_created(inst); 2471062SN/A} 2481062SN/Atemplate <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 2491062SN/A print_constr_details(inst, "created via move constructor", values...); 2502361SN/A track_move_created(inst); 2512326SN/A} 2522301SN/Atemplate <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) { 2532301SN/A print_constr_details(inst, "assigned via copy assignment", values...); 2542301SN/A track_copy_assigned(inst, values...); 2552301SN/A} 2562301SN/Atemplate <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) { 2572301SN/A print_constr_details(inst, "assigned via move assignment", values...); 2582326SN/A track_move_assigned(inst, values...); 2592301SN/A} 2602361SN/Atemplate <class T, typename... Values> void print_default_created(T *inst, Values &&...values) { 2612326SN/A print_constr_details(inst, "created via default constructor", values...); 2622307SN/A track_default_created(inst, values...); 2638240Snate@binkert.org} 2642301SN/Atemplate <class T, typename... Values> void print_created(T *inst, Values &&...values) { 2652307SN/A print_constr_details(inst, "created", values...); 2662301SN/A track_created(inst, values...); 2672301SN/A} 2682301SN/Atemplate <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values 2692301SN/A print_constr_details(inst, "destroyed", values...); 2708240Snate@binkert.org track_destroyed(inst); 2712301SN/A} 2722301SN/Atemplate <class T, typename... Values> void print_values(T *inst, Values &&...values) { 2732301SN/A print_constr_details(inst, ":", values...); 2742301SN/A track_values(inst, values...); 2752301SN/A} 2762301SN/A 2772301SN/A