@kylincloud/flamegraph 0.35.27 → 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 (65) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/dist/FlameGraph/FlameGraphComponent/DiffLegend.d.ts.map +1 -1
  3. package/dist/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.d.ts.map +1 -1
  4. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts +16 -2
  5. package/dist/FlameGraph/FlameGraphComponent/Flamegraph.d.ts.map +1 -1
  6. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts +15 -2
  7. package/dist/FlameGraph/FlameGraphComponent/Flamegraph_render.d.ts.map +1 -1
  8. package/dist/FlameGraph/FlameGraphComponent/Highlight.d.ts.map +1 -1
  9. package/dist/FlameGraph/FlameGraphComponent/index.d.ts.map +1 -1
  10. package/dist/FlameGraph/normalize.d.ts.map +1 -1
  11. package/dist/FlameGraph/uniqueness.d.ts.map +1 -1
  12. package/dist/Icons.d.ts.map +1 -1
  13. package/dist/ProfilerTable.d.ts.map +1 -1
  14. package/dist/SharedQueryInput.d.ts.map +1 -1
  15. package/dist/Toolbar.d.ts.map +1 -1
  16. package/dist/Tooltip/Tooltip.d.ts.map +1 -1
  17. package/dist/flamegraphRenderWorker.js +2 -0
  18. package/dist/flamegraphRenderWorker.js.map +1 -0
  19. package/dist/index.cjs.js +4 -4
  20. package/dist/index.cjs.js.map +1 -1
  21. package/dist/index.esm.js +4 -4
  22. package/dist/index.esm.js.map +1 -1
  23. package/dist/index.node.cjs.js +4 -4
  24. package/dist/index.node.cjs.js.map +1 -1
  25. package/dist/index.node.esm.js +4 -4
  26. package/dist/index.node.esm.js.map +1 -1
  27. package/dist/shims/Table.d.ts +15 -1
  28. package/dist/shims/Table.d.ts.map +1 -1
  29. package/dist/shims/Tooltip.d.ts.map +1 -1
  30. package/dist/workers/createFlamegraphRenderWorker.d.ts +2 -0
  31. package/dist/workers/createFlamegraphRenderWorker.d.ts.map +1 -0
  32. package/dist/workers/flamegraphRenderWorker.d.ts +2 -0
  33. package/dist/workers/flamegraphRenderWorker.d.ts.map +1 -0
  34. package/dist/workers/profilerTableWorker.d.ts +73 -0
  35. package/dist/workers/profilerTableWorker.d.ts.map +1 -0
  36. package/package.json +1 -1
  37. package/src/FlameGraph/FlameGraphComponent/DiffLegend.module.css +8 -2
  38. package/src/FlameGraph/FlameGraphComponent/DiffLegend.tsx +12 -1
  39. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.module.css +93 -10
  40. package/src/FlameGraph/FlameGraphComponent/DiffLegendPaletteDropdown.tsx +9 -4
  41. package/src/FlameGraph/FlameGraphComponent/Flamegraph.ts +33 -8
  42. package/src/FlameGraph/FlameGraphComponent/Flamegraph_render.ts +289 -85
  43. package/src/FlameGraph/FlameGraphComponent/Highlight.tsx +43 -17
  44. package/src/FlameGraph/FlameGraphComponent/index.tsx +208 -57
  45. package/src/FlameGraph/FlameGraphComponent/styles.module.scss +8 -0
  46. package/src/FlameGraph/normalize.ts +9 -7
  47. package/src/FlameGraph/uniqueness.ts +69 -59
  48. package/src/Icons.tsx +18 -9
  49. package/src/ProfilerTable.tsx +463 -33
  50. package/src/SharedQueryInput.module.scss +50 -0
  51. package/src/SharedQueryInput.tsx +18 -3
  52. package/src/Toolbar.module.scss +90 -0
  53. package/src/Toolbar.tsx +30 -16
  54. package/src/Tooltip/Tooltip.tsx +49 -16
  55. package/src/i18n.tsx +1 -1
  56. package/src/sass/_common.scss +22 -3
  57. package/src/sass/_css-variables.scss +5 -1
  58. package/src/sass/flamegraph.scss +26 -23
  59. package/src/shims/Table.module.scss +91 -13
  60. package/src/shims/Table.tsx +202 -7
  61. package/src/shims/Tooltip.module.scss +40 -0
  62. package/src/shims/Tooltip.tsx +31 -3
  63. package/src/workers/createFlamegraphRenderWorker.ts +7 -0
  64. package/src/workers/flamegraphRenderWorker.ts +198 -0
  65. 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
+ }