@parca/profile 0.19.83 → 0.19.85
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 +6 -29
- package/dist/ProfileSelector/MetricsGraphSection.d.ts +8 -10
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +8 -41
- package/dist/ProfileSelector/index.d.ts +1 -29
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +14 -10
- package/dist/QueryControls/index.d.ts +46 -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 +6 -10
- package/dist/SimpleMatchers/index.d.ts.map +1 -1
- package/dist/SimpleMatchers/index.js +30 -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 +3 -1
- package/dist/hooks/useQueryState.d.ts.map +1 -1
- package/dist/hooks/useQueryState.js +19 -12
- 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 +25 -119
- package/src/ProfileSelector/index.tsx +115 -109
- package/src/{ProfileSelector/QueryControls.tsx → QueryControls/index.tsx} +76 -84
- package/src/SimpleMatchers/Select.tsx +1 -1
- package/src/SimpleMatchers/index.tsx +46 -84
- 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 +27 -16
- 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,32 +11,28 @@
|
|
|
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';
|
|
20
|
+
import {useGrpcMetadata, useParcaContext} from '@parca/components';
|
|
22
21
|
import {Query} from '@parca/parser';
|
|
23
22
|
import {TEST_IDS, testId} from '@parca/test-utils';
|
|
24
23
|
import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
|
|
25
24
|
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
25
|
+
import {QuerySelection} from '../ProfileSelector';
|
|
26
|
+
import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
|
|
27
|
+
import {transformLabelName} from '../contexts/utils';
|
|
28
28
|
import Select, {type SelectItem} from './Select';
|
|
29
29
|
|
|
30
30
|
interface Props {
|
|
31
|
-
queryClient: QueryServiceClient;
|
|
32
|
-
setMatchersString: (arg: string) => void;
|
|
33
|
-
runQuery: () => void;
|
|
34
|
-
currentQuery: Query;
|
|
35
|
-
profileType: string;
|
|
36
31
|
queryBrowserRef: React.RefObject<HTMLDivElement>;
|
|
37
|
-
start?: number;
|
|
38
|
-
end?: number;
|
|
39
32
|
searchExecutedTimestamp?: number;
|
|
33
|
+
draftSelection: QuerySelection;
|
|
34
|
+
setDraftMatchers: (selection: string) => void;
|
|
35
|
+
draftParsedQuery?: Query | null;
|
|
40
36
|
}
|
|
41
37
|
|
|
42
38
|
interface QueryRow {
|
|
@@ -47,22 +43,12 @@ interface QueryRow {
|
|
|
47
43
|
isLoading: boolean;
|
|
48
44
|
}
|
|
49
45
|
|
|
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
46
|
export const transformLabelsForSelect = (labelNames: string[]): SelectItem[] => {
|
|
61
47
|
return labelNames.map(labelName => ({
|
|
62
48
|
key: labelName,
|
|
63
49
|
element: {
|
|
64
|
-
active: <>{
|
|
65
|
-
expanded: <>{
|
|
50
|
+
active: <>{transformLabelName(labelName)}</>,
|
|
51
|
+
expanded: <>{transformLabelName(labelName)}</>,
|
|
66
52
|
},
|
|
67
53
|
}));
|
|
68
54
|
};
|
|
@@ -115,25 +101,28 @@ const operatorOptions = [
|
|
|
115
101
|
];
|
|
116
102
|
|
|
117
103
|
const SimpleMatchers = ({
|
|
118
|
-
queryClient,
|
|
119
|
-
setMatchersString,
|
|
120
|
-
currentQuery,
|
|
121
|
-
profileType,
|
|
122
104
|
queryBrowserRef,
|
|
123
|
-
start,
|
|
124
|
-
end,
|
|
125
105
|
searchExecutedTimestamp,
|
|
106
|
+
draftSelection,
|
|
107
|
+
setDraftMatchers,
|
|
108
|
+
draftParsedQuery,
|
|
126
109
|
}: Props): JSX.Element => {
|
|
127
|
-
const utilizationLabels = useUtilizationLabels();
|
|
128
110
|
const [queryRows, setQueryRows] = useState<QueryRow[]>([
|
|
129
111
|
{labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false},
|
|
130
112
|
]);
|
|
131
113
|
const reactQueryClient = useQueryClient();
|
|
132
114
|
const metadata = useGrpcMetadata();
|
|
115
|
+
const {queryServiceClient: parcaQueryClient} = useParcaContext();
|
|
133
116
|
|
|
134
117
|
const [showAll, setShowAll] = useState(false);
|
|
135
118
|
const [isActivelyEditing, setIsActivelyEditing] = useState(false);
|
|
136
119
|
|
|
120
|
+
const {
|
|
121
|
+
labelNameMappingsForSimpleMatchers: labelNameOptions,
|
|
122
|
+
isLabelNamesLoading: labelNamesLoading,
|
|
123
|
+
refetchLabelNames,
|
|
124
|
+
} = useUnifiedLabels();
|
|
125
|
+
|
|
137
126
|
// Reset editing mode when search is executed
|
|
138
127
|
useEffect(() => {
|
|
139
128
|
if (searchExecutedTimestamp !== undefined && searchExecutedTimestamp > 0) {
|
|
@@ -147,18 +136,25 @@ const SimpleMatchers = ({
|
|
|
147
136
|
|
|
148
137
|
const maxWidthInPixels = `max-w-[${queryBrowserRef.current?.offsetWidth.toString() as string}px]`;
|
|
149
138
|
|
|
150
|
-
const currentMatchers =
|
|
139
|
+
const currentMatchers = draftParsedQuery?.matchersString();
|
|
140
|
+
const profileType = draftParsedQuery?.profileType().toString();
|
|
141
|
+
const start = draftSelection.from;
|
|
142
|
+
const end = draftSelection.to;
|
|
151
143
|
|
|
152
144
|
const fetchLabelValues = useCallback(
|
|
153
145
|
async (labelName: string): Promise<string[]> => {
|
|
154
|
-
if (labelName == null || labelName === ''
|
|
146
|
+
if (labelName == null || labelName === '') {
|
|
147
|
+
return [];
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (profileType == null || profileType === '') {
|
|
155
151
|
return [];
|
|
156
152
|
}
|
|
157
153
|
try {
|
|
158
154
|
const values = await reactQueryClient.fetchQuery(
|
|
159
155
|
[labelName, profileType, start, end],
|
|
160
156
|
async () => {
|
|
161
|
-
const response = await
|
|
157
|
+
const response = await parcaQueryClient.values(
|
|
162
158
|
{
|
|
163
159
|
labelName,
|
|
164
160
|
match: [],
|
|
@@ -183,14 +179,7 @@ const SimpleMatchers = ({
|
|
|
183
179
|
return [];
|
|
184
180
|
}
|
|
185
181
|
},
|
|
186
|
-
[
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
const fetchLabelValuesUtilization = useCallback(
|
|
190
|
-
async (labelName: string): Promise<string[]> => {
|
|
191
|
-
return (await utilizationLabels?.utilizationFetchLabelValues?.(labelName)) ?? [];
|
|
192
|
-
},
|
|
193
|
-
[utilizationLabels]
|
|
182
|
+
[parcaQueryClient, metadata, profileType, reactQueryClient, start, end]
|
|
194
183
|
);
|
|
195
184
|
|
|
196
185
|
const updateMatchersString = useCallback(
|
|
@@ -199,18 +188,11 @@ const SimpleMatchers = ({
|
|
|
199
188
|
.filter(row => row.labelName.length > 0 && row.labelValue)
|
|
200
189
|
.map(row => `${row.labelName}${row.operator}"${row.labelValue}"`)
|
|
201
190
|
.join(',');
|
|
202
|
-
|
|
191
|
+
setDraftMatchers(matcherString);
|
|
203
192
|
},
|
|
204
|
-
[
|
|
193
|
+
[setDraftMatchers]
|
|
205
194
|
);
|
|
206
195
|
|
|
207
|
-
const {
|
|
208
|
-
labelNameOptions,
|
|
209
|
-
isLoading: labelNamesLoading,
|
|
210
|
-
refetchLabelValues,
|
|
211
|
-
refetchLabelNames,
|
|
212
|
-
} = useLabels();
|
|
213
|
-
|
|
214
196
|
// Helper to ensure selected label name is in the options (for page load before API returns)
|
|
215
197
|
const getLabelNameOptionsWithSelected = useCallback(
|
|
216
198
|
(selectedLabelName: string): typeof labelNameOptions => {
|
|
@@ -276,20 +258,13 @@ const SimpleMatchers = ({
|
|
|
276
258
|
|
|
277
259
|
const fetchLabelValuesUnified = useCallback(
|
|
278
260
|
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;
|
|
261
|
+
return await fetchLabelValues(labelName);
|
|
287
262
|
},
|
|
288
|
-
[fetchLabelValues
|
|
263
|
+
[fetchLabelValues]
|
|
289
264
|
);
|
|
290
265
|
|
|
291
266
|
useEffect(() => {
|
|
292
|
-
if (currentMatchers === '') {
|
|
267
|
+
if (currentMatchers === '' || currentMatchers === undefined) {
|
|
293
268
|
const defaultRow = {
|
|
294
269
|
labelName: '',
|
|
295
270
|
operator: '=',
|
|
@@ -306,7 +281,7 @@ const SimpleMatchers = ({
|
|
|
306
281
|
const fetchAndSetQueryRows = async (): Promise<void> => {
|
|
307
282
|
const newRows = await Promise.all(
|
|
308
283
|
currentMatchers.split(',').map(async matcher => {
|
|
309
|
-
const match = matcher.match(
|
|
284
|
+
const match = matcher.match(/^([^=!~]+)([=!~]{1,2})(.+)$/);
|
|
310
285
|
if (match === null) return null;
|
|
311
286
|
|
|
312
287
|
const [, labelName, operator, labelValue] = match;
|
|
@@ -440,6 +415,13 @@ const SimpleMatchers = ({
|
|
|
440
415
|
|
|
441
416
|
const isRowRegex = (row: QueryRow): boolean => row.operator === '=~' || row.operator === '!~';
|
|
442
417
|
|
|
418
|
+
const handleRefetchForLabelValues = useCallback(
|
|
419
|
+
async (labelName: string): Promise<void> => {
|
|
420
|
+
await fetchLabelValuesUnified(labelName);
|
|
421
|
+
},
|
|
422
|
+
[fetchLabelValuesUnified]
|
|
423
|
+
);
|
|
424
|
+
|
|
443
425
|
return (
|
|
444
426
|
<div
|
|
445
427
|
className={`flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`}
|
|
@@ -485,7 +467,7 @@ const SimpleMatchers = ({
|
|
|
485
467
|
onButtonClick={() => handleLabelValueClick(index)}
|
|
486
468
|
editable={isRowRegex(row)}
|
|
487
469
|
{...testId(TEST_IDS.LABEL_VALUE_SELECT)}
|
|
488
|
-
refetchValues={async () => await
|
|
470
|
+
refetchValues={async () => await handleRefetchForLabelValues(row.labelName)}
|
|
489
471
|
showLoadingInButton={true}
|
|
490
472
|
/>
|
|
491
473
|
<button
|
|
@@ -530,24 +512,4 @@ const SimpleMatchers = ({
|
|
|
530
512
|
);
|
|
531
513
|
};
|
|
532
514
|
|
|
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
|
-
}
|
|
515
|
+
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
|
+
};
|