@parca/profile 0.19.113 → 0.19.114

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 (86) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts.map +1 -1
  3. package/dist/ProfileExplorer/ProfileExplorerSingle.js +9 -3
  4. package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.d.ts +31 -0
  5. package/dist/ProfileFlameChart/SamplesStrips/SamplesGraph/index.d.ts.map +1 -0
  6. package/dist/{MetricsGraphStrips/AreaGraph → ProfileFlameChart/SamplesStrips/SamplesGraph}/index.js +32 -60
  7. package/dist/{MetricsGraphStrips/MetricsGraphStrips.stories.d.ts → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.d.ts} +4 -3
  8. package/dist/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.d.ts.map +1 -0
  9. package/dist/{MetricsGraphStrips/MetricsGraphStrips.stories.js → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.js} +5 -4
  10. package/dist/{MetricsGraphStrips → ProfileFlameChart/SamplesStrips}/index.d.ts +5 -4
  11. package/dist/ProfileFlameChart/SamplesStrips/index.d.ts.map +1 -0
  12. package/dist/ProfileFlameChart/SamplesStrips/index.js +141 -0
  13. package/dist/ProfileFlameChart/index.d.ts +20 -0
  14. package/dist/ProfileFlameChart/index.d.ts.map +1 -0
  15. package/dist/ProfileFlameChart/index.js +155 -0
  16. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  17. package/dist/ProfileFlameGraph/index.js +0 -1
  18. package/dist/ProfileMetricsGraph/hooks/useQueryRange.d.ts +2 -1
  19. package/dist/ProfileMetricsGraph/hooks/useQueryRange.d.ts.map +1 -1
  20. package/dist/ProfileMetricsGraph/hooks/useQueryRange.js +11 -21
  21. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  22. package/dist/ProfileMetricsGraph/index.js +13 -3
  23. package/dist/ProfileSelector/index.d.ts.map +1 -1
  24. package/dist/ProfileSelector/index.js +4 -0
  25. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts +1 -0
  26. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.d.ts.map +1 -1
  27. package/dist/ProfileView/components/ActionButtons/GroupByDropdown.js +2 -2
  28. package/dist/ProfileView/components/DashboardItems/index.d.ts +5 -4
  29. package/dist/ProfileView/components/DashboardItems/index.d.ts.map +1 -1
  30. package/dist/ProfileView/components/DashboardItems/index.js +4 -3
  31. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts +2 -1
  32. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
  33. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +2 -2
  34. package/dist/ProfileView/components/Toolbars/MultiLevelDropdown.js +1 -1
  35. package/dist/ProfileView/components/Toolbars/index.d.ts +2 -0
  36. package/dist/ProfileView/components/Toolbars/index.d.ts.map +1 -1
  37. package/dist/ProfileView/components/Toolbars/index.js +4 -2
  38. package/dist/ProfileView/hooks/useAutoSelectDimension.d.ts +16 -0
  39. package/dist/ProfileView/hooks/useAutoSelectDimension.d.ts.map +1 -0
  40. package/dist/ProfileView/hooks/useAutoSelectDimension.js +75 -0
  41. package/dist/ProfileView/hooks/useVisualizationState.d.ts +2 -0
  42. package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
  43. package/dist/ProfileView/hooks/useVisualizationState.js +8 -0
  44. package/dist/ProfileView/index.d.ts +1 -1
  45. package/dist/ProfileView/index.d.ts.map +1 -1
  46. package/dist/ProfileView/index.js +7 -4
  47. package/dist/ProfileView/types/visualization.d.ts +15 -3
  48. package/dist/ProfileView/types/visualization.d.ts.map +1 -1
  49. package/dist/ProfileViewWithData.d.ts +2 -1
  50. package/dist/ProfileViewWithData.d.ts.map +1 -1
  51. package/dist/ProfileViewWithData.js +41 -29
  52. package/dist/index.d.ts +1 -0
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +4 -0
  55. package/dist/styles.css +1 -1
  56. package/package.json +8 -7
  57. package/src/ProfileExplorer/ProfileExplorerSingle.tsx +14 -3
  58. package/src/{MetricsGraphStrips/AreaGraph → ProfileFlameChart/SamplesStrips/SamplesGraph}/index.tsx +77 -81
  59. package/src/{MetricsGraphStrips/MetricsGraphStrips.stories.tsx → ProfileFlameChart/SamplesStrips/SamplesStrips.stories.tsx} +7 -6
  60. package/src/ProfileFlameChart/SamplesStrips/index.tsx +301 -0
  61. package/src/ProfileFlameChart/index.tsx +305 -0
  62. package/src/ProfileFlameGraph/index.tsx +0 -1
  63. package/src/ProfileMetricsGraph/hooks/useQueryRange.ts +18 -26
  64. package/src/ProfileMetricsGraph/index.tsx +24 -2
  65. package/src/ProfileSelector/index.tsx +11 -0
  66. package/src/ProfileView/components/ActionButtons/GroupByDropdown.tsx +3 -0
  67. package/src/ProfileView/components/DashboardItems/index.tsx +19 -17
  68. package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +4 -2
  69. package/src/ProfileView/components/Toolbars/MultiLevelDropdown.tsx +1 -1
  70. package/src/ProfileView/components/Toolbars/index.tsx +18 -1
  71. package/src/ProfileView/hooks/useAutoSelectDimension.ts +90 -0
  72. package/src/ProfileView/hooks/useVisualizationState.ts +17 -0
  73. package/src/ProfileView/index.tsx +16 -2
  74. package/src/ProfileView/types/visualization.ts +17 -3
  75. package/src/ProfileViewWithData.tsx +80 -37
  76. package/src/index.tsx +4 -0
  77. package/dist/MetricsGraphStrips/AreaGraph/Tooltip.d.ts +0 -10
  78. package/dist/MetricsGraphStrips/AreaGraph/Tooltip.d.ts.map +0 -1
  79. package/dist/MetricsGraphStrips/AreaGraph/Tooltip.js +0 -44
  80. package/dist/MetricsGraphStrips/AreaGraph/index.d.ts +0 -21
  81. package/dist/MetricsGraphStrips/AreaGraph/index.d.ts.map +0 -1
  82. package/dist/MetricsGraphStrips/MetricsGraphStrips.stories.d.ts.map +0 -1
  83. package/dist/MetricsGraphStrips/index.d.ts.map +0 -1
  84. package/dist/MetricsGraphStrips/index.js +0 -70
  85. package/src/MetricsGraphStrips/AreaGraph/Tooltip.tsx +0 -83
  86. package/src/MetricsGraphStrips/index.tsx +0 -142
