@kylincloud/flamegraph 0.36.2 → 0.36.3

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.
@@ -1 +1 @@
1
- {"version":3,"file":"createFlamegraphRenderWorker.d.ts","sourceRoot":"","sources":["../../src/workers/createFlamegraphRenderWorker.ts"],"names":[],"mappings":"AAAA,wBAAgB,4BAA4B,IAAI,MAAM,CAsBrD"}
1
+ {"version":3,"file":"createFlamegraphRenderWorker.d.ts","sourceRoot":"","sources":["../../src/workers/createFlamegraphRenderWorker.ts"],"names":[],"mappings":"AAAA,wBAAgB,4BAA4B,IAAI,MAAM,CAsCrD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kylincloud/flamegraph",
3
- "version": "0.36.2",
3
+ "version": "0.36.3",
4
4
  "description": "KylinCloud flamegraph renderer (Pyroscope-based)",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.node.cjs.js",
@@ -93,6 +93,16 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
93
93
  typeof window !== 'undefined' &&
94
94
  typeof OffscreenCanvas !== 'undefined' &&
95
95
  'transferControlToOffscreen' in HTMLCanvasElement.prototype;
96
+ const [enableWorker, setEnableWorker] = React.useState(useRenderWorker);
97
+ const workerStatusLoggedRef = useRef(false);
98
+
99
+ React.useEffect(() => {
100
+ if (typeof window === 'undefined' || workerStatusLoggedRef.current) return;
101
+ workerStatusLoggedRef.current = true;
102
+ if (!useRenderWorker) {
103
+ console.info('[flamegraph] OffscreenCanvas unavailable, using main thread renderer');
104
+ }
105
+ }, [useRenderWorker]);
96
106
 
97
107
  // ====== 新增:提取 canvas 渲染需要的 i18n messages ======
98
108
  const canvasMessages = useMemo(
@@ -153,10 +163,24 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
153
163
  );
154
164
 
155
165
  useEffect(() => {
156
- if (!useRenderWorker) {
166
+ if (!enableWorker) {
157
167
  return () => {};
158
168
  }
159
- const worker = createFlamegraphRenderWorker();
169
+ let worker: Worker;
170
+ try {
171
+ worker = createFlamegraphRenderWorker();
172
+ } catch (err) {
173
+ console.warn('[flamegraph] render worker init failed, fallback to main thread', err);
174
+ setEnableWorker(false);
175
+ return () => {};
176
+ }
177
+ console.info('[flamegraph] render worker created');
178
+ worker.addEventListener('error', (event) => {
179
+ console.error('[flamegraph] render worker error', event);
180
+ });
181
+ worker.addEventListener('messageerror', (event) => {
182
+ console.error('[flamegraph] render worker message error', event);
183
+ });
160
184
  renderWorkerRef.current = worker;
161
185
  setTimeout(() => {
162
186
  renderRectCanvas();
@@ -169,7 +193,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
169
193
  textOffscreenRef.current = null;
170
194
  workerReadyRef.current = { rect: false, text: false };
171
195
  };
172
- }, [useRenderWorker]);
196
+ }, [enableWorker]);
173
197
 
174
198
  useResizeObserver(canvasRef, () => {
175
199
  if (flamegraph) {
@@ -400,7 +424,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
400
424
  };
401
425
 
402
426
  const ensureRenderWorkerCanvases = useCallback(() => {
403
- if (!useRenderWorker || !renderWorkerRef.current) {
427
+ if (!enableWorker || !renderWorkerRef.current) {
404
428
  return false;
405
429
  }
406
430
  const worker = renderWorkerRef.current;
@@ -415,6 +439,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
415
439
  );
416
440
  workerReadyRef.current.rect = true;
417
441
  } catch (err) {
442
+ console.error('[flamegraph] offscreen init failed (rect)', err);
418
443
  }
419
444
  }
420
445
 
@@ -428,11 +453,12 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
428
453
  );
429
454
  workerReadyRef.current.text = true;
430
455
  } catch (err) {
456
+ console.error('[flamegraph] offscreen init failed (text)', err);
431
457
  }
432
458
  }
