js-self-profiling-utils 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/dist/index.cjs +3 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +86 -0
- package/dist/index.js +96 -0
- package/dist/index.js.map +1 -0
- package/dist/index.umd.js +3 -0
- package/dist/index.umd.js.map +1 -0
- package/package.json +65 -0
package/README.md
ADDED
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# js-self-profiling-utils
|
|
2
|
+
|
|
3
|
+
[](https://github.com/marco-prontera/js-self-profiling-utils/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/js-self-profiling-utils)
|
|
5
|
+
|
|
6
|
+
APIs to parse and summarize [JS Self-Profiling](https://wicg.github.io/js-self-profiling/) traces.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
### npm / yarn / pnpm
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install js-self-profiling-utils
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
### CDN (Browser)
|
|
17
|
+
|
|
18
|
+
```html
|
|
19
|
+
<script src="https://unpkg.com/js-self-profiling-utils"></script>
|
|
20
|
+
<!-- or -->
|
|
21
|
+
<script src="https://cdn.jsdelivr.net/npm/js-self-profiling-utils"></script>
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
|
|
26
|
+
### ES Modules (TypeScript / Modern JS)
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { summarizeTrace, printCallTree } from 'js-self-profiling-utils';
|
|
30
|
+
import type { ProfilerTrace } from 'js-self-profiling-utils';
|
|
31
|
+
|
|
32
|
+
// After collecting a trace with the JS Self-Profiling API
|
|
33
|
+
const trace: ProfilerTrace = await profiler.stop();
|
|
34
|
+
|
|
35
|
+
const summary = summarizeTrace(trace);
|
|
36
|
+
|
|
37
|
+
console.log('Total samples:', summary.totalSamples);
|
|
38
|
+
console.log('Top functions:', summary.topFunctions);
|
|
39
|
+
|
|
40
|
+
// Print call tree to console
|
|
41
|
+
printCallTree(summary.callTree);
|
|
42
|
+
|
|
43
|
+
// Export folded stacks for flamegraph tools
|
|
44
|
+
const folded = summary.toFoldedStacks();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### CommonJS (Node.js)
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
const { summarizeTrace, printCallTree } = require('js-self-profiling-utils');
|
|
51
|
+
|
|
52
|
+
const summary = summarizeTrace(trace);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Browser (Script Tag)
|
|
56
|
+
|
|
57
|
+
```html
|
|
58
|
+
<script src="https://unpkg.com/js-self-profiling-utils"></script>
|
|
59
|
+
<script>
|
|
60
|
+
const { summarizeTrace, printCallTree } = JSSelfProfilingUtils;
|
|
61
|
+
|
|
62
|
+
// Use the APIs
|
|
63
|
+
const summary = summarizeTrace(trace);
|
|
64
|
+
</script>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## API
|
|
68
|
+
|
|
69
|
+
### `summarizeTrace(trace, options?)`
|
|
70
|
+
|
|
71
|
+
Analyzes a ProfilerTrace and returns aggregated data.
|
|
72
|
+
|
|
73
|
+
**Options:**
|
|
74
|
+
| Option | Type | Default | Description |
|
|
75
|
+
|--------|------|---------|-------------|
|
|
76
|
+
| `topN` | number | 30 | Number of top functions to return |
|
|
77
|
+
| `minPercent` | number | 0 | Minimum inclusive percentage threshold |
|
|
78
|
+
| `formatFrame` | function | (built-in) | Custom frame formatting function |
|
|
79
|
+
|
|
80
|
+
**Returns:**
|
|
81
|
+
| Property | Type | Description |
|
|
82
|
+
|----------|------|-------------|
|
|
83
|
+
| `totalSamples` | number | Total number of samples in the trace |
|
|
84
|
+
| `topFunctions` | TraceRow[] | Array of top functions with inclusive/exclusive counts |
|
|
85
|
+
| `callTree` | CallTreeNode | Hierarchical call tree structure |
|
|
86
|
+
| `toFoldedStacks()` | () => string | Method to export folded stacks format (for flamegraphs) |
|
|
87
|
+
|
|
88
|
+
### `printCallTree(node, options?)`
|
|
89
|
+
|
|
90
|
+
Prints a call tree to console with indentation.
|
|
91
|
+
|
|
92
|
+
**Options:**
|
|
93
|
+
| Option | Type | Default | Description |
|
|
94
|
+
|--------|------|---------|-------------|
|
|
95
|
+
| `maxDepth` | number | 8 | Maximum depth to print |
|
|
96
|
+
| `minCount` | number | 1 | Minimum sample count to display |
|
|
97
|
+
|
|
98
|
+
## Development
|
|
99
|
+
|
|
100
|
+
### Prerequisites
|
|
101
|
+
|
|
102
|
+
- Node.js 18+
|
|
103
|
+
- Chrome or Chromium (for E2E tests)
|
|
104
|
+
|
|
105
|
+
> ⚠️ **Important:** The JS Self-Profiling API is only available in **Chromium-based browsers** (Chrome, Edge). Make sure to open the dev server URL in Chrome.
|
|
106
|
+
|
|
107
|
+
### Setup
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# Install dependencies
|
|
111
|
+
npm install
|
|
112
|
+
|
|
113
|
+
# Install Playwright browsers (for E2E tests)
|
|
114
|
+
npx playwright install chromium
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Running the Dev Server
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
npm run dev
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
This starts a Vite dev server at `http://localhost:3000` with the required `Document-Policy: js-profiling` header. Open in **Chrome** to test the profiler interactively.
|
|
124
|
+
|
|
125
|
+
### Running Tests
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# Unit tests
|
|
129
|
+
npm test
|
|
130
|
+
|
|
131
|
+
# Unit tests in watch mode
|
|
132
|
+
npm run test:watch
|
|
133
|
+
|
|
134
|
+
# Unit tests with coverage
|
|
135
|
+
npm run test:coverage
|
|
136
|
+
|
|
137
|
+
# E2E tests (requires Chromium)
|
|
138
|
+
npm run test:e2e
|
|
139
|
+
|
|
140
|
+
# E2E tests with UI
|
|
141
|
+
npm run test:e2e:ui
|
|
142
|
+
|
|
143
|
+
# All tests
|
|
144
|
+
npm run test:all
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Building
|
|
148
|
+
|
|
149
|
+
```bash
|
|
150
|
+
npm run build
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Outputs to `dist/`:
|
|
154
|
+
- `index.js` — ES Module
|
|
155
|
+
- `index.cjs` — CommonJS
|
|
156
|
+
- `index.umd.js` — UMD (for browsers)
|
|
157
|
+
- `index.d.ts` — TypeScript declarations
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
ISC
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});function b(n,i){const e=n.frames[i];if(!e)return"<unknown>";const c=e.name&&e.name.length?e.name:"(anonymous)",o=typeof e.resourceId=="number"&&n.resources?.[e.resourceId]?n.resources[e.resourceId]:"",t=typeof e.line=="number"?`:${e.line}${typeof e.column=="number"?":"+e.column:""}`:"";return o?`${c} @ ${o}${t}`:`${c}${t}`}function k(n,i){const e=[];let c=i;for(;c!==void 0;){const o=n.stacks[c];if(!o)break;e.push(o.frameId),c=o.parentId}return e.reverse(),e}function x(n){const i=new Map,e=new Map,c=new Map;let o=0;for(const t of n.samples||[]){if(typeof t?.stackId!="number")continue;const m=k(n,t.stackId);if(!m.length)continue;o+=1;for(const d of m)i.set(d,(i.get(d)||0)+1);const v=m[m.length-1];e.set(v,(e.get(v)||0)+1);const p=m.join(";");c.set(p,(c.get(p)||0)+1)}return{totalSamples:o,inclusive:i,exclusive:e,collapsedStacks:c}}function y(n,i={}){const{topN:e=30,minPercent:c=0,formatFrame:o=b}=i,{totalSamples:t,inclusive:m,exclusive:v,collapsedStacks:p}=x(n),d=[];for(const[s,r]of m.entries()){const a=v.get(s)||0,l=t?r/t*100:0,u=t?a/t*100:0;l<c||d.push({frameId:s,function:o(n,s),inclusive:r,inclusivePct:+l.toFixed(2),exclusive:a,exclusivePct:+u.toFixed(2)})}d.sort((s,r)=>r.inclusive-s.inclusive);const $=d.slice(0,e),g={name:"(root)",value:0,children:new Map};for(const[s,r]of p.entries()){const a=s.split(";").map(u=>Number(u));let l=g;l.value+=r;for(const u of a){const f=o(n,u);l.children.has(f)||l.children.set(f,{name:f,value:0,children:new Map}),l=l.children.get(f),l.value+=r}}function h(s){const r=Array.from(s.children.values()).sort((a,l)=>l.value-a.value).map(h);return{name:s.name,value:s.value,children:r}}return{totalSamples:t,topFunctions:$,callTree:h(g),toFoldedStacks(){const s=[];for(const[r,a]of p.entries()){const u=r.split(";").map(f=>Number(f)).map(f=>o(n,f));s.push(`${u.join(";")} ${a}`)}return s.sort((r,a)=>{const l=Number(r.slice(r.lastIndexOf(" ")+1));return Number(a.slice(a.lastIndexOf(" ")+1))-l}),s.join(`
|
|
2
|
+
`)}}}function I(n,i={},e=0){const{maxDepth:c=8,minCount:o=1}=i;if(!(e>c)){e===0&&console.log(`${n.name} [samples=${n.value}]`);for(const t of n.children||[])t.value<o||(console.log(`${" ".repeat(e+1)}- ${t.name} (${t.value})`),I(t,i,e+1))}}exports.printCallTree=I;exports.summarizeTrace=y;
|
|
3
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":["../src/index.ts"],"sourcesContent":["/**\n * JS Self-Profiling trace reader\n * Works with the ProfilerTrace returned by: await profiler.stop()\n *\n * Produces:\n * - top functions (inclusive/exclusive)\n * - collapsed call tree (like a simple flamegraph aggregation)\n * - folded stacks export\n */\n\n/**\n * A frame is an element in the context of a stack containing information about the current execution state.\n */\nexport interface Frame {\n name?: string;\n resourceId?: number;\n line?: number;\n column?: number;\n}\n\n/**\n * A stack is a list of frames that MUST be ordered sequentially from outermost to innermost frame.\n */\nexport interface Stack {\n frameId: number;\n parentId?: number;\n}\n\n/**\n * A sample is a descriptor of the instantaneous state of execution at a given point in time. Each sample is associated with a stack.\n */\nexport interface Sample {\n stackId?: number;\n timestamp: number;\n}\n\nexport interface ProfilerTrace {\n resources?: string[]; // The resources attribute MUST return the ProfilerResource list set by the take a sample algorithm.\n frames: Frame[];\n stacks: Stack[];\n samples: Sample[];\n}\n\nexport interface TraceRow {\n frameId: number;\n function: string;\n inclusive: number;\n inclusivePct: number;\n exclusive: number;\n exclusivePct: number;\n}\n\nexport interface CallTreeNode {\n name: string;\n value: number;\n children: CallTreeNode[];\n}\n\nexport interface SummarizeOptions {\n topN?: number;\n minPercent?: number;\n formatFrame?: (trace: ProfilerTrace, frameId: number) => string;\n}\n\nexport interface SummarizeResult {\n totalSamples: number;\n topFunctions: TraceRow[];\n callTree: CallTreeNode;\n toFoldedStacks(): string;\n}\n\nexport interface PrintCallTreeOptions {\n maxDepth?: number;\n minCount?: number;\n}\n\nfunction defaultFormatFrame(trace: ProfilerTrace, frameId: number): string {\n const f = trace.frames[frameId];\n if (!f) return \"<unknown>\";\n const name = f.name && f.name.length ? f.name : \"(anonymous)\";\n const url =\n typeof f.resourceId === \"number\" && trace.resources?.[f.resourceId]\n ? trace.resources[f.resourceId]\n : \"\";\n const loc =\n typeof f.line === \"number\"\n ? `:${f.line}${typeof f.column === \"number\" ? \":\" + f.column : \"\"}`\n : \"\";\n return url ? `${name} @ ${url}${loc}` : `${name}${loc}`;\n}\n\n/**\n * Reconstruct a stack (leaf->root in stacks structure) into an array of frameIds root->leaf.\n */\nfunction unwindStackToFrameIds(trace: ProfilerTrace, stackId: number): number[] {\n const out: number[] = [];\n let cur: number | undefined = stackId;\n // stacks entries reference a single frameId + parentId\n while (cur !== undefined) {\n const s: Stack | undefined = trace.stacks[cur];\n if (!s) break;\n out.push(s.frameId);\n cur = s.parentId;\n }\n // out is leaf->root by construction; reverse to root->leaf\n out.reverse();\n return out;\n}\n\n/**\n * Build aggregations from samples.\n * Inclusive: counts every frame that appears in a sample stack.\n * Exclusive: counts only the leaf frame of that sample stack.\n */\ninterface AggregationResult {\n totalSamples: number;\n inclusive: Map<number, number>;\n exclusive: Map<number, number>;\n collapsedStacks: Map<string, number>;\n}\n\nfunction aggregate(trace: ProfilerTrace): AggregationResult {\n const inclusive = new Map<number, number>();\n const exclusive = new Map<number, number>();\n const collapsedStacks = new Map<string, number>(); // key = \"frameId;frameId;...\"\n let totalSamples = 0;\n\n for (const sample of trace.samples || []) {\n if (typeof sample?.stackId !== \"number\") continue;\n const frameIds = unwindStackToFrameIds(trace, sample.stackId);\n if (!frameIds.length) continue;\n\n totalSamples += 1;\n\n // inclusive\n for (const fid of frameIds) {\n inclusive.set(fid, (inclusive.get(fid) || 0) + 1);\n }\n\n // exclusive = leaf\n const leaf = frameIds[frameIds.length - 1];\n exclusive.set(leaf, (exclusive.get(leaf) || 0) + 1);\n\n // collapsed stack key\n const key = frameIds.join(\";\");\n collapsedStacks.set(key, (collapsedStacks.get(key) || 0) + 1);\n }\n\n return { totalSamples, inclusive, exclusive, collapsedStacks };\n}\n\n/**\n * Make a readable report.\n */\nexport function summarizeTrace(\n trace: ProfilerTrace,\n opts: SummarizeOptions = {}\n): SummarizeResult {\n const { topN = 30, minPercent = 0, formatFrame = defaultFormatFrame } = opts;\n\n const { totalSamples, inclusive, exclusive, collapsedStacks } = aggregate(trace);\n\n const rows: TraceRow[] = [];\n for (const [frameId, incl] of inclusive.entries()) {\n const excl = exclusive.get(frameId) || 0;\n const inclPct = totalSamples ? (incl / totalSamples) * 100 : 0;\n const exclPct = totalSamples ? (excl / totalSamples) * 100 : 0;\n if (inclPct < minPercent) continue;\n rows.push({\n frameId,\n function: formatFrame(trace, frameId),\n inclusive: incl,\n inclusivePct: +inclPct.toFixed(2),\n exclusive: excl,\n exclusivePct: +exclPct.toFixed(2),\n });\n }\n\n rows.sort((a, b) => b.inclusive - a.inclusive);\n\n const top = rows.slice(0, topN);\n\n // Build a simple call tree from collapsed stacks\n interface TreeNode {\n name: string;\n value: number;\n children: Map<string, TreeNode>;\n }\n\n const tree: TreeNode = { name: \"(root)\", value: 0, children: new Map() };\n\n for (const [key, count] of collapsedStacks.entries()) {\n const frameIds = key.split(\";\").map((x) => Number(x));\n let node = tree;\n node.value += count;\n\n for (const fid of frameIds) {\n const label = formatFrame(trace, fid);\n if (!node.children.has(label)) {\n node.children.set(label, { name: label, value: 0, children: new Map() });\n }\n node = node.children.get(label)!;\n node.value += count;\n }\n }\n\n function freezeNode(n: TreeNode): CallTreeNode {\n const children = Array.from(n.children.values())\n .sort((a, b) => b.value - a.value)\n .map(freezeNode);\n return { name: n.name, value: n.value, children };\n }\n\n return {\n totalSamples,\n topFunctions: top,\n callTree: freezeNode(tree),\n /**\n * Folded stacks: \"A;B;C count\"\n * Useful for flamegraphs.\n */\n toFoldedStacks(): string {\n const lines: string[] = [];\n for (const [key, count] of collapsedStacks.entries()) {\n const frameIds = key.split(\";\").map((x) => Number(x));\n const names = frameIds.map((fid) => formatFrame(trace, fid));\n lines.push(`${names.join(\";\")} ${count}`);\n }\n // biggest first is convenient\n lines.sort((a, b) => {\n const ca = Number(a.slice(a.lastIndexOf(\" \") + 1));\n const cb = Number(b.slice(b.lastIndexOf(\" \") + 1));\n return cb - ca;\n });\n return lines.join(\"\\n\");\n },\n };\n}\n\n/**\n * Convenience: pretty-print a call tree to console with indentation.\n */\nexport function printCallTree(\n node: CallTreeNode,\n opts: PrintCallTreeOptions = {},\n _depth: number = 0\n): void {\n const { maxDepth = 8, minCount = 1 } = opts;\n if (_depth > maxDepth) return;\n if (_depth === 0) {\n console.log(`${node.name} [samples=${node.value}]`);\n }\n for (const child of node.children || []) {\n if (child.value < minCount) continue;\n console.log(`${\" \".repeat(_depth + 1)}- ${child.name} (${child.value})`);\n printCallTree(child, opts, _depth + 1);\n }\n}\n"],"names":["defaultFormatFrame","trace","frameId","f","name","url","loc","unwindStackToFrameIds","stackId","out","cur","s","aggregate","inclusive","exclusive","collapsedStacks","totalSamples","sample","frameIds","fid","leaf","key","summarizeTrace","opts","topN","minPercent","formatFrame","rows","incl","excl","inclPct","exclPct","a","b","top","tree","count","x","node","label","freezeNode","n","children","lines","names","ca","printCallTree","_depth","maxDepth","minCount","child"],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export declare interface CallTreeNode {
|
|
2
|
+
name: string;
|
|
3
|
+
value: number;
|
|
4
|
+
children: CallTreeNode[];
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* JS Self-Profiling trace reader
|
|
9
|
+
* Works with the ProfilerTrace returned by: await profiler.stop()
|
|
10
|
+
*
|
|
11
|
+
* Produces:
|
|
12
|
+
* - top functions (inclusive/exclusive)
|
|
13
|
+
* - collapsed call tree (like a simple flamegraph aggregation)
|
|
14
|
+
* - folded stacks export
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* A frame is an element in the context of a stack containing information about the current execution state.
|
|
18
|
+
*/
|
|
19
|
+
export declare interface Frame {
|
|
20
|
+
name?: string;
|
|
21
|
+
resourceId?: number;
|
|
22
|
+
line?: number;
|
|
23
|
+
column?: number;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convenience: pretty-print a call tree to console with indentation.
|
|
28
|
+
*/
|
|
29
|
+
export declare function printCallTree(node: CallTreeNode, opts?: PrintCallTreeOptions, _depth?: number): void;
|
|
30
|
+
|
|
31
|
+
export declare interface PrintCallTreeOptions {
|
|
32
|
+
maxDepth?: number;
|
|
33
|
+
minCount?: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export declare interface ProfilerTrace {
|
|
37
|
+
resources?: string[];
|
|
38
|
+
frames: Frame[];
|
|
39
|
+
stacks: Stack[];
|
|
40
|
+
samples: Sample[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* A sample is a descriptor of the instantaneous state of execution at a given point in time. Each sample is associated with a stack.
|
|
45
|
+
*/
|
|
46
|
+
export declare interface Sample {
|
|
47
|
+
stackId?: number;
|
|
48
|
+
timestamp: number;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* A stack is a list of frames that MUST be ordered sequentially from outermost to innermost frame.
|
|
53
|
+
*/
|
|
54
|
+
export declare interface Stack {
|
|
55
|
+
frameId: number;
|
|
56
|
+
parentId?: number;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export declare interface SummarizeOptions {
|
|
60
|
+
topN?: number;
|
|
61
|
+
minPercent?: number;
|
|
62
|
+
formatFrame?: (trace: ProfilerTrace, frameId: number) => string;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export declare interface SummarizeResult {
|
|
66
|
+
totalSamples: number;
|
|
67
|
+
topFunctions: TraceRow[];
|
|
68
|
+
callTree: CallTreeNode;
|
|
69
|
+
toFoldedStacks(): string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Make a readable report.
|
|
74
|
+
*/
|
|
75
|
+
export declare function summarizeTrace(trace: ProfilerTrace, opts?: SummarizeOptions): SummarizeResult;
|
|
76
|
+
|
|
77
|
+
export declare interface TraceRow {
|
|
78
|
+
frameId: number;
|
|
79
|
+
function: string;
|
|
80
|
+
inclusive: number;
|
|
81
|
+
inclusivePct: number;
|
|
82
|
+
exclusive: number;
|
|
83
|
+
exclusivePct: number;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
function g(n, i) {
|
|
2
|
+
const e = n.frames[i];
|
|
3
|
+
if (!e) return "<unknown>";
|
|
4
|
+
const c = e.name && e.name.length ? e.name : "(anonymous)", o = typeof e.resourceId == "number" && n.resources?.[e.resourceId] ? n.resources[e.resourceId] : "", t = typeof e.line == "number" ? `:${e.line}${typeof e.column == "number" ? ":" + e.column : ""}` : "";
|
|
5
|
+
return o ? `${c} @ ${o}${t}` : `${c}${t}`;
|
|
6
|
+
}
|
|
7
|
+
function k(n, i) {
|
|
8
|
+
const e = [];
|
|
9
|
+
let c = i;
|
|
10
|
+
for (; c !== void 0; ) {
|
|
11
|
+
const o = n.stacks[c];
|
|
12
|
+
if (!o) break;
|
|
13
|
+
e.push(o.frameId), c = o.parentId;
|
|
14
|
+
}
|
|
15
|
+
return e.reverse(), e;
|
|
16
|
+
}
|
|
17
|
+
function x(n) {
|
|
18
|
+
const i = /* @__PURE__ */ new Map(), e = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Map();
|
|
19
|
+
let o = 0;
|
|
20
|
+
for (const t of n.samples || []) {
|
|
21
|
+
if (typeof t?.stackId != "number") continue;
|
|
22
|
+
const m = k(n, t.stackId);
|
|
23
|
+
if (!m.length) continue;
|
|
24
|
+
o += 1;
|
|
25
|
+
for (const d of m)
|
|
26
|
+
i.set(d, (i.get(d) || 0) + 1);
|
|
27
|
+
const v = m[m.length - 1];
|
|
28
|
+
e.set(v, (e.get(v) || 0) + 1);
|
|
29
|
+
const p = m.join(";");
|
|
30
|
+
c.set(p, (c.get(p) || 0) + 1);
|
|
31
|
+
}
|
|
32
|
+
return { totalSamples: o, inclusive: i, exclusive: e, collapsedStacks: c };
|
|
33
|
+
}
|
|
34
|
+
function w(n, i = {}) {
|
|
35
|
+
const { topN: e = 30, minPercent: c = 0, formatFrame: o = g } = i, { totalSamples: t, inclusive: m, exclusive: v, collapsedStacks: p } = x(n), d = [];
|
|
36
|
+
for (const [s, r] of m.entries()) {
|
|
37
|
+
const a = v.get(s) || 0, l = t ? r / t * 100 : 0, u = t ? a / t * 100 : 0;
|
|
38
|
+
l < c || d.push({
|
|
39
|
+
frameId: s,
|
|
40
|
+
function: o(n, s),
|
|
41
|
+
inclusive: r,
|
|
42
|
+
inclusivePct: +l.toFixed(2),
|
|
43
|
+
exclusive: a,
|
|
44
|
+
exclusivePct: +u.toFixed(2)
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
d.sort((s, r) => r.inclusive - s.inclusive);
|
|
48
|
+
const $ = d.slice(0, e), h = { name: "(root)", value: 0, children: /* @__PURE__ */ new Map() };
|
|
49
|
+
for (const [s, r] of p.entries()) {
|
|
50
|
+
const a = s.split(";").map((u) => Number(u));
|
|
51
|
+
let l = h;
|
|
52
|
+
l.value += r;
|
|
53
|
+
for (const u of a) {
|
|
54
|
+
const f = o(n, u);
|
|
55
|
+
l.children.has(f) || l.children.set(f, { name: f, value: 0, children: /* @__PURE__ */ new Map() }), l = l.children.get(f), l.value += r;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function I(s) {
|
|
59
|
+
const r = Array.from(s.children.values()).sort((a, l) => l.value - a.value).map(I);
|
|
60
|
+
return { name: s.name, value: s.value, children: r };
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
totalSamples: t,
|
|
64
|
+
topFunctions: $,
|
|
65
|
+
callTree: I(h),
|
|
66
|
+
/**
|
|
67
|
+
* Folded stacks: "A;B;C count"
|
|
68
|
+
* Useful for flamegraphs.
|
|
69
|
+
*/
|
|
70
|
+
toFoldedStacks() {
|
|
71
|
+
const s = [];
|
|
72
|
+
for (const [r, a] of p.entries()) {
|
|
73
|
+
const u = r.split(";").map((f) => Number(f)).map((f) => o(n, f));
|
|
74
|
+
s.push(`${u.join(";")} ${a}`);
|
|
75
|
+
}
|
|
76
|
+
return s.sort((r, a) => {
|
|
77
|
+
const l = Number(r.slice(r.lastIndexOf(" ") + 1));
|
|
78
|
+
return Number(a.slice(a.lastIndexOf(" ") + 1)) - l;
|
|
79
|
+
}), s.join(`
|
|
80
|
+
`);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function b(n, i = {}, e = 0) {
|
|
85
|
+
const { maxDepth: c = 8, minCount: o = 1 } = i;
|
|
86
|
+
if (!(e > c)) {
|
|
87
|
+
e === 0 && console.log(`${n.name} [samples=${n.value}]`);
|
|
88
|
+
for (const t of n.children || [])
|
|
89
|
+
t.value < o || (console.log(`${" ".repeat(e + 1)}- ${t.name} (${t.value})`), b(t, i, e + 1));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
b as printCallTree,
|
|
94
|
+
w as summarizeTrace
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * JS Self-Profiling trace reader\n * Works with the ProfilerTrace returned by: await profiler.stop()\n *\n * Produces:\n * - top functions (inclusive/exclusive)\n * - collapsed call tree (like a simple flamegraph aggregation)\n * - folded stacks export\n */\n\n/**\n * A frame is an element in the context of a stack containing information about the current execution state.\n */\nexport interface Frame {\n name?: string;\n resourceId?: number;\n line?: number;\n column?: number;\n}\n\n/**\n * A stack is a list of frames that MUST be ordered sequentially from outermost to innermost frame.\n */\nexport interface Stack {\n frameId: number;\n parentId?: number;\n}\n\n/**\n * A sample is a descriptor of the instantaneous state of execution at a given point in time. Each sample is associated with a stack.\n */\nexport interface Sample {\n stackId?: number;\n timestamp: number;\n}\n\nexport interface ProfilerTrace {\n resources?: string[]; // The resources attribute MUST return the ProfilerResource list set by the take a sample algorithm.\n frames: Frame[];\n stacks: Stack[];\n samples: Sample[];\n}\n\nexport interface TraceRow {\n frameId: number;\n function: string;\n inclusive: number;\n inclusivePct: number;\n exclusive: number;\n exclusivePct: number;\n}\n\nexport interface CallTreeNode {\n name: string;\n value: number;\n children: CallTreeNode[];\n}\n\nexport interface SummarizeOptions {\n topN?: number;\n minPercent?: number;\n formatFrame?: (trace: ProfilerTrace, frameId: number) => string;\n}\n\nexport interface SummarizeResult {\n totalSamples: number;\n topFunctions: TraceRow[];\n callTree: CallTreeNode;\n toFoldedStacks(): string;\n}\n\nexport interface PrintCallTreeOptions {\n maxDepth?: number;\n minCount?: number;\n}\n\nfunction defaultFormatFrame(trace: ProfilerTrace, frameId: number): string {\n const f = trace.frames[frameId];\n if (!f) return \"<unknown>\";\n const name = f.name && f.name.length ? f.name : \"(anonymous)\";\n const url =\n typeof f.resourceId === \"number\" && trace.resources?.[f.resourceId]\n ? trace.resources[f.resourceId]\n : \"\";\n const loc =\n typeof f.line === \"number\"\n ? `:${f.line}${typeof f.column === \"number\" ? \":\" + f.column : \"\"}`\n : \"\";\n return url ? `${name} @ ${url}${loc}` : `${name}${loc}`;\n}\n\n/**\n * Reconstruct a stack (leaf->root in stacks structure) into an array of frameIds root->leaf.\n */\nfunction unwindStackToFrameIds(trace: ProfilerTrace, stackId: number): number[] {\n const out: number[] = [];\n let cur: number | undefined = stackId;\n // stacks entries reference a single frameId + parentId\n while (cur !== undefined) {\n const s: Stack | undefined = trace.stacks[cur];\n if (!s) break;\n out.push(s.frameId);\n cur = s.parentId;\n }\n // out is leaf->root by construction; reverse to root->leaf\n out.reverse();\n return out;\n}\n\n/**\n * Build aggregations from samples.\n * Inclusive: counts every frame that appears in a sample stack.\n * Exclusive: counts only the leaf frame of that sample stack.\n */\ninterface AggregationResult {\n totalSamples: number;\n inclusive: Map<number, number>;\n exclusive: Map<number, number>;\n collapsedStacks: Map<string, number>;\n}\n\nfunction aggregate(trace: ProfilerTrace): AggregationResult {\n const inclusive = new Map<number, number>();\n const exclusive = new Map<number, number>();\n const collapsedStacks = new Map<string, number>(); // key = \"frameId;frameId;...\"\n let totalSamples = 0;\n\n for (const sample of trace.samples || []) {\n if (typeof sample?.stackId !== \"number\") continue;\n const frameIds = unwindStackToFrameIds(trace, sample.stackId);\n if (!frameIds.length) continue;\n\n totalSamples += 1;\n\n // inclusive\n for (const fid of frameIds) {\n inclusive.set(fid, (inclusive.get(fid) || 0) + 1);\n }\n\n // exclusive = leaf\n const leaf = frameIds[frameIds.length - 1];\n exclusive.set(leaf, (exclusive.get(leaf) || 0) + 1);\n\n // collapsed stack key\n const key = frameIds.join(\";\");\n collapsedStacks.set(key, (collapsedStacks.get(key) || 0) + 1);\n }\n\n return { totalSamples, inclusive, exclusive, collapsedStacks };\n}\n\n/**\n * Make a readable report.\n */\nexport function summarizeTrace(\n trace: ProfilerTrace,\n opts: SummarizeOptions = {}\n): SummarizeResult {\n const { topN = 30, minPercent = 0, formatFrame = defaultFormatFrame } = opts;\n\n const { totalSamples, inclusive, exclusive, collapsedStacks } = aggregate(trace);\n\n const rows: TraceRow[] = [];\n for (const [frameId, incl] of inclusive.entries()) {\n const excl = exclusive.get(frameId) || 0;\n const inclPct = totalSamples ? (incl / totalSamples) * 100 : 0;\n const exclPct = totalSamples ? (excl / totalSamples) * 100 : 0;\n if (inclPct < minPercent) continue;\n rows.push({\n frameId,\n function: formatFrame(trace, frameId),\n inclusive: incl,\n inclusivePct: +inclPct.toFixed(2),\n exclusive: excl,\n exclusivePct: +exclPct.toFixed(2),\n });\n }\n\n rows.sort((a, b) => b.inclusive - a.inclusive);\n\n const top = rows.slice(0, topN);\n\n // Build a simple call tree from collapsed stacks\n interface TreeNode {\n name: string;\n value: number;\n children: Map<string, TreeNode>;\n }\n\n const tree: TreeNode = { name: \"(root)\", value: 0, children: new Map() };\n\n for (const [key, count] of collapsedStacks.entries()) {\n const frameIds = key.split(\";\").map((x) => Number(x));\n let node = tree;\n node.value += count;\n\n for (const fid of frameIds) {\n const label = formatFrame(trace, fid);\n if (!node.children.has(label)) {\n node.children.set(label, { name: label, value: 0, children: new Map() });\n }\n node = node.children.get(label)!;\n node.value += count;\n }\n }\n\n function freezeNode(n: TreeNode): CallTreeNode {\n const children = Array.from(n.children.values())\n .sort((a, b) => b.value - a.value)\n .map(freezeNode);\n return { name: n.name, value: n.value, children };\n }\n\n return {\n totalSamples,\n topFunctions: top,\n callTree: freezeNode(tree),\n /**\n * Folded stacks: \"A;B;C count\"\n * Useful for flamegraphs.\n */\n toFoldedStacks(): string {\n const lines: string[] = [];\n for (const [key, count] of collapsedStacks.entries()) {\n const frameIds = key.split(\";\").map((x) => Number(x));\n const names = frameIds.map((fid) => formatFrame(trace, fid));\n lines.push(`${names.join(\";\")} ${count}`);\n }\n // biggest first is convenient\n lines.sort((a, b) => {\n const ca = Number(a.slice(a.lastIndexOf(\" \") + 1));\n const cb = Number(b.slice(b.lastIndexOf(\" \") + 1));\n return cb - ca;\n });\n return lines.join(\"\\n\");\n },\n };\n}\n\n/**\n * Convenience: pretty-print a call tree to console with indentation.\n */\nexport function printCallTree(\n node: CallTreeNode,\n opts: PrintCallTreeOptions = {},\n _depth: number = 0\n): void {\n const { maxDepth = 8, minCount = 1 } = opts;\n if (_depth > maxDepth) return;\n if (_depth === 0) {\n console.log(`${node.name} [samples=${node.value}]`);\n }\n for (const child of node.children || []) {\n if (child.value < minCount) continue;\n console.log(`${\" \".repeat(_depth + 1)}- ${child.name} (${child.value})`);\n printCallTree(child, opts, _depth + 1);\n }\n}\n"],"names":["defaultFormatFrame","trace","frameId","f","name","url","loc","unwindStackToFrameIds","stackId","out","cur","s","aggregate","inclusive","exclusive","collapsedStacks","totalSamples","sample","frameIds","fid","leaf","key","summarizeTrace","opts","topN","minPercent","formatFrame","rows","incl","excl","inclPct","exclPct","a","b","top","tree","count","x","node","label","freezeNode","n","children","lines","names","ca","printCallTree","_depth","maxDepth","minCount","child"],"mappings":"AA4EA,SAASA,EAAmBC,GAAsBC,GAAyB;AACzE,QAAMC,IAAIF,EAAM,OAAOC,CAAO;AAC9B,MAAI,CAACC,EAAG,QAAO;AACf,QAAMC,IAAOD,EAAE,QAAQA,EAAE,KAAK,SAASA,EAAE,OAAO,eAC1CE,IACJ,OAAOF,EAAE,cAAe,YAAYF,EAAM,YAAYE,EAAE,UAAU,IAC9DF,EAAM,UAAUE,EAAE,UAAU,IAC5B,IACAG,IACJ,OAAOH,EAAE,QAAS,WACd,IAAIA,EAAE,IAAI,GAAG,OAAOA,EAAE,UAAW,WAAW,MAAMA,EAAE,SAAS,EAAE,KAC/D;AACN,SAAOE,IAAM,GAAGD,CAAI,MAAMC,CAAG,GAAGC,CAAG,KAAK,GAAGF,CAAI,GAAGE,CAAG;AACvD;AAKA,SAASC,EAAsBN,GAAsBO,GAA2B;AAC9E,QAAMC,IAAgB,CAAA;AACtB,MAAIC,IAA0BF;AAE9B,SAAOE,MAAQ,UAAW;AACxB,UAAMC,IAAuBV,EAAM,OAAOS,CAAG;AAC7C,QAAI,CAACC,EAAG;AACR,IAAAF,EAAI,KAAKE,EAAE,OAAO,GAClBD,IAAMC,EAAE;AAAA,EACV;AAEA,SAAAF,EAAI,QAAA,GACGA;AACT;AAcA,SAASG,EAAUX,GAAyC;AAC1D,QAAMY,wBAAgB,IAAA,GAChBC,wBAAgB,IAAA,GAChBC,wBAAsB,IAAA;AAC5B,MAAIC,IAAe;AAEnB,aAAWC,KAAUhB,EAAM,WAAW,CAAA,GAAI;AACxC,QAAI,OAAOgB,GAAQ,WAAY,SAAU;AACzC,UAAMC,IAAWX,EAAsBN,GAAOgB,EAAO,OAAO;AAC5D,QAAI,CAACC,EAAS,OAAQ;AAEtB,IAAAF,KAAgB;AAGhB,eAAWG,KAAOD;AAChB,MAAAL,EAAU,IAAIM,IAAMN,EAAU,IAAIM,CAAG,KAAK,KAAK,CAAC;AAIlD,UAAMC,IAAOF,EAASA,EAAS,SAAS,CAAC;AACzC,IAAAJ,EAAU,IAAIM,IAAON,EAAU,IAAIM,CAAI,KAAK,KAAK,CAAC;AAGlD,UAAMC,IAAMH,EAAS,KAAK,GAAG;AAC7B,IAAAH,EAAgB,IAAIM,IAAMN,EAAgB,IAAIM,CAAG,KAAK,KAAK,CAAC;AAAA,EAC9D;AAEA,SAAO,EAAE,cAAAL,GAAc,WAAAH,GAAW,WAAAC,GAAW,iBAAAC,EAAA;AAC/C;AAKO,SAASO,EACdrB,GACAsB,IAAyB,IACR;AACjB,QAAM,EAAE,MAAAC,IAAO,IAAI,YAAAC,IAAa,GAAG,aAAAC,IAAc1B,MAAuBuB,GAElE,EAAE,cAAAP,GAAc,WAAAH,GAAW,WAAAC,GAAW,iBAAAC,EAAA,IAAoBH,EAAUX,CAAK,GAEzE0B,IAAmB,CAAA;AACzB,aAAW,CAACzB,GAAS0B,CAAI,KAAKf,EAAU,WAAW;AACjD,UAAMgB,IAAOf,EAAU,IAAIZ,CAAO,KAAK,GACjC4B,IAAUd,IAAgBY,IAAOZ,IAAgB,MAAM,GACvDe,IAAUf,IAAgBa,IAAOb,IAAgB,MAAM;AAC7D,IAAIc,IAAUL,KACdE,EAAK,KAAK;AAAA,MACR,SAAAzB;AAAA,MACA,UAAUwB,EAAYzB,GAAOC,CAAO;AAAA,MACpC,WAAW0B;AAAA,MACX,cAAc,CAACE,EAAQ,QAAQ,CAAC;AAAA,MAChC,WAAWD;AAAA,MACX,cAAc,CAACE,EAAQ,QAAQ,CAAC;AAAA,IAAA,CACjC;AAAA,EACH;AAEA,EAAAJ,EAAK,KAAK,CAACK,GAAGC,MAAMA,EAAE,YAAYD,EAAE,SAAS;AAE7C,QAAME,IAAMP,EAAK,MAAM,GAAGH,CAAI,GASxBW,IAAiB,EAAE,MAAM,UAAU,OAAO,GAAG,UAAU,oBAAI,MAAI;AAErE,aAAW,CAACd,GAAKe,CAAK,KAAKrB,EAAgB,WAAW;AACpD,UAAMG,IAAWG,EAAI,MAAM,GAAG,EAAE,IAAI,CAACgB,MAAM,OAAOA,CAAC,CAAC;AACpD,QAAIC,IAAOH;AACX,IAAAG,EAAK,SAASF;AAEd,eAAWjB,KAAOD,GAAU;AAC1B,YAAMqB,IAAQb,EAAYzB,GAAOkB,CAAG;AACpC,MAAKmB,EAAK,SAAS,IAAIC,CAAK,KAC1BD,EAAK,SAAS,IAAIC,GAAO,EAAE,MAAMA,GAAO,OAAO,GAAG,UAAU,oBAAI,IAAA,EAAI,CAAG,GAEzED,IAAOA,EAAK,SAAS,IAAIC,CAAK,GAC9BD,EAAK,SAASF;AAAA,IAChB;AAAA,EACF;AAEA,WAASI,EAAWC,GAA2B;AAC7C,UAAMC,IAAW,MAAM,KAAKD,EAAE,SAAS,OAAA,CAAQ,EAC5C,KAAK,CAAC,GAAGR,MAAMA,EAAE,QAAQ,EAAE,KAAK,EAChC,IAAIO,CAAU;AACjB,WAAO,EAAE,MAAMC,EAAE,MAAM,OAAOA,EAAE,OAAO,UAAAC,EAAA;AAAA,EACzC;AAEA,SAAO;AAAA,IACL,cAAA1B;AAAA,IACA,cAAckB;AAAA,IACd,UAAUM,EAAWL,CAAI;AAAA;AAAA;AAAA;AAAA;AAAA,IAKzB,iBAAyB;AACvB,YAAMQ,IAAkB,CAAA;AACxB,iBAAW,CAACtB,GAAKe,CAAK,KAAKrB,EAAgB,WAAW;AAEpD,cAAM6B,IADWvB,EAAI,MAAM,GAAG,EAAE,IAAI,CAACgB,MAAM,OAAOA,CAAC,CAAC,EAC7B,IAAI,CAAClB,MAAQO,EAAYzB,GAAOkB,CAAG,CAAC;AAC3D,QAAAwB,EAAM,KAAK,GAAGC,EAAM,KAAK,GAAG,CAAC,IAAIR,CAAK,EAAE;AAAA,MAC1C;AAEA,aAAAO,EAAM,KAAK,CAACX,GAAGC,MAAM;AACnB,cAAMY,IAAK,OAAOb,EAAE,MAAMA,EAAE,YAAY,GAAG,IAAI,CAAC,CAAC;AAEjD,eADW,OAAOC,EAAE,MAAMA,EAAE,YAAY,GAAG,IAAI,CAAC,CAAC,IACrCY;AAAA,MACd,CAAC,GACMF,EAAM,KAAK;AAAA,CAAI;AAAA,IACxB;AAAA,EAAA;AAEJ;AAKO,SAASG,EACdR,GACAf,IAA6B,CAAA,GAC7BwB,IAAiB,GACX;AACN,QAAM,EAAE,UAAAC,IAAW,GAAG,UAAAC,IAAW,MAAM1B;AACvC,MAAI,EAAAwB,IAASC,IACb;AAAA,IAAID,MAAW,KACb,QAAQ,IAAI,GAAGT,EAAK,IAAI,aAAaA,EAAK,KAAK,GAAG;AAEpD,eAAWY,KAASZ,EAAK,YAAY,CAAA;AACnC,MAAIY,EAAM,QAAQD,MAClB,QAAQ,IAAI,GAAG,KAAK,OAAOF,IAAS,CAAC,CAAC,KAAKG,EAAM,IAAI,KAAKA,EAAM,KAAK,GAAG,GACxEJ,EAAcI,GAAO3B,GAAMwB,IAAS,CAAC;AAAA;AAEzC;"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
(function(m,v){typeof exports=="object"&&typeof module<"u"?v(exports):typeof define=="function"&&define.amd?define(["exports"],v):(m=typeof globalThis<"u"?globalThis:m||self,v(m.JSSelfProfilingUtils={}))})(this,(function(m){"use strict";function v(n,l){const e=n.frames[l];if(!e)return"<unknown>";const c=e.name&&e.name.length?e.name:"(anonymous)",o=typeof e.resourceId=="number"&&n.resources?.[e.resourceId]?n.resources[e.resourceId]:"",t=typeof e.line=="number"?`:${e.line}${typeof e.column=="number"?":"+e.column:""}`:"";return o?`${c} @ ${o}${t}`:`${c}${t}`}function y(n,l){const e=[];let c=l;for(;c!==void 0;){const o=n.stacks[c];if(!o)break;e.push(o.frameId),c=o.parentId}return e.reverse(),e}function k(n){const l=new Map,e=new Map,c=new Map;let o=0;for(const t of n.samples||[]){if(typeof t?.stackId!="number")continue;const d=y(n,t.stackId);if(!d.length)continue;o+=1;for(const p of d)l.set(p,(l.get(p)||0)+1);const g=d[d.length-1];e.set(g,(e.get(g)||0)+1);const h=d.join(";");c.set(h,(c.get(h)||0)+1)}return{totalSamples:o,inclusive:l,exclusive:e,collapsedStacks:c}}function x(n,l={}){const{topN:e=30,minPercent:c=0,formatFrame:o=v}=l,{totalSamples:t,inclusive:d,exclusive:g,collapsedStacks:h}=k(n),p=[];for(const[s,i]of d.entries()){const u=g.get(s)||0,r=t?i/t*100:0,a=t?u/t*100:0;r<c||p.push({frameId:s,function:o(n,s),inclusive:i,inclusivePct:+r.toFixed(2),exclusive:u,exclusivePct:+a.toFixed(2)})}p.sort((s,i)=>i.inclusive-s.inclusive);const S=p.slice(0,e),b={name:"(root)",value:0,children:new Map};for(const[s,i]of h.entries()){const u=s.split(";").map(a=>Number(a));let r=b;r.value+=i;for(const a of u){const f=o(n,a);r.children.has(f)||r.children.set(f,{name:f,value:0,children:new Map}),r=r.children.get(f),r.value+=i}}function $(s){const i=Array.from(s.children.values()).sort((u,r)=>r.value-u.value).map($);return{name:s.name,value:s.value,children:i}}return{totalSamples:t,topFunctions:S,callTree:$(b),toFoldedStacks(){const s=[];for(const[i,u]of h.entries()){const a=i.split(";").map(f=>Number(f)).map(f=>o(n,f));s.push(`${a.join(";")} ${u}`)}return s.sort((i,u)=>{const r=Number(i.slice(i.lastIndexOf(" ")+1));return Number(u.slice(u.lastIndexOf(" ")+1))-r}),s.join(`
|
|
2
|
+
`)}}}function I(n,l={},e=0){const{maxDepth:c=8,minCount:o=1}=l;if(!(e>c)){e===0&&console.log(`${n.name} [samples=${n.value}]`);for(const t of n.children||[])t.value<o||(console.log(`${" ".repeat(e+1)}- ${t.name} (${t.value})`),I(t,l,e+1))}}m.printCallTree=I,m.summarizeTrace=x,Object.defineProperty(m,Symbol.toStringTag,{value:"Module"})}));
|
|
3
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.umd.js","sources":["../src/index.ts"],"sourcesContent":["/**\n * JS Self-Profiling trace reader\n * Works with the ProfilerTrace returned by: await profiler.stop()\n *\n * Produces:\n * - top functions (inclusive/exclusive)\n * - collapsed call tree (like a simple flamegraph aggregation)\n * - folded stacks export\n */\n\n/**\n * A frame is an element in the context of a stack containing information about the current execution state.\n */\nexport interface Frame {\n name?: string;\n resourceId?: number;\n line?: number;\n column?: number;\n}\n\n/**\n * A stack is a list of frames that MUST be ordered sequentially from outermost to innermost frame.\n */\nexport interface Stack {\n frameId: number;\n parentId?: number;\n}\n\n/**\n * A sample is a descriptor of the instantaneous state of execution at a given point in time. Each sample is associated with a stack.\n */\nexport interface Sample {\n stackId?: number;\n timestamp: number;\n}\n\nexport interface ProfilerTrace {\n resources?: string[]; // The resources attribute MUST return the ProfilerResource list set by the take a sample algorithm.\n frames: Frame[];\n stacks: Stack[];\n samples: Sample[];\n}\n\nexport interface TraceRow {\n frameId: number;\n function: string;\n inclusive: number;\n inclusivePct: number;\n exclusive: number;\n exclusivePct: number;\n}\n\nexport interface CallTreeNode {\n name: string;\n value: number;\n children: CallTreeNode[];\n}\n\nexport interface SummarizeOptions {\n topN?: number;\n minPercent?: number;\n formatFrame?: (trace: ProfilerTrace, frameId: number) => string;\n}\n\nexport interface SummarizeResult {\n totalSamples: number;\n topFunctions: TraceRow[];\n callTree: CallTreeNode;\n toFoldedStacks(): string;\n}\n\nexport interface PrintCallTreeOptions {\n maxDepth?: number;\n minCount?: number;\n}\n\nfunction defaultFormatFrame(trace: ProfilerTrace, frameId: number): string {\n const f = trace.frames[frameId];\n if (!f) return \"<unknown>\";\n const name = f.name && f.name.length ? f.name : \"(anonymous)\";\n const url =\n typeof f.resourceId === \"number\" && trace.resources?.[f.resourceId]\n ? trace.resources[f.resourceId]\n : \"\";\n const loc =\n typeof f.line === \"number\"\n ? `:${f.line}${typeof f.column === \"number\" ? \":\" + f.column : \"\"}`\n : \"\";\n return url ? `${name} @ ${url}${loc}` : `${name}${loc}`;\n}\n\n/**\n * Reconstruct a stack (leaf->root in stacks structure) into an array of frameIds root->leaf.\n */\nfunction unwindStackToFrameIds(trace: ProfilerTrace, stackId: number): number[] {\n const out: number[] = [];\n let cur: number | undefined = stackId;\n // stacks entries reference a single frameId + parentId\n while (cur !== undefined) {\n const s: Stack | undefined = trace.stacks[cur];\n if (!s) break;\n out.push(s.frameId);\n cur = s.parentId;\n }\n // out is leaf->root by construction; reverse to root->leaf\n out.reverse();\n return out;\n}\n\n/**\n * Build aggregations from samples.\n * Inclusive: counts every frame that appears in a sample stack.\n * Exclusive: counts only the leaf frame of that sample stack.\n */\ninterface AggregationResult {\n totalSamples: number;\n inclusive: Map<number, number>;\n exclusive: Map<number, number>;\n collapsedStacks: Map<string, number>;\n}\n\nfunction aggregate(trace: ProfilerTrace): AggregationResult {\n const inclusive = new Map<number, number>();\n const exclusive = new Map<number, number>();\n const collapsedStacks = new Map<string, number>(); // key = \"frameId;frameId;...\"\n let totalSamples = 0;\n\n for (const sample of trace.samples || []) {\n if (typeof sample?.stackId !== \"number\") continue;\n const frameIds = unwindStackToFrameIds(trace, sample.stackId);\n if (!frameIds.length) continue;\n\n totalSamples += 1;\n\n // inclusive\n for (const fid of frameIds) {\n inclusive.set(fid, (inclusive.get(fid) || 0) + 1);\n }\n\n // exclusive = leaf\n const leaf = frameIds[frameIds.length - 1];\n exclusive.set(leaf, (exclusive.get(leaf) || 0) + 1);\n\n // collapsed stack key\n const key = frameIds.join(\";\");\n collapsedStacks.set(key, (collapsedStacks.get(key) || 0) + 1);\n }\n\n return { totalSamples, inclusive, exclusive, collapsedStacks };\n}\n\n/**\n * Make a readable report.\n */\nexport function summarizeTrace(\n trace: ProfilerTrace,\n opts: SummarizeOptions = {}\n): SummarizeResult {\n const { topN = 30, minPercent = 0, formatFrame = defaultFormatFrame } = opts;\n\n const { totalSamples, inclusive, exclusive, collapsedStacks } = aggregate(trace);\n\n const rows: TraceRow[] = [];\n for (const [frameId, incl] of inclusive.entries()) {\n const excl = exclusive.get(frameId) || 0;\n const inclPct = totalSamples ? (incl / totalSamples) * 100 : 0;\n const exclPct = totalSamples ? (excl / totalSamples) * 100 : 0;\n if (inclPct < minPercent) continue;\n rows.push({\n frameId,\n function: formatFrame(trace, frameId),\n inclusive: incl,\n inclusivePct: +inclPct.toFixed(2),\n exclusive: excl,\n exclusivePct: +exclPct.toFixed(2),\n });\n }\n\n rows.sort((a, b) => b.inclusive - a.inclusive);\n\n const top = rows.slice(0, topN);\n\n // Build a simple call tree from collapsed stacks\n interface TreeNode {\n name: string;\n value: number;\n children: Map<string, TreeNode>;\n }\n\n const tree: TreeNode = { name: \"(root)\", value: 0, children: new Map() };\n\n for (const [key, count] of collapsedStacks.entries()) {\n const frameIds = key.split(\";\").map((x) => Number(x));\n let node = tree;\n node.value += count;\n\n for (const fid of frameIds) {\n const label = formatFrame(trace, fid);\n if (!node.children.has(label)) {\n node.children.set(label, { name: label, value: 0, children: new Map() });\n }\n node = node.children.get(label)!;\n node.value += count;\n }\n }\n\n function freezeNode(n: TreeNode): CallTreeNode {\n const children = Array.from(n.children.values())\n .sort((a, b) => b.value - a.value)\n .map(freezeNode);\n return { name: n.name, value: n.value, children };\n }\n\n return {\n totalSamples,\n topFunctions: top,\n callTree: freezeNode(tree),\n /**\n * Folded stacks: \"A;B;C count\"\n * Useful for flamegraphs.\n */\n toFoldedStacks(): string {\n const lines: string[] = [];\n for (const [key, count] of collapsedStacks.entries()) {\n const frameIds = key.split(\";\").map((x) => Number(x));\n const names = frameIds.map((fid) => formatFrame(trace, fid));\n lines.push(`${names.join(\";\")} ${count}`);\n }\n // biggest first is convenient\n lines.sort((a, b) => {\n const ca = Number(a.slice(a.lastIndexOf(\" \") + 1));\n const cb = Number(b.slice(b.lastIndexOf(\" \") + 1));\n return cb - ca;\n });\n return lines.join(\"\\n\");\n },\n };\n}\n\n/**\n * Convenience: pretty-print a call tree to console with indentation.\n */\nexport function printCallTree(\n node: CallTreeNode,\n opts: PrintCallTreeOptions = {},\n _depth: number = 0\n): void {\n const { maxDepth = 8, minCount = 1 } = opts;\n if (_depth > maxDepth) return;\n if (_depth === 0) {\n console.log(`${node.name} [samples=${node.value}]`);\n }\n for (const child of node.children || []) {\n if (child.value < minCount) continue;\n console.log(`${\" \".repeat(_depth + 1)}- ${child.name} (${child.value})`);\n printCallTree(child, opts, _depth + 1);\n }\n}\n"],"names":["defaultFormatFrame","trace","frameId","f","name","url","loc","unwindStackToFrameIds","stackId","out","cur","s","aggregate","inclusive","exclusive","collapsedStacks","totalSamples","sample","frameIds","fid","leaf","key","summarizeTrace","opts","topN","minPercent","formatFrame","rows","incl","excl","inclPct","exclPct","a","b","top","tree","count","x","node","label","freezeNode","n","children","lines","names","ca","printCallTree","_depth","maxDepth","minCount","child"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "js-self-profiling-utils",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "APIs to parse and summarize JS Self-Profiling traces.",
|
|
5
|
+
"homepage": "https://github.com/marco-prontera/js-self-profiling-utils#readme",
|
|
6
|
+
"bugs": {
|
|
7
|
+
"url": "https://github.com/marco-prontera/js-self-profiling-utils/issues"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/marco-prontera/js-self-profiling-utils.git"
|
|
12
|
+
},
|
|
13
|
+
"license": "ISC",
|
|
14
|
+
"author": "Marco Prontera",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"main": "./dist/index.cjs",
|
|
17
|
+
"module": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
},
|
|
25
|
+
"require": {
|
|
26
|
+
"types": "./dist/index.d.cts",
|
|
27
|
+
"default": "./dist/index.cjs"
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
"unpkg": "./dist/index.umd.js",
|
|
32
|
+
"jsdelivr": "./dist/index.umd.js",
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"dev": "vite",
|
|
38
|
+
"build": "vite build",
|
|
39
|
+
"preview": "vite preview",
|
|
40
|
+
"typecheck": "tsc --noEmit",
|
|
41
|
+
"test": "vitest run",
|
|
42
|
+
"test:watch": "vitest",
|
|
43
|
+
"test:coverage": "vitest run --coverage",
|
|
44
|
+
"test:e2e": "playwright test",
|
|
45
|
+
"test:e2e:ui": "playwright test --ui",
|
|
46
|
+
"test:all": "npm run test && npm run test:e2e",
|
|
47
|
+
"prepublishOnly": "npm run build"
|
|
48
|
+
},
|
|
49
|
+
"devDependencies": {
|
|
50
|
+
"@playwright/test": "^1.58.0",
|
|
51
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
52
|
+
"typescript": "^5.9.3",
|
|
53
|
+
"vite": "^7.3.1",
|
|
54
|
+
"vite-plugin-dts": "^4.5.4",
|
|
55
|
+
"vitest": "^4.0.18"
|
|
56
|
+
},
|
|
57
|
+
"keywords": [
|
|
58
|
+
"profiling",
|
|
59
|
+
"js-self-profiling",
|
|
60
|
+
"performance",
|
|
61
|
+
"flamegraph",
|
|
62
|
+
"trace",
|
|
63
|
+
"profiler"
|
|
64
|
+
]
|
|
65
|
+
}
|