constructor_stats.h 11.5 KB
Newer Older
1
2
#pragma once
/*
Dean Moldovan's avatar
Dean Moldovan committed
3
    tests/constructor_stats.h -- framework for printing and tracking object
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
    instance lifetimes in example/test code.

    Copyright (c) 2016 Jason Rhinelander <jason@imaginary.ca>

    All rights reserved. Use of this source code is governed by a
    BSD-style license that can be found in the LICENSE file.

This header provides a few useful tools for writing examples or tests that want to check and/or
display object instance lifetimes.  It requires that you include this header and add the following
function calls to constructors:

    class MyClass {
        MyClass() { ...; print_default_created(this); }
        ~MyClass() { ...; print_destroyed(this); }
        MyClass(const MyClass &c) { ...; print_copy_created(this); }
        MyClass(MyClass &&c) { ...; print_move_created(this); }
        MyClass(int a, int b) { ...; print_created(this, a, b); }
        MyClass &operator=(const MyClass &c) { ...; print_copy_assigned(this); }
        MyClass &operator=(MyClass &&c) { ...; print_move_assigned(this); }

        ...
    }

Jason Rhinelander's avatar
Jason Rhinelander committed
27
You can find various examples of these in several of the existing testing .cpp files.  (Of course
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
you don't need to add any of the above constructors/operators that you don't actually have, except
for the destructor).

Each of these will print an appropriate message such as:

    ### MyClass @ 0x2801910 created via default constructor
    ### MyClass @ 0x27fa780 created 100 200
    ### MyClass @ 0x2801910 destroyed
    ### MyClass @ 0x27fa780 destroyed

You can also include extra arguments (such as the 100, 200 in the output above, coming from the
value constructor) for all of the above methods which will be included in the output.

For testing, each of these also keeps track the created instances and allows you to check how many
of the various constructors have been invoked from the Python side via code such as:

Jason Rhinelander's avatar
Jason Rhinelander committed
44
    from pybind11_tests import ConstructorStats
45
46
47
48
49
50
51
52
53
54
55
56
57
58
    cstats = ConstructorStats.get(MyClass)
    print(cstats.alive())
    print(cstats.default_constructions)

Note that `.alive()` should usually be the first thing you call as it invokes Python's garbage
collector to actually destroy objects that aren't yet referenced.

For everything except copy and move constructors and destructors, any extra values given to the
print_...() function is stored in a class-specific values list which you can retrieve and inspect
from the ConstructorStats instance `.values()` method.

In some cases, when you need to track instances of a C++ class not registered with pybind11, you
need to add a function returning the ConstructorStats for the C++ class; this can be done with:

59
60
    m.def("get_special_cstats", &ConstructorStats::get<SpecialClass>,
py::return_value_policy::reference)
61
62
63
64
65
66
67

Finally, you can suppress the output messages, but keep the constructor tracking (for
inspection/testing in python) by using the functions with `print_` replaced with `track_` (e.g.
`track_copy_created(this)`).

*/

Dean Moldovan's avatar
Dean Moldovan committed
68
#include "pybind11_tests.h"
69

70
71
#include <list>
#include <sstream>
72
73
#include <typeindex>
#include <unordered_map>
74
75
76

class ConstructorStats {
protected:
77
78
79
80
    std::unordered_map<void *, int> _instances; // Need a map rather than set because members can
                                                // shared address with parents
    std::list<std::string> _values;             // Used to track values
                                                // (e.g. of value constructors)
81
82
83
84
85
86
87
88
89
90
91
public:
    int default_constructions = 0;
    int copy_constructions = 0;
    int move_constructions = 0;
    int copy_assignments = 0;
    int move_assignments = 0;

