@parca/profile 0.19.81 → 0.19.82
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/MatchersInput/index.d.ts +2 -34
- package/dist/MatchersInput/index.d.ts.map +1 -1
- package/dist/MatchersInput/index.js +14 -91
- package/dist/MetricsGraph/index.d.ts.map +1 -1
- package/dist/MetricsGraph/index.js +13 -1
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +6 -29
- package/dist/ProfileSelector/MetricsGraphSection.d.ts +2 -9
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +3 -38
- package/dist/ProfileSelector/index.d.ts +1 -29
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +12 -9
- package/dist/QueryControls/index.d.ts +42 -0
- package/dist/QueryControls/index.d.ts.map +1 -0
- package/dist/{ProfileSelector/QueryControls.js → QueryControls/index.js} +16 -13
- package/dist/SimpleMatchers/Select.js +1 -1
- package/dist/SimpleMatchers/index.d.ts +2 -11
- package/dist/SimpleMatchers/index.d.ts.map +1 -1
- package/dist/SimpleMatchers/index.js +34 -45
- package/dist/ViewMatchers/index.d.ts +0 -9
- package/dist/ViewMatchers/index.d.ts.map +1 -1
- package/dist/ViewMatchers/index.js +20 -12
- package/dist/contexts/LabelsQueryProvider.d.ts +35 -0
- package/dist/contexts/LabelsQueryProvider.d.ts.map +1 -0
- package/dist/contexts/LabelsQueryProvider.js +70 -0
- package/dist/contexts/UnifiedLabelsContext.d.ts +37 -0
- package/dist/contexts/UnifiedLabelsContext.d.ts.map +1 -0
- package/dist/contexts/UnifiedLabelsContext.js +88 -0
- package/dist/contexts/utils.d.ts +10 -0
- package/dist/contexts/utils.d.ts.map +1 -0
- package/dist/contexts/utils.js +31 -0
- package/dist/hooks/useLabels.d.ts +23 -0
- package/dist/hooks/useLabels.d.ts.map +1 -0
- package/dist/hooks/useLabels.js +75 -0
- package/dist/hooks/useQueryState.d.ts +2 -0
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +18 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -3
- package/dist/styles.css +1 -1
- package/dist/useSumBy.js +1 -1
- package/package.json +2 -2
- package/src/MatchersInput/index.tsx +17 -163
- package/src/MetricsGraph/index.tsx +17 -1
- package/src/ProfileMetricsGraph/index.tsx +17 -98
- package/src/ProfileSelector/MetricsGraphSection.tsx +14 -115
- package/src/ProfileSelector/index.tsx +106 -109
- package/src/{ProfileSelector/QueryControls.tsx → QueryControls/index.tsx} +66 -84
- package/src/SimpleMatchers/Select.tsx +1 -1
- package/src/SimpleMatchers/index.tsx +46 -85
- package/src/ViewMatchers/index.tsx +22 -30
- package/src/contexts/LabelsQueryProvider.tsx +142 -0
- package/src/contexts/UnifiedLabelsContext.tsx +155 -0
- package/src/contexts/utils.ts +43 -0
- package/src/hooks/useLabels.ts +121 -0
- package/src/hooks/useQueryState.ts +25 -0
- package/src/index.tsx +29 -3
- package/src/useSumBy.ts +1 -1
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +0 -29
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +0 -1
- package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +0 -175
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +0 -28
- package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +0 -1
- package/dist/MetricsGraph/UtilizationMetrics/index.js +0 -186
- package/dist/ProfileSelector/QueryControls.d.ts +0 -43
- package/dist/ProfileSelector/QueryControls.d.ts.map +0 -1
- package/dist/contexts/MatchersInputLabelsContext.d.ts +0 -29
- package/dist/contexts/MatchersInputLabelsContext.d.ts.map +0 -1
- package/dist/contexts/MatchersInputLabelsContext.js +0 -79
- package/dist/contexts/SimpleMatchersLabelContext.d.ts +0 -25
- package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +0 -1
- package/dist/contexts/SimpleMatchersLabelContext.js +0 -115
- package/dist/contexts/UtilizationLabelsContext.d.ts +0 -15
- package/dist/contexts/UtilizationLabelsContext.d.ts.map +0 -1
- package/dist/contexts/UtilizationLabelsContext.js +0 -25
- package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +0 -405
- package/src/MetricsGraph/UtilizationMetrics/index.tsx +0 -426
- package/src/contexts/MatchersInputLabelsContext.tsx +0 -141
- package/src/contexts/SimpleMatchersLabelContext.tsx +0 -189
- package/src/contexts/UtilizationLabelsContext.tsx +0 -45
|
@@ -11,157 +11,19 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import React, {
|
|
14
|
+
import React, {useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
|
-
import {useQuery} from '@tanstack/react-query';
|
|
17
16
|
import cx from 'classnames';
|
|
18
17
|
import TextareaAutosize from 'react-textarea-autosize';
|
|
19
18
|
|
|
20
|
-
import {LabelsRequest, LabelsResponse, QueryServiceClient, ValuesRequest} from '@parca/client';
|
|
21
|
-
import {useGrpcMetadata} from '@parca/components';
|
|
22
19
|
import {Query} from '@parca/parser';
|
|
23
20
|
import {TEST_IDS, testId} from '@parca/test-utils';
|
|
24
|
-
import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
|
|
25
21
|
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import useGrpcQuery from '../useGrpcQuery';
|
|
22
|
+
import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
|
|
23
|
+
import {useQueryState} from '../hooks/useQueryState';
|
|
29
24
|
import SuggestionsList, {Suggestion, Suggestions} from './SuggestionsList';
|
|
30
25
|
|
|
31
|
-
|
|
32
|
-
queryClient: QueryServiceClient;
|
|
33
|
-
setMatchersString: (arg: string) => void;
|
|
34
|
-
runQuery: () => void;
|
|
35
|
-
currentQuery: Query;
|
|
36
|
-
profileType: string;
|
|
37
|
-
start?: number;
|
|
38
|
-
end?: number;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface ILabelNamesResult {
|
|
42
|
-
response?: LabelsResponse;
|
|
43
|
-
error?: Error;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface UseLabelNames {
|
|
47
|
-
result: ILabelNamesResult;
|
|
48
|
-
loading: boolean;
|
|
49
|
-
refetch: () => Promise<void>;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export const useLabelNames = (
|
|
53
|
-
client: QueryServiceClient,
|
|
54
|
-
profileType: string,
|
|
55
|
-
start?: number,
|
|
56
|
-
end?: number,
|
|
57
|
-
match?: string[]
|
|
58
|
-
): UseLabelNames => {
|
|
59
|
-
const metadata = useGrpcMetadata();
|
|
60
|
-
|
|
61
|
-
const {data, isLoading, error, refetch} = useGrpcQuery<LabelsResponse>({
|
|
62
|
-
key: ['labelNames', profileType, match?.join(','), start, end],
|
|
63
|
-
queryFn: async signal => {
|
|
64
|
-
const request: LabelsRequest = {match: match !== undefined ? match : []};
|
|
65
|
-
if (start !== undefined && end !== undefined) {
|
|
66
|
-
request.start = millisToProtoTimestamp(start);
|
|
67
|
-
request.end = millisToProtoTimestamp(end);
|
|
68
|
-
}
|
|
69
|
-
if (profileType !== undefined) {
|
|
70
|
-
request.profileType = profileType;
|
|
71
|
-
}
|
|
72
|
-
const {response} = await client.labels(request, {meta: metadata, abort: signal});
|
|
73
|
-
return response;
|
|
74
|
-
},
|
|
75
|
-
options: {
|
|
76
|
-
enabled: profileType !== undefined && profileType !== '',
|
|
77
|
-
keepPreviousData: false,
|
|
78
|
-
},
|
|
79
|
-
});
|
|
80
|
-
|
|
81
|
-
useEffect(() => {
|
|
82
|
-
console.log('Label names query result:', {data, error, isLoading});
|
|
83
|
-
}, [data, error, isLoading]);
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
result: {response: data, error: error as Error},
|
|
87
|
-
loading: isLoading,
|
|
88
|
-
refetch: async () => {
|
|
89
|
-
await refetch();
|
|
90
|
-
},
|
|
91
|
-
};
|
|
92
|
-
};
|
|
93
|
-
|
|
94
|
-
interface UseLabelValues {
|
|
95
|
-
result: {
|
|
96
|
-
response: string[];
|
|
97
|
-
error?: Error;
|
|
98
|
-
};
|
|
99
|
-
loading: boolean;
|
|
100
|
-
refetch: () => Promise<void>;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export const useLabelValues = (
|
|
104
|
-
client: QueryServiceClient,
|
|
105
|
-
labelName: string,
|
|
106
|
-
profileType: string,
|
|
107
|
-
start?: number,
|
|
108
|
-
end?: number
|
|
109
|
-
): UseLabelValues => {
|
|
110
|
-
const metadata = useGrpcMetadata();
|
|
111
|
-
|
|
112
|
-
const {data, isLoading, error, refetch} = useGrpcQuery<string[]>({
|
|
113
|
-
key: ['labelValues', labelName, profileType, start, end],
|
|
114
|
-
queryFn: async signal => {
|
|
115
|
-
const request: ValuesRequest = {labelName, match: [], profileType};
|
|
116
|
-
if (start !== undefined && end !== undefined) {
|
|
117
|
-
request.start = millisToProtoTimestamp(start);
|
|
118
|
-
request.end = millisToProtoTimestamp(end);
|
|
119
|
-
}
|
|
120
|
-
const {response} = await client.values(request, {meta: metadata, abort: signal});
|
|
121
|
-
return sanitizeLabelValue(response.labelValues);
|
|
122
|
-
},
|
|
123
|
-
options: {
|
|
124
|
-
enabled:
|
|
125
|
-
profileType !== undefined &&
|
|
126
|
-
profileType !== '' &&
|
|
127
|
-
labelName !== undefined &&
|
|
128
|
-
labelName !== '',
|
|
129
|
-
keepPreviousData: false,
|
|
130
|
-
},
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
console.log('Label values query result:', {data, error, isLoading, labelName});
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
result: {response: data ?? [], error: error as Error},
|
|
137
|
-
loading: isLoading,
|
|
138
|
-
refetch: async () => {
|
|
139
|
-
await refetch();
|
|
140
|
-
},
|
|
141
|
-
};
|
|
142
|
-
};
|
|
143
|
-
|
|
144
|
-
export const useFetchUtilizationLabelValues = (
|
|
145
|
-
labelName: string,
|
|
146
|
-
utilizationLabels?: UtilizationLabels
|
|
147
|
-
): string[] => {
|
|
148
|
-
const {data} = useQuery({
|
|
149
|
-
queryKey: ['utilizationLabelValues', labelName],
|
|
150
|
-
queryFn: async () => {
|
|
151
|
-
const result = await utilizationLabels?.utilizationFetchLabelValues?.(labelName);
|
|
152
|
-
return result ?? [];
|
|
153
|
-
},
|
|
154
|
-
enabled: utilizationLabels?.utilizationFetchLabelValues != null && labelName !== '',
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
return data ?? [];
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const MatchersInput = ({
|
|
161
|
-
setMatchersString,
|
|
162
|
-
runQuery,
|
|
163
|
-
currentQuery,
|
|
164
|
-
}: MatchersInputProps): JSX.Element => {
|
|
26
|
+
const MatchersInput = (): JSX.Element => {
|
|
165
27
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
166
28
|
const [focusedInput, setFocusedInput] = useState(false);
|
|
167
29
|
const [lastCompleted, setLastCompleted] = useState<Suggestion>(new Suggestion('', '', ''));
|
|
@@ -169,7 +31,7 @@ const MatchersInput = ({
|
|
|
169
31
|
const {
|
|
170
32
|
labelNames,
|
|
171
33
|
labelValues,
|
|
172
|
-
labelNameMappings,
|
|
34
|
+
labelNameMappingsForMatchersInput: labelNameMappings,
|
|
173
35
|
isLabelNamesLoading,
|
|
174
36
|
isLabelValuesLoading,
|
|
175
37
|
currentLabelName,
|
|
@@ -177,13 +39,16 @@ const MatchersInput = ({
|
|
|
177
39
|
shouldHandlePrefixes,
|
|
178
40
|
refetchLabelValues,
|
|
179
41
|
refetchLabelNames,
|
|
180
|
-
|
|
42
|
+
suffix,
|
|
43
|
+
} = useUnifiedLabels();
|
|
44
|
+
|
|
45
|
+
const {setDraftMatchers, commitDraft, draftParsedQuery} = useQueryState({suffix});
|
|
181
46
|
|
|
182
|
-
const value =
|
|
47
|
+
const value = draftParsedQuery != null ? draftParsedQuery.matchersString() : '';
|
|
183
48
|
|
|
184
49
|
const suggestionSections = useMemo(() => {
|
|
185
50
|
const suggestionSections = new Suggestions();
|
|
186
|
-
Query.suggest(`${
|
|
51
|
+
Query.suggest(`${draftParsedQuery?.profileName() as string}{${value}`).forEach(function (s) {
|
|
187
52
|
// Skip suggestions that we just completed. This really only works,
|
|
188
53
|
// because we know the language is not repetitive. For a language that
|
|
189
54
|
// has a repeating word, this would not work.
|
|
@@ -256,7 +121,7 @@ const MatchersInput = ({
|
|
|
256
121
|
});
|
|
257
122
|
return suggestionSections;
|
|
258
123
|
}, [
|
|
259
|
-
|
|
124
|
+
draftParsedQuery,
|
|
260
125
|
lastCompleted,
|
|
261
126
|
labelNames,
|
|
262
127
|
labelValues,
|
|
@@ -271,7 +136,7 @@ const MatchersInput = ({
|
|
|
271
136
|
|
|
272
137
|
const onChange = (e: React.ChangeEvent<HTMLTextAreaElement>): void => {
|
|
273
138
|
const newValue = e.target.value;
|
|
274
|
-
|
|
139
|
+
setDraftMatchers(newValue);
|
|
275
140
|
resetLastCompleted();
|
|
276
141
|
};
|
|
277
142
|
|
|
@@ -294,7 +159,7 @@ const MatchersInput = ({
|
|
|
294
159
|
const applySuggestion = (suggestion: Suggestion): void => {
|
|
295
160
|
const newValue = complete(suggestion);
|
|
296
161
|
setLastCompleted(suggestion);
|
|
297
|
-
|
|
162
|
+
setDraftMatchers(newValue);
|
|
298
163
|
if (inputRef.current !== null) {
|
|
299
164
|
inputRef.current.value = newValue;
|
|
300
165
|
inputRef.current.focus();
|
|
@@ -309,7 +174,7 @@ const MatchersInput = ({
|
|
|
309
174
|
setFocusedInput(false);
|
|
310
175
|
};
|
|
311
176
|
|
|
312
|
-
const profileSelected =
|
|
177
|
+
const profileSelected = draftParsedQuery?.profileName() === '';
|
|
313
178
|
|
|
314
179
|
return (
|
|
315
180
|
<div
|
|
@@ -345,7 +210,7 @@ const MatchersInput = ({
|
|
|
345
210
|
suggestions={suggestionSections}
|
|
346
211
|
applySuggestion={applySuggestion}
|
|
347
212
|
inputRef={inputRef.current}
|
|
348
|
-
runQuery={
|
|
213
|
+
runQuery={commitDraft}
|
|
349
214
|
focusedInput={focusedInput}
|
|
350
215
|
isLabelValuesLoading={
|
|
351
216
|
isLabelValuesLoading && lastCompleted.type === 'literal' && lastCompleted.value !== ','
|
|
@@ -358,15 +223,4 @@ const MatchersInput = ({
|
|
|
358
223
|
);
|
|
359
224
|
};
|
|
360
225
|
|
|
361
|
-
export default
|
|
362
|
-
return (
|
|
363
|
-
<LabelsProvider
|
|
364
|
-
queryClient={props.queryClient}
|
|
365
|
-
profileType={props.profileType}
|
|
366
|
-
start={props.start}
|
|
367
|
-
end={props.end}
|
|
368
|
-
>
|
|
369
|
-
<MatchersInput {...props} />
|
|
370
|
-
</LabelsProvider>
|
|
371
|
-
);
|
|
372
|
-
}
|
|
226
|
+
export default MatchersInput;
|
|
@@ -114,6 +114,7 @@ const MetricsGraph = ({
|
|
|
114
114
|
};
|
|
115
115
|
|
|
116
116
|
export default MetricsGraph;
|
|
117
|
+
|
|
117
118
|
export type {ContextMenuItemOrSubmenu, ContextMenuItem, ContextMenuSubmenu};
|
|
118
119
|
|
|
119
120
|
export const parseValue = (value: string): number | null => {
|
|
@@ -207,6 +208,13 @@ export const RawMetricsGraph = ({
|
|
|
207
208
|
}
|
|
208
209
|
|
|
209
210
|
const closestPointPerSeries = series.map(function (s) {
|
|
211
|
+
if (s.values.length === 0) {
|
|
212
|
+
return {
|
|
213
|
+
pointIndex: undefined,
|
|
214
|
+
distance: Infinity,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
210
218
|
const distances = s.values.map(d => {
|
|
211
219
|
const x = xScale(d[0]) + margin / 2; // d[0] is timestamp_ms
|
|
212
220
|
const y = yScale(d[1]) - margin / 3; // d[1] is value
|
|
@@ -216,7 +224,7 @@ export const RawMetricsGraph = ({
|
|
|
216
224
|
});
|
|
217
225
|
|
|
218
226
|
const pointIndex = d3.minIndex(distances);
|
|
219
|
-
const minDistance = distances[pointIndex];
|
|
227
|
+
const minDistance = pointIndex != null ? distances[pointIndex] : Infinity;
|
|
220
228
|
|
|
221
229
|
return {
|
|
222
230
|
pointIndex,
|
|
@@ -225,7 +233,15 @@ export const RawMetricsGraph = ({
|
|
|
225
233
|
});
|
|
226
234
|
|
|
227
235
|
const closestSeriesIndex = d3.minIndex(closestPointPerSeries, s => s.distance);
|
|
236
|
+
if (closestSeriesIndex == null || closestPointPerSeries[closestSeriesIndex] == null) {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
|
|
228
240
|
const pointIndex = closestPointPerSeries[closestSeriesIndex].pointIndex;
|
|
241
|
+
if (pointIndex == null) {
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
|
|
229
245
|
return {
|
|
230
246
|
seriesIndex: closestSeriesIndex,
|
|
231
247
|
pointIndex,
|
|
@@ -37,19 +37,11 @@ import MetricsGraph, {ContextMenuItemOrSubmenu, Series, SeriesPoint} from '../Me
|
|
|
37
37
|
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
38
38
|
import {useQueryRange} from './hooks/useQueryRange';
|
|
39
39
|
|
|
40
|
-
const transformUtilizationLabels = (label: string, utilizationMetrics: boolean): string => {
|
|
41
|
-
if (utilizationMetrics) {
|
|
42
|
-
return label.replace('attributes.', '').replace('attributes_resource.', '');
|
|
43
|
-
}
|
|
44
|
-
return label;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
40
|
const createProfileContextMenuItems = (
|
|
48
41
|
addLabelMatcher: (
|
|
49
42
|
labels: {key: string; value: string} | Array<{key: string; value: string}>
|
|
50
43
|
) => void,
|
|
51
|
-
data: MetricsSeriesPb[]
|
|
52
|
-
utilizationMetrics = false
|
|
44
|
+
data: MetricsSeriesPb[] // The original MetricsSeriesPb[] data
|
|
53
45
|
): ContextMenuItemOrSubmenu[] => {
|
|
54
46
|
return [
|
|
55
47
|
{
|
|
@@ -107,7 +99,7 @@ const createProfileContextMenuItems = (
|
|
|
107
99
|
id: `add-label-${label.name}`,
|
|
108
100
|
label: (
|
|
109
101
|
<div className="mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-300">
|
|
110
|
-
{`${
|
|
102
|
+
{`${label.name}="${label.value}"`}
|
|
111
103
|
</div>
|
|
112
104
|
),
|
|
113
105
|
onClick: () => {
|
|
@@ -438,28 +430,6 @@ const ProfileMetricsGraph = ({
|
|
|
438
430
|
const nameLabel = labels.find(e => e.name === '__name__');
|
|
439
431
|
const highlightedNameLabel = nameLabel ?? {name: '', value: ''};
|
|
440
432
|
|
|
441
|
-
// Calculate attributes maps for utilization metrics
|
|
442
|
-
const utilizationMetrics = false; // This is for profile metrics, not utilization
|
|
443
|
-
const attributesMap = labels
|
|
444
|
-
.filter(
|
|
445
|
-
label =>
|
|
446
|
-
label.name.startsWith('attributes.') &&
|
|
447
|
-
!label.name.startsWith('attributes_resource.')
|
|
448
|
-
)
|
|
449
|
-
.reduce<Record<string, string>>((acc, label) => {
|
|
450
|
-
const key = label.name.replace('attributes.', '');
|
|
451
|
-
acc[key] = label.value;
|
|
452
|
-
return acc;
|
|
453
|
-
}, {});
|
|
454
|
-
|
|
455
|
-
const attributesResourceMap = labels
|
|
456
|
-
.filter(label => label.name.startsWith('attributes_resource.'))
|
|
457
|
-
.reduce<Record<string, string>>((acc, label) => {
|
|
458
|
-
const key = label.name.replace('attributes_resource.', '');
|
|
459
|
-
acc[key] = label.value;
|
|
460
|
-
return acc;
|
|
461
|
-
}, {});
|
|
462
|
-
|
|
463
433
|
const isDeltaType =
|
|
464
434
|
profile !== null
|
|
465
435
|
? (profile as MergedProfileSelection)?.query.profType.delta
|
|
@@ -528,72 +498,21 @@ const ProfileMetricsGraph = ({
|
|
|
528
498
|
</table>
|
|
529
499
|
</span>
|
|
530
500
|
<span className="my-2 block text-gray-500">
|
|
531
|
-
{
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
text={`${name.replace('attributes.', '')}="${
|
|
547
|
-
attributesResourceMap[name]
|
|
548
|
-
}"`}
|
|
549
|
-
maxTextLength={48}
|
|
550
|
-
id={`tooltip-${name}-${attributesResourceMap[name]}`}
|
|
551
|
-
/>
|
|
552
|
-
</div>
|
|
553
|
-
))}
|
|
554
|
-
</span>
|
|
555
|
-
{Object.keys(attributesMap).length > 0 && (
|
|
556
|
-
<span className="text-sm font-bold text-gray-700 dark:text-white">
|
|
557
|
-
Attributes
|
|
558
|
-
</span>
|
|
559
|
-
)}
|
|
560
|
-
<span className="my-2 block text-gray-500">
|
|
561
|
-
{Object.keys(attributesMap).map(name => (
|
|
562
|
-
<div
|
|
563
|
-
key={name}
|
|
564
|
-
className="mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400"
|
|
565
|
-
{...testId(TEST_IDS.TOOLTIP_LABEL)}
|
|
566
|
-
>
|
|
567
|
-
<TextWithTooltip
|
|
568
|
-
text={`${name.replace('attributes.', '')}="${
|
|
569
|
-
attributesMap[name]
|
|
570
|
-
}"`}
|
|
571
|
-
maxTextLength={48}
|
|
572
|
-
id={`tooltip-${name}-${attributesMap[name]}`}
|
|
573
|
-
/>
|
|
574
|
-
</div>
|
|
575
|
-
))}
|
|
576
|
-
</span>
|
|
577
|
-
</>
|
|
578
|
-
) : (
|
|
579
|
-
<>
|
|
580
|
-
{labels
|
|
581
|
-
.filter((label: Label) => label.name !== '__name__')
|
|
582
|
-
.map((label: Label) => (
|
|
583
|
-
<div
|
|
584
|
-
key={label.name}
|
|
585
|
-
className="mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400"
|
|
586
|
-
{...testId(TEST_IDS.TOOLTIP_LABEL)}
|
|
587
|
-
>
|
|
588
|
-
<TextWithTooltip
|
|
589
|
-
text={`${label.name}="${label.value}"`}
|
|
590
|
-
maxTextLength={37}
|
|
591
|
-
id={`tooltip-${label.name}`}
|
|
592
|
-
/>
|
|
593
|
-
</div>
|
|
594
|
-
))}
|
|
595
|
-
</>
|
|
596
|
-
)}
|
|
501
|
+
{labels
|
|
502
|
+
.filter((label: Label) => label.name !== '__name__')
|
|
503
|
+
.map((label: Label) => (
|
|
504
|
+
<div
|
|
505
|
+
key={label.name}
|
|
506
|
+
className="mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400"
|
|
507
|
+
{...testId(TEST_IDS.TOOLTIP_LABEL)}
|
|
508
|
+
>
|
|
509
|
+
<TextWithTooltip
|
|
510
|
+
text={`${label.name}="${label.value}"`}
|
|
511
|
+
maxTextLength={37}
|
|
512
|
+
id={`tooltip-${label.name}`}
|
|
513
|
+
/>
|
|
514
|
+
</div>
|
|
515
|
+
))}
|
|
597
516
|
</span>
|
|
598
517
|
<div className="flex w-full items-center gap-1 text-xs text-gray-500">
|
|
599
518
|
<Icon icon="iconoir:mouse-button-right" />
|
|
@@ -18,11 +18,9 @@ import {DateTimeRange, useURLStateBatch} from '@parca/components';
|
|
|
18
18
|
import {Query} from '@parca/parser';
|
|
19
19
|
|
|
20
20
|
import {ProfileSelection} from '..';
|
|
21
|
-
import UtilizationMetricsGraph from '../MetricsGraph/UtilizationMetrics';
|
|
22
|
-
import AreaChart from '../MetricsGraph/UtilizationMetrics/Throughput';
|
|
23
21
|
import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
|
|
24
22
|
import {useResetStateOnSeriesChange} from '../ProfileView/hooks/useResetStateOnSeriesChange';
|
|
25
|
-
import {QuerySelection
|
|
23
|
+
import {QuerySelection} from './index';
|
|
26
24
|
|
|
27
25
|
interface MetricsGraphSectionProps {
|
|
28
26
|
showMetricsGraph: boolean;
|
|
@@ -41,13 +39,6 @@ interface MetricsGraphSectionProps {
|
|
|
41
39
|
query: Query;
|
|
42
40
|
setNewQueryExpression: (queryExpression: string, commit?: boolean) => void;
|
|
43
41
|
setQueryExpression: (updateTs?: boolean) => void;
|
|
44
|
-
utilizationMetrics?: Array<{
|
|
45
|
-
name: string;
|
|
46
|
-
humanReadableName: string;
|
|
47
|
-
data: UtilizationMetricsType[];
|
|
48
|
-
}>;
|
|
49
|
-
utilizationMetricsLoading?: boolean;
|
|
50
|
-
onUtilizationSeriesSelect?: (name: string, seriesIndex: number) => void;
|
|
51
42
|
}
|
|
52
43
|
|
|
53
44
|
export function MetricsGraphSection({
|
|
@@ -66,9 +57,6 @@ export function MetricsGraphSection({
|
|
|
66
57
|
setProfileSelection,
|
|
67
58
|
query,
|
|
68
59
|
setNewQueryExpression,
|
|
69
|
-
utilizationMetrics,
|
|
70
|
-
utilizationMetricsLoading,
|
|
71
|
-
onUtilizationSeriesSelect,
|
|
72
60
|
}: MetricsGraphSectionProps): JSX.Element {
|
|
73
61
|
const resetStateOnSeriesChange = useResetStateOnSeriesChange();
|
|
74
62
|
const batchUpdates = useURLStateBatch();
|
|
@@ -147,89 +135,6 @@ export function MetricsGraphSection({
|
|
|
147
135
|
});
|
|
148
136
|
};
|
|
149
137
|
|
|
150
|
-
const UtilizationGraphToShow = ({
|
|
151
|
-
utilizationMetrics,
|
|
152
|
-
}: {
|
|
153
|
-
utilizationMetrics: Array<{
|
|
154
|
-
name: string;
|
|
155
|
-
humanReadableName: string;
|
|
156
|
-
data: UtilizationMetricsType[];
|
|
157
|
-
}>;
|
|
158
|
-
}): JSX.Element => {
|
|
159
|
-
const throughputMetrics = utilizationMetrics.filter(
|
|
160
|
-
metric =>
|
|
161
|
-
metric.name === 'gpu_pcie_throughput_transmit_bytes' ||
|
|
162
|
-
metric.name === 'gpu_pcie_throughput_receive_bytes'
|
|
163
|
-
);
|
|
164
|
-
const transmitData =
|
|
165
|
-
throughputMetrics.find(metric => metric.name === 'gpu_pcie_throughput_transmit_bytes')
|
|
166
|
-
?.data ?? [];
|
|
167
|
-
const receiveData =
|
|
168
|
-
throughputMetrics.find(metric => metric.name === 'gpu_pcie_throughput_receive_bytes')?.data ??
|
|
169
|
-
[];
|
|
170
|
-
|
|
171
|
-
if (utilizationMetrics.length === 0) {
|
|
172
|
-
return <></>;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
return (
|
|
176
|
-
<div>
|
|
177
|
-
{utilizationMetrics.map(({name, humanReadableName, data}) => {
|
|
178
|
-
if (
|
|
179
|
-
name !== 'gpu_pcie_throughput_transmit_bytes' &&
|
|
180
|
-
name !== 'gpu_pcie_throughput_receive_bytes'
|
|
181
|
-
) {
|
|
182
|
-
return (
|
|
183
|
-
<UtilizationMetricsGraph
|
|
184
|
-
key={name}
|
|
185
|
-
data={data}
|
|
186
|
-
setTimeRange={handleTimeRangeChange}
|
|
187
|
-
utilizationMetricsLoading={utilizationMetricsLoading}
|
|
188
|
-
humanReadableName={humanReadableName}
|
|
189
|
-
from={querySelection.from}
|
|
190
|
-
to={querySelection.to}
|
|
191
|
-
yAxisUnit="percentage"
|
|
192
|
-
addLabelMatcher={addLabelMatcher}
|
|
193
|
-
onSeriesClick={seriesIndex => {
|
|
194
|
-
// For generic UtilizationMetrics, just pass the series index
|
|
195
|
-
if (onUtilizationSeriesSelect != null) {
|
|
196
|
-
onUtilizationSeriesSelect(name, seriesIndex);
|
|
197
|
-
}
|
|
198
|
-
}}
|
|
199
|
-
/>
|
|
200
|
-
);
|
|
201
|
-
}
|
|
202
|
-
return null;
|
|
203
|
-
})}
|
|
204
|
-
{throughputMetrics.length > 0 && (
|
|
205
|
-
<AreaChart
|
|
206
|
-
transmitData={transmitData}
|
|
207
|
-
receiveData={receiveData}
|
|
208
|
-
addLabelMatcher={addLabelMatcher}
|
|
209
|
-
setTimeRange={handleTimeRangeChange}
|
|
210
|
-
name={throughputMetrics[0].name}
|
|
211
|
-
humanReadableName={throughputMetrics[0].humanReadableName}
|
|
212
|
-
from={querySelection.from}
|
|
213
|
-
to={querySelection.to}
|
|
214
|
-
utilizationMetricsLoading={utilizationMetricsLoading}
|
|
215
|
-
selectedSeries={undefined}
|
|
216
|
-
onSeriesClick={(_, seriesIndex) => {
|
|
217
|
-
// For throughput metrics, just pass the series index
|
|
218
|
-
if (onUtilizationSeriesSelect != null) {
|
|
219
|
-
let name = 'gpu_pcie_throughput_transmit_bytes';
|
|
220
|
-
if (seriesIndex > transmitData.length - 1) {
|
|
221
|
-
name = 'gpu_pcie_throughput_receive_bytes';
|
|
222
|
-
seriesIndex -= transmitData.length;
|
|
223
|
-
}
|
|
224
|
-
onUtilizationSeriesSelect(name, seriesIndex);
|
|
225
|
-
}
|
|
226
|
-
}}
|
|
227
|
-
/>
|
|
228
|
-
)}
|
|
229
|
-
</div>
|
|
230
|
-
);
|
|
231
|
-
};
|
|
232
|
-
|
|
233
138
|
return (
|
|
234
139
|
<div className={cx('relative', {'py-4': !showMetricsGraph})}>
|
|
235
140
|
{setDisplayHideMetricsGraphButton != null ? (
|
|
@@ -251,25 +156,19 @@ export function MetricsGraphSection({
|
|
|
251
156
|
querySelection.from !== undefined &&
|
|
252
157
|
querySelection.to !== undefined ? (
|
|
253
158
|
<>
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
setTimeRange={handleTimeRangeChange}
|
|
268
|
-
addLabelMatcher={addLabelMatcher}
|
|
269
|
-
onPointClick={handlePointClick}
|
|
270
|
-
/>
|
|
271
|
-
</>
|
|
272
|
-
)}
|
|
159
|
+
<ProfileMetricsGraph
|
|
160
|
+
queryClient={queryClient}
|
|
161
|
+
queryExpression={querySelection.expression}
|
|
162
|
+
from={querySelection.from}
|
|
163
|
+
to={querySelection.to}
|
|
164
|
+
profile={profileSelection}
|
|
165
|
+
comparing={comparing}
|
|
166
|
+
sumBy={querySelection.sumBy ?? sumBy ?? []}
|
|
167
|
+
sumByLoading={defaultSumByLoading}
|
|
168
|
+
setTimeRange={handleTimeRangeChange}
|
|
169
|
+
addLabelMatcher={addLabelMatcher}
|
|
170
|
+
onPointClick={handlePointClick}
|
|
171
|
+
/>
|
|
273
172
|
</>
|
|
274
173
|
) : (
|
|
275
174
|
profileSelection === null && (
|