package/CHANGELOG.md CHANGED
@@ -3,6 +3,10 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## [0.19.114](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.113...@parca/profile@0.19.114) (2026-02-06)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
6
10
  ## [0.19.113](https://github.com/parca-dev/parca/compare/@parca/profile@0.19.112...@parca/profile@0.19.113) (2026-01-29)
7
11
 
8
12
  **Note:** Version bump only for package @parca/profile
@@ -1 +1 @@
1
- {"version":3,"file":"ProfileExplorerSingle.d.ts","sourceRoot":"","sources":["../../src/ProfileExplorer/ProfileExplorerSingle.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAMvD,UAAU,0BAA0B;IAClC,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,EAAE,gBAAgB,CAAC;CAC9B;AAED,QAAA,MAAM,qBAAqB,GAAI,8BAG5B,0BAA0B,KAAG,GAAG,CAAC,OAwBnC,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
1
+ {"version":3,"file":"ProfileExplorerSingle.d.ts","sourceRoot":"","sources":["../../src/ProfileExplorer/ProfileExplorerSingle.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAMvD,UAAU,0BAA0B;IAClC,WAAW,EAAE,kBAAkB,CAAC;IAChC,UAAU,EAAE,gBAAgB,CAAC;CAC9B;AAED,QAAA,MAAM,qBAAqB,GAAI,8BAG5B,0BAA0B,KAAG,GAAG,CAAC,OAmCnC,CAAC;AAEF,eAAe,qBAAqB,CAAC"}
@@ -11,13 +11,19 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
11
11
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
- import { useState } from 'react';
14
+ import { useCallback, useState } from 'react';
15
15
  import { ProfileViewWithData } from '..';
16
16
  import ProfileSelector from '../ProfileSelector';
17
17
  import { useQueryState } from '../hooks/useQueryState';
18
18
  const ProfileExplorerSingle = ({ queryClient, navigateTo, }) => {
19
19
  const [showMetricsGraph, setShowMetricsGraph] = useState(true);
20
- const { profileSource } = useQueryState({ suffix: '_a' });
21
- return (_jsxs(_Fragment, { children: [_jsx("div", { className: "relative", children: _jsx(ProfileSelector, { queryClient: queryClient, closeProfile: () => { }, comparing: false, enforcedProfileName: '', navigateTo: navigateTo, suffix: "_a", showMetricsGraph: showMetricsGraph, setDisplayHideMetricsGraphButton: setShowMetricsGraph }) }), profileSource != null && (_jsx(ProfileViewWithData, { queryClient: queryClient, profileSource: profileSource }))] }));
20
+ const { profileSource, setDraftTimeRange, commitDraft } = useQueryState({ suffix: '_a' });
21
+ const handleSwitchToOneMinute = useCallback(() => {
22
+ const now = Date.now();
23
+ const from = now - 60000; // 1 minute ago
24
+ setDraftTimeRange(from, now, 'relative:minute|1');
25
+ commitDraft({ from, to: now, timeSelection: 'relative:minute|1' });
26
+ }, [setDraftTimeRange, commitDraft]);
27
+ return (_jsxs(_Fragment, { children: [_jsx("div", { className: "relative", children: _jsx(ProfileSelector, { queryClient: queryClient, closeProfile: () => { }, comparing: false, enforcedProfileName: '', navigateTo: navigateTo, suffix: "_a", showMetricsGraph: showMetricsGraph, setDisplayHideMetricsGraphButton: setShowMetricsGraph }) }), profileSource != null && (_jsx(ProfileViewWithData, { queryClient: queryClient, profileSource: profileSource, onSwitchToOneMinute: handleSwitchToOneMinute }))] }));
22
28
  };
23
29
  export default ProfileExplorerSingle;
@@ -0,0 +1,31 @@
1
+ import { NumberDuo } from '../../../utils';
2
+ export interface DataPoint {
3
+ timestamp: number;
4
+ value: number;
5
+ sampleCount?: number;
6
+ }
7
+ interface DragState {
8
+ stripIndex: number;
9
+ startX: number;
10
+ currentX: number;
11
+ }
12
+ interface Props {
13
+ width: number;
14
+ height: number;
15
+ marginLeft?: number;
16
+ marginRight?: number;
17
+ marginTop?: number;
18
+ marginBottom?: number;
19
+ fill?: string;
20
+ data: DataPoint[];
21
+ selectionBounds?: NumberDuo | undefined;
22
+ setSelectionBounds: (newBounds: NumberDuo | undefined) => void;
23
+ stepMs: number;
24
+ onDragStart?: (startX: number) => void;
25
+ dragState?: DragState;
26
+ isAnyDragActive?: boolean;
27
+ timeBounds?: NumberDuo;
28
+ }
29
+ export declare const SamplesGraph: ({ data, height, width, marginLeft, marginRight, marginBottom, marginTop, fill, selectionBounds, setSelectionBounds, stepMs, onDragStart, dragState, isAnyDragActive, timeBounds, }: Props) => JSX.Element;
30
+ export {};
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/ProfileFlameChart/SamplesStrips/SamplesGraph/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAEzC,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,SAAS;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,KAAK;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,eAAe,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IACxC,kBAAkB,EAAE,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IAC/D,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,UAAU,CAAC,EAAE,SAAS,CAAC;CACxB;AAwJD,eAAO,MAAM,YAAY,GAAI,oLAgB1B,KAAK,KAAG,GAAG,CAAC,OAkId,CAAC"}
@@ -15,7 +15,6 @@ import { useEffect, useMemo, useRef, useState } from 'react';
15
15
  import { Icon } from '@iconify/react';
16
16
  import cx from 'classnames';
17
17
  import * as d3 from 'd3';
18
- import { Tooltip } from './Tooltip';
19
18
  const DraggingWindow = ({ dragStart, currentX, }) => {
20
19
  const start = useMemo(() => Math.min(dragStart ?? 0, currentX ?? 0), [dragStart, currentX]);
21
20
  const width = useMemo(() => Math.abs((dragStart ?? 0) - (currentX ?? 0)), [dragStart, currentX]);
@@ -100,26 +99,22 @@ const ZoomWindow = ({ zoomWindow, onZoomWindowChange, setIsHoveringDragHandle, }
100
99
  setIsHoveringDragHandle(false);
101
100
  }, children: _jsx(Icon, { icon: "si:drag-handle-line", className: "rotate-90", fontSize: 16 }) })] }) }));
