@parca/profile 0.16.443 → 0.16.445

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 (61) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/MetricsGraphStrips/AreaGraph/index.d.ts +20 -0
  3. package/dist/MetricsGraphStrips/AreaGraph/index.d.ts.map +1 -0
  4. package/dist/MetricsGraphStrips/AreaGraph/index.js +166 -0
  5. package/dist/MetricsGraphStrips/MetricsGraphStrips.stories.d.ts +17 -0
  6. package/dist/MetricsGraphStrips/MetricsGraphStrips.stories.d.ts.map +1 -0
  7. package/dist/MetricsGraphStrips/MetricsGraphStrips.stories.js +48 -0
  8. package/dist/MetricsGraphStrips/TimelineGuide/index.d.ts +10 -0
  9. package/dist/MetricsGraphStrips/TimelineGuide/index.d.ts.map +1 -0
  10. package/dist/MetricsGraphStrips/TimelineGuide/index.js +40 -0
  11. package/dist/MetricsGraphStrips/index.d.ts +13 -0
  12. package/dist/MetricsGraphStrips/index.d.ts.map +1 -0
  13. package/dist/MetricsGraphStrips/index.js +41 -0
  14. package/dist/ProfileIcicleGraph/index.d.ts.map +1 -1
  15. package/dist/ProfileIcicleGraph/index.js +3 -11
  16. package/dist/ProfileView/ColorStackLegend.d.ts.map +1 -0
  17. package/dist/{ProfileIcicleGraph/IcicleGraphArrow → ProfileView}/ColorStackLegend.js +2 -2
  18. package/dist/ProfileView/VisualizationPanel.d.ts +4 -1
  19. package/dist/ProfileView/VisualizationPanel.d.ts.map +1 -1
  20. package/dist/ProfileView/VisualizationPanel.js +4 -6
  21. package/dist/ProfileView/index.d.ts.map +1 -1
  22. package/dist/ProfileView/index.js +33 -10
  23. package/dist/ProfileViewWithData.d.ts.map +1 -1
  24. package/dist/ProfileViewWithData.js +1 -3
  25. package/dist/Table/index.d.ts +2 -29
  26. package/dist/Table/index.d.ts.map +1 -1
  27. package/dist/Table/index.js +52 -159
  28. package/dist/Table/utils/functions.d.ts +49 -0
  29. package/dist/Table/utils/functions.d.ts.map +1 -0
  30. package/dist/Table/utils/functions.js +181 -0
  31. package/dist/components/ActionButtons/GroupByDropdown.js +1 -1
  32. package/dist/components/ActionButtons/SortByDropdown.d.ts +3 -0
  33. package/dist/components/ActionButtons/SortByDropdown.d.ts.map +1 -0
  34. package/dist/components/ActionButtons/SortByDropdown.js +49 -0
  35. package/dist/components/VisualisationToolbar/MultiLevelDropdown.d.ts.map +1 -1
  36. package/dist/components/VisualisationToolbar/MultiLevelDropdown.js +3 -27
  37. package/dist/components/VisualisationToolbar/TableColumnsDropdown.d.ts.map +1 -1
  38. package/dist/components/VisualisationToolbar/TableColumnsDropdown.js +3 -1
  39. package/dist/components/VisualisationToolbar/index.d.ts +11 -0
  40. package/dist/components/VisualisationToolbar/index.d.ts.map +1 -1
  41. package/dist/components/VisualisationToolbar/index.js +13 -6
  42. package/dist/styles.css +1 -1
  43. package/package.json +4 -3
  44. package/src/MetricsGraphStrips/AreaGraph/index.tsx +321 -0
  45. package/src/MetricsGraphStrips/MetricsGraphStrips.stories.tsx +57 -0
  46. package/src/MetricsGraphStrips/TimelineGuide/index.tsx +111 -0
  47. package/src/MetricsGraphStrips/index.tsx +93 -0
  48. package/src/ProfileIcicleGraph/index.tsx +2 -18
  49. package/src/{ProfileIcicleGraph/IcicleGraphArrow → ProfileView}/ColorStackLegend.tsx +2 -2
  50. package/src/ProfileView/VisualizationPanel.tsx +13 -10
  51. package/src/ProfileView/index.tsx +59 -9
  52. package/src/ProfileViewWithData.tsx +1 -3
  53. package/src/Table/index.tsx +121 -263
  54. package/src/Table/utils/functions.ts +284 -0
  55. package/src/components/ActionButtons/GroupByDropdown.tsx +1 -1
  56. package/src/components/ActionButtons/SortByDropdown.tsx +84 -0
  57. package/src/components/VisualisationToolbar/MultiLevelDropdown.tsx +7 -30
  58. package/src/components/VisualisationToolbar/TableColumnsDropdown.tsx +3 -1
  59. package/src/components/VisualisationToolbar/index.tsx +103 -58
  60. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.d.ts.map +0 -1
  61. /package/dist/{ProfileIcicleGraph/IcicleGraphArrow → ProfileView}/ColorStackLegend.d.ts +0 -0
