@parca/profile 0.16.341 → 0.16.343

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/MetricsGraph/MetricsTooltip/index.js +2 -2
  3. package/dist/MetricsGraph/index.js +4 -2
  4. package/dist/ProfileIcicleGraph/ActionButtons/GroupByDropdown.js +1 -1
  5. package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.js +1 -1
  6. package/dist/ProfileIcicleGraph/ActionButtons/SortBySelect.js +2 -2
  7. package/dist/ProfileIcicleGraph/index.js +1 -1
  8. package/dist/ProfileMetricsGraph/index.js +8 -1
  9. package/dist/ProfileTypeSelector/index.js +1 -1
  10. package/dist/ProfileView/FilterByFunctionButton.js +1 -1
  11. package/dist/ProfileView/ViewSelector.d.ts +2 -1
  12. package/dist/ProfileView/ViewSelector.js +2 -2
  13. package/dist/ProfileView/VisualizationPanel.js +1 -1
  14. package/dist/ProfileView/index.js +2 -2
  15. package/dist/ProfileViewWithData.js +12 -1
  16. package/dist/components/DiffLegend.js +1 -1
  17. package/package.json +7 -7
  18. package/src/MetricsGraph/MetricsTooltip/index.tsx +22 -9
  19. package/src/MetricsGraph/index.tsx +4 -2
  20. package/src/ProfileIcicleGraph/ActionButtons/GroupByDropdown.tsx +2 -5
  21. package/src/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.tsx +2 -5
  22. package/src/ProfileIcicleGraph/ActionButtons/SortBySelect.tsx +1 -1
  23. package/src/ProfileIcicleGraph/index.tsx +1 -1
  24. package/src/ProfileMetricsGraph/index.tsx +9 -1
  25. package/src/ProfileTypeSelector/index.tsx +1 -1
  26. package/src/ProfileView/FilterByFunctionButton.tsx +1 -1
  27. package/src/ProfileView/ViewSelector.tsx +4 -0
  28. package/src/ProfileView/VisualizationPanel.tsx +6 -1
  29. package/src/ProfileView/index.tsx +2 -1
  30. package/src/ProfileViewWithData.tsx +12 -1
  31. package/src/components/DiffLegend.tsx +1 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
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.343](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.342...@parca/profile@0.16.343) (2024-02-20)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.342](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.341...@parca/profile@0.16.342) (2024-02-14)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.341](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.340...@parca/profile@0.16.341) (2024-02-13)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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.
@@ -73,7 +73,7 @@ const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta,
73
73
  }, [x, y, contextElement, update]);
74
74
  const nameLabel = highlighted?.labels.find(e => e.name === '__name__');
75
75
  const highlightedNameLabel = nameLabel !== undefined ? nameLabel : { name: '', value: '' };
76
- return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: _jsx("div", { className: "flex max-w-md", 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: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] }), delta && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })), 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, timeFormat) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
76
+ return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: _jsx("div", { className: "flex max-w-md", 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", children: "Per Second" }), _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, timeFormat) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
77
77
  .filter((label) => label.name !== '__name__')
78
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.value}` }) }, 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
79
  };
@@ -241,9 +241,11 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLab
241
241
  };
242
242
  const isDeltaType = profile !== null ? profile?.query.profType.delta : false;
243
243
  let yAxisLabel = sampleUnit;
244
+ let yAxisUnit = sampleUnit;
244
245
  if (isDeltaType) {
245
- if (profile?.query.profType.periodType === 'cpu' && sampleUnit === 'count') {
246
+ if (sampleUnit === 'nanoseconds') {
246
247
  yAxisLabel = 'CPU Cores';
248
+ yAxisUnit = '';
247
249
  }
248
250
  if (sampleUnit === 'bytes') {
249
251
  yAxisLabel = 'Bytes per Second';
@@ -260,7 +262,7 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLab
260
262
  }
261
263
  return (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
262
264
  /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
263
- 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, sampleUnit, 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()}`));
265
+ 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, yAxisUnit, 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()}`));
264
266
  }), _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: yAxisLabel }) })] }), _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",
265
267
  /* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
266
268
  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)) })] }, `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: xScale(from), x2: xScale(to), 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) => (_jsx("g", { className: "line", children: _jsx(MetricsSeries, { data: s, line: l, color: color(i.toString()), strokeWidth: hovering && highlighted != null && i === highlighted.seriesIndex
@@ -52,6 +52,6 @@ const GroupByDropdown = ({ groupBy, toggleGroupBy, }) => {
52
52
  : groupBy.length === 1
53
53
  ? groupByOptions.find(option => option.value === groupBy[0])?.label
54
54
  : 'Multiple';
55
- return (_jsxs("div", { className: "relative", children: [_jsx("label", { className: "text-sm", children: "Group" }), _jsxs(Menu, { as: "div", className: "relative text-left", children: [_jsxs(Menu.Button, { id: "h-group-by-filter", className: "relative w-max cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm", children: [_jsx("span", { className: "block overflow-x-hidden text-ellipsis", children: label }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }), _jsx(Transition, { as: "div", leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(Menu.Items, { className: "absolute left-0 z-10 mt-1 min-w-[400px] overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsx("div", { className: "space-y-5", children: groupByOptions.map(({ value, label, description, disabled }) => (_jsxs("div", { className: "relative flex items-start", children: [_jsx("div", { className: "flex h-6 items-center", children: _jsx("input", { id: value, name: value, type: "checkbox", disabled: disabled, className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", checked: groupBy.includes(value), onChange: () => toggleGroupBy(value) }) }), _jsxs("div", { className: "ml-3 text-sm leading-6", children: [_jsx("label", { htmlFor: value, className: "font-medium text-gray-900 dark:text-gray-200", children: label }), _jsx("p", { className: "text-gray-500 dark:text-gray-400", children: description })] })] }, value))) }) }) }) }) })] })] }));
55
+ return (_jsxs("div", { className: "relative", children: [_jsx("label", { className: "text-sm", children: "Group" }), _jsxs(Menu, { as: "div", className: "relative text-left", id: "h-group-by-filter", children: [_jsxs(Menu.Button, { className: "relative w-max cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm", children: [_jsx("span", { className: "block overflow-x-hidden text-ellipsis", children: label }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }), _jsx(Transition, { as: "div", leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(Menu.Items, { className: "absolute left-0 z-10 mt-1 min-w-[400px] overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsx("div", { className: "space-y-5", children: groupByOptions.map(({ value, label, description, disabled }) => (_jsxs("div", { className: "relative flex items-start", children: [_jsx("div", { className: "flex h-6 items-center", children: _jsx("input", { id: value, name: value, type: "checkbox", disabled: disabled, className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", checked: groupBy.includes(value), onChange: () => toggleGroupBy(value) }) }), _jsxs("div", { className: "ml-3 text-sm leading-6", children: [_jsx("label", { htmlFor: value, className: "font-medium text-gray-900 dark:text-gray-200", children: label }), _jsx("p", { className: "text-gray-500 dark:text-gray-400", children: description })] })] }, value))) }) }) }) }) })] })] }));
56
56
  };
