mirror of
https://github.com/wolfpld/tracy.git
synced 2026-10-29 20:15:26 +00:00
feat(mcp): get zone stats for a single thread
get_all_zone_stats mixes every thread, so the main-thread cost of a zone could not be separated from worker threads. get_thread_zone_stats returns the same per-name stats for the zones recorded on one thread, and its per-thread sums match get_all_zone_stats exactly. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L3DnWkXakGRgz9C8BaLM47
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
0ea62d9551
commit
bbb4eebc61
@@ -19,6 +19,9 @@ data surface. Common entry points:
|
||||
- Frames: `get_frame_count()`, `get_frame_times()`, `get_frame_times_named(name)`,
|
||||
`get_frame_boundaries()`, `get_zones_in_frame(...)`
|
||||
- Threads: `get_threads()`, `get_thread_name(tid)`, `get_thread_context_switches(tid)`
|
||||
`get_thread_zone_stats(tid)` is `get_all_zone_stats()` restricted to one thread's zones,
|
||||
so a main-thread cost is not mixed with worker threads. A zone name used at several
|
||||
source locations appears once in both, for its last location.
|
||||
- Messages / plots / locks / memory / callstacks: `get_messages()`, `get_plots()`,
|
||||
`get_locks()`, `get_memory_events()`, `get_callstack_frames(...)`
|
||||
- Plot samples: `get_plot_samples(name, start_ns=0, end_ns=max, max_samples=100000)` returns
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
"""Needs a saved trace:
|
||||
TRACY_TEST_TRACE=/path/to.tracy python -m pytest extra/mcp/test_thread_zone_stats.py
|
||||
"""
|
||||
import os
|
||||
import time
|
||||
|
||||
import pytest
|
||||
|
||||
import tracy_mcp
|
||||
|
||||
TRACE = os.environ.get("TRACY_TEST_TRACE")
|
||||
pytestmark = pytest.mark.skipif(not TRACE, reason="TRACY_TEST_TRACE not set")
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def worker():
|
||||
server = tracy_mcp.tracy_server
|
||||
w = server.create_worker_from_file(server.open_file(TRACE))
|
||||
while not w.is_background_done():
|
||||
time.sleep(0.1)
|
||||
yield w
|
||||
w.shutdown()
|
||||
|
||||
|
||||
def test_threads_partition_the_global_stats(worker):
|
||||
counts, totals = {}, {}
|
||||
for thread in worker.get_threads():
|
||||
for name, stats in worker.get_thread_zone_stats(thread["id"]).items():
|
||||
counts[name] = counts.get(name, 0) + stats.count
|
||||
totals[name] = totals.get(name, 0) + stats.total
|
||||
for name, stats in worker.get_all_zone_stats().items():
|
||||
assert counts.get(name, 0) == stats.count
|
||||
assert totals.get(name, 0) == stats.total
|
||||
|
||||
|
||||
def test_unknown_thread_is_empty(worker):
|
||||
assert worker.get_thread_zone_stats(2**40) == {}
|
||||
@@ -323,6 +323,42 @@ PYBIND11_MODULE( TracyServerBindings, m )
|
||||
return result;
|
||||
} ) )
|
||||
|
||||
.def( "get_thread_zone_stats", locked( +[]( const Worker& w, uint64_t threadId ) {
|
||||
// Same shape and name handling as get_all_zone_stats(), counting only zones recorded on one thread
|
||||
py::dict result;
|
||||
#ifndef TRACY_NO_STATISTICS
|
||||
std::unordered_map<std::string, int16_t> chosen;
|
||||
for( const auto& kv : w.GetSourceLocationZones() )
|
||||
{
|
||||
if( kv.second.zones.size() == 0 ) continue;
|
||||
chosen[w.GetZoneName( w.GetSourceLocation( kv.first ) )] = kv.first;
|
||||
}
|
||||
for( const auto& kv : w.GetSourceLocationZones() )
|
||||
{
|
||||
const std::string name = w.GetZoneName( w.GetSourceLocation( kv.first ) );
|
||||
const auto it = chosen.find( name );
|
||||
if( it == chosen.end() || it->second != kv.first ) continue;
|
||||
int64_t min = INT64_MAX, max = INT64_MIN, total = 0;
|
||||
double sumSq = 0;
|
||||
size_t count = 0;
|
||||
for( const auto& ztd : kv.second.zones )
|
||||
{
|
||||
if( w.DecompressThread( ztd.Thread() ) != threadId ) continue;
|
||||
const auto* z = ztd.Zone();
|
||||
if( !z || !z->IsEndValid() ) continue;
|
||||
const int64_t dur = z->End() - z->Start();
|
||||
total += dur;
|
||||
sumSq += (double)dur * dur;
|
||||
count++;
|
||||
if( dur < min ) min = dur;
|
||||
if( dur > max ) max = dur;
|
||||
}
|
||||
if( count ) result[name.c_str()] = ZoneStats{ min, max, total, sumSq, count, (double)total / count };
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
} ), "thread_id"_a )
|
||||
|
||||
// --- Per-occurrence zone data (for temporal correlation / distribution) ---
|
||||
.def( "get_zone_durations", locked( +[]( const Worker& w, const std::string& name, size_t maxSamples ) {
|
||||
// Accumulates across ALL srclocs with this name (same name can appear at multiple srclocs)
|
||||
|
||||
Reference in New Issue
Block a user