@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.
- package/CHANGELOG.md +8 -0
- package/dist/GraphTooltipArrow/Content.d.ts.map +1 -1
- package/dist/GraphTooltipArrow/Content.js +1 -1
- package/dist/MetricsGraph/MetricsContextMenu/index.d.ts +20 -11
- package/dist/MetricsGraph/MetricsContextMenu/index.d.ts.map +1 -1
- package/dist/MetricsGraph/MetricsContextMenu/index.js +16 -20
- package/dist/MetricsGraph/MetricsTooltip/index.d.ts +2 -8
- package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
- package/dist/MetricsGraph/MetricsTooltip/index.js +46 -55
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +2 -5
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +1 -1
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +126 -205
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +9 -17
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +1 -1
- package/dist/MetricsGraph/UtilizationMetrics/index.js +149 -208
- package/dist/MetricsGraph/index.d.ts +19 -26
- package/dist/MetricsGraph/index.d.ts.map +1 -1
- package/dist/MetricsGraph/index.js +50 -115
- package/dist/ProfileFlameGraph/index.d.ts.map +1 -1
- package/dist/ProfileFlameGraph/index.js +3 -1
- package/dist/ProfileMetricsGraph/index.d.ts +1 -1
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +232 -23
- package/dist/ProfileSelector/MetricsGraphSection.d.ts +1 -4
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +8 -4
- package/dist/ProfileSelector/index.d.ts +3 -6
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +2 -2
- package/dist/ProfileSource.d.ts +9 -6
- package/dist/ProfileSource.d.ts.map +1 -1
- package/dist/ProfileSource.js +23 -8
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.d.ts.map +1 -1
- package/dist/ProfileView/components/GroupByLabelsDropdown/index.js +5 -1
- package/dist/ProfileView/components/ProfileFilters/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/index.js +6 -5
- package/dist/styles.css +1 -1
- package/dist/useQuery.js +1 -1
- package/package.json +7 -7
- package/src/GraphTooltipArrow/Content.tsx +2 -4
- package/src/MetricsGraph/MetricsContextMenu/index.tsx +78 -66
- package/src/MetricsGraph/MetricsTooltip/index.tsx +53 -210
- package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +242 -434
- package/src/MetricsGraph/UtilizationMetrics/index.tsx +312 -448
- package/src/MetricsGraph/index.tsx +99 -185
- package/src/ProfileFlameGraph/index.tsx +3 -1
- package/src/ProfileMetricsGraph/index.tsx +430 -37
- package/src/ProfileSelector/MetricsGraphSection.tsx +12 -8
- package/src/ProfileSelector/index.tsx +5 -5
- package/src/ProfileSource.tsx +34 -17
- package/src/ProfileView/components/GroupByLabelsDropdown/index.tsx +15 -3
- package/src/ProfileView/components/ProfileFilters/index.tsx +23 -3
- package/src/useQuery.tsx +1 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs
|
|
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 {
|
|
15
|
-
import
|
|
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
|
|
19
|
-
import {
|
|
20
|
-
import
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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,
|
|
123
|
+
const RawUtilizationMetrics = ({ data, originalData, setTimeRange, width, height, margin, humanReadableName, from, to, yAxisUnit, contextMenuItems, onSeriesClick, }) => {
|
|
64
124
|
const { timezone } = useParcaContext();
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
|
1
|
+
import React from 'react';
|
|
2
2
|
import { DateTimeRange } from '@parca/components';
|
|
3
|
-
import {
|
|
3
|
+
import { ContextMenuItem, ContextMenuItemOrSubmenu, ContextMenuSubmenu } from './MetricsContextMenu';
|
|
4
4
|
interface Props {
|
|
5
|
-
data:
|
|
5
|
+
data: Series[];
|
|
6
6
|
from: number;
|
|
7
7
|
to: number;
|
|
8
|
-
|
|
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
|
-
|
|
19
|
-
|
|
10
|
+
yAxisLabel: string;
|
|
11
|
+
yAxisUnit: string;
|
|
20
12
|
width?: number;
|
|
21
13
|
height?: number;
|
|
22
14
|
margin?: number;
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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":"
|
|
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"}
|