@@ -0,0 +1,321 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {useEffect, useMemo, useRef, useState} from 'react';
15
+
16
+ import {Icon} from '@iconify/react';
17
+ import cx from 'classnames';
18
+ import * as d3 from 'd3';
19
+
20
+ export interface DataPoint {
21
+ timestamp: number;
22
+ value: number;
23
+ }
24
+
25
+ export type NumberDuo = [number, number];
26
+
27
+ interface Props {
28
+ width: number;
29
+ height: number;
30
+ marginLeft?: number;
31
+ marginRight?: number;
32
+ marginTop?: number;
33
+ marginBottom?: number;
34
+ fill?: string;
35
+ data: DataPoint[];
36
+ selectionBounds?: NumberDuo | undefined;
37
+ setSelectionBounds: (newBounds: NumberDuo | undefined) => void;
38
+ }
39
+
40
+ const DraggingWindow = ({
41
+ dragStart,
42
+ currentX,
43
+ }: {
44
+ dragStart: number | undefined;
45
+ currentX: number | undefined;
46
+ }): JSX.Element | null => {
47
+ const start = useMemo(() => Math.min(dragStart ?? 0, currentX ?? 0), [dragStart, currentX]);
48
+ const width = useMemo(() => Math.abs((dragStart ?? 0) - (currentX ?? 0)), [dragStart, currentX]);
49
+
50
+ if (dragStart === undefined || currentX === undefined) {
51
+ return null;
52
+ }
53
+
54
+ return (
55
+ <div
56
+ style={{height: '100%', width, left: start}}
57
+ className={cx(
58
+ 'bg-gray-500 absolute top-0 opacity-50 border-x-2 border-gray-900 dark:border-gray-100'
59
+ )}
60
+ ></div>
61
+ );
62
+ };
63
+
64
+ const ZoomWindow = ({
65
+ zoomWindow,
66
+ width,
67
+ onZoomWindowChange,
68
+ setIsHoveringDragHandle,
69
+ }: {
70
+ zoomWindow?: NumberDuo;
71
+ width: number;
72
+ onZoomWindowChange: (newWindow: NumberDuo) => void;
73
+ setIsHoveringDragHandle: (arg: boolean) => void;
74
+ }): JSX.Element | null => {
75
+ const windowStartHandleRef = useRef<HTMLDivElement>(null);
76
+ const windowEndHandleRef = useRef<HTMLDivElement>(null);
77
+ const [zoomWindowState, setZoomWindowState] = useState<NumberDuo | undefined>(zoomWindow);
78
+ const [dragginStart, setDraggingStart] = useState(false);
79
+ const [draggingEnd, setDraggingEnd] = useState(false);
80
+
81
+ useEffect(() => {
82
+ if (
83
+ zoomWindow === undefined ||
84
+ zoomWindowState === undefined ||
85
+ zoomWindow[0] !== zoomWindowState[0] ||
86
+ zoomWindow[1] !== zoomWindowState[1]
87
+ ) {
88
+ setZoomWindowState(zoomWindow);
89
+ }
90
+
91
+ // eslint-disable-next-line react-hooks/exhaustive-deps
92
+ }, [zoomWindow]);
93
+
94
+ if (zoomWindowState === undefined) {
95
+ return null;
96
+ }
97
+ const beforeStart = 0;
98
+ const beforeWidth = zoomWindowState[0];
99
+ const afterStart = zoomWindowState[1];
100
+ const afterWidth = width - zoomWindowState[1];
101
+
102
+ return (
103
+ <div
104
+ className="absolute w-full h-full"
105
+ onMouseMove={e => {
106
+ if (dragginStart) {
107
+ const [x] = d3.pointer(e);
108
+ if (x >= afterStart - 10) {
109
+ return;
110
+ }
111
+ const newStart = Math.min(x, afterStart);
112
+ const newEnd = Math.max(x, afterStart);
113
+ setZoomWindowState([newStart, newEnd]);
114
+ }
115
+ if (draggingEnd) {
116
+ const [x] = d3.pointer(e);
117
+ if (x <= beforeWidth + 10) {
118
+ return;
119
+ }
120
+ const newStart = Math.min(x, beforeWidth);
121
+ const newEnd = Math.max(x, beforeWidth);
122
+ setZoomWindowState([newStart, newEnd]);
123
+ }
124
+ }}
125
+ onMouseLeave={() => {
126
+ setDraggingStart(false);
127
+ setDraggingEnd(false);
128
+ }}
129
+ onMouseUp={() => {
130
+ if (dragginStart) {
131
+ setDraggingStart(false);
132
+ }
133
+ if (draggingEnd) {
134
+ setDraggingEnd(false);
135
+ }
136
+ if (zoomWindowState[0] === zoomWindow?.[0] && zoomWindowState[1] === zoomWindow?.[1]) {
137
+ return;
138
+ }
139
+ onZoomWindowChange(zoomWindowState);
140
+ setZoomWindowState(undefined);
141
+ }}
142
+ >
143
+ <div
144
+ style={{height: '100%', width: beforeWidth, left: beforeStart}}
145
+ className={cx(
146
+ 'bg-gray-500/50 absolute top-0 border-r-2 border-gray-900 dark:border-gray-100 z-20'
147
+ )}
148
+ >
149
+ <div
150
+ className="w-3 h-4 absolute top-0 right-[-7px] rounded-b bg-gray-200 cursor-ew-resize flex justify-center"
151
+ onMouseDown={e => {
152
+ setDraggingStart(true);
153
+ e.stopPropagation();
154
+ e.preventDefault();
155
+ }}
156
+ ref={windowStartHandleRef}
157
+ onMouseEnter={() => {
158
+ setIsHoveringDragHandle(true);
159
+ }}
160
+ onMouseLeave={() => {
161
+ setIsHoveringDragHandle(false);
162
+ }}
163
+ >
164
+ <Icon icon="si:drag-handle-line" className="rotate-90" fontSize={16} />
165
+ </div>
166
+ </div>
167
+
168
+ <div
169
+ style={{height: '100%', width: afterWidth, left: afterStart}}
170
+ className={cx(
171
+ 'bg-gray-500/50 absolute top-0 border-l-2 border-gray-900 dark:border-gray-100'
172
+ )}
173
+ >
174
+ <div
175
+ className="w-3 h-4 absolute top-0 rounded-b bg-gray-200 cursor-ew-resize flex justify-center left-[-7px]"
176
+ onMouseDown={e => {
177
+ setDraggingEnd(true);
178
+ e.stopPropagation();
179
+ e.preventDefault();
180
+ }}
181
+ ref={windowEndHandleRef}
182
+ onMouseEnter={() => {
183
+ setIsHoveringDragHandle(true);
184
+ }}
185
+ onMouseLeave={() => {
186
+ setIsHoveringDragHandle(false);
187
+ }}
188
+ >
189
+ <Icon icon="si:drag-handle-line" className="rotate-90" fontSize={16} />
190
+ </div>
191
+ </div>
192
+ </div>
193
+ );
194
+ };
195
+
196
+ export const AreaGraph = ({
197
+ data,
198
+ height,
199
+ width,
200
+ marginLeft = 0,
201
+ marginRight = 0,
202
+ marginBottom = 0,
203
+ marginTop = 0,
204
+ fill = 'gray',
205
+ selectionBounds,
206
+ setSelectionBounds,
207
+ }: Props): JSX.Element => {
208
+ const [mousePosition, setMousePosition] = useState<NumberDuo | undefined>(undefined);
209
+ const [dragStart, setDragStart] = useState<number | undefined>(undefined);
210
+ const [isHoveringDragHandle, setIsHoveringDragHandle] = useState(false);
211
+ const isDragging = dragStart !== undefined;
212
+
213
+ // Declare the x (horizontal position) scale.
214
+ const x = d3.scaleUtc(d3.extent(data, d => d.timestamp) as NumberDuo, [
215
+ marginLeft,
216
+ width - marginRight,
217
+ ]);
218
+
219
+ // Declare the y (vertical position) scale.
220
+ const y = d3.scaleLinear(
221
+ [0, d3.max(data, d => d.value) as number],
222
+ [height - marginBottom, marginTop]
223
+ );
224
+ const area = d3
225
+ .area<DataPoint>()
226
+ .curve(d3.curveMonotoneX)
227
+ .x(d => x(d.timestamp))
228
+ .y0(y(0))
229
+ .y1(d => y(d.value));
230
+
231
+ const zoomWindow: NumberDuo | undefined = useMemo(() => {
232
+ if (selectionBounds === undefined) {
233
+ return undefined;
234
+ }
235
+ return [x(selectionBounds[0]), x(selectionBounds[1])];
236
+
237
+ // eslint-disable-next-line react-hooks/exhaustive-deps
238
+ }, [selectionBounds]);
239
+
240
+ const setSelectionBoundsWithScaling = ([startPx, endPx]: NumberDuo): void => {
241
+ setSelectionBounds([x.invert(startPx).getTime(), x.invert(endPx).getTime()]);
242
+ };
243
+
244
+ return (
245
+ <div
246
+ style={{height, width}}
247
+ onMouseMove={e => {
248
+ const [x, y] = d3.pointer(e);
249
+ setMousePosition([x, y]);
250
+ }}
251
+ onMouseLeave={() => {
252
+ setMousePosition(undefined);
253
+ setDragStart(undefined);
254
+ }}
255
+ onMouseDown={e => {
256
+ // only left mouse button
257
+ if (e.button !== 0) {
258
+ return;
259
+ }
260
+
261
+ // X/Y coordinate array relative to svg
262
+ const rel = d3.pointer(e);
263
+
264
+ const xCoordinate = rel[0];
265
+ const xCoordinateWithoutMargin = xCoordinate - marginLeft;
266
+ if (xCoordinateWithoutMargin >= 0) {
267
+ setDragStart(xCoordinateWithoutMargin);
268
+ }
269
+
270
+ e.stopPropagation();
271
+ e.preventDefault();
272
+ }}
273
+ onMouseUp={e => {
274
+ if (dragStart === undefined) {
275
+ return;
276
+ }
277
+
278
+ const rel = d3.pointer(e);
279
+ const xCoordinate = rel[0];
280
+ const xCoordinateWithoutMargin = xCoordinate - marginLeft;
281
+ if (xCoordinateWithoutMargin >= 0 && dragStart !== xCoordinateWithoutMargin) {
282
+ const start = Math.min(dragStart, xCoordinateWithoutMargin);
283
+ const end = Math.max(dragStart, xCoordinateWithoutMargin);
284
+ setSelectionBoundsWithScaling([start, end]);
285
+ }
286
+ setDragStart(undefined);
287
+ }}
288
+ className="relative"
289
+ >
290
+ {/* onHover guide, only visible when hovering and not dragging and not having an active zoom window */}
291
+ <div
292
+ style={{height, width: 2, left: mousePosition?.[0] ?? -1}}
293
+ className={cx('bg-gray-700/75 dark:bg-gray-200/75 absolute top-0', {
294
+ hidden: mousePosition === undefined || isDragging || isHoveringDragHandle,
295
+ })}
296
+ ></div>
297
+
298
+ {/* drag guide, only visible when dragging */}
299
+ <DraggingWindow dragStart={dragStart} currentX={mousePosition?.[0]} />
300
+
301
+ {/* zoom window */}
302
+ <ZoomWindow
303
+ zoomWindow={zoomWindow}
304
+ width={width}
305
+ onZoomWindowChange={setSelectionBoundsWithScaling}
306
+ setIsHoveringDragHandle={setIsHoveringDragHandle}
307
+ />
308
+
309
+ {/* Inactive indicator */}
310
+ <div
311
+ className={cx('absolute top-0 left-0 w-full h-full bg-gray-900/50 dark:bg-gray-200/50', {
312
+ hidden: isDragging || selectionBounds !== undefined,
313
+ })}
314
+ ></div>
315
+
316
+ <svg style={{width: '100%', height: '100%'}}>
317
+ <path fill={fill} d={area(data) as string} className="opacity-80" />
318
+ </svg>
319
+ </div>
320
+ );
321
+ };
@@ -0,0 +1,57 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ // eslint-disable-next-line import/named
15
+ import {useArgs} from '@storybook/preview-api';
16
+ // eslint-disable-next-line import/named
17
+ import {Meta} from '@storybook/react';
18
+
19
+ import {DataPoint, NumberDuo} from './AreaGraph';
20
+ import {MetricsGraphStrips} from './index';
21
+
22
+ const mockData: DataPoint[][] = [[], [], []];
23
+
24
+ for (let i = 0; i < 200; i++) {
25
+ for (let j = 0; j < mockData.length; j++) {
26
+ mockData[j].push({
27
+ timestamp: 1731326092000 + i * 100,
28
+ value: Math.floor(Math.random() * 100),
29
+ });
30
+ }
31
+ }
32
+ const meta: Meta = {
33
+ title: 'components/MetricsGraphStrips',
34
+ component: MetricsGraphStrips,
35
+ };
36
+ export default meta;
37
+
38
+ export const ThreeCPUStrips = {
39
+ args: {
40
+ cpus: Array.from(mockData, (_, i) => `CPU ${i + 1}`),
41
+ data: mockData,
42
+ selectedTimeline: {index: 1, bounds: [mockData[0][25].timestamp, mockData[0][100].timestamp]},
43
+ onSelectedTimeline: (index: number, bounds: NumberDuo): void => {
44
+ console.log('onSelectedTimeline', index, bounds);
45
+ },
46
+ },
47
+ render: function Component(args: any): JSX.Element {
48
+ const [, setArgs] = useArgs();
49
+
50
+ const onSelectedTimeline = (index: number, bounds: NumberDuo): void => {
51
+ args.onSelectedTimeline(index, bounds);
52
+ setArgs({...args, selectedTimeline: {index, bounds}});
53
+ };
54
+
55
+ return <MetricsGraphStrips {...args} onSelectedTimeline={onSelectedTimeline} />;
56
+ },
57
+ };
@@ -0,0 +1,111 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {Fragment, useMemo} from 'react';
15
+
16
+ import * as d3 from 'd3';
17
+
18
+ import {DataPoint, NumberDuo} from '../AreaGraph';
19
+
20
+ interface Props {
21
+ width: number;
22
+ height: number;
23
+ margin: number;
24
+ data: DataPoint[][];
25
+ }
26
+
27
+ const alignBeforeAxisCorrection = (val: number): number => {
28
+ if (val < 10000) {
29
+ return -24;
30
+ }
31
+ if (val < 100000) {
32
+ return -28;
33
+ }
34
+
35
+ return 0;
36
+ };
37
+
38
+ export const TimelineGuide = ({data, width, height, margin}: Props): JSX.Element => {
39
+ const bounds = useMemo(() => {
40
+ const bounds: NumberDuo = [Infinity, -Infinity];
41
+ data.forEach(cpuData => {
42
+ cpuData.forEach(dataPoint => {
43
+ bounds[0] = Math.min(bounds[0], dataPoint.timestamp);
44
+ bounds[1] = Math.max(bounds[1], dataPoint.timestamp);
45
+ });
46
+ });
47
+ return [0, bounds[1] - bounds[0]];
48
+ }, [data]);
49
+
50
+ const xScale = d3.scaleLinear().domain(bounds).range([0, width]);
51
+
52
+ return (
53
+ <div className="relative h-4">
54
+ <div className="absolute" style={{width, height}}>
55
+ <svg style={{width: '100%', height: '100%'}}>
56
+ <g
57
+ className="x axis"
58
+ fill="none"
59
+ fontSize="10"
60
+ textAnchor="middle"
61
+ transform={`translate(0,${height - margin})`}
62
+ >
63
+ {xScale.ticks().map((d, i) => (
64
+ <Fragment key={`${i.toString()}-${d.toString()}`}>
65
+ <g
66
+ key={`tick-${i}`}
67
+ className="tick"
68
+ /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
69
+ transform={`translate(${xScale(d) + alignBeforeAxisCorrection(d)}, ${-height})`}
70
+ >
71
+ {/* <line y2={6} className="stroke-gray-300 dark:stroke-gray-500" /> */}
72
+ <text fill="currentColor" dy=".71em" y={9}>
73
+ {d} ms
74
+ </text>
75
+ </g>
76
+ <g key={`grid-${i}`}>
77
+ <line
78
+ className="stroke-gray-300 dark:stroke-gray-500"
79
+ x1={xScale(d)}
80
+ x2={xScale(d)}
81
+ y1={0}
82
+ y2={-height + margin}
83
+ />
84
+ </g>
85
+ </Fragment>
86
+ ))}
87
+ <line
88
+ className="stroke-gray-300 dark:stroke-gray-500"
89
+ x1={0}
90
+ x2={width}
91
+ y1={-height + 1}
92
+ y2={-height + 1}
93
+ />
94
+ <line
95
+ className="stroke-gray-300 dark:stroke-gray-500"
96
+ x1={0}
97
+ x2={width}
98
+ y1={-height + 20}
99
+ y2={-height + 20}
100
+ />
101
+ {/* <g transform={`translate(${(width - 2.5 * margin) / 2}, ${margin / 2})`}>
102
+ <text fill="currentColor" dy=".71em" y={5} className="text-sm">
103
+ Time
104
+ </text>
105
+ </g> */}
106
+ </g>
107
+ </svg>
108
+ </div>
109
+ </div>
110
+ );
111
+ };
@@ -0,0 +1,93 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {useState} from 'react';
15
+
16
+ import {Icon} from '@iconify/react';
17
+ import * as d3 from 'd3';
18
+
19
+ import {AreaGraph, DataPoint, NumberDuo} from './AreaGraph';
20
+ import {TimelineGuide} from './TimelineGuide';
21
+
22
+ interface Props {
23
+ cpus: string[];
24
+ data: DataPoint[][];
25
+ selectedTimeline?: {
26
+ index: number;
27
+ bounds: NumberDuo;
28
+ };
29
+ onSelectedTimeline: (index: number, bounds: NumberDuo | undefined) => void;
30
+ }
31
+
32
+ const getTimelineGuideHeight = (cpus: string[], collapsedIndices: number[]): number => {
33
+ return 56 * (cpus.length - collapsedIndices.length) + 20 * collapsedIndices.length + 24;
34
+ };
35
+
36
+ export const MetricsGraphStrips = ({
37
+ cpus,
38
+ data,
39
+ selectedTimeline,
40
+ onSelectedTimeline,
41
+ }: Props): JSX.Element => {
42
+ const [collapsedIndices, setCollapsedIndices] = useState<number[]>([]);
43
+
44
+ // @ts-expect-error
45
+ const color = d3.scaleOrdinal(d3.schemeObservable10);
46
+
47
+ return (
48
+ <div className="flex flex-col gap-1 relative">
49
+ <TimelineGuide
50
+ data={data}
51
+ width={1468}
52
+ height={getTimelineGuideHeight(cpus, collapsedIndices)}
53
+ margin={1}
54
+ />
55
+ {cpus.map((cpu, i) => {
56
+ const isCollapsed = collapsedIndices.includes(i);
57
+ return (
58
+ <div className="relative min-h-5" key={cpu}>
59
+ <div
60
+ className="text-xs absolute top-0 left-0 flex gap-[2px] items-center bg-white/50 px-1 rounded-sm cursor-pointer z-30"
61
+ onClick={() => {
62
+ const newCollapsedIndices = [...collapsedIndices];
63
+ if (collapsedIndices.includes(i)) {
64
+ newCollapsedIndices.splice(newCollapsedIndices.indexOf(i), 1);
65
+ } else {
66
+ newCollapsedIndices.push(i);
67
+ }
68
+ setCollapsedIndices(newCollapsedIndices);
69
+ }}
70
+ >
71
+ <Icon icon={isCollapsed ? 'bxs:right-arrow' : 'bxs:down-arrow'} />
72
+ {cpu}
73
+ </div>
74
+ {!isCollapsed ? (
75
+ <AreaGraph
76
+ data={data[i]}
77
+ height={56}
78
+ width={1468}
79
+ fill={color(i.toString()) as string}
80
+ selectionBounds={
81
+ selectedTimeline?.index === i ? selectedTimeline.bounds : undefined
82
+ }
83
+ setSelectionBounds={bounds => {
84
+ onSelectedTimeline(i, bounds);
85
+ }}
86
+ />
87
+ ) : null}
88
+ </div>
89
+ );
90
+ })}
91
+ </div>
92
+ );
93
+ };
@@ -13,20 +13,18 @@
13
13
 
