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