57
57
  export default GroupByDropdown;
@@ -18,6 +18,6 @@ const RuntimeToggle = ({ id, state, toggle, label, description, }) => {
18
18
  return (_jsxs("div", { className: "relative flex items-start", children: [_jsx("div", { className: "flex h-6 items-center", children: _jsx("input", { id: id, name: id, type: "checkbox", className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", checked: state, onChange: () => toggle() }) }), _jsxs("div", { className: "ml-3 text-sm leading-6", children: [_jsx("label", { htmlFor: id, className: "font-medium text-gray-900 dark:text-gray-200", children: label }), _jsx("p", { className: "text-gray-500 dark:text-gray-400", children: description })] })] }, id));
19
19
  };
20
20
  const RuntimeFilterDropdown = ({ showRuntimeRuby, toggleShowRuntimeRuby, showRuntimePython, toggleShowRuntimePython, showInterpretedOnly, toggleShowInterpretedOnly, }) => {
21
- return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Runtimes" }), _jsxs(Menu, { as: "div", className: "relative text-left", children: [_jsx("div", { children: _jsxs(Menu.Button, { className: "relative w-full cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm", id: "h-runtimes-filter", children: [_jsx("span", { className: "block overflow-x-hidden text-ellipsis", children: "Runtimes" }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }) }), _jsx(Transition, { as: Fragment, leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(Menu.Items, { className: "absolute left-0 z-10 mt-1 min-w-[400px] overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsxs("div", { className: "space-y-5", children: [_jsx(RuntimeToggle, { id: "show-runtime-ruby", state: showRuntimeRuby, toggle: toggleShowRuntimeRuby, label: "Ruby", description: "Show Ruby runtime functions." }), _jsx(RuntimeToggle, { id: "show-runtime-python", state: showRuntimePython, toggle: toggleShowRuntimePython, label: "Python", description: "Show Python runtime functions." }), _jsx(RuntimeToggle, { id: "show-interpreted-only", state: showInterpretedOnly, toggle: toggleShowInterpretedOnly, label: "Interpreted Only", description: "Show only interpreted functions." })] }) }) }) }) })] })] }));
21
+ return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Runtimes" }), _jsxs(Menu, { as: "div", className: "relative text-left", id: "h-runtimes-filter", children: [_jsx("div", { children: _jsxs(Menu.Button, { className: "relative w-full cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm", children: [_jsx("span", { className: "block overflow-x-hidden text-ellipsis", children: "Runtimes" }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }) }), _jsx(Transition, { as: Fragment, leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(Menu.Items, { className: "absolute left-0 z-10 mt-1 min-w-[400px] overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsxs("div", { className: "space-y-5", children: [_jsx(RuntimeToggle, { id: "show-runtime-ruby", state: showRuntimeRuby, toggle: toggleShowRuntimeRuby, label: "Ruby", description: "Show Ruby runtime functions." }), _jsx(RuntimeToggle, { id: "show-runtime-python", state: showRuntimePython, toggle: toggleShowRuntimePython, label: "Python", description: "Show Python runtime functions." }), _jsx(RuntimeToggle, { id: "show-interpreted-only", state: showInterpretedOnly, toggle: toggleShowInterpretedOnly, label: "Interpreted Only", description: "Show only interpreted functions." })] }) }) }) }) })] })] }));
22
22
  };
23
23
  export default RuntimeFilterDropdown;