14
14
  import React, {useEffect, useMemo, useState} from 'react';
15
15
 
16
- import {Table, tableFromIPC} from 'apache-arrow';
17
16
  import {AnimatePresence, motion} from 'framer-motion';
18
17
 
19
18
  import {Flamegraph, FlamegraphArrow} from '@parca/client';
20
19
  import {IcicleGraphSkeleton, useParcaContext, useURLState} from '@parca/components';
21
20
  import {ProfileType} from '@parca/parser';
22
- import {capitalizeOnlyFirstLetter, divide, selectQueryParam} from '@parca/utilities';
21
+ import {capitalizeOnlyFirstLetter, divide} from '@parca/utilities';
23
22
 
24
23
  import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
25
24
  import DiffLegend from '../components/DiffLegend';
26
25
  import {IcicleGraph} from './IcicleGraph';
27
26
  import {FIELD_FUNCTION_NAME, IcicleGraphArrow} from './IcicleGraphArrow';
28
- import ColorStackLegend from './IcicleGraphArrow/ColorStackLegend';
29
- import useMappingList, {useFilenamesList} from './IcicleGraphArrow/useMappingList';
27
+ import useMappingList from './IcicleGraphArrow/useMappingList';
30
28
 
31
29
  const numberFormatter = new Intl.NumberFormat('en-US');