102
101
  };
103
- export const AreaGraph = ({ data, height, width, marginLeft = 0, marginRight = 0, marginBottom = 0, marginTop = 0, fill = 'gray', selectionBounds, setSelectionBounds, valueBounds, }) => {
102
+ export const SamplesGraph = ({ data, height, width, marginLeft = 0, marginRight = 0, marginBottom = 0, marginTop = 0, fill = 'gray', selectionBounds, setSelectionBounds, stepMs, onDragStart, dragState, isAnyDragActive = false, timeBounds, }) => {
104
103
  const [mousePosition, setMousePosition] = useState(undefined);
105
- const [dragStart, setDragStart] = useState(undefined);
106
104
  const [isHoveringDragHandle, setIsHoveringDragHandle] = useState(false);
107
- const [hoverData, setHoverData] = useState(null);
108
- const [isMouseOverGraph, setIsMouseOverGraph] = useState(false);
109
- const isDragging = dragStart !== undefined;
110
- // Declare the x (horizontal position) scale.
111
- const x = d3.scaleUtc(d3.extent(data, d => d.timestamp), [
112
- marginLeft,
113
- width - marginRight,
114
- ]);
115
- // Declare the y (vertical position) scale.
116
- const y = d3.scaleLinear([valueBounds[0], valueBounds[1]], [height - marginBottom, marginTop]);
117
- const area = d3
118
- .area()
119
- .curve(d3.curveMonotoneX)
120
- .x(d => x(d.timestamp))
121
- .y0(y(0))
122
- .y1(d => y(d.value));
105
+ // use the bounds from props if provided, else compute from data
106
+ const xDomain = timeBounds ?? d3.extent(data, d => d.timestamp);
107
+ const x = d3.scaleUtc(xDomain, [marginLeft, width - marginRight]);
108
+ // Calculate sample count range for opacity scaling
109
+ const sampleCounts = data.map(d => Number(d.sampleCount ?? 1));
110
+ const maxSampleCount = Math.max(...sampleCounts);
111
+ const minSampleCount = Math.min(...sampleCounts);
112
+ // Create opacity scale: more samples = higher opacity
113
+ const opacityScale = d3
114
+ .scaleLinear()
115
+ .domain([minSampleCount, maxSampleCount])
116
+ .range([0.5, 1.0])
117
+ .clamp(true);
123
118
  const zoomWindow = useMemo(() => {
124
119
  if (selectionBounds === undefined) {
125
120
  return undefined;
@@ -130,67 +125,44 @@ export const AreaGraph = ({ data, height, width, marginLeft = 0, marginRight = 0
130
125
  const setSelectionBoundsWithScaling = ([startPx, endPx]) => {
131
126
  setSelectionBounds([x.invert(startPx).getTime(), x.invert(endPx).getTime()]);
132
127
  };
133
- return (_jsxs("div", { style: { height, width }, onMouseMove: e => {
128
+ return (_jsxs("div", { style: { height, width }, className: "relative", onMouseMove: e => {
129
+ // Only track hover position when no drag is active anywhere
130
+ if (isAnyDragActive)
131
+ return;
134
132
  const [xPos, yPos] = d3.pointer(e);
135
133
  if (xPos >= marginLeft &&
136
134
  xPos <= width - marginRight &&
137
135
  yPos >= marginTop &&
138
136
  yPos <= height - marginBottom) {
139
137
  setMousePosition([xPos, yPos]);
140
- // Find the closest data point
141
- if (!isHoveringDragHandle && !isDragging) {
142
- const xDate = x.invert(xPos);
143
- const bisect = d3.bisector((d) => d.timestamp).left;
144
- const index = bisect(data, xDate.getTime());
145
- const dataPoint = data[index];
146
- if (dataPoint !== undefined) {
147
- setHoverData(dataPoint);
148
- }
149
- }
150
138
  }
151
139
  else {
152
140
  setMousePosition(undefined);
153
- setHoverData(null);
154
141
  }
155
- }, onMouseEnter: () => {
156
- setIsMouseOverGraph(true);
157
142
  }, onMouseLeave: () => {
158
- setIsMouseOverGraph(false);
143
+ // Only clear hover position, drag is managed by parent
159
144
  setMousePosition(undefined);
160
- setDragStart(undefined);
161
- setHoverData(null);
162
145
  }, onMouseDown: e => {
163
146
  // only left mouse button
164
147
  if (e.button !== 0) {
165
148
  return;
166
149
  }
167
- // X/Y coordinate array relative to svg
150
+ // X/Y coordinate array relative to element
168
151
  const rel = d3.pointer(e);
169
152
  const xCoordinate = rel[0];
170
- const xCoordinateWithoutMargin = xCoordinate - marginLeft;
171
- if (xCoordinateWithoutMargin >= 0) {
172
- setDragStart(xCoordinateWithoutMargin);
153
+ if (xCoordinate >= 0 && onDragStart !== undefined) {
154
+ onDragStart(xCoordinate);
173
155
  }
174
156
  e.stopPropagation();
175
157
  e.preventDefault();
176
- }, onMouseUp: e => {
177
- if (dragStart === undefined) {
178
- return;
179
- }
180
- const rel = d3.pointer(e);
181
- const xCoordinate = rel[0];
182
- const xCoordinateWithoutMargin = xCoordinate - marginLeft;
183
- if (xCoordinateWithoutMargin >= 0 && dragStart !== xCoordinateWithoutMargin) {
184
- const start = Math.min(dragStart, xCoordinateWithoutMargin);
185
- const end = Math.max(dragStart, xCoordinateWithoutMargin);
186
- setSelectionBoundsWithScaling([start, end]);
187
- }
188
- setDragStart(undefined);
189
- }, className: "relative", children: [_jsx("div", { style: { height, width: 2, left: mousePosition?.[0] ?? -1 }, className: cx('bg-gray-700/75 dark:bg-gray-200/75 absolute top-0', {
190
- hidden: mousePosition === undefined || isDragging || isHoveringDragHandle,
191
- }) }), _jsx(DraggingWindow, { dragStart: dragStart, currentX: mousePosition?.[0] }), _jsx(ZoomWindow, { zoomWindow: zoomWindow, width: width, onZoomWindowChange: setSelectionBoundsWithScaling, setIsHoveringDragHandle: setIsHoveringDragHandle }), mousePosition !== undefined &&
192
- hoverData !== null &&
193
- !isDragging &&
194
- !isHoveringDragHandle &&
195
- isMouseOverGraph && (_jsx(Tooltip, { x: mousePosition[0], y: mousePosition[1], timestamp: hoverData.timestamp, value: hoverData.value, containerWidth: width })), _jsx("svg", { style: { width: '100%', height: '100%' }, children: _jsx("path", { fill: fill, d: area(data), className: "opacity-80" }) })] }));
158
+ }, children: [_jsx("div", { style: { height, width: 2, left: mousePosition?.[0] ?? -1 }, className: cx('bg-gray-700/75 dark:bg-gray-200/75 absolute top-0', {
159
+ hidden: mousePosition === undefined || isAnyDragActive || isHoveringDragHandle,
160
+ }) }), _jsx(DraggingWindow, { dragStart: dragState?.startX, currentX: dragState?.currentX }), _jsx(ZoomWindow, { zoomWindow: zoomWindow, width: width, onZoomWindowChange: setSelectionBoundsWithScaling, setIsHoveringDragHandle: setIsHoveringDragHandle }), _jsxs("svg", { style: { width: '100%', height: '100%' }, children: [_jsx("rect", { x: marginLeft, y: 0, width: width - marginLeft - marginRight, height: height, fill: fill, fillOpacity: 0.1 }), _jsx("g", { children: data.map((d, i) => {
161
+ const xPosition = x(d.timestamp);
162
+ // Use stepMs for bucket width
163
+ const rectWidth = x(d.timestamp + stepMs) - xPosition;
164
+ // Calculate opacity based on sample count
165
+ const opacity = opacityScale(Number(d.sampleCount ?? 1));
166
+ return (_jsx("rect", { x: xPosition, y: 0, width: rectWidth, height: height, fill: fill, fillOpacity: opacity }, i));
167
+ }) })] })] }));
196
168
  };
@@ -1,6 +1,6 @@
1
1
  import { Meta } from '@storybook/react';
2
- import { NumberDuo } from '../utils';
3
- import { DataPoint } from './AreaGraph';
2
+ import { NumberDuo } from '../../utils';
3
+ import { DataPoint } from './SamplesGraph';
4
4
  declare const meta: Meta;
5
5
  export default meta;
6
6
  export declare const ThreeCPUStrips: {
@@ -18,7 +18,8 @@ export declare const ThreeCPUStrips: {
18
18
  };
19
19
  onSelectedTimeframe: (index: number, bounds: NumberDuo) => void;
20
20
  bounds: number[];
21
+ stepMs: number;
21
22
  };
22
23
  render: (args: any) => JSX.Element;
23
24
  };
24
- //# sourceMappingURL=MetricsGraphStrips.stories.d.ts.map
25
+ //# sourceMappingURL=SamplesStrips.stories.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SamplesStrips.stories.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameChart/SamplesStrips/SamplesStrips.stories.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAC,IAAI,EAAC,MAAM,kBAAkB,CAAC;AAEtC,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAqBzC,QAAA,MAAM,IAAI,EAAE,IAGX,CAAC;AACF,eAAe,IAAI,CAAC;AAEpB,eAAO,MAAM,cAAc;;;;;;;;;;;;;qCAKM,MAAM,UAAU,SAAS,KAAG,IAAI;;;;mBAM9B,GAAG,KAAG,GAAG,CAAC,OAAO;CAUnD,CAAC"}
@@ -13,7 +13,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
13
13
  // limitations under the License.
14
14
  // eslint-disable-next-line import/named
15
15
  import { useArgs } from '@storybook/preview-api';
16
- import { MetricsGraphStrips } from './index';
16
+ import { SamplesStrip } from './index';
17
17
  function seededRandom(seed) {
18
18
  return () => {
19
19
  seed = (seed * 16807) % 2147483647;
@@ -31,8 +31,8 @@ for (let i = 0; i < 200; i++) {
31
31
  }
32
32
  }
33
33
  const meta = {
34
- title: 'components/MetricsGraphStrips',
35
- component: MetricsGraphStrips,
34
+ title: 'components/SamplesStrip',
35
+ component: SamplesStrip,
36
36
  };
37
37
  export default meta;
38
38
  export const ThreeCPUStrips = {
@@ -44,6 +44,7 @@ export const ThreeCPUStrips = {
44
44
  console.log('onSelectedTimeframe', index, bounds);
45
45
  },
46
46
  bounds: [mockData[0][0].timestamp, mockData[0][mockData[0].length - 1].timestamp],
47
+ stepMs: 100,
47
48
  },
48
49
  render: function Component(args) {
49
50
  const [, setArgs] = useArgs();
@@ -51,6 +52,6 @@ export const ThreeCPUStrips = {
51
52
  args.onSelectedTimeframe(index, bounds);
52
53
  setArgs({ ...args, selectedTimeframe: { index, bounds } });
53
54
  };
54
- return _jsx(MetricsGraphStrips, { ...args, onSelectedTimeframe: onSelectedTimeframe });
55
+ return _jsx(SamplesStrip, { ...args, onSelectedTimeframe: onSelectedTimeframe });
55
56
  },
56
57
  };
@@ -1,6 +1,7 @@
1
1
  import { LabelSet } from '@parca/client';
2
- import { NumberDuo } from '../utils';
3
- import { DataPoint } from './AreaGraph';
2
+ import { NumberDuo } from '../../utils';
3
+ import { DataPoint } from './SamplesGraph';
4
+ export type { DataPoint } from './SamplesGraph';
4
5
  interface Props {
5
6
  cpus: LabelSet[];
6
7
  data: DataPoint[][];
@@ -11,8 +12,8 @@ interface Props {
11
12
  onSelectedTimeframe: (labels: LabelSet, bounds: NumberDuo | undefined) => void;
12
13
  width?: number;
13
14
  bounds: NumberDuo;
15
+ stepMs: number;
14
16
  }
15
17
  export declare const labelSetToString: (labelSet?: LabelSet) => string;
16
- export declare const MetricsGraphStrips: ({ cpus, data, selectedTimeframe, onSelectedTimeframe, width, bounds, }: Props) => JSX.Element;
17
- export {};
18
+ export declare const SamplesStrip: ({ cpus, data, selectedTimeframe, onSelectedTimeframe, width, bounds, stepMs, }: Props) => JSX.Element;
18
19
  //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ProfileFlameChart/SamplesStrips/index.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAAC,QAAQ,EAAC,MAAM,eAAe,CAAC;AAGvC,OAAO,EAAC,SAAS,EAAC,MAAM,aAAa,CAAC;AACtC,OAAO,EAAC,SAAS,EAAe,MAAM,gBAAgB,CAAC;AAEvD,YAAY,EAAC,SAAS,EAAC,MAAM,gBAAgB,CAAC;AAQ9C,UAAU,KAAK;IACb,IAAI,EAAE,QAAQ,EAAE,CAAC;IACjB,IAAI,EAAE,SAAS,EAAE,EAAE,CAAC;IACpB,iBAAiB,CAAC,EAAE;QAClB,MAAM,EAAE,QAAQ,CAAC;QACjB,MAAM,EAAE,SAAS,CAAC;KACnB,CAAC;IACF,mBAAmB,EAAE,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,GAAG,SAAS,KAAK,IAAI,CAAC;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,eAAO,MAAM,gBAAgB,GAAI,WAAW,QAAQ,KAAG,MAoBtD,CAAC;AA6FF,eAAO,MAAM,YAAY,GAAI,gFAQ1B,KAAK,KAAG,GAAG,CAAC,OAmId,CAAC"}
@@ -0,0 +1,141 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Copyright 2022 The Parca Authors
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { useMemo, useRef, useState } from 'react';
15
+ import { Icon } from '@iconify/react';
16
+ import cx from 'classnames';
17
+ import * as d3 from 'd3';
18
+ import isEqual from 'fast-deep-equal';
19
+ import { useIntersectionObserver } from 'usehooks-ts';
20
+ import { TimelineGuide } from '../../TimelineGuide';
21
+ import { SamplesGraph } from './SamplesGraph';
22
+ export const labelSetToString = (labelSet) => {
23
+ if (labelSet === undefined) {
24
+ return '{}';
25
+ }
26
+ let str = '{';
27
+ let isFirst = true;
28
+ for (const label of labelSet.labels) {
29
+ if (!isFirst) {
30
+ str += ', ';
31
+ }
32
+ else {
33
+ isFirst = false;
34
+ }
35
+ str += `${label.name}: ${label.value}`;
36
+ }
37
+ str += '}';
38
+ return str;
39
+ };
40
+ const STRIP_HEIGHT = 24;
41
+ const getTimelineGuideHeight = (cpusCount, collapsedCount) => {
42
+ return (STRIP_HEIGHT + 4) * (cpusCount - collapsedCount) + 20 * collapsedCount + 24 - 6;
43
+ };
44
+ const stickyPx = 0;
45
+ const SamplesGraphContainer = ({ isSelected, isCollapsed, cpu, width, onToggleCollapse, data, selectionBounds, setSelectionBounds, color, stepMs, onDragStart, dragState, stripIndex, isAnyDragActive, timeBounds, }) => {
46
+ const labelStr = labelSetToString(cpu);
47
+ const { isIntersecting, ref } = useIntersectionObserver({
48
+ rootMargin: `${stickyPx}px 0px 0px 0px`,
49
+ });
50
+ const isSticky = useMemo(() => {
51
+ return isSelected && isIntersecting;
52
+ }, [isSelected, isIntersecting]);
53
+ return (_jsxs("div", { className: cx('min-h-5', {
54
+ relative: !isSelected,
55
+ 'sticky z-30 bg-white dark:bg-black bg-opacity-75': isSelected,
56
+ '!bg-opacity-100': isSticky,
57
+ }), style: { width: width ?? 1468, top: isSelected ? stickyPx : undefined }, ref: ref, children: [_jsxs("div", { className: "text-xs absolute top-0 left-0 flex gap-[2px] items-center bg-white/50 dark:bg-black/50 px-1 rounded-sm cursor-pointer", style: {
58
+ zIndex: 15,
59
+ }, onClick: onToggleCollapse, children: [_jsx(Icon, { icon: isCollapsed ? 'bxs:right-arrow' : 'bxs:down-arrow' }), labelStr] }), !isCollapsed ? (_jsx(SamplesGraph, { data: data, height: STRIP_HEIGHT, width: width ?? 1468, fill: color(labelStr), selectionBounds: selectionBounds, setSelectionBounds: setSelectionBounds, stepMs: stepMs, onDragStart: (startX) => onDragStart(stripIndex, startX), dragState: dragState?.stripIndex === stripIndex ? dragState : undefined, isAnyDragActive: isAnyDragActive, timeBounds: timeBounds })) : null] }, labelStr));
60
+ };
61
+ export const SamplesStrip = ({ cpus, data, selectedTimeframe, onSelectedTimeframe, width, bounds, stepMs, }) => {
62
+ const [collapsedLabels, setCollapsedLabels] = useState(new Set());
63
+ const [dragState, setDragState] = useState(undefined);
64
+ const containerRef = useRef(null);
65
+ const isDragging = dragState !== undefined;
66
+ // Sort cpus and data by label string for consistent ordering across reloads
67
+ const sortedItems = useMemo(() => {
68
+ const items = cpus.map((cpu, i) => ({
69
+ cpu,
70
+ data: data[i],
71
+ label: labelSetToString(cpu),
72
+ }));
73
+ return items.sort((a, b) => a.label.localeCompare(b.label));
74
+ }, [cpus, data]);
75
+ // Deterministic color: hash the label string so the same label always gets the same color
76
+ // regardless of render order.
77
+ const color = useMemo(() => {
78
+ const palette = d3.schemeObservable10;
79
+ const hashStr = (s) => {
80
+ let h = 0;
81
+ for (let i = 0; i < s.length; i++) {
82
+ h = (Math.imul(31, h) + s.charCodeAt(i)) | 0;
83
+ }
84
+ return Math.abs(h);
85
+ };
86
+ return (label) => palette[hashStr(label) % palette.length];
87
+ }, []);
88
+ const handleDragStart = (stripIndex, startX) => {
89
+ setDragState({ stripIndex, startX, currentX: startX });
90
+ };
91
+ const handleMouseMove = (e) => {
92
+ if (dragState === undefined || containerRef.current === null)
93
+ return;
94
+ const rect = containerRef.current.getBoundingClientRect();
95
+ const x = e.clientX - rect.left;
96
+ // Clamp to container bounds
97
+ const clampedX = Math.max(0, Math.min(x, width ?? rect.width));
98
+ setDragState({ ...dragState, currentX: clampedX });
99
+ };
100
+ const handleMouseUp = (e) => {
101
+ if (dragState === undefined || containerRef.current === null)
102
+ return;
103
+ const rect = containerRef.current.getBoundingClientRect();
104
+ const x = e.clientX - rect.left;
105
+ const clampedX = Math.max(0, Math.min(x, width ?? rect.width));
106
+ const { stripIndex, startX } = dragState;
107
+ if (startX !== clampedX) {
108
+ const start = Math.min(startX, clampedX);
109
+ const end = Math.max(startX, clampedX);
110
+ // Convert pixel positions to timestamps
111
+ const innerWidth = width ?? rect.width;
112
+ const startTs = bounds[0] + (start / innerWidth) * (bounds[1] - bounds[0]);
113
+ const endTs = bounds[0] + (end / innerWidth) * (bounds[1] - bounds[0]);
114
+ // Use sortedItems to get the correct cpu for the strip index
115
+ onSelectedTimeframe(sortedItems[stripIndex].cpu, [startTs, endTs]);
116
+ }
117
+ setDragState(undefined);
118
+ };
119
+ const handleMouseLeave = () => {
120
+ setDragState(undefined);
121
+ };
122
+ if (data.length === 0) {
123
+ return (_jsx("span", { className: "flex justify-center my-10", children: "There is no data matching your filter criteria, please try changing the filter." }));
124
+ }
125
+ return (_jsxs("div", { ref: containerRef, className: cx('flex flex-col gap-1 relative my-0', { 'cursor-ew-resize': isDragging }), style: { width: width ?? '100%' }, onMouseMove: handleMouseMove, onMouseUp: handleMouseUp, onMouseLeave: handleMouseLeave, children: [_jsx(TimelineGuide, { bounds: [BigInt(0), BigInt(bounds[1] - bounds[0])], width: width ?? 1468, height: getTimelineGuideHeight(sortedItems.length, collapsedLabels.size), margin: 1 }), sortedItems.map((item, i) => {
126
+ const isCollapsed = collapsedLabels.has(item.label);
127
+ const isSelected = isEqual(item.cpu, selectedTimeframe?.labels);
128
+ return (_jsx(SamplesGraphContainer, { isSelected: isSelected, isCollapsed: isCollapsed, cpu: item.cpu, width: width, data: item.data, onToggleCollapse: () => {
129
+ const newCollapsedLabels = new Set(collapsedLabels);
130
+ if (collapsedLabels.has(item.label)) {
131
+ newCollapsedLabels.delete(item.label);
132
+ }
133
+ else {
134
+ newCollapsedLabels.add(item.label);
135
+ }
136
+ setCollapsedLabels(newCollapsedLabels);
137
+ }, selectionBounds: isSelected ? selectedTimeframe?.bounds : undefined, setSelectionBounds: newBounds => {
138
+ onSelectedTimeframe(item.cpu, newBounds);
139
+ }, color: color, stepMs: stepMs, onDragStart: handleDragStart, dragState: dragState, stripIndex: i, isAnyDragActive: isDragging, timeBounds: bounds }, item.label));
140
+ })] }));
141
+ };
@@ -0,0 +1,20 @@
1
+ import { QueryServiceClient } from '@parca/client';
2
+ import { ProfileType } from '@parca/parser';
3
+ import { ProfileSource } from '../ProfileSource';
4
+ import type { SamplesData } from '../ProfileView/types/visualization';
5
+ interface ProfileFlameChartProps {
6
+ samplesData?: SamplesData;
7
+ queryClient: QueryServiceClient;
8
+ profileSource: ProfileSource;
9
+ width: number;
10
+ total: bigint;
11
+ filtered: bigint;
12
+ profileType?: ProfileType;
13
+ isHalfScreen: boolean;
14
+ metadataMappingFiles?: string[];
15
+ metadataLoading?: boolean;
16
+ onSwitchToOneMinute?: () => void;
17
+ }
18
+ export declare const ProfileFlameChart: ({ samplesData, queryClient, profileSource, width, total, filtered, profileType, isHalfScreen, metadataMappingFiles, metadataLoading, onSwitchToOneMinute, }: ProfileFlameChartProps) => JSX.Element;
19
+ export default ProfileFlameChart;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileFlameChart/index.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAoC,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAQpF,OAAO,EAAwB,WAAW,EAAQ,MAAM,eAAe,CAAC;AAIxE,OAAO,EAAsB,aAAa,EAAC,MAAM,kBAAkB,CAAC;AACpE,OAAO,KAAK,EAAC,WAAW,EAAC,MAAM,oCAAoC,CAAC;AA4CpE,UAAU,sBAAsB;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,WAAW,EAAE,kBAAkB,CAAC;IAChC,aAAa,EAAE,aAAa,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,YAAY,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,IAAI,CAAC;CAClC;AA+BD,eAAO,MAAM,iBAAiB,GAAI,6JAY/B,sBAAsB,KAAG,GAAG,CAAC,OA+K/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
@@ -0,0 +1,155 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // Copyright 2022 The Parca Authors
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { useEffect, useMemo, useRef } from 'react';
15
+ import { QueryRequest_ReportType } from '@parca/client';
16
+ import { Button, useParcaContext, useURLState, useURLStateCustom, } from '@parca/components';
17
+ import { Matcher, MatcherTypes, Query } from '@parca/parser';
18
+ import ProfileFlameGraph, { validateFlameChartQuery } from '../ProfileFlameGraph';
19
+ import { boundsFromProfileSource } from '../ProfileFlameGraph/FlameGraphArrow/utils';
20
+ import { MergedProfileSource } from '../ProfileSource';
21
+ import { useQuery } from '../useQuery';
22
+ import { SamplesStrip } from './SamplesStrips';
23
+ const TimeframeStateSerializer = {
24
+ parse: (value) => {
25
+ if (value == null || value === '' || value === 'undefined' || Array.isArray(value)) {
26
+ return undefined;
27
+ }
28
+ try {
29
+ const [labelPart, boundsPart] = value.split('|');
30
+ if (labelPart != null && boundsPart != null) {
31
+ const labels = labelPart.split(',').map(labelStr => {
32
+ const [name, ...rest] = labelStr.split(':');
33
+ return { name, value: rest.join(':') };
34
+ });
35
+ const [startMs, endMs] = boundsPart.split(',').map(Number);
36
+ if (labels.length > 0 && !isNaN(startMs) && !isNaN(endMs)) {
37
+ return {
38
+ labels: { labels },
39
+ bounds: [startMs, endMs],
40
+ };
41
+ }
42
+ }
43
+ }
44
+ catch {
45
+ // Ignore parsing errors
46
+ }
47
+ return undefined;
48
+ },
49
+ stringify: (value) => {
50
+ if (value == null) {
51
+ return '';
52
+ }
53
+ const labelsStr = value.labels.labels.map(l => `${l.name}:${l.value}`).join(',');
54
+ return `${labelsStr}|${value.bounds[0]},${value.bounds[1]}`;
55
+ },
56
+ };
57
+ // Helper to create a filtered profile source with narrowed time bounds
58
+ // and dimension label matchers from the selected strip.
59
+ const createFilteredProfileSource = (profileSource, selectedTimeframe) => {
60
+ if (!(profileSource instanceof MergedProfileSource)) {
61
+ return null;
62
+ }
63
+ // The bounds are in milliseconds, convert to nanoseconds for the profile source
64
+ // Round to integers since BigInt requires integer values
65
+ const mergeFrom = BigInt(Math.round(selectedTimeframe.bounds[0])) * 1000000n;
66
+ const mergeTo = BigInt(Math.round(selectedTimeframe.bounds[1])) * 1000000n;
67
+ // Add dimension labels as additional matchers to the query
68
+ const dimensionMatchers = selectedTimeframe.labels.labels.map(l => new Matcher(l.name, MatcherTypes.MatchEqual, l.value));
69
+ const query = new Query(profileSource.query.profType, [...profileSource.query.matchers, ...dimensionMatchers], '');
70
+ return new MergedProfileSource(mergeFrom, mergeTo, query);
71
+ };
72
+ export const ProfileFlameChart = ({ samplesData, queryClient, profileSource, width, total, filtered, profileType, isHalfScreen, metadataMappingFiles, metadataLoading, onSwitchToOneMinute, }) => {
73
+ const { loader } = useParcaContext();
74
+ const [selectedTimeframe, setSelectedTimeframe] = useURLStateCustom('flamechart_timeframe', TimeframeStateSerializer);
75
+ // Read flamechart dimension from URL state to detect changes
76
+ const [flamechartDimension] = useURLState('flamechart_dimension', {
77
+ alwaysReturnArray: true,
78
+ });
79
+ // Reset selection when the parent time range (profileSource) changes
80
+ const timeBoundsKey = boundsFromProfileSource(profileSource).join(',');
81
+ const prevTimeBoundsKey = useRef(timeBoundsKey);
82
+ useEffect(() => {
83
+ if (prevTimeBoundsKey.current !== timeBoundsKey) {
84
+ prevTimeBoundsKey.current = timeBoundsKey;
85
+ setSelectedTimeframe(undefined);
86
+ }
87
+ }, [timeBoundsKey, setSelectedTimeframe]);
88
+ // Reset selection when the dimension changes
89
+ const dimensionKey = (flamechartDimension ?? []).join(',');
90
+ const prevDimensionKey = useRef(dimensionKey);
91
+ useEffect(() => {
92
+ if (prevDimensionKey.current !== dimensionKey) {
93
+ prevDimensionKey.current = dimensionKey;
94
+ setSelectedTimeframe(undefined);
95
+ }
96
+ }, [dimensionKey, setSelectedTimeframe]);
97
+ // Handle timeframe selection from strips
98
+ const handleSelectedTimeframe = (labels, bounds) => {
99
+ if (bounds === undefined) {
100
+ setSelectedTimeframe(undefined);
101
+ }
102
+ else {
103
+ setSelectedTimeframe({ labels, bounds });
104
+ }
105
+ };
106
+ // Create filtered profile source when selection exists
107
+ const filteredProfileSource = useMemo(() => {
108
+ if (selectedTimeframe == null)
109
+ return null;
110
+ return createFilteredProfileSource(profileSource, selectedTimeframe);
111
+ }, [profileSource, selectedTimeframe]);
112
+ // Query flamechart data only when a strip selection exists
113
+ const { isLoading: flamechartLoading, response: flamechartResponse, error: flamechartError, } = useQuery(queryClient, filteredProfileSource ?? profileSource, QueryRequest_ReportType.FLAMECHART, {
114
+ skip: selectedTimeframe == null || filteredProfileSource == null,
115
+ });
116
+ const flamechartArrow = flamechartResponse?.report.oneofKind === 'flamegraphArrow'
117
+ ? flamechartResponse.report.flamegraphArrow
118
+ : undefined;
119
+ const flamechartTotal = flamechartResponse != null ? BigInt(flamechartResponse.total) : total;
120
+ const flamechartFiltered = flamechartResponse != null ? BigInt(flamechartResponse.filtered) : filtered;
121
+ // Get time bounds from profile source for the strips
122
+ const timeBounds = boundsFromProfileSource(profileSource);
123
+ // Transform samples data for SamplesStrip component
124
+ const stripsData = useMemo(() => {
125
+ if (samplesData?.series == null)
126
+ return { cpus: [], data: [], stepMs: 0 };
127
+ const cpus = samplesData.series.map(s => s.labelset);
128
+ const data = samplesData.series.map(s => s.data);
129
+ const stepMs = samplesData.stepMs ?? 0;
130
+ return { cpus, data, stepMs };
131
+ }, [samplesData?.series, samplesData?.stepMs]);
132
+ const { isValid, isNonDelta, isDurationTooLong } = validateFlameChartQuery(profileSource);
133
+ if (!isValid) {
134
+ if (isDurationTooLong) {
135
+ return (_jsxs("div", { className: "flex flex-col justify-center items-center p-10 text-center gap-4 text-sm", children: [_jsx("span", { children: "Flame chart is unavailable for queries longer than one minute. Try reducing the time range to one minute or selecting a point in the metrics graph." }), onSwitchToOneMinute != null && (_jsx(Button, { variant: "primary", onClick: onSwitchToOneMinute, children: "Switch to last 1 minute" }))] }));
136
+ }
137
+ const message = isNonDelta
138
+ ? 'To use the Flame chart, please switch to a Delta profile.'
139
+ : 'Flame chart is unavailable for this query.';
140
+ return (_jsx("div", { className: "flex flex-col justify-center p-10 text-center gap-6 text-sm", children: message }));
141
+ }
142
+ const hasDimension = (flamechartDimension ?? []).length > 0;
143
+ // Show loader while metadata labels are loading (needed for dimension auto-selection)
144
+ if (metadataLoading === true) {
145
+ return _jsx(_Fragment, { children: loader });
146
+ }
147
+ if (!hasDimension) {
148
+ return (_jsx("div", { className: "flex justify-center items-center py-10 text-gray-500 dark:text-gray-400 text-sm", children: "Select a label in the \"Samples group by\" dropdown above to view the samples strips." }));
149
+ }
150
+ if (samplesData?.loading === true) {
151
+ return _jsx(_Fragment, { children: loader });
152
+ }
153
+ return (_jsxs("div", { children: [stripsData.cpus.length > 0 && stripsData.data.length > 0 && (_jsx("div", { className: "mb-2", children: _jsx(SamplesStrip, { cpus: stripsData.cpus, data: stripsData.data, selectedTimeframe: selectedTimeframe, onSelectedTimeframe: handleSelectedTimeframe, width: width, bounds: [Number(timeBounds[0] / 1000000n), Number(timeBounds[1] / 1000000n)], stepMs: stripsData.stepMs }) })), selectedTimeframe != null && filteredProfileSource != null ? (_jsx(ProfileFlameGraph, { arrow: flamechartArrow, loading: flamechartLoading, error: flamechartError, profileSource: filteredProfileSource, width: width, total: flamechartTotal, filtered: flamechartFiltered, profileType: profileType, isHalfScreen: isHalfScreen, metadataMappingFiles: metadataMappingFiles, metadataLoading: metadataLoading, isFlameChart: true, curPathArrow: [], setNewCurPathArrow: () => { } })) : (_jsx("div", { className: "flex justify-center items-center py-10 text-gray-500 dark:text-gray-400 text-sm", children: "Select a time range in the samples strips above to view the flamechart." }))] }));
154
+ };
155
+ export default ProfileFlameChart;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileFlameGraph/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAM7F,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAO9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAI1C,OAAO,EAAC,mBAAmB,EAAE,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAOpE,OAAO,EAAC,gBAAgB,EAA0B,MAAM,yBAAyB,CAAC;AAIlF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpE,UAAU,sBAAsB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC;IACtC,kBAAkB,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC;IACxD,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAUD,eAAO,MAAM,uBAAuB,GAClC,eAAe,mBAAmB,KACjC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAMpE,CAAC;AAEF,QAAA,MAAM,iBAAiB,GAAqC,qQAoBzD,sBAAsB,KAAG,GAAG,CAAC,OA0T/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileFlameGraph/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAwE,MAAM,OAAO,CAAC;AAM7F,OAAO,EAAC,eAAe,EAAC,MAAM,eAAe,CAAC;AAO9C,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAI1C,OAAO,EAAC,mBAAmB,EAAE,aAAa,EAAC,MAAM,kBAAkB,CAAC;AAOpE,OAAO,EAAC,gBAAgB,EAA0B,MAAM,yBAAyB,CAAC;AAIlF,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;AAEpE,UAAU,sBAAsB;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,eAAe,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,aAAa,EAAE,aAAa,CAAC;IAC7B,YAAY,EAAE,gBAAgB,EAAE,GAAG,EAAE,CAAC;IACtC,kBAAkB,EAAE,CAAC,IAAI,EAAE,gBAAgB,EAAE,KAAK,IAAI,CAAC;IACvD,OAAO,EAAE,OAAO,CAAC;IACjB,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,KAAK,IAAI,CAAC;IACxD,KAAK,CAAC,EAAE,GAAG,CAAC;IACZ,YAAY,EAAE,OAAO,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAC;IAChC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAUD,eAAO,MAAM,uBAAuB,GAClC,eAAe,mBAAmB,KACjC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,UAAU,EAAE,OAAO,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAKpE,CAAC;AAEF,QAAA,MAAM,iBAAiB,GAAqC,qQAoBzD,sBAAsB,KAAG,GAAG,CAAC,OA0T/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}