433
459
 
434
460
  return true;
435
- }, [useRenderWorker]);
461
+ }, [enableWorker]);
436
462
 
437
463
  // ====== 修改:添加 canvasMessages 依赖 ======
438
464
  React.useEffect(() => {
@@ -463,7 +489,7 @@ export default function FlameGraphComponent(props: FlamegraphProps) {
463
489
  kind: 'rect' | 'text',
464
490
  options: { renderRects?: boolean; renderText?: boolean }
465
491
  ) => {
466
- if (!useRenderWorker) {
492
+ if (!enableWorker) {
467
493
  return false;
468
494
  }
469
495
  if (!renderWorkerRef.current) {
@@ -11,7 +11,13 @@ export function createFlamegraphRenderWorker(): Worker {
11
11
  '../../@kylincloud/flamegraph/dist/flamegraphRenderWorker.js',
12
12
  metaUrl
13
13
  );
14
- return new Worker(viteDepsWorkerUrl, { type: 'module' });
14
+ try {
15
+ const worker = new Worker(viteDepsWorkerUrl, { type: 'module' });
16
+ return worker;
17
+ } catch (err) {
18
+ console.error('[flamegraph] worker create failed (vite deps)', err);
19
+ throw err;
20
+ }
15
21
  }
16
22
 
17
23
  const workerUrl = isFromSrc
@@ -19,5 +25,15 @@ export function createFlamegraphRenderWorker(): Worker {
19
25
  : isFromDist
20
26
  ? new URL('./flamegraphRenderWorker.js', metaUrl)
21
27
  : new URL('./flamegraphRenderWorker.js', metaUrl);
22
- return new Worker(workerUrl, { type: 'module' });
28
+ try {
29
+ const worker = new Worker(workerUrl, { type: 'module' });
30
+ return worker;
31
+ } catch (err) {
32
+ console.error('[flamegraph] worker create failed', err, {
33
+ workerUrl: workerUrl.href,
34
+ isFromSrc,
35
+ isFromDist,
36
+ });
37
+ throw err;
38
+ }
23
39
  }
@@ -83,6 +83,14 @@ const state: {
83
83
  },
84
84
  };
85
85
 
86
+ const logWorkerError = (err: any, context: string) => {
87
+ try {
88
+ console.error('[flamegraph-worker] error', context, err);
89
+ } catch {
90
+ // ignore
91
+ }
92
+ };
93
+
86
94
  const buildPalette = (payload: SerializablePalette): FlamegraphPalette => ({
87
95
  name: payload.name,
88
96
  goodColor: Color.rgb(...payload.goodColor),
@@ -100,53 +108,63 @@ const RECT_BUDGET_MS = 12;
100
108
  const TEXT_BUDGET_MS = 8;
101
109
 
102
110
  const runChunk = (kind: 'rect' | 'text', token: number) => {
103
- const renderState = state.renderState[kind];
104
- const payload = renderState.payload;
105
- if (!payload || renderState.token !== token) {
106
- return;
107
- }
108
- const canvas = kind === 'rect' ? state.rectCanvas : state.textCanvas;
109
- if (!canvas) {
110
- renderState.running = false;
111
- return;
112
- }
113
- if (!renderState.flamegraph) {
114
- renderState.flamegraph = new Flamegraph(
115
- payload.flamebearer,
116
- canvas,
117
- toMaybe(payload.focusedNode),
118
- payload.fitMode,
119
- payload.highlightQuery,
120
- toMaybe(payload.zoom),
121
- buildPalette(payload.palette),
122
- payload.messages
123
- );
124
- }
111
+ try {
112
+ const renderState = state.renderState[kind];
113
+ const payload = renderState.payload;
114
+ if (!payload || renderState.token !== token) {
115
+ return;
116
+ }
117
+ const canvas = kind === 'rect' ? state.rectCanvas : state.textCanvas;
118
+ if (!canvas) {
119
+ renderState.running = false;
120
+ return;
121
+ }
122
+ if (!renderState.flamegraph) {
123
+ renderState.flamegraph = new Flamegraph(
124
+ payload.flamebearer,
125
+ canvas,
126
+ toMaybe(payload.focusedNode),
127
+ payload.fitMode,
128
+ payload.highlightQuery,
129
+ toMaybe(payload.zoom),
130
+ buildPalette(payload.palette),
131
+ payload.messages
132
+ );
133
+ }
125
134
 
126
- const result = renderState.flamegraph.render({
127
- renderRects: payload.renderRects,
128
- renderText: payload.renderText,
129
- timeBudgetMs: kind === 'rect' ? RECT_BUDGET_MS : TEXT_BUDGET_MS,
130
- startI: renderState.nextI,
131
- startJ: renderState.nextJ,
132
- skipCanvasResize: !renderState.firstChunk,
133
- skipDprScale: !renderState.firstChunk,
134
- devicePixelRatio: payload.devicePixelRatio,
135
- });
136
-
137
- renderState.firstChunk = false;
138
- if (!result.done) {
139
- renderState.nextI = result.nextI;
140
- renderState.nextJ = result.nextJ;
141
- setTimeout(() => runChunk(kind, token), 0);
142
- return;
143
- }
135
+ const result = renderState.flamegraph.render({
136
+ renderRects: payload.renderRects,
137
+ renderText: payload.renderText,
138
+ timeBudgetMs: kind === 'rect' ? RECT_BUDGET_MS : TEXT_BUDGET_MS,
139
+ startI: renderState.nextI,
140
+ startJ: renderState.nextJ,
141
+ skipCanvasResize: !renderState.firstChunk,
142
+ skipDprScale: !renderState.firstChunk,
143
+ devicePixelRatio: payload.devicePixelRatio,
144
+ });
145
+
146
+ renderState.firstChunk = false;
147
+ if (!result.done) {
148
+ renderState.nextI = result.nextI;
149
+ renderState.nextJ = result.nextJ;
150
+ setTimeout(() => runChunk(kind, token), 0);
151
+ return;
152
+ }
144
153
 
145
- renderState.running = false;
146
- renderState.nextI = 0;
147
- renderState.nextJ = 0;
148
- renderState.firstChunk = true;
149
- renderState.flamegraph = null;
154
+ renderState.running = false;
155
+ renderState.nextI = 0;
156
+ renderState.nextJ = 0;
157
+ renderState.firstChunk = true;
158
+ renderState.flamegraph = null;
159
+ } catch (err) {
160
+ console.error('[flamegraph-worker] render error', err);
161
+ const renderState = state.renderState[kind];
162
+ renderState.running = false;
163
+ renderState.nextI = 0;
164
+ renderState.nextJ = 0;
165
+ renderState.firstChunk = true;
166
+ renderState.flamegraph = null;
167
+ }
150
168
  };
151
169
 
152
170
  const renderFlamegraph = (payload: FlamegraphRenderRequest['payload']) => {
@@ -190,3 +208,15 @@ self.onmessage = (event: MessageEvent<FlamegraphWorkerMessage>) => {
190
208
  renderFlamegraph(msg.payload);
191
209
  }
192
210
  };
211
+
212
+ self.onerror = (event) => {
213
+ logWorkerError(event, 'self.error');
214
+ };
215
+
216
+ self.onmessageerror = (event) => {
217
+ logWorkerError(event, 'self.messageerror');
218
+ };
219
+
220
+ self.addEventListener('unhandledrejection', (event: PromiseRejectionEvent) => {
221
+ logWorkerError(event?.reason, 'self.unhandledrejection');
222
+ });