@parca/profile 0.19.73 → 0.19.75
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.map +1 -1
- package/dist/MatchersInput/index.js +4 -2
- package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +1 -12
- package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts.map +1 -1
- package/dist/ProfileExplorer/ProfileExplorerCompare.js +52 -11
- package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +1 -7
- package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts.map +1 -1
- package/dist/ProfileExplorer/ProfileExplorerSingle.js +4 -2
- package/dist/ProfileExplorer/index.d.ts +1 -4
- package/dist/ProfileExplorer/index.d.ts.map +1 -1
- package/dist/ProfileExplorer/index.js +11 -225
- package/dist/ProfileMetricsGraph/index.d.ts +1 -1
- package/dist/ProfileMetricsGraph/index.d.ts.map +1 -1
- package/dist/ProfileMetricsGraph/index.js +16 -20
- package/dist/ProfileSelector/MetricsGraphSection.d.ts +3 -3
- package/dist/ProfileSelector/MetricsGraphSection.d.ts.map +1 -1
- package/dist/ProfileSelector/MetricsGraphSection.js +10 -6
- package/dist/ProfileSelector/index.d.ts +2 -7
- package/dist/ProfileSelector/index.d.ts.map +1 -1
- package/dist/ProfileSelector/index.js +40 -46
- package/dist/ProfileSelector/useAutoQuerySelector.d.ts.map +1 -1
- package/dist/ProfileSelector/useAutoQuerySelector.js +19 -4
- package/dist/ProfileTypeSelector/index.d.ts.map +1 -1
- package/dist/ProfileTypeSelector/index.js +1 -1
- package/dist/ProfileView/components/ProfileFilters/filterPresets.d.ts.map +1 -1
- package/dist/ProfileView/components/ProfileFilters/filterPresets.js +75 -0
- package/dist/ProfileView/components/ViewSelector/index.d.ts.map +1 -1
- package/dist/ProfileView/components/ViewSelector/index.js +10 -4
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useResetStateOnProfileTypeChange.js +4 -2
- package/dist/ProfileView/hooks/useVisualizationState.d.ts.map +1 -1
- package/dist/ProfileView/hooks/useVisualizationState.js +20 -13
- package/dist/Table/MoreDropdown.d.ts.map +1 -1
- package/dist/Table/MoreDropdown.js +7 -3
- package/dist/Table/TableContextMenu.d.ts.map +1 -1
- package/dist/Table/TableContextMenu.js +9 -5
- package/dist/hooks/useCompareModeMeta.d.ts +10 -0
- package/dist/hooks/useCompareModeMeta.d.ts.map +1 -0
- package/dist/hooks/useCompareModeMeta.js +113 -0
- package/dist/hooks/useQueryState.d.ts +32 -0
- package/dist/hooks/useQueryState.d.ts.map +1 -0
- package/dist/hooks/useQueryState.js +285 -0
- package/dist/hooks/useQueryState.test.d.ts +2 -0
- package/dist/hooks/useQueryState.test.d.ts.map +1 -0
- package/dist/hooks/useQueryState.test.js +910 -0
- package/dist/index.d.ts +4 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -3
- package/dist/styles.css +1 -1
- package/dist/useSumBy.d.ts +7 -0
- package/dist/useSumBy.d.ts.map +1 -1
- package/dist/useSumBy.js +31 -6
- package/package.json +6 -6
- package/src/MatchersInput/index.tsx +4 -2
- package/src/ProfileExplorer/ProfileExplorerCompare.tsx +64 -46
- package/src/ProfileExplorer/ProfileExplorerSingle.tsx +7 -19
- package/src/ProfileExplorer/index.tsx +11 -339
- package/src/ProfileMetricsGraph/index.tsx +16 -20
- package/src/ProfileSelector/MetricsGraphSection.tsx +14 -10
- package/src/ProfileSelector/index.tsx +65 -83
- package/src/ProfileSelector/useAutoQuerySelector.ts +23 -5
- package/src/ProfileTypeSelector/index.tsx +3 -1
- package/src/ProfileView/components/ProfileFilters/filterPresets.ts +75 -0
- package/src/ProfileView/components/ViewSelector/index.tsx +9 -4
- package/src/ProfileView/hooks/useResetStateOnProfileTypeChange.ts +4 -2
- package/src/ProfileView/hooks/useVisualizationState.ts +25 -12
- package/src/Table/MoreDropdown.tsx +7 -3
- package/src/Table/TableContextMenu.tsx +9 -5
- package/src/hooks/useCompareModeMeta.ts +131 -0
- package/src/hooks/useQueryState.test.tsx +1202 -0
- package/src/hooks/useQueryState.ts +414 -0
- package/src/index.tsx +9 -11
- package/src/useSumBy.ts +62 -7
- package/src/ProfileExplorer/index.test.ts +0 -97
|
@@ -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 {Dispatch, SetStateAction, useEffect, useMemo, useRef, useState} from 'react';
|
|
14
|
+
import {Dispatch, SetStateAction, useCallback, useEffect, useMemo, useRef, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
17
|
|
|
@@ -28,12 +28,11 @@ import {Query} from '@parca/parser';
|
|
|
28
28
|
import {TEST_IDS, testId} from '@parca/test-utils';
|
|
29
29
|
import {millisToProtoTimestamp, type NavigateFunction} from '@parca/utilities';
|
|
30
30
|
|
|
31
|
-
import {ProfileSelection} from '..';
|
|
32
31
|
import {useLabelNames} from '../MatchersInput/index';
|
|
33
32
|
import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
|
|
34
33
|
import {UtilizationLabelsProvider} from '../contexts/UtilizationLabelsContext';
|
|
34
|
+
import {useQueryState} from '../hooks/useQueryState';
|
|
35
35
|
import useGrpcQuery from '../useGrpcQuery';
|
|
36
|
-
import {useDefaultSumBy, useSumBySelection} from '../useSumBy';
|
|
37
36
|
import {MetricsGraphSection} from './MetricsGraphSection';
|
|
38
37
|
import {QueryControls} from './QueryControls';
|
|
39
38
|
import {useAutoQuerySelector} from './useAutoQuerySelector';
|
|
@@ -79,16 +78,12 @@ export interface UtilizationLabels {
|
|
|
79
78
|
|
|
80
79
|
interface ProfileSelectorProps extends ProfileSelectorFeatures {
|
|
81
80
|
queryClient: QueryServiceClient;
|
|
82
|
-
querySelection: QuerySelection;
|
|
83
|
-
selectProfile: (source: ProfileSelection) => void;
|
|
84
|
-
selectQuery: (query: QuerySelection) => void;
|
|
85
81
|
closeProfile: () => void;
|
|
86
82
|
enforcedProfileName: string;
|
|
87
|
-
profileSelection: ProfileSelection | null;
|
|
88
83
|
comparing: boolean;
|
|
89
84
|
navigateTo: NavigateFunction;
|
|
90
85
|
setDisplayHideMetricsGraphButton?: Dispatch<SetStateAction<boolean>>;
|
|
91
|
-
suffix?:
|
|
86
|
+
suffix?: '_a' | '_b'; // For comparison mode
|
|
92
87
|
utilizationMetrics?: Array<{
|
|
93
88
|
name: string;
|
|
94
89
|
humanReadableName: string;
|
|
@@ -135,12 +130,8 @@ export const useProfileTypes = (
|
|
|
135
130
|
|
|
136
131
|
const ProfileSelector = ({
|
|
137
132
|
queryClient,
|
|
138
|
-
querySelection,
|
|
139
|
-
selectProfile,
|
|
140
|
-
selectQuery,
|
|
141
133
|
closeProfile,
|
|
142
134
|
enforcedProfileName,
|
|
143
|
-
profileSelection,
|
|
144
135
|
comparing,
|
|
145
136
|
navigateTo,
|
|
146
137
|
showMetricsGraph = true,
|
|
@@ -148,6 +139,7 @@ const ProfileSelector = ({
|
|
|
148
139
|
showProfileTypeSelector = true,
|
|
149
140
|
disableExplorativeQuerying = false,
|
|
150
141
|
setDisplayHideMetricsGraphButton,
|
|
142
|
+
suffix,
|
|
151
143
|
utilizationMetrics,
|
|
152
144
|
utilizationMetricsLoading,
|
|
153
145
|
utilizationLabels,
|
|
@@ -157,24 +149,45 @@ const ProfileSelector = ({
|
|
|
157
149
|
const {viewComponent} = useParcaContext();
|
|
158
150
|
const [queryBrowserMode, setQueryBrowserMode] = useURLState('query_browser_mode');
|
|
159
151
|
|
|
152
|
+
// Use the new useQueryState hook - reads directly from URL params
|
|
153
|
+
const {
|
|
154
|
+
querySelection,
|
|
155
|
+
draftSelection,
|
|
156
|
+
setDraftExpression,
|
|
157
|
+
setDraftTimeRange,
|
|
158
|
+
setDraftSumBy,
|
|
159
|
+
setDraftProfileName,
|
|
160
|
+
setDraftMatchers,
|
|
161
|
+
commitDraft,
|
|
162
|
+
profileSelection,
|
|
163
|
+
setProfileSelection,
|
|
164
|
+
sumByLoading,
|
|
165
|
+
} = useQueryState({suffix});
|
|
166
|
+
|
|
167
|
+
// Use draft state for local state instead of committed state
|
|
160
168
|
const [timeRangeSelection, setTimeRangeSelection] = useState(
|
|
161
|
-
DateTimeRange.fromRangeKey(
|
|
169
|
+
DateTimeRange.fromRangeKey(draftSelection.timeSelection, draftSelection.from, draftSelection.to)
|
|
162
170
|
);
|
|
163
171
|
|
|
164
|
-
const [queryExpressionString, setQueryExpressionString] = useState(
|
|
172
|
+
const [queryExpressionString, setQueryExpressionString] = useState(draftSelection.expression);
|
|
165
173
|
|
|
166
174
|
const [advancedModeForQueryBrowser, setAdvancedModeForQueryBrowser] = useState(
|
|
167
175
|
queryBrowserMode === 'advanced'
|
|
168
176
|
);
|
|
169
177
|
|
|
178
|
+
// Handler to update draft when time range changes
|
|
179
|
+
const handleTimeRangeChange = useCallback(
|
|
180
|
+
(range: DateTimeRange) => {
|
|
181
|
+
setTimeRangeSelection(range);
|
|
182
|
+
setDraftTimeRange(range.getFromMs(), range.getToMs(), range.getRangeKey());
|
|
183
|
+
},
|
|
184
|
+
[setDraftTimeRange]
|
|
185
|
+
);
|
|
186
|
+
|
|
170
187
|
const profileType = useMemo(() => {
|
|
171
188
|
return Query.parse(queryExpressionString).profileType();
|
|
172
189
|
}, [queryExpressionString]);
|
|
173
190
|
|
|
174
|
-
const selectedProfileType = useMemo(() => {
|
|
175
|
-
return Query.parse(querySelection.expression).profileType();
|
|
176
|
-
}, [querySelection.expression]);
|
|
177
|
-
|
|
178
191
|
const from = timeRangeSelection.getFromMs();
|
|
179
192
|
const to = timeRangeSelection.getToMs();
|
|
180
193
|
|
|
@@ -184,39 +197,12 @@ const ProfileSelector = ({
|
|
|
184
197
|
error,
|
|
185
198
|
} = useProfileTypes(queryClient, from, to);
|
|
186
199
|
|
|
187
|
-
const {
|
|
188
|
-
loading: labelNamesLoading,
|
|
189
|
-
result,
|
|
190
|
-
refetch,
|
|
191
|
-
} = useLabelNames(queryClient, profileType.toString(), from, to);
|
|
192
|
-
const {loading: selectedLabelNamesLoading, result: selectedLabelNamesResult} = useLabelNames(
|
|
193
|
-
queryClient,
|
|
194
|
-
selectedProfileType.toString(),
|
|
195
|
-
from,
|
|
196
|
-
to
|
|
197
|
-
);
|
|
200
|
+
const {result, refetch} = useLabelNames(queryClient, profileType.toString(), from, to);
|
|
198
201
|
|
|
199
202
|
const labels = useMemo(() => {
|
|
200
203
|
return result.response?.labelNames === undefined ? [] : result.response.labelNames;
|
|
201
204
|
}, [result]);
|
|
202
205
|
|
|
203
|
-
const selectedLabels = useMemo(() => {
|
|
204
|
-
return selectedLabelNamesResult.response?.labelNames === undefined
|
|
205
|
-
? []
|
|
206
|
-
: selectedLabelNamesResult.response.labelNames;
|
|
207
|
-
}, [selectedLabelNamesResult]);
|
|
208
|
-
|
|
209
|
-
const [sumBySelection, setUserSumBySelection, {isLoading: sumBySelectionLoading}] =
|
|
210
|
-
useSumBySelection(profileType, labelNamesLoading, labels, {
|
|
211
|
-
defaultValue: querySelection.sumBy,
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const {defaultSumBy, isLoading: defaultSumByLoading} = useDefaultSumBy(
|
|
215
|
-
selectedProfileType,
|
|
216
|
-
selectedLabelNamesLoading,
|
|
217
|
-
selectedLabels
|
|
218
|
-
);
|
|
219
|
-
|
|
220
206
|
useEffect(() => {
|
|
221
207
|
if (enforcedProfileName !== '') {
|
|
222
208
|
const [q, changed] = Query.parse(querySelection.expression).setProfileName(
|
|
@@ -240,41 +226,37 @@ const ProfileSelector = ({
|
|
|
240
226
|
enforcedProfileName !== '' ? enforcedProfileNameQuery() : Query.parse(queryExpressionString);
|
|
241
227
|
const selectedProfileName = query.profileName();
|
|
242
228
|
|
|
243
|
-
const setNewQueryExpression = (expr: string, updateTs = false): void => {
|
|
244
|
-
const query = enforcedProfileName !== '' ? enforcedProfileNameQuery() : Query.parse(expr);
|
|
245
|
-
const delta = query.profileType().delta;
|
|
246
|
-
const from = timeRangeSelection.getFromMs(updateTs);
|
|
247
|
-
const to = timeRangeSelection.getToMs(updateTs);
|
|
248
|
-
const mergeParams = delta
|
|
249
|
-
? {
|
|
250
|
-
mergeFrom: (BigInt(from) * 1_000_000n).toString(),
|
|
251
|
-
mergeTo: (BigInt(to) * 1_000_000n).toString(),
|
|
252
|
-
}
|
|
253
|
-
: {};
|
|
254
|
-
|
|
255
|
-
selectQuery({
|
|
256
|
-
expression: expr,
|
|
257
|
-
from,
|
|
258
|
-
to,
|
|
259
|
-
timeSelection: timeRangeSelection.getRangeKey(),
|
|
260
|
-
sumBy: sumBySelection,
|
|
261
|
-
...mergeParams,
|
|
262
|
-
});
|
|
263
|
-
};
|
|
264
|
-
|
|
265
229
|
const setQueryExpression = (updateTs = false): void => {
|
|
266
|
-
|
|
230
|
+
// When updateTs is true, re-evaluate the time range to current values
|
|
231
|
+
if (updateTs) {
|
|
232
|
+
// Force re-evaluation of time range (important for relative ranges like "last 15 minutes")
|
|
233
|
+
const currentFrom = timeRangeSelection.getFromMs(true);
|
|
234
|
+
const currentTo = timeRangeSelection.getToMs(true);
|
|
235
|
+
const currentRangeKey = timeRangeSelection.getRangeKey();
|
|
236
|
+
// Commit with refreshed time range
|
|
237
|
+
commitDraft({
|
|
238
|
+
from: currentFrom,
|
|
239
|
+
to: currentTo,
|
|
240
|
+
timeSelection: currentRangeKey,
|
|
241
|
+
});
|
|
242
|
+
} else {
|
|
243
|
+
// Commit the draft with existing values
|
|
244
|
+
commitDraft();
|
|
245
|
+
}
|
|
267
246
|
};
|
|
268
247
|
|
|
269
248
|
const setMatchersString = (matchers: string): void => {
|
|
270
|
-
|
|
271
|
-
|
|
249
|
+
// Update draft state only
|
|
250
|
+
setDraftMatchers(matchers);
|
|
251
|
+
setQueryExpressionString(`${selectedProfileName}{${matchers}}`);
|
|
272
252
|
};
|
|
273
253
|
|
|
274
254
|
const setProfileName = (profileName: string | undefined): void => {
|
|
275
255
|
if (profileName === undefined) {
|
|
276
256
|
return;
|
|
277
257
|
}
|
|
258
|
+
// Update draft state only
|
|
259
|
+
setDraftProfileName(profileName);
|
|
278
260
|
const [newQuery, changed] = query.setProfileName(profileName);
|
|
279
261
|
if (changed) {
|
|
280
262
|
const q = newQuery.toString();
|
|
@@ -293,9 +275,9 @@ const ProfileSelector = ({
|
|
|
293
275
|
profileTypesData,
|
|
294
276
|
setProfileName,
|
|
295
277
|
setQueryExpression,
|
|
296
|
-
querySelection
|
|
278
|
+
querySelection,
|
|
297
279
|
navigateTo,
|
|
298
|
-
loading:
|
|
280
|
+
loading: sumByLoading,
|
|
299
281
|
});
|
|
300
282
|
|
|
301
283
|
const searchDisabled =
|
|
@@ -323,7 +305,7 @@ const ProfileSelector = ({
|
|
|
323
305
|
query={query}
|
|
324
306
|
queryBrowserRef={queryBrowserRef}
|
|
325
307
|
timeRangeSelection={timeRangeSelection}
|
|
326
|
-
setTimeRangeSelection={
|
|
308
|
+
setTimeRangeSelection={handleTimeRangeChange}
|
|
327
309
|
searchDisabled={searchDisabled}
|
|
328
310
|
queryBrowserMode={queryBrowserMode as string}
|
|
329
311
|
setQueryBrowserMode={setQueryBrowserMode}
|
|
@@ -332,9 +314,9 @@ const ProfileSelector = ({
|
|
|
332
314
|
queryClient={queryClient}
|
|
333
315
|
sumByRef={sumByRef}
|
|
334
316
|
labels={labels}
|
|
335
|
-
sumBySelection={
|
|
336
|
-
sumBySelectionLoading={
|
|
337
|
-
setUserSumBySelection={
|
|
317
|
+
sumBySelection={draftSelection.sumBy ?? []}
|
|
318
|
+
sumBySelectionLoading={sumByLoading}
|
|
319
|
+
setUserSumBySelection={setDraftSumBy}
|
|
338
320
|
profileType={profileType}
|
|
339
321
|
profileTypesError={error}
|
|
340
322
|
viewComponent={viewComponent}
|
|
@@ -361,16 +343,16 @@ const ProfileSelector = ({
|
|
|
361
343
|
querySelection={querySelection}
|
|
362
344
|
profileSelection={profileSelection}
|
|
363
345
|
comparing={comparing}
|
|
364
|
-
sumBy={querySelection.sumBy ??
|
|
365
|
-
defaultSumByLoading={
|
|
346
|
+
sumBy={querySelection.sumBy ?? []}
|
|
347
|
+
defaultSumByLoading={sumByLoading}
|
|
366
348
|
queryClient={queryClient}
|
|
367
349
|
queryExpressionString={queryExpressionString}
|
|
368
|
-
setTimeRangeSelection={
|
|
369
|
-
selectQuery={
|
|
370
|
-
|
|
350
|
+
setTimeRangeSelection={handleTimeRangeChange}
|
|
351
|
+
selectQuery={commitDraft}
|
|
352
|
+
setProfileSelection={setProfileSelection}
|
|
371
353
|
query={query}
|
|
372
354
|
setQueryExpression={setQueryExpression}
|
|
373
|
-
setNewQueryExpression={
|
|
355
|
+
setNewQueryExpression={setDraftExpression}
|
|
374
356
|
utilizationMetrics={utilizationMetrics}
|
|
375
357
|
utilizationMetricsLoading={utilizationMetricsLoading}
|
|
376
358
|
onUtilizationSeriesSelect={onUtilizationSeriesSelect}
|
|
@@ -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 {useEffect} from 'react';
|
|
14
|
+
import {useEffect, useRef} from 'react';
|
|
15
15
|
|
|
16
16
|
import {ProfileTypesResponse} from '@parca/client';
|
|
17
17
|
import {selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector} from '@parca/store';
|
|
@@ -43,15 +43,32 @@ export const useAutoQuerySelector = ({
|
|
|
43
43
|
const autoQuery = useAppSelector(selectAutoQuery);
|
|
44
44
|
const dispatch = useAppDispatch();
|
|
45
45
|
const queryParams = new URLSearchParams(location.search);
|
|
46
|
-
|
|
47
|
-
const
|
|
46
|
+
const compareA = queryParams.get('compare_a');
|
|
47
|
+
const compareB = queryParams.get('compare_b');
|
|
48
|
+
const comparing = compareA === 'true' || compareB === 'true';
|
|
48
49
|
const expressionA = queryParams.get('expression_a');
|
|
50
|
+
const expressionB = queryParams.get('expression_b');
|
|
51
|
+
|
|
52
|
+
// Track if we've already set up compare mode to prevent infinite loops
|
|
53
|
+
const hasSetupCompareMode = useRef(false);
|
|
49
54
|
|
|
50
55
|
useEffect(() => {
|
|
51
56
|
if (loading) {
|
|
52
57
|
return;
|
|
53
58
|
}
|
|
54
|
-
|
|
59
|
+
|
|
60
|
+
// Only run this effect if:
|
|
61
|
+
// 1. We're in compare mode
|
|
62
|
+
// 2. expressionA exists
|
|
63
|
+
// 3. expressionB doesn't exist yet (meaning we need to set it up)
|
|
64
|
+
// 4. We haven't already set it up in this session
|
|
65
|
+
if (
|
|
66
|
+
comparing &&
|
|
67
|
+
expressionA !== null &&
|
|
68
|
+
expressionA !== undefined &&
|
|
69
|
+
expressionB === null &&
|
|
70
|
+
!hasSetupCompareMode.current
|
|
71
|
+
) {
|
|
55
72
|
if (querySelection.expression === undefined) {
|
|
56
73
|
return;
|
|
57
74
|
}
|
|
@@ -96,13 +113,14 @@ export const useAutoQuerySelector = ({
|
|
|
96
113
|
};
|
|
97
114
|
}
|
|
98
115
|
|
|
116
|
+
hasSetupCompareMode.current = true;
|
|
99
117
|
void navigateTo('/', {
|
|
100
118
|
...compareQuery,
|
|
101
119
|
search_string: '',
|
|
102
120
|
dashboard_items: ['flamegraph'],
|
|
103
121
|
});
|
|
104
122
|
}
|
|
105
|
-
}, [comparing, querySelection, navigateTo, expressionA, dispatch, loading]);
|
|
123
|
+
}, [comparing, querySelection, navigateTo, expressionA, expressionB, dispatch, loading]);
|
|
106
124
|
|
|
107
125
|
// Effect to load some initial data on load when is no selection
|
|
108
126
|
useEffect(() => {
|
|
@@ -184,7 +184,9 @@ const ProfileTypeSelector = ({
|
|
|
184
184
|
: [];
|
|
185
185
|
}, [profileTypesData, error]);
|
|
186
186
|
|
|
187
|
-
const profileLabels =
|
|
187
|
+
const profileLabels = (
|
|
188
|
+
profileNames.length > 0 ? profileNames : selectedKey != null ? [selectedKey] : []
|
|
189
|
+
).map(name => ({
|
|
188
190
|
key: name,
|
|
189
191
|
element: profileSelectElement(name, flexibleKnownProfilesDetection),
|
|
190
192
|
}));
|
|
@@ -87,6 +87,81 @@ export const filterPresets: FilterPreset[] = [
|
|
|
87
87
|
},
|
|
88
88
|
],
|
|
89
89
|
},
|
|
90
|
+
{
|
|
91
|
+
key: 'hide_cuda_internals',
|
|
92
|
+
name: 'Hide CUDA Internals',
|
|
93
|
+
description: 'Excludes CUDA and NVIDIA GPU driver internal functions from the profile',
|
|
94
|
+
filters: [
|
|
95
|
+
{
|
|
96
|
+
type: 'frame',
|
|
97
|
+
field: 'binary',
|
|
98
|
+
matchType: 'not_contains',
|
|
99
|
+
value: 'libcudnn_engines_precompiled.so',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
type: 'frame',
|
|
103
|
+
field: 'binary',
|
|
104
|
+
matchType: 'not_contains',
|
|
105
|
+
value: 'libcupti.so',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'frame',
|
|
109
|
+
field: 'binary',
|
|
110
|
+
matchType: 'not_contains',
|
|
111
|
+
value: 'libparcgpcupti.so',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
type: 'frame',
|
|
115
|
+
field: 'binary',
|
|
116
|
+
matchType: 'not_contains',
|
|
117
|
+
value: 'libcudart.so',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
type: 'frame',
|
|
121
|
+
field: 'binary',
|
|
122
|
+
matchType: 'not_contains',
|
|
123
|
+
value: 'libcuda.so',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
key: 'hide_python_internals',
|
|
129
|
+
name: 'Hide Python Internals',
|
|
130
|
+
description: 'Excludes Python interpreter internal functions from the profile',
|
|
131
|
+
filters: [
|
|
132
|
+
{
|
|
133
|
+
type: 'frame',
|
|
134
|
+
field: 'binary',
|
|
135
|
+
matchType: 'not_contains',
|
|
136
|
+
value: 'python3',
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
type: 'frame',
|
|
140
|
+
field: 'function_name',
|
|
141
|
+
matchType: 'not_equal',
|
|
142
|
+
value: '<interpreter trampoline>',
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'frame',
|
|
146
|
+
field: 'function_name',
|
|
147
|
+
matchType: 'not_equal',
|
|
148
|
+
value: '<module>',
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: 'hide_libc',
|
|
154
|
+
name: 'Hide libc',
|
|
155
|
+
description: 'Excludes C standard library functions from the profile',
|
|
156
|
+
filters: [
|
|
157
|
+
{
|
|
158
|
+
type: 'frame',
|
|
159
|
+
field: 'binary',
|
|
160
|
+
matchType: 'not_contains',
|
|
161
|
+
value: 'libc.so',
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
90
165
|
];
|
|
91
166
|
|
|
92
167
|
const presetKeys = new Set(filterPresets.map(preset => preset.key));
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import {ReactNode} from 'react';
|
|
15
15
|
|
|
16
|
-
import {useParcaContext, useURLState} from '@parca/components';
|
|
16
|
+
import {useParcaContext, useURLState, useURLStateBatch} from '@parca/components';
|
|
17
17
|
|
|
18
18
|
import {ProfileSource} from '../../../ProfileSource';
|
|
19
19
|
import Dropdown, {DropdownElement, InnerAction} from './Dropdown';
|
|
@@ -31,6 +31,7 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
|
|
|
31
31
|
);
|
|
32
32
|
const [, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
|
|
33
33
|
const {enableSourcesView, enableSandwichView} = useParcaContext();
|
|
34
|
+
const batchUpdates = useURLStateBatch();
|
|
34
35
|
|
|
35
36
|
const allItems: Array<{
|
|
36
37
|
key: string;
|
|
@@ -127,11 +128,15 @@ const ViewSelector = ({profileSource}: Props): JSX.Element => {
|
|
|
127
128
|
setDashboardItems([...dashboardItems, item.key]);
|
|
128
129
|
} else {
|
|
129
130
|
const newDashboardItems = dashboardItems.filter(v => v !== item.key);
|
|
130
|
-
setDashboardItems(newDashboardItems);
|
|
131
131
|
|
|
132
|
-
//
|
|
132
|
+
// Batch updates when removing sandwich panel to combine both URL changes
|
|
133
133
|
if (item.key === 'sandwich') {
|
|
134
|
-
|
|
134
|
+
batchUpdates(() => {
|
|
135
|
+
setDashboardItems(newDashboardItems);
|
|
136
|
+
setSandwichFunctionName(undefined);
|
|
137
|
+
});
|
|
138
|
+
} else {
|
|
139
|
+
setDashboardItems(newDashboardItems);
|
|
135
140
|
}
|
|
136
141
|
}
|
|
137
142
|
},
|
|
@@ -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 {useURLState} from '@parca/components';
|
|
14
|
+
import {useURLState, useURLStateBatch} from '@parca/components';
|
|
15
15
|
|
|
16
16
|
import {useProfileFilters} from '../components/ProfileFilters/useProfileFilters';
|
|
17
17
|
|
|
@@ -20,9 +20,11 @@ export const useResetStateOnProfileTypeChange = (): (() => void) => {
|
|
|
20
20
|
const [curPath, setCurPath] = useURLState('cur_path');
|
|
21
21
|
const {resetFilters} = useProfileFilters();
|
|
22
22
|
const [sandwichFunctionName, setSandwichFunctionName] = useURLState('sandwich_function_name');
|
|
23
|
+
const batchUpdates = useURLStateBatch();
|
|
23
24
|
|
|
24
25
|
return () => {
|
|
25
|
-
|
|
26
|
+
// Batch all URL state resets into a single navigation
|
|
27
|
+
batchUpdates(() => {
|
|
26
28
|
if (groupBy !== undefined) {
|
|
27
29
|
setGroupBy(undefined);
|
|
28
30
|
}
|
|
@@ -13,7 +13,13 @@
|
|
|
13
13
|
|
|
14
14
|
import {useCallback, useMemo} from 'react';
|
|
15
15
|
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
JSONParser,
|
|
18
|
+
JSONSerializer,
|
|
19
|
+
useURLState,
|
|
20
|
+
useURLStateBatch,
|
|
21
|
+
useURLStateCustom,
|
|
22
|
+
} from '@parca/components';
|
|
17
23
|
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
18
24
|
|
|
19
25
|
import {
|
|
@@ -69,6 +75,7 @@ export const useVisualizationState = (): {
|
|
|
69
75
|
'sandwich_function_name'
|
|
70
76
|
);
|
|
71
77
|
const resetFlameGraphState = useResetFlameGraphState();
|
|
78
|
+
const batchUpdates = useURLStateBatch();
|
|
72
79
|
|
|
73
80
|
const levelsOfProfiling = useMemo(
|
|
74
81
|
() => [
|
|
@@ -89,25 +96,31 @@ export const useVisualizationState = (): {
|
|
|
89
96
|
|
|
90
97
|
const toggleGroupBy = useCallback(
|
|
91
98
|
(key: string): void => {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
99
|
+
// Batch updates to combine setGroupBy + resetFlameGraphState into single URL navigation
|
|
100
|
+
batchUpdates(() => {
|
|
101
|
+
if (groupBy.includes(key)) {
|
|
102
|
+
setGroupBy(groupBy.filter(v => v !== key)); // remove
|
|
103
|
+
} else {
|
|
104
|
+
const filteredGroupBy = groupBy.filter(item => !levelsOfProfiling.includes(item));
|
|
105
|
+
setGroupBy([...filteredGroupBy, key]); // add
|
|
106
|
+
}
|
|
98
107
|
|
|
99
|
-
|
|
108
|
+
resetFlameGraphState();
|
|
109
|
+
});
|
|
100
110
|
},
|
|
101
|
-
[groupBy, setGroupBy, levelsOfProfiling, resetFlameGraphState]
|
|
111
|
+
[groupBy, setGroupBy, levelsOfProfiling, resetFlameGraphState, batchUpdates]
|
|
102
112
|
);
|
|
103
113
|
|
|
104
114
|
const setGroupByLabels = useCallback(
|
|
105
115
|
(labels: string[]): void => {
|
|
106
|
-
setGroupBy
|
|
116
|
+
// Batch updates to combine setGroupBy + resetFlameGraphState into single URL navigation
|
|
117
|
+
batchUpdates(() => {
|
|
118
|
+
setGroupBy(groupBy.filter(l => !l.startsWith(`${FIELD_LABELS}.`)).concat(labels));
|
|
107
119
|
|
|
108
|
-
|
|
120
|
+
resetFlameGraphState();
|
|
121
|
+
});
|
|
109
122
|
},
|
|
110
|
-
[groupBy, setGroupBy, resetFlameGraphState]
|
|
123
|
+
[groupBy, setGroupBy, resetFlameGraphState, batchUpdates]
|
|
111
124
|
);
|
|
112
125
|
|
|
113
126
|
const resetSandwichFunctionName = useCallback((): void => {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
import {Menu} from '@headlessui/react';
|
|
15
15
|
import {Icon} from '@iconify/react';
|
|
16
16
|
|
|
17
|
-
import {useParcaContext, useURLState} from '@parca/components';
|
|
17
|
+
import {useParcaContext, useURLState, useURLStateBatch} from '@parca/components';
|
|
18
18
|
|
|
19
19
|
const MoreDropdown = ({functionName}: {functionName: string}): React.JSX.Element | null => {
|
|
20
20
|
const [_, setSandwichFunctionName] = useURLState<string | undefined>('sandwich_function_name');
|
|
@@ -22,10 +22,14 @@ const MoreDropdown = ({functionName}: {functionName: string}): React.JSX.Element
|
|
|
22
22
|
alwaysReturnArray: true,
|
|
23
23
|
});
|
|
24
24
|
const {enableSandwichView} = useParcaContext();
|
|
25
|
+
const batchUpdates = useURLStateBatch();
|
|
25
26
|
|
|
26
27
|
const onSandwichViewSelect = (): void => {
|
|
27
|
-
setSandwichFunctionName
|
|
28
|
-
|
|
28
|
+
// Batch updates to combine setSandwichFunctionName + setDashboardItems into single URL navigation
|
|
29
|
+
batchUpdates(() => {
|
|
30
|
+
setSandwichFunctionName(functionName.trim());
|
|
31
|
+
setDashboardItems([...dashboardItems, 'sandwich']);
|
|
32
|
+
});
|
|
29
33
|
};
|
|
30
34
|
|
|
31
35
|
const menuItems: Array<{label: string; action: () => void}> = [];
|
|
@@ -17,7 +17,7 @@ import {Item, Menu, Submenu} from 'react-contexify';
|
|
|
17
17
|
|
|
18
18
|
import 'react-contexify/dist/ReactContexify.css';
|
|
19
19
|
|
|
20
|
-
import {useParcaContext, useURLState} from '@parca/components';
|
|
20
|
+
import {useParcaContext, useURLState, useURLStateBatch} from '@parca/components';
|
|
21
21
|
import {valueFormatter} from '@parca/utilities';
|
|
22
22
|
|
|
23
23
|
import {type Row} from '.';
|
|
@@ -47,13 +47,17 @@ const TableContextMenu = ({
|
|
|
47
47
|
alwaysReturnArray: true,
|
|
48
48
|
});
|
|
49
49
|
const {enableSandwichView, isDarkMode} = useParcaContext();
|
|
50
|
+
const batchUpdates = useURLStateBatch();
|
|
50
51
|
|
|
51
52
|
const onSandwichViewSelect = (): void => {
|
|
52
53
|
if (row?.name != null && row.name.length > 0) {
|
|
53
|
-
setSandwichFunctionName
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
// Batch updates to combine setSandwichFunctionName + setDashboardItems into single URL navigation
|
|
55
|
+
batchUpdates(() => {
|
|
56
|
+
setSandwichFunctionName(row.name.trim());
|
|
57
|
+
if (!dashboardItems.includes('sandwich')) {
|
|
58
|
+
setDashboardItems([...dashboardItems, 'sandwich']);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
57
61
|
}
|
|
58
62
|
};
|
|
59
63
|
|