From 79f01c73add2324846c02f17158db38796ad9554 Mon Sep 17 00:00:00 2001 From: "Gabriele N. Tornetta" Date: Fri, 9 Oct 2026 12:41:37 +0100 Subject: [PATCH] feat: track sample counts in hierarchical stats HierarchicalStats nodes now carry a samples attribute that counts the samples contributing to their total, so that aggregated metrics can be reported together with the number of samples backing them. --- austin/stats.py | 11 ++++++++--- test/stats/test_austin_stats.py | 17 +++++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/austin/stats.py b/austin/stats.py index f1d64cc..bd832eb 100644 --- a/austin/stats.py +++ b/austin/stats.py @@ -67,7 +67,8 @@ class HierarchicalStats: leaf is given by those instances with an empty ``children`` attribute. The ``label`` attribute is used for indexing reasons and therefore should - be of a hashable type. + be of a hashable type. The ``samples`` attribute counts the samples that + contributed to the ``total`` of the node. This class overrides the default ``add`` operator so that one can perform operations like ``stats1 + stats2``. Note, however, that instances of this @@ -79,6 +80,7 @@ class are not assumed to be immutable and indeed this operation will modify own: int total: int children: Dict[Any, "HierarchicalStats"] = field(default_factory=dict) + samples: int = 0 def __lshift__(self, other: "HierarchicalStats") -> "HierarchicalStats": """Merge the RHS into the LHS.""" @@ -87,6 +89,7 @@ def __lshift__(self, other: "HierarchicalStats") -> "HierarchicalStats": self.own += other.own self.total += other.total + self.samples += other.samples for frame, child in other.children.items(): try: @@ -292,13 +295,15 @@ def update(self, sample: AustinSample) -> None: pid = sample.pid thread_info = ThreadInfo(sample.thread, sample.iid or 0) - thread_stats = ThreadStats(thread_info, own=0, total=metric) + thread_stats = ThreadStats(thread_info, own=0, total=metric, samples=1) # Convert the list of frames into a nested FrameStats instance stats: HierarchicalStats = thread_stats container = thread_stats.children for height, frame in enumerate(sample.frames or []): - stats = FrameStats(label=frame, height=height, own=0, total=metric) + stats = FrameStats( + label=frame, height=height, own=0, total=metric, samples=1 + ) container[frame] = stats container = stats.children stats.own = stats.total diff --git a/test/stats/test_austin_stats.py b/test/stats/test_austin_stats.py index 3f18deb..97f62b6 100644 --- a/test/stats/test_austin_stats.py +++ b/test/stats/test_austin_stats.py @@ -57,6 +57,7 @@ def test_austin_stats_single_process(): label=ThreadInfo("0x7f45645646", 0), own=0, total=152, + samples=1, children={ AustinFrame( function="foo", filename="foo_module.py", line=10 @@ -66,6 +67,7 @@ def test_austin_stats_single_process(): ), own=152, total=152, + samples=1, ) }, ) @@ -84,6 +86,7 @@ def test_austin_stats_single_process(): ThreadInfo("0x7f45645646", 0): ThreadStats( label=ThreadInfo("0x7f45645646", 0), total=300, + samples=2, own=148, children={ AustinFrame( @@ -94,6 +97,7 @@ def test_austin_stats_single_process(): ), own=152, total=152, + samples=1, ) }, ) @@ -112,6 +116,7 @@ def test_austin_stats_single_process(): ThreadInfo("0x7f45645646", 0): ThreadStats( label=ThreadInfo("0x7f45645646", 0), total=400, + samples=3, own=148, children={ AustinFrame( @@ -122,6 +127,7 @@ def test_austin_stats_single_process(): ), own=252, total=252, + samples=2, ) }, ) @@ -140,6 +146,7 @@ def test_austin_stats_single_process(): ThreadInfo("0x7f45645646", 0): ThreadStats( label=ThreadInfo("0x7f45645646", 0), total=800, + samples=4, own=148, children={ AustinFrame( @@ -150,6 +157,7 @@ def test_austin_stats_single_process(): ), own=252, total=252, + samples=2, ), AustinFrame( function="bar", filename="foo_module.py", line=35 @@ -159,6 +167,7 @@ def test_austin_stats_single_process(): ), own=400, total=400, + samples=1, ), }, ) @@ -178,6 +187,7 @@ def test_austin_stats_single_process(): label=ThreadInfo("0x7f45645664", 0), own=0, total=152, + samples=1, children={ AustinFrame( function="foo", filename="foo_module.py", line=10 @@ -187,12 +197,14 @@ def test_austin_stats_single_process(): ), own=152, total=152, + samples=1, ) }, ), ThreadInfo("0x7f45645646", 0): ThreadStats( label=ThreadInfo("0x7f45645646", 0), total=800, + samples=4, own=148, children={ AustinFrame( @@ -203,6 +215,7 @@ def test_austin_stats_single_process(): ), own=252, total=252, + samples=2, ), AustinFrame( function="bar", filename="foo_module.py", line=35 @@ -212,6 +225,7 @@ def test_austin_stats_single_process(): ), own=400, total=400, + samples=1, ), }, ), @@ -305,6 +319,7 @@ def test_load(): label=ThreadInfo("0x7f45645646", 0), own=0, total=1300, + samples=2, children={ AustinFrame( function="foo", filename="foo_module.py", line=10 @@ -314,6 +329,7 @@ def test_load(): ), own=300, total=1300, + samples=2, children={ AustinFrame( function="bar", @@ -327,6 +343,7 @@ def test_load(): ), own=1000, total=1000, + samples=1, children={}, height=1, )