| 64 | |
| 65 | |
| 66 | class FlamegraphCollector(StackTraceCollector): |
| 67 | def __init__(self, *args, **kwargs): |
| 68 | super().__init__(*args, **kwargs) |
| 69 | self.stats = {} |
| 70 | self._root = {"samples": 0, "children": {}, "threads": set()} |
| 71 | self._total_samples = 0 |
| 72 | self._sample_count = 0 # Track actual number of samples (not thread traces) |
| 73 | self._func_intern = {} |
| 74 | self._string_table = StringTable() |
| 75 | self._all_threads = set() |
| 76 | |
| 77 | # Thread status statistics (similar to LiveStatsCollector) |
| 78 | self.thread_status_counts = { |
| 79 | "has_gil": 0, |
| 80 | "on_cpu": 0, |
| 81 | "gil_requested": 0, |
| 82 | "unknown": 0, |
| 83 | "has_exception": 0, |
| 84 | "total": 0, |
| 85 | } |
| 86 | self.samples_with_gc_frames = 0 |
| 87 | |
| 88 | # Per-thread statistics |
| 89 | self.per_thread_stats = {} # {thread_id: {has_gil, on_cpu, gil_requested, unknown, has_exception, total, gc_samples}} |
| 90 | |
| 91 | def collect(self, stack_frames, timestamps_us=None): |
| 92 | """Override to track thread status statistics before processing frames.""" |
| 93 | # Weight is number of timestamps (samples with identical stack) |
| 94 | weight = len(timestamps_us) if timestamps_us else 1 |
| 95 | |
| 96 | # Increment sample count by weight |
| 97 | self._sample_count += weight |
| 98 | |
| 99 | # Collect both aggregate and per-thread statistics using base method |
| 100 | status_counts, has_gc_frame, per_thread_stats = self._collect_thread_status_stats(stack_frames) |
| 101 | |
| 102 | # Merge aggregate status counts (multiply by weight) |
| 103 | for key in status_counts: |
| 104 | self.thread_status_counts[key] += status_counts[key] * weight |
| 105 | |
| 106 | # Update aggregate GC frame count |
| 107 | if has_gc_frame: |
| 108 | self.samples_with_gc_frames += weight |
| 109 | |
| 110 | # Merge per-thread statistics (multiply by weight) |
| 111 | for thread_id, stats in per_thread_stats.items(): |
| 112 | if thread_id not in self.per_thread_stats: |
| 113 | self.per_thread_stats[thread_id] = { |
| 114 | "has_gil": 0, |
| 115 | "on_cpu": 0, |
| 116 | "gil_requested": 0, |
| 117 | "unknown": 0, |
| 118 | "has_exception": 0, |
| 119 | "total": 0, |
| 120 | "gc_samples": 0, |
| 121 | } |
| 122 | for key, value in stats.items(): |
| 123 | self.per_thread_stats[thread_id][key] += value * weight |
no outgoing calls
searching dependent graphs…