Process stack frames and count samples per line. Args: frames: List of (filename, location, funcname, opcode) tuples in leaf-to-root order. location is (lineno, end_lineno, col_offset, end_col_offset). opcode is None if not gathered.
(self, frames, thread_id, weight=1)
| 529 | self.stats.update(kwargs) |
| 530 | |
| 531 | def process_frames(self, frames, thread_id, weight=1): |
| 532 | """Process stack frames and count samples per line. |
| 533 | |
| 534 | Args: |
| 535 | frames: List of (filename, location, funcname, opcode) tuples in |
| 536 | leaf-to-root order. location is (lineno, end_lineno, col_offset, end_col_offset). |
| 537 | opcode is None if not gathered. |
| 538 | thread_id: Thread ID for this stack trace |
| 539 | weight: Number of samples this stack represents (for batched RLE) |
| 540 | """ |
| 541 | self._total_samples += weight |
| 542 | self._seen_lines.clear() |
| 543 | |
| 544 | for i, (filename, location, funcname, opcode) in enumerate(frames): |
| 545 | # Normalize location to 4-tuple format |
| 546 | lineno, end_lineno, col_offset, end_col_offset = normalize_location(location) |
| 547 | |
| 548 | if not self._is_valid_frame(filename, lineno): |
| 549 | continue |
| 550 | |
| 551 | # frames[0] is the leaf - where execution is actually happening |
| 552 | is_leaf = (i == 0) |
| 553 | line_key = (filename, lineno) |
| 554 | count_cumulative = line_key not in self._seen_lines |
| 555 | if count_cumulative: |
| 556 | self._seen_lines.add(line_key) |
| 557 | |
| 558 | self._record_line_sample(filename, lineno, funcname, is_leaf=is_leaf, |
| 559 | count_cumulative=count_cumulative, weight=weight) |
| 560 | |
| 561 | if opcode is not None: |
| 562 | # Set opcodes_enabled flag when we first encounter opcode data |
| 563 | self.opcodes_enabled = True |
| 564 | self._record_bytecode_sample(filename, lineno, opcode, |
| 565 | end_lineno, col_offset, end_col_offset, |
| 566 | weight=weight) |
| 567 | |
| 568 | # Build call graph for adjacent frames (relationships are deduplicated anyway) |
| 569 | if i + 1 < len(frames): |
| 570 | next_frame = frames[i + 1] |
| 571 | next_lineno = extract_lineno(next_frame[1]) |
| 572 | self._record_call_relationship( |
| 573 | (filename, lineno, funcname), |
| 574 | (next_frame[0], next_lineno, next_frame[2]) |
| 575 | ) |
| 576 | |
| 577 | def _is_valid_frame(self, filename, lineno): |
| 578 | """Check if a frame should be included in the heatmap.""" |