@parca/profile 0.16.478 → 0.16.480
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 +12 -0
- package/dist/MetricsGraph/MetricsContextMenu/index.d.ts +2 -1
- package/dist/MetricsGraph/MetricsContextMenu/index.d.ts.map +1 -1
- package/dist/MetricsGraph/MetricsContextMenu/index.js +13 -2
- package/dist/MetricsGraph/MetricsTooltip/index.d.ts +2 -1
- package/dist/MetricsGraph/MetricsTooltip/index.d.ts.map +1 -1
- package/dist/MetricsGraph/MetricsTooltip/index.js +23 -5
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +1 -10
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +1 -1
- package/dist/MetricsGraph/UtilizationMetrics/index.js +30 -23
- package/dist/ProfileSelector/index.d.ts +9 -7
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/package.json +2 -2
- package/src/MetricsGraph/MetricsContextMenu/index.tsx +16 -2
- package/src/MetricsGraph/MetricsTooltip/index.tsx +87 -15
- package/src/MetricsGraph/UtilizationMetrics/index.tsx +39 -37
- package/src/ProfileSelector/index.tsx +9 -7
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,18 @@
|
|
|
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.16.480](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.479...@parca/profile@0.16.480) (2025-02-25)
|
|
7
|
+
|
|
8
|
+
## 0.23.1 (2025-02-24)
|
|
9
|
+
|
|
10
|
+
**Note:** Version bump only for package @parca/profile
|
|
11
|
+
|
|
12
|
+
## [0.16.479](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.478...@parca/profile@0.16.479) (2025-02-25)
|
|
13
|
+
|
|
14
|
+
## 0.23.1 (2025-02-24)
|
|
15
|
+
|
|
16
|
+
**Note:** Version bump only for package @parca/profile
|
|
17
|
+
|
|
6
18
|
## 0.16.478 (2025-02-25)
|
|
7
19
|
|
|
8
20
|
## 0.23.1 (2025-02-24)
|
|
@@ -10,7 +10,8 @@ interface MetricsContextMenuProps {
|
|
|
10
10
|
}>) => void;
|
|
11
11
|
highlighted: HighlightedSeries | null;
|
|
12
12
|
trackVisibility: (isVisible: boolean) => void;
|
|
13
|
+
utilizationMetrics?: boolean;
|
|
13
14
|
}
|
|
14
|
-
declare const MetricsContextMenu: ({ menuId, onAddLabelMatcher, highlighted, trackVisibility, }: MetricsContextMenuProps) => JSX.Element;
|
|
15
|
+
declare const MetricsContextMenu: ({ menuId, onAddLabelMatcher, highlighted, trackVisibility, utilizationMetrics, }: MetricsContextMenuProps) => JSX.Element;
|
|
15
16
|
export default MetricsContextMenu;
|
|
16
17
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/MetricsContextMenu/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAC,iBAAiB,EAAC,MAAM,KAAK,CAAC;AAEtC,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,CACjB,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,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtC,eAAe,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/MetricsContextMenu/index.tsx"],"names":[],"mappings":"AAmBA,OAAO,EAAC,iBAAiB,EAAC,MAAM,KAAK,CAAC;AAEtC,UAAU,uBAAuB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,iBAAiB,EAAE,CACjB,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,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACtC,eAAe,EAAE,CAAC,SAAS,EAAE,OAAO,KAAK,IAAI,CAAC;IAC9C,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AASD,QAAA,MAAM,kBAAkB,qFAMrB,uBAAuB,KAAG,GAAG,CAAC,OAmDhC,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -14,7 +14,13 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
14
14
|
import { Icon } from '@iconify/react';
|
|
15
15
|
import { Item, Menu, Submenu } from 'react-contexify';
|
|
16
16
|
import { useParcaContext } from '@parca/components';
|
|
17
|
-
const
|
|
17
|
+
const transformUtilizationLabels = (label, utilizationMetrics) => {
|
|
18
|
+
if (utilizationMetrics) {
|
|
19
|
+
return label.replace('attributes.', '').replace('attributes_resource.', '');
|
|
20
|
+
}
|
|
21
|
+
return label;
|
|
22
|
+
};
|
|
23
|
+
const MetricsContextMenu = ({ menuId, onAddLabelMatcher, highlighted, trackVisibility, utilizationMetrics = false, }) => {
|
|
18
24
|
const { isDarkMode } = useParcaContext();
|
|
19
25
|
const labels = highlighted?.labels.filter((label) => label.name !== '__name__');
|
|
20
26
|
const handleFocusOnSingleSeries = () => {
|
|
@@ -24,6 +30,11 @@ const MetricsContextMenu = ({ menuId, onAddLabelMatcher, highlighted, trackVisib
|
|
|
24
30
|
}));
|
|
25
31
|
labelsToAdd !== undefined && onAddLabelMatcher(labelsToAdd);
|
|
26
32
|
};
|
|
27
|
-
return (_jsxs(Menu, { id: menuId, onVisibilityChange: trackVisibility, theme: isDarkMode ? 'dark' : '', children: [_jsx(Item, { id: "focus-on-single-series", onClick: handleFocusOnSingleSeries, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:star" }), _jsx("div", { children: "Focus only on this series" })] }) }), _jsx(Submenu, { label: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "material-symbols:add" }), _jsx("div", { children: "Add to query" })] }), children: _jsx("div", { className: "max-h-[300px] overflow-scroll", children: labels?.map((label) => (_jsx(Item, { id: label.name, onClick: () =>
|
|
33
|
+
return (_jsxs(Menu, { id: menuId, onVisibilityChange: trackVisibility, theme: isDarkMode ? 'dark' : '', children: [_jsx(Item, { id: "focus-on-single-series", onClick: handleFocusOnSingleSeries, children: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "ph:star" }), _jsx("div", { children: "Focus only on this series" })] }) }), _jsx(Submenu, { label: _jsxs("div", { className: "flex w-full items-center gap-2", children: [_jsx(Icon, { icon: "material-symbols:add" }), _jsx("div", { children: "Add to query" })] }), children: _jsx("div", { className: "max-h-[300px] overflow-scroll", children: labels?.map((label) => (_jsx(Item, { id: label.name, onClick: () => {
|
|
34
|
+
onAddLabelMatcher({
|
|
35
|
+
key: transformUtilizationLabels(label.name, utilizationMetrics),
|
|
36
|
+
value: label.value,
|
|
37
|
+
});
|
|
38
|
+
}, className: "max-w-[400px] overflow-hidden", children: _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, utilizationMetrics)}="${label.value}"` }) }, label.name))) }) })] }));
|
|
28
39
|
};
|
|
29
40
|
export default MetricsContextMenu;
|
|
@@ -6,7 +6,8 @@ interface Props {
|
|
|
6
6
|
contextElement: Element | null;
|
|
7
7
|
sampleUnit: string;
|
|
8
8
|
delta: boolean;
|
|
9
|
+
utilizationMetrics?: boolean;
|
|
9
10
|
}
|
|
10
|
-
declare const MetricsTooltip: ({ x, y, highlighted, contextElement, sampleUnit, delta, }: Props) => JSX.Element;
|
|
11
|
+
declare const MetricsTooltip: ({ x, y, highlighted, contextElement, sampleUnit, delta, utilizationMetrics, }: Props) => JSX.Element;
|
|
11
12
|
export default MetricsTooltip;
|
|
12
13
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/MetricsTooltip/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAC,iBAAiB,EAAC,MAAM,KAAK,CAAC;AAEtC,UAAU,KAAK;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,WAAW,EAAE,iBAAiB,CAAC;IAC/B,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/MetricsTooltip/index.tsx"],"names":[],"mappings":"AAuBA,OAAO,EAAC,iBAAiB,EAAC,MAAM,KAAK,CAAC;AAEtC,UAAU,KAAK;IACb,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,WAAW,EAAE,iBAAiB,CAAC;IAC/B,cAAc,EAAE,OAAO,GAAG,IAAI,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,OAAO,CAAC;IACf,kBAAkB,CAAC,EAAE,OAAO,CAAC;CAC9B;AA8BD,QAAA,MAAM,cAAc,kFAQjB,KAAK,KAAG,GAAG,CAAC,OAoMd,CAAC;AAEF,eAAe,cAAc,CAAC"}
|
|
@@ -11,7 +11,7 @@ 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 { useEffect, useState } from 'react';
|
|
14
|
+
import { useEffect, useMemo, useState } from 'react';
|
|
15
15
|
import { Icon } from '@iconify/react';
|
|
16
16
|
import { usePopper } from 'react-popper';
|
|
17
17
|
import { TextWithTooltip, useParcaContext } from '@parca/components';
|
|
@@ -42,7 +42,7 @@ function generateGetBoundingClientRect(contextElement, x = 0, y = 0) {
|
|
|
42
42
|
bottom: domRect.y + y,
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
|
-
const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta, }) => {
|
|
45
|
+
const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta, utilizationMetrics = false, }) => {
|
|
46
46
|
const { timezone } = useParcaContext();
|
|
47
47
|
const [popperElement, setPopperElement] = useState(null);
|
|
48
48
|
const { styles, attributes, ...popperProps } = usePopper(virtualElement, popperElement, {
|
|
@@ -65,6 +65,24 @@ const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta,
|
|
|
65
65
|
],
|
|
66
66
|
});
|
|
67
67
|
const update = popperProps.update;
|
|
68
|
+
const attributesMap = useMemo(() => {
|
|
69
|
+
return highlighted.labels
|
|
70
|
+
.filter(label => label.name.startsWith('attributes.') && !label.name.startsWith('attributes_resource.'))
|
|
71
|
+
.reduce((acc, label) => {
|
|
72
|
+
const key = label.name.replace('attributes.', '');
|
|
73
|
+
acc[key] = label.value;
|
|
74
|
+
return acc;
|
|
75
|
+
}, {});
|
|
76
|
+
}, [highlighted.labels]);
|
|
77
|
+
const attributesResourceMap = useMemo(() => {
|
|
78
|
+
return highlighted.labels
|
|
79
|
+
.filter(label => label.name.startsWith('attributes_resource.'))
|
|
80
|
+
.reduce((acc, label) => {
|
|
81
|
+
const key = label.name.replace('attributes_resource.', '');
|
|
82
|
+
acc[key] = label.value;
|
|
83
|
+
return acc;
|
|
84
|
+
}, {});
|
|
85
|
+
}, [highlighted.labels]);
|
|
68
86
|
useEffect(() => {
|
|
69
87
|
if (contextElement != null) {
|
|
70
88
|
virtualElement.getBoundingClientRect = generateGetBoundingClientRect(contextElement, x, y);
|
|
@@ -73,8 +91,8 @@ const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta,
|
|
|
73
91
|
}, [x, y, contextElement, update]);
|
|
74
92
|
const nameLabel = highlighted?.labels.find(e => e.name === '__name__');
|
|
75
93
|
const highlightedNameLabel = nameLabel !== undefined ? nameLabel : { name: '', value: '' };
|
|
76
|
-
return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-20", children: _jsx("div", { className: "flex max-w-lg", children: _jsx("div", { className: "m-auto", children: _jsx("div", { className: "rounded-lg border-gray-300 bg-gray-50 p-3 opacity-90 shadow-lg dark:border-gray-500 dark:bg-gray-900", style: { borderWidth: 1 }, children: _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: [delta ? (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4 pr-3", children: "Per\u00A0Second" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit === 'nanoseconds' ? 'CPU Cores' : sampleUnit, 5) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })] })) : (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] })), highlighted.duration > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Duration" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.duration, 'nanoseconds', 2) })] })), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(highlighted.timestamp, timePattern(timezone), timezone) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
|
|
77
|
-
|
|
78
|
-
|
|
94
|
+
return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-20", children: _jsx("div", { className: "flex max-w-lg", children: _jsx("div", { className: "m-auto", children: _jsx("div", { className: "rounded-lg border-gray-300 bg-gray-50 p-3 opacity-90 shadow-lg dark:border-gray-500 dark:bg-gray-900", style: { borderWidth: 1 }, children: _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: [delta ? (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4 pr-3", children: "Per\u00A0Second" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit === 'nanoseconds' ? 'CPU Cores' : sampleUnit, 5) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })] })) : (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] })), highlighted.duration > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Duration" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.duration, 'nanoseconds', 2) })] })), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(highlighted.timestamp, timePattern(timezone), timezone) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: utilizationMetrics ? (_jsxs(_Fragment, { children: [Object.keys(attributesResourceMap).length > 0 && (_jsx("span", { className: "text-sm font-bold text-gray-700 dark:text-white", children: "Resource Attributes" })), _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: `${name.replace('attributes.', '')}="${attributesResourceMap[name]}"`, maxTextLength: 48, id: `tooltip-${name}-${attributesResourceMap[name]}` }) }, name))) }), Object.keys(attributesMap).length > 0 && (_jsx("span", { className: "text-sm font-bold text-gray-700 dark:text-white", children: "Attributes" })), _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: `${name.replace('attributes.', '')}="${attributesMap[name]}"`, maxTextLength: 48, id: `tooltip-${name}-${attributesMap[name]}` }) }, name))) })] })) : (_jsx(_Fragment, { children: highlighted.labels
|
|
95
|
+
.filter((label) => label.name !== '__name__')
|
|
96
|
+
.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: `${label.name}="${label.value}"`, maxTextLength: 37, id: `tooltip-${label.name}` }) }, 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." })] })] }) }) }) }) }) }));
|
|
79
97
|
};
|
|
80
98
|
export default MetricsTooltip;
|
|
@@ -1,15 +1,6 @@
|
|
|
1
1
|
import { DateTimeRange } from '@parca/components';
|
|
2
2
|
import { Matcher } from '@parca/parser';
|
|
3
|
-
|
|
4
|
-
timestamp: number;
|
|
5
|
-
value: number;
|
|
6
|
-
resource: {
|
|
7
|
-
[key: string]: string;
|
|
8
|
-
};
|
|
9
|
-
attributes: {
|
|
10
|
-
[key: string]: string;
|
|
11
|
-
};
|
|
12
|
-
}
|
|
3
|
+
import { type UtilizationMetrics as MetricSeries } from '../../ProfileSelector';
|
|
13
4
|
interface CommonProps {
|
|
14
5
|
data: MetricSeries[];
|
|
15
6
|
addLabelMatcher: (labels: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/UtilizationMetrics/index.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAAC,aAAa,EAAwC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/MetricsGraph/UtilizationMetrics/index.tsx"],"names":[],"mappings":"AAqBA,OAAO,EAAC,aAAa,EAAwC,MAAM,mBAAmB,CAAC;AACvF,OAAO,EAAC,OAAO,EAAC,MAAM,eAAe,CAAC;AAItC,OAAO,EAAC,KAAK,kBAAkB,IAAI,YAAY,EAAC,MAAM,uBAAuB,CAAC;AAM9E,UAAU,WAAW;IACnB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,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,sBAAsB,CAAC,EAAE,OAAO,EAAE,CAAC;CACpC;AAQD,KAAK,KAAK,GAAG,WAAW,GAAG;IACzB,IAAI,EAAE,YAAY,EAAE,CAAC;IACrB,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,yBAAyB,CAAC,EAAE,OAAO,CAAC;CACrC,CAAC;AAicF,QAAA,MAAM,kBAAkB,gGAMrB,KAAK,KAAG,GAAG,CAAC,OA6Bd,CAAC;AAEF,eAAe,kBAAkB,CAAC"}
|
|
@@ -24,22 +24,24 @@ import MetricsContextMenu from '../MetricsContextMenu';
|
|
|
24
24
|
import MetricsTooltip from '../MetricsTooltip';
|
|
25
25
|
import { useMetricsGraphDimensions } from '../useMetricsGraphDimensions';
|
|
26
26
|
function transformToSeries(data) {
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
.
|
|
30
|
-
.
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
27
|
+
const series = data.reduce(function (agg, s) {
|
|
28
|
+
if (s.labelset !== undefined) {
|
|
29
|
+
const metric = s.labelset.labels.sort((a, b) => a.name.localeCompare(b.name));
|
|
30
|
+
agg.push({
|
|
31
|
+
metric,
|
|
32
|
+
values: s.samples.reduce(function (agg, d) {
|
|
33
|
+
if (d.timestamp !== undefined && d.value !== undefined) {
|
|
34
|
+
agg.push([d.timestamp, d.value]);
|
|
35
|
+
}
|
|
36
|
+
return agg;
|
|
37
|
+
}, []),
|
|
38
|
+
labelset: metric.map(m => `${m.name}=${m.value}`).join(','),
|
|
39
|
+
});
|
|
37
40
|
}
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}, {});
|
|
41
|
+
return agg;
|
|
42
|
+
}, []);
|
|
41
43
|
// Sort values by timestamp for each series
|
|
42
|
-
return
|
|
44
|
+
return series.map(series => ({
|
|
43
45
|
...series,
|
|
44
46
|
values: series.values.sort((a, b) => a[0] - b[0]),
|
|
45
47
|
}));
|
|
@@ -57,13 +59,12 @@ const RawUtilizationMetrics = ({ data, addLabelMatcher, setTimeRange, width, hei
|
|
|
57
59
|
const lineStrokeHover = '2px';
|
|
58
60
|
const lineStrokeSelected = '3px';
|
|
59
61
|
const graphWidth = width - margin * 1.5 - margin / 2;
|
|
60
|
-
|
|
61
|
-
const timeExtent = d3.extent(data, d => d.timestamp);
|
|
62
|
+
const timeExtent = d3.extent(data.flatMap(d => d.samples.map(s => s.timestamp)));
|
|
62
63
|
const from = timeExtent[0] ?? 0;
|
|
63
64
|
const to = timeExtent[1] ?? 0;
|
|
64
65
|
const paddedFrom = from;
|
|
65
66
|
const paddedTo = to;
|
|
66
|
-
const series = transformToSeries(data);
|
|
67
|
+
const series = useMemo(() => transformToSeries(data), [data]);
|
|
67
68
|
const extentsY = series.map(function (s) {
|
|
68
69
|
return d3.extent(s.values, function (d) {
|
|
69
70
|
return d[1];
|
|
@@ -185,7 +186,7 @@ const RawUtilizationMetrics = ({ data, addLabelMatcher, setTimeRange, width, hei
|
|
|
185
186
|
e.stopPropagation();
|
|
186
187
|
e.preventDefault();
|
|
187
188
|
};
|
|
188
|
-
return (_jsxs(_Fragment, { children: [_jsx(MetricsContextMenu, { onAddLabelMatcher: addLabelMatcher, menuId: MENU_ID, highlighted: highlighted, trackVisibility: trackVisibility }), 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, sampleUnit: "%", delta: false })) })), _jsx("div", { ref: graph, onMouseEnter: function () {
|
|
189
|
+
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, sampleUnit: "%", delta: false, utilizationMetrics: true })) })), _jsx("div", { ref: graph, onMouseEnter: function () {
|
|
189
190
|
setHovering(true);
|
|
190
191
|
}, 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) => {
|
|
191
192
|
let decimals = 2;
|
|
@@ -201,12 +202,13 @@ const RawUtilizationMetrics = ({ data, addLabelMatcher, setTimeRange, width, hei
|
|
|
201
202
|
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
|
202
203
|
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", children: series.map((s, i) => {
|
|
203
204
|
let isSelected = false;
|
|
204
|
-
if (selectedSeriesMatchers != null &&
|
|
205
|
-
selectedSeriesMatchers.length > 0 &&
|
|
206
|
-
selectedSeriesMatchers.length === s.metric.length) {
|
|
205
|
+
if (selectedSeriesMatchers != null && selectedSeriesMatchers.length > 0) {
|
|
207
206
|
isSelected = selectedSeriesMatchers.every(m => {
|
|
208
207
|
for (let i = 0; i < s.metric.length; i++) {
|
|
209
|
-
if (s.metric[i].name
|
|
208
|
+
if (s.metric[i].name
|
|
209
|
+
.replace('attributes_resource.', '')
|
|
210
|
+
.replace('attributes.', '') === m.key &&
|
|
211
|
+
s.metric[i].value === m.value) {
|
|
210
212
|
return true;
|
|
211
213
|
}
|
|
212
214
|
}
|
|
@@ -219,7 +221,12 @@ const RawUtilizationMetrics = ({ data, addLabelMatcher, setTimeRange, width, hei
|
|
|
219
221
|
? lineStrokeHover
|
|
220
222
|
: lineStroke, xScale: xScale, yScale: yScale, onClick: () => {
|
|
221
223
|
if (highlighted != null) {
|
|
222
|
-
addLabelMatcher(highlighted.labels
|
|
224
|
+
addLabelMatcher(highlighted.labels
|
|
225
|
+
.filter(l => l.name.startsWith('attributes_resource.'))
|
|
226
|
+
.map(l => ({
|
|
227
|
+
key: l.name.replace('attributes_resource.', ''),
|
|
228
|
+
value: l.value,
|
|
229
|
+
})));
|
|
223
230
|
}
|
|
224
231
|
} }) }, i));
|
|
225
232
|
}) })] })] }) })] }));
|
|
@@ -20,14 +20,16 @@ interface ProfileSelectorFeatures {
|
|
|
20
20
|
disableProfileTypesDropdown?: boolean;
|
|
21
21
|
}
|
|
22
22
|
export interface UtilizationMetrics {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
attributes: {
|
|
29
|
-
[key: string]: string;
|
|
23
|
+
labelset: {
|
|
24
|
+
labels: Array<{
|
|
25
|
+
name: string;
|
|
26
|
+
value: string;
|
|
27
|
+
}>;
|
|
30
28
|
};
|
|
29
|
+
samples: Array<{
|
|
30
|
+
timestamp: number;
|
|
31
|
+
value: number;
|
|
32
|
+
}>;
|
|
31
33
|
}
|
|
32
34
|
export interface UtilizationLabels {
|
|
33
35
|
utilizationLabelNames?: string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,QAAQ,EAAE,cAAc,EAAuC,MAAM,OAAO,CAAC;AAErF,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAC,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAUvE,OAAO,EAAC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAC,gBAAgB,EAAC,MAAM,IAAI,CAAC;AASpC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB;IACjC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileSelector/index.tsx"],"names":[],"mappings":"AAaA,OAAO,EAAC,QAAQ,EAAE,cAAc,EAAuC,MAAM,OAAO,CAAC;AAErF,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAElD,OAAO,EAAC,oBAAoB,EAAE,kBAAkB,EAAC,MAAM,eAAe,CAAC;AAUvE,OAAO,EAAC,KAAK,gBAAgB,EAAC,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAC,gBAAgB,EAAC,MAAM,IAAI,CAAC;AASpC,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,UAAU,uBAAuB;IAC/B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,0BAA0B,CAAC,EAAE,OAAO,CAAC;IACrC,2BAA2B,CAAC,EAAE,OAAO,CAAC;CACvC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE;QACR,MAAM,EAAE,KAAK,CAAC;YACZ,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;SACf,CAAC,CAAC;KACJ,CAAC;IACF,OAAO,EAAE,KAAK,CAAC;QACb,SAAS,EAAE,MAAM,CAAC;QAClB,KAAK,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,iBAAiB;IAChC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,2BAA2B,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACjE,sBAAsB,CAAC,EAAE,MAAM,EAAE,CAAC;CACnC;AAED,UAAU,oBAAqB,SAAQ,uBAAuB;IAC5D,WAAW,EAAE,kBAAkB,CAAC;IAChC,cAAc,EAAE,cAAc,CAAC;IAC/B,aAAa,EAAE,CAAC,MAAM,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAClD,WAAW,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,IAAI,CAAC;IAC7C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,gBAAgB,GAAG,IAAI,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;IAC7B,gCAAgC,CAAC,EAAE,QAAQ,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC;IACrE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,kBAAkB,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC1C,yBAAyB,CAAC,EAAE,OAAO,CAAC;IACpC,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;CACvC;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,CAAC,EAAE,oBAAoB,CAAC;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;CAClB;AAED,eAAO,MAAM,eAAe,WAAY,kBAAkB,KAAG,mBAkB5D,CAAC;AAEF,QAAA,MAAM,eAAe,2UAkBlB,oBAAoB,KAAG,GAAG,CAAC,OA4M7B,CAAC;AAEF,eAAe,eAAe,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.480",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@headlessui/react": "^1.7.19",
|
|
@@ -75,5 +75,5 @@
|
|
|
75
75
|
"access": "public",
|
|
76
76
|
"registry": "https://registry.npmjs.org/"
|
|
77
77
|
},
|
|
78
|
-
"gitHead": "
|
|
78
|
+
"gitHead": "3866c635c33d8d37e52e41fbaf62e81da153c88e"
|
|
79
79
|
}
|
|
@@ -26,13 +26,22 @@ interface MetricsContextMenuProps {
|
|
|
26
26
|
) => void;
|
|
27
27
|
highlighted: HighlightedSeries | null;
|
|
28
28
|
trackVisibility: (isVisible: boolean) => void;
|
|
29
|
+
utilizationMetrics?: boolean;
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
const transformUtilizationLabels = (label: string, utilizationMetrics: boolean): string => {
|
|
33
|
+
if (utilizationMetrics) {
|
|
34
|
+
return label.replace('attributes.', '').replace('attributes_resource.', '');
|
|
35
|
+
}
|
|
36
|
+
return label;
|
|
37
|
+
};
|
|
38
|
+
|
|
31
39
|
const MetricsContextMenu = ({
|
|
32
40
|
menuId,
|
|
33
41
|
onAddLabelMatcher,
|
|
34
42
|
highlighted,
|
|
35
43
|
trackVisibility,
|
|
44
|
+
utilizationMetrics = false,
|
|
36
45
|
}: MetricsContextMenuProps): JSX.Element => {
|
|
37
46
|
const {isDarkMode} = useParcaContext();
|
|
38
47
|
const labels = highlighted?.labels.filter((label: Label) => label.name !== '__name__');
|
|
@@ -67,11 +76,16 @@ const MetricsContextMenu = ({
|
|
|
67
76
|
<Item
|
|
68
77
|
key={label.name}
|
|
69
78
|
id={label.name}
|
|
70
|
-
onClick={() =>
|
|
79
|
+
onClick={() => {
|
|
80
|
+
onAddLabelMatcher({
|
|
81
|
+
key: transformUtilizationLabels(label.name, utilizationMetrics),
|
|
82
|
+
value: label.value,
|
|
83
|
+
});
|
|
84
|
+
}}
|
|
71
85
|
className="max-w-[400px] overflow-hidden"
|
|
72
86
|
>
|
|
73
87
|
<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">
|
|
74
|
-
{`${label.name}="${label.value}"`}
|
|
88
|
+
{`${transformUtilizationLabels(label.name, utilizationMetrics)}="${label.value}"`}
|
|
75
89
|
</div>
|
|
76
90
|
</Item>
|
|
77
91
|
))}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useEffect, useState} from 'react';
|
|
14
|
+
import {useEffect, useMemo, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {Icon} from '@iconify/react';
|
|
17
17
|
import type {VirtualElement} from '@popperjs/core';
|
|
@@ -30,6 +30,7 @@ interface Props {
|
|
|
30
30
|
contextElement: Element | null;
|
|
31
31
|
sampleUnit: string;
|
|
32
32
|
delta: boolean;
|
|
33
|
+
utilizationMetrics?: boolean;
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
const virtualElement: VirtualElement = {
|
|
@@ -67,6 +68,7 @@ const MetricsTooltip = ({
|
|
|
67
68
|
contextElement,
|
|
68
69
|
sampleUnit,
|
|
69
70
|
delta,
|
|
71
|
+
utilizationMetrics = false,
|
|
70
72
|
}: Props): JSX.Element => {
|
|
71
73
|
const {timezone} = useParcaContext();
|
|
72
74
|
|
|
@@ -94,6 +96,29 @@ const MetricsTooltip = ({
|
|
|
94
96
|
|
|
95
97
|
const update = popperProps.update;
|
|
96
98
|
|
|
99
|
+
const attributesMap = useMemo(() => {
|
|
100
|
+
return highlighted.labels
|
|
101
|
+
.filter(
|
|
102
|
+
label =>
|
|
103
|
+
label.name.startsWith('attributes.') && !label.name.startsWith('attributes_resource.')
|
|
104
|
+
)
|
|
105
|
+
.reduce<Record<string, string>>((acc, label) => {
|
|
106
|
+
const key = label.name.replace('attributes.', '');
|
|
107
|
+
acc[key] = label.value;
|
|
108
|
+
return acc;
|
|
109
|
+
}, {});
|
|
110
|
+
}, [highlighted.labels]);
|
|
111
|
+
|
|
112
|
+
const attributesResourceMap = useMemo(() => {
|
|
113
|
+
return highlighted.labels
|
|
114
|
+
.filter(label => label.name.startsWith('attributes_resource.'))
|
|
115
|
+
.reduce<Record<string, string>>((acc, label) => {
|
|
116
|
+
const key = label.name.replace('attributes_resource.', '');
|
|
117
|
+
acc[key] = label.value;
|
|
118
|
+
return acc;
|
|
119
|
+
}, {});
|
|
120
|
+
}, [highlighted.labels]);
|
|
121
|
+
|
|
97
122
|
useEffect(() => {
|
|
98
123
|
if (contextElement != null) {
|
|
99
124
|
virtualElement.getBoundingClientRect = generateGetBoundingClientRect(contextElement, x, y);
|
|
@@ -167,20 +192,67 @@ const MetricsTooltip = ({
|
|
|
167
192
|
</table>
|
|
168
193
|
</span>
|
|
169
194
|
<span className="my-2 block text-gray-500">
|
|
170
|
-
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
195
|
+
{utilizationMetrics ? (
|
|
196
|
+
<>
|
|
197
|
+
{Object.keys(attributesResourceMap).length > 0 && (
|
|
198
|
+
<span className="text-sm font-bold text-gray-700 dark:text-white">
|
|
199
|
+
Resource Attributes
|
|
200
|
+
</span>
|
|
201
|
+
)}
|
|
202
|
+
<span className="my-2 block text-gray-500">
|
|
203
|
+
{Object.keys(attributesResourceMap).map(name => (
|
|
204
|
+
<div
|
|
205
|
+
key={name}
|
|
206
|
+
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"
|
|
207
|
+
>
|
|
208
|
+
<TextWithTooltip
|
|
209
|
+
text={`${name.replace('attributes.', '')}="${
|
|
210
|
+
attributesResourceMap[name]
|
|
211
|
+
}"`}
|
|
212
|
+
maxTextLength={48}
|
|
213
|
+
id={`tooltip-${name}-${attributesResourceMap[name]}`}
|
|
214
|
+
/>
|
|
215
|
+
</div>
|
|
216
|
+
))}
|
|
217
|
+
</span>
|
|
218
|
+
{Object.keys(attributesMap).length > 0 && (
|
|
219
|
+
<span className="text-sm font-bold text-gray-700 dark:text-white">
|
|
220
|
+
Attributes
|
|
221
|
+
</span>
|
|
222
|
+
)}
|
|
223
|
+
<span className="my-2 block text-gray-500">
|
|
224
|
+
{Object.keys(attributesMap).map(name => (
|
|
225
|
+
<div
|
|
226
|
+
key={name}
|
|
227
|
+
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"
|
|
228
|
+
>
|
|
229
|
+
<TextWithTooltip
|
|
230
|
+
text={`${name.replace('attributes.', '')}="${attributesMap[name]}"`}
|
|
231
|
+
maxTextLength={48}
|
|
232
|
+
id={`tooltip-${name}-${attributesMap[name]}`}
|
|
233
|
+
/>
|
|
234
|
+
</div>
|
|
235
|
+
))}
|
|
236
|
+
</span>
|
|
237
|
+
</>
|
|
238
|
+
) : (
|
|
239
|
+
<>
|
|
240
|
+
{highlighted.labels
|
|
241
|
+
.filter((label: Label) => label.name !== '__name__')
|
|
242
|
+
.map((label: Label) => (
|
|
243
|
+
<div
|
|
244
|
+
key={label.name}
|
|
245
|
+
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"
|
|
246
|
+
>
|
|
247
|
+
<TextWithTooltip
|
|
248
|
+
text={`${label.name}="${label.value}"`}
|
|
249
|
+
maxTextLength={37}
|
|
250
|
+
id={`tooltip-${label.name}`}
|
|
251
|
+
/>
|
|
252
|
+
</div>
|
|
253
|
+
))}
|
|
254
|
+
</>
|
|
255
|
+
)}
|
|
184
256
|
</span>
|
|
185
257
|
<div className="flex w-full items-center gap-1 text-xs text-gray-500">
|
|
186
258
|
<Icon icon="iconoir:mouse-button-right" />
|
|
@@ -24,22 +24,12 @@ import {Matcher} from '@parca/parser';
|
|
|
24
24
|
import {formatDate, formatForTimespan, getPrecision, valueFormatter} from '@parca/utilities';
|
|
25
25
|
|
|
26
26
|
import MetricsSeries from '../../MetricsSeries';
|
|
27
|
+
import {type UtilizationMetrics as MetricSeries} from '../../ProfileSelector';
|
|
27
28
|
import MetricsContextMenu from '../MetricsContextMenu';
|
|
28
29
|
import MetricsTooltip from '../MetricsTooltip';
|
|
29
30
|
import {type Series} from '../index';
|
|
30
31
|
import {useMetricsGraphDimensions} from '../useMetricsGraphDimensions';
|
|
31
32
|
|
|
32
|
-
interface MetricSeries {
|
|
33
|
-
timestamp: number;
|
|
34
|
-
value: number;
|
|
35
|
-
resource: {
|
|
36
|
-
[key: string]: string;
|
|
37
|
-
};
|
|
38
|
-
attributes: {
|
|
39
|
-
[key: string]: string;
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
|
|
43
33
|
interface CommonProps {
|
|
44
34
|
data: MetricSeries[];
|
|
45
35
|
addLabelMatcher: (
|
|
@@ -64,26 +54,31 @@ type Props = CommonProps & {
|
|
|
64
54
|
utilizationMetricsLoading?: boolean;
|
|
65
55
|
};
|
|
66
56
|
|
|
57
|
+
interface MetricsSample {
|
|
58
|
+
timestamp: number;
|
|
59
|
+
value: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
67
62
|
function transformToSeries(data: MetricSeries[]): Series[] {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
.
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
63
|
+
const series: Series[] = data.reduce<Series[]>(function (agg: Series[], s: MetricSeries) {
|
|
64
|
+
if (s.labelset !== undefined) {
|
|
65
|
+
const metric = s.labelset.labels.sort((a, b) => a.name.localeCompare(b.name));
|
|
66
|
+
agg.push({
|
|
67
|
+
metric,
|
|
68
|
+
values: s.samples.reduce<number[][]>(function (agg: number[][], d: MetricsSample) {
|
|
69
|
+
if (d.timestamp !== undefined && d.value !== undefined) {
|
|
70
|
+
agg.push([d.timestamp, d.value]);
|
|
71
|
+
}
|
|
72
|
+
return agg;
|
|
73
|
+
}, []),
|
|
74
|
+
labelset: metric.map(m => `${m.name}=${m.value}`).join(','),
|
|
75
|
+
});
|
|
79
76
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
return acc;
|
|
83
|
-
}, {});
|
|
77
|
+
return agg;
|
|
78
|
+
}, []);
|
|
84
79
|
|
|
85
80
|
// Sort values by timestamp for each series
|
|
86
|
-
return
|
|
81
|
+
return series.map(series => ({
|
|
87
82
|
...series,
|
|
88
83
|
values: series.values.sort((a, b) => a[0] - b[0]),
|
|
89
84
|
}));
|
|
@@ -113,15 +108,14 @@ const RawUtilizationMetrics = ({
|
|
|
113
108
|
|
|
114
109
|
const graphWidth = width - margin * 1.5 - margin / 2;
|
|
115
110
|
|
|
116
|
-
|
|
117
|
-
const timeExtent = d3.extent(data, d => d.timestamp);
|
|
111
|
+
const timeExtent = d3.extent(data.flatMap(d => d.samples.map(s => s.timestamp)));
|
|
118
112
|
const from = timeExtent[0] ?? 0;
|
|
119
113
|
const to = timeExtent[1] ?? 0;
|
|
120
114
|
|
|
121
115
|
const paddedFrom = from;
|
|
122
116
|
const paddedTo = to;
|
|
123
117
|
|
|
124
|
-
const series = transformToSeries(data);
|
|
118
|
+
const series = useMemo(() => transformToSeries(data), [data]);
|
|
125
119
|
|
|
126
120
|
const extentsY = series.map(function (s) {
|
|
127
121
|
return d3.extent(s.values, function (d) {
|
|
@@ -293,6 +287,7 @@ const RawUtilizationMetrics = ({
|
|
|
293
287
|
menuId={MENU_ID}
|
|
294
288
|
highlighted={highlighted}
|
|
295
289
|
trackVisibility={trackVisibility}
|
|
290
|
+
utilizationMetrics={true}
|
|
296
291
|
/>
|
|
297
292
|
|
|
298
293
|
{highlighted != null && hovering && !dragging && pos[0] !== 0 && pos[1] !== 0 && (
|
|
@@ -309,6 +304,7 @@ const RawUtilizationMetrics = ({
|
|
|
309
304
|
contextElement={graph.current}
|
|
310
305
|
sampleUnit="%"
|
|
311
306
|
delta={false}
|
|
307
|
+
utilizationMetrics={true}
|
|
312
308
|
/>
|
|
313
309
|
)}
|
|
314
310
|
</div>
|
|
@@ -450,14 +446,15 @@ const RawUtilizationMetrics = ({
|
|
|
450
446
|
<g className="lines fill-transparent">
|
|
451
447
|
{series.map((s, i) => {
|
|
452
448
|
let isSelected = false;
|
|
453
|
-
if (
|
|
454
|
-
selectedSeriesMatchers != null &&
|
|
455
|
-
selectedSeriesMatchers.length > 0 &&
|
|
456
|
-
selectedSeriesMatchers.length === s.metric.length
|
|
457
|
-
) {
|
|
449
|
+
if (selectedSeriesMatchers != null && selectedSeriesMatchers.length > 0) {
|
|
458
450
|
isSelected = selectedSeriesMatchers.every(m => {
|
|
459
451
|
for (let i = 0; i < s.metric.length; i++) {
|
|
460
|
-
if (
|
|
452
|
+
if (
|
|
453
|
+
s.metric[i].name
|
|
454
|
+
.replace('attributes_resource.', '')
|
|
455
|
+
.replace('attributes.', '') === m.key &&
|
|
456
|
+
s.metric[i].value === m.value
|
|
457
|
+
) {
|
|
461
458
|
return true;
|
|
462
459
|
}
|
|
463
460
|
}
|
|
@@ -483,7 +480,12 @@ const RawUtilizationMetrics = ({
|
|
|
483
480
|
onClick={() => {
|
|
484
481
|
if (highlighted != null) {
|
|
485
482
|
addLabelMatcher(
|
|
486
|
-
highlighted.labels
|
|
483
|
+
highlighted.labels
|
|
484
|
+
.filter(l => l.name.startsWith('attributes_resource.'))
|
|
485
|
+
.map(l => ({
|
|
486
|
+
key: l.name.replace('attributes_resource.', ''),
|
|
487
|
+
value: l.value,
|
|
488
|
+
}))
|
|
487
489
|
);
|
|
488
490
|
}
|
|
489
491
|
}}
|
|
@@ -55,14 +55,16 @@ interface ProfileSelectorFeatures {
|
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
export interface UtilizationMetrics {
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
attributes: {
|
|
64
|
-
[key: string]: string;
|
|
58
|
+
labelset: {
|
|
59
|
+
labels: Array<{
|
|
60
|
+
name: string;
|
|
61
|
+
value: string;
|
|
62
|
+
}>;
|
|
65
63
|
};
|
|
64
|
+
samples: Array<{
|
|
65
|
+
timestamp: number;
|
|
66
|
+
value: number;
|
|
67
|
+
}>;
|
|
66
68
|
}
|
|
67
69
|
|
|
68
70
|
export interface UtilizationLabels {
|