@parca/profile 0.16.401 → 0.16.403
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/ProfileExplorer/ProfileExplorerCompare.js +3 -2
- package/dist/ProfileIcicleGraph/index.js +13 -1
- package/dist/ProfileMetricsGraph/index.js +28 -4
- package/dist/ProfileSource.d.ts +2 -1
- package/dist/ProfileSource.js +5 -3
- package/package.json +7 -7
- package/src/ProfileExplorer/ProfileExplorerCompare.tsx +9 -2
- package/src/ProfileIcicleGraph/index.tsx +27 -1
- package/src/ProfileMetricsGraph/index.tsx +34 -3
- package/src/ProfileSource.tsx +6 -3
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.403](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.402...@parca/profile@0.16.403) (2024-07-05)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## [0.16.402](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.401...@parca/profile@0.16.402) (2024-07-04)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.401](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.400...@parca/profile@0.16.401) (2024-07-03)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { Card } from '@parca/components';
|
|
2
|
+
import { Card, useURLState } from '@parca/components';
|
|
3
3
|
import { Query } from '@parca/parser';
|
|
4
4
|
import { ProfileDiffSource, ProfileViewWithData } from '..';
|
|
5
5
|
import ProfileSelector from '../ProfileSelector';
|
|
@@ -10,6 +10,7 @@ const ProfileExplorerCompare = ({ queryClient, queryA, queryB, profileA, profile
|
|
|
10
10
|
const closeProfileB = () => {
|
|
11
11
|
closeProfile('B');
|
|
12
12
|
};
|
|
13
|
-
|
|
13
|
+
const [compareAbsolute] = useURLState({ param: 'compare_absolute', navigateTo });
|
|
14
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex justify-between gap-2", children: [_jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryA, profileSelection: profileA, selectProfile: selectProfileA, selectQuery: selectQueryA, closeProfile: closeProfileA, enforcedProfileName: '', comparing: true, navigateTo: navigateTo }) }), _jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryB, profileSelection: profileB, selectProfile: selectProfileB, selectQuery: selectQueryB, closeProfile: closeProfileB, enforcedProfileName: Query.parse(queryA.expression).profileName(), comparing: true, navigateTo: navigateTo }) })] }), _jsx("div", { className: "grid grid-cols-1", children: profileA != null && profileB != null ? (_jsx("div", { children: _jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { navigateTo: navigateTo, queryClient: queryClient, profileSource: new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource(), '', compareAbsolute === 'true') }) }) })) : (_jsx("div", { children: _jsx("div", { className: "my-20 text-center", children: _jsx("p", { children: "Select a profile on both sides." }) }) })) })] }));
|
|
14
15
|
};
|
|
15
16
|
export default ProfileExplorerCompare;
|
|
@@ -93,6 +93,15 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
93
93
|
navigateTo,
|
|
94
94
|
});
|
|
95
95
|
const isInvert = invertStack === 'true';
|
|
96
|
+
// By default, we want delta profiles (CPU) to be relatively compared.
|
|
97
|
+
// For non-delta profiles, like goroutines or memory, we want the profiles to be compared absolutely.
|
|
98
|
+
const compareAbsoluteDefault = profileType?.delta === false ? 'true' : 'false';
|
|
99
|
+
const [compareAbsolute = compareAbsoluteDefault, setCompareAbsolute] = useURLState({
|
|
100
|
+
param: 'compare_absolute',
|
|
101
|
+
navigateTo,
|
|
102
|
+
withURLUpdate: true,
|
|
103
|
+
});
|
|
104
|
+
const isCompareAbsolute = compareAbsolute === 'true';
|
|
96
105
|
const [totalFormatted, totalUnfilteredFormatted, isTrimmed, trimmedFormatted, trimmedPercentage, isFiltered, filteredPercentage,] = useMemo(() => {
|
|
97
106
|
if (graph === undefined && arrow === undefined) {
|
|
98
107
|
return ['0', '0', false, '0', '0', false, '0', '0'];
|
|
@@ -112,7 +121,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
112
121
|
];
|
|
113
122
|
}, [graph, arrow, filtered, total]);
|
|
114
123
|
useEffect(() => {
|
|
115
|
-
setActionButtons?.(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [_jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', toolTipText: isInvert ? 'Original Call Stack' : 'Invert Call Stack', onClick: () => setInvertStack(isInvert ? '' : 'true'), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setInvertStack(isInvert ? '' : 'true'), children: [isInvert ? 'Original Call Stack' : 'Invert Call Stack', _jsx(Icon, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', width: 20 })] })), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
|
|
124
|
+
setActionButtons?.(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [_jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', toolTipText: isInvert ? 'Original Call Stack' : 'Invert Call Stack', onClick: () => setInvertStack(isInvert ? '' : 'true'), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setInvertStack(isInvert ? '' : 'true'), children: [isInvert ? 'Original Call Stack' : 'Invert Call Stack', _jsx(Icon, { icon: isInvert ? 'ph:sort-ascending' : 'ph:sort-descending', width: 20 })] })), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), compareMode && (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setCompareAbsolute(isCompareAbsolute ? '' : 'true'), children: [isCompareAbsolute ? 'Compare Relative' : 'Compare Absolute', _jsx(Icon, { icon: isCompareAbsolute ? 'fluent-mdl2:compare' : 'fluent-mdl2:compare-uneven', width: 20 })] })), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), className: "rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
|
|
116
125
|
}, [
|
|
117
126
|
navigateTo,
|
|
118
127
|
isInvert,
|
|
@@ -124,6 +133,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
124
133
|
loading,
|
|
125
134
|
isHalfScreen,
|
|
126
135
|
isLoading,
|
|
136
|
+
compareMode,
|
|
137
|
+
isCompareAbsolute,
|
|
138
|
+
setCompareAbsolute,
|
|
127
139
|
]);
|
|
128
140
|
const loadingState = !loading && (arrow !== undefined || graph !== undefined) && mappings !== undefined;
|
|
129
141
|
useEffect(() => {
|
|
@@ -11,10 +11,10 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
11
11
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
|
-
import { useEffect, useRef, useState } from 'react';
|
|
14
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
15
15
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
16
16
|
import { Duration, Timestamp } from '@parca/client';
|
|
17
|
-
import { MetricsGraphSkeleton, useGrpcMetadata, useParcaContext, } from '@parca/components';
|
|
17
|
+
import { MetricsGraphSkeleton, useGrpcMetadata, useParcaContext, useURLState, } from '@parca/components';
|
|
18
18
|
import { Query } from '@parca/parser';
|
|
19
19
|
import { capitalizeOnlyFirstLetter, getStepDuration } from '@parca/utilities';
|
|
20
20
|
import MetricsGraph from '../MetricsGraph';
|
|
@@ -25,6 +25,14 @@ const ErrorContent = ({ errorMessage }) => {
|
|
|
25
25
|
export const ProfileMetricsEmptyState = ({ message }) => {
|
|
26
26
|
return (_jsx("div", { className: "flex h-full w-full flex-col items-center justify-center", children: _jsx("p", { children: message }) }));
|
|
27
27
|
};
|
|
28
|
+
const getStepCountFromScreenWidth = (pixelsPerPoint) => {
|
|
29
|
+
let width =
|
|
30
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
31
|
+
window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
|
32
|
+
// subtract the padding around the graph
|
|
33
|
+
width = width - (20 + 24 + 68) * 2;
|
|
34
|
+
return Math.round(width / pixelsPerPoint);
|
|
35
|
+
};
|
|
28
36
|
const EMPTY_SUM_BY = [];
|
|
29
37
|
export const useQueryRange = (client, queryExpression, start, end, timeRange, sumBy = EMPTY_SUM_BY, skip = false) => {
|
|
30
38
|
const previousQueryParams = useRef({});
|
|
@@ -35,6 +43,22 @@ export const useQueryRange = (client, queryExpression, start, end, timeRange, su
|
|
|
35
43
|
error: null,
|
|
36
44
|
});
|
|
37
45
|
const metadata = useGrpcMetadata();
|
|
46
|
+
const { navigateTo } = useParcaContext();
|
|
47
|
+
const [stepCountStr, setStepCount] = useURLState({ param: 'step_count', navigateTo });
|
|
48
|
+
const defaultStepCount = useMemo(() => {
|
|
49
|
+
return getStepCountFromScreenWidth(10);
|
|
50
|
+
}, []);
|
|
51
|
+
const stepCount = useMemo(() => {
|
|
52
|
+
if (stepCountStr != null) {
|
|
53
|
+
return parseInt(stepCountStr, 10);
|
|
54
|
+
}
|
|
55
|
+
return defaultStepCount;
|
|
56
|
+
}, [stepCountStr, defaultStepCount]);
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
if (stepCountStr == null) {
|
|
59
|
+
setStepCount(defaultStepCount.toString());
|
|
60
|
+
}
|
|
61
|
+
}, [stepCountStr, defaultStepCount, setStepCount]);
|
|
38
62
|
useEffect(() => {
|
|
39
63
|
void (async () => {
|
|
40
64
|
if (skip) {
|
|
@@ -50,7 +74,7 @@ export const useQueryRange = (client, queryExpression, start, end, timeRange, su
|
|
|
50
74
|
previousQueryParams.current.isRefresh = true;
|
|
51
75
|
}
|
|
52
76
|
setLoading(true);
|
|
53
|
-
const stepDuration = getStepDuration(start, end);
|
|
77
|
+
const stepDuration = getStepDuration(start, end, stepCount);
|
|
54
78
|
const call = client.queryRange({
|
|
55
79
|
query: queryExpression,
|
|
56
80
|
start: Timestamp.fromDate(new Date(start)),
|
|
@@ -75,7 +99,7 @@ export const useQueryRange = (client, queryExpression, start, end, timeRange, su
|
|
|
75
99
|
}
|
|
76
100
|
});
|
|
77
101
|
})();
|
|
78
|
-
}, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip]);
|
|
102
|
+
}, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip, stepCount]);
|
|
79
103
|
return { ...state, isLoading, isRefreshing: previousQueryParams.current.isRefresh };
|
|
80
104
|
};
|
|
81
105
|
const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to, setTimeRange, addLabelMatcher, onPointClick, comparing = false, timeRange, }) => {
|
package/dist/ProfileSource.d.ts
CHANGED
|
@@ -42,7 +42,8 @@ export declare class ProfileDiffSource implements ProfileSource {
|
|
|
42
42
|
b: ProfileSource;
|
|
43
43
|
filterQuery: string | undefined;
|
|
44
44
|
profileType: ProfileType;
|
|
45
|
-
|
|
45
|
+
absolute?: boolean;
|
|
46
|
+
constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string, absolute?: boolean);
|
|
46
47
|
DiffSelection(): ProfileDiffSelection;
|
|
47
48
|
QueryRequest(): QueryRequest;
|
|
48
49
|
ProfileType(): ProfileType;
|
package/dist/ProfileSource.js
CHANGED
|
@@ -73,11 +73,12 @@ export class MergedProfileSelection {
|
|
|
73
73
|
}
|
|
74
74
|
}
|
|
75
75
|
export class ProfileDiffSource {
|
|
76
|
-
constructor(a, b, filterQuery) {
|
|
76
|
+
constructor(a, b, filterQuery, absolute) {
|
|
77
77
|
this.a = a;
|
|
78
78
|
this.b = b;
|
|
79
79
|
this.filterQuery = filterQuery;
|
|
80
80
|
this.profileType = a.ProfileType();
|
|
81
|
+
this.absolute = absolute;
|
|
81
82
|
}
|
|
82
83
|
DiffSelection() {
|
|
83
84
|
throw new Error('Method not implemented.');
|
|
@@ -89,9 +90,10 @@ export class ProfileDiffSource {
|
|
|
89
90
|
diff: {
|
|
90
91
|
a: this.a.DiffSelection(),
|
|
91
92
|
b: this.b.DiffSelection(),
|
|
93
|
+
absolute: this.absolute,
|
|
92
94
|
},
|
|
93
95
|
},
|
|
94
|
-
reportType: QueryRequest_ReportType.
|
|
96
|
+
reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
|
|
95
97
|
mode: QueryRequest_Mode.DIFF,
|
|
96
98
|
filterQuery: this.filterQuery,
|
|
97
99
|
filter: [],
|
|
@@ -143,7 +145,7 @@ export class MergedProfileSource {
|
|
|
143
145
|
query: this.query.toString(),
|
|
144
146
|
},
|
|
145
147
|
},
|
|
146
|
-
reportType: QueryRequest_ReportType.
|
|
148
|
+
reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
|
|
147
149
|
mode: QueryRequest_Mode.MERGE,
|
|
148
150
|
filterQuery: this.filterQuery,
|
|
149
151
|
filter: [],
|
package/package.json
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.403",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@headlessui/react": "^1.7.19",
|
|
7
7
|
"@iconify/react": "^4.0.0",
|
|
8
|
-
"@parca/client": "0.16.
|
|
9
|
-
"@parca/components": "0.16.
|
|
8
|
+
"@parca/client": "0.16.120",
|
|
9
|
+
"@parca/components": "0.16.290",
|
|
10
10
|
"@parca/dynamicsize": "0.16.65",
|
|
11
|
-
"@parca/hooks": "0.0.
|
|
11
|
+
"@parca/hooks": "0.0.65",
|
|
12
12
|
"@parca/icons": "0.16.70",
|
|
13
13
|
"@parca/parser": "0.16.77",
|
|
14
|
-
"@parca/store": "0.16.
|
|
15
|
-
"@parca/utilities": "0.0.
|
|
14
|
+
"@parca/store": "0.16.154",
|
|
15
|
+
"@parca/utilities": "0.0.81",
|
|
16
16
|
"@popperjs/core": "^2.11.8",
|
|
17
17
|
"@protobuf-ts/runtime-rpc": "^2.5.0",
|
|
18
18
|
"@tanstack/react-query": "^4.0.5",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"access": "public",
|
|
74
74
|
"registry": "https://registry.npmjs.org/"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "a29408aa67b9e9791fd62789420cbfc7503adb46"
|
|
77
77
|
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
14
|
import {QueryServiceClient} from '@parca/client';
|
|
15
|
-
import {Card} from '@parca/components';
|
|
15
|
+
import {Card, useURLState} from '@parca/components';
|
|
16
16
|
import {Query} from '@parca/parser';
|
|
17
17
|
import type {NavigateFunction} from '@parca/utilities';
|
|
18
18
|
|
|
@@ -56,6 +56,8 @@ const ProfileExplorerCompare = ({
|
|
|
56
56
|
closeProfile('B');
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
+
const [compareAbsolute] = useURLState({param: 'compare_absolute', navigateTo});
|
|
60
|
+
|
|
59
61
|
return (
|
|
60
62
|
<>
|
|
61
63
|
<div className="flex justify-between gap-2">
|
|
@@ -94,7 +96,12 @@ const ProfileExplorerCompare = ({
|
|
|
94
96
|
navigateTo={navigateTo}
|
|
95
97
|
queryClient={queryClient}
|
|
96
98
|
profileSource={
|
|
97
|
-
new ProfileDiffSource(
|
|
99
|
+
new ProfileDiffSource(
|
|
100
|
+
profileA.ProfileSource(),
|
|
101
|
+
profileB.ProfileSource(),
|
|
102
|
+
'',
|
|
103
|
+
compareAbsolute === 'true'
|
|
104
|
+
)
|
|
98
105
|
}
|
|
99
106
|
/>
|
|
100
107
|
</Card>
|
|
@@ -238,9 +238,19 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
238
238
|
param: 'invert_call_stack',
|
|
239
239
|
navigateTo,
|
|
240
240
|
});
|
|
241
|
-
|
|
242
241
|
const isInvert = invertStack === 'true';
|
|
243
242
|
|
|
243
|
+
// By default, we want delta profiles (CPU) to be relatively compared.
|
|
244
|
+
// For non-delta profiles, like goroutines or memory, we want the profiles to be compared absolutely.
|
|
245
|
+
const compareAbsoluteDefault = profileType?.delta === false ? 'true' : 'false';
|
|
246
|
+
|
|
247
|
+
const [compareAbsolute = compareAbsoluteDefault, setCompareAbsolute] = useURLState({
|
|
248
|
+
param: 'compare_absolute',
|
|
249
|
+
navigateTo,
|
|
250
|
+
withURLUpdate: true,
|
|
251
|
+
});
|
|
252
|
+
const isCompareAbsolute = compareAbsolute === 'true';
|
|
253
|
+
|
|
244
254
|
const [
|
|
245
255
|
totalFormatted,
|
|
246
256
|
totalUnfilteredFormatted,
|
|
@@ -294,6 +304,19 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
294
304
|
</Button>
|
|
295
305
|
)}
|
|
296
306
|
<ShowHideLegendButton isHalfScreen={isHalfScreen} navigateTo={navigateTo} />
|
|
307
|
+
{compareMode && (
|
|
308
|
+
<Button
|
|
309
|
+
variant="neutral"
|
|
310
|
+
className="gap-2 w-max"
|
|
311
|
+
onClick={() => setCompareAbsolute(isCompareAbsolute ? '' : 'true')}
|
|
312
|
+
>
|
|
313
|
+
{isCompareAbsolute ? 'Compare Relative' : 'Compare Absolute'}
|
|
314
|
+
<Icon
|
|
315
|
+
icon={isCompareAbsolute ? 'fluent-mdl2:compare' : 'fluent-mdl2:compare-uneven'}
|
|
316
|
+
width={20}
|
|
317
|
+
/>
|
|
318
|
+
</Button>
|
|
319
|
+
)}
|
|
297
320
|
{isHalfScreen ? (
|
|
298
321
|
<IconButton
|
|
299
322
|
icon="system-uicons:reset"
|
|
@@ -327,6 +350,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
327
350
|
loading,
|
|
328
351
|
isHalfScreen,
|
|
329
352
|
isLoading,
|
|
353
|
+
compareMode,
|
|
354
|
+
isCompareAbsolute,
|
|
355
|
+
setCompareAbsolute,
|
|
330
356
|
]);
|
|
331
357
|
|
|
332
358
|
const loadingState =
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useEffect, useRef, useState} from 'react';
|
|
14
|
+
import {useEffect, useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
17
|
import {AnimatePresence, motion} from 'framer-motion';
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
MetricsGraphSkeleton,
|
|
23
23
|
useGrpcMetadata,
|
|
24
24
|
useParcaContext,
|
|
25
|
+
useURLState,
|
|
25
26
|
} from '@parca/components';
|
|
26
27
|
import {Query} from '@parca/parser';
|
|
27
28
|
import {capitalizeOnlyFirstLetter, getStepDuration} from '@parca/utilities';
|
|
@@ -80,6 +81,16 @@ export interface IQueryRangeState {
|
|
|
80
81
|
error: RpcError | null;
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
const getStepCountFromScreenWidth = (pixelsPerPoint: number): number => {
|
|
85
|
+
let width =
|
|
86
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
87
|
+
window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
|
88
|
+
|
|
89
|
+
// subtract the padding around the graph
|
|
90
|
+
width = width - (20 + 24 + 68) * 2;
|
|
91
|
+
return Math.round(width / pixelsPerPoint);
|
|
92
|
+
};
|
|
93
|
+
|
|
83
94
|
const EMPTY_SUM_BY: string[] = [];
|
|
84
95
|
|
|
85
96
|
export const useQueryRange = (
|
|
@@ -103,6 +114,26 @@ export const useQueryRange = (
|
|
|
103
114
|
error: null,
|
|
104
115
|
});
|
|
105
116
|
const metadata = useGrpcMetadata();
|
|
117
|
+
const {navigateTo} = useParcaContext();
|
|
118
|
+
const [stepCountStr, setStepCount] = useURLState({param: 'step_count', navigateTo});
|
|
119
|
+
|
|
120
|
+
const defaultStepCount = useMemo(() => {
|
|
121
|
+
return getStepCountFromScreenWidth(10);
|
|
122
|
+
}, []);
|
|
123
|
+
|
|
124
|
+
const stepCount = useMemo(() => {
|
|
125
|
+
if (stepCountStr != null) {
|
|
126
|
+
return parseInt(stepCountStr as string, 10);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return defaultStepCount;
|
|
130
|
+
}, [stepCountStr, defaultStepCount]);
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (stepCountStr == null) {
|
|
134
|
+
setStepCount(defaultStepCount.toString());
|
|
135
|
+
}
|
|
136
|
+
}, [stepCountStr, defaultStepCount, setStepCount]);
|
|
106
137
|
|
|
107
138
|
useEffect(() => {
|
|
108
139
|
void (async () => {
|
|
@@ -123,7 +154,7 @@ export const useQueryRange = (
|
|
|
123
154
|
|
|
124
155
|
setLoading(true);
|
|
125
156
|
|
|
126
|
-
const stepDuration = getStepDuration(start, end);
|
|
157
|
+
const stepDuration = getStepDuration(start, end, stepCount);
|
|
127
158
|
const call = client.queryRange(
|
|
128
159
|
{
|
|
129
160
|
query: queryExpression,
|
|
@@ -152,7 +183,7 @@ export const useQueryRange = (
|
|
|
152
183
|
}
|
|
153
184
|
});
|
|
154
185
|
})();
|
|
155
|
-
}, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip]);
|
|
186
|
+
}, [client, queryExpression, start, end, metadata, timeRange, sumBy, skip, stepCount]);
|
|
156
187
|
|
|
157
188
|
return {...state, isLoading, isRefreshing: previousQueryParams.current.isRefresh};
|
|
158
189
|
};
|
package/src/ProfileSource.tsx
CHANGED
|
@@ -136,12 +136,14 @@ export class ProfileDiffSource implements ProfileSource {
|
|
|
136
136
|
b: ProfileSource;
|
|
137
137
|
filterQuery: string | undefined;
|
|
138
138
|
profileType: ProfileType;
|
|
139
|
+
absolute?: boolean;
|
|
139
140
|
|
|
140
|
-
constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string) {
|
|
141
|
+
constructor(a: ProfileSource, b: ProfileSource, filterQuery?: string, absolute?: boolean) {
|
|
141
142
|
this.a = a;
|
|
142
143
|
this.b = b;
|
|
143
144
|
this.filterQuery = filterQuery;
|
|
144
145
|
this.profileType = a.ProfileType();
|
|
146
|
+
this.absolute = absolute;
|
|
145
147
|
}
|
|
146
148
|
|
|
147
149
|
DiffSelection(): ProfileDiffSelection {
|
|
@@ -155,9 +157,10 @@ export class ProfileDiffSource implements ProfileSource {
|
|
|
155
157
|
diff: {
|
|
156
158
|
a: this.a.DiffSelection(),
|
|
157
159
|
b: this.b.DiffSelection(),
|
|
160
|
+
absolute: this.absolute,
|
|
158
161
|
},
|
|
159
162
|
},
|
|
160
|
-
reportType: QueryRequest_ReportType.
|
|
163
|
+
reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
|
|
161
164
|
mode: QueryRequest_Mode.DIFF,
|
|
162
165
|
filterQuery: this.filterQuery,
|
|
163
166
|
filter: [],
|
|
@@ -227,7 +230,7 @@ export class MergedProfileSource implements ProfileSource {
|
|
|
227
230
|
query: this.query.toString(),
|
|
228
231
|
},
|
|
229
232
|
},
|
|
230
|
-
reportType: QueryRequest_ReportType.
|
|
233
|
+
reportType: QueryRequest_ReportType.FLAMEGRAPH_ARROW,
|
|
231
234
|
mode: QueryRequest_Mode.MERGE,
|
|
232
235
|
filterQuery: this.filterQuery,
|
|
233
236
|
filter: [],
|