@parca/profile 0.19.82 → 0.19.83

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.
Files changed (83) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/MatchersInput/index.d.ts +34 -2
  3. package/dist/MatchersInput/index.d.ts.map +1 -1
  4. package/dist/MatchersInput/index.js +91 -14
  5. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts +29 -0
  6. package/dist/MetricsGraph/UtilizationMetrics/Throughput.d.ts.map +1 -0
  7. package/dist/MetricsGraph/UtilizationMetrics/Throughput.js +175 -0
  8. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts +28 -0
  9. package/dist/MetricsGraph/UtilizationMetrics/index.d.ts.map +1 -0
  10. package/dist/MetricsGraph/UtilizationMetrics/index.js +186 -0
  11. package/dist/MetricsGraph/index.d.ts.map +1 -1
  12. package/dist/MetricsGraph/index.js +1 -13
  13. package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
  14. package/dist/ProfileMetricsGraph/index.js +29 -6
  15. package/dist/ProfileSelector/MetricsGraphSection.d.ts +9 -2
  16. package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
  17. package/dist/ProfileSelector/MetricsGraphSection.js +38 -3
  18. package/dist/ProfileSelector/QueryControls.d.ts +43 -0
  19. package/dist/ProfileSelector/QueryControls.d.ts.map +1 -0
  20. package/dist/{QueryControls/index.js → ProfileSelector/QueryControls.js} +13 -16
  21. package/dist/ProfileSelector/index.d.ts +29 -1
  22. package/dist/ProfileSelector/index.d.ts.map +1 -1
  23. package/dist/ProfileSelector/index.js +9 -12
  24. package/dist/SimpleMatchers/Select.js +1 -1
  25. package/dist/SimpleMatchers/index.d.ts +11 -2
  26. package/dist/SimpleMatchers/index.d.ts.map +1 -1
  27. package/dist/SimpleMatchers/index.js +45 -34
  28. package/dist/ViewMatchers/index.d.ts +9 -0
  29. package/dist/ViewMatchers/index.d.ts.map +1 -1
  30. package/dist/ViewMatchers/index.js +12 -20
  31. package/dist/contexts/MatchersInputLabelsContext.d.ts +29 -0
  32. package/dist/contexts/MatchersInputLabelsContext.d.ts.map +1 -0
  33. package/dist/contexts/MatchersInputLabelsContext.js +79 -0
  34. package/dist/contexts/SimpleMatchersLabelContext.d.ts +25 -0
  35. package/dist/contexts/SimpleMatchersLabelContext.d.ts.map +1 -0
  36. package/dist/contexts/SimpleMatchersLabelContext.js +115 -0
  37. package/dist/contexts/UtilizationLabelsContext.d.ts +15 -0
  38. package/dist/contexts/UtilizationLabelsContext.d.ts.map +1 -0
  39. package/dist/contexts/UtilizationLabelsContext.js +25 -0
  40. package/dist/hooks/useQueryState.d.ts +0 -2
  41. package/dist/hooks/useQueryState.d.ts.map +1 -1
  42. package/dist/hooks/useQueryState.js +0 -18
  43. package/dist/index.d.ts +3 -9
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +3 -9
  46. package/dist/styles.css +1 -1
  47. package/dist/useSumBy.js +1 -1
  48. package/package.json +2 -2
  49. package/src/MatchersInput/index.tsx +163 -17
  50. package/src/MetricsGraph/UtilizationMetrics/Throughput.tsx +405 -0
  51. package/src/MetricsGraph/UtilizationMetrics/index.tsx +426 -0
  52. package/src/MetricsGraph/index.tsx +1 -17
  53. package/src/ProfileMetricsGraph/index.tsx +98 -17
  54. package/src/ProfileSelector/MetricsGraphSection.tsx +115 -14
  55. package/src/{QueryControls/index.tsx → ProfileSelector/QueryControls.tsx} +84 -66
  56. package/src/ProfileSelector/index.tsx +109 -106
  57. package/src/SimpleMatchers/Select.tsx +1 -1
  58. package/src/SimpleMatchers/index.tsx +85 -46
  59. package/src/ViewMatchers/index.tsx +30 -22
  60. package/src/contexts/MatchersInputLabelsContext.tsx +141 -0
  61. package/src/contexts/SimpleMatchersLabelContext.tsx +189 -0
  62. package/src/contexts/UtilizationLabelsContext.tsx +45 -0
  63. package/src/hooks/useQueryState.ts +0 -25
  64. package/src/index.tsx +3 -29
  65. package/src/useSumBy.ts +1 -1
  66. package/dist/QueryControls/index.d.ts +0 -42
  67. package/dist/QueryControls/index.d.ts.map +0 -1
  68. package/dist/contexts/LabelsQueryProvider.d.ts +0 -35
  69. package/dist/contexts/LabelsQueryProvider.d.ts.map +0 -1
  70. package/dist/contexts/LabelsQueryProvider.js +0 -70
  71. package/dist/contexts/UnifiedLabelsContext.d.ts +0 -37
  72. package/dist/contexts/UnifiedLabelsContext.d.ts.map +0 -1
  73. package/dist/contexts/UnifiedLabelsContext.js +0 -88
  74. package/dist/contexts/utils.d.ts +0 -10
  75. package/dist/contexts/utils.d.ts.map +0 -1
  76. package/dist/contexts/utils.js +0 -31
  77. package/dist/hooks/useLabels.d.ts +0 -23
  78. package/dist/hooks/useLabels.d.ts.map +0 -1
  79. package/dist/hooks/useLabels.js +0 -75
  80. package/src/contexts/LabelsQueryProvider.tsx +0 -142
  81. package/src/contexts/UnifiedLabelsContext.tsx +0 -155
  82. package/src/contexts/utils.ts +0 -43
  83. package/src/hooks/useLabels.ts +0 -121
