| 48 | MIN_SAMPLES_FOR_TUI = 200 |
| 49 | |
| 50 | class SampleProfiler: |
| 51 | def __init__(self, pid, sample_interval_usec, all_threads, *, mode=PROFILING_MODE_WALL, native=False, gc=True, opcodes=False, skip_non_matching_threads=True, collect_stats=False, blocking=False): |
| 52 | self.pid = pid |
| 53 | self.sample_interval_usec = sample_interval_usec |
| 54 | self.all_threads = all_threads |
| 55 | self.mode = mode # Store mode for later use |
| 56 | self.collect_stats = collect_stats |
| 57 | self.blocking = blocking |
| 58 | try: |
| 59 | self.unwinder = self._new_unwinder(native, gc, opcodes, skip_non_matching_threads) |
| 60 | except RuntimeError as err: |
| 61 | raise SystemExit(err) from err |
| 62 | # Track sample intervals and total sample count |
| 63 | self.sample_intervals = deque(maxlen=100) |
| 64 | self.total_samples = 0 |
| 65 | self.realtime_stats = False |
| 66 | |
| 67 | def _new_unwinder(self, native, gc, opcodes, skip_non_matching_threads): |
| 68 | kwargs = {} |
| 69 | if _FREE_THREADED_BUILD or self.all_threads: |
| 70 | kwargs['all_threads'] = self.all_threads |
| 71 | else: |
| 72 | kwargs['only_active_thread'] = bool(self.all_threads) |
| 73 | |
| 74 | return _remote_debugging.RemoteUnwinder( |
| 75 | self.pid, |
| 76 | mode=self.mode, |
| 77 | native=native, |
| 78 | gc=gc, |
| 79 | opcodes=opcodes, |
| 80 | skip_non_matching_threads=skip_non_matching_threads, |
| 81 | cache_frames=True, |
| 82 | stats=self.collect_stats, |
| 83 | **kwargs |
| 84 | ) |
| 85 | |
| 86 | def sample(self, collector, duration_sec=None, *, async_aware=False): |
| 87 | sample_interval_sec = self.sample_interval_usec / 1_000_000 |
| 88 | num_samples = 0 |
| 89 | errors = 0 |
| 90 | interrupted = False |
| 91 | running_time_sec = 0 |
| 92 | start_time = next_time = time.perf_counter() |
| 93 | last_sample_time = start_time |
| 94 | realtime_update_interval = 1.0 # Update every second |
| 95 | last_realtime_update = start_time |
| 96 | try: |
| 97 | while duration_sec is None or running_time_sec < duration_sec: |
| 98 | # Check if live collector wants to stop |
| 99 | if hasattr(collector, 'running') and not collector.running: |
| 100 | break |
| 101 | |
| 102 | current_time = time.perf_counter() |
| 103 | if next_time > current_time: |
| 104 | sleep_time = (next_time - current_time) * 0.9 |
| 105 | if sleep_time > 0.0001: |
| 106 | time.sleep(sleep_time) |
| 107 | elif next_time < current_time: |
no outgoing calls
searching dependent graphs…