| /****************************************************************************** | |
| * 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; | |
| uintptr_t sequence_address = gc_stats_addr | |
| + offsetof(struct gc_stats, update_seq); | |
| /* A short GC update may finish before a second attempt. */ | |
| for (int attempt = 0; attempt < 2; attempt++) { | |
| uint32_t before; | |
| if (_Py_RemoteDebug_ReadRemoteMemory(&offsets->handle, | |
| sequence_address, | |
| sizeof(before), &before) < 0) { | |
| set_exception_cause(offsets, PyExc_RuntimeError, | |
| "Failed to read GC update sequence"); | |
| return -1; | |
| } | |
| 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; | |
| } | |
| uint32_t after; | |
| if (_Py_RemoteDebug_ReadRemoteMemory(&offsets->handle, | |
| sequence_address, | |
| sizeof(after), &after) < 0) { | |
| set_exception_cause(offsets, PyExc_RuntimeError, | |
| "Failed to read GC update sequence"); | |
| return -1; | |
| } | |
| if (before == after && before == stats.update_seq && !(after & 1)) { | |
| break; | |
| } | |
| if (attempt == 1) { | |
| PyErr_SetString(PyExc_RuntimeError, | |
| "GC stats changed while being read; retry later"); | |
| 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; | |
| } |