mirror of
https://github.com/ROCm/composable_kernel.git
synced 2026-06-30 11:47:48 +00:00
Add build trace visualization.
This commit is contained in:
960
script/analyze_build/docs/CHROME_TRACE_EXPORT.md
Normal file
960
script/analyze_build/docs/CHROME_TRACE_EXPORT.md
Normal file
@@ -0,0 +1,960 @@
|
||||
# Chrome Trace Export for Cross-Validation
|
||||
|
||||
**Status**: Design Document
|
||||
**Author**: Build Analysis Team
|
||||
**Date**: January 2026
|
||||
**Version**: 1.0
|
||||
|
||||
## Executive Summary
|
||||
|
||||
This document proposes adding Chrome Trace Event Format export capabilities to the `analyze_build` library to enable cross-validation with the existing `ninja_json_converter.py` tool. The two tools serve complementary purposes and this enhancement will allow verification of data consistency between them.
|
||||
|
||||
## Background
|
||||
|
||||
### Current State: Two Complementary Tools
|
||||
|
||||
The project currently has two distinct build analysis tools:
|
||||
|
||||
#### 1. `ninja_json_converter.py` - Build System Monitoring
|
||||
- **Purpose**: Monitor build-level parallelism and efficiency
|
||||
- **Primary Users**: Build engineers, CI/CD optimization teams
|
||||
- **Key Metrics**: Worker utilization, critical path, slow compilation units
|
||||
- **Output Format**: Chrome Trace Event Format (JSON)
|
||||
- **Granularity**: File-level (compilation units)
|
||||
- **Visualization**: Perfetto / Chrome Tracing UI
|
||||
- **Use Cases**:
|
||||
- Is our build sharding efficient?
|
||||
- Which files are compilation bottlenecks?
|
||||
- How well are we utilizing available CPU cores?
|
||||
- What's the critical path in our build?
|
||||
|
||||
#### 2. `analyze_build` Library - Compiler Performance Analysis
|
||||
- **Purpose**: Deep analysis of C++ template metaprogramming costs
|
||||
- **Primary Users**: C++ developers, library maintainers, performance engineers
|
||||
- **Key Metrics**: Template instantiation times, template relationships, compiler event breakdown
|
||||
- **Output Format**: Pandas DataFrames for statistical analysis
|
||||
- **Granularity**: Template-level and compiler event-level (within compilation)
|
||||
- **Visualization**: Jupyter notebooks with statistical analysis
|
||||
- **Use Cases**:
|
||||
- Which templates are most expensive to instantiate?
|
||||
- What are the template dependency relationships?
|
||||
- How can we optimize our metaprogramming patterns?
|
||||
- How can we measure improved build times with better metaprogramming?
|
||||
- What percentage of build time is template instantiation?
|
||||
|
||||
### The Problem: Need for Cross-Validation
|
||||
|
||||
Currently, these tools operate independently with no mechanism to verify consistency. This creates several challenges:
|
||||
|
||||
1. **Data Accuracy**: No way to verify both tools are parsing the same underlying data correctly
|
||||
2. **Discrepancy Detection**: When numbers differ, unclear which tool is correct
|
||||
3. **Cross-Referencing**: Difficult to correlate findings (e.g., "slow file in ninja" vs "high template time in analyzer")
|
||||
4. **Debugging**: Hard to diagnose when tools report different build times
|
||||
5. **Trust**: Users may question which tool's numbers to believe
|
||||
|
||||
## Goals and Non-Goals
|
||||
|
||||
### Primary Goals
|
||||
|
||||
1. **Enable Cross-Validation**: Export analyze_build data to Chrome Trace format for comparison with ninja_json_converter
|
||||
2. **Verify Consistency**: Provide utilities to compare outputs and identify discrepancies
|
||||
3. **Sanity Checking**: Quick visual verification in Perfetto that data looks correct
|
||||
4. **Cross-Reference Findings**: Correlate slow files with expensive templates
|
||||
|
||||
### Secondary Goals
|
||||
|
||||
1. **Template Event Visualization**: Optionally export template instantiation events as additional trace layer
|
||||
2. **Debugging Support**: Help diagnose when tools report different results
|
||||
3. **Documentation**: Clear workflow for validation process
|
||||
|
||||
### Explicit Non-Goals
|
||||
|
||||
1. **Not Replacing ninja_json_converter**: The tools serve different purposes and both should continue to exist
|
||||
2. **Not Full-Featured Visualization**: analyze_build focuses on statistical analysis, not interactive timelines
|
||||
3. **Not Advanced Timeline Features**: Keep it simple - just export for validation
|
||||
4. **Not Multi-Build Comparison**: ninja_json_converter already handles this well
|
||||
|
||||
## Technical Design
|
||||
|
||||
### Architecture Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ analyze_build Library │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ ┌──────────────┐ ┌──────────────┐ │
|
||||
│ │ NinjaParser │─────▶│ builds_df │ │
|
||||
│ └──────────────┘ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌──────────────┐ ┌──────▼───────┐ │
|
||||
│ │ TraceParser │─────▶│ events_df │ │
|
||||
│ └──────────────┘ └──────┬───────┘ │
|
||||
│ │ │
|
||||
│ ┌──────▼───────────┐ │
|
||||
│ │ ChromeTraceExporter│ │
|
||||
│ └──────┬───────────┘ │
|
||||
│ │ │
|
||||
│ ┌──────▼───────────┐ │
|
||||
│ │ trace_events │ │
|
||||
│ │ (Chrome Format) │ │
|
||||
│ └──────┬───────────┘ │
|
||||
│ │ │
|
||||
└───────────────────────────────┼──────────────────────────────┘
|
||||
│
|
||||
┌───────────▼────────────┐
|
||||
│ Validation Utilities │
|
||||
└───────────┬────────────┘
|
||||
│
|
||||
┌───────────▼────────────┐
|
||||
│ ninja_json_converter │
|
||||
│ output │
|
||||
└────────────────────────┘
|
||||
```
|
||||
|
||||
### New Module: `trace_analysis/chrome_trace.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
Chrome Trace Event Format export for cross-validation.
|
||||
|
||||
Exports trace analysis data to Chrome Trace Event Format compatible
|
||||
with ninja_json_converter.py output for validation purposes.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Optional, Any
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class ChromeTraceExporter:
|
||||
"""Export trace analysis data to Chrome Trace Event Format."""
|
||||
|
||||
@staticmethod
|
||||
def export_ninja_timeline(
|
||||
builds_df: pd.DataFrame,
|
||||
process_id: int = 1,
|
||||
include_metadata: bool = True
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Export ninja build timeline to Chrome Trace format.
|
||||
|
||||
Creates trace events compatible with ninja_json_converter.py output
|
||||
for cross-validation purposes.
|
||||
|
||||
Args:
|
||||
builds_df: DataFrame with columns: target, start_ms, end_ms,
|
||||
duration_ms, worker_id, (optional) category
|
||||
process_id: Process ID for trace events (default: 1)
|
||||
include_metadata: Include trace metadata (default: True)
|
||||
|
||||
Returns:
|
||||
Dictionary in Chrome Trace Event Format:
|
||||
{
|
||||
'traceEvents': [...],
|
||||
'displayTimeUnit': 'ms',
|
||||
'otherData': {...}
|
||||
}
|
||||
|
||||
Example:
|
||||
>>> trace_data = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
>>> with open('trace.json', 'w') as f:
|
||||
... json.dump(trace_data, f)
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def export_template_events(
|
||||
instantiations_df: pd.DataFrame,
|
||||
templates_df: pd.DataFrame,
|
||||
builds_df: pd.DataFrame,
|
||||
process_id: int = 1,
|
||||
granularity_us: int = 50000
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Export template instantiation events as Chrome Trace layer.
|
||||
|
||||
Creates template-level trace events that can be overlaid on the
|
||||
ninja build timeline for detailed compiler analysis.
|
||||
|
||||
Args:
|
||||
instantiations_df: Template instantiation events
|
||||
templates_df: Template definitions
|
||||
builds_df: Ninja builds (for timing alignment)
|
||||
process_id: Process ID for trace events
|
||||
granularity_us: Minimum duration threshold in microseconds
|
||||
|
||||
Returns:
|
||||
Chrome Trace Event Format dictionary with template events
|
||||
|
||||
Note:
|
||||
Template events are aligned with ninja build timing and
|
||||
filtered by granularity threshold to reduce trace size.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def merge_traces(
|
||||
ninja_trace: Dict[str, Any],
|
||||
template_trace: Dict[str, Any]
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Merge ninja and template traces into single trace file.
|
||||
|
||||
Combines build-level and template-level events for unified
|
||||
visualization in Perfetto.
|
||||
|
||||
Args:
|
||||
ninja_trace: Ninja build timeline trace
|
||||
template_trace: Template instantiation trace
|
||||
|
||||
Returns:
|
||||
Merged trace with both event types
|
||||
"""
|
||||
```
|
||||
|
||||
### New Module: `trace_analysis/validation.py`
|
||||
|
||||
```python
|
||||
"""
|
||||
Validation utilities for cross-checking trace analysis tools.
|
||||
|
||||
Compares outputs from analyze_build and ninja_json_converter to
|
||||
verify data consistency and identify discrepancies.
|
||||
"""
|
||||
|
||||
from typing import Dict, List, Any, Optional
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class TraceValidator:
|
||||
"""Validate consistency between trace analysis tools."""
|
||||
|
||||
@staticmethod
|
||||
def compare_traces(
|
||||
analyzer_trace: Dict[str, Any],
|
||||
ninja_converter_trace: Dict[str, Any],
|
||||
tolerance_ms: float = 1.0
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Compare Chrome Trace outputs from both tools.
|
||||
|
||||
Validates that analyze_build and ninja_json_converter produce
|
||||
consistent results from the same underlying data.
|
||||
|
||||
Args:
|
||||
analyzer_trace: Trace from ChromeTraceExporter
|
||||
ninja_converter_trace: Trace from ninja_json_converter.py
|
||||
tolerance_ms: Acceptable time difference in milliseconds
|
||||
|
||||
Returns:
|
||||
Validation report:
|
||||
{
|
||||
'total_time_match': bool,
|
||||
'total_time_diff_ms': float,
|
||||
'event_count_match': bool,
|
||||
'event_count_diff': int,
|
||||
'file_discrepancies': [
|
||||
{
|
||||
'file': str,
|
||||
'analyzer_ms': float,
|
||||
'ninja_ms': float,
|
||||
'diff_ms': float,
|
||||
'diff_pct': float
|
||||
}
|
||||
],
|
||||
'summary': str
|
||||
}
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def validate_ninja_log_parsing(
|
||||
builds_df: pd.DataFrame,
|
||||
ninja_log_path: str
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Validate that NinjaLogParser correctly parsed .ninja_log.
|
||||
|
||||
Cross-checks parsed DataFrame against raw .ninja_log file
|
||||
to ensure no data loss or corruption.
|
||||
|
||||
Args:
|
||||
builds_df: Parsed builds DataFrame
|
||||
ninja_log_path: Path to original .ninja_log file
|
||||
|
||||
Returns:
|
||||
Validation report with any parsing issues
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def generate_validation_report(
|
||||
validation_results: Dict[str, Any],
|
||||
output_path: Optional[str] = None
|
||||
) -> str:
|
||||
"""
|
||||
Generate human-readable validation report.
|
||||
|
||||
Creates formatted report of validation results for review.
|
||||
|
||||
Args:
|
||||
validation_results: Results from compare_traces()
|
||||
output_path: Optional path to save report
|
||||
|
||||
Returns:
|
||||
Formatted report string
|
||||
"""
|
||||
```
|
||||
|
||||
### Data Flow
|
||||
|
||||
```
|
||||
1. Parse .ninja_log
|
||||
└─> NinjaLogParser.parse() -> builds_df
|
||||
|
||||
2. Export to Chrome Trace
|
||||
└─> ChromeTraceExporter.export_ninja_timeline(builds_df) -> trace_data
|
||||
|
||||
3. Save trace file
|
||||
└─> json.dump(trace_data, 'analyzer_trace.json')
|
||||
|
||||
4. Generate ninja_json_converter trace (separately)
|
||||
└─> python ninja_json_converter.py .ninja_log -o ninja_trace.json
|
||||
|
||||
5. Validate consistency
|
||||
└─> TraceValidator.compare_traces(analyzer_trace, ninja_trace) -> report
|
||||
|
||||
6. Review discrepancies
|
||||
└─> TraceValidator.generate_validation_report(report)
|
||||
```
|
||||
|
||||
## Chrome Trace Event Format Specification
|
||||
|
||||
### Event Structure
|
||||
|
||||
Each trace event follows the Chrome Trace Event Format:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "target_name.o",
|
||||
"cat": "compile",
|
||||
"ph": "X",
|
||||
"ts": 1234567890,
|
||||
"dur": 5000000,
|
||||
"pid": 1,
|
||||
"tid": 3,
|
||||
"args": {
|
||||
"output": "target_name.o",
|
||||
"duration_ms": 5000,
|
||||
"cmd_hash": "abc123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Field Descriptions:**
|
||||
- `name`: Target name (file being built)
|
||||
- `cat`: Category (compile, link_shared, link_executable, archive, test, other)
|
||||
- `ph`: Phase ("X" for complete events)
|
||||
- `ts`: Timestamp in microseconds
|
||||
- `dur`: Duration in microseconds
|
||||
- `pid`: Process ID (1 for ninja builds)
|
||||
- `tid`: Thread ID (worker ID)
|
||||
- `args`: Additional metadata
|
||||
|
||||
### Trace File Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"traceEvents": [
|
||||
{ /* event 1 */ },
|
||||
{ /* event 2 */ },
|
||||
...
|
||||
],
|
||||
"displayTimeUnit": "ms",
|
||||
"otherData": {
|
||||
"version": "1.0",
|
||||
"generator": "trace_analysis",
|
||||
"source": "analyze_build"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Compatibility with ninja_json_converter
|
||||
|
||||
The export format must be **byte-for-byte compatible** with ninja_json_converter output for the same input data, with these exceptions:
|
||||
|
||||
**Acceptable Differences:**
|
||||
- `otherData.generator`: Different tool name
|
||||
- Event ordering: May differ if timestamps are identical
|
||||
- Floating point precision: ±0.001ms acceptable
|
||||
|
||||
**Must Match Exactly:**
|
||||
- Total build time
|
||||
- Per-file durations (within tolerance)
|
||||
- Worker assignments
|
||||
- Event counts
|
||||
- Category assignments
|
||||
|
||||
## Validation Strategy
|
||||
|
||||
### Validation Checks
|
||||
|
||||
1. **Total Build Time**
|
||||
- Sum of all event durations
|
||||
- Should match within ±1ms (rounding tolerance)
|
||||
|
||||
2. **Event Count**
|
||||
- Number of trace events
|
||||
- Should match exactly
|
||||
|
||||
3. **Per-File Duration**
|
||||
- Duration for each compilation unit
|
||||
- Should match within ±1ms per file
|
||||
|
||||
4. **Worker Assignment**
|
||||
- Thread ID (worker) for each event
|
||||
- Should match exactly (deterministic algorithm)
|
||||
|
||||
5. **Category Assignment**
|
||||
- Event category based on file extension
|
||||
- Should match exactly
|
||||
|
||||
### Expected Discrepancies
|
||||
|
||||
Some differences are expected and acceptable:
|
||||
|
||||
1. **Timestamp Precision**: Microsecond rounding differences
|
||||
2. **Event Ordering**: When timestamps are identical
|
||||
3. **Metadata Fields**: Different tool names, versions
|
||||
4. **Floating Point**: Minor precision differences (< 0.001ms)
|
||||
|
||||
### Validation Workflow
|
||||
|
||||
```python
|
||||
# 1. Generate trace from analyze_build
|
||||
from trace_analysis import NinjaLogParser, ChromeTraceExporter
|
||||
import json
|
||||
|
||||
builds = NinjaLogParser.parse(Path('.ninja_log'))
|
||||
builds_df = NinjaLogParser.to_dataframe(builds)
|
||||
analyzer_trace = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
|
||||
with open('analyzer_trace.json', 'w') as f:
|
||||
json.dump(analyzer_trace, f)
|
||||
|
||||
# 2. Generate trace from ninja_json_converter (shell)
|
||||
# $ python script/ninja_json_converter.py .ninja_log -o ninja_trace.json
|
||||
|
||||
# 3. Load both traces
|
||||
with open('ninja_trace.json') as f:
|
||||
ninja_trace = json.load(f)
|
||||
|
||||
# 4. Validate
|
||||
from trace_analysis import TraceValidator
|
||||
|
||||
report = TraceValidator.compare_traces(analyzer_trace, ninja_trace)
|
||||
|
||||
# 5. Review results
|
||||
print(TraceValidator.generate_validation_report(report))
|
||||
```
|
||||
|
||||
### Validation Report Format
|
||||
|
||||
```
|
||||
=== Trace Validation Report ===
|
||||
|
||||
Overall Status: PASS / FAIL
|
||||
|
||||
Build Statistics:
|
||||
Total Events: 1,234 (analyzer) vs 1,234 (ninja) ✓
|
||||
Total Time: 123.456s (analyzer) vs 123.457s (ninja) ✓ (diff: 0.001s)
|
||||
|
||||
Worker Assignment:
|
||||
Match Rate: 100% (1,234/1,234 events) ✓
|
||||
|
||||
Per-File Duration:
|
||||
Files Checked: 1,234
|
||||
Exact Matches: 1,230 (99.7%)
|
||||
Within Tolerance: 4 (0.3%)
|
||||
Outside Tolerance: 0 (0.0%) ✓
|
||||
|
||||
Discrepancies:
|
||||
file1.o: 1234ms (analyzer) vs 1235ms (ninja) - diff: 1ms (0.08%)
|
||||
file2.o: 5678ms (analyzer) vs 5677ms (ninja) - diff: 1ms (0.02%)
|
||||
|
||||
Conclusion: Tools are consistent within acceptable tolerance.
|
||||
```
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: Basic Export (Week 1)
|
||||
|
||||
**Deliverables:**
|
||||
- `trace_analysis/chrome_trace.py` with `export_ninja_timeline()`
|
||||
- Unit tests for Chrome Trace format
|
||||
- Integration test comparing with ninja_json_converter
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Implement ChromeTraceExporter class
|
||||
- [ ] Add event categorization logic
|
||||
- [ ] Write unit tests for event generation
|
||||
- [ ] Test with sample .ninja_log files
|
||||
- [ ] Verify format matches ninja_json_converter exactly
|
||||
|
||||
**Success Criteria:**
|
||||
- Exports valid Chrome Trace JSON
|
||||
- Loads correctly in Perfetto
|
||||
- Matches ninja_json_converter output for same input
|
||||
|
||||
### Phase 2: Validation Utilities (Week 1-2)
|
||||
|
||||
**Deliverables:**
|
||||
- `trace_analysis/validation.py` with comparison utilities
|
||||
- Validation report generator
|
||||
- Documentation of validation workflow
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Implement TraceValidator class
|
||||
- [ ] Add comparison algorithms
|
||||
- [ ] Create validation report formatter
|
||||
- [ ] Write tests for validation logic
|
||||
- [ ] Document expected discrepancies
|
||||
|
||||
**Success Criteria:**
|
||||
- Accurately identifies discrepancies
|
||||
- Generates clear validation reports
|
||||
- Handles edge cases gracefully
|
||||
|
||||
### Phase 3: Template Event Export (Week 2)
|
||||
|
||||
**Deliverables:**
|
||||
- Template event export in `chrome_trace.py`
|
||||
- Merged trace generation
|
||||
- Examples in notebook
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Implement `export_template_events()`
|
||||
- [ ] Add timing alignment logic
|
||||
- [ ] Implement granularity filtering
|
||||
- [ ] Add merge functionality
|
||||
- [ ] Test with real -ftime-trace data
|
||||
|
||||
**Success Criteria:**
|
||||
- Template events align with ninja timeline
|
||||
- Granularity filtering works correctly
|
||||
- Merged traces load in Perfetto
|
||||
|
||||
### Phase 4: Documentation & Examples (Week 2-3)
|
||||
|
||||
**Deliverables:**
|
||||
- Updated README with validation workflow
|
||||
- Notebook section demonstrating export
|
||||
- API documentation
|
||||
- Validation guide
|
||||
|
||||
**Tasks:**
|
||||
- [ ] Add notebook section for Chrome Trace export
|
||||
- [ ] Document validation workflow
|
||||
- [ ] Create troubleshooting guide
|
||||
- [ ] Add API documentation
|
||||
- [ ] Write migration guide for ninja_json_converter users
|
||||
|
||||
**Success Criteria:**
|
||||
- Clear documentation of validation process
|
||||
- Working examples in notebook
|
||||
- Users can successfully validate traces
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
```python
|
||||
# test_chrome_trace.py
|
||||
|
||||
def test_export_ninja_timeline_format():
|
||||
"""Verify Chrome Trace format is valid."""
|
||||
|
||||
def test_export_ninja_timeline_compatibility():
|
||||
"""Verify compatibility with ninja_json_converter."""
|
||||
|
||||
def test_event_categorization():
|
||||
"""Verify file extension -> category mapping."""
|
||||
|
||||
def test_worker_assignment():
|
||||
"""Verify worker IDs match ninja_json_converter."""
|
||||
```
|
||||
|
||||
### Integration Tests
|
||||
|
||||
```python
|
||||
# test_validation.py
|
||||
|
||||
def test_compare_identical_traces():
|
||||
"""Validation passes for identical traces."""
|
||||
|
||||
def test_detect_discrepancies():
|
||||
"""Validation detects timing differences."""
|
||||
|
||||
def test_tolerance_handling():
|
||||
"""Small differences within tolerance pass."""
|
||||
```
|
||||
|
||||
### Validation Tests
|
||||
|
||||
```python
|
||||
# test_cross_validation.py
|
||||
|
||||
def test_real_ninja_log():
|
||||
"""Compare with actual ninja_json_converter output."""
|
||||
|
||||
def test_large_build():
|
||||
"""Handle large builds (1000+ files)."""
|
||||
|
||||
def test_incremental_build():
|
||||
"""Handle incremental build scenarios."""
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic Export
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from trace_analysis import NinjaLogParser, ChromeTraceExporter
|
||||
import json
|
||||
|
||||
# Parse ninja log
|
||||
builds = NinjaLogParser.parse(Path('build/.ninja_log'))
|
||||
builds_df = NinjaLogParser.to_dataframe(builds)
|
||||
|
||||
# Export to Chrome Trace
|
||||
trace_data = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
|
||||
# Save for Perfetto
|
||||
with open('build_trace.json', 'w') as f:
|
||||
json.dump(trace_data, f)
|
||||
|
||||
print("Open build_trace.json in chrome://tracing or https://ui.perfetto.dev")
|
||||
```
|
||||
|
||||
### Cross-Validation
|
||||
|
||||
```python
|
||||
from trace_analysis import ChromeTraceExporter, TraceValidator
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
# Generate trace from analyze_build
|
||||
analyzer_trace = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
|
||||
# Generate trace from ninja_json_converter
|
||||
subprocess.run([
|
||||
'python', 'script/ninja_json_converter.py',
|
||||
'build/.ninja_log',
|
||||
'-o', 'ninja_trace.json'
|
||||
])
|
||||
|
||||
# Load ninja_json_converter output
|
||||
with open('ninja_trace.json') as f:
|
||||
ninja_trace = json.load(f)
|
||||
|
||||
# Validate
|
||||
report = TraceValidator.compare_traces(analyzer_trace, ninja_trace)
|
||||
|
||||
# Print report
|
||||
print(TraceValidator.generate_validation_report(report))
|
||||
|
||||
# Check if validation passed
|
||||
if report['total_time_match'] and report['event_count_match']:
|
||||
print("✓ Validation PASSED - Tools are consistent")
|
||||
else:
|
||||
print("✗ Validation FAILED - Discrepancies found")
|
||||
for disc in report['file_discrepancies']:
|
||||
print(f" {disc['file']}: {disc['diff_ms']}ms difference")
|
||||
```
|
||||
|
||||
### Template Event Export
|
||||
|
||||
```python
|
||||
from trace_analysis import (
|
||||
TraceParser, TraceTransformer,
|
||||
ChromeTraceExporter, find_trace_files
|
||||
)
|
||||
|
||||
# Parse -ftime-trace files
|
||||
trace_files = find_trace_files(Path('build'))
|
||||
all_events = []
|
||||
all_instantiations = []
|
||||
|
||||
for trace_file in trace_files:
|
||||
events = TraceParser.parse(trace_file)
|
||||
schema = TraceTransformer.to_enhanced_schema(events, file_id=0)
|
||||
all_instantiations.append(schema['instantiations'])
|
||||
|
||||
instantiations_df = pd.concat(all_instantiations, ignore_index=True)
|
||||
|
||||
# Export template events
|
||||
template_trace = ChromeTraceExporter.export_template_events(
|
||||
instantiations_df,
|
||||
templates_df,
|
||||
builds_df,
|
||||
granularity_us=50000 # Only events > 50ms
|
||||
)
|
||||
|
||||
# Merge with ninja timeline
|
||||
merged_trace = ChromeTraceExporter.merge_traces(
|
||||
ninja_trace,
|
||||
template_trace
|
||||
)
|
||||
|
||||
# Save merged trace
|
||||
with open('merged_trace.json', 'w') as f:
|
||||
json.dump(merged_trace, f)
|
||||
```
|
||||
|
||||
### Notebook Integration
|
||||
|
||||
```python
|
||||
# In comprehensive_example.ipynb
|
||||
|
||||
## Chrome Trace Export for Validation
|
||||
|
||||
# Export ninja timeline
|
||||
from trace_analysis import ChromeTraceExporter
|
||||
import json
|
||||
|
||||
trace_data = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
|
||||
# Save trace
|
||||
with open('../data/analyzer_trace.json', 'w') as f:
|
||||
json.dump(trace_data, f, indent=2)
|
||||
|
||||
print(f"Exported {len(trace_data['traceEvents'])} events")
|
||||
print(f"Total build time: {sum(e['dur'] for e in trace_data['traceEvents']) / 1e6:.2f}s")
|
||||
|
||||
# Validate against ninja_json_converter
|
||||
# (Assuming ninja_trace.json was generated separately)
|
||||
with open('../data/ninja_trace.json') as f:
|
||||
ninja_trace = json.load(f)
|
||||
|
||||
from trace_analysis import TraceValidator
|
||||
|
||||
report = TraceValidator.compare_traces(trace_data, ninja_trace)
|
||||
print(TraceValidator.generate_validation_report(report))
|
||||
```
|
||||
|
||||
## Open Questions
|
||||
|
||||
### Critical Questions
|
||||
|
||||
1. **Data Consistency**
|
||||
- Q: Do you currently see discrepancies between the tools?
|
||||
- Q: What tolerance is acceptable? (±1ms suggested)
|
||||
- Q: Are there known sources of differences?
|
||||
|
||||
2. **Validation Workflow**
|
||||
- Q: How often do you need to cross-validate?
|
||||
- Q: Should this be automated in CI?
|
||||
- Q: What triggers a validation run?
|
||||
|
||||
3. **Template Event Export**
|
||||
- Q: Should template events be in same file as ninja events?
|
||||
- Q: Or separate files for different analysis?
|
||||
- Q: Priority: High, Medium, or Low?
|
||||
|
||||
### Technical Questions
|
||||
|
||||
4. **Output Format**
|
||||
- Q: Must we match ninja_json_converter format exactly?
|
||||
- Q: Or can we use enhanced format with metadata?
|
||||
- Q: Is backward compatibility required?
|
||||
|
||||
5. **Performance**
|
||||
- Q: What's the largest build to support?
|
||||
- Q: Number of targets? (hundreds, thousands, tens of thousands?)
|
||||
- Q: Should we implement sampling for huge builds?
|
||||
|
||||
## Success Metrics
|
||||
|
||||
### Functional Metrics
|
||||
|
||||
- ✅ Exports valid Chrome Trace JSON
|
||||
- ✅ Loads correctly in Perfetto
|
||||
- ✅ Matches ninja_json_converter output (within tolerance)
|
||||
- ✅ Validation detects discrepancies accurately
|
||||
- ✅ Clear validation reports
|
||||
|
||||
### Quality Metrics
|
||||
|
||||
- ✅ 100% unit test coverage for new modules
|
||||
- ✅ Integration tests with real data pass
|
||||
- ✅ Documentation complete and clear
|
||||
- ✅ Examples work in notebook
|
||||
|
||||
### Performance Metrics
|
||||
|
||||
- ✅ Export completes in < 1s for 1000 files
|
||||
- ✅ Validation completes in < 5s for 1000 files
|
||||
- ✅ Memory usage < 100MB for typical builds
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
### Potential Phase 2 Features
|
||||
|
||||
1. **Automated Validation in CI**
|
||||
- Run validation on every build
|
||||
- Fail CI if discrepancies exceed threshold
|
||||
- Track validation metrics over time
|
||||
|
||||
2. **Differential Analysis**
|
||||
- Compare traces from different builds
|
||||
- Identify performance regressions
|
||||
- Track optimization progress
|
||||
|
||||
3. **Enhanced Visualization**
|
||||
- Plotly timeline charts in notebooks
|
||||
- Interactive exploration of discrepancies
|
||||
- Side-by-side comparison views
|
||||
|
||||
4. **Template Optimization Recommendations**
|
||||
- Correlate slow files with expensive templates
|
||||
- Suggest optimization targets
|
||||
- Estimate potential improvements
|
||||
|
||||
## References
|
||||
|
||||
- [Chrome Trace Event Format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview)
|
||||
- [Perfetto UI](https://ui.perfetto.dev)
|
||||
- [Clang -ftime-trace Documentation](https://releases.llvm.org/11.0.0/tools/clang/docs/ClangCommandLineReference.html#cmdoption-clang-ftime-trace)
|
||||
- [Ninja Build System](https://ninja-build.org/)
|
||||
|
||||
## Appendix A: Chrome Trace Event Format Details
|
||||
|
||||
### Complete Event Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "event_name",
|
||||
"cat": "category",
|
||||
"ph": "X",
|
||||
"ts": 1234567890,
|
||||
"dur": 5000000,
|
||||
"pid": 1,
|
||||
"tid": 3,
|
||||
"args": {
|
||||
"custom_field": "value"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase Types
|
||||
|
||||
- `X`: Complete event (has duration)
|
||||
- `B`: Begin event
|
||||
- `E`: End event
|
||||
- `i`: Instant event
|
||||
- `M`: Metadata event
|
||||
|
||||
For build traces, we use `X` (complete events) exclusively.
|
||||
|
||||
### Category Conventions
|
||||
|
||||
Standard categories for build events:
|
||||
|
||||
- `compile`: Compilation of source files (.o, .obj)
|
||||
- `link_shared`: Shared library linking (.so, .dll, .dylib)
|
||||
- `link_executable`: Executable linking (.exe, .out)
|
||||
- `archive`: Static library creation (.a, .lib)
|
||||
- `test`: Test execution
|
||||
- `other`: Other build steps
|
||||
|
||||
## Appendix B: Validation Algorithm
|
||||
|
||||
### Comparison Algorithm
|
||||
|
||||
```python
|
||||
def compare_events(event1, event2, tolerance_ms=1.0):
|
||||
"""Compare two trace events for equivalence."""
|
||||
|
||||
# Must match exactly
|
||||
if event1['name'] != event2['name']:
|
||||
return False, "Name mismatch"
|
||||
if event1['tid'] != event2['tid']:
|
||||
return False, "Worker ID mismatch"
|
||||
if event1['cat'] != event2['cat']:
|
||||
return False, "Category mismatch"
|
||||
|
||||
# Must match within tolerance
|
||||
dur1_ms = event1['dur'] / 1000
|
||||
dur2_ms = event2['dur'] / 1000
|
||||
diff_ms = abs(dur1_ms - dur2_ms)
|
||||
|
||||
if diff_ms > tolerance_ms:
|
||||
return False, f"Duration mismatch: {diff_ms}ms"
|
||||
|
||||
return True, "Match"
|
||||
```
|
||||
|
||||
### Discrepancy Categorization
|
||||
|
||||
**Critical**: Must be fixed
|
||||
- Total time difference > 1%
|
||||
- Event count mismatch
|
||||
- Worker assignment errors
|
||||
|
||||
**Warning**: Should investigate
|
||||
- Per-file duration > 1ms difference
|
||||
- Category mismatches
|
||||
- Timestamp ordering issues
|
||||
|
||||
**Info**: Acceptable
|
||||
- Floating point precision differences
|
||||
- Metadata differences
|
||||
- Event ordering when timestamps identical
|
||||
|
||||
## Appendix C: Migration Guide
|
||||
|
||||
### For ninja_json_converter Users
|
||||
|
||||
If you currently use `ninja_json_converter.py`, you can continue to do so. The new Chrome Trace export in `analyze_build` is complementary, not a replacement.
|
||||
|
||||
**When to use ninja_json_converter:**
|
||||
- Quick build timeline visualization
|
||||
- Build system optimization
|
||||
- CI/CD monitoring
|
||||
- Multi-build comparison
|
||||
|
||||
**When to use analyze_build Chrome Trace export:**
|
||||
- Cross-validation with template analysis
|
||||
- Verifying data consistency
|
||||
- Debugging discrepancies
|
||||
- Correlating build and template metrics
|
||||
|
||||
**Using both together:**
|
||||
```bash
|
||||
# Generate trace from ninja_json_converter
|
||||
python script/ninja_json_converter.py build/.ninja_log -o ninja_trace.json
|
||||
|
||||
# Generate trace from analyze_build
|
||||
python -c "
|
||||
from pathlib import Path
|
||||
from trace_analysis import NinjaLogParser, ChromeTraceExporter
|
||||
import json
|
||||
|
||||
builds = NinjaLogParser.parse(Path('build/.ninja_log'))
|
||||
builds_df = NinjaLogParser.to_dataframe(builds)
|
||||
trace = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
|
||||
with open('analyzer_trace.json', 'w') as f:
|
||||
json.dump(trace, f)
|
||||
"
|
||||
|
||||
# Compare
|
||||
python -c "
|
||||
from trace_analysis import TraceValidator
|
||||
import json
|
||||
|
||||
with open('ninja_trace.json') as f:
|
||||
ninja = json.load(f)
|
||||
with open('analyzer_trace.json') as f:
|
||||
analyzer = json.load(f)
|
||||
|
||||
report = TraceValidator.compare_traces(analyzer, ninja)
|
||||
print(TraceValidator.generate_validation_report(report))
|
||||
"
|
||||
231
script/analyze_build/docs/PERFETTO_VISUALIZATION.md
Normal file
231
script/analyze_build/docs/PERFETTO_VISUALIZATION.md
Normal file
@@ -0,0 +1,231 @@
|
||||
# Perfetto Visualization Guide
|
||||
|
||||
This guide shows how to visualize ninja build timelines in Perfetto UI using the `trace_analysis` library.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Command Line Usage
|
||||
|
||||
```bash
|
||||
# Run the example script
|
||||
python examples/perfetto_visualization_example.py path/to/.ninja_log
|
||||
|
||||
# This will:
|
||||
# 1. Parse the ninja log
|
||||
# 2. Assign workers for parallelism visualization
|
||||
# 3. Export to Chrome Trace format
|
||||
# 4. Save to build_trace.json
|
||||
```
|
||||
|
||||
### Jupyter Notebook Usage
|
||||
|
||||
```python
|
||||
from pathlib import Path
|
||||
from trace_analysis import NinjaLogParser, ChromeTraceExporter
|
||||
from trace_analysis.perfetto_display import display_perfetto, print_trace_summary
|
||||
|
||||
# Parse ninja log
|
||||
builds = NinjaLogParser.parse(Path('build/.ninja_log'))
|
||||
builds_df = NinjaLogParser.to_dataframe(builds)
|
||||
builds_df = NinjaLogParser.assign_workers(builds_df)
|
||||
|
||||
# Export to Chrome Trace format
|
||||
trace_data = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
|
||||
# Print summary
|
||||
print_trace_summary(trace_data)
|
||||
|
||||
# Display in Perfetto UI (embedded in notebook)
|
||||
display_perfetto(trace_data)
|
||||
|
||||
# Or save to file for large traces
|
||||
from trace_analysis.perfetto_display import save_and_link
|
||||
save_and_link(trace_data, '../data/build_trace.json')
|
||||
```
|
||||
|
||||
## What You Get
|
||||
|
||||
The Chrome Trace export provides:
|
||||
|
||||
- **Build Timeline**: Visual representation of when each target was built
|
||||
- **Parallelism Analysis**: See how many workers were active at any time
|
||||
- **Category Breakdown**: Targets categorized by type (compile, link, archive, etc.)
|
||||
- **Duration Analysis**: Identify slow compilation units
|
||||
- **Critical Path**: Understand build dependencies and bottlenecks
|
||||
|
||||
## Viewing in Perfetto UI
|
||||
|
||||
### Option 1: Embedded in Jupyter (Small Traces)
|
||||
|
||||
For traces < 10MB, use `display_perfetto()` to embed directly in the notebook:
|
||||
|
||||
```python
|
||||
display_perfetto(trace_data, height=600)
|
||||
```
|
||||
|
||||
### Option 2: Manual Upload (Large Traces)
|
||||
|
||||
For larger traces, save to file and upload manually:
|
||||
|
||||
```python
|
||||
ChromeTraceExporter.export_to_file(trace_data, 'build_trace.json')
|
||||
```
|
||||
|
||||
Then:
|
||||
1. Go to https://ui.perfetto.dev
|
||||
2. Click "Open trace file"
|
||||
3. Select your `build_trace.json`
|
||||
|
||||
Or drag and drop the file directly into Perfetto UI.
|
||||
|
||||
## DataFrame Schema
|
||||
|
||||
The `builds_df` DataFrame has the following columns:
|
||||
|
||||
| Column | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `target` | str | Build target name (e.g., "obj/foo.o") |
|
||||
| `start_ms` | int64 | Start time in milliseconds since epoch |
|
||||
| `end_ms` | int64 | End time in milliseconds since epoch |
|
||||
| `duration_ms` | int32 | Build duration in milliseconds |
|
||||
| `cmd_hash` | str | Command hash from ninja |
|
||||
| `worker_id` | int16 | Assigned worker ID (0-based) |
|
||||
|
||||
### Adding Category Column
|
||||
|
||||
The Chrome Trace exporter automatically categorizes targets based on file extension:
|
||||
|
||||
- `.o`, `.obj` → `compile`
|
||||
- `.a`, `.lib` → `archive`
|
||||
- `.so`, `.dll`, `.dylib` → `link_shared`
|
||||
- `.exe`, `.out` → `link_executable`
|
||||
- Contains "test" → `test`
|
||||
- Everything else → `other`
|
||||
|
||||
## Chrome Trace Event Format
|
||||
|
||||
Each build target is exported as a Chrome Trace event:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "obj/foo.o",
|
||||
"cat": "compile",
|
||||
"ph": "X",
|
||||
"ts": 1234567890000,
|
||||
"dur": 5000000,
|
||||
"pid": 1,
|
||||
"tid": 3,
|
||||
"args": {
|
||||
"output": "obj/foo.o",
|
||||
"duration_ms": 5000,
|
||||
"cmd_hash": "abc123"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Comparison with ninja_json_converter.py
|
||||
|
||||
The `trace_analysis` library provides similar functionality to `ninja_json_converter.py` but with additional features:
|
||||
|
||||
### Similarities
|
||||
- Both parse `.ninja_log` files
|
||||
- Both export to Chrome Trace Event Format
|
||||
- Both can be viewed in Perfetto UI
|
||||
|
||||
### Differences
|
||||
|
||||
| Feature | ninja_json_converter.py | trace_analysis |
|
||||
|---------|------------------------|----------------|
|
||||
| **Primary Use** | Quick build visualization | Integrated analysis workflow |
|
||||
| **Output** | Chrome Trace JSON only | DataFrames + Chrome Trace |
|
||||
| **Analysis** | External (Perfetto UI) | In-notebook with pandas |
|
||||
| **Template Data** | No | Yes (with -ftime-trace) |
|
||||
| **Worker Assignment** | Built-in algorithm | Same algorithm, exposed as DataFrame |
|
||||
| **Customization** | Command-line flags | Programmatic API |
|
||||
|
||||
### When to Use Each
|
||||
|
||||
**Use `ninja_json_converter.py` when:**
|
||||
- You just want a quick visualization
|
||||
- You're working from the command line
|
||||
- You don't need further analysis
|
||||
|
||||
**Use `trace_analysis` when:**
|
||||
- You want to analyze build data with pandas
|
||||
- You're working in Jupyter notebooks
|
||||
- You want to correlate build times with template analysis
|
||||
- You need programmatic access to build data
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Find Slowest Builds
|
||||
|
||||
```python
|
||||
# Get top 10 slowest builds
|
||||
slowest = builds_df.nlargest(10, 'duration_ms')
|
||||
print(slowest[['target', 'duration_ms', 'worker_id']])
|
||||
```
|
||||
|
||||
### Example 2: Analyze Worker Utilization
|
||||
|
||||
```python
|
||||
worker_stats = NinjaLogParser.compute_worker_stats(builds_df)
|
||||
print(worker_stats)
|
||||
```
|
||||
|
||||
### Example 3: Category Breakdown
|
||||
|
||||
```python
|
||||
from trace_analysis.perfetto_display import get_trace_summary
|
||||
|
||||
summary = get_trace_summary(trace_data)
|
||||
print(f"Total events: {summary['event_count']}")
|
||||
print(f"Total duration: {summary['total_duration_s']:.2f}s")
|
||||
print(f"Workers: {summary['worker_count']}")
|
||||
print("\nBy category:")
|
||||
for cat, count in summary['categories'].items():
|
||||
print(f" {cat}: {count} events")
|
||||
```
|
||||
|
||||
### Example 4: Export with Custom Process ID
|
||||
|
||||
```python
|
||||
# Useful when combining multiple build logs
|
||||
trace_data = ChromeTraceExporter.export_ninja_timeline(
|
||||
builds_df,
|
||||
process_id=2, # Use different PID for each log
|
||||
include_metadata=True
|
||||
)
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: Trace file too large for embedded display
|
||||
|
||||
**Solution**: Use `save_and_link()` instead of `display_perfetto()`:
|
||||
|
||||
```python
|
||||
save_and_link(trace_data, 'build_trace.json')
|
||||
```
|
||||
|
||||
### Issue: Worker IDs all show as -1
|
||||
|
||||
**Solution**: Make sure to call `assign_workers()`:
|
||||
|
||||
```python
|
||||
builds_df = NinjaLogParser.assign_workers(builds_df)
|
||||
```
|
||||
|
||||
### Issue: Import error for perfetto_display
|
||||
|
||||
**Solution**: The perfetto display functions are in a separate module:
|
||||
|
||||
```python
|
||||
from trace_analysis.perfetto_display import display_perfetto
|
||||
```
|
||||
|
||||
## See Also
|
||||
|
||||
- [CHROME_TRACE_EXPORT.md](CHROME_TRACE_EXPORT.md) - Full design document
|
||||
- [comprehensive_example.ipynb](../notebooks/comprehensive_example.ipynb) - Complete analysis workflow
|
||||
- [ninja_json_converter.py](../../ninja_json_converter.py) - Command-line alternative
|
||||
@@ -0,0 +1,81 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Example: Visualizing Build Timeline in Perfetto UI
|
||||
|
||||
This example demonstrates how to:
|
||||
1. Parse a ninja .ninja_log file
|
||||
2. Export to Chrome Trace format
|
||||
3. Display in Perfetto UI (for Jupyter notebooks)
|
||||
4. Save to file for manual upload
|
||||
|
||||
Usage:
|
||||
python perfetto_visualization_example.py path/to/.ninja_log
|
||||
"""
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Add parent directory to path
|
||||
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||||
|
||||
from trace_analysis import NinjaLogParser, ChromeTraceExporter
|
||||
from trace_analysis.perfetto_display import (
|
||||
print_trace_summary,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
"""Main example function."""
|
||||
if len(sys.argv) < 2:
|
||||
print("Usage: python perfetto_visualization_example.py path/to/.ninja_log")
|
||||
sys.exit(1)
|
||||
|
||||
ninja_log_path = Path(sys.argv[1])
|
||||
|
||||
if not ninja_log_path.exists():
|
||||
print(f"Error: {ninja_log_path} not found")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"Parsing {ninja_log_path}...")
|
||||
|
||||
# Step 1: Parse ninja log
|
||||
builds = NinjaLogParser.parse(ninja_log_path)
|
||||
builds_df = NinjaLogParser.to_dataframe(builds)
|
||||
|
||||
print(f"Found {len(builds_df):,} build targets")
|
||||
|
||||
# Step 2: Assign workers (for parallelism visualization)
|
||||
builds_df = NinjaLogParser.assign_workers(builds_df)
|
||||
|
||||
print(f"Assigned {builds_df['worker_id'].max() + 1} workers")
|
||||
|
||||
# Step 3: Export to Chrome Trace format
|
||||
trace_data = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
|
||||
print(f"\nGenerated {len(trace_data['traceEvents']):,} trace events")
|
||||
|
||||
# Step 4: Print summary
|
||||
print_trace_summary(trace_data)
|
||||
|
||||
# Step 5: Save to file
|
||||
output_path = ninja_log_path.parent / "build_trace.json"
|
||||
ChromeTraceExporter.export_to_file(trace_data, str(output_path))
|
||||
|
||||
print(f"\n✓ Trace saved to: {output_path}")
|
||||
print("\nTo view in Perfetto UI:")
|
||||
print(" 1. Go to https://ui.perfetto.dev")
|
||||
print(" 2. Click 'Open trace file'")
|
||||
print(f" 3. Select: {output_path}")
|
||||
print("\nOr drag and drop the file directly into Perfetto UI")
|
||||
|
||||
# For Jupyter notebook usage, you would use:
|
||||
# display_perfetto(trace_data)
|
||||
# or for large traces:
|
||||
# save_and_link(trace_data, str(output_path))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
File diff suppressed because it is too large
Load Diff
@@ -71,6 +71,7 @@ from .parser import TraceParser
|
||||
from .transformer import TraceTransformer
|
||||
from .template_parser import TemplateParser
|
||||
from .ninja_parser import NinjaLogParser
|
||||
from .chrome_trace import ChromeTraceExporter
|
||||
from .utils import find_trace_files
|
||||
|
||||
__all__ = [
|
||||
@@ -87,6 +88,8 @@ __all__ = [
|
||||
"NinjaLogParser",
|
||||
"NinjaBuild",
|
||||
"CompilationTimeline",
|
||||
# Chrome Trace export
|
||||
"ChromeTraceExporter",
|
||||
# Metadata and statistics
|
||||
"FileMetadata",
|
||||
"BuildStatistics",
|
||||
|
||||
133
script/analyze_build/trace_analysis/chrome_trace.py
Normal file
133
script/analyze_build/trace_analysis/chrome_trace.py
Normal file
@@ -0,0 +1,133 @@
|
||||
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Chrome Trace Event Format export for Perfetto visualization.
|
||||
|
||||
Exports ninja build timeline data to Chrome Trace Event Format
|
||||
for visualization in Perfetto UI within Jupyter notebooks.
|
||||
"""
|
||||
|
||||
from pathlib import Path
|
||||
from typing import Dict, Any
|
||||
import pandas as pd
|
||||
|
||||
|
||||
class ChromeTraceExporter:
|
||||
"""Export trace analysis data to Chrome Trace Event Format."""
|
||||
|
||||
@staticmethod
|
||||
def categorize_target(target: str) -> str:
|
||||
"""
|
||||
Categorize a build target based on file extension.
|
||||
|
||||
Args:
|
||||
target: Build target name (e.g., "obj/foo.o")
|
||||
|
||||
Returns:
|
||||
Category string for Chrome Trace format
|
||||
"""
|
||||
ext = Path(target).suffix.lower()
|
||||
|
||||
if ext in [".o", ".obj"]:
|
||||
return "compile"
|
||||
elif ext in [".a", ".lib"]:
|
||||
return "archive"
|
||||
elif ext in [".so", ".dll", ".dylib"]:
|
||||
return "link_shared"
|
||||
elif ext in [".exe", ".out"]:
|
||||
return "link_executable"
|
||||
elif "test" in target.lower():
|
||||
return "test"
|
||||
else:
|
||||
return "other"
|
||||
|
||||
@staticmethod
|
||||
def export_ninja_timeline(
|
||||
builds_df: pd.DataFrame, process_id: int = 1, include_metadata: bool = True
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Export ninja build timeline to Chrome Trace format.
|
||||
|
||||
Creates trace events compatible with Perfetto UI for visualization
|
||||
in Jupyter notebooks or chrome://tracing.
|
||||
|
||||
Args:
|
||||
builds_df: DataFrame with columns: target, start_ms, end_ms,
|
||||
duration_ms, worker_id, cmd_hash
|
||||
process_id: Process ID for trace events (default: 1)
|
||||
include_metadata: Include trace metadata (default: True)
|
||||
|
||||
Returns:
|
||||
Dictionary in Chrome Trace Event Format:
|
||||
{
|
||||
'traceEvents': [...],
|
||||
'displayTimeUnit': 'ms',
|
||||
'otherData': {...}
|
||||
}
|
||||
|
||||
Example:
|
||||
>>> from trace_analysis import NinjaLogParser, ChromeTraceExporter
|
||||
>>> builds = NinjaLogParser.parse(Path('build/.ninja_log'))
|
||||
>>> builds_df = NinjaLogParser.to_dataframe(builds)
|
||||
>>> builds_df = NinjaLogParser.assign_workers(builds_df)
|
||||
>>> trace_data = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
>>> # Display in notebook or save to file
|
||||
"""
|
||||
if len(builds_df) == 0:
|
||||
return {
|
||||
"traceEvents": [],
|
||||
"displayTimeUnit": "ms",
|
||||
"otherData": {"version": "1.0", "generator": "analyze_build"},
|
||||
}
|
||||
|
||||
events = []
|
||||
|
||||
for _, row in builds_df.iterrows():
|
||||
# Categorize based on file extension
|
||||
category = ChromeTraceExporter.categorize_target(row["target"])
|
||||
|
||||
# Create Chrome Trace event
|
||||
event = {
|
||||
"name": row["target"],
|
||||
"cat": category,
|
||||
"ph": "X", # Complete event (has duration)
|
||||
"ts": int(row["start_ms"] * 1000), # Convert to microseconds
|
||||
"dur": int(row["duration_ms"] * 1000), # Convert to microseconds
|
||||
"pid": process_id,
|
||||
"tid": int(row["worker_id"]),
|
||||
"args": {
|
||||
"output": row["target"],
|
||||
"duration_ms": int(row["duration_ms"]),
|
||||
"cmd_hash": row["cmd_hash"],
|
||||
},
|
||||
}
|
||||
|
||||
events.append(event)
|
||||
|
||||
if include_metadata:
|
||||
return {
|
||||
"traceEvents": events,
|
||||
"displayTimeUnit": "ms",
|
||||
"otherData": {"version": "1.0", "generator": "analyze_build"},
|
||||
}
|
||||
else:
|
||||
# Simple format (just events array)
|
||||
return {"traceEvents": events}
|
||||
|
||||
@staticmethod
|
||||
def export_to_file(trace_data: Dict[str, Any], output_path: str) -> None:
|
||||
"""
|
||||
Export trace data to a JSON file.
|
||||
|
||||
Args:
|
||||
trace_data: Chrome Trace format dictionary
|
||||
output_path: Path to output file
|
||||
|
||||
Example:
|
||||
>>> ChromeTraceExporter.export_to_file(trace_data, 'trace.json')
|
||||
"""
|
||||
import json
|
||||
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(trace_data, f, indent=2)
|
||||
190
script/analyze_build/trace_analysis/perfetto_display.py
Normal file
190
script/analyze_build/trace_analysis/perfetto_display.py
Normal file
@@ -0,0 +1,190 @@
|
||||
# Copyright (c) Advanced Micro Devices, Inc., or its affiliates.
|
||||
# SPDX-License-Identifier: MIT
|
||||
|
||||
"""
|
||||
Perfetto UI display utilities for Jupyter notebooks.
|
||||
|
||||
Provides functions to display Chrome Trace data in Perfetto UI
|
||||
directly within Jupyter notebooks.
|
||||
"""
|
||||
|
||||
import json
|
||||
import base64
|
||||
from typing import Dict, Any, Optional
|
||||
|
||||
|
||||
def display_perfetto(trace_data: Dict[str, Any], height: int = 600):
|
||||
"""
|
||||
Display Perfetto UI in Jupyter notebook with embedded trace data.
|
||||
|
||||
Args:
|
||||
trace_data: Chrome Trace Event Format dictionary
|
||||
height: Height of the IFrame in pixels (default: 600)
|
||||
|
||||
Returns:
|
||||
IPython IFrame object for display in notebook
|
||||
|
||||
Example:
|
||||
>>> from trace_analysis import NinjaLogParser, ChromeTraceExporter
|
||||
>>> from trace_analysis.perfetto_display import display_perfetto
|
||||
>>> builds_df = NinjaLogParser.to_dataframe(builds)
|
||||
>>> trace_data = ChromeTraceExporter.export_ninja_timeline(builds_df)
|
||||
>>> display_perfetto(trace_data)
|
||||
|
||||
Note:
|
||||
This function requires IPython to be installed (available in Jupyter).
|
||||
The trace data is base64-encoded and embedded in the Perfetto UI URL.
|
||||
For very large traces (>10MB), consider using save_and_link() instead.
|
||||
"""
|
||||
try:
|
||||
from IPython.display import IFrame
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"IPython is required for display_perfetto(). "
|
||||
"Install it with: pip install ipython"
|
||||
)
|
||||
|
||||
# Convert trace to JSON string
|
||||
trace_json = json.dumps(trace_data)
|
||||
|
||||
# Base64 encode for URL
|
||||
trace_b64 = base64.b64encode(trace_json.encode()).decode()
|
||||
|
||||
# Perfetto UI URL with embedded trace
|
||||
perfetto_url = f"https://ui.perfetto.dev/#!/?s={trace_b64}"
|
||||
|
||||
# Display in IFrame
|
||||
return IFrame(perfetto_url, width="100%", height=height)
|
||||
|
||||
|
||||
def save_and_link(
|
||||
trace_data: Dict[str, Any], output_path: str, link_text: Optional[str] = None
|
||||
):
|
||||
"""
|
||||
Save trace to file and display a link to open in Perfetto UI.
|
||||
|
||||
This is useful for large traces that are too big to embed in a URL.
|
||||
|
||||
Args:
|
||||
trace_data: Chrome Trace Event Format dictionary
|
||||
output_path: Path to save the trace file
|
||||
link_text: Custom link text (default: "Open trace in Perfetto UI")
|
||||
|
||||
Returns:
|
||||
IPython HTML object with download link and instructions
|
||||
|
||||
Example:
|
||||
>>> save_and_link(trace_data, '../data/build_trace.json')
|
||||
|
||||
Note:
|
||||
The user will need to manually upload the saved file to
|
||||
https://ui.perfetto.dev
|
||||
"""
|
||||
try:
|
||||
from IPython.display import HTML
|
||||
except ImportError:
|
||||
raise ImportError(
|
||||
"IPython is required for save_and_link(). "
|
||||
"Install it with: pip install ipython"
|
||||
)
|
||||
|
||||
# Save trace to file
|
||||
with open(output_path, "w") as f:
|
||||
json.dump(trace_data, f, indent=2)
|
||||
|
||||
if link_text is None:
|
||||
link_text = "Open trace in Perfetto UI"
|
||||
|
||||
# Create HTML with instructions
|
||||
html = f"""
|
||||
<div style="padding: 10px; border: 1px solid #ddd; border-radius: 5px; background-color: #f9f9f9;">
|
||||
<h4>Trace saved to: <code>{output_path}</code></h4>
|
||||
<p>To view in Perfetto UI:</p>
|
||||
<ol>
|
||||
<li>Go to <a href="https://ui.perfetto.dev" target="_blank">{link_text}</a></li>
|
||||
<li>Click "Open trace file" and select: <code>{output_path}</code></li>
|
||||
</ol>
|
||||
<p><em>Or drag and drop the file directly into the Perfetto UI.</em></p>
|
||||
</div>
|
||||
"""
|
||||
|
||||
return HTML(html)
|
||||
|
||||
|
||||
def get_trace_summary(trace_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
Get summary statistics from trace data.
|
||||
|
||||
Args:
|
||||
trace_data: Chrome Trace Event Format dictionary
|
||||
|
||||
Returns:
|
||||
Dictionary with summary statistics
|
||||
|
||||
Example:
|
||||
>>> summary = get_trace_summary(trace_data)
|
||||
>>> print(f"Total events: {summary['event_count']}")
|
||||
>>> print(f"Total time: {summary['total_duration_s']:.2f}s")
|
||||
"""
|
||||
events = trace_data.get("traceEvents", [])
|
||||
|
||||
if not events:
|
||||
return {
|
||||
"event_count": 0,
|
||||
"total_duration_s": 0.0,
|
||||
"categories": {},
|
||||
"worker_count": 0,
|
||||
}
|
||||
|
||||
# Count events by category
|
||||
categories = {}
|
||||
total_duration_us = 0
|
||||
worker_ids = set()
|
||||
|
||||
for event in events:
|
||||
cat = event.get("cat", "unknown")
|
||||
categories[cat] = categories.get(cat, 0) + 1
|
||||
|
||||
dur = event.get("dur", 0)
|
||||
total_duration_us += dur
|
||||
|
||||
tid = event.get("tid")
|
||||
if tid is not None:
|
||||
worker_ids.add(tid)
|
||||
|
||||
return {
|
||||
"event_count": len(events),
|
||||
"total_duration_s": total_duration_us / 1e6,
|
||||
"categories": categories,
|
||||
"worker_count": len(worker_ids),
|
||||
}
|
||||
|
||||
|
||||
def print_trace_summary(trace_data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Print a formatted summary of trace data.
|
||||
|
||||
Args:
|
||||
trace_data: Chrome Trace Event Format dictionary
|
||||
|
||||
Example:
|
||||
>>> print_trace_summary(trace_data)
|
||||
=== Trace Summary ===
|
||||
Total events: 1,234
|
||||
Total duration: 123.45s
|
||||
Workers: 8
|
||||
...
|
||||
"""
|
||||
summary = get_trace_summary(trace_data)
|
||||
|
||||
print("=== Trace Summary ===")
|
||||
print(f"Total events: {summary['event_count']:,}")
|
||||
print(f"Total duration: {summary['total_duration_s']:.2f}s")
|
||||
print(f"Workers: {summary['worker_count']}")
|
||||
|
||||
if summary["categories"]:
|
||||
print("\nEvents by category:")
|
||||
for cat, count in sorted(
|
||||
summary["categories"].items(), key=lambda x: x[1], reverse=True
|
||||
):
|
||||
print(f" {cat:15} {count:6,} events")
|
||||
Reference in New Issue
Block a user