@kylincloud/flamegraph 0.35.28 → 0.35.29

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.
Files changed (43) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +16 -2
  3. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
  4. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +15 -2
  5. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
  6. package/dist/FlameGraph/FlameGraphComponent/Highlight.d.ts.map +1 -1
  7. package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
  8. package/dist/FlameGraph/normalize.d.ts.map +1 -1
  9. package/dist/FlameGraph/uniqueness.d.ts.map +1 -1
  10. package/dist/ProfilerTable.d.ts.map +1 -1
  11. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  12. package/dist/flamegraphRenderWorker.js +2 -0
  13. package/dist/flamegraphRenderWorker.js.map +1 -0
  14. package/dist/index.cjs.js +4 -4
  15. package/dist/index.cjs.js.map +1 -1
  16. package/dist/index.esm.js +4 -4
  17. package/dist/index.esm.js.map +1 -1
  18. package/dist/index.node.cjs.js +4 -4
  19. package/dist/index.node.cjs.js.map +1 -1
  20. package/dist/index.node.esm.js +4 -4
  21. package/dist/index.node.esm.js.map +1 -1
  22. package/dist/shims/Table.d.ts +15 -1
  23. package/dist/shims/Table.d.ts.map +1 -1
  24. package/dist/workers/createFlamegraphRenderWorker.d.ts +2 -0
  25. package/dist/workers/createFlamegraphRenderWorker.d.ts.map +1 -0
  26. package/dist/workers/flamegraphRenderWorker.d.ts +2 -0
  27. package/dist/workers/flamegraphRenderWorker.d.ts.map +1 -0
  28. package/dist/workers/profilerTableWorker.d.ts +73 -0
  29. package/dist/workers/profilerTableWorker.d.ts.map +1 -0
  30. package/package.json +1 -1
  31. package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +33 -8
  32. package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +289 -85
  33. package/src/FlameGraph/FlameGraphComponent/Highlight.tsx +43 -17
  34. package/src/FlameGraph/FlameGraphComponent/index.tsx +150 -1
  35. package/src/FlameGraph/normalize.ts +9 -7
  36. package/src/FlameGraph/uniqueness.ts +69 -59
  37. package/src/ProfilerTable.tsx +463 -33
  38. package/src/Tooltip/Tooltip.tsx +49 -16
  39. package/src/shims/Table.module.scss +5 -0
  40. package/src/shims/Table.tsx +195 -5
  41. package/src/workers/createFlamegraphRenderWorker.ts +7 -0
  42. package/src/workers/flamegraphRenderWorker.ts +198 -0
  43. package/src/workers/profilerTableWorker.ts +368 -0
