constructor_stats.h revision 11986
111986Sandreas.sandberg@arm.com#pragma once 211986Sandreas.sandberg@arm.com/* 311986Sandreas.sandberg@arm.com tests/constructor_stats.h -- framework for printing and tracking object 411986Sandreas.sandberg@arm.com instance lifetimes in example/test code. 511986Sandreas.sandberg@arm.com 611986Sandreas.sandberg@arm.com Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca> 711986Sandreas.sandberg@arm.com 811986Sandreas.sandberg@arm.com All rights reserved. Use of this source code is governed by a 911986Sandreas.sandberg@arm.com BSD-style license that can be found in the LICENSE file. 1011986Sandreas.sandberg@arm.com 1111986Sandreas.sandberg@arm.comThis header provides a few useful tools for writing examples or tests that want to check and/or 1211986Sandreas.sandberg@arm.comdisplay object instance lifetimes. It requires that you include this header and add the following 1311986Sandreas.sandberg@arm.comfunction calls to constructors: 1411986Sandreas.sandberg@arm.com 1511986Sandreas.sandberg@arm.com class MyClass { 1611986Sandreas.sandberg@arm.com MyClass() { ...; print_default_created(this); } 1711986Sandreas.sandberg@arm.com ~MyClass() { ...; print_destroyed(this); } 1811986Sandreas.sandberg@arm.com MyClass(const MyClass &c) { ...; print_copy_created(this); } 1911986Sandreas.sandberg@arm.com MyClass(MyClass &&c) { ...; print_move_created(this); } 2011986Sandreas.sandberg@arm.com MyClass(int a, int b) { ...; print_created(this, a, b); } 2111986Sandreas.sandberg@arm.com MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); } 2211986Sandreas.sandberg@arm.com MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); } 2311986Sandreas.sandberg@arm.com 2411986Sandreas.sandberg@arm.com ... 2511986Sandreas.sandberg@arm.com } 2611986Sandreas.sandberg@arm.com 2711986Sandreas.sandberg@arm.comYou can find various examples of these in several of the existing example .cpp files. (Of course 2811986Sandreas.sandberg@arm.comyou don't need to add any of the above constructors/operators that you don't actually have, except 2911986Sandreas.sandberg@arm.comfor the destructor). 3011986Sandreas.sandberg@arm.com 3111986Sandreas.sandberg@arm.comEach of these will print an appropriate message such as: 3211986Sandreas.sandberg@arm.com 3311986Sandreas.sandberg@arm.com ### MyClass @ 0x2801910 created via default constructor 3411986Sandreas.sandberg@arm.com ### MyClass @ 0x27fa780 created 100 200 3511986Sandreas.sandberg@arm.com ### MyClass @ 0x2801910 destroyed 3611986Sandreas.sandberg@arm.com ### MyClass @ 0x27fa780 destroyed 3711986Sandreas.sandberg@arm.com 3811986Sandreas.sandberg@arm.comYou can also include extra arguments (such as the 100, 200 in the output above, coming from the 3911986Sandreas.sandberg@arm.comvalue constructor) for all of the above methods which will be included in the output. 4011986Sandreas.sandberg@arm.com 4111986Sandreas.sandberg@arm.comFor testing, each of these also keeps track the created instances and allows you to check how many 4211986Sandreas.sandberg@arm.comof the various constructors have been invoked from the Python side via code such as: 4311986Sandreas.sandberg@arm.com 4411986Sandreas.sandberg@arm.com from example import ConstructorStats 4511986Sandreas.sandberg@arm.com cstats = ConstructorStats.get(MyClass) 4611986Sandreas.sandberg@arm.com print(cstats.alive()) 4711986Sandreas.sandberg@arm.com print(cstats.default_constructions) 4811986Sandreas.sandberg@arm.com 4911986Sandreas.sandberg@arm.comNote that `.alive()` should usually be the first thing you call as it invokes Python's garbage 5011986Sandreas.sandberg@arm.comcollector to actually destroy objects that aren't yet referenced. 5111986Sandreas.sandberg@arm.com 5211986Sandreas.sandberg@arm.comFor everything except copy and move constructors and destructors, any extra values given to the 5311986Sandreas.sandberg@arm.comprint_...() function is stored in a class-specific values list which you can retrieve and inspect 5411986Sandreas.sandberg@arm.comfrom the ConstructorStats instance `.values()` method. 5511986Sandreas.sandberg@arm.com 5611986Sandreas.sandberg@arm.comIn some cases, when you need to track instances of a C++ class not registered with pybind11, you 5711986Sandreas.sandberg@arm.comneed to add a function returning the ConstructorStats for the C++ class; this can be done with: 5811986Sandreas.sandberg@arm.com 5911986Sandreas.sandberg@arm.com m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference) 6011986Sandreas.sandberg@arm.com 6111986Sandreas.sandberg@arm.comFinally, you can suppress the output messages, but keep the constructor tracking (for 6211986Sandreas.sandberg@arm.cominspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g. 6311986Sandreas.sandberg@arm.com`track_copy_created(this)`). 6411986Sandreas.sandberg@arm.com 6511986Sandreas.sandberg@arm.com*/ 6611986Sandreas.sandberg@arm.com 6711986Sandreas.sandberg@arm.com#include "pybind11_tests.h" 6811986Sandreas.sandberg@arm.com#include <unordered_map> 6911986Sandreas.sandberg@arm.com#include <list> 7011986Sandreas.sandberg@arm.com#include <typeindex> 7111986Sandreas.sandberg@arm.com#include <sstream> 7211986Sandreas.sandberg@arm.com 7311986Sandreas.sandberg@arm.comclass ConstructorStats { 7411986Sandreas.sandberg@arm.comprotected: 7511986Sandreas.sandberg@arm.com std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents 7611986Sandreas.sandberg@arm.com std::list<std::string> _values; // Used to track values (e.g. of value constructors) 7711986Sandreas.sandberg@arm.compublic: 7811986Sandreas.sandberg@arm.com int default_constructions = 0; 7911986Sandreas.sandberg@arm.com int copy_constructions = 0; 8011986Sandreas.sandberg@arm.com int move_constructions = 0; 8111986Sandreas.sandberg@arm.com int copy_assignments = 0; 8211986Sandreas.sandberg@arm.com int move_assignments = 0; 8311986Sandreas.sandberg@arm.com 8411986Sandreas.sandberg@arm.com void copy_created(void *inst) { 8511986Sandreas.sandberg@arm.com created(inst); 8611986Sandreas.sandberg@arm.com copy_constructions++; 8711986Sandreas.sandberg@arm.com } 8811986Sandreas.sandberg@arm.com void move_created(void *inst) { 8911986Sandreas.sandberg@arm.com created(inst); 9011986Sandreas.sandberg@arm.com move_constructions++; 9111986Sandreas.sandberg@arm.com } 9211986Sandreas.sandberg@arm.com void default_created(void *inst) { 9311986Sandreas.sandberg@arm.com created(inst); 9411986Sandreas.sandberg@arm.com default_constructions++; 9511986Sandreas.sandberg@arm.com } 9611986Sandreas.sandberg@arm.com void created(void *inst) { 9711986Sandreas.sandberg@arm.com ++_instances[inst]; 9811986Sandreas.sandberg@arm.com }; 9911986Sandreas.sandberg@arm.com void destroyed(void *inst) { 10011986Sandreas.sandberg@arm.com if (--_instances[inst] < 0) 10111986Sandreas.sandberg@arm.com throw std::runtime_error("cstats.destroyed() called with unknown instance; potential double-destruction or a missing cstats.created()"); 10211986Sandreas.sandberg@arm.com } 10311986Sandreas.sandberg@arm.com 10411986Sandreas.sandberg@arm.com int alive() { 10511986Sandreas.sandberg@arm.com // Force garbage collection to ensure any pending destructors are invoked: 10611986Sandreas.sandberg@arm.com py::module::import("gc").attr("collect")(); 10711986Sandreas.sandberg@arm.com int total = 0; 10811986Sandreas.sandberg@arm.com for (const auto &p : _instances) if (p.second > 0) total += p.second; 10911986Sandreas.sandberg@arm.com return total; 11011986Sandreas.sandberg@arm.com } 11111986Sandreas.sandberg@arm.com 11211986Sandreas.sandberg@arm.com void value() {} // Recursion terminator 11311986Sandreas.sandberg@arm.com // Takes one or more values, converts them to strings, then stores them. 11411986Sandreas.sandberg@arm.com template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) { 11511986Sandreas.sandberg@arm.com std::ostringstream oss; 11611986Sandreas.sandberg@arm.com oss << v; 11711986Sandreas.sandberg@arm.com _values.push_back(oss.str()); 11811986Sandreas.sandberg@arm.com value(std::forward<Tmore>(args)...); 11911986Sandreas.sandberg@arm.com } 12011986Sandreas.sandberg@arm.com 12111986Sandreas.sandberg@arm.com // Move out stored values 12211986Sandreas.sandberg@arm.com py::list values() { 12311986Sandreas.sandberg@arm.com py::list l; 12411986Sandreas.sandberg@arm.com for (const auto &v : _values) l.append(py::cast(v)); 12511986Sandreas.sandberg@arm.com _values.clear(); 12611986Sandreas.sandberg@arm.com return l; 12711986Sandreas.sandberg@arm.com } 12811986Sandreas.sandberg@arm.com 12911986Sandreas.sandberg@arm.com // Gets constructor stats from a C++ type index 13011986Sandreas.sandberg@arm.com static ConstructorStats& get(std::type_index type) { 13111986Sandreas.sandberg@arm.com static std::unordered_map<std::type_index, ConstructorStats> all_cstats; 13211986Sandreas.sandberg@arm.com return all_cstats[type]; 13311986Sandreas.sandberg@arm.com } 13411986Sandreas.sandberg@arm.com 13511986Sandreas.sandberg@arm.com // Gets constructor stats from a C++ type 13611986Sandreas.sandberg@arm.com template <typename T> static ConstructorStats& get() { 13711986Sandreas.sandberg@arm.com return get(typeid(T)); 13811986Sandreas.sandberg@arm.com } 13911986Sandreas.sandberg@arm.com 14011986Sandreas.sandberg@arm.com // Gets constructor stats from a Python class 14111986Sandreas.sandberg@arm.com static ConstructorStats& get(py::object class_) { 14211986Sandreas.sandberg@arm.com auto &internals = py::detail::get_internals(); 14311986Sandreas.sandberg@arm.com const std::type_index *t1 = nullptr, *t2 = nullptr; 14411986Sandreas.sandberg@arm.com try { 14511986Sandreas.sandberg@arm.com auto *type_info = internals.registered_types_py.at(class_.ptr()); 14611986Sandreas.sandberg@arm.com for (auto &p : internals.registered_types_cpp) { 14711986Sandreas.sandberg@arm.com if (p.second == type_info) { 14811986Sandreas.sandberg@arm.com if (t1) { 14911986Sandreas.sandberg@arm.com t2 = &p.first; 15011986Sandreas.sandberg@arm.com break; 15111986Sandreas.sandberg@arm.com } 15211986Sandreas.sandberg@arm.com t1 = &p.first; 15311986Sandreas.sandberg@arm.com } 15411986Sandreas.sandberg@arm.com } 15511986Sandreas.sandberg@arm.com } 15611986Sandreas.sandberg@arm.com catch (std::out_of_range) {} 15711986Sandreas.sandberg@arm.com if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()"); 15811986Sandreas.sandberg@arm.com auto &cs1 = get(*t1); 15911986Sandreas.sandberg@arm.com // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever 16011986Sandreas.sandberg@arm.com // has more constructions (typically one or the other will be 0) 16111986Sandreas.sandberg@arm.com if (t2) { 16211986Sandreas.sandberg@arm.com auto &cs2 = get(*t2); 16311986Sandreas.sandberg@arm.com int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size(); 16411986Sandreas.sandberg@arm.com int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size(); 16511986Sandreas.sandberg@arm.com if (cs2_total > cs1_total) return cs2; 16611986Sandreas.sandberg@arm.com } 16711986Sandreas.sandberg@arm.com return cs1; 16811986Sandreas.sandberg@arm.com } 16911986Sandreas.sandberg@arm.com}; 17011986Sandreas.sandberg@arm.com 17111986Sandreas.sandberg@arm.com// To track construction/destruction, you need to call these methods from the various 17211986Sandreas.sandberg@arm.com// constructors/operators. The ones that take extra values record the given values in the 17311986Sandreas.sandberg@arm.com// constructor stats values for later inspection. 17411986Sandreas.sandberg@arm.comtemplate <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); } 17511986Sandreas.sandberg@arm.comtemplate <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); } 17611986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) { 17711986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 17811986Sandreas.sandberg@arm.com cst.copy_assignments++; 17911986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 18011986Sandreas.sandberg@arm.com} 18111986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_move_assigned(T *, Values &&...values) { 18211986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 18311986Sandreas.sandberg@arm.com cst.move_assignments++; 18411986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 18511986Sandreas.sandberg@arm.com} 18611986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_default_created(T *inst, Values &&...values) { 18711986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 18811986Sandreas.sandberg@arm.com cst.default_created(inst); 18911986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 19011986Sandreas.sandberg@arm.com} 19111986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_created(T *inst, Values &&...values) { 19211986Sandreas.sandberg@arm.com auto &cst = ConstructorStats::get<T>(); 19311986Sandreas.sandberg@arm.com cst.created(inst); 19411986Sandreas.sandberg@arm.com cst.value(std::forward<Values>(values)...); 19511986Sandreas.sandberg@arm.com} 19611986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_destroyed(T *inst) { 19711986Sandreas.sandberg@arm.com ConstructorStats::get<T>().destroyed(inst); 19811986Sandreas.sandberg@arm.com} 19911986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void track_values(T *, Values &&...values) { 20011986Sandreas.sandberg@arm.com ConstructorStats::get<T>().value(std::forward<Values>(values)...); 20111986Sandreas.sandberg@arm.com} 20211986Sandreas.sandberg@arm.com 20311986Sandreas.sandberg@arm.com/// Don't cast pointers to Python, print them as strings 20411986Sandreas.sandberg@arm.cominline const char *format_ptrs(const char *p) { return p; } 20511986Sandreas.sandberg@arm.comtemplate <typename T> 20611986Sandreas.sandberg@arm.compy::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); } 20711986Sandreas.sandberg@arm.comtemplate <typename T> 20811986Sandreas.sandberg@arm.comauto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); } 20911986Sandreas.sandberg@arm.com 21011986Sandreas.sandberg@arm.comtemplate <class T, typename... Output> 21111986Sandreas.sandberg@arm.comvoid print_constr_details(T *inst, const std::string &action, Output &&...output) { 21211986Sandreas.sandberg@arm.com py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action, 21311986Sandreas.sandberg@arm.com format_ptrs(std::forward<Output>(output))...); 21411986Sandreas.sandberg@arm.com} 21511986Sandreas.sandberg@arm.com 21611986Sandreas.sandberg@arm.com// Verbose versions of the above: 21711986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 21811986Sandreas.sandberg@arm.com print_constr_details(inst, "created via copy constructor", values...); 21911986Sandreas.sandberg@arm.com track_copy_created(inst); 22011986Sandreas.sandberg@arm.com} 22111986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values 22211986Sandreas.sandberg@arm.com print_constr_details(inst, "created via move constructor", values...); 22311986Sandreas.sandberg@arm.com track_move_created(inst); 22411986Sandreas.sandberg@arm.com} 22511986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) { 22611986Sandreas.sandberg@arm.com print_constr_details(inst, "assigned via copy assignment", values...); 22711986Sandreas.sandberg@arm.com track_copy_assigned(inst, values...); 22811986Sandreas.sandberg@arm.com} 22911986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) { 23011986Sandreas.sandberg@arm.com print_constr_details(inst, "assigned via move assignment", values...); 23111986Sandreas.sandberg@arm.com track_move_assigned(inst, values...); 23211986Sandreas.sandberg@arm.com} 23311986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_default_created(T *inst, Values &&...values) { 23411986Sandreas.sandberg@arm.com print_constr_details(inst, "created via default constructor", values...); 23511986Sandreas.sandberg@arm.com track_default_created(inst, values...); 23611986Sandreas.sandberg@arm.com} 23711986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_created(T *inst, Values &&...values) { 23811986Sandreas.sandberg@arm.com print_constr_details(inst, "created", values...); 23911986Sandreas.sandberg@arm.com track_created(inst, values...); 24011986Sandreas.sandberg@arm.com} 24111986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values 24211986Sandreas.sandberg@arm.com print_constr_details(inst, "destroyed", values...); 24311986Sandreas.sandberg@arm.com track_destroyed(inst); 24411986Sandreas.sandberg@arm.com} 24511986Sandreas.sandberg@arm.comtemplate <class T, typename... Values> void print_values(T *inst, Values &&...values) { 24611986Sandreas.sandberg@arm.com print_constr_details(inst, ":", values...); 24711986Sandreas.sandberg@arm.com track_values(inst, values...); 24811986Sandreas.sandberg@arm.com} 24911986Sandreas.sandberg@arm.com 250