@parca/profile 0.16.199 → 0.16.201
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 +5 -2
- package/dist/MetricsGraph/index.js +3 -8
- package/dist/MetricsGraph/useMetricsGraphDimensions.d.ts +9 -0
- package/dist/MetricsGraph/useMetricsGraphDimensions.js +29 -0
- package/dist/ProfileMetricsGraph/index.d.ts +4 -0
- package/dist/ProfileMetricsGraph/index.js +24 -14
- package/dist/ProfileSelector/index.js +44 -35
- package/dist/ProfileSelector/useAutoQuerySelector.d.ts +9 -0
- package/dist/ProfileSelector/useAutoQuerySelector.js +66 -0
- package/dist/ProfileTypeSelector/index.d.ts +1 -0
- package/dist/ProfileTypeSelector/index.js +4 -3
- package/dist/ProfileView/index.js +1 -1
- package/dist/ProfileViewWithData.js +1 -1
- package/dist/styles.css +1 -1
- package/package.json +7 -7
- package/src/MetricsGraph/index.tsx +24 -20
- package/src/MetricsGraph/useMetricsGraphDimensions.ts +42 -0
- package/src/ProfileMetricsGraph/index.tsx +38 -20
- package/src/ProfileSelector/index.tsx +66 -54
- package/src/ProfileSelector/useAutoQuerySelector.ts +87 -0
- package/src/ProfileTypeSelector/index.tsx +9 -10
- package/src/ProfileView/index.tsx +1 -1
- package/src/ProfileViewWithData.tsx +2 -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.201](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.200...@parca/profile@0.16.201) (2023-07-07)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## 0.16.200 (2023-07-06)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.199](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.198...@parca/profile@0.16.199) (2023-07-05)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -11,6 +11,9 @@ interface Props {
|
|
|
11
11
|
setTimeRange: (range: DateTimeRange) => void;
|
|
12
12
|
sampleUnit: string;
|
|
13
13
|
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
margin?: number;
|
|
16
|
+
marginRight?: number;
|
|
14
17
|
}
|
|
15
18
|
export interface HighlightedSeries {
|
|
16
19
|
seriesIndex: number;
|
|
@@ -22,7 +25,7 @@ export interface HighlightedSeries {
|
|
|
22
25
|
x: number;
|
|
23
26
|
y: number;
|
|
24
27
|
}
|
|
25
|
-
declare const MetricsGraph: ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, sampleUnit, }: Props) => JSX.Element;
|
|
28
|
+
declare const MetricsGraph: ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, sampleUnit, width, height, margin, marginRight, }: Props) => JSX.Element;
|
|
26
29
|
export default MetricsGraph;
|
|
27
30
|
export declare const parseValue: (value: string) => number | null;
|
|
28
|
-
export declare const RawMetricsGraph: ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, width, sampleUnit, }: Props) => JSX.Element;
|
|
31
|
+
export declare const RawMetricsGraph: ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, width, height, margin, marginRight, sampleUnit, }: Props) => JSX.Element;
|
|
@@ -16,14 +16,12 @@ import * as d3 from 'd3';
|
|
|
16
16
|
import { pointer } from 'd3-selection';
|
|
17
17
|
import throttle from 'lodash.throttle';
|
|
18
18
|
import { DateTimeRange, useKeyDown } from '@parca/components';
|
|
19
|
-
import { useContainerDimensions } from '@parca/hooks';
|
|
20
19
|
import { formatDate, formatForTimespan, sanitizeHighlightedValues, valueFormatter, } from '@parca/utilities';
|
|
21
20
|
import MetricsCircle from '../MetricsCircle';
|
|
22
21
|
import MetricsSeries from '../MetricsSeries';
|
|
23
22
|
import MetricsTooltip from './MetricsTooltip';
|
|
24
|
-
const MetricsGraph = ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, sampleUnit, }) => {
|
|
25
|
-
|
|
26
|
-
return (_jsx("div", { ref: ref, children: _jsx(RawMetricsGraph, { data: data, from: from, to: to, profile: profile, onSampleClick: onSampleClick, onLabelClick: onLabelClick, setTimeRange: setTimeRange, sampleUnit: sampleUnit, width: dimensions?.width }) }));
|
|
23
|
+
const MetricsGraph = ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, sampleUnit, width = 0, height = 0, margin = 0, marginRight = 0, }) => {
|
|
24
|
+
return (_jsx(RawMetricsGraph, { data: data, from: from, to: to, profile: profile, onSampleClick: onSampleClick, onLabelClick: onLabelClick, setTimeRange: setTimeRange, sampleUnit: sampleUnit, width: width, height: height, margin: margin, marginRight: marginRight }));
|
|
27
25
|
};
|
|
28
26
|
export default MetricsGraph;
|
|
29
27
|
export const parseValue = (value) => {
|
|
@@ -34,7 +32,7 @@ export const parseValue = (value) => {
|
|
|
34
32
|
};
|
|
35
33
|
const lineStroke = '1px';
|
|
36
34
|
const lineStrokeHover = '2px';
|
|
37
|
-
export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, width, sampleUnit, }) => {
|
|
35
|
+
export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabelClick, setTimeRange, width, height = 50, margin = 0, marginRight = 0, sampleUnit, }) => {
|
|
38
36
|
const graph = useRef(null);
|
|
39
37
|
const [dragging, setDragging] = useState(false);
|
|
40
38
|
const [hovering, setHovering] = useState(false);
|
|
@@ -47,9 +45,6 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, onLabe
|
|
|
47
45
|
if (width === undefined || width == null) {
|
|
48
46
|
width = 0;
|
|
49
47
|
}
|
|
50
|
-
const height = Math.min(width / 2.5, 400);
|
|
51
|
-
const margin = 50;
|
|
52
|
-
const marginRight = 20;
|
|
53
48
|
const series = data.reduce(function (agg, s) {
|
|
54
49
|
if (s.labelset !== undefined) {
|
|
55
50
|
const metric = s.labelset.labels.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
import { useWindowSize } from 'react-use';
|
|
14
|
+
const maxHeight = 402;
|
|
15
|
+
const margin = 50;
|
|
16
|
+
const heightStyle = `min(${maxHeight + margin}px, 47vw - 24px)`;
|
|
17
|
+
export const useMetricsGraphDimensions = () => {
|
|
18
|
+
let { width } = useWindowSize();
|
|
19
|
+
width = width - 58;
|
|
20
|
+
const height = Math.min(width / 2.5, maxHeight);
|
|
21
|
+
const marginRight = 20;
|
|
22
|
+
return {
|
|
23
|
+
width,
|
|
24
|
+
height,
|
|
25
|
+
heightStyle,
|
|
26
|
+
margin,
|
|
27
|
+
marginRight,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
@@ -2,6 +2,10 @@ import { RpcError } from '@protobuf-ts/runtime-rpc';
|
|
|
2
2
|
import { Label, QueryRangeResponse, QueryServiceClient } from '@parca/client';
|
|
3
3
|
import { DateTimeRange } from '@parca/components';
|
|
4
4
|
import { ProfileSelection } from '..';
|
|
5
|
+
interface ProfileMetricsEmptyStateProps {
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const ProfileMetricsEmptyState: ({ message }: ProfileMetricsEmptyStateProps) => JSX.Element;
|
|
5
9
|
interface ProfileMetricsGraphProps {
|
|
6
10
|
queryClient: QueryServiceClient;
|
|
7
11
|
queryExpression: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, 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.
|
|
@@ -17,21 +17,22 @@ import { useGrpcMetadata, useParcaContext } from '@parca/components';
|
|
|
17
17
|
import { Query } from '@parca/parser';
|
|
18
18
|
import { getStepDuration } from '@parca/utilities';
|
|
19
19
|
import MetricsGraph from '../MetricsGraph';
|
|
20
|
+
import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
|
|
20
21
|
import useDelayedLoader from '../useDelayedLoader';
|
|
22
|
+
export const ProfileMetricsEmptyState = ({ message }) => {
|
|
23
|
+
return (_jsx("div", { className: "flex h-full w-full flex-col items-center justify-center", children: _jsx("p", { children: message }) }));
|
|
24
|
+
};
|
|
21
25
|
export const useQueryRange = (client, queryExpression, start, end) => {
|
|
22
26
|
const [state, setState] = useState({
|
|
23
27
|
response: null,
|
|
24
28
|
isLoading: true,
|
|
25
29
|
error: null,
|
|
26
30
|
});
|
|
31
|
+
const [isLoading, setLoading] = useState(true);
|
|
27
32
|
const metadata = useGrpcMetadata();
|
|
28
33
|
useEffect(() => {
|
|
29
34
|
void (async () => {
|
|
30
|
-
|
|
31
|
-
response: null,
|
|
32
|
-
isLoading: true,
|
|
33
|
-
error: null,
|
|
34
|
-
});
|
|
35
|
+
setLoading(true);
|
|
35
36
|
const stepDuration = getStepDuration(start, end);
|
|
36
37
|
const call = client.queryRange({
|
|
37
38
|
query: queryExpression,
|
|
@@ -41,16 +42,24 @@ export const useQueryRange = (client, queryExpression, start, end) => {
|
|
|
41
42
|
limit: 0,
|
|
42
43
|
}, { meta: metadata });
|
|
43
44
|
call.response
|
|
44
|
-
.then(response =>
|
|
45
|
-
|
|
45
|
+
.then(response => {
|
|
46
|
+
setState({ response, isLoading: false, error: null });
|
|
47
|
+
setLoading(false);
|
|
48
|
+
return null;
|
|
49
|
+
})
|
|
50
|
+
.catch(error => {
|
|
51
|
+
setState({ response: null, isLoading: false, error });
|
|
52
|
+
setLoading(false);
|
|
53
|
+
});
|
|
46
54
|
})();
|
|
47
55
|
}, [client, queryExpression, start, end, metadata]);
|
|
48
|
-
return state;
|
|
56
|
+
return { ...state, isLoading };
|
|
49
57
|
};
|
|
50
58
|
const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to, setTimeRange, addLabelMatcher, onPointClick, }) => {
|
|
51
59
|
const { isLoading, response, error } = useQueryRange(queryClient, queryExpression, from, to);
|
|
52
60
|
const isLoaderVisible = useDelayedLoader(isLoading);
|
|
53
61
|
const { loader, onError, perf } = useParcaContext();
|
|
62
|
+
const { width, height, margin, marginRight } = useMetricsGraphDimensions();
|
|
54
63
|
useEffect(() => {
|
|
55
64
|
if (error !== null) {
|
|
56
65
|
onError?.(error, 'metricsGraph');
|
|
@@ -62,19 +71,20 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
|
|
|
62
71
|
}
|
|
63
72
|
perf?.markInteraction('Metrics graph render', response.series[0].samples.length);
|
|
64
73
|
}, [perf, response]);
|
|
65
|
-
|
|
74
|
+
const series = response?.series;
|
|
75
|
+
const dataAvailable = series !== null && series !== undefined && series?.length > 0;
|
|
76
|
+
if (isLoaderVisible || (isLoading && !dataAvailable)) {
|
|
66
77
|
return _jsx(_Fragment, { children: loader });
|
|
67
78
|
}
|
|
68
79
|
if (error !== null) {
|
|
69
80
|
return (_jsxs("div", { className: "relative rounded border border-red-400 bg-red-100 px-4 py-3 text-red-700", role: "alert", children: [_jsx("strong", { className: "font-bold", children: "Error! " }), _jsx("span", { className: "block sm:inline", children: error.message })] }));
|
|
70
81
|
}
|
|
71
|
-
|
|
72
|
-
if (series !== null && series !== undefined && series?.length > 0) {
|
|
82
|
+
if (dataAvailable) {
|
|
73
83
|
const handleSampleClick = (timestamp, _value, labels) => {
|
|
74
84
|
onPointClick(timestamp, labels, queryExpression);
|
|
75
85
|
};
|
|
76
|
-
return (_jsx("div", { className: "rounded border-gray-300 dark:border-gray-500 dark:bg-gray-700", style: { borderWidth: 1 }, children: _jsx(MetricsGraph, { data: series, from: from, to: to, profile: profile, setTimeRange: setTimeRange, onSampleClick: handleSampleClick, onLabelClick: addLabelMatcher,
|
|
86
|
+
return (_jsx("div", { className: "h-full w-full rounded border-gray-300 dark:border-gray-500 dark:bg-gray-700", style: { borderWidth: 1 }, children: _jsx(MetricsGraph, { data: series, from: from, to: to, profile: profile, setTimeRange: setTimeRange, onSampleClick: handleSampleClick, onLabelClick: addLabelMatcher, sampleUnit: Query.parse(queryExpression).profileType().sampleUnit, height: height, width: width, margin: margin, marginRight: marginRight }) }));
|
|
77
87
|
}
|
|
78
|
-
return
|
|
88
|
+
return _jsx(ProfileMetricsEmptyState, { message: "No data found. Try a different query." });
|
|
79
89
|
};
|
|
80
90
|
export default ProfileMetricsGraph;
|
|
@@ -18,9 +18,11 @@ import { Query } from '@parca/parser';
|
|
|
18
18
|
import { getStepDuration, getStepDurationInMilliseconds } from '@parca/utilities';
|
|
19
19
|
import { MergedProfileSelection } from '..';
|
|
20
20
|
import MatchersInput from '../MatchersInput/index';
|
|
21
|
-
import
|
|
21
|
+
import { useMetricsGraphDimensions } from '../MetricsGraph/useMetricsGraphDimensions';
|
|
22
|
+
import ProfileMetricsGraph, { ProfileMetricsEmptyState } from '../ProfileMetricsGraph';
|
|
22
23
|
import ProfileTypeSelector from '../ProfileTypeSelector/index';
|
|
23
24
|
import CompareButton from './CompareButton';
|
|
25
|
+
import { useAutoQuerySelector } from './useAutoQuerySelector';
|
|
24
26
|
export const useProfileTypes = (client) => {
|
|
25
27
|
const [result, setResult] = useState(undefined);
|
|
26
28
|
const [error, setError] = useState(undefined);
|
|
@@ -40,6 +42,7 @@ export const useProfileTypes = (client) => {
|
|
|
40
42
|
};
|
|
41
43
|
const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQuery, closeProfile, enforcedProfileName, profileSelection, comparing, onCompareProfile, }) => {
|
|
42
44
|
const { loading: profileTypesLoading, data: profileTypesData, error, } = useProfileTypes(queryClient);
|
|
45
|
+
const { heightStyle } = useMetricsGraphDimensions();
|
|
43
46
|
const [timeRangeSelection, setTimeRangeSelection] = useState(DateTimeRange.fromRangeKey(querySelection.timeSelection));
|
|
44
47
|
const [queryExpressionString, setQueryExpressionString] = useState(querySelection.expression);
|
|
45
48
|
useEffect(() => {
|
|
@@ -105,6 +108,12 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
|
|
|
105
108
|
setQueryExpressionString(q);
|
|
106
109
|
}
|
|
107
110
|
};
|
|
111
|
+
useAutoQuerySelector({
|
|
112
|
+
selectedProfileName,
|
|
113
|
+
profileTypesData,
|
|
114
|
+
setProfileName,
|
|
115
|
+
setQueryExpression,
|
|
116
|
+
});
|
|
108
117
|
const handleCompareClick = () => onCompareProfile();
|
|
109
118
|
const searchDisabled = queryExpressionString === undefined ||
|
|
110
119
|
queryExpressionString === '' ||
|
|
@@ -113,40 +122,40 @@ const ProfileSelector = ({ queryClient, querySelection, selectProfile, selectQue
|
|
|
113
122
|
return (_jsxs(Card, { className: "overflow-visible", children: [_jsxs(Card.Header, { className: "flex !items-center space-x-2", children: [_jsxs("div", { className: "flex w-full flex-wrap items-center justify-start space-x-2 space-y-1", children: [_jsx("div", { className: "ml-2 mt-1", children: _jsx(ProfileTypeSelector, { profileTypesData: profileTypesData, loading: profileTypesLoading, selectedKey: selectedProfileName, onSelection: setProfileName, error: error }) }), _jsx("div", { className: "w-full flex-1", children: _jsx(MatchersInput, { queryClient: queryClient, setMatchersString: setMatchersString, runQuery: setQueryExpression, currentQuery: query }) }), _jsx(DateTimeRangePicker, { onRangeSelection: setTimeRangeSelection, range: timeRangeSelection }), _jsxs(ButtonGroup, { children: [!searchDisabled && (_jsx(_Fragment, { children: !comparing && (_jsx(CompareButton, { disabled: compareDisabled, onClick: handleCompareClick })) })), _jsx(Button, { disabled: searchDisabled, onClick: (e) => {
|
|
114
123
|
e.preventDefault();
|
|
115
124
|
setQueryExpression();
|
|
116
|
-
}, children: "Search" })] })] }), _jsx("div", { children: comparing && _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}) }) })] }), _jsx(Card.Body, { children: querySelection.expression !== undefined &&
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
}
|
|
126
|
-
setTimeRangeSelection(range);
|
|
127
|
-
selectQuery({
|
|
128
|
-
expression: queryExpressionString,
|
|
129
|
-
from,
|
|
130
|
-
to,
|
|
131
|
-
timeSelection: range.getRangeKey(),
|
|
132
|
-
...mergedProfileParams,
|
|
133
|
-
});
|
|
134
|
-
}, addLabelMatcher: addLabelMatcher, onPointClick: (timestamp, labels, queryExpression) => {
|
|
135
|
-
// TODO: Pass the query object via click rather than queryExpression
|
|
136
|
-
let query = Query.parse(queryExpression);
|
|
137
|
-
labels.forEach(l => {
|
|
138
|
-
const [newQuery, updated] = query.setMatcher(l.name, l.value);
|
|
139
|
-
if (updated) {
|
|
140
|
-
query = newQuery;
|
|
125
|
+
}, children: "Search" })] })] }), _jsx("div", { children: comparing && _jsx(IconButton, { onClick: () => closeProfile(), icon: _jsx(CloseIcon, {}) }) })] }), _jsx(Card.Body, { children: _jsx("div", { style: { height: heightStyle }, children: querySelection.expression !== undefined &&
|
|
126
|
+
querySelection.expression.length > 0 &&
|
|
127
|
+
querySelection.from !== undefined &&
|
|
128
|
+
querySelection.to !== undefined ? (_jsx(ProfileMetricsGraph, { queryClient: queryClient, queryExpression: querySelection.expression, from: querySelection.from, to: querySelection.to, profile: profileSelection, setTimeRange: (range) => {
|
|
129
|
+
const from = range.getFromMs();
|
|
130
|
+
const to = range.getToMs();
|
|
131
|
+
let mergedProfileParams = {};
|
|
132
|
+
if (query.profileType().delta) {
|
|
133
|
+
mergedProfileParams = { mergeFrom: from, mergeTo: to };
|
|
141
134
|
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
135
|
+
setTimeRangeSelection(range);
|
|
136
|
+
selectQuery({
|
|
137
|
+
expression: queryExpressionString,
|
|
138
|
+
from,
|
|
139
|
+
to,
|
|
140
|
+
timeSelection: range.getRangeKey(),
|
|
141
|
+
...mergedProfileParams,
|
|
142
|
+
});
|
|
143
|
+
}, addLabelMatcher: addLabelMatcher, onPointClick: (timestamp, labels, queryExpression) => {
|
|
144
|
+
// TODO: Pass the query object via click rather than queryExpression
|
|
145
|
+
let query = Query.parse(queryExpression);
|
|
146
|
+
labels.forEach(l => {
|
|
147
|
+
const [newQuery, updated] = query.setMatcher(l.name, l.value);
|
|
148
|
+
if (updated) {
|
|
149
|
+
query = newQuery;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
const stepDuration = getStepDuration(querySelection.from, querySelection.to);
|
|
153
|
+
const stepDurationInMilliseconds = getStepDurationInMilliseconds(stepDuration);
|
|
154
|
+
const mergeFrom = timestamp;
|
|
155
|
+
const mergeTo = query.profileType().delta
|
|
156
|
+
? mergeFrom + stepDurationInMilliseconds
|
|
157
|
+
: mergeFrom;
|
|
158
|
+
selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
|
|
159
|
+
} })) : (_jsx(_Fragment, { children: profileSelection == null ? (_jsx(ProfileMetricsEmptyState, { message: `Please select a profile type and click "Search" to begin.` })) : null })) }) })] }));
|
|
151
160
|
};
|
|
152
161
|
export default ProfileSelector;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ProfileTypesResponse } from '@parca/client';
|
|
2
|
+
interface Props {
|
|
3
|
+
selectedProfileName: string;
|
|
4
|
+
profileTypesData: ProfileTypesResponse | undefined;
|
|
5
|
+
setProfileName: (name: string) => void;
|
|
6
|
+
setQueryExpression: () => void;
|
|
7
|
+
}
|
|
8
|
+
export declare const useAutoQuerySelector: ({ selectedProfileName, profileTypesData, setProfileName, setQueryExpression, }: Props) => void;
|
|
9
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
import { useEffect } from 'react';
|
|
14
|
+
import { selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector } from '@parca/store';
|
|
15
|
+
import { constructProfileName } from '../ProfileTypeSelector';
|
|
16
|
+
export const useAutoQuerySelector = ({ selectedProfileName, profileTypesData, setProfileName, setQueryExpression, }) => {
|
|
17
|
+
const autoQuery = useAppSelector(selectAutoQuery);
|
|
18
|
+
const dispatch = useAppDispatch();
|
|
19
|
+
// Effect to load some initial data on load when is no selection
|
|
20
|
+
useEffect(() => {
|
|
21
|
+
void (async () => {
|
|
22
|
+
if (selectedProfileName.length > 0) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (profileTypesData?.types == null || profileTypesData.types.length < 1) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (autoQuery === 'true') {
|
|
29
|
+
// Autoquery already enabled.
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
dispatch(setAutoQuery('true'));
|
|
33
|
+
let profileType = profileTypesData.types.find(type => type.name === 'parca_agent_cpu');
|
|
34
|
+
if (profileType == null) {
|
|
35
|
+
profileType = profileTypesData.types[0];
|
|
36
|
+
}
|
|
37
|
+
setProfileName(constructProfileName(profileType));
|
|
38
|
+
})();
|
|
39
|
+
}, [
|
|
40
|
+
profileTypesData,
|
|
41
|
+
selectedProfileName,
|
|
42
|
+
autoQuery,
|
|
43
|
+
dispatch,
|
|
44
|
+
setQueryExpression,
|
|
45
|
+
setProfileName,
|
|
46
|
+
]);
|
|
47
|
+
useEffect(() => {
|
|
48
|
+
void (async () => {
|
|
49
|
+
if (autoQuery !== 'true' ||
|
|
50
|
+
profileTypesData?.types == null ||
|
|
51
|
+
profileTypesData.types.length < 1 ||
|
|
52
|
+
selectedProfileName.length === 0) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
setQueryExpression();
|
|
56
|
+
dispatch(setAutoQuery('false'));
|
|
57
|
+
})();
|
|
58
|
+
}, [
|
|
59
|
+
profileTypesData,
|
|
60
|
+
setQueryExpression,
|
|
61
|
+
autoQuery,
|
|
62
|
+
setProfileName,
|
|
63
|
+
dispatch,
|
|
64
|
+
selectedProfileName,
|
|
65
|
+
]);
|
|
66
|
+
};
|
|
@@ -10,6 +10,7 @@ interface WellKnownProfiles {
|
|
|
10
10
|
}
|
|
11
11
|
export declare const wellKnownProfiles: WellKnownProfiles;
|
|
12
12
|
export declare function profileSelectElement(name: string, flexibleKnownProfilesDetection: boolean): SelectElement;
|
|
13
|
+
export declare const constructProfileName: (type: ProfileType) => string;
|
|
13
14
|
export declare const normalizeProfileTypesData: (types: ProfileType[]) => string[];
|
|
14
15
|
interface Props {
|
|
15
16
|
profileTypesData?: ProfileTypesResponse;
|
|
@@ -102,10 +102,11 @@ export function profileSelectElement(name, flexibleKnownProfilesDetection) {
|
|
|
102
102
|
expanded: (_jsxs(_Fragment, { children: [_jsx("span", { children: title }), _jsx("br", {}), _jsx("span", { className: "text-xs", children: wellKnown.help })] })),
|
|
103
103
|
};
|
|
104
104
|
}
|
|
105
|
+
export const constructProfileName = (type) => {
|
|
106
|
+
return `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${type.delta ? ':delta' : ''}`;
|
|
107
|
+
};
|
|
105
108
|
export const normalizeProfileTypesData = (types) => {
|
|
106
|
-
return types
|
|
107
|
-
.map(type => `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${type.delta ? ':delta' : ''}`)
|
|
108
|
-
.sort((a, b) => {
|
|
109
|
+
return types.map(constructProfileName).sort((a, b) => {
|
|
109
110
|
return a.localeCompare(b);
|
|
110
111
|
});
|
|
111
112
|
};
|
|
@@ -39,7 +39,7 @@ function arrayEquals(a, b) {
|
|
|
39
39
|
export const ProfileView = ({ total, filtered, flamegraphData, topTableData, callgraphData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, }) => {
|
|
40
40
|
const { ref, dimensions } = useContainerDimensions();
|
|
41
41
|
const [curPath, setCurPath] = useState([]);
|
|
42
|
-
const [rawDashboardItems, setDashboardItems] = useURLState({
|
|
42
|
+
const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
|
|
43
43
|
param: 'dashboard_items',
|
|
44
44
|
navigateTo,
|
|
45
45
|
});
|
|
@@ -22,7 +22,7 @@ import { useQuery } from './useQuery';
|
|
|
22
22
|
import { downloadPprof } from './utils';
|
|
23
23
|
export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, }) => {
|
|
24
24
|
const metadata = useGrpcMetadata();
|
|
25
|
-
const [dashboardItems] = useURLState({ param: 'dashboard_items', navigateTo });
|
|
25
|
+
const [dashboardItems = ['icicle']] = useURLState({ param: 'dashboard_items', navigateTo });
|
|
26
26
|
const [enableTrimming] = useUserPreference(USER_PREFERENCES.ENABLE_GRAPH_TRIMMING.key);
|
|
27
27
|
const [arrowFlamegraphEnabled] = useUIFeatureFlag('flamegraph-arrow');
|
|
28
28
|
const [pprofDownloading, setPprofDownloading] = useState(false);
|
package/dist/styles.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
/*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.visible{visibility:visible}.invisible{visibility:hidden}.absolute{position:absolute}.relative{position:relative}.-inset-2{top:-.5rem;right:-.5rem;bottom:-.5rem;left:-.5rem}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-0{margin:0}.m-2{margin:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-20{margin-top:5rem;margin-bottom:5rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mt-1{margin-top:.25rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mb-2{margin-bottom:.5rem}.mr-6{margin-right:1.5rem}.mr-3{margin-right:.75rem}.mr-1{margin-right:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-1{height:.25rem}.h-\[80vh\]{height:80vh}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.min-h-52{min-height:13rem}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-40{width:10rem}.w-1\/2{width:50%}.w-8{width:2rem}.w-4{width:1rem}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.max-w-\[500px\]{max-width:500px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.\!items-center{align-items:center!important}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-scroll{overflow:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-transparent{fill:#0000}.fill-current{fill:currentColor}.stroke-white{stroke:#fff}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-20{padding-top:5rem;padding-bottom:5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pr-0{padding-right:0}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pr-2{padding-right:.5rem}.pl-2{padding-left:.5rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-90{opacity:.9}.opacity-50{opacity:.5}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px #0000004d;--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.outline-none{outline:2px solid #0000;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:w-1\/2{width:50%}}
|
|
1
|
+
/*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:initial}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,::backdrop,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:#3b82f680;--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.visible{visibility:visible}.invisible{visibility:hidden}.absolute{position:absolute}.relative{position:relative}.-inset-2{top:-.5rem;right:-.5rem;bottom:-.5rem;left:-.5rem}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-top:.5rem;margin-bottom:.5rem}.my-20{margin-top:5rem;margin-bottom:5rem}.my-6{margin-top:1.5rem;margin-bottom:1.5rem}.mt-1{margin-top:.25rem}.ml-2{margin-left:.5rem}.ml-auto{margin-left:auto}.mb-2{margin-bottom:.5rem}.mr-6{margin-right:1.5rem}.mr-3{margin-right:.75rem}.mr-1{margin-right:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-full{height:100%}.h-1{height:.25rem}.h-\[80vh\]{height:80vh}.h-4{height:1rem}.max-h-\[400px\]{max-height:400px}.min-h-52{min-height:13rem}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-40{width:10rem}.w-1\/2{width:50%}.w-8{width:2rem}.w-4{width:1rem}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.max-w-\[500px\]{max-width:500px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-center{align-items:center}.\!items-center{align-items:center!important}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*var(--tw-space-y-reverse))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-visible{overflow:visible}.overflow-scroll{overflow:scroll}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-l{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.rounded-r{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.border{border-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-transparent{fill:#0000}.fill-current{fill:currentColor}.stroke-white{stroke:#fff}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.pr-0{padding-right:0}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pr-2{padding-right:.5rem}.pl-2{padding-left:.5rem}.pb-2{padding-bottom:.5rem}.text-left{text-align:left}.text-center{text-align:center}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-90{opacity:.9}.opacity-50{opacity:.5}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px #0000004d;--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a;--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.outline-none{outline:2px solid #0000;outline-offset:2px}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.focus\:outline-none:focus{outline:2px solid #0000;outline-offset:2px}.focus\:ring-indigo-800:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(55 48 163/var(--tw-ring-opacity))}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width:1024px){.lg\:w-1\/2{width:50%}}
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.201",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@parca/client": "^0.16.
|
|
7
|
-
"@parca/components": "^0.16.
|
|
6
|
+
"@parca/client": "^0.16.77",
|
|
7
|
+
"@parca/components": "^0.16.161",
|
|
8
8
|
"@parca/dynamicsize": "^0.16.54",
|
|
9
|
-
"@parca/hooks": "^0.0.
|
|
9
|
+
"@parca/hooks": "^0.0.12",
|
|
10
10
|
"@parca/parser": "^0.16.55",
|
|
11
|
-
"@parca/store": "^0.16.
|
|
12
|
-
"@parca/utilities": "^0.0.
|
|
11
|
+
"@parca/store": "^0.16.88",
|
|
12
|
+
"@parca/utilities": "^0.0.17",
|
|
13
13
|
"@tanstack/react-query": "^4.0.5",
|
|
14
14
|
"@types/react-beautiful-dnd": "^13.1.3",
|
|
15
15
|
"apache-arrow": "^12.0.0",
|
|
@@ -47,5 +47,5 @@
|
|
|
47
47
|
"access": "public",
|
|
48
48
|
"registry": "https://registry.npmjs.org/"
|
|
49
49
|
},
|
|
50
|
-
"gitHead": "
|
|
50
|
+
"gitHead": "89efef90a26666bdf1f3584e49abd6bfe48408b6"
|
|
51
51
|
}
|
|
@@ -19,7 +19,6 @@ import throttle from 'lodash.throttle';
|
|
|
19
19
|
|
|
20
20
|
import {Label, MetricsSample, MetricsSeries as MetricsSeriesPb} from '@parca/client';
|
|
21
21
|
import {DateTimeRange, useKeyDown} from '@parca/components';
|
|
22
|
-
import {useContainerDimensions} from '@parca/hooks';
|
|
23
22
|
import {
|
|
24
23
|
formatDate,
|
|
25
24
|
formatForTimespan,
|
|
@@ -42,6 +41,9 @@ interface Props {
|
|
|
42
41
|
setTimeRange: (range: DateTimeRange) => void;
|
|
43
42
|
sampleUnit: string;
|
|
44
43
|
width?: number;
|
|
44
|
+
height?: number;
|
|
45
|
+
margin?: number;
|
|
46
|
+
marginRight?: number;
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
export interface HighlightedSeries {
|
|
@@ -70,23 +72,26 @@ const MetricsGraph = ({
|
|
|
70
72
|
onLabelClick,
|
|
71
73
|
setTimeRange,
|
|
72
74
|
sampleUnit,
|
|
75
|
+
width = 0,
|
|
76
|
+
height = 0,
|
|
77
|
+
margin = 0,
|
|
78
|
+
marginRight = 0,
|
|
73
79
|
}: Props): JSX.Element => {
|
|
74
|
-
const {ref, dimensions} = useContainerDimensions();
|
|
75
|
-
|
|
76
80
|
return (
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
81
|
+
<RawMetricsGraph
|
|
82
|
+
data={data}
|
|
83
|
+
from={from}
|
|
84
|
+
to={to}
|
|
85
|
+
profile={profile}
|
|
86
|
+
onSampleClick={onSampleClick}
|
|
87
|
+
onLabelClick={onLabelClick}
|
|
88
|
+
setTimeRange={setTimeRange}
|
|
89
|
+
sampleUnit={sampleUnit}
|
|
90
|
+
width={width}
|
|
91
|
+
height={height}
|
|
92
|
+
margin={margin}
|
|
93
|
+
marginRight={marginRight}
|
|
94
|
+
/>
|
|
90
95
|
);
|
|
91
96
|
};
|
|
92
97
|
|
|
@@ -111,6 +116,9 @@ export const RawMetricsGraph = ({
|
|
|
111
116
|
onLabelClick,
|
|
112
117
|
setTimeRange,
|
|
113
118
|
width,
|
|
119
|
+
height = 50,
|
|
120
|
+
margin = 0,
|
|
121
|
+
marginRight = 0,
|
|
114
122
|
sampleUnit,
|
|
115
123
|
}: Props): JSX.Element => {
|
|
116
124
|
const graph = useRef(null);
|
|
@@ -128,10 +136,6 @@ export const RawMetricsGraph = ({
|
|
|
128
136
|
width = 0;
|
|
129
137
|
}
|
|
130
138
|
|
|
131
|
-
const height = Math.min(width / 2.5, 400);
|
|
132
|
-
const margin = 50;
|
|
133
|
-
const marginRight = 20;
|
|
134
|
-
|
|
135
139
|
const series: Series[] = data.reduce<Series[]>(function (agg: Series[], s: MetricsSeriesPb) {
|
|
136
140
|
if (s.labelset !== undefined) {
|
|
137
141
|
const metric = s.labelset.labels.sort((a, b) => a.name.localeCompare(b.name));
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {useWindowSize} from 'react-use';
|
|
15
|
+
|
|
16
|
+
interface MetricsGraphDimensions {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
heightStyle: string;
|
|
20
|
+
margin: number;
|
|
21
|
+
marginRight: number;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const maxHeight = 402;
|
|
25
|
+
const margin = 50;
|
|
26
|
+
|
|
27
|
+
const heightStyle = `min(${maxHeight + margin}px, 47vw - 24px)`;
|
|
28
|
+
|
|
29
|
+
export const useMetricsGraphDimensions = (): MetricsGraphDimensions => {
|
|
30
|
+
let {width} = useWindowSize();
|
|
31
|
+
width = width - 58;
|
|
32
|
+
|
|
33
|
+
const height = Math.min(width / 2.5, maxHeight);
|
|
34
|
+
const marginRight = 20;
|
|
35
|
+
return {
|
|
36
|
+
width,
|
|
37
|
+
height,
|
|
38
|
+
heightStyle,
|
|
39
|
+
margin,
|
|
40
|
+
marginRight,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
@@ -22,8 +22,21 @@ import {getStepDuration} from '@parca/utilities';
|
|
|
22
22
|
|
|
23
23
|
import {MergedProfileSelection, ProfileSelection} from '..';
|
|
24
24
|
import MetricsGraph from '../MetricsGraph';
|
|
25
|
+
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
25
26
|
import useDelayedLoader from '../useDelayedLoader';
|
|
26
27
|
|
|
28
|
+
interface ProfileMetricsEmptyStateProps {
|
|
29
|
+
message: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const ProfileMetricsEmptyState = ({message}: ProfileMetricsEmptyStateProps): JSX.Element => {
|
|
33
|
+
return (
|
|
34
|
+
<div className="flex h-full w-full flex-col items-center justify-center">
|
|
35
|
+
<p>{message}</p>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|
|
39
|
+
|
|
27
40
|
interface ProfileMetricsGraphProps {
|
|
28
41
|
queryClient: QueryServiceClient;
|
|
29
42
|
queryExpression: string;
|
|
@@ -52,15 +65,12 @@ export const useQueryRange = (
|
|
|
52
65
|
isLoading: true,
|
|
53
66
|
error: null,
|
|
54
67
|
});
|
|
68
|
+
const [isLoading, setLoading] = useState<boolean>(true);
|
|
55
69
|
const metadata = useGrpcMetadata();
|
|
56
70
|
|
|
57
71
|
useEffect(() => {
|
|
58
72
|
void (async () => {
|
|
59
|
-
|
|
60
|
-
response: null,
|
|
61
|
-
isLoading: true,
|
|
62
|
-
error: null,
|
|
63
|
-
});
|
|
73
|
+
setLoading(true);
|
|
64
74
|
|
|
65
75
|
const stepDuration = getStepDuration(start, end);
|
|
66
76
|
const call = client.queryRange(
|
|
@@ -75,12 +85,19 @@ export const useQueryRange = (
|
|
|
75
85
|
);
|
|
76
86
|
|
|
77
87
|
call.response
|
|
78
|
-
.then(response =>
|
|
79
|
-
|
|
88
|
+
.then(response => {
|
|
89
|
+
setState({response, isLoading: false, error: null});
|
|
90
|
+
setLoading(false);
|
|
91
|
+
return null;
|
|
92
|
+
})
|
|
93
|
+
.catch(error => {
|
|
94
|
+
setState({response: null, isLoading: false, error});
|
|
95
|
+
setLoading(false);
|
|
96
|
+
});
|
|
80
97
|
})();
|
|
81
98
|
}, [client, queryExpression, start, end, metadata]);
|
|
82
99
|
|
|
83
|
-
return state;
|
|
100
|
+
return {...state, isLoading};
|
|
84
101
|
};
|
|
85
102
|
|
|
86
103
|
const ProfileMetricsGraph = ({
|
|
@@ -96,6 +113,7 @@ const ProfileMetricsGraph = ({
|
|
|
96
113
|
const {isLoading, response, error} = useQueryRange(queryClient, queryExpression, from, to);
|
|
97
114
|
const isLoaderVisible = useDelayedLoader(isLoading);
|
|
98
115
|
const {loader, onError, perf} = useParcaContext();
|
|
116
|
+
const {width, height, margin, marginRight} = useMetricsGraphDimensions();
|
|
99
117
|
|
|
100
118
|
useEffect(() => {
|
|
101
119
|
if (error !== null) {
|
|
@@ -111,7 +129,10 @@ const ProfileMetricsGraph = ({
|
|
|
111
129
|
perf?.markInteraction('Metrics graph render', response.series[0].samples.length);
|
|
112
130
|
}, [perf, response]);
|
|
113
131
|
|
|
114
|
-
|
|
132
|
+
const series = response?.series;
|
|
133
|
+
const dataAvailable = series !== null && series !== undefined && series?.length > 0;
|
|
134
|
+
|
|
135
|
+
if (isLoaderVisible || (isLoading && !dataAvailable)) {
|
|
115
136
|
return <>{loader}</>;
|
|
116
137
|
}
|
|
117
138
|
|
|
@@ -127,15 +148,14 @@ const ProfileMetricsGraph = ({
|
|
|
127
148
|
);
|
|
128
149
|
}
|
|
129
150
|
|
|
130
|
-
|
|
131
|
-
if (series !== null && series !== undefined && series?.length > 0) {
|
|
151
|
+
if (dataAvailable) {
|
|
132
152
|
const handleSampleClick = (timestamp: number, _value: number, labels: Label[]): void => {
|
|
133
153
|
onPointClick(timestamp, labels, queryExpression);
|
|
134
154
|
};
|
|
135
155
|
|
|
136
156
|
return (
|
|
137
157
|
<div
|
|
138
|
-
className="rounded border-gray-300 dark:border-gray-500 dark:bg-gray-700"
|
|
158
|
+
className="h-full w-full rounded border-gray-300 dark:border-gray-500 dark:bg-gray-700"
|
|
139
159
|
style={{borderWidth: 1}}
|
|
140
160
|
>
|
|
141
161
|
<MetricsGraph
|
|
@@ -146,19 +166,17 @@ const ProfileMetricsGraph = ({
|
|
|
146
166
|
setTimeRange={setTimeRange}
|
|
147
167
|
onSampleClick={handleSampleClick}
|
|
148
168
|
onLabelClick={addLabelMatcher}
|
|
149
|
-
width={0}
|
|
150
169
|
sampleUnit={Query.parse(queryExpression).profileType().sampleUnit}
|
|
170
|
+
height={height}
|
|
171
|
+
width={width}
|
|
172
|
+
margin={margin}
|
|
173
|
+
marginRight={marginRight}
|
|
151
174
|
/>
|
|
152
175
|
</div>
|
|
153
176
|
);
|
|
154
177
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
<div className="flex justify-center py-20">
|
|
158
|
-
<p className="m-0">No data found. Try a different query.</p>
|
|
159
|
-
</div>
|
|
160
|
-
</div>
|
|
161
|
-
);
|
|
178
|
+
|
|
179
|
+
return <ProfileMetricsEmptyState message="No data found. Try a different query." />;
|
|
162
180
|
};
|
|
163
181
|
|
|
164
182
|
export default ProfileMetricsGraph;
|
|
@@ -31,9 +31,11 @@ import {getStepDuration, getStepDurationInMilliseconds} from '@parca/utilities';
|
|
|
31
31
|
|
|
32
32
|
import {MergedProfileSelection, ProfileSelection} from '..';
|
|
33
33
|
import MatchersInput from '../MatchersInput/index';
|
|
34
|
-
import
|
|
34
|
+
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
35
|
+
import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
|
|
35
36
|
import ProfileTypeSelector from '../ProfileTypeSelector/index';
|
|
36
37
|
import CompareButton from './CompareButton';
|
|
38
|
+
import {useAutoQuerySelector} from './useAutoQuerySelector';
|
|
37
39
|
|
|
38
40
|
export interface QuerySelection {
|
|
39
41
|
expression: string;
|
|
@@ -98,6 +100,7 @@ const ProfileSelector = ({
|
|
|
98
100
|
data: profileTypesData,
|
|
99
101
|
error,
|
|
100
102
|
} = useProfileTypes(queryClient);
|
|
103
|
+
const {heightStyle} = useMetricsGraphDimensions();
|
|
101
104
|
|
|
102
105
|
const [timeRangeSelection, setTimeRangeSelection] = useState(
|
|
103
106
|
DateTimeRange.fromRangeKey(querySelection.timeSelection)
|
|
@@ -179,6 +182,13 @@ const ProfileSelector = ({
|
|
|
179
182
|
}
|
|
180
183
|
};
|
|
181
184
|
|
|
185
|
+
useAutoQuerySelector({
|
|
186
|
+
selectedProfileName,
|
|
187
|
+
profileTypesData,
|
|
188
|
+
setProfileName,
|
|
189
|
+
setQueryExpression,
|
|
190
|
+
});
|
|
191
|
+
|
|
182
192
|
const handleCompareClick = (): void => onCompareProfile();
|
|
183
193
|
|
|
184
194
|
const searchDisabled =
|
|
@@ -236,61 +246,63 @@ const ProfileSelector = ({
|
|
|
236
246
|
</Card.Header>
|
|
237
247
|
{
|
|
238
248
|
<Card.Body>
|
|
239
|
-
{
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
setTimeRangeSelection(range);
|
|
257
|
-
selectQuery({
|
|
258
|
-
expression: queryExpressionString,
|
|
259
|
-
from,
|
|
260
|
-
to,
|
|
261
|
-
timeSelection: range.getRangeKey(),
|
|
262
|
-
...mergedProfileParams,
|
|
263
|
-
});
|
|
264
|
-
}}
|
|
265
|
-
addLabelMatcher={addLabelMatcher}
|
|
266
|
-
onPointClick={(timestamp, labels, queryExpression) => {
|
|
267
|
-
// TODO: Pass the query object via click rather than queryExpression
|
|
268
|
-
let query = Query.parse(queryExpression);
|
|
269
|
-
labels.forEach(l => {
|
|
270
|
-
const [newQuery, updated] = query.setMatcher(l.name, l.value);
|
|
271
|
-
if (updated) {
|
|
272
|
-
query = newQuery;
|
|
249
|
+
<div style={{height: heightStyle}}>
|
|
250
|
+
{querySelection.expression !== undefined &&
|
|
251
|
+
querySelection.expression.length > 0 &&
|
|
252
|
+
querySelection.from !== undefined &&
|
|
253
|
+
querySelection.to !== undefined ? (
|
|
254
|
+
<ProfileMetricsGraph
|
|
255
|
+
queryClient={queryClient}
|
|
256
|
+
queryExpression={querySelection.expression}
|
|
257
|
+
from={querySelection.from}
|
|
258
|
+
to={querySelection.to}
|
|
259
|
+
profile={profileSelection}
|
|
260
|
+
setTimeRange={(range: DateTimeRange) => {
|
|
261
|
+
const from = range.getFromMs();
|
|
262
|
+
const to = range.getToMs();
|
|
263
|
+
let mergedProfileParams = {};
|
|
264
|
+
if (query.profileType().delta) {
|
|
265
|
+
mergedProfileParams = {mergeFrom: from, mergeTo: to};
|
|
273
266
|
}
|
|
274
|
-
|
|
267
|
+
setTimeRangeSelection(range);
|
|
268
|
+
selectQuery({
|
|
269
|
+
expression: queryExpressionString,
|
|
270
|
+
from,
|
|
271
|
+
to,
|
|
272
|
+
timeSelection: range.getRangeKey(),
|
|
273
|
+
...mergedProfileParams,
|
|
274
|
+
});
|
|
275
|
+
}}
|
|
276
|
+
addLabelMatcher={addLabelMatcher}
|
|
277
|
+
onPointClick={(timestamp, labels, queryExpression) => {
|
|
278
|
+
// TODO: Pass the query object via click rather than queryExpression
|
|
279
|
+
let query = Query.parse(queryExpression);
|
|
280
|
+
labels.forEach(l => {
|
|
281
|
+
const [newQuery, updated] = query.setMatcher(l.name, l.value);
|
|
282
|
+
if (updated) {
|
|
283
|
+
query = newQuery;
|
|
284
|
+
}
|
|
285
|
+
});
|
|
275
286
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
287
|
+
const stepDuration = getStepDuration(querySelection.from, querySelection.to);
|
|
288
|
+
const stepDurationInMilliseconds = getStepDurationInMilliseconds(stepDuration);
|
|
289
|
+
const mergeFrom = timestamp;
|
|
290
|
+
const mergeTo = query.profileType().delta
|
|
291
|
+
? mergeFrom + stepDurationInMilliseconds
|
|
292
|
+
: mergeFrom;
|
|
293
|
+
selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
|
|
294
|
+
}}
|
|
295
|
+
/>
|
|
296
|
+
) : (
|
|
297
|
+
<>
|
|
298
|
+
{profileSelection == null ? (
|
|
299
|
+
<ProfileMetricsEmptyState
|
|
300
|
+
message={`Please select a profile type and click "Search" to begin.`}
|
|
301
|
+
/>
|
|
302
|
+
) : null}
|
|
303
|
+
</>
|
|
304
|
+
)}
|
|
305
|
+
</div>
|
|
294
306
|
</Card.Body>
|
|
295
307
|
}
|
|
296
308
|
</Card>
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
|
|
14
|
+
import {useEffect} from 'react';
|
|
15
|
+
|
|
16
|
+
import {ProfileTypesResponse} from '@parca/client';
|
|
17
|
+
import {selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector} from '@parca/store';
|
|
18
|
+
|
|
19
|
+
import {constructProfileName} from '../ProfileTypeSelector';
|
|
20
|
+
|
|
21
|
+
interface Props {
|
|
22
|
+
selectedProfileName: string;
|
|
23
|
+
profileTypesData: ProfileTypesResponse | undefined;
|
|
24
|
+
setProfileName: (name: string) => void;
|
|
25
|
+
setQueryExpression: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const useAutoQuerySelector = ({
|
|
29
|
+
selectedProfileName,
|
|
30
|
+
profileTypesData,
|
|
31
|
+
setProfileName,
|
|
32
|
+
setQueryExpression,
|
|
33
|
+
}: Props): void => {
|
|
34
|
+
const autoQuery = useAppSelector(selectAutoQuery);
|
|
35
|
+
const dispatch = useAppDispatch();
|
|
36
|
+
|
|
37
|
+
// Effect to load some initial data on load when is no selection
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
void (async () => {
|
|
40
|
+
if (selectedProfileName.length > 0) {
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (profileTypesData?.types == null || profileTypesData.types.length < 1) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (autoQuery === 'true') {
|
|
47
|
+
// Autoquery already enabled.
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
dispatch(setAutoQuery('true'));
|
|
51
|
+
let profileType = profileTypesData.types.find(type => type.name === 'parca_agent_cpu');
|
|
52
|
+
if (profileType == null) {
|
|
53
|
+
profileType = profileTypesData.types[0];
|
|
54
|
+
}
|
|
55
|
+
setProfileName(constructProfileName(profileType));
|
|
56
|
+
})();
|
|
57
|
+
}, [
|
|
58
|
+
profileTypesData,
|
|
59
|
+
selectedProfileName,
|
|
60
|
+
autoQuery,
|
|
61
|
+
dispatch,
|
|
62
|
+
setQueryExpression,
|
|
63
|
+
setProfileName,
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
void (async () => {
|
|
68
|
+
if (
|
|
69
|
+
autoQuery !== 'true' ||
|
|
70
|
+
profileTypesData?.types == null ||
|
|
71
|
+
profileTypesData.types.length < 1 ||
|
|
72
|
+
selectedProfileName.length === 0
|
|
73
|
+
) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
setQueryExpression();
|
|
77
|
+
dispatch(setAutoQuery('false'));
|
|
78
|
+
})();
|
|
79
|
+
}, [
|
|
80
|
+
profileTypesData,
|
|
81
|
+
setQueryExpression,
|
|
82
|
+
autoQuery,
|
|
83
|
+
setProfileName,
|
|
84
|
+
dispatch,
|
|
85
|
+
selectedProfileName,
|
|
86
|
+
]);
|
|
87
|
+
};
|
|
@@ -128,17 +128,16 @@ export function profileSelectElement(
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
export const constructProfileName = (type: ProfileType): string => {
|
|
132
|
+
return `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${
|
|
133
|
+
type.delta ? ':delta' : ''
|
|
134
|
+
}`;
|
|
135
|
+
};
|
|
136
|
+
|
|
131
137
|
export const normalizeProfileTypesData = (types: ProfileType[]): string[] => {
|
|
132
|
-
return types
|
|
133
|
-
.
|
|
134
|
-
|
|
135
|
-
`${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${
|
|
136
|
-
type.delta ? ':delta' : ''
|
|
137
|
-
}`
|
|
138
|
-
)
|
|
139
|
-
.sort((a: string, b: string): number => {
|
|
140
|
-
return a.localeCompare(b);
|
|
141
|
-
});
|
|
138
|
+
return types.map(constructProfileName).sort((a: string, b: string): number => {
|
|
139
|
+
return a.localeCompare(b);
|
|
140
|
+
});
|
|
142
141
|
};
|
|
143
142
|
|
|
144
143
|
interface Props {
|
|
@@ -115,7 +115,7 @@ export const ProfileView = ({
|
|
|
115
115
|
}: ProfileViewProps): JSX.Element => {
|
|
116
116
|
const {ref, dimensions} = useContainerDimensions();
|
|
117
117
|
const [curPath, setCurPath] = useState<string[]>([]);
|
|
118
|
-
const [rawDashboardItems, setDashboardItems] = useURLState({
|
|
118
|
+
const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
|
|
119
119
|
param: 'dashboard_items',
|
|
120
120
|
navigateTo,
|
|
121
121
|
});
|
|
@@ -38,7 +38,8 @@ export const ProfileViewWithData = ({
|
|
|
38
38
|
navigateTo,
|
|
39
39
|
}: ProfileViewWithDataProps): JSX.Element => {
|
|
40
40
|
const metadata = useGrpcMetadata();
|
|
41
|
-
const [dashboardItems] = useURLState({param: 'dashboard_items', navigateTo});
|
|
41
|
+
const [dashboardItems = ['icicle']] = useURLState({param: 'dashboard_items', navigateTo});
|
|
42
|
+
|
|
42
43
|
const [enableTrimming] = useUserPreference<boolean>(USER_PREFERENCES.ENABLE_GRAPH_TRIMMING.key);
|
|
43
44
|
const [arrowFlamegraphEnabled] = useUIFeatureFlag('flamegraph-arrow');
|
|
44
45
|
const [pprofDownloading, setPprofDownloading] = useState<boolean>(false);
|