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