@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
@@ -18,9 +18,11 @@ import {DateTimeRange, useURLStateBatch} from '@parca/components';
18
18
  import {Query} from '@parca/parser';
19
19
 
20
20
  import {ProfileSelection} from '..';
21
+ import UtilizationMetricsGraph from '../MetricsGraph/UtilizationMetrics';
22
+ import AreaChart from '../MetricsGraph/UtilizationMetrics/Throughput';
21
23
  import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
22
24
  import {useResetStateOnSeriesChange} from '../ProfileView/hooks/useResetStateOnSeriesChange';
23
- import {QuerySelection} from './index';
25
+ import {QuerySelection, type UtilizationMetrics as UtilizationMetricsType} from './index';
24
26
 
25
27
  interface MetricsGraphSectionProps {
26
28
  showMetricsGraph: boolean;
@@ -39,6 +41,13 @@ interface MetricsGraphSectionProps {
39
41
  query: Query;
40
42
  setNewQueryExpression: (queryExpression: string, commit?: boolean) => void;
41
43
  setQueryExpression: (updateTs?: boolean) => void;
44
+ utilizationMetrics?: Array<{
45
+ name: string;
46
+ humanReadableName: string;
47
+ data: UtilizationMetricsType[];
48
+ }>;
49
+ utilizationMetricsLoading?: boolean;
50
+ onUtilizationSeriesSelect?: (name: string, seriesIndex: number) => void;
42
51
  }
43
52
 
44
53
  export function MetricsGraphSection({
@@ -57,6 +66,9 @@ export function MetricsGraphSection({
57
66
  setProfileSelection,
58
67
  query,
59
68
  setNewQueryExpression,
69
+ utilizationMetrics,
70
+ utilizationMetricsLoading,
71
+ onUtilizationSeriesSelect,
60
72
  }: MetricsGraphSectionProps): JSX.Element {
61
73
  const resetStateOnSeriesChange = useResetStateOnSeriesChange();
62
74
  const batchUpdates = useURLStateBatch();
@@ -135,6 +147,89 @@ export function MetricsGraphSection({
135
147
  });
136
148
  };
137
149
 
150
+ const UtilizationGraphToShow = ({
151
+ utilizationMetrics,
152
+ }: {
153
+ utilizationMetrics: Array<{
154
+ name: string;
155
+ humanReadableName: string;
156
+ data: UtilizationMetricsType[];
157
+ }>;
158
+ }): JSX.Element => {
159
+ const throughputMetrics = utilizationMetrics.filter(
160
+ metric =>
161
+ metric.name === 'gpu_pcie_throughput_transmit_bytes' ||
162
+ metric.name === 'gpu_pcie_throughput_receive_bytes'
163
+ );
164
+ const transmitData =
165
+ throughputMetrics.find(metric => metric.name === 'gpu_pcie_throughput_transmit_bytes')
166
+ ?.data ?? [];
167
+ const receiveData =
168
+ throughputMetrics.find(metric => metric.name === 'gpu_pcie_throughput_receive_bytes')?.data ??
169
+ [];
170
+
171
+ if (utilizationMetrics.length === 0) {
172
+ return <></>;
173
+ }
174
+
175
+ return (
176
+ <div>
177
+ {utilizationMetrics.map(({name, humanReadableName, data}) => {
178
+ if (
179
+ name !== 'gpu_pcie_throughput_transmit_bytes' &&
180
+ name !== 'gpu_pcie_throughput_receive_bytes'
181
+ ) {
182
+ return (
183
+ <UtilizationMetricsGraph
184
+ key={name}
185
+ data={data}
186
+ setTimeRange={handleTimeRangeChange}
187
+ utilizationMetricsLoading={utilizationMetricsLoading}
188
+ humanReadableName={humanReadableName}
189
+ from={querySelection.from}
190
+ to={querySelection.to}
191
+ yAxisUnit="percentage"
192
+ addLabelMatcher={addLabelMatcher}
193
+ onSeriesClick={seriesIndex => {
194
+ // For generic UtilizationMetrics, just pass the series index
195
+ if (onUtilizationSeriesSelect != null) {
196
+ onUtilizationSeriesSelect(name, seriesIndex);
197
+ }
198
+ }}
199
+ />
200
+ );
201
+ }
202
+ return null;
203
+ })}
204
+ {throughputMetrics.length > 0 && (
205
+ <AreaChart
206
+ transmitData={transmitData}
207
+ receiveData={receiveData}
208
+ addLabelMatcher={addLabelMatcher}
209
+ setTimeRange={handleTimeRangeChange}
210
+ name={throughputMetrics[0].name}
211
+ humanReadableName={throughputMetrics[0].humanReadableName}
212
+ from={querySelection.from}
213
+ to={querySelection.to}
214
+ utilizationMetricsLoading={utilizationMetricsLoading}
215
+ selectedSeries={undefined}
216
+ onSeriesClick={(_, seriesIndex) => {
217
+ // For throughput metrics, just pass the series index
218
+ if (onUtilizationSeriesSelect != null) {
219
+ let name = 'gpu_pcie_throughput_transmit_bytes';
220
+ if (seriesIndex > transmitData.length - 1) {
221
+ name = 'gpu_pcie_throughput_receive_bytes';
222
+ seriesIndex -= transmitData.length;
223
+ }
224
+ onUtilizationSeriesSelect(name, seriesIndex);
225
+ }
226
+ }}
227
+ />
228
+ )}
229
+ </div>
230
+ );
231
+ };
232
+
138
233
  return (
139
234
  <div className={cx('relative', {'py-4': !showMetricsGraph})}>
140
235
  {setDisplayHideMetricsGraphButton != null ? (
@@ -156,19 +251,25 @@ export function MetricsGraphSection({
156
251
  querySelection.from !== undefined &&
157
252
  querySelection.to !== undefined ? (
158
253
  <>
159
- <ProfileMetricsGraph
160
- queryClient={queryClient}
161
- queryExpression={querySelection.expression}
162
- from={querySelection.from}
163
- to={querySelection.to}
164
- profile={profileSelection}
165
- comparing={comparing}
166
- sumBy={querySelection.sumBy ?? sumBy ?? []}
167
- sumByLoading={defaultSumByLoading}
168
- setTimeRange={handleTimeRangeChange}
169
- addLabelMatcher={addLabelMatcher}
170
- onPointClick={handlePointClick}
171
- />
254
+ {utilizationMetrics !== undefined ? (
255
+ <UtilizationGraphToShow utilizationMetrics={utilizationMetrics} />
256
+ ) : (
257
+ <>
258
+ <ProfileMetricsGraph
259
+ queryClient={queryClient}
260
+ queryExpression={querySelection.expression}
261
+ from={querySelection.from}
262
+ to={querySelection.to}
263
+ profile={profileSelection}
264
+ comparing={comparing}
265
+ sumBy={querySelection.sumBy ?? sumBy ?? []}
266
+ sumByLoading={defaultSumByLoading}
267
+ setTimeRange={handleTimeRangeChange}
268
+ addLabelMatcher={addLabelMatcher}
269
+ onPointClick={handlePointClick}
270
+ />
271
+ </>
272
+ )}
172
273
  </>
173
274
  ) : (
174
275
  profileSelection === null && (
@@ -11,7 +11,7 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
- import {useRef, useState} from 'react';
14
+ import {useState} from 'react';
15
15
 
16
16
  import {Switch} from '@headlessui/react';
17
17
  import {RpcError} from '@protobuf-ts/runtime-rpc';
@@ -24,10 +24,9 @@ import {TEST_IDS, testId} from '@parca/test-utils';
24
24
 
25
25
  import MatchersInput from '../MatchersInput';
26
26
  import ProfileTypeSelector from '../ProfileTypeSelector';
27
- import {SelectWithRefresh} from '../SelectWithRefresh';
28
- import SimpleMatchers from '../SimpleMatchers/';
27
+ import SelectWithRefresh from '../SelectWithRefresh';
28
+ import SimpleMatchers from '../SimpleMatchers';
29
29
  import ViewMatchers from '../ViewMatchers';
30
- import {useLabelNames} from '../hooks/useLabels';
31
30
 
32
31
  interface SelectOption {
33
32
  label: string;
@@ -35,22 +34,13 @@ interface SelectOption {
35
34
  }
36
35
 
37
36
  interface QueryControlsProps {
38
- queryClient: QueryServiceClient;
39
- query: Query;
40
- profileType: string | ProfileType;
41
- timeRangeSelection: DateTimeRange;
42
- setTimeRangeSelection: (range: DateTimeRange) => void;
43
- setMatchersString: (matchers: string) => void;
44
- setQueryExpression: (updateTs?: boolean) => void;
45
- searchDisabled: boolean;
46
- showProfileTypeSelector?: boolean;
47
- showSumBySelector?: boolean;
48
- showAdvancedMode?: boolean;
49
- disableExplorativeQuerying?: boolean;
37
+ showProfileTypeSelector: boolean;
38
+ showSumBySelector: boolean;
39
+ disableExplorativeQuerying: boolean;
50
40
  profileTypesData?: ProfileTypesResponse;
51
- profileTypesLoading?: boolean;
52
- selectedProfileName?: string;
53
- setProfileName?: (name: string | undefined) => void;
41
+ profileTypesLoading: boolean;
42
+ selectedProfileName: string;
43
+ setProfileName: (name: string | undefined) => void;
54
44
  profileTypesError?: RpcError;
55
45
  viewComponent?: {
56
46
  disableProfileTypesDropdown?: boolean;
@@ -58,55 +48,58 @@ interface QueryControlsProps {
58
48
  labelnames?: string[];
59
49
  createViewComponent?: React.ReactNode;
60
50
  };
61
- setQueryBrowserMode?: (mode: string) => void;
62
- advancedModeForQueryBrowser?: boolean;
63
- setAdvancedModeForQueryBrowser?: (mode: boolean) => void;
64
- queryBrowserRef?: React.RefObject<HTMLDivElement>;
65
- labels?: string[];
66
- sumBySelection?: string[];
67
- sumBySelectionLoading?: boolean;
68
- setUserSumBySelection?: (sumBy: string[]) => void;
69
- sumByRef?: React.RefObject<SelectInstance>;
51
+ queryBrowserMode: string;
52
+ setQueryBrowserMode: (mode: string) => void;
53
+ advancedModeForQueryBrowser: boolean;
54
+ setAdvancedModeForQueryBrowser: (mode: boolean) => void;
55
+ setMatchersString: (matchers: string) => void;
56
+ setQueryExpression: (updateTs?: boolean) => void;
57
+ query: Query;
58
+ queryBrowserRef: React.RefObject<HTMLDivElement>;
59
+ timeRangeSelection: DateTimeRange;
60
+ setTimeRangeSelection: (range: DateTimeRange) => void;
61
+ searchDisabled: boolean;
62
+ queryClient: QueryServiceClient;
63
+ labels: string[];
64
+ sumBySelection: string[];
65
+ sumBySelectionLoading: boolean;
66
+ setUserSumBySelection: (sumBy: string[]) => void;
67
+ sumByRef: React.RefObject<SelectInstance>;
68
+ profileType: ProfileType;
69
+ refreshLabelNames: () => Promise<void>;
70
70
  }
71
71
 
72
72
  export function QueryControls({
73
- profileType,
74
- timeRangeSelection,
75
- setTimeRangeSelection,
76
- setQueryExpression,
77
- searchDisabled,
78
- showProfileTypeSelector = false,
79
- showSumBySelector = false,
80
- showAdvancedMode = true,
73
+ showProfileTypeSelector,
81
74
  profileTypesData,
82
- profileTypesLoading = false,
75
+ profileTypesLoading,
83
76
  selectedProfileName,
84
77
  setProfileName,
85
- profileTypesError,
86
78
  viewComponent,
87
79
  setQueryBrowserMode,
88
- advancedModeForQueryBrowser = false,
80
+ advancedModeForQueryBrowser,
89
81
  setAdvancedModeForQueryBrowser,
82
+ setMatchersString,
83
+ setQueryExpression,
84
+ query,
90
85
  queryBrowserRef,
91
- labels = [],
92
- sumBySelection = [],
93
- sumBySelectionLoading = false,
86
+ timeRangeSelection,
87
+ setTimeRangeSelection,
88
+ searchDisabled,
89
+ queryClient,
90
+ labels,
91
+ sumBySelection,
92
+ sumBySelectionLoading,
94
93
  setUserSumBySelection,
95
94
  sumByRef,
96
- queryClient,
95
+ profileType,
96
+ showSumBySelector,
97
+ profileTypesError,
98
+ refreshLabelNames,
97
99
  }: QueryControlsProps): JSX.Element {
98
100
  const {timezone} = useParcaContext();
99
- const defaultQueryBrowserRef = useRef<HTMLDivElement>(null);
100
- const actualQueryBrowserRef = queryBrowserRef ?? defaultQueryBrowserRef;
101
101
  const [searchExecutedTimestamp, setSearchExecutedTimestamp] = useState<number>(0);
102
102
 
103
- const {refetch: refetchLabelNames} = useLabelNames(
104
- queryClient,
105
- profileType as string,
106
- timeRangeSelection.getFromMs(),
107
- timeRangeSelection.getToMs()
108
- );
109
-
110
103
  return (
111
104
  <div
112
105
  className="flex w-full flex-wrap items-start gap-2"
@@ -121,7 +114,7 @@ export function QueryControls({
121
114
  profileTypesData={profileTypesData}
122
115
  loading={profileTypesLoading}
123
116
  selectedKey={selectedProfileName}
124
- onSelection={setProfileName ?? (() => {})}
117
+ onSelection={setProfileName}
125
118
  error={profileTypesError}
126
119
  disabled={viewComponent?.disableProfileTypesDropdown}
127
120
  />
@@ -130,7 +123,7 @@ export function QueryControls({
130
123
 
131
124
  <div
132
125
  className="w-full flex-1 flex flex-col gap-1 mt-auto"
133
- ref={actualQueryBrowserRef}
126
+ ref={queryBrowserRef}
134
127
  {...testId(TEST_IDS.QUERY_BROWSER_CONTAINER)}
135
128
  >
136
129
  <div className="flex items-center justify-between">
@@ -138,13 +131,13 @@ export function QueryControls({
138
131
  <label className="text-xs" {...testId(TEST_IDS.QUERY_LABEL)}>
139
132
  Query
140
133
  </label>
141
- {showAdvancedMode && viewComponent?.disableExplorativeQuerying !== true && (
134
+ {viewComponent?.disableExplorativeQuerying !== true && (
142
135
  <>
143
136
  <Switch
144
137
  checked={advancedModeForQueryBrowser}
145
138
  onChange={() => {
146
- setAdvancedModeForQueryBrowser?.(!advancedModeForQueryBrowser);
147
- setQueryBrowserMode?.(advancedModeForQueryBrowser ? 'simple' : 'advanced');
139
+ setAdvancedModeForQueryBrowser(!advancedModeForQueryBrowser);
140
+ setQueryBrowserMode(advancedModeForQueryBrowser ? 'simple' : 'advanced');
148
141
  }}
149
142
  className={`${
150
143
  advancedModeForQueryBrowser ? 'bg-indigo-600' : 'bg-gray-400 dark:bg-gray-800'
@@ -171,12 +164,36 @@ export function QueryControls({
171
164
  {viewComponent?.disableExplorativeQuerying === true &&
172
165
  viewComponent?.labelnames !== undefined &&
173
166
  viewComponent?.labelnames.length >= 1 ? (
174
- <ViewMatchers labelNames={viewComponent.labelnames} />
175
- ) : showAdvancedMode && advancedModeForQueryBrowser ? (
176
- <MatchersInput />
167
+ <ViewMatchers
168
+ labelNames={viewComponent.labelnames}
169
+ setMatchersString={setMatchersString}
170
+ profileType={selectedProfileName}
171
+ runQuery={setQueryExpression}
172
+ currentQuery={query}
173
+ queryClient={queryClient}
174
+ start={timeRangeSelection.getFromMs()}
175
+ end={timeRangeSelection.getToMs()}
176
+ />
177
+ ) : advancedModeForQueryBrowser ? (
178
+ <MatchersInput
179
+ setMatchersString={setMatchersString}
180
+ runQuery={setQueryExpression}
181
+ currentQuery={query}
182
+ profileType={selectedProfileName}
183
+ queryClient={queryClient}
184
+ start={timeRangeSelection.getFromMs()}
185
+ end={timeRangeSelection.getToMs()}
186
+ />
177
187
  ) : (
178
188
  <SimpleMatchers
179
- queryBrowserRef={actualQueryBrowserRef}
189
+ setMatchersString={setMatchersString}
190
+ runQuery={setQueryExpression}
191
+ currentQuery={query}
192
+ profileType={selectedProfileName}
193
+ queryBrowserRef={queryBrowserRef}
194
+ queryClient={queryClient}
195
+ start={timeRangeSelection.getFromMs()}
196
+ end={timeRangeSelection.getToMs()}
180
197
  searchExecutedTimestamp={searchExecutedTimestamp}
181
198
  />
182
199
  )}
@@ -199,9 +216,9 @@ export function QueryControls({
199
216
  options={labels.map(label => ({label, value: label}))}
200
217
  className="parca-select-container text-sm w-full max-w-80"
201
218
  classNamePrefix="parca-select"
202
- value={sumBySelection.map(sumBy => ({label: sumBy, value: sumBy}))}
219
+ value={(sumBySelection ?? []).map(sumBy => ({label: sumBy, value: sumBy}))}
203
220
  onChange={newValue => {
204
- setUserSumBySelection?.(newValue.map(option => option.value));
221
+ setUserSumBySelection(newValue.map(option => option.value));
205
222
  }}
206
223
  placeholder="Labels..."
207
224
  styles={{
@@ -215,13 +232,14 @@ export function QueryControls({
215
232
  minWidth: '320px',
216
233
  position: 'absolute',
217
234
  }),
235
+ // menu: provided => ({...provided, width: 'max-content', zIndex: 50}), // Setting the same zIndex as drop down menus
218
236
  }}
219
237
  isLoading={sumBySelectionLoading}
220
- isDisabled={!(profileType as ProfileType)?.delta}
238
+ isDisabled={!profileType.delta}
221
239
  // @ts-expect-error
222
240
  ref={sumByRef}
223
241
  onKeyDown={e => {
224
- const currentRef = sumByRef?.current as unknown as SelectInstance | null;
242
+ const currentRef = sumByRef.current as unknown as SelectInstance | null;
225
243
  if (currentRef == null) {
226
244
  return;
227
245
  }
@@ -239,7 +257,7 @@ export function QueryControls({
239
257
  currentRef.blur();
240
258
  }
241
259
  }}
242
- onRefresh={refetchLabelNames}
260
+ onRefresh={refreshLabelNames}
243
261
  refreshTitle="Refresh label names"
244
262
  refreshTestId="sum-by-refresh-button"
245
263
  menuTestId={TEST_IDS.SUM_BY_SELECT_FLYOUT}
@@ -29,14 +29,13 @@ import {Query} from '@parca/parser';
29
29
  import {TEST_IDS, testId} from '@parca/test-utils';
30
30
  import {millisToProtoTimestamp, type NavigateFunction} from '@parca/utilities';
31
31
 
32
+ import {useLabelNames} from '../MatchersInput/index';
32
33
  import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
33
- import {QueryControls} from '../QueryControls';
34
- import {LabelsQueryProvider, useLabelsQueryProvider} from '../contexts/LabelsQueryProvider';
35
- import {UnifiedLabelsProvider} from '../contexts/UnifiedLabelsContext';
36
- import {useLabelNames} from '../hooks/useLabels';
34
+ import {UtilizationLabelsProvider} from '../contexts/UtilizationLabelsContext';
37
35
  import {useQueryState} from '../hooks/useQueryState';
38
36
  import useGrpcQuery from '../useGrpcQuery';
39
37
  import {MetricsGraphSection} from './MetricsGraphSection';
38
+ import {QueryControls} from './QueryControls';
40
39
  import {useAutoQuerySelector} from './useAutoQuerySelector';
41
40
 
42
41
  export interface QuerySelection {
@@ -53,9 +52,31 @@ interface ProfileSelectorFeatures {
53
52
  showMetricsGraph: boolean;
54
53
  showSumBySelector?: boolean;
55
54
  showProfileTypeSelector?: boolean;
55
+ disableExplorativeQuerying?: boolean;
56
56
  disableProfileTypesDropdown?: boolean;
57
57
  }
58
58
 
59
+ export interface UtilizationMetrics {
60
+ isSelected: boolean;
61
+ labelset: {
62
+ labels: Array<{
63
+ name: string;
64
+ value: string;
65
+ }>;
66
+ };
67
+ samples: Array<{
68
+ timestamp: number;
69
+ value: number;
70
+ }>;
71
+ }
72
+
73
+ export interface UtilizationLabels {
74
+ utilizationLabelNames?: string[];
75
+ utilizationFetchLabelValues?: (key: string) => Promise<string[]>;
76
+ utilizationLabelValues?: string[];
77
+ utilizationLabelNamesLoading?: boolean;
78
+ }
79
+
59
80
  interface ProfileSelectorProps extends ProfileSelectorFeatures {
60
81
  queryClient: QueryServiceClient;
61
82
  closeProfile: () => void;
@@ -64,6 +85,14 @@ interface ProfileSelectorProps extends ProfileSelectorFeatures {
64
85
  navigateTo: NavigateFunction;
65
86
  setDisplayHideMetricsGraphButton?: Dispatch<SetStateAction<boolean>>;
66
87
  suffix?: '_a' | '_b'; // For comparison mode
88
+ utilizationMetrics?: Array<{
89
+ name: string;
90
+ humanReadableName: string;
91
+ data: UtilizationMetrics[];
92
+ }>;
93
+ utilizationMetricsLoading?: boolean;
94
+ utilizationLabels?: UtilizationLabels;
95
+ onUtilizationSeriesSelect?: (name: string, seriesIndex: number) => void;
67
96
  onSearchHook?: () => void;
68
97
  }
69
98
 
@@ -110,11 +139,16 @@ const ProfileSelector = ({
110
139
  showMetricsGraph = true,
111
140
  showSumBySelector = true,
112
141
  showProfileTypeSelector = true,
142
+ disableExplorativeQuerying = false,
113
143
  setDisplayHideMetricsGraphButton,
114
144
  suffix,
145
+ utilizationMetrics,
146
+ utilizationMetricsLoading,
147
+ utilizationLabels,
148
+ onUtilizationSeriesSelect,
115
149
  onSearchHook,
116
150
  }: ProfileSelectorProps): JSX.Element => {
117
- const {heightStyle} = useMetricsGraphDimensions(comparing, false);
151
+ const {heightStyle} = useMetricsGraphDimensions(comparing, utilizationMetrics != null);
118
152
  const {viewComponent} = useParcaContext();
119
153
  const [queryBrowserMode, setQueryBrowserMode] = useURLState('query_browser_mode');
120
154
  const batchUpdates = useURLStateBatch();
@@ -167,7 +201,7 @@ const ProfileSelector = ({
167
201
  error,
168
202
  } = useProfileTypes(queryClient, from, to);
169
203
 
170
- const {result} = useLabelNames(queryClient, profileType.toString(), from, to);
204
+ const {result, refetch} = useLabelNames(queryClient, profileType.toString(), from, to);
171
205
 
172
206
  const labels = useMemo(() => {
173
207
  return result.response?.labelNames === undefined ? [] : result.response.labelNames;
@@ -264,108 +298,77 @@ const ProfileSelector = ({
264
298
  const sumByRef = useRef(null);
265
299
 
266
300
  return (
267
- <>
268
- <div className="mb-2 flex">
269
- <LabelsQueryProvider
270
- setMatchersString={setMatchersString}
271
- runQuery={setQueryExpression}
272
- currentQuery={query}
273
- profileType={selectedProfileName ?? profileType.toString()}
301
+ <UtilizationLabelsProvider value={{...utilizationLabels}}>
302
+ <>
303
+ <div className="mb-2 flex">
304
+ <QueryControls
305
+ showProfileTypeSelector={showProfileTypeSelector}
306
+ showSumBySelector={showSumBySelector}
307
+ disableExplorativeQuerying={disableExplorativeQuerying}
308
+ profileTypesData={profileTypesData}
309
+ profileTypesLoading={profileTypesLoading}
310
+ selectedProfileName={selectedProfileName}
311
+ setProfileName={setProfileName}
312
+ setMatchersString={setMatchersString}
313
+ setQueryExpression={setQueryExpression}
314
+ query={query}
315
+ queryBrowserRef={queryBrowserRef}
316
+ timeRangeSelection={timeRangeSelection}
317
+ setTimeRangeSelection={handleTimeRangeChange}
318
+ searchDisabled={searchDisabled}
319
+ queryBrowserMode={queryBrowserMode as string}
320
+ setQueryBrowserMode={setQueryBrowserMode}
321
+ advancedModeForQueryBrowser={advancedModeForQueryBrowser}
322
+ setAdvancedModeForQueryBrowser={setAdvancedModeForQueryBrowser}
323
+ queryClient={queryClient}
324
+ sumByRef={sumByRef}
325
+ labels={labels}
326
+ sumBySelection={draftSelection.sumBy ?? []}
327
+ sumBySelectionLoading={sumByLoading}
328
+ setUserSumBySelection={setDraftSumBy}
329
+ profileType={profileType}
330
+ profileTypesError={error}
331
+ viewComponent={viewComponent}
332
+ refreshLabelNames={refetch}
333
+ />
334
+ {comparing && (
335
+ <div>
336
+ <IconButton
337
+ onClick={() => closeProfile()}
338
+ icon={<CloseIcon />}
339
+ {...testId(TEST_IDS.COMPARE_CLOSE_BUTTON)}
340
+ />
341
+ </div>
342
+ )}
343
+ </div>
344
+ <MetricsGraphSection
345
+ showMetricsGraph={showMetricsGraph}
346
+ setDisplayHideMetricsGraphButton={setDisplayHideMetricsGraphButton}
347
+ heightStyle={
348
+ utilizationMetrics !== undefined && utilizationMetrics?.length > 0
349
+ ? 'auto'
350
+ : heightStyle
351
+ }
352
+ querySelection={querySelection}
353
+ profileSelection={profileSelection}
354
+ comparing={comparing}
355
+ sumBy={querySelection.sumBy ?? []}
356
+ defaultSumByLoading={sumByLoading}
274
357
  queryClient={queryClient}
275
- start={timeRangeSelection.getFromMs()}
276
- end={timeRangeSelection.getToMs()}
277
- suffix={suffix}
278
- >
279
- <LabelsSource>
280
- <QueryControls
281
- showProfileTypeSelector={showProfileTypeSelector}
282
- showSumBySelector={showSumBySelector}
283
- profileTypesData={profileTypesData}
284
- profileTypesLoading={profileTypesLoading}
285
- selectedProfileName={selectedProfileName}
286
- setProfileName={setProfileName}
287
- setMatchersString={setMatchersString}
288
- setQueryExpression={setQueryExpression}
289
- query={query}
290
- queryBrowserRef={queryBrowserRef}
291
- timeRangeSelection={timeRangeSelection}
292
- setTimeRangeSelection={handleTimeRangeChange}
293
- searchDisabled={searchDisabled}
294
- setQueryBrowserMode={setQueryBrowserMode}
295
- advancedModeForQueryBrowser={advancedModeForQueryBrowser}
296
- setAdvancedModeForQueryBrowser={setAdvancedModeForQueryBrowser}
297
- queryClient={queryClient}
298
- sumByRef={sumByRef}
299
- labels={labels}
300
- sumBySelection={draftSelection.sumBy ?? []}
301
- sumBySelectionLoading={sumByLoading}
302
- setUserSumBySelection={setDraftSumBy}
303
- profileType={profileType}
304
- profileTypesError={error}
305
- viewComponent={viewComponent}
306
- />
307
- </LabelsSource>
308
- </LabelsQueryProvider>
309
- {comparing && (
310
- <div>
311
- <IconButton
312
- onClick={() => closeProfile()}
313
- icon={<CloseIcon />}
314
- {...testId(TEST_IDS.COMPARE_CLOSE_BUTTON)}
315
- />
316
- </div>
317
- )}
318
- </div>
319
- <MetricsGraphSection
320
- showMetricsGraph={showMetricsGraph}
321
- setDisplayHideMetricsGraphButton={setDisplayHideMetricsGraphButton}
322
- heightStyle={heightStyle}
323
- querySelection={querySelection}
324
- profileSelection={profileSelection}
325
- comparing={comparing}
326
- sumBy={querySelection.sumBy ?? []}
327
- defaultSumByLoading={sumByLoading}
328
- queryClient={queryClient}
329
- queryExpressionString={queryExpressionString}
330
- setTimeRangeSelection={handleTimeRangeChange}
331
- selectQuery={commitDraft}
332
- setProfileSelection={setProfileSelection}
333
- query={query}
334
- setQueryExpression={setQueryExpression}
335
- setNewQueryExpression={setDraftExpression}
336
- />
337
- </>
358
+ queryExpressionString={queryExpressionString}
359
+ setTimeRangeSelection={handleTimeRangeChange}
360
+ selectQuery={commitDraft}
361
+ setProfileSelection={setProfileSelection}
362
+ query={query}
363
+ setQueryExpression={setQueryExpression}
364
+ setNewQueryExpression={setDraftExpression}
365
+ utilizationMetrics={utilizationMetrics}
366
+ utilizationMetricsLoading={utilizationMetricsLoading}
367
+ onUtilizationSeriesSelect={onUtilizationSeriesSelect}
368
+ />
369
+ </>
370
+ </UtilizationLabelsProvider>
338
371
  );
339
372
  };
340
373
 
341
374
  export default ProfileSelector;
342
-
343
- const LabelsSource = ({children}: {children: React.ReactNode}): JSX.Element => {
344
- const {
345
- labelNames,
346
- labelValues,
347
- isLabelNamesLoading,
348
- isLabelValuesLoading,
349
- refetchLabelValues,
350
- refetchLabelNames,
351
- currentLabelName,
352
- setCurrentLabelName,
353
- suffix,
354
- } = useLabelsQueryProvider();
355
-
356
- return (
357
- <UnifiedLabelsProvider
358
- labelNames={labelNames}
359
- labelValues={labelValues}
360
- isLabelNamesLoading={isLabelNamesLoading}
361
- isLabelValuesLoading={isLabelValuesLoading}
362
- refetchLabelValues={refetchLabelValues}
363
- refetchLabelNames={refetchLabelNames}
364
- currentLabelName={currentLabelName}
365
- setCurrentLabelName={setCurrentLabelName}
366
- suffix={suffix}
367
- >
368
- {children}
369
- </UnifiedLabelsProvider>
370
- );
371
- };