32
30
 
@@ -70,14 +68,8 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
70
68
  const {onError, authenticationErrorMessage, isDarkMode} = useParcaContext();
71
69
  const {compareMode} = useProfileViewContext();
72
70
  const [isLoading, setIsLoading] = useState<boolean>(true);
73
- const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
74
-
75
- const table: Table<any> | null = useMemo(() => {
76
- return arrow !== undefined ? tableFromIPC(arrow.record) : null;
77
- }, [arrow]);
78
71
 
79
72
  const mappingsList = useMappingList(metadataMappingFiles);
80
- const filenamesList = useFilenamesList(table);
81
73
 
82
74
  const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState('sort_by');
83
75
  const [colorBy, setColorBy] = useURLState('color_by');
@@ -89,7 +81,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
89
81
  const [compareAbsolute = compareAbsoluteDefault] = useURLState('compare_absolute');
90
82
  const isCompareAbsolute = compareAbsolute === 'true';
91
83
 
92
- const colorByValue = colorBy === undefined || colorBy === '' ? 'binary' : (colorBy as string);
93
84
  const mappingsListCount = useMemo(
94
85
  () => mappingsList.filter(m => m !== '').length,
95
86
  [mappingsList]
@@ -231,13 +222,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
231
222
  transition={{duration: 0.5}}
232
223
  >
233
224
  {compareMode ? <DiffLegend /> : null}
234
- {isColorStackLegendEnabled && (
235
- <ColorStackLegend
236
- compareMode={compareMode}
237
- mappings={colorByValue === 'binary' ? mappingsList : filenamesList}
238
- loading={isLoading}
239
- />
240
- )}
241
225
  <div className="min-h-48" id="h-icicle-graph">
242
226
  <>{icicleGraph}</>
243
227
  </div>
@@ -20,8 +20,8 @@ import {useURLState} from '@parca/components';
20
20
  import {USER_PREFERENCES, useCurrentColorProfile, useUserPreference} from '@parca/hooks';
21
21
  import {EVERYTHING_ELSE, selectDarkMode, useAppSelector} from '@parca/store';
22
22
 
23
- import {getMappingColors} from '.';
24
- import useMappingList from './useMappingList';
23
+ import {getMappingColors} from '../ProfileIcicleGraph/IcicleGraphArrow/';
24
+ import useMappingList from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';
25
25
 
26
26
  interface Props {
27
27
  mappings?: string[];