    void copy_created(void *inst) {
        created(inst);
        copy_constructions++;
    }
Wenzel Jakob's avatar
Wenzel Jakob committed
92

93
94
95
96
    void move_created(void *inst) {
        created(inst);
        move_constructions++;
    }
Wenzel Jakob's avatar
Wenzel Jakob committed
97

98
99
100
101
    void default_created(void *inst) {
        created(inst);
        default_constructions++;
    }
Wenzel Jakob's avatar
Wenzel Jakob committed
102

103
    void created(void *inst) { ++_instances[inst]; }
Wenzel Jakob's avatar
Wenzel Jakob committed
104

105
    void destroyed(void *inst) {
106
        if (--_instances[inst] < 0) {
Wenzel Jakob's avatar
Wenzel Jakob committed
107
108
109
            throw std::runtime_error("cstats.destroyed() called with unknown "
                                     "instance; potential double-destruction "
                                     "or a missing cstats.created()");
110
        }
111
112
    }

Wenzel Jakob's avatar
Wenzel Jakob committed
113
    static void gc() {
114
        // Force garbage collection to ensure any pending destructors are invoked:
Wenzel Jakob's avatar
Wenzel Jakob committed
115
116
#if defined(PYPY_VERSION)
        PyObject *globals = PyEval_GetGlobals();
117
        PyObject *result = PyRun_String("import gc\n"
118
                                        "for i in range(2):\n"
119
120
121
122
                                        "    gc.collect()\n",
                                        Py_file_input,
                                        globals,
                                        globals);
Wenzel Jakob's avatar
Wenzel Jakob committed
123
124
125
126
        if (result == nullptr)
            throw py::error_already_set();
        Py_DECREF(result);
#else
127
        py::module_::import("gc").attr("collect")();
Wenzel Jakob's avatar
Wenzel Jakob committed
128
129
130
131
132
#endif
    }

    int alive() {
        gc();
133
        int total = 0;
134
135
        for (const auto &p : _instances) {
            if (p.second > 0) {
Wenzel Jakob's avatar
Wenzel Jakob committed
136
                total += p.second;
137
138
            }
        }
139
140
141
142
143
        return total;
    }

    void value() {} // Recursion terminator
    // Takes one or more values, converts them to strings, then stores them.
144
145
    template <typename T, typename... Tmore>
    void value(const T &v, Tmore &&...args) {
146
147
148
149
150
        std::ostringstream oss;
        oss << v;
        _values.push_back(oss.str());
        value(std::forward<Tmore>(args)...);
    }
Dean Moldovan's avatar
Dean Moldovan committed
151
152

    // Move out stored values
153
154
    py::list values() {
        py::list l;
155
156
157
        for (const auto &v : _values) {
            l.append(py::cast(v));
        }
Dean Moldovan's avatar
Dean Moldovan committed
158
        _values.clear();
159
160
161
162
        return l;
    }

    // Gets constructor stats from a C++ type index
163
    static ConstructorStats &get(std::type_index type) {
164
165
166
167
168
        static std::unordered_map<std::type_index, ConstructorStats> all_cstats;
        return all_cstats[type];
    }

    // Gets constructor stats from a C++ type
169
170
    template <typename T>
    static ConstructorStats &get() {
Wenzel Jakob's avatar
Wenzel Jakob committed
171
172
173
#if defined(PYPY_VERSION)
        gc();
#endif
174
175
176
177
        return get(typeid(T));
    }

    // Gets constructor stats from a Python class
178
    static ConstructorStats &get(py::object class_) {
179
180
181
        auto &internals = py::detail::get_internals();
        const std::type_index *t1 = nullptr, *t2 = nullptr;
        try {
182
183
            auto *type_info
                = internals.registered_types_py.at((PyTypeObject *) class_.ptr()).at(0);
184
185
186
187
188
189
190
191
192
            for (auto &p : internals.registered_types_cpp) {
                if (p.second == type_info) {
                    if (t1) {
                        t2 = &p.first;
                        break;
                    }
                    t1 = &p.first;
                }
            }
193
        } catch (const std::out_of_range &) {
194
        }
195
196
197
        if (!t1) {
            throw std::runtime_error("Unknown class passed to ConstructorStats::get()");
        }
198
        auto &cs1 = get(*t1);
199
200
        // If we have both a t1 and t2 match, one is probably the trampoline class; return
        // whichever has more constructions (typically one or the other will be 0)
201
202
        if (t2) {
            auto &cs2 = get(*t2);
203
204
205
206
            int cs1_total = cs1.default_constructions + cs1.copy_constructions
                            + cs1.move_constructions + (int) cs1._values.size();
            int cs2_total = cs2.default_constructions + cs2.copy_constructions
                            + cs2.move_constructions + (int) cs2._values.size();
207
208
209
            if (cs2_total > cs1_total) {
                return cs2;
            }
210
211
212
213
214
215
216
217
        }
        return cs1;
    }
};

