Spaces:
Sleeping
Sleeping
| const fs = require('fs') | |
| const path = require('path') | |
| const file = process.argv[2] | |
| if (!file) { console.error('Usage: node analyze-react-profiler.cjs <profile.json>'); process.exit(1) } | |
| const raw = JSON.parse(fs.readFileSync(path.resolve(file), 'utf8')) | |
| const root = raw.dataForRoots[0] | |
| const commits = root.commitData | |
| // snapshots: array of [fiberId, {displayName, ...}] | |
| const nameMap = new Map() | |
| for (const snap of root.snapshots) { | |
| const id = snap[0] | |
| const data = snap[1] | |
| if (data?.displayName) nameMap.set(id, data.displayName) | |
| } | |
| console.log(`Commits: ${commits.length} Tracked components: ${nameMap.size}`) | |
| // Probe the unit of fiberActualDurations against the known commit duration | |
| // fiberActualDurations contains durations for the subtree — the root fiber's | |
| // actual duration should be >= commit.duration. Find a plausible scale factor. | |
| const c0 = commits[0] | |
| const knownDur = c0.duration // already in ms per React DevTools spec | |
| const rootId = root.rootID ?? 1 | |
| // Check a few values to pick scale | |
| const sampleDurs = c0.fiberActualDurations.slice(0, 10).map(e => e[1]) | |
| console.log(`\nDebug — commit[0].duration=${knownDur}ms, first 5 raw fiberActualDurations values:`, sampleDurs.slice(0,5)) | |
| // If max sample > 10*knownDur, values are in units of 1/100 ms; otherwise already ms | |
| const maxSample = Math.max(...c0.fiberActualDurations.map(e => e[1])) | |
| const scale = maxSample > knownDur * 10 ? 0.01 : 1 | |
| console.log(`Unit scale: ${scale === 0.01 ? '1/100 ms (dividing by 100)' : 'ms (no conversion)'}\n`) | |
| // --- 1. Commit summary --- | |
| const fmt = (v) => v == null ? ' -' : (v * 1).toFixed(1).padStart(7) | |
| console.log('=== Commit summary ===') | |
| console.log(' # t(s) dur(ms) passive(ms) effects(ms) priority') | |
| const sorted = [...commits].map((c, i) => ({ i, ...c })).sort((a, b) => b.duration - a.duration) | |
| for (const c of sorted.slice(0, 15)) { | |
| const ts = (c.timestamp / 1000).toFixed(3) | |
| console.log(` ${String(c.i).padStart(2)} ${ts} ${fmt(c.duration)} ${fmt(c.passiveEffectDuration)} ${fmt(c.effectDuration)} ${c.priorityLevel ?? ''}`) | |
| } | |
| // --- 2. Aggregate self + actual duration per component --- | |
| const selfTotals = new Map() // name → { total, count, max } | |
| const actualTotals = new Map() | |
| for (const commit of commits) { | |
| for (const [id, raw] of commit.fiberActualDurations) { | |
| const dur = raw * scale | |
| const name = nameMap.get(id) ?? `(fiber#${id})` | |
| const e = actualTotals.get(name) ?? { total: 0, count: 0, max: 0 } | |
| e.total += dur; e.count += 1; e.max = Math.max(e.max, dur) | |
| actualTotals.set(name, e) | |
| } | |
| for (const [id, raw] of commit.fiberSelfDurations) { | |
| const dur = raw * scale | |
| const name = nameMap.get(id) ?? `(fiber#${id})` | |
| const e = selfTotals.get(name) ?? { total: 0, count: 0, max: 0 } | |
| e.total += dur; e.count += 1; e.max = Math.max(e.max, dur) | |
| selfTotals.set(name, e) | |
| } | |
| } | |
| const ranked = [...selfTotals.entries()] | |
| .sort((a, b) => b[1].total - a[1].total) | |
| .filter(([, s]) => s.total > 0.5) | |
| console.log('\n=== Top 40 components by SELF render time (excludes children) ===') | |
| console.log(' Component Self total Renders Self max Actual total') | |
| for (const [name, s] of ranked.slice(0, 40)) { | |
| const actual = actualTotals.get(name) ?? { total: 0 } | |
| console.log( | |
| ` ${name.padEnd(48)} ${s.total.toFixed(1).padStart(8)} ms` + | |
| ` ${String(s.count).padStart(6)}x` + | |
| ` ${s.max.toFixed(1).padStart(7)} ms` + | |
| ` ${actual.total.toFixed(1).padStart(10)} ms` | |
| ) | |
| } | |
| console.log('\n=== Most frequently re-rendering components (top 20) ===') | |
| const byCount = [...selfTotals.entries()].sort((a, b) => b[1].count - a[1].count) | |
| console.log(' Component Renders Self total') | |
| for (const [name, s] of byCount.slice(0, 20)) { | |
| console.log(` ${name.padEnd(48)} ${String(s.count).padStart(6)}x ${s.total.toFixed(1).padStart(8)} ms`) | |
| } | |
| const totalPassive = commits.reduce((a, c) => a + (c.passiveEffectDuration ?? 0), 0) | |
| const totalCommit = commits.reduce((a, c) => a + c.duration, 0) | |
| console.log(`\n=== Totals ===`) | |
| console.log(` Total commit render time: ${totalCommit.toFixed(1)} ms (${commits.length} commits)`) | |
| console.log(` Total passive effect time: ${totalPassive.toFixed(1)} ms (useEffect)`) | |
| console.log(` Avg commit duration: ${(totalCommit / commits.length).toFixed(1)} ms`) | |