mirror of
https://github.com/python/cpython.git
synced 2025-12-08 06:10:17 +00:00
Merge remote-tracking branch 'upstream/main' into tachyon-opcodes
This commit is contained in:
commit
8129e3d7f4
154 changed files with 10330 additions and 4926 deletions
|
|
@ -253,6 +253,8 @@ _remote_debugging.RemoteUnwinder.__init__
|
|||
native: bool = False
|
||||
gc: bool = False
|
||||
opcodes: bool = False
|
||||
cache_frames: bool = False
|
||||
stats: bool = False
|
||||
|
||||
Initialize a new RemoteUnwinder object for debugging a remote Python process.
|
||||
|
||||
|
|
@ -273,6 +275,10 @@ Initialize a new RemoteUnwinder object for debugging a remote Python process.
|
|||
collection.
|
||||
opcodes: If True, gather bytecode opcode information for instruction-level
|
||||
profiling.
|
||||
cache_frames: If True, enable frame caching optimization to avoid re-reading
|
||||
unchanged parent frames between samples.
|
||||
stats: If True, collect statistics about cache hits, memory reads, etc.
|
||||
Use get_stats() to retrieve the collected statistics.
|
||||
|
||||
The RemoteUnwinder provides functionality to inspect and debug a running Python
|
||||
process, including examining thread states, stack frames and other runtime data.
|
||||
|
|
@ -291,8 +297,9 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
|
|||
int mode, int debug,
|
||||
int skip_non_matching_threads,
|
||||
int native, int gc,
|
||||
int opcodes)
|
||||
/*[clinic end generated code: output=e7f77865c7dd662f input=3dba9e3da913a1e0]*/
|
||||
int opcodes, int cache_frames,
|
||||
int stats)
|
||||
/*[clinic end generated code: output=0031f743f4b9ad52 input=8fb61b24102dec6e]*/
|
||||
{
|
||||
// Validate that all_threads and only_active_thread are not both True
|
||||
if (all_threads && only_active_thread) {
|
||||
|
|
@ -304,7 +311,7 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
|
|||
#ifdef Py_GIL_DISABLED
|
||||
if (only_active_thread) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"only_active_thread is not supported when Py_GIL_DISABLED is not defined");
|
||||
"only_active_thread is not supported in free-threaded builds");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
|
|
@ -312,11 +319,17 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
|
|||
self->native = native;
|
||||
self->gc = gc;
|
||||
self->opcodes = opcodes;
|
||||
self->cache_frames = cache_frames;
|
||||
self->collect_stats = stats;
|
||||
self->stale_invalidation_counter = 0;
|
||||
self->debug = debug;
|
||||
self->only_active_thread = only_active_thread;
|
||||
self->mode = mode;
|
||||
self->skip_non_matching_threads = skip_non_matching_threads;
|
||||
self->cached_state = NULL;
|
||||
self->frame_cache = NULL;
|
||||
// Initialize stats to zero
|
||||
memset(&self->stats, 0, sizeof(self->stats));
|
||||
if (_Py_RemoteDebug_InitProcHandle(&self->handle, pid) < 0) {
|
||||
set_exception_cause(self, PyExc_RuntimeError, "Failed to initialize process handle");
|
||||
return -1;
|
||||
|
|
@ -397,6 +410,16 @@ _remote_debugging_RemoteUnwinder___init___impl(RemoteUnwinderObject *self,
|
|||
self->win_process_buffer_size = 0;
|
||||
#endif
|
||||
|
||||
if (cache_frames && frame_cache_init(self) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Clear stale last_profiled_frame values from previous profilers
|
||||
// This prevents us from stopping frame walking early due to stale values
|
||||
if (cache_frames) {
|
||||
clear_last_profiled_frames(self);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -451,6 +474,8 @@ static PyObject *
|
|||
_remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self)
|
||||
/*[clinic end generated code: output=666192b90c69d567 input=bcff01c73cccc1c0]*/
|
||||
{
|
||||
STATS_INC(self, total_samples);
|
||||
|
||||
PyObject* result = PyList_New(0);
|
||||
if (!result) {
|
||||
set_exception_cause(self, PyExc_MemoryError, "Failed to create stack trace result list");
|
||||
|
|
@ -613,7 +638,15 @@ _remote_debugging_RemoteUnwinder_get_stack_trace_impl(RemoteUnwinderObject *self
|
|||
}
|
||||
|
||||
exit:
|
||||
_Py_RemoteDebug_ClearCache(&self->handle);
|
||||
// Invalidate cache entries for threads not seen in this sample.
|
||||
// Only do this every 1024 iterations to avoid performance overhead.
|
||||
if (self->cache_frames && result) {
|
||||
if (++self->stale_invalidation_counter >= 1024) {
|
||||
self->stale_invalidation_counter = 0;
|
||||
frame_cache_invalidate_stale(self, result);
|
||||
}
|
||||
}
|
||||
_Py_RemoteDebug_ClearCache(&self->handle);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -667,9 +700,7 @@ static PyObject *
|
|||
_remote_debugging_RemoteUnwinder_get_all_awaited_by_impl(RemoteUnwinderObject *self)
|
||||
/*[clinic end generated code: output=6a49cd345e8aec53 input=307f754cbe38250c]*/
|
||||
{
|
||||
if (!self->async_debug_offsets_available) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
|
||||
set_exception_cause(self, PyExc_RuntimeError, "AsyncioDebug section unavailable in get_all_awaited_by");
|
||||
if (ensure_async_debug_offsets(self) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -758,9 +789,7 @@ static PyObject *
|
|||
_remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject *self)
|
||||
/*[clinic end generated code: output=6433d52b55e87bbe input=6129b7d509a887c9]*/
|
||||
{
|
||||
if (!self->async_debug_offsets_available) {
|
||||
PyErr_SetString(PyExc_RuntimeError, "AsyncioDebug section not available");
|
||||
set_exception_cause(self, PyExc_RuntimeError, "AsyncioDebug section unavailable in get_async_stack_trace");
|
||||
if (ensure_async_debug_offsets(self) < 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
|
@ -783,10 +812,114 @@ _remote_debugging_RemoteUnwinder_get_async_stack_trace_impl(RemoteUnwinderObject
|
|||
return NULL;
|
||||
}
|
||||
|
||||
/*[clinic input]
|
||||
@permit_long_docstring_body
|
||||
@critical_section
|
||||
_remote_debugging.RemoteUnwinder.get_stats
|
||||
|
||||
Get collected statistics about profiling performance.
|
||||
|
||||
Returns a dictionary containing statistics about cache performance,
|
||||
memory reads, and other profiling metrics. Only available if the
|
||||
RemoteUnwinder was created with stats=True.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing:
|
||||
- total_samples: Total number of get_stack_trace calls
|
||||
- frame_cache_hits: Full cache hits (entire stack unchanged)
|
||||
- frame_cache_misses: Cache misses requiring full walk
|
||||
- frame_cache_partial_hits: Partial hits (stopped at cached frame)
|
||||
- frames_read_from_cache: Total frames retrieved from cache
|
||||
- frames_read_from_memory: Total frames read from remote memory
|
||||
- memory_reads: Total remote memory read operations
|
||||
- memory_bytes_read: Total bytes read from remote memory
|
||||
- code_object_cache_hits: Code object cache hits
|
||||
- code_object_cache_misses: Code object cache misses
|
||||
- stale_cache_invalidations: Times stale cache entries were cleared
|
||||
- frame_cache_hit_rate: Percentage of samples that hit the cache
|
||||
- code_object_cache_hit_rate: Percentage of code object lookups that hit cache
|
||||
|
||||
Raises:
|
||||
RuntimeError: If stats collection was not enabled (stats=False)
|
||||
[clinic start generated code]*/
|
||||
|
||||
static PyObject *
|
||||
_remote_debugging_RemoteUnwinder_get_stats_impl(RemoteUnwinderObject *self)
|
||||
/*[clinic end generated code: output=21e36477122be2a0 input=75fef4134c12a8c9]*/
|
||||
{
|
||||
if (!self->collect_stats) {
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"Statistics collection was not enabled. "
|
||||
"Create RemoteUnwinder with stats=True to collect statistics.");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
PyObject *result = PyDict_New();
|
||||
if (!result) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#define ADD_STAT(name) do { \
|
||||
PyObject *val = PyLong_FromUnsignedLongLong(self->stats.name); \
|
||||
if (!val || PyDict_SetItemString(result, #name, val) < 0) { \
|
||||
Py_XDECREF(val); \
|
||||
Py_DECREF(result); \
|
||||
return NULL; \
|
||||
} \
|
||||
Py_DECREF(val); \
|
||||
} while(0)
|
||||
|
||||
ADD_STAT(total_samples);
|
||||
ADD_STAT(frame_cache_hits);
|
||||
ADD_STAT(frame_cache_misses);
|
||||
ADD_STAT(frame_cache_partial_hits);
|
||||
ADD_STAT(frames_read_from_cache);
|
||||
ADD_STAT(frames_read_from_memory);
|
||||
ADD_STAT(memory_reads);
|
||||
ADD_STAT(memory_bytes_read);
|
||||
ADD_STAT(code_object_cache_hits);
|
||||
ADD_STAT(code_object_cache_misses);
|
||||
ADD_STAT(stale_cache_invalidations);
|
||||
|
||||
#undef ADD_STAT
|
||||
|
||||
// Calculate and add derived statistics
|
||||
// Hit rate is calculated as (hits + partial_hits) / total_cache_lookups
|
||||
double frame_cache_hit_rate = 0.0;
|
||||
uint64_t total_cache_lookups = self->stats.frame_cache_hits + self->stats.frame_cache_partial_hits + self->stats.frame_cache_misses;
|
||||
if (total_cache_lookups > 0) {
|
||||
frame_cache_hit_rate = 100.0 * (double)(self->stats.frame_cache_hits + self->stats.frame_cache_partial_hits)
|
||||
/ (double)total_cache_lookups;
|
||||
}
|
||||
PyObject *hit_rate = PyFloat_FromDouble(frame_cache_hit_rate);
|
||||
if (!hit_rate || PyDict_SetItemString(result, "frame_cache_hit_rate", hit_rate) < 0) {
|
||||
Py_XDECREF(hit_rate);
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(hit_rate);
|
||||
|
||||
double code_object_hit_rate = 0.0;
|
||||
uint64_t total_code_lookups = self->stats.code_object_cache_hits + self->stats.code_object_cache_misses;
|
||||
if (total_code_lookups > 0) {
|
||||
code_object_hit_rate = 100.0 * (double)self->stats.code_object_cache_hits / (double)total_code_lookups;
|
||||
}
|
||||
PyObject *code_hit_rate = PyFloat_FromDouble(code_object_hit_rate);
|
||||
if (!code_hit_rate || PyDict_SetItemString(result, "code_object_cache_hit_rate", code_hit_rate) < 0) {
|
||||
Py_XDECREF(code_hit_rate);
|
||||
Py_DECREF(result);
|
||||
return NULL;
|
||||
}
|
||||
Py_DECREF(code_hit_rate);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static PyMethodDef RemoteUnwinder_methods[] = {
|
||||
_REMOTE_DEBUGGING_REMOTEUNWINDER_GET_STACK_TRACE_METHODDEF
|
||||
_REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ALL_AWAITED_BY_METHODDEF
|
||||
_REMOTE_DEBUGGING_REMOTEUNWINDER_GET_ASYNC_STACK_TRACE_METHODDEF
|
||||
_REMOTE_DEBUGGING_REMOTEUNWINDER_GET_STATS_METHODDEF
|
||||
{NULL, NULL}
|
||||
};
|
||||
|
||||
|
|
@ -813,6 +946,7 @@ RemoteUnwinder_dealloc(PyObject *op)
|
|||
_Py_RemoteDebug_ClearCache(&self->handle);
|
||||
_Py_RemoteDebug_CleanupProcHandle(&self->handle);
|
||||
}
|
||||
frame_cache_cleanup(self);
|
||||
PyObject_Del(self);
|
||||
Py_DECREF(tp);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue