@parca/profile 0.19.44 → 0.19.46

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 (53) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.d.ts.map +1 -1
  3. package/dist/GraphTooltipArrow/Content.js +1 -1
  4. package/dist/MetricsGraph/MetricsContextMenu/index.d.ts +20 -11
  5. package/dist/MetricsGraph/MetricsContextMenu/index.d.ts.map +1 -1
  6. package/dist/MetricsGraph/MetricsContextMenu/index.js +16 -20
  7. package/dist/MetricsGraph/MetricsTooltip/index.d.ts +2 -8
  8. package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
  9. package/dist/MetricsGraph/MetricsTooltip/index.js +46 -55
  10. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +2 -5
  11. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +1 -1
  12. package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +126 -205
  13. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +9 -17
  14. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +1 -1
  15. package/dist/MetricsGraph/UtilizationMetrics/index.js +149 -208
  16. package/dist/MetricsGraph/index.d.ts +19 -26
  17. package/dist/MetricsGraph/index.d.ts.map +1 -1
  18. package/dist/MetricsGraph/index.js +50 -115
  19. package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
  20. package/dist/ProfileFlameGraph/index.js +3 -1
  21. package/dist/ProfileMetricsGraph/index.d.ts +1 -1
  22. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  23. package/dist/ProfileMetricsGraph/index.js +232 -23
  24. package/dist/ProfileSelector/MetricsGraphSection.d.ts +1 -4
  25. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  26. package/dist/ProfileSelector/MetricsGraphSection.js +8 -4
  27. package/dist/ProfileSelector/index.d.ts +3 -6
  28. package/dist/ProfileSelector/index.d.ts.map +1 -1
  29. package/dist/ProfileSelector/index.js +2 -2
  30. package/dist/ProfileSource.d.ts +9 -6
  31. package/dist/ProfileSource.d.ts.map +1 -1
  32. package/dist/ProfileSource.js +23 -8
  33. package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
  34. package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +5 -1
  35. package/dist/ProfileView/components/ProfileFilters/index.d.ts.map +1 -1
  36. package/dist/ProfileView/components/ProfileFilters/index.js +6 -5
  37. package/dist/styles.css +1 -1
  38. package/dist/useQuery.js +1 -1
  39. package/package.json +7 -7
  40. package/src/GraphTooltipArrow/Content.tsx +2 -4
  41. package/src/MetricsGraph/MetricsContextMenu/index.tsx +78 -66
  42. package/src/MetricsGraph/MetricsTooltip/index.tsx +53 -210
  43. package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +242 -434
  44. package/src/MetricsGraph/UtilizationMetrics/index.tsx +312 -448
  45. package/src/MetricsGraph/index.tsx +99 -185
  46. package/src/ProfileFlameGraph/index.tsx +3 -1
  47. package/src/ProfileMetricsGraph/index.tsx +430 -37
  48. package/src/ProfileSelector/MetricsGraphSection.tsx +12 -8
  49. package/src/ProfileSelector/index.tsx +5 -5
  50. package/src/ProfileSource.tsx +34 -17
  51. package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +15 -3
  52. package/src/ProfileView/components/ProfileFilters/index.tsx +23 -3
  53. package/src/useQuery.tsx +1 -1
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Copyright 2022 The Parca Authors
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
@@ -11,44 +11,104 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { Fragment, useCallback, useId, useMemo, useRef, useState } from 'react';
15
- import * as d3 from 'd3';
16
- import { pointer } from 'd3-selection';
14
+ import { useMemo } from 'react';
15
+ import { Icon } from '@iconify/react';
17
16
  import { AnimatePresence, motion } from 'framer-motion';
18
- import throttle from 'lodash.throttle';
19
- import { useContextMenu } from 'react-contexify';
20
- import { DateTimeRange, MetricsGraphSkeleton, useParcaContext, useURLState } from '@parca/components';
21
- import { formatDate, formatForTimespan, getPrecision, valueFormatter } from '@parca/utilities';
22
- import MetricsSeries from '../../MetricsSeries';
23
- import MetricsContextMenu from '../MetricsContextMenu';
24
- import MetricsTooltip from '../MetricsTooltip';
17
+ import { MetricsGraphSkeleton, TextWithTooltip, useParcaContext, } from '@parca/components';
18
+ import { formatDate, timePattern, valueFormatter } from '@parca/utilities';
19
+ import MetricsGraph from '../index';
25
20
  import { useMetricsGraphDimensions } from '../useMetricsGraphDimensions';
26
- import { getSeriesColor } from '../utils/colorMapping';
27
- function transformToSeries(data) {
28
- const series = data.reduce(function (agg, s) {
29
- if (s.labelset !== undefined) {
30
- const metric = s.labelset.labels.sort((a, b) => a.name.localeCompare(b.name));
31
- agg.push({
32
- metric,
33
- isSelected: s.isSelected ?? false,
34
- values: s.samples.reduce(function (agg, d) {
35
- if (d.timestamp !== undefined && d.value !== undefined) {
36
- agg.push([d.timestamp, d.value]);
21
+ const transformUtilizationLabels = (label) => {
22
+ return label.replace('attributes.', '').replace('attributes_resource.', '');
23
+ };
24
+ const createUtilizationContextMenuItems = (addLabelMatcher, originalData) => {
25
+ return [
26
+ {
27
+ id: 'focus-on-single-series',
28
+ label: 'Focus only on this series',
29
+ icon: 'ph:star',
30
+ onClick: (closestPoint, _series) => {
31
+ if (closestPoint != null &&
32
+ originalData.length > 0 &&
33
+ originalData[closestPoint.seriesIndex] != null) {
34
+ const originalSeriesData = originalData[closestPoint.seriesIndex];
35
+ if (originalSeriesData.labelset?.labels != null) {
36
+ const labels = originalSeriesData.labelset.labels.filter(label => label.name !== '__name__');
37
+ const labelsToAdd = labels.map(label => ({
38
+ key: label.name,
39
+ value: label.value,
40
+ }));
41
+ addLabelMatcher(labelsToAdd);
37
42
  }
38
- return agg;
39
- }, []),
40
- labelset: metric.map(m => `${m.name}=${m.value}`).join(','),
41
- });
43
+ }
44
+ },
45
+ },
46
+ {
47
+ id: 'add-to-query',
48
+ label: 'Add to query',
49
+ icon: 'material-symbols:add',
50
+ createDynamicItems: (closestPoint, _series) => {
51
+ if (closestPoint == null ||
52
+ originalData.length === 0 ||
53
+ originalData[closestPoint.seriesIndex] == null) {
54
+ return [
55
+ {
56
+ id: 'no-labels-available',
57
+ label: 'No labels available',
58
+ icon: 'ph:warning',
59
+ disabled: () => true,
60
+ onClick: () => { }, // No-op for disabled item
61
+ },
62
+ ];
63
+ }
64
+ const originalSeriesData = originalData[closestPoint.seriesIndex];
65
+ if (originalSeriesData.labelset?.labels == null) {
66
+ return [
67
+ {
68
+ id: 'no-labels-available',
69
+ label: 'No labels available',
70
+ icon: 'ph:warning',
71
+ disabled: () => true,
72
+ onClick: () => { }, // No-op for disabled item
73
+ },
74
+ ];
75
+ }
76
+ const labels = originalSeriesData.labelset.labels.filter(label => label.name !== '__name__');
77
+ return labels.map(label => ({
78
+ id: `add-label-${label.name}`,
79
+ label: (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-300", children: `${transformUtilizationLabels(label.name)}="${label.value}"` })),
80
+ onClick: () => {
81
+ addLabelMatcher({
82
+ key: label.name,
83
+ value: label.value,
84
+ });
85
+ },
86
+ }));
87
+ },
88
+ },
89
+ ];
90
+ };
91
+ const transformMetricSeriesToSeries = (data) => {
92
+ return data.map(metricSeries => {
93
+ if (metricSeries.labelset != null) {
94
+ const labels = metricSeries.labelset.labels ?? [];
95
+ const sortedLabels = labels.sort((a, b) => a.name.localeCompare(b.name));
96
+ const id = sortedLabels.map(label => `${label.name}=${label.value}`).join(',');
97
+ return {
98
+ id: id !== '' ? id : 'default',
99
+ values: metricSeries.samples.map((sample) => [
100
+ sample.timestamp,
101
+ sample.value,
102
+ ]),
103
+ };
42
104
  }
43
- return agg;
44
- }, []);
45
- // Sort values by timestamp for each series
46
- return series.map(series => ({
47
- ...series,
48
- values: series.values.sort((a, b) => a[0] - b[0]),
49
- }));
50
- }
51
- const getYAxisUnit = (name) => {
105
+ return {
106
+ id: 'default',
107
+ values: [],
108
+ };
109
+ });
110
+ };
111
+ const _getYAxisUnit = (name) => {
52
112
  switch (name) {
53
113
  case 'gpu_power_watt':
54
114
  return 'watts';
@@ -60,183 +120,64 @@ const getYAxisUnit = (name) => {
60
120
  return 'percent';
61
121
  }
62
122
  };
63
- const RawUtilizationMetrics = ({ data, addLabelMatcher, setTimeRange, width, height, margin, name, humanReadableName, from, to, onSelectedSeriesChange, }) => {
123
+ const RawUtilizationMetrics = ({ data, originalData, setTimeRange, width, height, margin, humanReadableName, from, to, yAxisUnit, contextMenuItems, onSeriesClick, }) => {
64
124
  const { timezone } = useParcaContext();
65
- const graph = useRef(null);
66
- const [dragging, setDragging] = useState(false);
67
- const [hovering, setHovering] = useState(false);
68
- const [relPos, setRelPos] = useState(-1);
69
- const [pos, setPos] = useState([0, 0]);
70
- const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
71
- const idForContextMenu = useId();
72
- const [_, setSelectedTimeframe] = useURLState('gpu_selected_timeframe');
73
- const lineStroke = '1px';
74
- const lineStrokeHover = '2px';
75
- const lineStrokeSelected = '3px';
76
- const graphWidth = useMemo(() => width - margin * 1.5 - margin / 2, [width, margin]);
77
- const graphTransform = useMemo(() => {
78
- // Adds 10px padding which aligns the graph on the grid
79
- return `translate(10, 0) scale(${(graphWidth - 10) / graphWidth}, 1)`;
80
- }, [graphWidth]);
81
- const paddedFrom = from;
82
- const paddedTo = to;
83
- const series = useMemo(() => transformToSeries(data), [data]);
84
- const extentsY = series.map(function (s) {
85
- return d3.extent(s.values, function (d) {
86
- return d[1];
87
- });
88
- });
89
- const minY = d3.min(extentsY, function (d) {
90
- return d[0];
91
- });
92
- const maxY = d3.max(extentsY, function (d) {
93
- return d[1];
94
- });
95
- // Setup scales with padded time range
96
- const xScale = d3.scaleUtc().domain([paddedFrom, paddedTo]).range([0, graphWidth]);
97
- const yScale = d3
98
- .scaleLinear()
99
- // tslint:disable-next-line
100
- .domain([minY, maxY])
101
- .range([height - margin, 0])
102
- .nice();
103
- const throttledSetPos = throttle(setPos, 20);
104
- const onMouseMove = (e) => {
105
- if (isContextMenuOpen) {
106
- return;
107
- }
108
- // X/Y coordinate array relative to svg
109
- const rel = pointer(e);
110
- const xCoordinate = rel[0];
111
- const xCoordinateWithoutMargin = xCoordinate - margin;
112
- const yCoordinate = rel[1];
113
- const yCoordinateWithoutMargin = yCoordinate - margin;
114
- throttledSetPos([xCoordinateWithoutMargin, yCoordinateWithoutMargin]);
115
- };
116
- const trackVisibility = (isVisible) => {
117
- setIsContextMenuOpen(isVisible);
118
- };
119
- const MENU_ID = `utilizationmetrics-context-menu-${idForContextMenu}`;
120
- const { show } = useContextMenu({
121
- id: MENU_ID,
122
- });
123
- const displayMenu = useCallback((e) => {
124
- show({
125
- event: e,
126
- });
127
- }, [show]);
128
- const l = d3.line(d => xScale(d[0]), d => yScale(d[1]));
129
- const highlighted = useMemo(() => {
130
- if (series.length === 0) {
125
+ return (_jsx(MetricsGraph, { data: data, from: from, to: to, setTimeRange: setTimeRange, onSampleClick: closestPoint => {
126
+ if (onSeriesClick != null) {
127
+ onSeriesClick(closestPoint.seriesIndex);
128
+ }
129
+ }, yAxisLabel: humanReadableName, yAxisUnit: yAxisUnit, width: width, height: height, margin: margin, contextMenuItems: contextMenuItems, renderTooltipContent: (seriesIndex, pointIndex) => {
130
+ if (originalData?.[seriesIndex]?.samples?.[pointIndex] != null) {
131
+ const originalSeriesData = originalData[seriesIndex];
132
+ const originalPoint = originalData[seriesIndex].samples[pointIndex];
133
+ const labels = originalSeriesData.labelset?.labels ?? [];
134
+ const nameLabel = labels.find(e => e.name === '__name__');
135
+ const highlightedNameLabel = nameLabel ?? { name: '', value: '' };
136
+ // Calculate attributes maps for utilization metrics
137
+ const attributesMap = labels
138
+ .filter(label => label.name.startsWith('attributes.') &&
139
+ !label.name.startsWith('attributes_resource.'))
140
+ .reduce((acc, label) => {
141
+ const key = label.name.replace('attributes.', '');
142
+ acc[key] = label.value;
143
+ return acc;
144
+ }, {});
145
+ const attributesResourceMap = labels
146
+ .filter(label => label.name.startsWith('attributes_resource.'))
147
+ .reduce((acc, label) => {
148
+ const key = label.name.replace('attributes_resource.', '');
149
+ acc[key] = label.value;
150
+ return acc;
151
+ }, {});
152
+ return (_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "ml-2 mr-6", children: [_jsx("span", { className: "font-semibold", children: highlightedNameLabel.value }), _jsx("span", { className: "my-2 block text-gray-700 dark:text-gray-300", children: _jsx("table", { className: "table-auto", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(originalPoint.value, yAxisUnit, 2) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(new Date(originalPoint.timestamp), timePattern(timezone), timezone) })] })] }) }) }), _jsxs("span", { className: "my-2 block text-gray-500", children: [Object.keys(attributesResourceMap).length > 0 ? (_jsx("span", { className: "text-sm font-bold text-gray-700 dark:text-white", children: "Resource Attributes" })) : null, _jsx("span", { className: "my-2 block text-gray-500", children: Object.keys(attributesResourceMap).map(name => (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: _jsx(TextWithTooltip, { text: `${transformUtilizationLabels(name)}="${attributesResourceMap[name] ?? ''}"`, maxTextLength: 48, id: `tooltip-${name}-${attributesResourceMap[name] ?? ''}` }) }, 'resourceattribute-' +
153
+ seriesIndex.toString() +
154
+ '-' +
155
+ pointIndex.toString() +
156
+ '-' +
157
+ name))) }), Object.keys(attributesMap).length > 0 ? (_jsx("span", { className: "text-sm font-bold text-gray-700 dark:text-white", children: "Attributes" })) : null, _jsx("span", { className: "my-2 block text-gray-500", children: Object.keys(attributesMap).map(name => (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: _jsx(TextWithTooltip, { text: `${transformUtilizationLabels(name)}="${attributesMap[name] ?? ''}"`, maxTextLength: 48, id: `tooltip-${name}-${attributesMap[name] ?? ''}` }) }, 'attribute-' +
158
+ seriesIndex.toString() +
159
+ '-' +
160
+ pointIndex.toString() +
161
+ '-' +
162
+ name))) }), labels
163
+ .filter(label => label.name !== '__name__' && !label.name.startsWith('attributes'))
164
+ .map(label => (_jsx("div", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: _jsx(TextWithTooltip, { text: `${transformUtilizationLabels(label.name)}="${label.value}"`, maxTextLength: 37, id: `tooltip-${label.name}` }) }, 'attribute-' +
165
+ seriesIndex.toString() +
166
+ '-' +
167
+ pointIndex.toString() +
168
+ '-label-' +
169
+ label.name)))] }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to add labels to query." })] })] }) }));
170
+ }
131
171
  return null;
132
- }
133
- // Return the closest point as the highlighted point
134
- const closestPointPerSeries = series.map(function (s) {
135
- const distances = s.values.map(d => {
136
- const x = xScale(d[0]) + margin / 2;
137
- const y = yScale(d[1]) - margin / 3;
138
- return Math.sqrt(Math.pow(pos[0] - x, 2) + Math.pow(pos[1] - y, 2));
139
- });
140
- const pointIndex = d3.minIndex(distances);
141
- const minDistance = distances[pointIndex];
142
- return {
143
- pointIndex,
144
- distance: minDistance,
145
- };
146
- });
147
- const closestSeriesIndex = d3.minIndex(closestPointPerSeries, s => s.distance);
148
- const pointIndex = closestPointPerSeries[closestSeriesIndex].pointIndex;
149
- const point = series[closestSeriesIndex].values[pointIndex];
150
- return {
151
- seriesIndex: closestSeriesIndex,
152
- labels: series[closestSeriesIndex].metric,
153
- timestamp: point[0],
154
- valuePerSecond: point[1],
155
- value: point[2],
156
- duration: point[3],
157
- x: xScale(point[0]),
158
- y: yScale(point[1]),
159
- };
160
- }, [pos, series, xScale, yScale, margin]);
161
- const onMouseDown = (e) => {
162
- // only left mouse button
163
- if (e.button !== 0) {
164
- return;
165
- }
166
- // X/Y coordinate array relative to svg
167
- const rel = pointer(e);
168
- const xCoordinate = rel[0];
169
- const xCoordinateWithoutMargin = xCoordinate - margin;
170
- if (xCoordinateWithoutMargin >= 0) {
171
- setRelPos(xCoordinateWithoutMargin);
172
- setDragging(true);
173
- }
174
- e.stopPropagation();
175
- e.preventDefault();
176
- };
177
- const onMouseUp = (e) => {
178
- setDragging(false);
179
- if (relPos === -1) {
180
- // MouseDown happened outside of this element.
181
- return;
182
- }
183
- // This is a normal click. We tolerate tiny movements to still be a
184
- // click as they can occur when clicking based on user feedback.
185
- if (Math.abs(relPos - pos[0]) <= 1) {
186
- setRelPos(-1);
187
- return;
188
- }
189
- let startPos = relPos;
190
- let endPos = pos[0];
191
- if (startPos > endPos) {
192
- startPos = pos[0];
193
- endPos = relPos;
194
- }
195
- const startCorrection = 10;
196
- const endCorrection = 30;
197
- const firstTime = xScale.invert(startPos - startCorrection).valueOf();
198
- const secondTime = xScale.invert(endPos - endCorrection).valueOf();
199
- setTimeRange(DateTimeRange.fromAbsoluteDates(firstTime, secondTime));
200
- setRelPos(-1);
201
- e.stopPropagation();
202
- e.preventDefault();
203
- };
204
- return (_jsxs(_Fragment, { children: [_jsx(MetricsContextMenu, { onAddLabelMatcher: addLabelMatcher, menuId: MENU_ID, highlighted: highlighted, trackVisibility: trackVisibility, utilizationMetrics: true }), highlighted != null && hovering && !dragging && pos[0] !== 0 && pos[1] !== 0 && (_jsx("div", { onMouseMove: onMouseMove, onMouseEnter: () => setHovering(true), onMouseLeave: () => setHovering(false), children: !isContextMenuOpen && (_jsx(MetricsTooltip, { x: pos[0] + margin, y: pos[1] + margin, highlighted: highlighted, contextElement: graph.current, sampleType: name, sampleUnit: getYAxisUnit(name), delta: false, utilizationMetrics: true })) })), _jsx("div", { ref: graph, onMouseEnter: function () {
205
- setHovering(true);
206
- }, onMouseLeave: () => setHovering(false), onContextMenu: displayMenu, children: _jsxs("svg", { width: `${width}px`, height: `${height + margin}px`, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onMouseMove: onMouseMove, children: [_jsx("g", { transform: `translate(${margin}, 0)`, children: dragging && (_jsx("g", { className: "zoom-time-rect", children: _jsx("rect", { className: "bar", x: pos[0] - relPos < 0 ? pos[0] : relPos, y: 0, height: height, width: Math.abs(pos[0] - relPos), fill: 'rgba(0, 0, 0, 0.125)' }) })) }), _jsxs("g", { transform: `translate(${margin * 1.5}, ${margin / 1.5})`, children: [_jsxs("g", { className: "y axis", textAnchor: "end", fontSize: "10", fill: "none", children: [yScale.ticks(3).map((d, i, allTicks) => {
207
- let decimals = 2;
208
- const intervalBetweenTicks = allTicks[1] - allTicks[0];
209
- if (intervalBetweenTicks < 1) {
210
- const precision = getPrecision(intervalBetweenTicks);
211
- decimals = precision;
212
- }
213
- return (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
214
- /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
215
- transform: `translate(0, ${yScale(d)})`, children: [_jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x2: -6 }), _jsx("text", { fill: "currentColor", x: -9, dy: '0.32em', children: valueFormatter(d, getYAxisUnit(name), decimals) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: yScale(d), y2: yScale(d) }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`));
216
- }), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: 0, x2: 0, y1: 0, y2: height - margin }), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(to), x2: xScale(to), y1: 0, y2: height - margin }), _jsx("g", { transform: `translate(${-margin}, ${(height - margin) / 2}) rotate(270)`, children: _jsx("text", { fill: "currentColor", dy: "-0.7em", className: "text-sm capitalize", textAnchor: "middle", children: humanReadableName }) })] }), _jsxs("g", { className: "x axis", fill: "none", fontSize: "10", textAnchor: "middle", transform: `translate(0,${height - margin})`, children: [xScale.ticks(5).map((d, i) => (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
217
- /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
218
- transform: `translate(${xScale(d)}, 0)`, children: [_jsx("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-500" }), _jsx("text", { fill: "currentColor", dy: ".71em", y: 9, children: formatDate(d, formatForTimespan(from, to), timezone) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(d), x2: xScale(d), y1: 0, y2: -height + margin }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`))), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: 0, x2: graphWidth, y1: 0, y2: 0 }), _jsx("g", { transform: `translate(${(width - 2.5 * margin) / 2}, ${margin / 2})`, children: _jsx("text", { fill: "currentColor", dy: ".71em", y: 5, className: "text-sm", children: "Time" }) })] }), _jsx("g", { className: "lines fill-transparent", transform: graphTransform, children: series.map((s, i) => {
219
- const isLimit = s.metric.findIndex(m => m.name === '__type__' && m.value === 'limit') > -1;
220
- const strokeDasharray = isLimit ? '8 4' : '';
221
- return (_jsx("g", { className: "line cursor-pointer", children: _jsx(MetricsSeries, { data: s, line: l, color: getSeriesColor(s.metric), strokeWidth: s.isSelected === true
222
- ? lineStrokeSelected
223
- : hovering && highlighted != null && i === highlighted.seriesIndex
224
- ? lineStrokeHover
225
- : lineStroke, strokeDasharray: strokeDasharray, xScale: xScale, yScale: yScale, onClick: () => {
226
- if (highlighted != null && onSelectedSeriesChange != null) {
227
- onSelectedSeriesChange(highlighted.labels.map(l => ({
228
- key: l.name,
229
- value: l.value,
230
- })));
231
- // reset the selected_timeframe
232
- setSelectedTimeframe(undefined);
233
- }
234
- } }) }, i));
235
- }) })] })] }) })] }));
172
+ } }));
236
173
  };
237
- const UtilizationMetrics = ({ data, addLabelMatcher, setTimeRange, utilizationMetricsLoading, name, humanReadableName, from, to, onSelectedSeriesChange, }) => {
174
+ const UtilizationMetrics = ({ data, setTimeRange, utilizationMetricsLoading, humanReadableName, from, to, yAxisUnit, addLabelMatcher, onSeriesClick, onSelectedSeriesChange: _onSelectedSeriesChange, }) => {
238
175
  const { isDarkMode } = useParcaContext();
239
176
  const { width, height, margin, heightStyle } = useMetricsGraphDimensions(false, true);
240
- return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "w-full relative", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: utilizationMetricsLoading === true ? (_jsx(MetricsGraphSkeleton, { heightStyle: heightStyle, isDarkMode: isDarkMode, isMini: true })) : (_jsx(RawUtilizationMetrics, { data: data, addLabelMatcher: addLabelMatcher, setTimeRange: setTimeRange, width: width, height: height, margin: margin, name: name, humanReadableName: humanReadableName, from: from, to: to, onSelectedSeriesChange: onSelectedSeriesChange })) }, "utilization-metrics-graph-loaded") }));
177
+ const transformedData = useMemo(() => transformMetricSeriesToSeries(data), [data]);
178
+ const contextMenuItems = useMemo(() => {
179
+ return addLabelMatcher != null ? createUtilizationContextMenuItems(addLabelMatcher, data) : [];
180
+ }, [addLabelMatcher, data]);
181
+ return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "w-full relative", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: utilizationMetricsLoading === true ? (_jsx(MetricsGraphSkeleton, { heightStyle: heightStyle, isDarkMode: isDarkMode, isMini: true })) : (_jsx(RawUtilizationMetrics, { data: transformedData, originalData: data, setTimeRange: setTimeRange, width: width, height: height, margin: margin, humanReadableName: humanReadableName, from: from, to: to, yAxisUnit: yAxisUnit, contextMenuItems: contextMenuItems, onSeriesClick: onSeriesClick })) }, "utilization-metrics-graph-loaded") }));
241
182
  };
242
183
  export default UtilizationMetrics;
@@ -1,45 +1,38 @@
1
- import { Label, MetricsSeries as MetricsSeriesPb } from '@parca/client';
1
+ import React from 'react';
2
2
  import { DateTimeRange } from '@parca/components';
3
- import { MergedProfileSelection } from '..';
3
+ import { ContextMenuItem, ContextMenuItemOrSubmenu, ContextMenuSubmenu } from './MetricsContextMenu';
4
4
  interface Props {
5
- data: MetricsSeriesPb[];
5
+ data: Series[];
6
6
  from: number;
7
7
  to: number;
8
- profile: MergedProfileSelection | null;
9
- onSampleClick: (timestamp: number, value: number, labels: Label[], duration: number) => void;
10
- addLabelMatcher: (labels: {
11
- key: string;
12
- value: string;
13
- } | Array<{
14
- key: string;
15
- value: string;
16
- }>) => void;
8
+ onSampleClick: (closestPoint: SeriesPoint) => void;
17
9
  setTimeRange: (range: DateTimeRange) => void;
18
- sampleType: string;
19
- sampleUnit: string;
10
+ yAxisLabel: string;
11
+ yAxisUnit: string;
20
12
  width?: number;
21
13
  height?: number;
22
14
  margin?: number;
23
- sumBy?: string[];
15
+ selectedPoint?: SeriesPoint | null;
16
+ contextMenuItems?: ContextMenuItemOrSubmenu[];
17
+ renderTooltipContent?: (seriesIndex: number, pointIndex: number) => React.ReactNode;
18
+ }
19
+ export interface SeriesPoint {
20
+ seriesIndex: number;
21
+ pointIndex: number;
24
22
  }
25
23
  export interface HighlightedSeries {
26
24
  seriesIndex: number;
27
- labels: Label[];
28
- timestamp: number;
29
- value: number;
30
- valuePerSecond: number;
31
- duration: number;
25
+ pointIndex: number;
32
26
  x: number;
33
27
  y: number;
34
28
  }
35
29
  export interface Series {
36
- metric: Label[];
37
- values: number[][];
38
- labelset: string;
39
- isSelected?: boolean;
30
+ id: string;
31
+ values: Array<[number, number]>;
40
32
  }
41
- declare const MetricsGraph: ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, sampleType, sampleUnit, width, height, margin, sumBy, }: Props) => JSX.Element;
33
+ declare const MetricsGraph: ({ data, from, to, onSampleClick, setTimeRange, yAxisLabel, yAxisUnit, width, height, margin, selectedPoint, contextMenuItems, renderTooltipContent, }: Props) => JSX.Element;
42
34
  export default MetricsGraph;
35
+ export type { ContextMenuItemOrSubmenu, ContextMenuItem, ContextMenuSubmenu };
43
36
  export declare const parseValue: (value: string) => number | null;
44
- export declare const RawMetricsGraph: ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, sampleType, sampleUnit, width, height, margin, sumBy, }: Props) => JSX.Element;
37
+ export declare const RawMetricsGraph: ({ data, from, to, onSampleClick, setTimeRange, yAxisLabel, yAxisUnit, width, height, margin, selectedPoint, contextMenuItems, renderTooltipContent, }: Props) => JSX.Element;
45
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/MetricsGraph/index.tsx"],"names":[],"mappings":"AAoBA,OAAO,EAAC,KAAK,EAAiB,aAAa,IAAI,eAAe,EAAC,MAAM,eAAe,CAAC;AACrF,OAAO,EAAC,aAAa,EAAkB,MAAM,mBAAmB,CAAC;AASjE,OAAO,EAAC,sBAAsB,EAAC,MAAM,IAAI,CAAC;AAO1C,UAAU,KAAK;IACb,IAAI,EAAE,eAAe,EAAE,CAAC;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,sBAAsB,GAAG,IAAI,CAAC;IACvC,aAAa,EAAE,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7F,eAAe,EAAE,CACf,MAAM,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,GAAG,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,KACvE,IAAI,CAAC;IACV,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,KAAK,EAAE,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,QAAA,MAAM,YAAY,GAAI,kIAcnB,KAAK,KAAG,GAAG,CAAC,OA2Bd,CAAC;AAEF,eAAe,YAAY,CAAC;AAE5B,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,IAKnD,CAAC;AAKF,eAAO,MAAM,eAAe,GAAI,kIAc7B,KAAK,KAAG,GAAG,CAAC,OA6fd,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/MetricsGraph/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAgE,MAAM,OAAO,CAAC;AAOrF,OAAO,EAAC,aAAa,EAAkB,MAAM,mBAAmB,CAAC;AAKjE,OAA2B,EACzB,eAAe,EACf,wBAAwB,EACxB,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAI9B,UAAU,KAAK;IACb,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,CAAC,YAAY,EAAE,WAAW,KAAK,IAAI,CAAC;IACnD,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,WAAW,GAAG,IAAI,CAAC;IACnC,gBAAgB,CAAC,EAAE,wBAAwB,EAAE,CAAC;IAC9C,oBAAoB,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CACrF;AAED,MAAM,WAAW,WAAW;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;CACX;AAED,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,KAAK,CAAC,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjC;AAED,QAAA,MAAM,YAAY,GAAI,uJAcnB,KAAK,KAAG,GAAG,CAAC,OA2Bd,CAAC;AAEF,eAAe,YAAY,CAAC;AAC5B,YAAY,EAAC,wBAAwB,EAAE,eAAe,EAAE,kBAAkB,EAAC,CAAC;AAE5E,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,IAKnD,CAAC;AAKF,eAAO,MAAM,eAAe,GAAI,uJAc7B,KAAK,KAAG,GAAG,CAAC,OA6ad,CAAC"}