@@ -14,7 +14,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
14
14
  import { Select } from '@parca/components';
15
15
  import { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_NAME } from '../IcicleGraphArrow';
16
16
  const SortBySelect = ({ sortBy, setSortBy, compareMode, }) => {
17
- return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Sort" }), _jsx(Select, { id: "h-sort-by-filter", className: "!px-3", items: [
17
+ return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Sort" }), _jsx(Select, { className: "!px-3", items: [
18
18
  {
19
19
  key: FIELD_FUNCTION_NAME,
20
20
  disabled: false,
@@ -39,6 +39,6 @@ const SortBySelect = ({ sortBy, setSortBy, compareMode, }) => {
39
39
  expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "Diff" }) })),
40
40
  },
41
41
  },
42
- ], selectedKey: sortBy, onSelection: key => setSortBy(key), placeholder: 'Sort By', primary: false, disabled: false })] }));
42
+ ], selectedKey: sortBy, onSelection: key => setSortBy(key), placeholder: 'Sort By', primary: false, disabled: false, id: "h-sort-by-filter" })] }));
43
43
  };
44
44
  export default SortBySelect;
@@ -134,6 +134,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
134
134
  if (isTrimmed) {
135
135
  console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
136
136
  }
137
- return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsxs("div", { className: "min-h-48", children: [graph !== undefined && (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo })), arrow !== undefined && (_jsx(IcicleGraphArrow, { width: width, arrow: arrow, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo, sortBy: storeSortBy }))] }), _jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] })] }, "icicle-graph-loaded") }));
137
+ return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsxs("div", { className: "min-h-48", id: "h-icicle-graph", children: [graph !== undefined && (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo })), arrow !== undefined && (_jsx(IcicleGraphArrow, { width: width, arrow: arrow, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo, sortBy: storeSortBy }))] }), _jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] })] }, "icicle-graph-loaded") }));
138
138
  };
139
139
  export default ProfileIcicleGraph;
@@ -91,7 +91,14 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
91
91
  const handleSampleClick = (timestamp, _value, labels) => {
92
92
  onPointClick(timestamp, labels, queryExpression);
93
93
  };
94
- return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: _jsx(MetricsGraph, { data: series, from: from, to: to, profile: profile, setTimeRange: setTimeRange, onSampleClick: handleSampleClick, addLabelMatcher: addLabelMatcher, sampleUnit: Query.parse(queryExpression).profileType().sampleUnit, height: height, width: width, margin: margin }) }, "metrics-graph-loaded") }));
94
+ let sampleUnit = '';
95
+ if (series.every((val, i, arr) => val?.sampleType?.unit === arr[0]?.sampleType?.unit)) {
96
+ sampleUnit = series[0]?.sampleType?.unit ?? '';
97
+ }
98
+ if (sampleUnit === '') {
99
+ sampleUnit = Query.parse(queryExpression).profileType().sampleUnit;
100
+ }
101
+ return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: _jsx(MetricsGraph, { data: series, from: from, to: to, profile: profile, setTimeRange: setTimeRange, onSampleClick: handleSampleClick, addLabelMatcher: addLabelMatcher, sampleUnit: sampleUnit, height: height, width: width, margin: margin }) }, "metrics-graph-loaded") }));
95
102
  }
96
103
  return _jsx(ProfileMetricsEmptyState, { message: "No data found. Try a different query." });
97
104
  };
@@ -122,6 +122,6 @@ const ProfileTypeSelector = ({ profileTypesData, loading = false, error, selecte
122
122
  key: name,
123
123
  element: profileSelectElement(name, flexibleKnownProfilesDetection),
124
124
  }));
125
- return (_jsx(Select, { items: profileLabels, selectedKey: selectedKey, onSelection: onSelection, placeholder: "Select profile type...", loading: loading, className: "bg-white" }));
125
+ return (_jsx(Select, { items: profileLabels, selectedKey: selectedKey, onSelection: onSelection, placeholder: "Select profile type...", loading: loading, className: "bg-white h-profile-type-dropdown" }));
126
126
  };
127
127
  export default ProfileTypeSelector;
@@ -29,6 +29,6 @@ const FilterByFunctionButton = ({ navigateTo, }) => {
29
29
  setStoreValue(localValue);
30
30
  }
31
31
  }, [localValue, isClearAction, setStoreValue]);
32
- return (_jsx(Input, { placeholder: "Filter by function", id: "h-filter-by-function", className: "text-sm", onAction: onAction, onChange: e => setLocalValue(e.target.value), value: localValue ?? '', onBlur: () => setLocalValue(storeValue), actionIcon: isClearAction ? _jsx(Icon, { icon: "ep:circle-close" }) : _jsx(Icon, { icon: "ep:arrow-right" }) }));
32
+ return (_jsx(Input, { placeholder: "Filter by function", className: "text-sm", onAction: onAction, onChange: e => setLocalValue(e.target.value), value: localValue ?? '', onBlur: () => setLocalValue(storeValue), actionIcon: isClearAction ? _jsx(Icon, { icon: "ep:circle-close" }) : _jsx(Icon, { icon: "ep:arrow-right" }), id: "h-filter-by-function" }));
33
33
  };
34
34
  export default FilterByFunctionButton;
