@parca/profile 0.19.80 → 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 +8 -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 +24 -33
- 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/ProfileView/components/ProfileFilters/filterPresets.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/filterPresets.js +15 -3
- 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 +37 -102
- package/src/ProfileSelector/MetricsGraphSection.tsx +14 -115
- package/src/ProfileSelector/index.tsx +106 -109
- package/src/ProfileView/components/ProfileFilters/filterPresets.ts +15 -3
- 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,31 +11,23 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useCallback, useEffect,
|
|
14
|
+
import {useCallback, useEffect, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {Icon} from '@iconify/react';
|
|
17
17
|
import {useQueryClient} from '@tanstack/react-query';
|
|
18
18
|
import cx from 'classnames';
|
|
19
19
|
|
|
20
|
-
import {
|
|
21
|
-
import {useGrpcMetadata} from '@parca/components';
|
|
22
|
-
import {Query} from '@parca/parser';
|
|
20
|
+
import {useGrpcMetadata, useParcaContext} from '@parca/components';
|
|
23
21
|
import {TEST_IDS, testId} from '@parca/test-utils';
|
|
24
22
|
import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
|
|
25
23
|
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
24
|
+
import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
|
|
25
|
+
import {transformLabelName} from '../contexts/utils';
|
|
26
|
+
import {useQueryState} from '../hooks/useQueryState';
|
|
28
27
|
import Select, {type SelectItem} from './Select';
|
|
29
28
|
|
|
30
29
|
interface Props {
|
|
31
|
-
queryClient: QueryServiceClient;
|
|
32
|
-
setMatchersString: (arg: string) => void;
|
|
33
|
-
runQuery: () => void;
|
|
34
|
-
currentQuery: Query;
|
|
35
|
-
profileType: string;
|
|
36
30
|
queryBrowserRef: React.RefObject<HTMLDivElement>;
|
|
37
|
-
start?: number;
|
|
38
|
-
end?: number;
|
|
39
31
|
searchExecutedTimestamp?: number;
|
|
40
32
|
}
|
|
41
33
|
|
|
@@ -47,22 +39,12 @@ interface QueryRow {
|
|
|
47
39
|
isLoading: boolean;
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
const trimOtelPrefix = (labelName: string): string => {
|
|
51
|
-
if (labelName.startsWith('attributes_resource.')) {
|
|
52
|
-
return labelName.replace('attributes_resource.', '');
|
|
53
|
-
}
|
|
54
|
-
if (labelName.startsWith('attributes.')) {
|
|
55
|
-
return labelName.replace('attributes.', '');
|
|
56
|
-
}
|
|
57
|
-
return labelName;
|
|
58
|
-
};
|
|
59
|
-
|
|
60
42
|
export const transformLabelsForSelect = (labelNames: string[]): SelectItem[] => {
|
|
61
43
|
return labelNames.map(labelName => ({
|
|
62
44
|
key: labelName,
|
|
63
45
|
element: {
|
|
64
|
-
active: <>{
|
|
65
|
-
expanded: <>{
|
|
46
|
+
active: <>{transformLabelName(labelName)}</>,
|
|
47
|
+
expanded: <>{transformLabelName(labelName)}</>,
|
|
66
48
|
},
|
|
67
49
|
}));
|
|
68
50
|
};
|
|
@@ -115,25 +97,31 @@ const operatorOptions = [
|
|
|
115
97
|
];
|
|
116
98
|
|
|
117
99
|
const SimpleMatchers = ({
|
|
118
|
-
queryClient,
|
|
119
|
-
setMatchersString,
|
|
120
|
-
currentQuery,
|
|
121
|
-
profileType,
|
|
122
100
|
queryBrowserRef,
|
|
123
|
-
|
|
124
|
-
end,
|
|
101
|
+
|
|
125
102
|
searchExecutedTimestamp,
|
|
126
103
|
}: Props): JSX.Element => {
|
|
127
|
-
const utilizationLabels = useUtilizationLabels();
|
|
128
104
|
const [queryRows, setQueryRows] = useState<QueryRow[]>([
|
|
129
105
|
{labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false},
|
|
130
106
|
]);
|
|
131
107
|
const reactQueryClient = useQueryClient();
|
|
132
108
|
const metadata = useGrpcMetadata();
|
|
109
|
+
const {queryServiceClient: parcaQueryClient} = useParcaContext();
|
|
133
110
|
|
|
134
111
|
const [showAll, setShowAll] = useState(false);
|
|
135
112
|
const [isActivelyEditing, setIsActivelyEditing] = useState(false);
|
|
136
113
|
|
|
114
|
+
const {
|
|
115
|
+
labelNameMappingsForSimpleMatchers: labelNameOptions,
|
|
116
|
+
isLabelNamesLoading: labelNamesLoading,
|
|
117
|
+
refetchLabelNames,
|
|
118
|
+
suffix,
|
|
119
|
+
} = useUnifiedLabels();
|
|
120
|
+
|
|
121
|
+
const {draftSelection, setDraftMatchers, draftParsedQuery} = useQueryState({
|
|
122
|
+
suffix,
|
|
123
|
+
});
|
|
124
|
+
|
|
137
125
|
// Reset editing mode when search is executed
|
|
138
126
|
useEffect(() => {
|
|
139
127
|
if (searchExecutedTimestamp !== undefined && searchExecutedTimestamp > 0) {
|
|
@@ -147,18 +135,25 @@ const SimpleMatchers = ({
|
|
|
147
135
|
|
|
148
136
|
const maxWidthInPixels = `max-w-[${queryBrowserRef.current?.offsetWidth.toString() as string}px]`;
|
|
149
137
|
|
|
150
|
-
const currentMatchers =
|
|
138
|
+
const currentMatchers = draftParsedQuery?.matchersString();
|
|
139
|
+
const profileType = draftParsedQuery?.profileType().toString();
|
|
140
|
+
const start = draftSelection.from;
|
|
141
|
+
const end = draftSelection.to;
|
|
151
142
|
|
|
152
143
|
const fetchLabelValues = useCallback(
|
|
153
144
|
async (labelName: string): Promise<string[]> => {
|
|
154
|
-
if (labelName == null || labelName === ''
|
|
145
|
+
if (labelName == null || labelName === '') {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (profileType == null || profileType === '') {
|
|
155
150
|
return [];
|
|
156
151
|
}
|
|
157
152
|
try {
|
|
158
153
|
const values = await reactQueryClient.fetchQuery(
|
|
159
154
|
[labelName, profileType, start, end],
|
|
160
155
|
async () => {
|
|
161
|
-
const response = await
|
|
156
|
+
const response = await parcaQueryClient.values(
|
|
162
157
|
{
|
|
163
158
|
labelName,
|
|
164
159
|
match: [],
|
|
@@ -183,14 +178,7 @@ const SimpleMatchers = ({
|
|
|
183
178
|
return [];
|
|
184
179
|
}
|
|
185
180
|
},
|
|
186
|
-
[
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const fetchLabelValuesUtilization = useCallback(
|
|
190
|
-
async (labelName: string): Promise<string[]> => {
|
|
191
|
-
return (await utilizationLabels?.utilizationFetchLabelValues?.(labelName)) ?? [];
|
|
192
|
-
},
|
|
193
|
-
[utilizationLabels]
|
|
181
|
+
[parcaQueryClient, metadata, profileType, reactQueryClient, start, end]
|
|
194
182
|
);
|
|
195
183
|
|
|
196
184
|
const updateMatchersString = useCallback(
|
|
@@ -199,18 +187,11 @@ const SimpleMatchers = ({
|
|
|
199
187
|
.filter(row => row.labelName.length > 0 && row.labelValue)
|
|
200
188
|
.map(row => `${row.labelName}${row.operator}"${row.labelValue}"`)
|
|
201
189
|
.join(',');
|
|
202
|
-
|
|
190
|
+
setDraftMatchers(matcherString);
|
|
203
191
|
},
|
|
204
|
-
[
|
|
192
|
+
[setDraftMatchers]
|
|
205
193
|
);
|
|
206
194
|
|
|
207
|
-
const {
|
|
208
|
-
labelNameOptions,
|
|
209
|
-
isLoading: labelNamesLoading,
|
|
210
|
-
refetchLabelValues,
|
|
211
|
-
refetchLabelNames,
|
|
212
|
-
} = useLabels();
|
|
213
|
-
|
|
214
195
|
// Helper to ensure selected label name is in the options (for page load before API returns)
|
|
215
196
|
const getLabelNameOptionsWithSelected = useCallback(
|
|
216
197
|
(selectedLabelName: string): typeof labelNameOptions => {
|
|
@@ -276,20 +257,13 @@ const SimpleMatchers = ({
|
|
|
276
257
|
|
|
277
258
|
const fetchLabelValuesUnified = useCallback(
|
|
278
259
|
async (labelName: string): Promise<string[]> => {
|
|
279
|
-
|
|
280
|
-
option.values.some(e => e.key === labelName)
|
|
281
|
-
)?.type;
|
|
282
|
-
const labelValues =
|
|
283
|
-
labelType === 'gpu'
|
|
284
|
-
? await fetchLabelValuesUtilization(labelName)
|
|
285
|
-
: await fetchLabelValues(labelName);
|
|
286
|
-
return labelValues;
|
|
260
|
+
return await fetchLabelValues(labelName);
|
|
287
261
|
},
|
|
288
|
-
[fetchLabelValues
|
|
262
|
+
[fetchLabelValues]
|
|
289
263
|
);
|
|
290
264
|
|
|
291
265
|
useEffect(() => {
|
|
292
|
-
if (currentMatchers === '') {
|
|
266
|
+
if (currentMatchers === '' || currentMatchers === undefined) {
|
|
293
267
|
const defaultRow = {
|
|
294
268
|
labelName: '',
|
|
295
269
|
operator: '=',
|
|
@@ -306,7 +280,7 @@ const SimpleMatchers = ({
|
|
|
306
280
|
const fetchAndSetQueryRows = async (): Promise<void> => {
|
|
307
281
|
const newRows = await Promise.all(
|
|
308
282
|
currentMatchers.split(',').map(async matcher => {
|
|
309
|
-
const match = matcher.match(
|
|
283
|
+
const match = matcher.match(/^([^=!~]+)([=!~]{1,2})(.+)$/);
|
|
310
284
|
if (match === null) return null;
|
|
311
285
|
|
|
312
286
|
const [, labelName, operator, labelValue] = match;
|
|
@@ -440,6 +414,13 @@ const SimpleMatchers = ({
|
|
|
440
414
|
|
|
441
415
|
const isRowRegex = (row: QueryRow): boolean => row.operator === '=~' || row.operator === '!~';
|
|
442
416
|
|
|
417
|
+
const handleRefetchForLabelValues = useCallback(
|
|
418
|
+
async (labelName: string): Promise<void> => {
|
|
419
|
+
await fetchLabelValuesUnified(labelName);
|
|
420
|
+
},
|
|
421
|
+
[fetchLabelValuesUnified]
|
|
422
|
+
);
|
|
423
|
+
|
|
443
424
|
return (
|
|
444
425
|
<div
|
|
445
426
|
className={`flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`}
|
|
@@ -485,7 +466,7 @@ const SimpleMatchers = ({
|
|
|
485
466
|
onButtonClick={() => handleLabelValueClick(index)}
|
|
486
467
|
editable={isRowRegex(row)}
|
|
487
468
|
{...testId(TEST_IDS.LABEL_VALUE_SELECT)}
|
|
488
|
-
refetchValues={async () => await
|
|
469
|
+
refetchValues={async () => await handleRefetchForLabelValues(row.labelName)}
|
|
489
470
|
showLoadingInButton={true}
|
|
490
471
|
/>
|
|
491
472
|
<button
|
|
@@ -530,24 +511,4 @@ const SimpleMatchers = ({
|
|
|
530
511
|
);
|
|
531
512
|
};
|
|
532
513
|
|
|
533
|
-
export default
|
|
534
|
-
const labelNameFromMatchers = useMemo(() => {
|
|
535
|
-
if (props.currentQuery === undefined) return [];
|
|
536
|
-
|
|
537
|
-
const matchers = props.currentQuery.matchers;
|
|
538
|
-
|
|
539
|
-
return matchers.map(matcher => matcher.key);
|
|
540
|
-
}, [props.currentQuery]);
|
|
541
|
-
|
|
542
|
-
return (
|
|
543
|
-
<LabelProvider
|
|
544
|
-
queryClient={props.queryClient}
|
|
545
|
-
profileType={props.profileType}
|
|
546
|
-
labelNameFromMatchers={labelNameFromMatchers}
|
|
547
|
-
start={props.start}
|
|
548
|
-
end={props.end}
|
|
549
|
-
>
|
|
550
|
-
<SimpleMatchers {...props} />
|
|
551
|
-
</LabelProvider>
|
|
552
|
-
);
|
|
553
|
-
}
|
|
514
|
+
export default SimpleMatchers;
|
|
@@ -16,40 +16,32 @@ import React, {useCallback, useEffect, useRef, useState} from 'react';
|
|
|
16
16
|
import {Icon} from '@iconify/react';
|
|
17
17
|
import cx from 'classnames';
|
|
18
18
|
|
|
19
|
-
import {
|
|
20
|
-
import {useGrpcMetadata} from '@parca/components';
|
|
21
|
-
import {Query} from '@parca/parser';
|
|
19
|
+
import {useGrpcMetadata, useParcaContext} from '@parca/components';
|
|
22
20
|
import {TEST_IDS, testId} from '@parca/test-utils';
|
|
23
21
|
import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
|
|
24
22
|
|
|
25
23
|
import CustomSelect, {SelectItem} from '../SimpleMatchers/Select';
|
|
24
|
+
import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
|
|
25
|
+
import {useQueryState} from '../hooks/useQueryState';
|
|
26
26
|
|
|
27
27
|
interface Props {
|
|
28
28
|
labelNames: string[];
|
|
29
|
-
profileType: string;
|
|
30
|
-
runQuery: () => void;
|
|
31
|
-
currentQuery: Query;
|
|
32
|
-
queryClient: QueryServiceClient;
|
|
33
|
-
setMatchersString: (arg: string) => void;
|
|
34
|
-
start?: number;
|
|
35
|
-
end?: number;
|
|
36
29
|
}
|
|
37
30
|
|
|
38
|
-
const ViewMatchers: React.FC<Props> = ({
|
|
39
|
-
labelNames,
|
|
40
|
-
profileType,
|
|
41
|
-
queryClient,
|
|
42
|
-
runQuery,
|
|
43
|
-
setMatchersString,
|
|
44
|
-
start,
|
|
45
|
-
end,
|
|
46
|
-
currentQuery,
|
|
47
|
-
}) => {
|
|
31
|
+
const ViewMatchers: React.FC<Props> = ({labelNames}) => {
|
|
48
32
|
const [labelValuesMap, setLabelValuesMap] = useState<Record<string, string[]>>({});
|
|
49
33
|
const [isLoading, setIsLoading] = useState<Record<string, boolean>>({});
|
|
50
34
|
const metadata = useGrpcMetadata();
|
|
35
|
+
const {queryServiceClient: parcaQueryClient} = useParcaContext();
|
|
51
36
|
|
|
52
|
-
const
|
|
37
|
+
const {suffix} = useUnifiedLabels();
|
|
38
|
+
|
|
39
|
+
const {draftSelection, setDraftMatchers, commitDraft, draftParsedQuery} = useQueryState({suffix});
|
|
40
|
+
|
|
41
|
+
const currentMatchers = draftParsedQuery?.matchersString();
|
|
42
|
+
const profileType = draftParsedQuery?.profileType().toString();
|
|
43
|
+
const start = draftSelection.from;
|
|
44
|
+
const end = draftSelection.to;
|
|
53
45
|
|
|
54
46
|
const parseCurrentMatchers = useCallback((matchersString: string): Record<string, string> => {
|
|
55
47
|
const matches = matchersString.match(/(\w+)="([^"]+)"/g);
|
|
@@ -68,15 +60,15 @@ const ViewMatchers: React.FC<Props> = ({
|
|
|
68
60
|
);
|
|
69
61
|
}, []);
|
|
70
62
|
|
|
71
|
-
const initialSelections = parseCurrentMatchers(currentMatchers);
|
|
63
|
+
const initialSelections = parseCurrentMatchers(currentMatchers ?? '');
|
|
72
64
|
const selectionsRef = useRef<Record<string, string | null>>(initialSelections);
|
|
73
65
|
|
|
74
|
-
const
|
|
66
|
+
const commitDraftRef = useRef(commitDraft);
|
|
75
67
|
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
|
76
68
|
|
|
77
69
|
useEffect(() => {
|
|
78
|
-
|
|
79
|
-
}, [
|
|
70
|
+
commitDraftRef.current = commitDraft;
|
|
71
|
+
}, [commitDraft]);
|
|
80
72
|
|
|
81
73
|
useEffect(() => {
|
|
82
74
|
selectionsRef.current = initialSelections;
|
|
@@ -85,7 +77,7 @@ const ViewMatchers: React.FC<Props> = ({
|
|
|
85
77
|
const fetchLabelValues = useCallback(
|
|
86
78
|
async (labelName: string): Promise<string[]> => {
|
|
87
79
|
try {
|
|
88
|
-
const response = await
|
|
80
|
+
const response = await parcaQueryClient.values(
|
|
89
81
|
{
|
|
90
82
|
labelName,
|
|
91
83
|
match: [],
|
|
@@ -105,7 +97,7 @@ const ViewMatchers: React.FC<Props> = ({
|
|
|
105
97
|
return [];
|
|
106
98
|
}
|
|
107
99
|
},
|
|
108
|
-
[
|
|
100
|
+
[parcaQueryClient, metadata, profileType, start, end]
|
|
109
101
|
);
|
|
110
102
|
|
|
111
103
|
const fetchAllLabelValues = useCallback(async (): Promise<void> => {
|
|
@@ -135,16 +127,16 @@ const ViewMatchers: React.FC<Props> = ({
|
|
|
135
127
|
.map(([ln, v]) => `${ln}="${v as string}"`);
|
|
136
128
|
|
|
137
129
|
const matcherString = matcherParts.join(',');
|
|
138
|
-
|
|
130
|
+
setDraftMatchers(matcherString);
|
|
139
131
|
|
|
140
132
|
if (timeoutRef.current !== null) {
|
|
141
133
|
clearTimeout(timeoutRef.current);
|
|
142
134
|
}
|
|
143
135
|
|
|
144
136
|
timeoutRef.current = setTimeout(() => {
|
|
145
|
-
|
|
137
|
+
commitDraftRef.current();
|
|
146
138
|
}, 300);
|
|
147
|
-
}, [
|
|
139
|
+
}, [setDraftMatchers]);
|
|
148
140
|
|
|
149
141
|
const handleSelection = useCallback(
|
|
150
142
|
(labelName: string, value: string | null): void => {
|
|
@@ -0,0 +1,142 @@
|
|
|
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
|
+
/**
|
|
15
|
+
* LabelsQueryProvider - Data Fetching Layer
|
|
16
|
+
*
|
|
17
|
+
* This context provider is responsible for fetching label data from the Parca API
|
|
18
|
+
* and making it available to child components through React Context.
|
|
19
|
+
*
|
|
20
|
+
* Purpose:
|
|
21
|
+
* - Fetches label names and values from the Parca profiling API
|
|
22
|
+
* - Manages loading states for label data
|
|
23
|
+
* - Provides refetch functions for manual data refresh
|
|
24
|
+
* - Acts as the primary data source in the label provider architecture
|
|
25
|
+
*
|
|
26
|
+
* Architecture Pattern:
|
|
27
|
+
* This is the first layer in a three-layer architecture:
|
|
28
|
+
* 1. LabelsQueryProvider (this file) - Fetches data from API
|
|
29
|
+
* 2. LabelsSource (in ProfileSelector) - Transforms/merges data
|
|
30
|
+
* 3. UnifiedLabelsProvider - Provides unified interface to UI components
|
|
31
|
+
*
|
|
32
|
+
* Consumer Hook: useLabelsQueryProvider()
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import {createContext, useContext, useState} from 'react';
|
|
36
|
+
|
|
37
|
+
import {QueryServiceClient} from '@parca/client';
|
|
38
|
+
import {Query} from '@parca/parser';
|
|
39
|
+
|
|
40
|
+
import {useLabelNames, useLabelValues} from '../hooks/useLabels';
|
|
41
|
+
import {useExtractedLabelNames} from './utils';
|
|
42
|
+
|
|
43
|
+
export interface LabelsQueryProviderContextType {
|
|
44
|
+
isLabelNamesLoading: boolean;
|
|
45
|
+
isLabelValuesLoading: boolean;
|
|
46
|
+
currentLabelName: string | null;
|
|
47
|
+
setCurrentLabelName: (name: string | null) => void;
|
|
48
|
+
refetchLabelValues: () => Promise<void>;
|
|
49
|
+
refetchLabelNames: () => Promise<void>;
|
|
50
|
+
|
|
51
|
+
labelNames: string[];
|
|
52
|
+
labelValues: string[];
|
|
53
|
+
|
|
54
|
+
queryClient: QueryServiceClient;
|
|
55
|
+
setMatchersString: (arg: string) => void;
|
|
56
|
+
runQuery: () => void;
|
|
57
|
+
currentQuery: Query;
|
|
58
|
+
profileType: string;
|
|
59
|
+
start?: number;
|
|
60
|
+
end?: number;
|
|
61
|
+
|
|
62
|
+
suffix?: '_a' | '_b';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const LabelsQueryProviderContext = createContext<LabelsQueryProviderContextType | null>(null);
|
|
66
|
+
|
|
67
|
+
interface LabelsQueryProviderProps {
|
|
68
|
+
children: React.ReactNode;
|
|
69
|
+
|
|
70
|
+
queryClient: QueryServiceClient;
|
|
71
|
+
setMatchersString: (arg: string) => void;
|
|
72
|
+
runQuery: () => void;
|
|
73
|
+
currentQuery: Query;
|
|
74
|
+
profileType: string;
|
|
75
|
+
start?: number;
|
|
76
|
+
end?: number;
|
|
77
|
+
|
|
78
|
+
suffix?: '_a' | '_b';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function LabelsQueryProvider({
|
|
82
|
+
children,
|
|
83
|
+
queryClient,
|
|
84
|
+
setMatchersString,
|
|
85
|
+
runQuery,
|
|
86
|
+
currentQuery,
|
|
87
|
+
profileType,
|
|
88
|
+
start,
|
|
89
|
+
end,
|
|
90
|
+
suffix,
|
|
91
|
+
}: LabelsQueryProviderProps): JSX.Element {
|
|
92
|
+
const [currentLabelName, setCurrentLabelName] = useState<string | null>(null);
|
|
93
|
+
|
|
94
|
+
const {
|
|
95
|
+
result: labelNamesResponse,
|
|
96
|
+
loading: isLabelNamesLoading,
|
|
97
|
+
refetch: labelNamesRefetch,
|
|
98
|
+
} = useLabelNames(queryClient, profileType, start, end);
|
|
99
|
+
|
|
100
|
+
const labelNames = useExtractedLabelNames(labelNamesResponse.response, labelNamesResponse.error);
|
|
101
|
+
|
|
102
|
+
const {
|
|
103
|
+
result: labelValuesOriginal,
|
|
104
|
+
loading: isLabelValuesLoading,
|
|
105
|
+
refetch: labelValuesRefetch,
|
|
106
|
+
} = useLabelValues(queryClient, currentLabelName ?? '', profileType, start, end);
|
|
107
|
+
|
|
108
|
+
const labelValues = labelValuesOriginal.response;
|
|
109
|
+
|
|
110
|
+
const value = {
|
|
111
|
+
labelNames,
|
|
112
|
+
labelValues,
|
|
113
|
+
isLabelNamesLoading,
|
|
114
|
+
isLabelValuesLoading,
|
|
115
|
+
refetchLabelValues: labelValuesRefetch,
|
|
116
|
+
refetchLabelNames: labelNamesRefetch,
|
|
117
|
+
queryClient,
|
|
118
|
+
setMatchersString,
|
|
119
|
+
runQuery,
|
|
120
|
+
currentQuery,
|
|
121
|
+
profileType,
|
|
122
|
+
start,
|
|
123
|
+
end,
|
|
124
|
+
setCurrentLabelName,
|
|
125
|
+
currentLabelName,
|
|
126
|
+
suffix,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<LabelsQueryProviderContext.Provider value={value}>
|
|
131
|
+
{children}
|
|
132
|
+
</LabelsQueryProviderContext.Provider>
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function useLabelsQueryProvider(): LabelsQueryProviderContextType {
|
|
137
|
+
const context = useContext(LabelsQueryProviderContext);
|
|
138
|
+
if (context === null) {
|
|
139
|
+
throw new Error('useLabelsQueryProvider must be used within a LabelsQueryProvider');
|
|
140
|
+
}
|
|
141
|
+
return context;
|
|
142
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
/**
|
|
15
|
+
* UnifiedLabelsProvider - UI Presentation Layer
|
|
16
|
+
*
|
|
17
|
+
* This context provider transforms raw label data into a format optimized for
|
|
18
|
+
* UI components (QueryControls, SimpleMatchers, etc.).
|
|
19
|
+
*
|
|
20
|
+
* Purpose:
|
|
21
|
+
* - Transforms label arrays into structured formats for dropdowns and selectors
|
|
22
|
+
* - Groups labels by type (e.g., 'cpu', 'gpu') for organized display
|
|
23
|
+
* - Handles label name prefixes and mappings for user-friendly display
|
|
24
|
+
* - Provides a unified interface regardless of data source(s)
|
|
25
|
+
*
|
|
26
|
+
* Architecture Pattern:
|
|
27
|
+
* This is the final layer in a three-layer architecture:
|
|
28
|
+
* 1. LabelsQueryProvider - Fetches data from API
|
|
29
|
+
* 2. LabelsSource (in ProfileSelector) - Transforms/merges data
|
|
30
|
+
* 3. UnifiedLabelsProvider (this file) - Presents data to UI components
|
|
31
|
+
*
|
|
32
|
+
* Consumer Hook: useUnifiedLabels()
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
import {createContext, useContext} from 'react';
|
|
36
|
+
|
|
37
|
+
import {transformLabelsForSelect} from '../SimpleMatchers';
|
|
38
|
+
import type {SelectItem} from '../SimpleMatchers/Select';
|
|
39
|
+
import {useLabelNameMappings, type LabelNameMapping} from './utils';
|
|
40
|
+
|
|
41
|
+
interface LabelNameSection {
|
|
42
|
+
type: string;
|
|
43
|
+
values: SelectItem[];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface UnifiedLabelsContextType {
|
|
47
|
+
labelNameMappingsForMatchersInput: LabelNameMapping[];
|
|
48
|
+
labelNameMappingsForSimpleMatchers: LabelNameSection[];
|
|
49
|
+
labelNames: string[];
|
|
50
|
+
labelValues: string[];
|
|
51
|
+
|
|
52
|
+
isLabelNamesLoading: boolean;
|
|
53
|
+
isLabelValuesLoading: boolean;
|
|
54
|
+
currentLabelName: string | null;
|
|
55
|
+
setCurrentLabelName: (name: string | null) => void;
|
|
56
|
+
shouldHandlePrefixes: boolean;
|
|
57
|
+
refetchLabelValues: () => Promise<void>;
|
|
58
|
+
refetchLabelNames: () => Promise<void>;
|
|
59
|
+
labelNameFromMatchers: string[];
|
|
60
|
+
|
|
61
|
+
suffix?: '_a' | '_b';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const UnifiedLabelsContext = createContext<UnifiedLabelsContextType | null>(null);
|
|
65
|
+
|
|
66
|
+
interface UnifiedLabelsProviderProps {
|
|
67
|
+
children: React.ReactNode;
|
|
68
|
+
|
|
69
|
+
currentLabelName: string | null;
|
|
70
|
+
setCurrentLabelName: (name: string | null) => void;
|
|
71
|
+
|
|
72
|
+
labelNames: string[];
|
|
73
|
+
labelValues: string[];
|
|
74
|
+
isLabelNamesLoading: boolean;
|
|
75
|
+
isLabelValuesLoading: boolean;
|
|
76
|
+
|
|
77
|
+
refetchLabelValues: () => Promise<void>;
|
|
78
|
+
refetchLabelNames: () => Promise<void>;
|
|
79
|
+
|
|
80
|
+
suffix?: '_a' | '_b';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function UnifiedLabelsProvider({
|
|
84
|
+
children,
|
|
85
|
+
labelNames,
|
|
86
|
+
isLabelNamesLoading,
|
|
87
|
+
isLabelValuesLoading,
|
|
88
|
+
refetchLabelValues,
|
|
89
|
+
refetchLabelNames,
|
|
90
|
+
currentLabelName,
|
|
91
|
+
setCurrentLabelName,
|
|
92
|
+
labelValues,
|
|
93
|
+
suffix,
|
|
94
|
+
}: UnifiedLabelsProviderProps): JSX.Element {
|
|
95
|
+
const labelNameFromMatchers: string[] = [];
|
|
96
|
+
|
|
97
|
+
const labelNamesFromAPI = labelNames;
|
|
98
|
+
|
|
99
|
+
const labelNameMappingsForMatchersInput = useLabelNameMappings(labelNamesFromAPI);
|
|
100
|
+
|
|
101
|
+
const allLabelNames = new Set(labelNamesFromAPI);
|
|
102
|
+
|
|
103
|
+
const nonMatchingLabels = labelNameFromMatchers.filter(label => !allLabelNames.has(label));
|
|
104
|
+
|
|
105
|
+
const labelNameMappingsForSimpleMatchers: LabelNameSection[] = [];
|
|
106
|
+
|
|
107
|
+
const labels = {
|
|
108
|
+
type: 'cpu',
|
|
109
|
+
labelNames: labelNamesFromAPI,
|
|
110
|
+
isLoading: isLabelNamesLoading,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
labelNameMappingsForSimpleMatchers.push({
|
|
114
|
+
type: labels.type,
|
|
115
|
+
values: transformLabelsForSelect(labels.labelNames),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (nonMatchingLabels.length > 0) {
|
|
119
|
+
const uniqueNonMatchingLabels = Array.from(new Set(nonMatchingLabels));
|
|
120
|
+
labelNameMappingsForSimpleMatchers.push({
|
|
121
|
+
type: '',
|
|
122
|
+
values: transformLabelsForSelect(uniqueNonMatchingLabels),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const value = {
|
|
127
|
+
labelNames: labelNamesFromAPI,
|
|
128
|
+
labelNameMappingsForMatchersInput,
|
|
129
|
+
isLabelNamesLoading,
|
|
130
|
+
isLabelValuesLoading,
|
|
131
|
+
currentLabelName,
|
|
132
|
+
labelValues,
|
|
133
|
+
setCurrentLabelName,
|
|
134
|
+
shouldHandlePrefixes: false,
|
|
135
|
+
refetchLabelValues: async () => {
|
|
136
|
+
await refetchLabelValues();
|
|
137
|
+
},
|
|
138
|
+
refetchLabelNames: async () => {
|
|
139
|
+
await refetchLabelNames();
|
|
140
|
+
},
|
|
141
|
+
labelNameFromMatchers,
|
|
142
|
+
labelNameMappingsForSimpleMatchers,
|
|
143
|
+
suffix,
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
return <UnifiedLabelsContext.Provider value={value}>{children}</UnifiedLabelsContext.Provider>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export function useUnifiedLabels(): UnifiedLabelsContextType {
|
|
150
|
+
const context = useContext(UnifiedLabelsContext);
|
|
151
|
+
if (context === null) {
|
|
152
|
+
throw new Error('useUnifiedLabels must be used within a UnifiedLabelsProvider');
|
|
153
|
+
}
|
|
154
|
+
return context;
|
|
155
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
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 {useMemo} from 'react';
|
|
15
|
+
|
|
16
|
+
export interface LabelNameMapping {
|
|
17
|
+
displayName: string;
|
|
18
|
+
fullName: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const transformLabelName = (labelName: string): string => {
|
|
22
|
+
return labelName.replace(/^(attributes\.|attributes_resource\.)/, '');
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const useLabelNameMappings = (labelNames: string[]): LabelNameMapping[] => {
|
|
26
|
+
return useMemo(() => {
|
|
27
|
+
return labelNames.map(name => ({
|
|
28
|
+
displayName: transformLabelName(name),
|
|
29
|
+
fullName: name,
|
|
30
|
+
}));
|
|
31
|
+
}, [labelNames]);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const useExtractedLabelNames = (
|
|
35
|
+
response: {labelNames?: string[]} | undefined,
|
|
36
|
+
error: Error | undefined | null
|
|
37
|
+
): string[] => {
|
|
38
|
+
return useMemo(() => {
|
|
39
|
+
return (error === undefined || error == null) && response !== undefined && response != null
|
|
40
|
+
? response.labelNames?.filter(e => e !== '__name__') ?? []
|
|
41
|
+
: [];
|
|
42
|
+
}, [response, error]);
|
|
43
|
+
};
|