@parca/profile 0.16.342 → 0.16.344
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/MetricsGraph/index.d.ts +1 -1
- package/dist/MetricsGraph/index.js +2 -2
- package/dist/ProfileIcicleGraph/ActionButtons/GroupByDropdown.js +1 -1
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.js +1 -1
- package/dist/ProfileIcicleGraph/ActionButtons/SortBySelect.js +2 -2
- package/dist/ProfileIcicleGraph/index.js +1 -1
- package/dist/ProfileMetricsGraph/index.d.ts +1 -1
- package/dist/ProfileMetricsGraph/index.js +2 -2
- package/dist/ProfileSelector/index.js +3 -5
- package/dist/ProfileTypeSelector/index.js +1 -1
- package/dist/ProfileView/FilterByFunctionButton.js +1 -1
- package/dist/ProfileView/ViewSelector.d.ts +2 -1
- package/dist/ProfileView/ViewSelector.js +2 -2
- package/dist/ProfileView/VisualizationPanel.js +1 -1
- package/dist/ProfileView/index.js +2 -2
- package/dist/components/DiffLegend.js +1 -1
- package/package.json +3 -3
- package/src/MetricsGraph/index.tsx +3 -2
- package/src/ProfileIcicleGraph/ActionButtons/GroupByDropdown.tsx +2 -5
- package/src/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.tsx +2 -5
- package/src/ProfileIcicleGraph/ActionButtons/SortBySelect.tsx +1 -1
- package/src/ProfileIcicleGraph/index.tsx +1 -1
- package/src/ProfileMetricsGraph/index.tsx +13 -3
- package/src/ProfileSelector/index.tsx +9 -6
- package/src/ProfileTypeSelector/index.tsx +1 -1
- package/src/ProfileView/FilterByFunctionButton.tsx +1 -1
- package/src/ProfileView/ViewSelector.tsx +4 -0
- package/src/ProfileView/VisualizationPanel.tsx +6 -1
- package/src/ProfileView/index.tsx +2 -1
- 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.344](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.343...@parca/profile@0.16.344) (2024-02-21)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## [0.16.343](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.342...@parca/profile@0.16.343) (2024-02-20)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.342](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.341...@parca/profile@0.16.342) (2024-02-14)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -7,7 +7,7 @@ interface Props {
|
|
|
7
7
|
from: number;
|
|
8
8
|
to: number;
|
|
9
9
|
profile: MergedProfileSelection | null;
|
|
10
|
-
onSampleClick: (timestamp: number, value: number, labels: Label[]) => void;
|
|
10
|
+
onSampleClick: (timestamp: number, value: number, labels: Label[], duration: number) => void;
|
|
11
11
|
addLabelMatcher: (labels: {
|
|
12
12
|
key: string;
|
|
13
13
|
value: string;
|
|
@@ -139,8 +139,8 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLab
|
|
|
139
139
|
};
|
|
140
140
|
const openClosestProfile = () => {
|
|
141
141
|
if (highlighted != null) {
|
|
142
|
-
onSampleClick(Math.round(highlighted.timestamp), highlighted.value, sanitizeHighlightedValues(highlighted.labels) // When a user clicks on any sample in the graph, replace single `\` in the `labelValues` string with doubles `\\` if available.
|
|
143
|
-
);
|
|
142
|
+
onSampleClick(Math.round(highlighted.timestamp), highlighted.value, sanitizeHighlightedValues(highlighted.labels), // When a user clicks on any sample in the graph, replace single `\` in the `labelValues` string with doubles `\\` if available.
|
|
143
|
+
highlighted.duration);
|
|
144
144
|
}
|
|
145
145
|
};
|
|
146
146
|
const onMouseUp = (e) => {
|
|
@@ -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",
|
|
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",
|
|
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, {
|
|
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;
|
|
@@ -21,7 +21,7 @@ interface ProfileMetricsGraphProps {
|
|
|
21
21
|
key: string;
|
|
22
22
|
value: string;
|
|
23
23
|
}>) => void;
|
|
24
|
-
onPointClick: (timestamp: number, labels: Label[], queryExpression: string) => void;
|
|
24
|
+
onPointClick: (timestamp: number, labels: Label[], queryExpression: string, duration: number) => void;
|
|
25
25
|
comparing?: boolean;
|
|
26
26
|
}
|
|
27
27
|
export interface IQueryRangeState {
|
|
@@ -88,8 +88,8 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
|
|
|
88
88
|
return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(error.message) });
|
|
89
89
|
}
|
|
90
90
|
if (dataAvailable) {
|
|
91
|
-
const handleSampleClick = (timestamp, _value, labels) => {
|
|
92
|
-
onPointClick(timestamp, labels, queryExpression);
|
|
91
|
+
const handleSampleClick = (timestamp, _value, labels, duration) => {
|
|
92
|
+
onPointClick(timestamp, labels, queryExpression, duration);
|
|
93
93
|
};
|
|
94
94
|
let sampleUnit = '';
|
|
95
95
|
if (series.every((val, i, arr) => val?.sampleType?.unit === arr[0]?.sampleType?.unit)) {
|
|
@@ -15,7 +15,6 @@ import { useEffect, useState } from 'react';
|
|
|
15
15
|
import { Button, ButtonGroup, DateTimeRange, DateTimeRangePicker, IconButton, useGrpcMetadata, useParcaContext, } from '@parca/components';
|
|
16
16
|
import { CloseIcon } from '@parca/icons';
|
|
17
17
|
import { Query } from '@parca/parser';
|
|
18
|
-
import { getStepDuration, getStepDurationInMilliseconds } from '@parca/utilities';
|
|
19
18
|
import { MergedProfileSelection } from '..';
|
|
20
19
|
import MatchersInput from '../MatchersInput/index';
|
|
21
20
|
import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
|
|
@@ -177,7 +176,7 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
|
|
|
177
176
|
timeSelection: range.getRangeKey(),
|
|
178
177
|
...mergedProfileParams,
|
|
179
178
|
});
|
|
180
|
-
}, addLabelMatcher: addLabelMatcher, onPointClick: (timestamp, labels, queryExpression) => {
|
|
179
|
+
}, addLabelMatcher: addLabelMatcher, onPointClick: (timestamp, labels, queryExpression, duration) => {
|
|
181
180
|
// TODO: Pass the query object via click rather than queryExpression
|
|
182
181
|
let query = Query.parse(queryExpression);
|
|
183
182
|
labels.forEach(l => {
|
|
@@ -186,11 +185,10 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
|
|
|
186
185
|
query = newQuery;
|
|
187
186
|
}
|
|
188
187
|
});
|
|
189
|
-
const
|
|
190
|
-
const stepDurationInMilliseconds = getStepDurationInMilliseconds(stepDuration);
|
|
188
|
+
const durationInMilliseconds = duration / 1000000; // duration is in nanoseconds
|
|
191
189
|
const mergeFrom = timestamp;
|
|
192
190
|
const mergeTo = query.profileType().delta
|
|
193
|
-
? mergeFrom +
|
|
191
|
+
? mergeFrom + durationInMilliseconds
|
|
194
192
|
: mergeFrom;
|
|
195
193
|
selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
|
|
196
194
|
} }) })) : (_jsx(_Fragment, { children: profileSelection == null ? (_jsx("div", { className: "p-2", children: _jsx(ProfileMetricsEmptyState, { message: `Please select a profile type and click "Search" to begin.` }) })) : null })) }) })] }));
|
|
@@ -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",
|
|
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, {
|
|
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') },
|
|
@@ -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,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.344",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@parca/client": "^0.16.103",
|
|
7
|
-
"@parca/components": "^0.16.
|
|
7
|
+
"@parca/components": "^0.16.253",
|
|
8
8
|
"@parca/dynamicsize": "^0.16.60",
|
|
9
9
|
"@parca/hooks": "^0.0.41",
|
|
10
10
|
"@parca/parser": "^0.16.68",
|
|
@@ -51,5 +51,5 @@
|
|
|
51
51
|
"access": "public",
|
|
52
52
|
"registry": "https://registry.npmjs.org/"
|
|
53
53
|
},
|
|
54
|
-
"gitHead": "
|
|
54
|
+
"gitHead": "419fad9b80e702bafd775521f69a812bc5e81124"
|
|
55
55
|
}
|
|
@@ -40,7 +40,7 @@ interface Props {
|
|
|
40
40
|
from: number;
|
|
41
41
|
to: number;
|
|
42
42
|
profile: MergedProfileSelection | null;
|
|
43
|
-
onSampleClick: (timestamp: number, value: number, labels: Label[]) => void;
|
|
43
|
+
onSampleClick: (timestamp: number, value: number, labels: Label[], duration: number) => void;
|
|
44
44
|
addLabelMatcher: (
|
|
45
45
|
labels: {key: string; value: string} | Array<{key: string; value: string}>
|
|
46
46
|
) => void;
|
|
@@ -260,7 +260,8 @@ export const RawMetricsGraph = ({
|
|
|
260
260
|
onSampleClick(
|
|
261
261
|
Math.round(highlighted.timestamp),
|
|
262
262
|
highlighted.value,
|
|
263
|
-
sanitizeHighlightedValues(highlighted.labels) // When a user clicks on any sample in the graph, replace single `\` in the `labelValues` string with doubles `\\` if available.
|
|
263
|
+
sanitizeHighlightedValues(highlighted.labels), // When a user clicks on any sample in the graph, replace single `\` in the `labelValues` string with doubles `\\` if available.
|
|
264
|
+
highlighted.duration
|
|
264
265
|
);
|
|
265
266
|
}
|
|
266
267
|
};
|
|
@@ -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}
|
|
@@ -64,7 +64,12 @@ interface ProfileMetricsGraphProps {
|
|
|
64
64
|
addLabelMatcher: (
|
|
65
65
|
labels: {key: string; value: string} | Array<{key: string; value: string}>
|
|
66
66
|
) => void;
|
|
67
|
-
onPointClick: (
|
|
67
|
+
onPointClick: (
|
|
68
|
+
timestamp: number,
|
|
69
|
+
labels: Label[],
|
|
70
|
+
queryExpression: string,
|
|
71
|
+
duration: number
|
|
72
|
+
) => void;
|
|
68
73
|
comparing?: boolean;
|
|
69
74
|
}
|
|
70
75
|
|
|
@@ -168,8 +173,13 @@ const ProfileMetricsGraph = ({
|
|
|
168
173
|
}
|
|
169
174
|
|
|
170
175
|
if (dataAvailable) {
|
|
171
|
-
const handleSampleClick = (
|
|
172
|
-
|
|
176
|
+
const handleSampleClick = (
|
|
177
|
+
timestamp: number,
|
|
178
|
+
_value: number,
|
|
179
|
+
labels: Label[],
|
|
180
|
+
duration: number
|
|
181
|
+
): void => {
|
|
182
|
+
onPointClick(timestamp, labels, queryExpression, duration);
|
|
173
183
|
};
|
|
174
184
|
|
|
175
185
|
let sampleUnit = '';
|
|
@@ -15,7 +15,7 @@ import React, {useEffect, useState} from 'react';
|
|
|
15
15
|
|
|
16
16
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
17
|
|
|
18
|
-
import {ProfileTypesResponse, QueryServiceClient} from '@parca/client';
|
|
18
|
+
import {Label, ProfileTypesResponse, QueryServiceClient} from '@parca/client';
|
|
19
19
|
import {
|
|
20
20
|
Button,
|
|
21
21
|
ButtonGroup,
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
} from '@parca/components';
|
|
28
28
|
import {CloseIcon} from '@parca/icons';
|
|
29
29
|
import {Query} from '@parca/parser';
|
|
30
|
-
import {getStepDuration, getStepDurationInMilliseconds} from '@parca/utilities';
|
|
31
30
|
|
|
32
31
|
import {MergedProfileSelection, ProfileSelection} from '..';
|
|
33
32
|
import MatchersInput from '../MatchersInput/index';
|
|
@@ -327,7 +326,12 @@ const ProfileSelector = ({
|
|
|
327
326
|
});
|
|
328
327
|
}}
|
|
329
328
|
addLabelMatcher={addLabelMatcher}
|
|
330
|
-
onPointClick={(
|
|
329
|
+
onPointClick={(
|
|
330
|
+
timestamp: number,
|
|
331
|
+
labels: Label[],
|
|
332
|
+
queryExpression: string,
|
|
333
|
+
duration: number
|
|
334
|
+
) => {
|
|
331
335
|
// TODO: Pass the query object via click rather than queryExpression
|
|
332
336
|
let query = Query.parse(queryExpression);
|
|
333
337
|
labels.forEach(l => {
|
|
@@ -337,11 +341,10 @@ const ProfileSelector = ({
|
|
|
337
341
|
}
|
|
338
342
|
});
|
|
339
343
|
|
|
340
|
-
const
|
|
341
|
-
const stepDurationInMilliseconds = getStepDurationInMilliseconds(stepDuration);
|
|
344
|
+
const durationInMilliseconds = duration / 1000000; // duration is in nanoseconds
|
|
342
345
|
const mergeFrom = timestamp;
|
|
343
346
|
const mergeTo = query.profileType().delta
|
|
344
|
-
? mergeFrom +
|
|
347
|
+
? mergeFrom + durationInMilliseconds
|
|
345
348
|
: mergeFrom;
|
|
346
349
|
selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
|
|
347
350
|
}}
|
|
@@ -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
|
|
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>
|
|
@@ -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} />
|