Blame external/pybind11/tests/constructor_stats.h

Packit 534379
#pragma once
Packit 534379
/*
Packit 534379
    tests/constructor_stats.h -- framework for printing and tracking object
Packit 534379
    instance lifetimes in example/test code.
Packit 534379
Packit 534379
    Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>
Packit 534379
Packit 534379
    All rights reserved. Use of this source code is governed by a
Packit 534379
    BSD-style license that can be found in the LICENSE file.
Packit 534379
Packit 534379
This header provides a few useful tools for writing examples or tests that want to check and/or
Packit 534379
display object instance lifetimes.  It requires that you include this header and add the following
Packit 534379
function calls to constructors:
Packit 534379
Packit 534379
    class MyClass {
Packit 534379
        MyClass() { ...; print_default_created(this); }
Packit 534379
        ~MyClass() { ...; print_destroyed(this); }
Packit 534379
        MyClass(const MyClass &c) { ...; print_copy_created(this); }
Packit 534379
        MyClass(MyClass &&c) { ...; print_move_created(this); }
Packit 534379
        MyClass(int a, int b) { ...; print_created(this, a, b); }
Packit 534379
        MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
Packit 534379
        MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }
Packit 534379
Packit 534379
        ...
Packit 534379
    }
Packit 534379
Packit 534379
You can find various examples of these in several of the existing testing .cpp files.  (Of course
Packit 534379
you don't need to add any of the above constructors/operators that you don't actually have, except
Packit 534379
for the destructor).
Packit 534379
Packit 534379
Each of these will print an appropriate message such as:
Packit 534379
Packit 534379
    ### MyClass @ 0x2801910 created via default constructor
Packit 534379
    ### MyClass @ 0x27fa780 created 100 200
Packit 534379
    ### MyClass @ 0x2801910 destroyed
Packit 534379
    ### MyClass @ 0x27fa780 destroyed
Packit 534379
Packit 534379
You can also include extra arguments (such as the 100, 200 in the output above, coming from the
Packit 534379
value constructor) for all of the above methods which will be included in the output.
Packit 534379
Packit 534379
For testing, each of these also keeps track the created instances and allows you to check how many
Packit 534379
of the various constructors have been invoked from the Python side via code such as:
Packit 534379
Packit 534379
    from pybind11_tests import ConstructorStats
Packit 534379
    cstats = ConstructorStats.get(MyClass)
Packit 534379
    print(cstats.alive())
Packit 534379
    print(cstats.default_constructions)
Packit 534379
Packit 534379
Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
Packit 534379
collector to actually destroy objects that aren't yet referenced.
Packit 534379
Packit 534379
For everything except copy and move constructors and destructors, any extra values given to the
Packit 534379
print_...() function is stored in a class-specific values list which you can retrieve and inspect
Packit 534379
from the ConstructorStats instance `.values()` method.
Packit 534379
Packit 534379
In some cases, when you need to track instances of a C++ class not registered with pybind11, you
Packit 534379
need to add a function returning the ConstructorStats for the C++ class; this can be done with:
Packit 534379
Packit 534379
    m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>, py::return_value_policy::reference)
Packit 534379
Packit 534379
Finally, you can suppress the output messages, but keep the constructor tracking (for
Packit 534379
inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
Packit 534379
`track_copy_created(this)`).
Packit 534379
Packit 534379
*/
Packit 534379
Packit 534379
#include "pybind11_tests.h"
Packit 534379
#include <unordered_map>
Packit 534379
#include <list>
Packit 534379
#include <typeindex>
Packit 534379
#include <sstream>
Packit 534379
Packit 534379
class ConstructorStats {
Packit 534379
protected:
Packit 534379
    std::unordered_map<void*, int> _instances; // Need a map rather than set because members can shared address with parents
Packit 534379
    std::list<std::string> _values; // Used to track values (e.g. of value constructors)
Packit 534379
public:
Packit 534379
    int default_constructions = 0;
Packit 534379
    int copy_constructions = 0;
Packit 534379
    int move_constructions = 0;
Packit 534379
    int copy_assignments = 0;
Packit 534379
    int move_assignments = 0;
Packit 534379
Packit 534379
    void copy_created(void *inst) {
Packit 534379
        created(inst);
Packit 534379
        copy_constructions++;
Packit 534379
    }
Packit 534379
Packit 534379
    void move_created(void *inst) {
Packit 534379
        created(inst);
Packit 534379
        move_constructions++;
Packit 534379
    }
Packit 534379
Packit 534379
    void default_created(void *inst) {
Packit 534379
        created(inst);
Packit 534379
        default_constructions++;
Packit 534379
    }
Packit 534379
Packit 534379
    void created(void *inst) {
Packit 534379
        ++_instances[inst];
Packit 534379
    }
Packit 534379
Packit 534379
    void destroyed(void *inst) {
Packit 534379
        if (--_instances[inst] < 0)
Packit 534379
            throw std::runtime_error("cstats.destroyed() called with unknown "
Packit 534379
                                     "instance; potential double-destruction "
Packit 534379
                                     "or a missing cstats.created()");
Packit 534379
    }
Packit 534379
Packit 534379
    static void gc() {
Packit 534379
        // Force garbage collection to ensure any pending destructors are invoked:
Packit 534379
#if defined(PYPY_VERSION)
Packit 534379
        PyObject *globals = PyEval_GetGlobals();
Packit 534379
        PyObject *result = PyRun_String(
Packit 534379
            "import gc\n"
Packit 534379
            "for i in range(2):"
Packit 534379
            "    gc.collect()\n",
Packit 534379
            Py_file_input, globals, globals);
Packit 534379
        if (result == nullptr)
Packit 534379
            throw py::error_already_set();
Packit 534379
        Py_DECREF(result);
Packit 534379
#else
Packit 534379
        py::module::import("gc").attr("collect")();
Packit 534379
#endif
Packit 534379
    }
Packit 534379
Packit 534379
    int alive() {
Packit 534379
        gc();
Packit 534379
        int total = 0;
Packit 534379
        for (const auto &p : _instances)
Packit 534379
            if (p.second > 0)
Packit 534379
                total += p.second;
Packit 534379
        return total;
Packit 534379
    }
Packit 534379
Packit 534379
    void value() {} // Recursion terminator
Packit 534379
    // Takes one or more values, converts them to strings, then stores them.
Packit 534379
    template <typename T, typename... Tmore> void value(const T &v, Tmore &&...args) {
Packit 534379
        std::ostringstream oss;
Packit 534379
        oss << v;
Packit 534379
        _values.push_back(oss.str());
Packit 534379
        value(std::forward<Tmore>(args)...);
Packit 534379
    }
Packit 534379
Packit 534379
    // Move out stored values
Packit 534379
    py::list values() {
Packit 534379
        py::list l;
Packit 534379
        for (const auto &v : _values) l.append(py::cast(v));
Packit 534379
        _values.clear();
Packit 534379
        return l;
Packit 534379
    }
Packit 534379
Packit 534379
    // Gets constructor stats from a C++ type index
Packit 534379
    static ConstructorStats& get(std::type_index type) {
Packit 534379
        static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
Packit 534379
        return all_cstats[type];
Packit 534379
    }
Packit 534379
Packit 534379
    // Gets constructor stats from a C++ type
Packit 534379
    template <typename T> static ConstructorStats& get() {
Packit 534379
#if defined(PYPY_VERSION)
Packit 534379
        gc();
Packit 534379
#endif
Packit 534379
        return get(typeid(T));
Packit 534379
    }
Packit 534379
Packit 534379
    // Gets constructor stats from a Python class
Packit 534379
    static ConstructorStats& get(py::object class_) {
Packit 534379
        auto &internals = py::detail::get_internals();
Packit 534379
        const std::type_index *t1 = nullptr, *t2 = nullptr;
Packit 534379
        try {
Packit 534379
            auto *type_info = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
Packit 534379
            for (auto &p : internals.registered_types_cpp) {
Packit 534379
                if (p.second == type_info) {
Packit 534379
                    if (t1) {
Packit 534379
                        t2 = &p.first;
Packit 534379
                        break;
Packit 534379
                    }
Packit 534379
                    t1 = &p.first;
Packit 534379
                }
Packit 534379
            }
Packit 534379
        }
Packit 534379
        catch (const std::out_of_range &) {}
Packit 534379
        if (!t1) throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
Packit 534379
        auto &cs1 = get(*t1);
Packit 534379
        // If we have both a t1 and t2 match, one is probably the trampoline class; return whichever
Packit 534379
        // has more constructions (typically one or the other will be 0)
Packit 534379
        if (t2) {
Packit 534379
            auto &cs2 = get(*t2);
Packit 534379
            int cs1_total = cs1.default_constructions + cs1.copy_constructions + cs1.move_constructions + (int) cs1._values.size();
Packit 534379
            int cs2_total = cs2.default_constructions + cs2.copy_constructions + cs2.move_constructions + (int) cs2._values.size();
Packit 534379
            if (cs2_total > cs1_total) return cs2;
Packit 534379
        }
Packit 534379
        return cs1;
Packit 534379
    }
Packit 534379
};
Packit 534379
Packit 534379
// To track construction/destruction, you need to call these methods from the various
Packit 534379
// constructors/operators.  The ones that take extra values record the given values in the
Packit 534379
// constructor stats values for later inspection.
Packit 534379
template <class T> void track_copy_created(T *inst) { ConstructorStats::get<T>().copy_created(inst); }
Packit 534379
template <class T> void track_move_created(T *inst) { ConstructorStats::get<T>().move_created(inst); }
Packit 534379
template <class T, typename... Values> void track_copy_assigned(T *, Values &&...values) {
Packit 534379
    auto &cst = ConstructorStats::get<T>();
Packit 534379
    cst.copy_assignments++;
Packit 534379
    cst.value(std::forward<Values>(values)...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void track_move_assigned(T *, Values &&...values) {
Packit 534379
    auto &cst = ConstructorStats::get<T>();
Packit 534379
    cst.move_assignments++;
Packit 534379
    cst.value(std::forward<Values>(values)...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void track_default_created(T *inst, Values &&...values) {
Packit 534379
    auto &cst = ConstructorStats::get<T>();
Packit 534379
    cst.default_created(inst);
Packit 534379
    cst.value(std::forward<Values>(values)...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void track_created(T *inst, Values &&...values) {
Packit 534379
    auto &cst = ConstructorStats::get<T>();
Packit 534379
    cst.created(inst);
Packit 534379
    cst.value(std::forward<Values>(values)...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void track_destroyed(T *inst) {
Packit 534379
    ConstructorStats::get<T>().destroyed(inst);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void track_values(T *, Values &&...values) {
Packit 534379
    ConstructorStats::get<T>().value(std::forward<Values>(values)...);
Packit 534379
}
Packit 534379
Packit 534379
/// Don't cast pointers to Python, print them as strings
Packit 534379
inline const char *format_ptrs(const char *p) { return p; }
Packit 534379
template <typename T>
Packit 534379
py::str format_ptrs(T *p) { return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p)); }
Packit 534379
template <typename T>
Packit 534379
auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) { return std::forward<T>(x); }
Packit 534379
Packit 534379
template <class T, typename... Output>
Packit 534379
void print_constr_details(T *inst, const std::string &action, Output &&...output) {
Packit 534379
    py::print("###", py::type_id<T>(), "@", format_ptrs(inst), action,
Packit 534379
              format_ptrs(std::forward<Output>(output))...);
Packit 534379
}
Packit 534379
Packit 534379
// Verbose versions of the above:
Packit 534379
template <class T, typename... Values> void print_copy_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
Packit 534379
    print_constr_details(inst, "created via copy constructor", values...);
Packit 534379
    track_copy_created(inst);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void print_move_created(T *inst, Values &&...values) { // NB: this prints, but doesn't store, given values
Packit 534379
    print_constr_details(inst, "created via move constructor", values...);
Packit 534379
    track_move_created(inst);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void print_copy_assigned(T *inst, Values &&...values) {
Packit 534379
    print_constr_details(inst, "assigned via copy assignment", values...);
Packit 534379
    track_copy_assigned(inst, values...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void print_move_assigned(T *inst, Values &&...values) {
Packit 534379
    print_constr_details(inst, "assigned via move assignment", values...);
Packit 534379
    track_move_assigned(inst, values...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void print_default_created(T *inst, Values &&...values) {
Packit 534379
    print_constr_details(inst, "created via default constructor", values...);
Packit 534379
    track_default_created(inst, values...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void print_created(T *inst, Values &&...values) {
Packit 534379
    print_constr_details(inst, "created", values...);
Packit 534379
    track_created(inst, values...);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
Packit 534379
    print_constr_details(inst, "destroyed", values...);
Packit 534379
    track_destroyed(inst);
Packit 534379
}
Packit 534379
template <class T, typename... Values> void print_values(T *inst, Values &&...values) {
Packit 534379
    print_constr_details(inst, ":", values...);
Packit 534379
    track_values(inst, values...);
Packit 534379
}
Packit 534379