| /****************************************************************************** |
| * 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(PyLong_FromSsize_t, items->heap_size); |
| |
| 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; |
| } |