Skip to content

Commit

Permalink
generate GC stats
Browse files Browse the repository at this point in the history
Port of Pablo's pythonGH-100403 to Python 3.11.x.
  • Loading branch information
nascheme committed Jan 12, 2023
1 parent a7a450f commit 20d0368
Show file tree
Hide file tree
Showing 2 changed files with 363 additions and 0 deletions.
186 changes: 186 additions & 0 deletions Modules/gcmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,22 @@ module gc
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b5c9690ecc842d79]*/

#define GC_STATS 1

#ifdef GC_STATS

typedef struct _gc_stats {
size_t n_collections;
PyObject* generation_number;
PyObject* total_objects;
PyObject* uncollectable;
PyObject* collected_cycles;
PyObject* collection_time;
} PyGCStats;

PyGCStats _pygc_stats_struct = { 0 };
#endif


#ifdef Py_DEBUG
# define GC_DEBUG
Expand Down Expand Up @@ -135,6 +151,34 @@ get_gc_state(void)
return &interp->gc;
}

#ifdef GC_STATS

static inline int
is_main_interpreter(void)
{
return (PyInterpreterState_Get() == PyInterpreterState_Main());
}

static int
_PyInitGCStats() {
if (!is_main_interpreter()) {
return 0;
}
#define INIT_FIELD(field) \
_pygc_stats_struct.field = PyList_New(0);\
if (_pygc_stats_struct.field== NULL) {\
return -1; \
}\

INIT_FIELD(generation_number);
INIT_FIELD(total_objects);
INIT_FIELD(uncollectable);
INIT_FIELD(collected_cycles);
INIT_FIELD(collection_time);
#undef INIT_FIELD
return 0;
}
#endif

void
_PyGC_InitState(GCState *gcstate)
Expand Down Expand Up @@ -171,6 +215,12 @@ _PyGC_Init(PyInterpreterState *interp)
return _PyStatus_NO_MEMORY();
}

#ifdef GC_STATS
if(_PyInitGCStats()) {
Py_FatalError("Could not initialize GC stats");
}
#endif

return _PyStatus_OK();
}