@@ -9,6 +9,7 @@ interface Props {
9
9
  addView?: boolean;
10
10
  disabled?: boolean;
11
11
  icon?: JSX.Element;
12
+ id?: string;
12
13
  }
13
- declare const ViewSelector: ({ defaultValue, navigateTo, position, placeholderText, primary, addView, disabled, icon, }: Props) => JSX.Element;
14
+ declare const ViewSelector: ({ defaultValue, navigateTo, position, placeholderText, primary, addView, disabled, icon, id, }: Props) => JSX.Element;
14
15
  export default ViewSelector;
@@ -13,7 +13,7 @@ import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-run
13
13
  // limitations under the License.
14
14
  import { Select, useParcaContext, useURLState } from '@parca/components';
15
15
  import { useUIFeatureFlag } from '@parca/hooks';
16
- const ViewSelector = ({ defaultValue, navigateTo, position, placeholderText, primary = false, addView = false, disabled = false, icon, }) => {
16
+ const ViewSelector = ({ defaultValue, navigateTo, position, placeholderText, primary = false, addView = false, disabled = false, icon, id, }) => {
17
17
  const [callgraphEnabled] = useUIFeatureFlag('callgraph');
18
18
  const [dashboardItems = ['icicle'], setDashboardItems] = useURLState({
19
19
  param: 'dashboard_items',
@@ -65,6 +65,6 @@ const ViewSelector = ({ defaultValue, navigateTo, position, placeholderText, pri
65
65
  : [dashboardItems[0], value];
66
66
  setDashboardItems(newDashboardItems);
67
67
  };
68
- return (_jsx(Select, { items: items, selectedKey: defaultValue, onSelection: onSelection, placeholder: placeholderText ?? 'Select view type...', primary: primary, disabled: disabled, icon: icon }));
68
+ return (_jsx(Select, { className: "h-view-selector", items: items, selectedKey: defaultValue, onSelection: onSelection, placeholder: placeholderText ?? 'Select view type...', primary: primary, disabled: disabled, icon: icon, id: id }));
69
69
  };
70
70
  export default ViewSelector;
@@ -20,7 +20,7 @@ import ViewSelector from './ViewSelector';
20
20
  export const VisualizationPanel = React.memo(function VisualizationPanel({ dashboardItem, index, isMultiPanelView, handleClosePanel, navigateTo, dragHandleProps, getDashboardItemByType, }) {
21
21
  const [actionButtons, setActionButtons] = useState(_jsx(_Fragment, {}));
22
22
  const { flamegraphHint } = useParcaContext();
23
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full items-center justify-end gap-2 pb-2 min-h-[78px]", children: [_jsxs("div", { className: cx('flex w-full justify-between flex-col-reverse md:flex-row', isMultiPanelView && dashboardItem === 'icicle' ? 'items-end gap-x-2' : 'items-end'), children: [_jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: cx(isMultiPanelView ? '' : 'hidden', 'flex items-center'), ...dragHandleProps, children: _jsx(Icon, { className: "text-xl", icon: "material-symbols:drag-indicator" }) }), _jsx("div", { className: "flex gap-2", children: actionButtons })] }), _jsxs("div", { className: cx('flex flex-row items-center gap-4', isMultiPanelView && dashboardItem === 'icicle' && 'pb-[10px]'), children: [_jsx(ViewSelector, { defaultValue: dashboardItem, navigateTo: navigateTo, position: index }), dashboardItem === 'icicle' && flamegraphHint != null ? (_jsx("div", { className: "px-2", children: flamegraphHint })) : null] })] }), isMultiPanelView && (_jsx(IconButton, { className: "py-0", onClick: () => handleClosePanel(dashboardItem), icon: _jsx(CloseIcon, {}) }))] }), getDashboardItemByType({
23
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full items-center justify-end gap-2 pb-2 min-h-[78px]", children: [_jsxs("div", { className: cx('flex w-full justify-between flex-col-reverse md:flex-row', isMultiPanelView && dashboardItem === 'icicle' ? 'items-end gap-x-2' : 'items-end'), children: [_jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: cx(isMultiPanelView ? '' : 'hidden', 'flex items-center'), ...dragHandleProps, children: _jsx(Icon, { className: "text-xl", icon: "material-symbols:drag-indicator" }) }), _jsx("div", { className: "flex gap-2", children: actionButtons })] }), _jsxs("div", { className: cx('flex flex-row items-center gap-4', isMultiPanelView && dashboardItem === 'icicle' && 'pb-[10px]'), children: [_jsx(ViewSelector, { id: "h-switch-viz", defaultValue: dashboardItem, navigateTo: navigateTo, position: index }), dashboardItem === 'icicle' && flamegraphHint != null ? (_jsx("div", { className: "px-2", children: flamegraphHint })) : null] })] }), isMultiPanelView && (_jsx(IconButton, { className: "py-0", onClick: () => handleClosePanel(dashboardItem), icon: _jsx(CloseIcon, {}) }))] }), getDashboardItemByType({
24
24
  type: dashboardItem,
25
25
  isHalfScreen: isMultiPanelView,
26
26
  setActionButtons,
@@ -156,10 +156,10 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
156
156
  'items-center': hasProfileSource,
157
157
  }), children: [_jsxs("div", { children: [hasProfileSource && (_jsxs("div", { className: "max-w-[300px]", children: [_jsx("div", { className: "text-sm font-medium capitalize", children: headerParts.length > 0 ? headerParts[0].replace(/"/g, '') : '' }), _jsx("div", { className: "text-xs", children: headerParts.length > 1
158
158
  ? headerParts[headerParts.length - 1].replace(/"/g, '')
159
- : '' })] })), profileViewExternalMainActions != null ? profileViewExternalMainActions : null] }), _jsxs("div", { className: "lg:flex flex-wrap items-center gap-2 md:justify-end hidden", children: [_jsx(FilterByFunctionButton, { navigateTo: navigateTo }), profileViewExternalSubActions != null ? profileViewExternalSubActions : null, _jsx(UserPreferences, { customButton: _jsxs(Button, { className: "gap-2", variant: "neutral", id: "h-viz-preferences", children: ["Preferences", _jsx(Icon, { icon: "pajamas:preferences", width: 20 })] }) }), profileSource !== undefined && queryClient !== undefined ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsxs(Button, { id: "h-download-pprof", className: "gap-2", variant: "neutral", onClick: e => {
159
+ : '' })] })), profileViewExternalMainActions != null ? profileViewExternalMainActions : null] }), _jsxs("div", { className: "lg:flex flex-wrap items-center gap-2 md:justify-end hidden", children: [_jsx(FilterByFunctionButton, { navigateTo: navigateTo }), profileViewExternalSubActions != null ? profileViewExternalSubActions : null, _jsx(UserPreferences, { customButton: _jsxs(Button, { className: "gap-2", variant: "neutral", id: "h-viz-preferences", children: ["Preferences", _jsx(Icon, { icon: "pajamas:preferences", width: 20 })] }) }), profileSource !== undefined && queryClient !== undefined ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsxs(Button, { className: "gap-2", variant: "neutral", onClick: e => {
160
160
  e.preventDefault();
161
161
  onDownloadPProf();
162
- }, disabled: pprofDownloading, children: [pprofDownloading != null && pprofDownloading ? 'Downloading...' : 'Download pprof', _jsx(Icon, { icon: "material-symbols:download", width: 20 })] }), _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel", icon: _jsx(Icon, { icon: "material-symbols:add", width: 20 }), addView: true, disabled: isMultiPanelView || dashboardItems.length < 1 })] })] }), _jsx("div", { className: "w-full", ref: ref, children: _jsx(DragDropContext, { onDragEnd: onDragEnd, children: _jsx(Droppable, { droppableId: "droppable", direction: "horizontal", children: provided => (_jsx("div", { ref: provided.innerRef, className: cx('grid w-full gap-2', isMultiPanelView ? 'grid-cols-2' : 'grid-cols-1'), ...provided.droppableProps, children: dashboardItems.map((dashboardItem, index) => {
162
+ }, disabled: pprofDownloading, id: "h-download-pprof", children: [pprofDownloading != null && pprofDownloading ? 'Downloading...' : 'Download pprof', _jsx(Icon, { icon: "material-symbols:download", width: 20 })] }), _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel", icon: _jsx(Icon, { icon: "material-symbols:add", width: 20 }), addView: true, disabled: isMultiPanelView || dashboardItems.length < 1, id: "h-add-panel" })] })] }), _jsx("div", { className: "w-full", ref: ref, children: _jsx(DragDropContext, { onDragEnd: onDragEnd, children: _jsx(Droppable, { droppableId: "droppable", direction: "horizontal", children: provided => (_jsx("div", { ref: provided.innerRef, className: cx('grid w-full gap-2', isMultiPanelView ? 'grid-cols-2' : 'grid-cols-1'), ...provided.droppableProps, children: dashboardItems.map((dashboardItem, index) => {
163
163
  return (_jsx(Draggable, { draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView, children: (provided, snapshot) => (_createElement("div", { ref: provided.innerRef, ...provided.draggableProps, key: dashboardItem, className: cx('w-full rounded p-2 shadow dark:border dark:border-gray-700 dark:bg-gray-700 min-h-96', snapshot.isDragging
164
164
  ? 'bg-gray-200 dark:bg-gray-500'
165
165
  : 'bg-white dark:bg-gray-700') },
@@ -87,7 +87,18 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
87
87
  sourceResponse,
88
88
  perf,
89
89
  ]);
90
- const sampleUnit = profileSource.ProfileType().sampleUnit;
90
+ // Default to the unit of the selected profile type. This is a heuristic, and
91
+ // only a fallback, the unit should be returned by the backend.
92
+ let sampleUnit = profileSource.ProfileType().sampleUnit;
93
+ if (flamegraphResponse?.report.oneofKind === 'flamegraphArrow') {
94
+ sampleUnit = flamegraphResponse.report.flamegraphArrow.unit;
95
+ }
96
+ if (tableResponse?.report.oneofKind === 'tableArrow') {
97
+ sampleUnit = tableResponse.report.tableArrow.unit;
98
+ }
99
+ if (sourceResponse?.report.oneofKind === 'source') {
100
+ sampleUnit = sourceResponse.report.source.unit;
101
+ }
91
102
  const downloadPProfClick = async () => {
92
103
  if (profileSource == null || queryClient == null) {
93
104
  return;
@@ -45,6 +45,6 @@ const DiffLegend = () => {
45
45
  const handleMouseLeave = () => {
46
46
  setShowLegendTooltip(false);
47
47
  };
48
- return (_jsxs("div", { className: "mt-1 mb-2 hidden md:block", children: [_jsxs("div", { ref: setReferenceElement, className: "flex items-center justify-center", children: [_jsx("span", { children: "Better" }), _jsx(DiffLegendBar, { onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }), _jsx("span", { children: "Worse" })] }), _jsx(Popover, { className: "relative", children: () => (_jsx(Transition, { show: showLegendTooltip, as: Fragment, enter: "transition ease-out duration-200", enterFrom: "opacity-0 translate-y-1", enterTo: "opacity-100 translate-y-0", leave: "transition ease-in duration-150", leaveFrom: "opacity-100 translate-y-0", leaveTo: "opacity-0 translate-y-1", children: _jsx(Popover.Panel, { ref: setPopperElement, style: styles.popper, ...attributes.popper, children: _jsx("div", { className: "overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5", children: _jsxs("div", { className: "bg-gray-50 p-4 dark:bg-gray-800", children: [_jsx("div", { className: "flex items-center justify-center" }), _jsx("span", { className: "block text-sm text-gray-500 dark:text-gray-50", children: "This is a differential icicle graph, where a purple-colored node means unchanged, and the darker the red, the worse the node got, and the darker the green, the better the node got." })] }) }) }) })) })] }));
48
+ return (_jsxs("div", { className: "mt-1 mb-2 hidden md:block", id: "h-diff-legend", children: [_jsxs("div", { ref: setReferenceElement, className: "flex items-center justify-center", children: [_jsx("span", { children: "Better" }), _jsx(DiffLegendBar, { onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave }), _jsx("span", { children: "Worse" })] }), _jsx(Popover, { className: "relative", children: () => (_jsx(Transition, { show: showLegendTooltip, as: Fragment, enter: "transition ease-out duration-200", enterFrom: "opacity-0 translate-y-1", enterTo: "opacity-100 translate-y-0", leave: "transition ease-in duration-150", leaveFrom: "opacity-100 translate-y-0", leaveTo: "opacity-0 translate-y-1", children: _jsx(Popover.Panel, { ref: setPopperElement, style: styles.popper, ...attributes.popper, children: _jsx("div", { className: "overflow-hidden rounded-lg shadow-lg ring-1 ring-black ring-opacity-5", children: _jsxs("div", { className: "bg-gray-50 p-4 dark:bg-gray-800", children: [_jsx("div", { className: "flex items-center justify-center" }), _jsx("span", { className: "block text-sm text-gray-500 dark:text-gray-50", children: "This is a differential icicle graph, where a purple-colored node means unchanged, and the darker the red, the worse the node got, and the darker the green, the better the node got." })] }) }) }) })) })] }));
49
49
  };
50
50
  export default DiffLegend;
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.341",
3
+ "version": "0.16.343",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
- "@parca/client": "^0.16.102",
7
- "@parca/components": "^0.16.251",
6
+ "@parca/client": "^0.16.103",
7
+ "@parca/components": "^0.16.253",
8
8
  "@parca/dynamicsize": "^0.16.60",
9
- "@parca/hooks": "^0.0.40",
9
+ "@parca/hooks": "^0.0.41",
10
10
  "@parca/parser": "^0.16.68",
11
- "@parca/store": "^0.16.128",
12
- "@parca/utilities": "^0.0.56",
11
+ "@parca/store": "^0.16.129",
12
+ "@parca/utilities": "^0.0.57",
13
13
  "@tanstack/react-query": "^4.0.5",
14
14
  "@types/react-beautiful-dnd": "^13.1.3",
15
15
  "apache-arrow": "^12.0.0",
@@ -51,5 +51,5 @@
51
51
  "access": "public",
52
52
  "registry": "https://registry.npmjs.org/"
53
53
  },
54
- "gitHead": "1e56ee8a3d821ac02819b9264f55464787d6e667"
54
+ "gitHead": "c5b8204dc83fb3f120c2536fa15de23171cb04b9"
55
55
  }
@@ -117,17 +117,30 @@ const MetricsTooltip = ({
117
117
  <span className="my-2 block text-gray-700 dark:text-gray-300">
118
118
  <table className="table-auto">
119
119
  <tbody>
120
- <tr>
121
- <td className="w-1/4">Value</td>
122
- <td className="w-3/4">
123
- {valueFormatter(highlighted.valuePerSecond, sampleUnit, 5)}
124
- </td>
125
- </tr>
126
- {delta && (
120
+ {delta ? (
121
+ <>
122
+ <tr>
123
+ <td className="w-1/4">Per Second</td>
124
+ <td className="w-3/4">
125
+ {valueFormatter(
126
+ highlighted.valuePerSecond,
127
+ sampleUnit === 'nanoseconds' ? 'CPU Cores' : sampleUnit,
128
+ 5
129
+ )}
130
+ </td>
131
+ </tr>
132
+ <tr>
133
+ <td className="w-1/4">Total</td>
134
+ <td className="w-3/4">
135
+ {valueFormatter(highlighted.value, sampleUnit, 2)}
136
+ </td>
137
+ </tr>
138
+ </>
139
+ ) : (
127
140
  <tr>
128
- <td className="w-1/4">Total</td>
141
+ <td className="w-1/4">Value</td>
129
142
  <td className="w-3/4">
130
- {valueFormatter(highlighted.value, sampleUnit, 2)}
143
+ {valueFormatter(highlighted.valuePerSecond, sampleUnit, 5)}
131
144
  </td>
132
145
  </tr>
133
146
  )}
@@ -392,9 +392,11 @@ export const RawMetricsGraph = ({
392
392
  const isDeltaType = profile !== null ? profile?.query.profType.delta : false;
393
393
 
394
394
  let yAxisLabel = sampleUnit;
395
+ let yAxisUnit = sampleUnit;
395
396
  if (isDeltaType) {
396
- if (profile?.query.profType.periodType === 'cpu' && sampleUnit === 'count') {
397
+ if (sampleUnit === 'nanoseconds') {
397
398
  yAxisLabel = 'CPU Cores';
399
+ yAxisUnit = '';
398
400
  }
399
401
  if (sampleUnit === 'bytes') {
400
402
  yAxisLabel = 'Bytes per Second';
@@ -477,7 +479,7 @@ export const RawMetricsGraph = ({
477
479
  >
478
480
  <line className="stroke-gray-300 dark:stroke-gray-500" x2={-6} />
479
481
  <text fill="currentColor" x={-9} dy={'0.32em'}>
480
- {valueFormatter(d, sampleUnit, decimals)}
482
+ {valueFormatter(d, yAxisUnit, decimals)}
481
483
  </text>
482
484
  </g>
483
485
  <g key={`grid-${i}`}>
@@ -72,11 +72,8 @@ const GroupByDropdown = ({
72
72
  return (
73
73
  <div className="relative">
74
74
  <label className="text-sm">Group</label>
75
- <Menu as="div" className="relative text-left">
76
- <Menu.Button
77
- id="h-group-by-filter"
78
- className="relative w-max cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm"
79
- >
75
+ <Menu as="div" className="relative text-left" id="h-group-by-filter">
76
+ <Menu.Button className="relative w-max cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm">
80
77
  <span className="block overflow-x-hidden text-ellipsis">{label}</span>
81
78
  <span className="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400">
82
79
  <Icon icon="heroicons:chevron-down-20-solid" aria-hidden="true" />
@@ -69,12 +69,9 @@ const RuntimeFilterDropdown = ({
69
69
  return (
70
70
  <div>
71
71
  <label className="text-sm">Runtimes</label>
72
- <Menu as="div" className="relative text-left">
72
+ <Menu as="div" className="relative text-left" id="h-runtimes-filter">
73
73
  <div>
74
- <Menu.Button
75
- className="relative w-full cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm"
76
- id="h-runtimes-filter"
77
- >
74
+ <Menu.Button className="relative w-full cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm">
78
75
  <span className="block overflow-x-hidden text-ellipsis">Runtimes</span>
79
76
  <span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400">
80
77
  <Icon icon="heroicons:chevron-down-20-solid" aria-hidden="true" />
@@ -28,7 +28,6 @@ const SortBySelect = ({
28
28
  <div>
29
29
  <label className="text-sm">Sort</label>
30
30
  <Select
31
- id="h-sort-by-filter"
32
31
  className="!px-3"
33
32
  items={[
34
33
  {
@@ -73,6 +72,7 @@ const SortBySelect = ({
73
72
  placeholder={'Sort By'}
74
73
  primary={false}
75
74
  disabled={false}
75
+ id="h-sort-by-filter"
76
76
  />
77
77
  </div>
78
78
  );
@@ -325,7 +325,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
325
325
  transition={{duration: 0.5}}
326
326
  >
327
327
  {compareMode ? <DiffLegend /> : null}
328
- <div className="min-h-48">
328
+ <div className="min-h-48" id="h-icicle-graph">
329
329
  {graph !== undefined && (
330
330
  <IcicleGraph
331
331
  width={width}
@@ -172,6 +172,14 @@ const ProfileMetricsGraph = ({
172
172
  onPointClick(timestamp, labels, queryExpression);
173
173
  };
174
174
 
175
+ let sampleUnit = '';
176
+ if (series.every((val, i, arr) => val?.sampleType?.unit === arr[0]?.sampleType?.unit)) {
177
+ sampleUnit = series[0]?.sampleType?.unit ?? '';
178
+ }
179
+ if (sampleUnit === '') {
180
+ sampleUnit = Query.parse(queryExpression).profileType().sampleUnit;
181
+ }
182
+
175
183
  return (
176
184
  <AnimatePresence>
177
185
  <motion.div
@@ -189,7 +197,7 @@ const ProfileMetricsGraph = ({
189
197
  setTimeRange={setTimeRange}
190
198
  onSampleClick={handleSampleClick}
191
199
  addLabelMatcher={addLabelMatcher}
192
- sampleUnit={Query.parse(queryExpression).profileType().sampleUnit}
200
+ sampleUnit={sampleUnit}
193
201
  height={height}
194
202
  width={width}
195
203
  margin={margin}
@@ -177,7 +177,7 @@ const ProfileTypeSelector = ({
177
177
  onSelection={onSelection}
178
178
  placeholder="Select profile type..."
179
179
  loading={loading}
180
- className="bg-white"
180
+ className="bg-white h-profile-type-dropdown"
181
181
  />
182
182
  );
183
183
  };
@@ -42,13 +42,13 @@ const FilterByFunctionButton = ({
42
42
  return (
43
43
  <Input
44
44
  placeholder="Filter by function"
45
- id="h-filter-by-function"
46
45
  className="text-sm"
47
46
  onAction={onAction}
48
47
  onChange={e => setLocalValue(e.target.value)}
49
48
  value={localValue ?? ''}
50
49
  onBlur={() => setLocalValue(storeValue as string)}
51
50
  actionIcon={isClearAction ? <Icon icon="ep:circle-close" /> : <Icon icon="ep:arrow-right" />}
51
+ id="h-filter-by-function"
52
52
  />
53
53
  );
54
54
  };
@@ -24,6 +24,7 @@ interface Props {
24
24
  addView?: boolean;
25
25
  disabled?: boolean;
26
26
  icon?: JSX.Element;
27
+ id?: string;
27
28
  }
28
29
 
29
30
  const ViewSelector = ({
@@ -35,6 +36,7 @@ const ViewSelector = ({
35
36
  addView = false,
36
37
  disabled = false,
37
38
  icon,
39
+ id,
38
40
  }: Props): JSX.Element => {
39
41
  const [callgraphEnabled] = useUIFeatureFlag('callgraph');
40
42
  const [dashboardItems = ['icicle'], setDashboardItems] = useURLState({
@@ -110,6 +112,7 @@ const ViewSelector = ({
110
112
 
111
113
  return (
112
114
  <Select
115
+ className="h-view-selector"
113
116
  items={items}
114
117
  selectedKey={defaultValue}
115
118
  onSelection={onSelection}
@@ -117,6 +120,7 @@ const ViewSelector = ({
117
120
  primary={primary}
118
121
  disabled={disabled}
119
122
  icon={icon}
123
+ id={id}
120
124
  />
121
125
  );
122
126
  };
@@ -73,7 +73,12 @@ export const VisualizationPanel = React.memo(function VisualizationPanel({
73
73
  isMultiPanelView && dashboardItem === 'icicle' && 'pb-[10px]'
74
74
  )}
75
75
  >
76
- <ViewSelector defaultValue={dashboardItem} navigateTo={navigateTo} position={index} />
76
+ <ViewSelector
77
+ id="h-switch-viz"
78
+ defaultValue={dashboardItem}
79
+ navigateTo={navigateTo}
80
+ position={index}
81
+ />
77
82
 
78
83
  {dashboardItem === 'icicle' && flamegraphHint != null ? (
79
84
  <div className="px-2">{flamegraphHint}</div>
@@ -383,7 +383,6 @@ export const ProfileView = ({
383
383
  />
384
384
  ) : null}
385
385
  <Button
386
- id="h-download-pprof"
387
386
  className="gap-2"
388
387
  variant="neutral"
389
388
  onClick={e => {
@@ -391,6 +390,7 @@ export const ProfileView = ({
391
390
  onDownloadPProf();
392
391
  }}
393
392
  disabled={pprofDownloading}
393
+ id="h-download-pprof"
394
394
  >
395
395
  {pprofDownloading != null && pprofDownloading ? 'Downloading...' : 'Download pprof'}
396
396
  <Icon icon="material-symbols:download" width={20} />
@@ -403,6 +403,7 @@ export const ProfileView = ({
403
403
  icon={<Icon icon="material-symbols:add" width={20} />}
404
404
  addView={true}
405
405
  disabled={isMultiPanelView || dashboardItems.length < 1}
406
+ id="h-add-panel"
406
407
  />
407
408
  </div>
408
409
  </div>
@@ -135,7 +135,18 @@ export const ProfileViewWithData = ({
135
135
  perf,
136
136
  ]);
137
137
 
138
- const sampleUnit = profileSource.ProfileType().sampleUnit;
138
+ // Default to the unit of the selected profile type. This is a heuristic, and
139
+ // only a fallback, the unit should be returned by the backend.
140
+ let sampleUnit = profileSource.ProfileType().sampleUnit;
141
+ if (flamegraphResponse?.report.oneofKind === 'flamegraphArrow') {
142
+ sampleUnit = flamegraphResponse.report.flamegraphArrow.unit;
143
+ }
144
+ if (tableResponse?.report.oneofKind === 'tableArrow') {
145
+ sampleUnit = tableResponse.report.tableArrow.unit;
146
+ }
147
+ if (sourceResponse?.report.oneofKind === 'source') {
148
+ sampleUnit = sourceResponse.report.source.unit;
149
+ }
139
150
 
140
151
  const downloadPProfClick = async (): Promise<void> => {
141
152
  if (profileSource == null || queryClient == null) {
@@ -74,7 +74,7 @@ const DiffLegend = (): JSX.Element => {
74
74
  };
75
75
 
76
76
  return (
77
- <div className="mt-1 mb-2 hidden md:block">
77
+ <div className="mt-1 mb-2 hidden md:block" id="h-diff-legend">
78
78
  <div ref={setReferenceElement} className="flex items-center justify-center">
79
79
  <span>Better</span>
80
80
  <DiffLegendBar onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave} />