@@ -377,7 +377,7 @@ const CustomSelect: React.FC<CustomSelectProps & Record<string, any>> = ({
377
377
  <RefreshButton
378
378
  onClick={() => void handleRefetch()}
379
379
  disabled={isRefetching}
380
- title="Refresh results"
380
+ title="Refresh label values"
381
381
  testId={TEST_IDS.LABEL_VALUE_REFRESH_BUTTON}
382
382
  sticky={true}
383
383
  loading={isRefetching}
@@ -11,23 +11,31 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useCallback, useEffect, useState} from 'react';
14
+ import {useCallback, useEffect, useMemo, 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 {useGrpcMetadata, useParcaContext} from '@parca/components';
20
+ import {QueryServiceClient} from '@parca/client';
21
+ import {useGrpcMetadata} from '@parca/components';
22
+ import {Query} from '@parca/parser';
21
23
  import {TEST_IDS, testId} from '@parca/test-utils';
22
24
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
23
25
 
24
- import {useUnifiedLabels} from '../contexts/UnifiedLabelsContext';
25
- import {transformLabelName} from '../contexts/utils';
26
- import {useQueryState} from '../hooks/useQueryState';
26
+ import {LabelProvider, useLabels} from '../contexts/SimpleMatchersLabelContext';
27
+ import {useUtilizationLabels} from '../contexts/UtilizationLabelsContext';
27
28
  import Select, {type SelectItem} from './Select';
28
29
 
29
30
  interface Props {
31
+ queryClient: QueryServiceClient;
32
+ setMatchersString: (arg: string) => void;
33
+ runQuery: () => void;
34
+ currentQuery: Query;
35
+ profileType: string;
30
36
  queryBrowserRef: React.RefObject<HTMLDivElement>;
37
+ start?: number;
38
+ end?: number;
31
39
  searchExecutedTimestamp?: number;
32
40
  }
33
41
 
@@ -39,12 +47,22 @@ interface QueryRow {
39
47
  isLoading: boolean;
40
48
  }
41
49
 
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
+
42
60
  export const transformLabelsForSelect = (labelNames: string[]): SelectItem[] => {
43
61
  return labelNames.map(labelName => ({
44
62
  key: labelName,
45
63
  element: {
46
- active: <>{transformLabelName(labelName)}</>,
47
- expanded: <>{transformLabelName(labelName)}</>,
64
+ active: <>{trimOtelPrefix(labelName)}</>,
65
+ expanded: <>{trimOtelPrefix(labelName)}</>,
48
66
  },
49
67
  }));
50
68
  };
@@ -97,31 +115,25 @@ const operatorOptions = [
97
115
  ];
98
116
 
99
117
  const SimpleMatchers = ({
118
+ queryClient,
119
+ setMatchersString,
120
+ currentQuery,
121
+ profileType,
100
122
  queryBrowserRef,
101
-
123
+ start,
124
+ end,
102
125
  searchExecutedTimestamp,
103
126
  }: Props): JSX.Element => {
127
+ const utilizationLabels = useUtilizationLabels();
104
128
  const [queryRows, setQueryRows] = useState<QueryRow[]>([
105
129
  {labelName: '', operator: '=', labelValue: '', labelValues: [], isLoading: false},
106
130
  ]);
107
131
  const reactQueryClient = useQueryClient();
108
132
  const metadata = useGrpcMetadata();
109
- const {queryServiceClient: parcaQueryClient} = useParcaContext();
110
133
 
111
134
  const [showAll, setShowAll] = useState(false);
112
135
  const [isActivelyEditing, setIsActivelyEditing] = useState(false);
113
136
 
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
-
125
137
  // Reset editing mode when search is executed
126
138
  useEffect(() => {
127
139
  if (searchExecutedTimestamp !== undefined && searchExecutedTimestamp > 0) {
@@ -135,25 +147,18 @@ const SimpleMatchers = ({
135
147
 
136
148
  const maxWidthInPixels = `max-w-[${queryBrowserRef.current?.offsetWidth.toString() as string}px]`;
137
149
 
138
- const currentMatchers = draftParsedQuery?.matchersString();
139
- const profileType = draftParsedQuery?.profileType().toString();
140
- const start = draftSelection.from;
141
- const end = draftSelection.to;
150
+ const currentMatchers = currentQuery.matchersString();
142
151
 
143
152
  const fetchLabelValues = useCallback(
144
153
  async (labelName: string): Promise<string[]> => {
145
- if (labelName == null || labelName === '') {
146
- return [];
147
- }
148
-
149
- if (profileType == null || profileType === '') {
154
+ if (labelName == null || labelName === '' || profileType == null || profileType === '') {
150
155
  return [];
151
156
  }
152
157
  try {
153
158
  const values = await reactQueryClient.fetchQuery(
154
159
  [labelName, profileType, start, end],
155
160
  async () => {
156
- const response = await parcaQueryClient.values(
161
+ const response = await queryClient.values(
157
162
  {
158
163
  labelName,
159
164
  match: [],
@@ -178,7 +183,14 @@ const SimpleMatchers = ({
178
183
  return [];
179
184
  }
180
185
  },
181
- [parcaQueryClient, metadata, profileType, reactQueryClient, start, end]
186
+ [queryClient, metadata, profileType, reactQueryClient, start, end]
187
+ );
188
+
189
+ const fetchLabelValuesUtilization = useCallback(
190
+ async (labelName: string): Promise<string[]> => {
191
+ return (await utilizationLabels?.utilizationFetchLabelValues?.(labelName)) ?? [];
192
+ },
193
+ [utilizationLabels]
182
194
  );
183
195
 
184
196
  const updateMatchersString = useCallback(
@@ -187,11 +199,18 @@ const SimpleMatchers = ({
187
199
  .filter(row => row.labelName.length > 0 && row.labelValue)
188
200
  .map(row => `${row.labelName}${row.operator}"${row.labelValue}"`)
189
201
  .join(',');
190
- setDraftMatchers(matcherString);
202
+ setMatchersString(matcherString);
191
203
  },
192
- [setDraftMatchers]
204
+ [setMatchersString]
193
205
  );
194
206
 
207
+ const {
208
+ labelNameOptions,
209
+ isLoading: labelNamesLoading,
210
+ refetchLabelValues,
211
+ refetchLabelNames,
212
+ } = useLabels();
213
+
195
214
  // Helper to ensure selected label name is in the options (for page load before API returns)
196
215
  const getLabelNameOptionsWithSelected = useCallback(
197
216
  (selectedLabelName: string): typeof labelNameOptions => {
@@ -257,13 +276,20 @@ const SimpleMatchers = ({
257
276
 
258
277
  const fetchLabelValuesUnified = useCallback(
259
278
  async (labelName: string): Promise<string[]> => {
260
- return await fetchLabelValues(labelName);
279
+ const labelType = labelNameOptions.find(option =>
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
287
  },
262
- [fetchLabelValues]
288
+ [fetchLabelValues, fetchLabelValuesUtilization, labelNameOptions]
263
289
  );
264
290
 
265
291
  useEffect(() => {
266
- if (currentMatchers === '' || currentMatchers === undefined) {
292
+ if (currentMatchers === '') {
267
293
  const defaultRow = {
268
294
  labelName: '',
269
295
  operator: '=',
@@ -280,7 +306,7 @@ const SimpleMatchers = ({
280
306
  const fetchAndSetQueryRows = async (): Promise<void> => {
281
307
  const newRows = await Promise.all(
282
308
  currentMatchers.split(',').map(async matcher => {
283
- const match = matcher.match(/^([^=!~]+)([=!~]{1,2})(.+)$/);
309
+ const match = matcher.match(/([^=!~]+)([=!~]{1,2})(.+)/);
284
310
  if (match === null) return null;
285
311
 
286
312
  const [, labelName, operator, labelValue] = match;
@@ -414,13 +440,6 @@ const SimpleMatchers = ({
414
440
 
415
441
  const isRowRegex = (row: QueryRow): boolean => row.operator === '=~' || row.operator === '!~';
416
442
 
417
- const handleRefetchForLabelValues = useCallback(
418
- async (labelName: string): Promise<void> => {
419
- await fetchLabelValuesUnified(labelName);
420
- },
421
- [fetchLabelValuesUnified]
422
- );
423
-
424
443
  return (
425
444
  <div
426
445
  className={`flex items-center gap-3 ${maxWidthInPixels} w-full flex-wrap`}
@@ -466,7 +485,7 @@ const SimpleMatchers = ({
466
485
  onButtonClick={() => handleLabelValueClick(index)}
467
486
  editable={isRowRegex(row)}
468
487
  {...testId(TEST_IDS.LABEL_VALUE_SELECT)}
469
- refetchValues={async () => await handleRefetchForLabelValues(row.labelName)}
488
+ refetchValues={async () => await refetchLabelValues(row.labelName)}
470
489
  showLoadingInButton={true}
471
490
  />
472
491
  <button
@@ -511,4 +530,24 @@ const SimpleMatchers = ({
511
530
  );
512
531
  };
513
532
 
514
- export default SimpleMatchers;
533
+ export default function SimpleMathersWithProvider(props: Props): JSX.Element {
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
+ }
@@ -16,32 +16,40 @@ 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 {useGrpcMetadata, useParcaContext} from '@parca/components';
19
+ import {QueryServiceClient} from '@parca/client';
20
+ import {useGrpcMetadata} from '@parca/components';
21
+ import {Query} from '@parca/parser';
20
22
  import {TEST_IDS, testId} from '@parca/test-utils';
21
23
  import {millisToProtoTimestamp, sanitizeLabelValue} from '@parca/utilities';
22
24
 
23
25
  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;
29
36
  }
30
37
 
31
- const ViewMatchers: React.FC<Props> = ({labelNames}) => {
38
+ const ViewMatchers: React.FC<Props> = ({
39
+ labelNames,
40
+ profileType,
41
+ queryClient,
42
+ runQuery,
43
+ setMatchersString,
44
+ start,
45
+ end,
46
+ currentQuery,
47
+ }) => {
32
48
  const [labelValuesMap, setLabelValuesMap] = useState<Record<string, string[]>>({});
33
49
  const [isLoading, setIsLoading] = useState<Record<string, boolean>>({});
34
50
  const metadata = useGrpcMetadata();
35
- const {queryServiceClient: parcaQueryClient} = useParcaContext();
36
51
 
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;
52
+ const currentMatchers = currentQuery.matchersString();
45
53
 
46
54
  const parseCurrentMatchers = useCallback((matchersString: string): Record<string, string> => {
47
55
  const matches = matchersString.match(/(\w+)="([^"]+)"/g);
@@ -60,15 +68,15 @@ const ViewMatchers: React.FC<Props> = ({labelNames}) => {
60
68
  );
61
69
  }, []);
62
70
 
63
- const initialSelections = parseCurrentMatchers(currentMatchers ?? '');
71
+ const initialSelections = parseCurrentMatchers(currentMatchers);
64
72
  const selectionsRef = useRef<Record<string, string | null>>(initialSelections);
65
73
 
66
- const commitDraftRef = useRef(commitDraft);
74
+ const runQueryRef = useRef(runQuery);
67
75
  const timeoutRef = useRef<NodeJS.Timeout | null>(null);
68
76
 
69
77
  useEffect(() => {
70
- commitDraftRef.current = commitDraft;
71
- }, [commitDraft]);
78
+ runQueryRef.current = runQuery;
79
+ }, [runQuery]);
72
80
 
73
81
  useEffect(() => {
74
82
  selectionsRef.current = initialSelections;
@@ -77,7 +85,7 @@ const ViewMatchers: React.FC<Props> = ({labelNames}) => {
77
85
  const fetchLabelValues = useCallback(
78
86
  async (labelName: string): Promise<string[]> => {
79
87
  try {
80
- const response = await parcaQueryClient.values(
88
+ const response = await queryClient.values(
81
89
  {
82
90
  labelName,
83
91
  match: [],
@@ -97,7 +105,7 @@ const ViewMatchers: React.FC<Props> = ({labelNames}) => {
97
105
  return [];
98
106
  }
99
107
  },
100
- [parcaQueryClient, metadata, profileType, start, end]
108
+ [queryClient, metadata, profileType, start, end]
101
109
  );
102
110
 
103
111
  const fetchAllLabelValues = useCallback(async (): Promise<void> => {
@@ -127,16 +135,16 @@ const ViewMatchers: React.FC<Props> = ({labelNames}) => {
127
135
  .map(([ln, v]) => `${ln}="${v as string}"`);
128
136
 
129
137
  const matcherString = matcherParts.join(',');
130
- setDraftMatchers(matcherString);
138
+ setMatchersString(matcherString);
131
139
 
132
140
  if (timeoutRef.current !== null) {
133
141
  clearTimeout(timeoutRef.current);
134
142
  }
135
143
 
136
144
  timeoutRef.current = setTimeout(() => {
137
- commitDraftRef.current();
145
+ runQueryRef.current();
138
146
  }, 300);
139
- }, [setDraftMatchers]);
147
+ }, [setMatchersString]);
140
148
 
141
149
  const handleSelection = useCallback(
142
150
  (labelName: string, value: string | null): void => {
@@ -0,0 +1,141 @@
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 React, {createContext, useContext, useMemo} from 'react';
15
+
16
+ import {QueryServiceClient} from '@parca/client';
17
+
18
+ import {useFetchUtilizationLabelValues, useLabelNames, useLabelValues} from '../MatchersInput';
19
+ import {useUtilizationLabels} from './UtilizationLabelsContext';
20
+
21
+ interface LabelNameMapping {
22
+ displayName: string;
23
+ fullName: string;
24
+ }
25
+
26
+ interface LabelsContextType {
27
+ labelNames: string[];
28
+ labelValues: string[];
29
+ labelNameMappings: LabelNameMapping[];
30
+ isLabelNamesLoading: boolean;
31
+ isLabelValuesLoading: boolean;
32
+ currentLabelName: string | null;
33
+ setCurrentLabelName: (name: string | null) => void;
34
+ shouldHandlePrefixes: boolean;
35
+ refetchLabelValues: () => Promise<void>;
36
+ refetchLabelNames: () => Promise<void>;
37
+ }
38
+
39
+ const LabelsContext = createContext<LabelsContextType | null>(null);
40
+
41
+ interface LabelsProviderProps {
42
+ children: React.ReactNode;
43
+ queryClient: QueryServiceClient;
44
+ profileType: string;
45
+ start?: number;
46
+ end?: number;
47
+ }
48
+
49
+ // With there being the possibility of having utilization labels, we need to be able to determine whether the labels to be used are utilization labels or profiling data labels.
50
+ // This context is used to determine this.
51
+ export function LabelsProvider({
52
+ children,
53
+ queryClient,
54
+ profileType,
55
+ start,
56
+ end,
57
+ }: LabelsProviderProps): JSX.Element {
58
+ const [currentLabelName, setCurrentLabelName] = React.useState<string | null>(null);
59
+ const utilizationLabels = useUtilizationLabels();
60
+
61
+ const {
62
+ result: labelNamesResponse,
63
+ loading: isLabelNamesLoading,
64
+ refetch: refetchLabelNames,
65
+ } = useLabelNames(queryClient, profileType, start, end);
66
+
67
+ const labelNamesFromAPI = useMemo(() => {
68
+ return (labelNamesResponse.error === undefined || labelNamesResponse.error == null) &&
69
+ labelNamesResponse !== undefined &&
70
+ labelNamesResponse != null
71
+ ? labelNamesResponse.response?.labelNames.filter(e => e !== '__name__') ?? []
72
+ : [];
73
+ }, [labelNamesResponse]);
74
+
75
+ const {
76
+ result: labelValuesOriginal,
77
+ loading: isLabelValuesLoading,
78
+ refetch: refetchLabelValues,
79
+ } = useLabelValues(queryClient, currentLabelName ?? '', profileType, start, end);
80
+
81
+ const utilizationLabelValues = useFetchUtilizationLabelValues(
82
+ currentLabelName ?? '',
83
+ utilizationLabels
84
+ );
85
+
86
+ const shouldHandlePrefixes = utilizationLabels?.utilizationLabelNames !== undefined;
87
+
88
+ const labelNameMappings = useMemo(() => {
89
+ const names = utilizationLabels?.utilizationLabelNames ?? labelNamesFromAPI;
90
+ return names.map(name => ({
91
+ displayName: name.replace(/^(attributes\.|attributes_resource\.)/, ''),
92
+ fullName: name,
93
+ }));
94
+ }, [labelNamesFromAPI, utilizationLabels?.utilizationLabelNames]);
95
+
96
+ const labelNames = useMemo(() => {
97
+ return shouldHandlePrefixes ? labelNameMappings.map(m => m.displayName) : labelNamesFromAPI;
98
+ }, [labelNameMappings, labelNamesFromAPI, shouldHandlePrefixes]);
99
+
100
+ const labelValues = useMemo(() => {
101
+ return utilizationLabels?.utilizationFetchLabelValues !== undefined
102
+ ? utilizationLabelValues
103
+ : labelValuesOriginal.response;
104
+ }, [labelValuesOriginal, utilizationLabelValues, utilizationLabels]);
105
+
106
+ const value = useMemo(
107
+ () => ({
108
+ labelNames,
109
+ labelValues,
110
+ labelNameMappings,
111
+ isLabelNamesLoading,
112
+ isLabelValuesLoading,
113
+ currentLabelName,
114
+ setCurrentLabelName,
115
+ shouldHandlePrefixes,
116
+ refetchLabelValues,
117
+ refetchLabelNames,
118
+ }),
119
+ [
120
+ labelNames,
121
+ labelValues,
122
+ labelNameMappings,
123
+ isLabelNamesLoading,
124
+ isLabelValuesLoading,
125
+ currentLabelName,
126
+ shouldHandlePrefixes,
127
+ refetchLabelValues,
128
+ refetchLabelNames,
129
+ ]
130
+ );
131
+
132
+ return <LabelsContext.Provider value={value}>{children}</LabelsContext.Provider>;
133
+ }
134
+
135
+ export function useLabels(): LabelsContextType {
136
+ const context = useContext(LabelsContext);
137
+ if (context === null) {
138
+ throw new Error('useLabels must be used within a LabelsProvider');
139
+ }
140
+ return context;
141
+ }
@@ -0,0 +1,189 @@
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 {createContext, useCallback, useContext, useMemo} from 'react';
15
+
16
+ import {useQueryClient} from '@tanstack/react-query';
17
+
18
+ import {QueryServiceClient} from '@parca/client';
19
+
20
+ import {useLabelNames} from '../MatchersInput';
21
+ import {transformLabelsForSelect} from '../SimpleMatchers';
22
+ import type {SelectItem} from '../SimpleMatchers/Select';
23
+ import {useUtilizationLabels} from './UtilizationLabelsContext';
24
+
25
+ interface LabelNameSection {
26
+ type: string;
27
+ values: SelectItem[];
28
+ }
29
+
30
+ interface LabelContextValue {
31
+ labelNameOptions: LabelNameSection[];
32
+ isLoading: boolean;
33
+ error: Error | null;
34
+ refetchLabelValues: (labelName?: string) => Promise<void>;
35
+ refetchLabelNames: () => Promise<void>;
36
+ }
37
+
38
+ const LabelContext = createContext<LabelContextValue | null>(null);
39
+
40
+ interface LabelProviderProps {
41
+ children: React.ReactNode;
42
+ queryClient: QueryServiceClient;
43
+ profileType: string;
44
+ labelNameFromMatchers: string[];
45
+ start?: number;
46
+ end?: number;
47
+ }
48
+
49
+ // With there being the possibility of having utilization labels, we need to be able to determine whether the labels to be used are utilization labels or profiling data labels.
50
+ // This context is used to determine this.
51
+
52
+ export function LabelProvider({
53
+ children,
54
+ queryClient,
55
+ profileType,
56
+ labelNameFromMatchers,
57
+ start,
58
+ end,
59
+ }: LabelProviderProps): JSX.Element {
60
+ const reactQueryClient = useQueryClient();
61
+ const utilizationLabelResponse = useUtilizationLabels();
62
+ const {
63
+ loading,
64
+ result,
65
+ refetch: refetchLabelNamesQuery,
66
+ } = useLabelNames(queryClient, profileType, start, end);
67
+
68
+ const profileValues = useMemo(() => {
69
+ const profileLabelNames =
70
+ result.error != null ? [] : result.response?.labelNames.filter(e => e !== '__name__') ?? [];
71
+ const uniqueProfileLabelNames = Array.from(new Set(profileLabelNames));
72
+
73
+ return {
74
+ labelNameOptions: uniqueProfileLabelNames,
75
+ isLoading: loading,
76
+ error: result.error ?? null,
77
+ };
78
+ }, [result, loading]);
79
+
80
+ const utilizationValues = useMemo(() => {
81
+ if (utilizationLabelResponse?.utilizationLabelNamesLoading === true) {
82
+ return {labelNameOptions: [] as string[], isLoading: true};
83
+ }
84
+ if (
85
+ utilizationLabelResponse == null ||
86
+ utilizationLabelResponse.utilizationLabelNames == null
87
+ ) {
88
+ return {labelNameOptions: [] as string[], isLoading: false};
89
+ }
90
+
91
+ const uniqueUtilizationLabelNames = Array.from(
92
+ new Set(utilizationLabelResponse.utilizationLabelNames)
93
+ );
94
+ return {
95
+ labelNameOptions: uniqueUtilizationLabelNames,
96
+ isLoading: utilizationLabelResponse.utilizationLabelNamesLoading,
97
+ };
98
+ }, [utilizationLabelResponse]);
99
+
100
+ const value = useMemo(() => {
101
+ if (
102
+ profileValues.error != null ||
103
+ profileValues.isLoading ||
104
+ utilizationValues.isLoading === true
105
+ ) {
106
+ return {
107
+ labelNameOptions: [],
108
+ isLoading: (profileValues.isLoading || utilizationValues.isLoading) ?? false,
109
+ error: profileValues.error,
110
+ };
111
+ }
112
+
113
+ let nonMatchingLabels = labelNameFromMatchers.filter(
114
+ label => !utilizationValues.labelNameOptions.includes(label)
115
+ );
116
+ nonMatchingLabels = nonMatchingLabels.filter(
117
+ label => !profileValues.labelNameOptions.includes(label)
118
+ );
119
+
120
+ const nonMatchingLabelsSet = Array.from(new Set(nonMatchingLabels));
121
+ const options = [
122
+ {
123
+ type: 'cpu',
124
+ values: transformLabelsForSelect(profileValues.labelNameOptions),
125
+ },
126
+ {
127
+ type: 'gpu',
128
+ values: transformLabelsForSelect(utilizationValues.labelNameOptions),
129
+ },
130
+ {
131
+ type: '',
132
+ values: transformLabelsForSelect(nonMatchingLabelsSet),
133
+ },
134
+ ];
135
+
136
+ return {
137
+ labelNameOptions: options.filter(e => e.values.length > 0),
138
+ isLoading: false,
139
+ error: null,
140
+ };
141
+ }, [profileValues, utilizationValues, labelNameFromMatchers]);
142
+
143
+ const refetchLabelValues = useCallback(
144
+ async (labelName?: string) => {
145
+ await reactQueryClient.refetchQueries({
146
+ predicate: query => {
147
+ const key = query.queryKey;
148
+ const matchesStructure =
149
+ Array.isArray(key) &&
150
+ key.length === 4 &&
151
+ typeof key[0] === 'string' &&
152
+ key[1] === profileType;
153
+
154
+ if (!matchesStructure) return false;
155
+
156
+ if (labelName !== undefined && labelName !== '') {
157
+ return key[0] === labelName;
158
+ }
159
+
160
+ return true;
161
+ },
162
+ });
163
+ },
164
+ [reactQueryClient, profileType]
165
+ );
166
+
167
+ const refetchLabelNames = useCallback(async () => {
168
+ await refetchLabelNamesQuery();
169
+ }, [refetchLabelNamesQuery]);
170
+
171
+ const contextValue = useMemo(
172
+ () => ({
173
+ ...value,
174
+ refetchLabelValues,
175
+ refetchLabelNames,
176
+ }),
177
+ [value, refetchLabelValues, refetchLabelNames]
178
+ );
179
+
180
+ return <LabelContext.Provider value={contextValue}>{children}</LabelContext.Provider>;
181
+ }
182
+
183
+ export function useLabels(): LabelContextValue {
184
+ const context = useContext(LabelContext);
185
+ if (context === null) {
186
+ throw new Error('useLabels must be used within a LabelProvider');
187
+ }
188
+ return context;
189
+ }