@@ -0,0 +1,368 @@
1
+ export type ProfilerTableWorkerRow =
2
+ | {
3
+ type: 'single';
4
+ name: string;
5
+ self: number;
6
+ total: number;
7
+ }
8
+ | {
9
+ type: 'double';
10
+ name: string;
11
+ totalLeft: number;
12
+ totalRght: number;
13
+ leftTicks: number;
14
+ rightTicks: number;
15
+ };
16
+
17
+ export type ProfilerTableWorkerRequest =
18
+ | {
19
+ type: 'init';
20
+ payload: {
21
+ flamebearer: {
22
+ names: string[];
23
+ levels: number[][];
24
+ format: 'single' | 'double';
25
+ leftTicks?: number;
26
+ rightTicks?: number;
27
+ };
28
+ sortBy: string;
29
+ sortByDirection: 'asc' | 'desc';
30
+ highlightQuery: string;
31
+ };
32
+ }
33
+ | {
34
+ type: 'update';
35
+ payload: {
36
+ sortBy: string;
37
+ sortByDirection: 'asc' | 'desc';
38
+ highlightQuery: string;
39
+ };
40
+ }
41
+ | {
42
+ type: 'range';
43
+ payload: {
44
+ start: number;
45
+ end: number;
46
+ requestId: number;
47
+ };
48
+ }
49
+ | {
50
+ type: 'findNode';
51
+ payload: {
52
+ name: string;
53
+ requestId: number;
54
+ };
55
+ };
56
+
57
+ export type ProfilerTableWorkerResponse =
58
+ | {
59
+ type: 'ready';
60
+ payload: {
61
+ rowCount: number;
62
+ };
63
+ }
64
+ | {
65
+ type: 'range';
66
+ payload: {
67
+ rows: ProfilerTableWorkerRow[];
68
+ start: number;
69
+ end: number;
70
+ requestId: number;
71
+ };
72
+ }
73
+ | {
74
+ type: 'findNode';
75
+ payload: {
76
+ node: { i: number; j: number } | null;
77
+ requestId: number;
78
+ };
79
+ };
80
+
81
+ export function createProfilerTableWorker(): Worker {
82
+ const workerCode = () => {
83
+ const zero = (v) => v || 0;
84
+
85
+ const singleFF = {
86
+ format: 'single',
87
+ jStep: 4,
88
+ jName: 3,
89
+ getBarTotal: (level, j) => level[j + 1],
90
+ getBarSelf: (level, j) => level[j + 2],
91
+ getBarName: (level, j) => level[j + 3],
92
+ };
93
+
94
+ const doubleFF = {
95
+ format: 'double',
96
+ jStep: 7,
97
+ jName: 6,
98
+ getBarTotal: (level, j) => level[j + 4] + level[j + 1],
99
+ getBarTotalLeft: (level, j) => level[j + 1],
100
+ getBarTotalRght: (level, j) => level[j + 4],
101
+ getBarSelf: (level, j) => level[j + 5] + level[j + 2],
102
+ getBarSelfLeft: (level, j) => level[j + 2],
103
+ getBarSelfRght: (level, j) => level[j + 5],
104
+ getBarName: (level, j) => level[j + 6],
105
+ };
106
+
107
+ const ratioToPercent = (ratio) => Math.round(10000 * ratio) / 100;
108
+ const diffPercent = (leftPercent, rightPercent) =>
109
+ ((rightPercent - leftPercent) / leftPercent) * 100;
110
+
111
+ const state = {
112
+ baseRows: [],
113
+ rows: [],
114
+ bestNodeByName: new Map(),
115
+ sortBy: 'total',
116
+ sortByDirection: 'desc',
117
+ highlightQuery: '',
118
+ format: 'single',
119
+ leftTicks: 0,
120
+ rightTicks: 0,
121
+ };
122
+
123
+ const buildBaseRows = (flamebearer) => {
124
+ const { names, levels, format } = flamebearer;
125
+ const map = new Map();
126
+ const bestNodeByName = new Map();
127
+ const leftTicks = flamebearer.leftTicks || 0;
128
+ const rightTicks = flamebearer.rightTicks || 0;
129
+
130
+ if (format === 'double') {
131
+ const ff = doubleFF;
132
+ for (let i = 0; i < levels.length; i += 1) {
133
+ const level = levels[i];
134
+ for (let j = 0; j < level.length; j += ff.jStep) {
135
+ const nameIndex = ff.getBarName(level, j);
136
+ const name = names[nameIndex] || '<empty>';
137
+
138
+ if (!map.has(name)) {
139
+ map.set(name, {
140
+ type: 'double',
141
+ name,
142
+ totalLeft: 0,
143
+ totalRght: 0,
144
+ leftTicks,
145
+ rightTicks,
146
+ });
147
+ }
148
+
149
+ const row = map.get(name);
150
+ if (!row) continue;
151
+
152
+ row.totalLeft = zero(row.totalLeft) + ff.getBarTotalLeft(level, j);
153
+ row.totalRght = zero(row.totalRght) + ff.getBarTotalRght(level, j);
154
+
155
+ const total = ff.getBarTotal(level, j);
156
+ const best = bestNodeByName.get(name);
157
+ if (!best || total > best.total) {
158
+ bestNodeByName.set(name, { i, j, total });
159
+ }
160
+ }
161
+ }
162
+ } else {
163
+ const ff = singleFF;
164
+ for (let i = 0; i < levels.length; i += 1) {
165
+ const level = levels[i];
166
+ for (let j = 0; j < level.length; j += ff.jStep) {
167
+ const nameIndex = ff.getBarName(level, j);
168
+ const name = names[nameIndex] || '<empty>';
169
+
170
+ if (!map.has(name)) {
171
+ map.set(name, {
172
+ type: 'single',
173
+ name,
174
+ self: 0,
175
+ total: 0,
176
+ });
177
+ }
178
+
179
+ const row = map.get(name);
180
+ if (!row) continue;
181
+
182
+ row.self = zero(row.self) + ff.getBarSelf(level, j);
183
+ row.total = zero(row.total) + ff.getBarTotal(level, j);
184
+
185
+ const total = ff.getBarTotal(level, j);
186
+ const best = bestNodeByName.get(name);
187
+ if (!best || total > best.total) {
188
+ bestNodeByName.set(name, { i, j, total });
189
+ }
190
+ }
191
+ }
192
+ }
193
+
194
+ return {
195
+ rows: Array.from(map.values()),
196
+ bestNodeByName,
197
+ format,
198
+ leftTicks,
199
+ rightTicks,
200
+ };
201
+ };
202
+
203
+ const applyFilterAndSort = () => {
204
+ const query = state.highlightQuery.trim().toLowerCase();
205
+ const filtered = query
206
+ ? state.baseRows.filter((row) =>
207
+ row.name.toLowerCase().includes(query)
208
+ )
209
+ : state.baseRows.slice();
210
+
211
+ const m = state.sortByDirection === 'asc' ? 1 : -1;
212
+
213
+ filtered.sort((a, b) => {
214
+ if (state.sortBy === 'name') {
215
+ return m * a.name.localeCompare(b.name);
216
+ }
217
+
218
+ switch (state.sortBy) {
219
+ case 'total':
220
+ case 'self': {
221
+ const av = a[state.sortBy] || 0;
222
+ const bv = b[state.sortBy] || 0;
223
+ return m * (av - bv);
224
+ }
225
+ case 'baseline': {
226
+ const av =
227
+ a.leftTicks > 0 ? a.totalLeft / a.leftTicks : 0;
228
+ const bv =
229
+ b.leftTicks > 0 ? b.totalLeft / b.leftTicks : 0;
230
+ return m * (av - bv);
231
+ }
232
+ case 'comparison': {
233
+ const av =
234
+ a.rightTicks > 0 ? a.totalRght / a.rightTicks : 0;
235
+ const bv =
236
+ b.rightTicks > 0 ? b.totalRght / b.rightTicks : 0;
237
+ return m * (av - bv);
238
+ }
239
+ case 'diff': {
240
+ const leftA =
241
+ a.leftTicks > 0 ? a.totalLeft / a.leftTicks : 0;
242
+ const rightA =
243
+ a.rightTicks > 0 ? a.totalRght / a.rightTicks : 0;
244
+ const leftB =
245
+ b.leftTicks > 0 ? b.totalLeft / b.leftTicks : 0;
246
+ const rightB =
247
+ b.rightTicks > 0 ? b.totalRght / b.rightTicks : 0;
248
+ const totalDiffA = diffPercent(
249
+ ratioToPercent(leftA),
250
+ ratioToPercent(rightA)
251
+ );
252
+ const totalDiffB = diffPercent(
253
+ ratioToPercent(leftB),
254
+ ratioToPercent(rightB)
255
+ );
256
+ return m * (totalDiffA - totalDiffB);
257
+ }
258
+ default:
259
+ return 0;
260
+ }
261
+ });
262
+
263
+ state.rows = filtered;
264
+ };
265
+
266
+ const handleInit = (payload) => {
267
+ const { rows, bestNodeByName, format, leftTicks, rightTicks } =
268
+ buildBaseRows(payload.flamebearer);
269
+ state.baseRows = rows;
270
+ state.bestNodeByName = bestNodeByName;
271
+ state.format = format;
272
+ state.leftTicks = leftTicks;
273
+ state.rightTicks = rightTicks;
274
+ state.sortBy = payload.sortBy;
275
+ state.sortByDirection = payload.sortByDirection;
276
+ state.highlightQuery = payload.highlightQuery || '';
277
+ applyFilterAndSort();
278
+ // eslint-disable-next-line no-console
279
+ console.debug('[profiler-table-worker] init ready', {
280
+ rowCount: state.rows.length,
281
+ sortBy: state.sortBy,
282
+ sortByDirection: state.sortByDirection,
283
+ highlightQuery: state.highlightQuery,
284
+ });
285
+ self.postMessage({
286
+ type: 'ready',
287
+ payload: { rowCount: state.rows.length },
288
+ });
289
+ };
290
+
291
+ const handleUpdate = (payload) => {
292
+ state.sortBy = payload.sortBy;
293
+ state.sortByDirection = payload.sortByDirection;
294
+ state.highlightQuery = payload.highlightQuery || '';
295
+ applyFilterAndSort();
296
+ // eslint-disable-next-line no-console
297
+ console.debug('[profiler-table-worker] update ready', {
298
+ rowCount: state.rows.length,
299
+ sortBy: state.sortBy,
300
+ sortByDirection: state.sortByDirection,
301
+ highlightQuery: state.highlightQuery,
302
+ });
303
+ self.postMessage({
304
+ type: 'ready',
305
+ payload: { rowCount: state.rows.length },
306
+ });
307
+ };
308
+
309
+ const handleRange = (payload) => {
310
+ const { start, end, requestId } = payload;
311
+ const safeStart = Math.max(0, start);
312
+ const safeEnd = Math.min(state.rows.length, end);
313
+ const rows = state.rows.slice(safeStart, safeEnd);
314
+ // eslint-disable-next-line no-console
315
+ console.debug('[profiler-table-worker] range', {
316
+ start: safeStart,
317
+ end: safeEnd,
318
+ requestId,
319
+ });
320
+ self.postMessage({
321
+ type: 'range',
322
+ payload: { rows, start: safeStart, end: safeEnd, requestId },
323
+ });
324
+ };
325
+
326
+ const handleFindNode = (payload) => {
327
+ const { name, requestId } = payload;
328
+ const best = state.bestNodeByName.get(name);
329
+ // eslint-disable-next-line no-console
330
+ console.debug('[profiler-table-worker] findNode', {
331
+ name,
332
+ found: !!best,
333
+ requestId,
334
+ });
335
+ self.postMessage({
336
+ type: 'findNode',
337
+ payload: { node: best ? { i: best.i, j: best.j } : null, requestId },
338
+ });
339
+ };
340
+
341
+ self.onmessage = (event) => {
342
+ const msg = event.data;
343
+ if (!msg || !msg.type) return;
344
+ switch (msg.type) {
345
+ case 'init':
346
+ handleInit(msg.payload);
347
+ break;
348
+ case 'update':
349
+ handleUpdate(msg.payload);
350
+ break;
351
+ case 'range':
352
+ handleRange(msg.payload);
353
+ break;
354
+ case 'findNode':
355
+ handleFindNode(msg.payload);
356
+ break;
357
+ default:
358
+ break;
359
+ }
360
+ };
361
+ };
362
+
363
+ const blob = new Blob([`(${workerCode.toString()})()`], {
364
+ type: 'text/javascript',
365
+ });
366
+
367
+ return new Worker(URL.createObjectURL(blob));
368
+ }