Expand Down Expand Up @@ -1190,6 +1240,12 @@ gc_collect_main(PyThreadState *tstate, int generation,
_PyTime_t t1 = 0; /* initialize to prevent a compiler warning */
GCState *gcstate = &tstate->interp->gc;

#ifdef GC_STATS
_PyTime_t gc_t1 = 0;
_PyTime_t gc_t2 = 0;
gc_t1 = _PyTime_GetPerfCounter();
#endif

// gc_collect_main() must not be called before _PyGC_Init
// or after _PyGC_Fini()
assert(gcstate->garbage != NULL);
Expand Down Expand Up @@ -1223,6 +1279,11 @@ gc_collect_main(PyThreadState *tstate, int generation,
old = young;
validate_list(old, collecting_clear_unreachable_clear);

#if GC_STATS
Py_ssize_t t = 0; /* # total objects being collected */
t = gc_list_size(young);
#endif

deduce_unreachable(young, &unreachable);

untrack_tuples(young);
Expand Down Expand Up @@ -1321,6 +1382,42 @@ gc_collect_main(PyThreadState *tstate, int generation,
_PyErr_WriteUnraisableMsg("in garbage collection", NULL);
}
}
#ifdef GC_STATS
gc_t2 = _PyTime_GetPerfCounter();
#define ADD_ELEMENT(field, elem) \
{ \
PyObject* item = PyLong_FromLong(elem); \
if (!item) { \
Py_FatalError("Arg!"); \
_PyErr_WriteUnraisableMsg("in garbage collection", NULL); \
} \
if (item && PyList_Append(_pygc_stats_struct.field, item) < 0) { \
Py_FatalError("Arg2!"); \
_PyErr_WriteUnraisableMsg("in garbage collection", NULL); \
} \
Py_DECREF(item); \
} \

if (_pygc_stats_struct.generation_number) {
_pygc_stats_struct.n_collections++;
ADD_ELEMENT(generation_number, generation);
ADD_ELEMENT(total_objects, t);
ADD_ELEMENT(uncollectable, n);
ADD_ELEMENT(collected_cycles, m);
double d = _PyTime_AsSecondsDouble(gc_t2 - gc_t1);
PyObject* item = PyFloat_FromDouble(d);
if (!item) {
Py_FatalError("Arg3!");
_PyErr_WriteUnraisableMsg("in garbage collection", NULL);
}
if (item && PyList_Append(_pygc_stats_struct.collection_time, item) < 0) {
Py_FatalError("Arg4!");
_PyErr_WriteUnraisableMsg("in garbage collection", NULL);
}
}
#undef ADD_ELEMENT

#endif

/* Update stats */
if (n_collected) {
Expand Down Expand Up @@ -2167,9 +2264,98 @@ gc_fini_untrack(PyGC_Head *list)
}



#ifdef GC_STATS
static void
print_stats(FILE *out) {
#define WRITE_ITEM(collection, index) { \
PyObject* item = PyList_GET_ITEM(_pygc_stats_struct.collection, index); \
if (!item) { \
_PyErr_WriteUnraisableMsg(" when writing gc stats", NULL); \
} \
long num = PyLong_AsLong(item); \
if (!num && PyErr_Occurred()) { \
_PyErr_WriteUnraisableMsg(" when writing gc stats", NULL); \
} \
fprintf(out, ",%zd", num); } \

fprintf(out, "collection,generation_number,total_objects,uncollectable,collected_cycles,collection_time\n");
for (size_t i = 0; i < _pygc_stats_struct.n_collections; i++) {
fprintf(out, "%zd", i);
WRITE_ITEM(generation_number, i);
WRITE_ITEM(total_objects, i);
WRITE_ITEM(uncollectable, i);
WRITE_ITEM(collected_cycles, i);
{
PyObject* item = PyList_GET_ITEM(_pygc_stats_struct.collection_time, i);
if (!item) {
_PyErr_WriteUnraisableMsg(" when writing gc stats", NULL);
}
double num = PyFloat_AS_DOUBLE(item);
if (!num && PyErr_Occurred()) {
_PyErr_WriteUnraisableMsg(" when writing gc stats", NULL);
}
fprintf(out, ",%f", num);
}
fprintf(out, "\n");
}
}

void
_Py_PrintGCStats(int to_file)
{
FILE *out = stderr;
if (to_file) {
/* Write to a file instead of stderr. */
# ifdef MS_WINDOWS
const char *dirname = "c:\\temp\\py_stats\\";
# else
const char *dirname = "/tmp/py_stats/";
# endif
/* Use random 160 bit number as file name,
* to avoid both accidental collisions and
* symlink attacks. */
unsigned char rand[20];
char hex_name[41];
_PyOS_URandomNonblock(rand, 20);
for (int i = 0; i < 20; i++) {
hex_name[2*i] = "0123456789abcdef"[rand[i]&15];
hex_name[2*i+1] = "0123456789abcdef"[(rand[i]>>4)&15];
}
hex_name[40] = '\0';
char buf[64];
assert(strlen(dirname) + 40 + strlen("_gc.txt") < 64);
sprintf(buf, "%s%s_gc.txt", dirname, hex_name);
FILE *fout = fopen(buf, "w");
if (fout) {
out = fout;
}
}
else {
fprintf(out, "GC stats:\n");
}
print_stats(out);
if (out != stderr) {
fclose(out);
}

Py_CLEAR(_pygc_stats_struct.generation_number);
Py_CLEAR(_pygc_stats_struct.total_objects);
Py_CLEAR(_pygc_stats_struct.uncollectable);
Py_CLEAR(_pygc_stats_struct.collected_cycles);
Py_CLEAR(_pygc_stats_struct.collection_time);
#
}
#endif

void
_PyGC_Fini(PyInterpreterState *interp)
{
#ifdef GC_STATS
if (is_main_interpreter() && Py_GETENV("PYTHON_GC_STATS")) {
_Py_PrintGCStats(1);
}
#endif
GCState *gcstate = &interp->gc;
Py_CLEAR(gcstate->garbage);
Py_CLEAR(gcstate->callbacks);
Expand Down
Loading

0 comments on commit 20d0368

Please sign in to comment.