/****************************************************************************** * Remote Debugging Module - GC Stats Functions * * This file contains functions for reading GC stats from interpreter state. ******************************************************************************/ #include "gc_stats.h" typedef struct { PyObject *result; PyTypeObject *gc_stats_info_type; bool all_interpreters; } GetGCStatsContext; static int read_gc_stats(struct gc_stats *stats, int64_t iid, PyObject *result, PyTypeObject *gc_stats_info_type) { #define SET_FIELD(converter, expr) do { \ PyObject *value = converter(expr); \ if (value == NULL) { \ goto error; \ } \ PyStructSequence_SetItem(item, field++, value); \ } while (0) PyObject *item = NULL; for (unsigned long gen = 0; gen < NUM_GENERATIONS; gen++) { struct gc_generation_stats *items; int size; if (gen == 0) { items = (struct gc_generation_stats *)stats->young.items; size = GC_YOUNG_STATS_SIZE; } else { items = (struct gc_generation_stats *)stats->old[gen-1].items; size = GC_OLD_STATS_SIZE; } for (int i = 0; i < size; i++, items++) { item = PyStructSequence_New(gc_stats_info_type); if (item == NULL) { goto error; } Py_ssize_t field = 0; SET_FIELD(PyLong_FromUnsignedLong, gen); SET_FIELD(PyLong_FromInt64, iid); SET_FIELD(PyLong_FromInt64, items->ts_start); SET_FIELD(PyLong_FromInt64, items->ts_stop); SET_FIELD(PyLong_FromSsize_t, items->collections); SET_FIELD(PyLong_FromSsize_t, items->collected); SET_FIELD(PyLong_FromSsize_t, items->uncollectable); SET_FIELD(PyLong_FromSsize_t, items->candidates); SET_FIELD(PyFloat_FromDouble, items->duration); int rc = PyList_Append(result, item); Py_CLEAR(item); if (rc < 0) { goto error; } } } #undef SET_FIELD return 0; error: Py_XDECREF(item); return -1; } static int get_gc_stats_from_interpreter_state(RuntimeOffsets *offsets, uintptr_t interpreter_state_addr, int64_t iid, void *context) { GetGCStatsContext *ctx = (GetGCStatsContext *)context; if (!ctx->all_interpreters && iid > 0) { return 0; } uintptr_t gc_stats_addr = 0; uintptr_t gc_stats_pointer_address = interpreter_state_addr + offsets->debug_offsets.interpreter_state.gc + offsets->debug_offsets.gc.generation_stats; if (_Py_RemoteDebug_ReadRemoteMemory(&offsets->handle, gc_stats_pointer_address, sizeof(gc_stats_addr), &gc_stats_addr) < 0) { set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read GC state address"); return -1; } if (gc_stats_addr == 0) { PyErr_SetString(PyExc_RuntimeError, "GC state address is NULL"); return -1; } struct gc_stats stats; if (_Py_RemoteDebug_ReadRemoteMemory(&offsets->handle, gc_stats_addr, sizeof(stats), &stats) < 0) { set_exception_cause(offsets, PyExc_RuntimeError, "Failed to read GC state"); return -1; } if (read_gc_stats(&stats, iid, ctx->result, ctx->gc_stats_info_type) < 0) { set_exception_cause(offsets, PyExc_RuntimeError, "Failed to populate GC stats result"); return -1; } return 0; } PyObject * get_gc_stats(RuntimeOffsets *offsets, bool all_interpreters, PyTypeObject *gc_stats_info_type) { uint64_t gc_stats_size = offsets->debug_offsets.gc.generation_stats_size; if (gc_stats_size != sizeof(struct gc_stats)) { PyErr_Format(PyExc_RuntimeError, "Remote gc_stats size (%llu) does not match " "local size (%zu)", (unsigned long long)gc_stats_size, sizeof(struct gc_stats)); set_exception_cause(offsets, PyExc_RuntimeError, "Remote gc_stats size mismatch"); return NULL; } PyObject *result = PyList_New(0); if (result == NULL) { return NULL; } GetGCStatsContext ctx = { .result = result, .gc_stats_info_type = gc_stats_info_type, .all_interpreters = all_interpreters, }; if (iterate_interpreters(offsets, get_gc_stats_from_interpreter_state, &ctx) < 0) { Py_CLEAR(result); return NULL; } return result; }