@parca/profile 0.16.478 → 0.16.479

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 CHANGED
@@ -3,6 +3,12 @@
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.479](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.478...@parca/profile@0.16.479) (2025-02-25)
7
+
8
+ ## 0.23.1 (2025-02-24)
9
+
10
+ **Note:** Version bump only for package @parca/profile
11
+
6
12
  ## 0.16.478 (2025-02-25)
7
13
 
8
14
  ## 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;CAC/C;AAED,QAAA,MAAM,kBAAkB,iEAKrB,uBAAuB,KAAG,GAAG,CAAC,OA8ChC,CAAC;AAEF,eAAe,kBAAkB,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,OA8ChC,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 MetricsContextMenu = ({ menuId, onAddLabelMatcher, highlighted, trackVisibility, }) => {
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,6 @@ 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: () => onAddLabelMatcher({ key: label.name, value: label.value }), 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: `${label.name}="${label.value}"` }) }, label.name))) }) })] }));
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: () => onAddLabelMatcher({ key: label.name, value: label.value }), 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
34
  };
29
35
  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;CAChB;AA8BD,QAAA,MAAM,cAAc,8DAOjB,KAAK,KAAG,GAAG,CAAC,OA8Hd,CAAC;AAEF,eAAe,cAAc,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
- .filter((label) => label.name !== '__name__')
78
- .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." })] })] }) }) }) }) }) }));
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
- interface MetricSeries {
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;AAStC,UAAU,YAAY;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;KACvB,CAAC;IACF,UAAU,EAAE;QACV,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;KACvB,CAAC;CACH;AAED,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;AAqbF,QAAA,MAAM,kBAAkB,gGAMrB,KAAK,KAAG,GAAG,CAAC,OA6Bd,CAAC;AAEF,eAAe,kBAAkB,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;AAgcF,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 groupedData = data.reduce((acc, series) => {
28
- const resourceKey = Object.entries(series.resource)
29
- .map(([name, value]) => `${name}=${value}`)
30
- .join(',');
31
- if (!Object.hasOwn(acc, resourceKey)) {
32
- acc[resourceKey] = {
33
- metric: Object.entries(series.resource).map(([name, value]) => ({ name, value })),
34
- values: [],
35
- labelset: resourceKey,
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
- acc[resourceKey].values.push([series.timestamp, series.value, 0, 0]);
39
- return acc;
40
- }, {});
41
+ return agg;
42
+ }, []);
41
43
  // Sort values by timestamp for each series
42
- return Object.values(groupedData).map(series => ({
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
- // Calculate the time range from the data
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;
@@ -219,7 +220,12 @@ const RawUtilizationMetrics = ({ data, addLabelMatcher, setTimeRange, width, hei
219
220
  ? lineStrokeHover
220
221
  : lineStroke, xScale: xScale, yScale: yScale, onClick: () => {
221
222
  if (highlighted != null) {
222
- addLabelMatcher(highlighted.labels.map(l => ({ key: l.name, value: l.value })));
223
+ addLabelMatcher(highlighted.labels
224
+ .filter(l => l.name.startsWith('attributes_resource.'))
225
+ .map(l => ({
226
+ key: l.name.replace('attributes_resource.', ''),
227
+ value: l.value,
228
+ })));
223
229
  }
224
230
  } }) }, i));
225
231
  }) })] })] }) })] }));
@@ -20,14 +20,16 @@ interface ProfileSelectorFeatures {
20
20
  disableProfileTypesDropdown?: boolean;
21
21
  }
22
22
  export interface UtilizationMetrics {
23
- timestamp: number;
24
- value: number;
25
- resource: {
26
- [key: string]: string;
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,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QACR,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;KACvB,CAAC;IACF,UAAU,EAAE;QACV,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC;KACvB,CAAC;CACH;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"}
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.478",
3
+ "version": "0.16.479",
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": "43097f0543dd2bfcb337724764478ddcdd3f5a01"
78
+ "gitHead": "11658aa6fcf7455df7adb7d45ca68e8235566d5d"
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__');
@@ -71,7 +80,7 @@ const MetricsContextMenu = ({
71
80
  className="max-w-[400px] overflow-hidden"
72
81
  >
73
82
  <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}"`}
83
+ {`${transformUtilizationLabels(label.name, utilizationMetrics)}="${label.value}"`}
75
84
  </div>
76
85
  </Item>
77
86
  ))}
@@ -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
- {highlighted.labels
171
- .filter((label: Label) => label.name !== '__name__')
172
- .map((label: Label) => (
173
- <div
174
- key={label.name}
175
- 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"
176
- >
177
- <TextWithTooltip
178
- text={`${label.name}="${label.value}"`}
179
- maxTextLength={37}
180
- id={`tooltip-${label.name}`}
181
- />
182
- </div>
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 groupedData = data.reduce<Record<string, Series>>((acc, series) => {
69
- const resourceKey = Object.entries(series.resource)
70
- .map(([name, value]) => `${name}=${value}`)
71
- .join(',');
72
-
73
- if (!Object.hasOwn(acc, resourceKey)) {
74
- acc[resourceKey] = {
75
- metric: Object.entries(series.resource).map(([name, value]) => ({name, value})),
76
- values: [],
77
- labelset: resourceKey,
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
- acc[resourceKey].values.push([series.timestamp, series.value, 0, 0]);
82
- return acc;
83
- }, {});
77
+ return agg;
78
+ }, []);
84
79
 
85
80
  // Sort values by timestamp for each series
86
- return Object.values(groupedData).map(series => ({
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
- // Calculate the time range from the data
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>
@@ -483,7 +479,12 @@ const RawUtilizationMetrics = ({
483
479
  onClick={() => {
484
480
  if (highlighted != null) {
485
481
  addLabelMatcher(
486
- highlighted.labels.map(l => ({key: l.name, value: l.value}))
482
+ highlighted.labels
483
+ .filter(l => l.name.startsWith('attributes_resource.'))
484
+ .map(l => ({
485
+ key: l.name.replace('attributes_resource.', ''),
486
+ value: l.value,
487
+ }))
487
488
  );
488
489
  }
489
490
  }}
@@ -55,14 +55,16 @@ interface ProfileSelectorFeatures {
55
55
  }
56
56
 
57
57
  export interface UtilizationMetrics {
58
- timestamp: number;
59
- value: number;
60
- resource: {
61
- [key: string]: string;
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 {