perfViz/src/components/TraceStats.tsx
Michael Mainguy 1f12b143ef Initial commit: Performance Trace Analyzer with comprehensive features
🚀 Features implemented:
- IndexedDB local database for trace storage and management
- Drag & drop trace file upload with validation
- HTTP Request viewer with advanced filtering and analysis
- CDN provider detection (Cloudflare, Akamai, CloudFront, etc.)
- Queue time analysis with bottleneck detection
- Visual highlighting for file sizes and request durations
- Priority-based request analysis
- Phase event viewer with detailed trace exploration
- Filmstrip screenshot integration (with debugging)
- 3D Babylon.js viewer component

📊 Analysis capabilities:
- HTTP/1.1 vs HTTP/2 performance comparison
- CDN edge vs origin detection
- Connection limit bottleneck identification
- Priority queue analysis
- Visual correlation with network requests
- Performance bottleneck identification

🛠️ Technical stack:
- React 19.1.0 + TypeScript 5.8.3
- Vite build system
- IndexedDB for local storage
- Babylon.js 8+ for 3D visualization
- Chrome DevTools trace format support

🎨 User experience:
- Clean, professional interface design
- Color-coded performance indicators
- Expandable detailed views
- Search and filtering capabilities
- Responsive grid layouts
- Intuitive navigation

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-06 19:27:12 -05:00

165 lines
5.4 KiB
TypeScript

import { getTraceStats } from '../utils/traceLoader'
interface TraceStatsProps {
stats: ReturnType<typeof getTraceStats>
}
export default function TraceStats({ stats }: TraceStatsProps) {
const formatNumber = (num: number) => {
return new Intl.NumberFormat().format(num)
}
const formatDuration = (microseconds: number) => {
const milliseconds = microseconds / 1000
if (milliseconds < 1000) {
return `${milliseconds.toFixed(2)}ms`
}
const seconds = milliseconds / 1000
return `${seconds.toFixed(2)}s`
}
const formatTimestamp = (timestamp: number) => {
return new Date(timestamp / 1000).toLocaleString()
}
const getPhaseDescription = (phase: string) => {
const descriptions: Record<string, string> = {
'M': 'Metadata',
'X': 'Complete Events',
'I': 'Instant Events',
'B': 'Begin Events',
'E': 'End Events',
'D': 'Deprecated',
'b': 'Nestable Start',
'e': 'Nestable End',
'n': 'Nestable Instant',
'S': 'Async Start',
'T': 'Async Instant',
'F': 'Async End',
'P': 'Sample',
'C': 'Counter',
'R': 'Mark'
}
return descriptions[phase] || `Unknown (${phase})`
}
return (
<div style={{
padding: '20px',
fontFamily: 'system-ui, sans-serif',
maxWidth: '800px',
margin: '0 auto'
}}>
<h2 style={{ marginBottom: '20px', color: '#333' }}>Trace Statistics</h2>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fit, minmax(250px, 1fr))',
gap: '20px',
marginBottom: '30px'
}}>
{/* Overview Section */}
<div style={{
background: '#f8f9fa',
padding: '15px',
borderRadius: '8px',
border: '1px solid #e9ecef'
}}>
<h3 style={{ marginTop: '0', color: '#495057' }}>Overview</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div><strong>Total Events:</strong> {formatNumber(stats.totalEvents)}</div>
<div><strong>Processes:</strong> {stats.processCount}</div>
<div><strong>Threads:</strong> {stats.threadCount}</div>
<div><strong>Duration:</strong> {formatDuration(stats.timeRange.duration)}</div>
</div>
</div>
{/* Metadata Section */}
<div style={{
background: '#f8f9fa',
padding: '15px',
borderRadius: '8px',
border: '1px solid #e9ecef'
}}>
<h3 style={{ marginTop: '0', color: '#495057' }}>Metadata</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px' }}>
<div><strong>Source:</strong> {stats.metadata.source}</div>
<div><strong>Hardware Threads:</strong> {stats.metadata.hardwareConcurrency}</div>
<div><strong>Start Time:</strong> {new Date(stats.metadata.startTime).toLocaleString()}</div>
</div>
</div>
{/* Time Range Section */}
<div style={{
background: '#f8f9fa',
padding: '15px',
borderRadius: '8px',
border: '1px solid #e9ecef'
}}>
<h3 style={{ marginTop: '0', color: '#495057' }}>Time Range</h3>
<div style={{ display: 'flex', flexDirection: 'column', gap: '8px', fontSize: '12px' }}>
<div><strong>Start:</strong> {formatTimestamp(stats.timeRange.start)}</div>
<div><strong>End:</strong> {formatTimestamp(stats.timeRange.end)}</div>
<div><strong>Duration:</strong> {formatDuration(stats.timeRange.duration)}</div>
</div>
</div>
</div>
{/* Event Types Section */}
<div style={{
background: '#f8f9fa',
padding: '20px',
borderRadius: '8px',
border: '1px solid #e9ecef'
}}>
<h3 style={{ marginTop: '0', color: '#495057' }}>Events by Type</h3>
<div style={{
display: 'grid',
gridTemplateColumns: 'repeat(auto-fill, minmax(200px, 1fr))',
gap: '10px'
}}>
{Object.entries(stats.eventsByPhase)
.sort(([, a], [, b]) => b - a)
.map(([phase, count]) => (
<div key={phase} style={{
background: 'white',
padding: '10px',
borderRadius: '4px',
border: '1px solid #dee2e6',
display: 'flex',
flexDirection: 'column',
gap: '4px'
}}>
<div style={{
fontWeight: 'bold',
color: '#495057',
fontSize: '14px'
}}>
{getPhaseDescription(phase)}
</div>
<div style={{
fontSize: '12px',
color: '#6c757d'
}}>
Phase: {phase}
</div>
<div style={{
fontSize: '16px',
fontWeight: 'bold',
color: '#007bff'
}}>
{formatNumber(count)}
</div>
<div style={{
fontSize: '11px',
color: '#6c757d'
}}>
{((count / stats.totalEvents) * 100).toFixed(1)}%
</div>
</div>
))}
</div>
</div>
</div>
)
}