// To track construction/destruction, you need to call these methods from the various
// constructors/operators.  The ones that take extra values record the given values in the
// constructor stats values for later inspection.
218
219
220
221
222
223
224
225
226
227
template <class T>
void track_copy_created(T *inst) {
    ConstructorStats::get<T>().copy_created(inst);
}
template <class T>
void track_move_created(T *inst) {
    ConstructorStats::get<T>().move_created(inst);
}
template <class T, typename... Values>
void track_copy_assigned(T *, Values &&...values) {
228
229
230
231
    auto &cst = ConstructorStats::get<T>();
    cst.copy_assignments++;
    cst.value(std::forward<Values>(values)...);
}
232
233
template <class T, typename... Values>
void track_move_assigned(T *, Values &&...values) {
234
235
236
237
    auto &cst = ConstructorStats::get<T>();
    cst.move_assignments++;
    cst.value(std::forward<Values>(values)...);
}
238
239
template <class T, typename... Values>
void track_default_created(T *inst, Values &&...values) {
240
241
242
243
    auto &cst = ConstructorStats::get<T>();
    cst.default_created(inst);
    cst.value(std::forward<Values>(values)...);
}
244
245
template <class T, typename... Values>
void track_created(T *inst, Values &&...values) {
246
247
248
249
    auto &cst = ConstructorStats::get<T>();
    cst.created(inst);
    cst.value(std::forward<Values>(values)...);
}
250
251
template <class T, typename... Values>
void track_destroyed(T *inst) {
252
253
    ConstructorStats::get<T>().destroyed(inst);
}
254
255
template <class T, typename... Values>
void track_values(T *, Values &&...values) {
256
257
258
    ConstructorStats::get<T>().value(std::forward<Values>(values)...);
}

259
260
261
/// Don't cast pointers to Python, print them as strings
inline const char *format_ptrs(const char *p) { return p; }
template <typename T>
262
263
264
py::str format_ptrs(T *p) {
    return "{:#x}"_s.format(reinterpret_cast<std::uintptr_t>(p));
}
265
template <typename T>
266
267
268
auto format_ptrs(T &&x) -> decltype(std::forward<T>(x)) {
    return std::forward<T>(x);
}
269
270
271

template <class T, typename... Output>
void print_constr_details(T *inst, const std::string &action, Output &&...output) {
272
273
274
275
276
    py::print("###",
              py::type_id<T>(),
              "@",
              format_ptrs(inst),
              action,
277
              format_ptrs(std::forward<Output>(output))...);
278
279
280
}

// Verbose versions of the above:
281
282
283
template <class T, typename... Values>
void print_copy_created(T *inst,
                        Values &&...values) { // NB: this prints, but doesn't store, given values
284
285
286
    print_constr_details(inst, "created via copy constructor", values...);
    track_copy_created(inst);
}
287
288
289
template <class T, typename... Values>
void print_move_created(T *inst,
                        Values &&...values) { // NB: this prints, but doesn't store, given values
290
291
292
    print_constr_details(inst, "created via move constructor", values...);
    track_move_created(inst);
}
293
294
template <class T, typename... Values>
void print_copy_assigned(T *inst, Values &&...values) {
295
296
297
    print_constr_details(inst, "assigned via copy assignment", values...);
    track_copy_assigned(inst, values...);
}
298
299
template <class T, typename... Values>
void print_move_assigned(T *inst, Values &&...values) {
300
301
302
    print_constr_details(inst, "assigned via move assignment", values...);
    track_move_assigned(inst, values...);
}
303
304
template <class T, typename... Values>
void print_default_created(T *inst, Values &&...values) {
305
306
307
    print_constr_details(inst, "created via default constructor", values...);
    track_default_created(inst, values...);
}
308
309
template <class T, typename... Values>
void print_created(T *inst, Values &&...values) {
310
311
312
    print_constr_details(inst, "created", values...);
    track_created(inst, values...);
}
313
314
template <class T, typename... Values>
void print_destroyed(T *inst, Values &&...values) { // Prints but doesn't store given values
315
316
317
    print_constr_details(inst, "destroyed", values...);
    track_destroyed(inst);
}
318
319
template <class T, typename... Values>
void print_values(T *inst, Values &&...values) {
320
321
322
    print_constr_details(inst, ":", values...);
    track_values(inst, values...);
}