@parca/profile 0.16.409 → 0.16.410
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 +4 -0
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +30 -39
- package/dist/ProfileMetricsGraph/useSumBy.d.ts +1 -0
- package/dist/ProfileMetricsGraph/useSumBy.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/useSumBy.js +49 -6
- package/package.json +2 -2
- package/src/ProfileMetricsGraph/index.tsx +71 -63
- package/src/ProfileMetricsGraph/useSumBy.ts +66 -6
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
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.410](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.409...@parca/profile@0.16.410) (2024-07-11)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
6
10
|
## [0.16.409](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.408...@parca/profile@0.16.409) (2024-07-10)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileMetricsGraph/index.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAGlD,OAAO,EAAW,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAY,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,aAAa,EAKd,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAyB,gBAAgB,EAAC,MAAM,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/ProfileMetricsGraph/index.tsx"],"names":[],"mappings":"AAeA,OAAO,EAAC,QAAQ,EAAC,MAAM,0BAA0B,CAAC;AAGlD,OAAO,EAAW,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAY,MAAM,eAAe,CAAC;AACjG,OAAO,EACL,aAAa,EAKd,MAAM,mBAAmB,CAAC;AAI3B,OAAO,EAAyB,gBAAgB,EAAC,MAAM,IAAI,CAAC;AAQ5D,UAAU,6BAA6B;IACrC,OAAO,EAAE,MAAM,CAAC;CACjB;AAaD,eAAO,MAAM,wBAAwB,gBAAe,6BAA6B,KAAG,GAAG,CAAC,OAMvF,CAAC;AAEF,UAAU,wBAAwB;IAChC,WAAW,EAAE,kBAAkB,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,MAAM,CAAC;IACX,YAAY,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC7C,eAAe,EAAE,CACf,MAAM,EAAE;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,GAAG,KAAK,CAAC;QAAC,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAC,CAAC,KACvE,IAAI,CAAC;IACV,YAAY,EAAE,CACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,KAAK,EAAE,EACf,eAAe,EAAE,MAAM,EACvB,QAAQ,EAAE,MAAM,KACb,IAAI,CAAC;IACV,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,kBAAkB,GAAG,IAAI,CAAC;IACpC,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAYD,eAAO,MAAM,aAAa,WAChB,kBAAkB,mBACT,MAAM,SAChB,MAAM,OACR,MAAM,UACJ,MAAM,EAAE,qBAEd,gBA+CF,CAAC;AAEF,QAAA,MAAM,mBAAmB,iHAUtB,wBAAwB,KAAG,GAAG,CAAC,OA2GjC,CAAC;AAEF,eAAe,mBAAmB,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } 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.
|
|
@@ -11,14 +11,18 @@ 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, useMemo
|
|
14
|
+
import { useEffect, useMemo } from 'react';
|
|
15
15
|
import { AnimatePresence, motion } from 'framer-motion';
|
|
16
16
|
import { Duration, Timestamp } from '@parca/client';
|
|
17
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
|
+
import { useLabelNames } from '../MatchersInput';
|
|
20
21
|
import MetricsGraph from '../MetricsGraph';
|
|
21
22
|
import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
|
|
23
|
+
import useGrpcQuery from '../useGrpcQuery';
|
|
24
|
+
import { Toolbar } from './Toolbar';
|
|
25
|
+
import { DEFAULT_EMPTY_SUM_BY, useSumBy } from './useSumBy';
|
|
22
26
|
const ErrorContent = ({ errorMessage }) => {
|
|
23
27
|
return (_jsx("div", { className: "relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700", role: "alert", children: _jsx("span", { className: "block sm:inline", children: errorMessage }) }));
|
|
24
28
|
};
|
|
@@ -33,14 +37,7 @@ const getStepCountFromScreenWidth = (pixelsPerPoint) => {
|
|
|
33
37
|
width = width - (20 + 24 + 68) * 2;
|
|
34
38
|
return Math.round(width / pixelsPerPoint);
|
|
35
39
|
};
|
|
36
|
-
const
|
|
37
|
-
export const useQueryRange = (client, queryExpression, start, end, sumBy = EMPTY_SUM_BY, skip = false) => {
|
|
38
|
-
const [isLoading, setLoading] = useState(!skip);
|
|
39
|
-
const [state, setState] = useState({
|
|
40
|
-
response: null,
|
|
41
|
-
isLoading,
|
|
42
|
-
error: null,
|
|
43
|
-
});
|
|
40
|
+
export const useQueryRange = (client, queryExpression, start, end, sumBy = DEFAULT_EMPTY_SUM_BY, skip = false) => {
|
|
44
41
|
const metadata = useGrpcMetadata();
|
|
45
42
|
const { navigateTo } = useParcaContext();
|
|
46
43
|
const [stepCountStr, setStepCount] = useURLState({ param: 'step_count', navigateTo });
|
|
@@ -58,14 +55,11 @@ export const useQueryRange = (client, queryExpression, start, end, sumBy = EMPTY
|
|
|
58
55
|
setStepCount(defaultStepCount.toString());
|
|
59
56
|
}
|
|
60
57
|
}, [stepCountStr, defaultStepCount, setStepCount]);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
setLoading(true);
|
|
58
|
+
const { data, isLoading, error } = useGrpcQuery({
|
|
59
|
+
key: ['query-range', queryExpression, start, end, sumBy.join(','), stepCount, metadata],
|
|
60
|
+
queryFn: async () => {
|
|
67
61
|
const stepDuration = getStepDuration(start, end, stepCount);
|
|
68
|
-
const
|
|
62
|
+
const { response } = await client.queryRange({
|
|
69
63
|
query: queryExpression,
|
|
70
64
|
start: Timestamp.fromDate(new Date(start)),
|
|
71
65
|
end: Timestamp.fromDate(new Date(end)),
|
|
@@ -73,22 +67,20 @@ export const useQueryRange = (client, queryExpression, start, end, sumBy = EMPTY
|
|
|
73
67
|
limit: 0,
|
|
74
68
|
sumBy,
|
|
75
69
|
}, { meta: metadata });
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
});
|
|
86
|
-
})();
|
|
87
|
-
}, [client, queryExpression, start, end, metadata, sumBy, skip, stepCount]);
|
|
88
|
-
return { ...state, isLoading };
|
|
70
|
+
return response;
|
|
71
|
+
},
|
|
72
|
+
options: {
|
|
73
|
+
retry: false,
|
|
74
|
+
enabled: !skip && sumBy !== DEFAULT_EMPTY_SUM_BY,
|
|
75
|
+
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
return { isLoading, error: error, response: data ?? null };
|
|
89
79
|
};
|
|
90
80
|
const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to, setTimeRange, addLabelMatcher, onPointClick, comparing = false, }) => {
|
|
91
|
-
const {
|
|
81
|
+
const { loading: labelNamesLoading, result: labelNamesResult } = useLabelNames(queryClient, profile?.ProfileSource()?.ProfileType()?.toString() ?? '', from, to);
|
|
82
|
+
const [sumBy, setSumBy] = useSumBy(profile?.ProfileSource()?.ProfileType(), labelNamesResult.response?.labelNames);
|
|
83
|
+
const { isLoading: metricsGraphLoading, response, error, } = useQueryRange(queryClient, queryExpression, from, to, sumBy, labelNamesLoading);
|
|
92
84
|
const { onError, perf, authenticationErrorMessage, isDarkMode } = useParcaContext();
|
|
93
85
|
const { width, height, margin, heightStyle } = useMetricsGraphDimensions(comparing);
|
|
94
86
|
useEffect(() => {
|
|
@@ -104,8 +96,9 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
|
|
|
104
96
|
}, [perf, response]);
|
|
105
97
|
const series = response?.series;
|
|
106
98
|
const dataAvailable = series !== null && series !== undefined && series?.length > 0;
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
const loading = metricsGraphLoading || labelNamesLoading;
|
|
100
|
+
if (!labelNamesLoading && labelNamesResult?.error?.message != null) {
|
|
101
|
+
return (_jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(labelNamesResult.error.message) }));
|
|
109
102
|
}
|
|
110
103
|
if (!metricsGraphLoading && error !== null) {
|
|
111
104
|
if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
|
|
@@ -113,19 +106,17 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
|
|
|
113
106
|
}
|
|
114
107
|
return _jsx(ErrorContent, { errorMessage: capitalizeOnlyFirstLetter(error.message) });
|
|
115
108
|
}
|
|
109
|
+
let sampleUnit = '';
|
|
116
110
|
if (dataAvailable) {
|
|
117
|
-
const handleSampleClick = (timestamp, _value, labels, duration) => {
|
|
118
|
-
onPointClick(timestamp, labels, queryExpression, duration);
|
|
119
|
-
};
|
|
120
|
-
let sampleUnit = '';
|
|
121
111
|
if (series.every((val, i, arr) => val?.sampleType?.unit === arr[0]?.sampleType?.unit)) {
|
|
122
112
|
sampleUnit = series[0]?.sampleType?.unit ?? '';
|
|
123
113
|
}
|
|
124
114
|
if (sampleUnit === '') {
|
|
125
115
|
sampleUnit = Query.parse(queryExpression).profileType().sampleUnit;
|
|
126
116
|
}
|
|
127
|
-
return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full relative", 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") }));
|
|
128
117
|
}
|
|
129
|
-
return _jsx(
|
|
118
|
+
return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "h-full w-full relative", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: [_jsx(Toolbar, { sumBy: sumBy, setSumBy: setSumBy, labels: labelNamesResult.response?.labelNames ?? [] }), loading ? (_jsx(MetricsGraphSkeleton, { heightStyle: heightStyle, isDarkMode: isDarkMode })) : dataAvailable ? (_jsx(MetricsGraph, { data: series, from: from, to: to, profile: profile, setTimeRange: setTimeRange, onSampleClick: (timestamp, _value, labels, duration) => {
|
|
119
|
+
onPointClick(timestamp, labels, queryExpression, duration);
|
|
120
|
+
}, addLabelMatcher: addLabelMatcher, sampleUnit: sampleUnit, height: height, width: width, margin: margin })) : (_jsx(ProfileMetricsEmptyState, { message: "No data found. Try a different query." }))] }, "metrics-graph-loaded") }));
|
|
130
121
|
};
|
|
131
122
|
export default ProfileMetricsGraph;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
import { ProfileType } from '@parca/parser';
|
|
2
|
+
export declare const DEFAULT_EMPTY_SUM_BY: string[];
|
|
2
3
|
export declare const useSumBy: (profileType: ProfileType | undefined, labels: string[] | undefined) => [string[], (labels: string[]) => void];
|
|
3
4
|
//# sourceMappingURL=useSumBy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSumBy.d.ts","sourceRoot":"","sources":["../../src/ProfileMetricsGraph/useSumBy.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useSumBy.d.ts","sourceRoot":"","sources":["../../src/ProfileMetricsGraph/useSumBy.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAC,WAAW,EAAC,MAAM,eAAe,CAAC;AAE1C,eAAO,MAAM,oBAAoB,EAAE,MAAM,EAAO,CAAC;AA6BjD,eAAO,MAAM,QAAQ,gBACN,WAAW,GAAG,SAAS,UAC5B,MAAM,EAAE,GAAG,SAAS,KAC3B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CA+EvC,CAAC"}
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
|
-
import { useEffect, useState } from 'react';
|
|
14
|
-
|
|
13
|
+
import { useCallback, useEffect, useMemo, useState } from 'react';
|
|
14
|
+
import { useParcaContext, useURLState } from '@parca/components';
|
|
15
|
+
export const DEFAULT_EMPTY_SUM_BY = [];
|
|
15
16
|
const getDefaultSumBy = (profile, labels) => {
|
|
16
17
|
if (profile === undefined || labels === undefined) {
|
|
17
18
|
return undefined;
|
|
@@ -31,17 +32,59 @@ const getDefaultSumBy = (profile, labels) => {
|
|
|
31
32
|
return undefined;
|
|
32
33
|
};
|
|
33
34
|
export const useSumBy = (profileType, labels) => {
|
|
34
|
-
const
|
|
35
|
+
const { navigateTo } = useParcaContext();
|
|
36
|
+
const [userSelectedSumByParam, setUserSelectedSumByParam] = useURLState({
|
|
37
|
+
param: 'sum_by',
|
|
38
|
+
navigateTo,
|
|
39
|
+
});
|
|
40
|
+
console.log('labels', labels);
|
|
41
|
+
console.log('userSelectedSumByParam', userSelectedSumByParam);
|
|
42
|
+
const userSelectedSumBy = useMemo(() => {
|
|
43
|
+
if (userSelectedSumByParam?.length === 0) {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (userSelectedSumByParam === '__none__') {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
if (typeof userSelectedSumByParam === 'string') {
|
|
50
|
+
return [userSelectedSumByParam];
|
|
51
|
+
}
|
|
52
|
+
return userSelectedSumByParam;
|
|
53
|
+
}, [userSelectedSumByParam]);
|
|
54
|
+
console.log('userSelectedSumBy', userSelectedSumBy);
|
|
55
|
+
const setUserSelectedSumBy = useCallback((sumBy) => {
|
|
56
|
+
if (sumBy.length === 0) {
|
|
57
|
+
setUserSelectedSumByParam('__none__');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (sumBy.length === 1) {
|
|
61
|
+
// Handle this separately to take care of the empty string scenario
|
|
62
|
+
setUserSelectedSumByParam(sumBy[0]);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
setUserSelectedSumByParam(sumBy);
|
|
66
|
+
}, [setUserSelectedSumByParam]);
|
|
35
67
|
const [defaultSumBy, setDefaultSumBy] = useState(getDefaultSumBy(profileType, labels));
|
|
36
68
|
useEffect(() => {
|
|
37
69
|
setDefaultSumBy(getDefaultSumBy(profileType, labels));
|
|
38
70
|
}, [profileType, labels]);
|
|
39
71
|
useEffect(() => {
|
|
40
|
-
if (profileType === undefined) {
|
|
72
|
+
if (profileType === undefined || labels === undefined) {
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (userSelectedSumBy !== undefined && userSelectedSumBy.length === 0) {
|
|
76
|
+
// User has explicitly selected no sumBy, so don't reset it
|
|
41
77
|
return;
|
|
42
78
|
}
|
|
79
|
+
if (userSelectedSumBy !== undefined && userSelectedSumBy.length > 0) {
|
|
80
|
+
// If any of the user selected sumBy is present in the labels, then don't reset it
|
|
81
|
+
if (userSelectedSumBy.some(sumBy => labels?.includes(sumBy))) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
43
85
|
// Reset user selected sumBy if profile type changes
|
|
44
|
-
setUserSelectedSumBy(
|
|
45
|
-
|
|
86
|
+
setUserSelectedSumBy(['']);
|
|
87
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
88
|
+
}, [profileType, labels]);
|
|
46
89
|
return [userSelectedSumBy ?? defaultSumBy ?? DEFAULT_EMPTY_SUM_BY, setUserSelectedSumBy];
|
|
47
90
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.410",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@headlessui/react": "^1.7.19",
|
|
@@ -73,5 +73,5 @@
|
|
|
73
73
|
"access": "public",
|
|
74
74
|
"registry": "https://registry.npmjs.org/"
|
|
75
75
|
},
|
|
76
|
-
"gitHead": "
|
|
76
|
+
"gitHead": "7fc4b0d3e5a29c4a79f8762185ec8b874dfdef2c"
|
|
77
77
|
}
|
|
@@ -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, useMemo
|
|
14
|
+
import {useEffect, useMemo} from 'react';
|
|
15
15
|
|
|
16
16
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
17
|
import {AnimatePresence, motion} from 'framer-motion';
|
|
@@ -28,8 +28,12 @@ import {Query} from '@parca/parser';
|
|
|
28
28
|
import {capitalizeOnlyFirstLetter, getStepDuration} from '@parca/utilities';
|
|
29
29
|
|
|
30
30
|
import {MergedProfileSelection, ProfileSelection} from '..';
|
|
31
|
+
import {useLabelNames} from '../MatchersInput';
|
|
31
32
|
import MetricsGraph from '../MetricsGraph';
|
|
32
33
|
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
34
|
+
import useGrpcQuery from '../useGrpcQuery';
|
|
35
|
+
import {Toolbar} from './Toolbar';
|
|
36
|
+
import {DEFAULT_EMPTY_SUM_BY, useSumBy} from './useSumBy';
|
|
33
37
|
|
|
34
38
|
interface ProfileMetricsEmptyStateProps {
|
|
35
39
|
message: string;
|
|
@@ -89,26 +93,17 @@ const getStepCountFromScreenWidth = (pixelsPerPoint: number): number => {
|
|
|
89
93
|
return Math.round(width / pixelsPerPoint);
|
|
90
94
|
};
|
|
91
95
|
|
|
92
|
-
const EMPTY_SUM_BY: string[] = [];
|
|
93
|
-
|
|
94
96
|
export const useQueryRange = (
|
|
95
97
|
client: QueryServiceClient,
|
|
96
98
|
queryExpression: string,
|
|
97
99
|
start: number,
|
|
98
100
|
end: number,
|
|
99
|
-
sumBy: string[] =
|
|
101
|
+
sumBy: string[] = DEFAULT_EMPTY_SUM_BY,
|
|
100
102
|
skip = false
|
|
101
103
|
): IQueryRangeState => {
|
|
102
|
-
const [isLoading, setLoading] = useState<boolean>(!skip);
|
|
103
|
-
const [state, setState] = useState<IQueryRangeState>({
|
|
104
|
-
response: null,
|
|
105
|
-
isLoading,
|
|
106
|
-
error: null,
|
|
107
|
-
});
|
|
108
104
|
const metadata = useGrpcMetadata();
|
|
109
105
|
const {navigateTo} = useParcaContext();
|
|
110
106
|
const [stepCountStr, setStepCount] = useURLState({param: 'step_count', navigateTo});
|
|
111
|
-
|
|
112
107
|
const defaultStepCount = useMemo(() => {
|
|
113
108
|
return getStepCountFromScreenWidth(10);
|
|
114
109
|
}, []);
|
|
@@ -127,16 +122,11 @@ export const useQueryRange = (
|
|
|
127
122
|
}
|
|
128
123
|
}, [stepCountStr, defaultStepCount, setStepCount]);
|
|
129
124
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
setLoading(true);
|
|
137
|
-
|
|
125
|
+
const {data, isLoading, error} = useGrpcQuery<QueryRangeResponse | undefined>({
|
|
126
|
+
key: ['query-range', queryExpression, start, end, sumBy.join(','), stepCount, metadata],
|
|
127
|
+
queryFn: async () => {
|
|
138
128
|
const stepDuration = getStepDuration(start, end, stepCount);
|
|
139
|
-
const
|
|
129
|
+
const {response} = await client.queryRange(
|
|
140
130
|
{
|
|
141
131
|
query: queryExpression,
|
|
142
132
|
start: Timestamp.fromDate(new Date(start)),
|
|
@@ -147,21 +137,16 @@ export const useQueryRange = (
|
|
|
147
137
|
},
|
|
148
138
|
{meta: metadata}
|
|
149
139
|
);
|
|
140
|
+
return response;
|
|
141
|
+
},
|
|
142
|
+
options: {
|
|
143
|
+
retry: false,
|
|
144
|
+
enabled: !skip && sumBy !== DEFAULT_EMPTY_SUM_BY,
|
|
145
|
+
staleTime: 1000 * 60 * 5, // 5 minutes
|
|
146
|
+
},
|
|
147
|
+
});
|
|
150
148
|
|
|
151
|
-
|
|
152
|
-
.then(response => {
|
|
153
|
-
setState({response, isLoading: false, error: null});
|
|
154
|
-
setLoading(false);
|
|
155
|
-
return null;
|
|
156
|
-
})
|
|
157
|
-
.catch(error => {
|
|
158
|
-
setState({response: null, isLoading: false, error});
|
|
159
|
-
setLoading(false);
|
|
160
|
-
});
|
|
161
|
-
})();
|
|
162
|
-
}, [client, queryExpression, start, end, metadata, sumBy, skip, stepCount]);
|
|
163
|
-
|
|
164
|
-
return {...state, isLoading};
|
|
149
|
+
return {isLoading, error: error as RpcError | null, response: data ?? null};
|
|
165
150
|
};
|
|
166
151
|
|
|
167
152
|
const ProfileMetricsGraph = ({
|
|
@@ -175,11 +160,22 @@ const ProfileMetricsGraph = ({
|
|
|
175
160
|
onPointClick,
|
|
176
161
|
comparing = false,
|
|
177
162
|
}: ProfileMetricsGraphProps): JSX.Element => {
|
|
163
|
+
const {loading: labelNamesLoading, result: labelNamesResult} = useLabelNames(
|
|
164
|
+
queryClient,
|
|
165
|
+
profile?.ProfileSource()?.ProfileType()?.toString() ?? '',
|
|
166
|
+
from,
|
|
167
|
+
to
|
|
168
|
+
);
|
|
169
|
+
const [sumBy, setSumBy] = useSumBy(
|
|
170
|
+
profile?.ProfileSource()?.ProfileType(),
|
|
171
|
+
labelNamesResult.response?.labelNames
|
|
172
|
+
);
|
|
173
|
+
|
|
178
174
|
const {
|
|
179
175
|
isLoading: metricsGraphLoading,
|
|
180
176
|
response,
|
|
181
177
|
error,
|
|
182
|
-
} = useQueryRange(queryClient, queryExpression, from, to);
|
|
178
|
+
} = useQueryRange(queryClient, queryExpression, from, to, sumBy, labelNamesLoading);
|
|
183
179
|
const {onError, perf, authenticationErrorMessage, isDarkMode} = useParcaContext();
|
|
184
180
|
const {width, height, margin, heightStyle} = useMetricsGraphDimensions(comparing);
|
|
185
181
|
|
|
@@ -200,8 +196,12 @@ const ProfileMetricsGraph = ({
|
|
|
200
196
|
const series = response?.series;
|
|
201
197
|
const dataAvailable = series !== null && series !== undefined && series?.length > 0;
|
|
202
198
|
|
|
203
|
-
|
|
204
|
-
|
|
199
|
+
const loading = metricsGraphLoading || labelNamesLoading;
|
|
200
|
+
|
|
201
|
+
if (!labelNamesLoading && labelNamesResult?.error?.message != null) {
|
|
202
|
+
return (
|
|
203
|
+
<ErrorContent errorMessage={capitalizeOnlyFirstLetter(labelNamesResult.error.message)} />
|
|
204
|
+
);
|
|
205
205
|
}
|
|
206
206
|
|
|
207
207
|
if (!metricsGraphLoading && error !== null) {
|
|
@@ -212,52 +212,60 @@ const ProfileMetricsGraph = ({
|
|
|
212
212
|
return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(error.message)} />;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
|
|
216
|
-
const handleSampleClick = (
|
|
217
|
-
timestamp: number,
|
|
218
|
-
_value: number,
|
|
219
|
-
labels: Label[],
|
|
220
|
-
duration: number
|
|
221
|
-
): void => {
|
|
222
|
-
onPointClick(timestamp, labels, queryExpression, duration);
|
|
223
|
-
};
|
|
215
|
+
let sampleUnit = '';
|
|
224
216
|
|
|
225
|
-
|
|
217
|
+
if (dataAvailable) {
|
|
226
218
|
if (series.every((val, i, arr) => val?.sampleType?.unit === arr[0]?.sampleType?.unit)) {
|
|
227
219
|
sampleUnit = series[0]?.sampleType?.unit ?? '';
|
|
228
220
|
}
|
|
229
221
|
if (sampleUnit === '') {
|
|
230
222
|
sampleUnit = Query.parse(queryExpression).profileType().sampleUnit;
|
|
231
223
|
}
|
|
224
|
+
}
|
|
232
225
|
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
226
|
+
return (
|
|
227
|
+
<AnimatePresence>
|
|
228
|
+
<motion.div
|
|
229
|
+
className="h-full w-full relative"
|
|
230
|
+
key="metrics-graph-loaded"
|
|
231
|
+
initial={{display: 'none', opacity: 0}}
|
|
232
|
+
animate={{display: 'block', opacity: 1}}
|
|
233
|
+
transition={{duration: 0.5}}
|
|
234
|
+
>
|
|
235
|
+
<Toolbar
|
|
236
|
+
sumBy={sumBy}
|
|
237
|
+
setSumBy={setSumBy}
|
|
238
|
+
labels={labelNamesResult.response?.labelNames ?? []}
|
|
239
|
+
/>
|
|
240
|
+
{loading ? (
|
|
241
|
+
<MetricsGraphSkeleton heightStyle={heightStyle} isDarkMode={isDarkMode} />
|
|
242
|
+
) : dataAvailable ? (
|
|
242
243
|
<MetricsGraph
|
|
243
244
|
data={series}
|
|
244
245
|
from={from}
|
|
245
246
|
to={to}
|
|
246
247
|
profile={profile as MergedProfileSelection}
|
|
247
248
|
setTimeRange={setTimeRange}
|
|
248
|
-
onSampleClick={
|
|
249
|
+
onSampleClick={(
|
|
250
|
+
timestamp: number,
|
|
251
|
+
_value: number,
|
|
252
|
+
labels: Label[],
|
|
253
|
+
duration: number
|
|
254
|
+
): void => {
|
|
255
|
+
onPointClick(timestamp, labels, queryExpression, duration);
|
|
256
|
+
}}
|
|
249
257
|
addLabelMatcher={addLabelMatcher}
|
|
250
258
|
sampleUnit={sampleUnit}
|
|
251
259
|
height={height}
|
|
252
260
|
width={width}
|
|
253
261
|
margin={margin}
|
|
254
262
|
/>
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
263
|
+
) : (
|
|
264
|
+
<ProfileMetricsEmptyState message="No data found. Try a different query." />
|
|
265
|
+
)}
|
|
266
|
+
</motion.div>
|
|
267
|
+
</AnimatePresence>
|
|
268
|
+
);
|
|
261
269
|
};
|
|
262
270
|
|
|
263
271
|
export default ProfileMetricsGraph;
|
|
@@ -11,11 +11,12 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useEffect, useState} from 'react';
|
|
14
|
+
import {useCallback, useEffect, useMemo, useState} from 'react';
|
|
15
15
|
|
|
16
|
+
import {useParcaContext, useURLState} from '@parca/components';
|
|
16
17
|
import {ProfileType} from '@parca/parser';
|
|
17
18
|
|
|
18
|
-
const DEFAULT_EMPTY_SUM_BY: string[] = [];
|
|
19
|
+
export const DEFAULT_EMPTY_SUM_BY: string[] = [];
|
|
19
20
|
|
|
20
21
|
const getDefaultSumBy = (
|
|
21
22
|
profile: ProfileType | undefined,
|
|
@@ -48,7 +49,52 @@ export const useSumBy = (
|
|
|
48
49
|
profileType: ProfileType | undefined,
|
|
49
50
|
labels: string[] | undefined
|
|
50
51
|
): [string[], (labels: string[]) => void] => {
|
|
51
|
-
const
|
|
52
|
+
const {navigateTo} = useParcaContext();
|
|
53
|
+
const [userSelectedSumByParam, setUserSelectedSumByParam] = useURLState({
|
|
54
|
+
param: 'sum_by',
|
|
55
|
+
navigateTo,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
console.log('labels', labels);
|
|
59
|
+
|
|
60
|
+
console.log('userSelectedSumByParam', userSelectedSumByParam);
|
|
61
|
+
|
|
62
|
+
const userSelectedSumBy = useMemo<string[] | undefined>(() => {
|
|
63
|
+
if (userSelectedSumByParam?.length === 0) {
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (userSelectedSumByParam === '__none__') {
|
|
68
|
+
return [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (typeof userSelectedSumByParam === 'string') {
|
|
72
|
+
return [userSelectedSumByParam];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return userSelectedSumByParam;
|
|
76
|
+
}, [userSelectedSumByParam]);
|
|
77
|
+
|
|
78
|
+
console.log('userSelectedSumBy', userSelectedSumBy);
|
|
79
|
+
|
|
80
|
+
const setUserSelectedSumBy = useCallback(
|
|
81
|
+
(sumBy: string[]) => {
|
|
82
|
+
if (sumBy.length === 0) {
|
|
83
|
+
setUserSelectedSumByParam('__none__');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (sumBy.length === 1) {
|
|
88
|
+
// Handle this separately to take care of the empty string scenario
|
|
89
|
+
setUserSelectedSumByParam(sumBy[0]);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
setUserSelectedSumByParam(sumBy);
|
|
94
|
+
},
|
|
95
|
+
[setUserSelectedSumByParam]
|
|
96
|
+
);
|
|
97
|
+
|
|
52
98
|
const [defaultSumBy, setDefaultSumBy] = useState<string[] | undefined>(
|
|
53
99
|
getDefaultSumBy(profileType, labels)
|
|
54
100
|
);
|
|
@@ -58,13 +104,27 @@ export const useSumBy = (
|
|
|
58
104
|
}, [profileType, labels]);
|
|
59
105
|
|
|
60
106
|
useEffect(() => {
|
|
61
|
-
if (profileType === undefined) {
|
|
107
|
+
if (profileType === undefined || labels === undefined) {
|
|
62
108
|
return;
|
|
63
109
|
}
|
|
64
110
|
|
|
111
|
+
if (userSelectedSumBy !== undefined && userSelectedSumBy.length === 0) {
|
|
112
|
+
// User has explicitly selected no sumBy, so don't reset it
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (userSelectedSumBy !== undefined && userSelectedSumBy.length > 0) {
|
|
117
|
+
// If any of the user selected sumBy is present in the labels, then don't reset it
|
|
118
|
+
if (userSelectedSumBy.some(sumBy => labels?.includes(sumBy))) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
65
123
|
// Reset user selected sumBy if profile type changes
|
|
66
|
-
setUserSelectedSumBy(
|
|
67
|
-
|
|
124
|
+
setUserSelectedSumBy(['']);
|
|
125
|
+
|
|
126
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
127
|
+
}, [profileType, labels]);
|
|
68
128
|
|
|
69
129
|
return [userSelectedSumBy ?? defaultSumBy ?? DEFAULT_EMPTY_SUM_BY, setUserSelectedSumBy];
|
|
70
130
|
};
|