@parca/profile 0.19.73 → 0.19.74
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 +4 -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/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/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/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,28 +11,22 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import {useEffect, useMemo
|
|
14
|
+
import {useEffect, useMemo} from 'react';
|
|
15
15
|
|
|
16
16
|
import {Provider} from 'react-redux';
|
|
17
17
|
|
|
18
18
|
import {QueryServiceClient} from '@parca/client';
|
|
19
|
-
import {
|
|
20
|
-
import {Query} from '@parca/parser';
|
|
19
|
+
import {KeyDownProvider, useParcaContext} from '@parca/components';
|
|
21
20
|
import {createStore} from '@parca/store';
|
|
22
|
-
import {capitalizeOnlyFirstLetter,
|
|
21
|
+
import {capitalizeOnlyFirstLetter, type NavigateFunction} from '@parca/utilities';
|
|
23
22
|
|
|
24
|
-
import {
|
|
25
|
-
import {QuerySelection} from '../ProfileSelector';
|
|
26
|
-
import {useResetFlameGraphState} from '../ProfileView/hooks/useResetFlameGraphState';
|
|
27
|
-
import {useResetStateOnProfileTypeChange} from '../ProfileView/hooks/useResetStateOnProfileTypeChange';
|
|
23
|
+
import {useCompareModeMeta} from '../hooks/useCompareModeMeta';
|
|
28
24
|
import {useHasProfileData} from '../useHasProfileData';
|
|
29
|
-
import {sumByToParam, useSumByFromParams} from '../useSumBy';
|
|
30
25
|
import ProfileExplorerCompare from './ProfileExplorerCompare';
|
|
31
26
|
import ProfileExplorerSingle from './ProfileExplorerSingle';
|
|
32
27
|
|
|
33
28
|
interface ProfileExplorerProps {
|
|
34
29
|
queryClient: QueryServiceClient;
|
|
35
|
-
queryParams: any;
|
|
36
30
|
navigateTo: NavigateFunction;
|
|
37
31
|
}
|
|
38
32
|
|
|
@@ -47,73 +41,7 @@ const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
|
|
|
47
41
|
);
|
|
48
42
|
};
|
|
49
43
|
|
|
50
|
-
|
|
51
|
-
const x = Array.isArray(expression) ? expression.join() : expression;
|
|
52
|
-
return x;
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
/* eslint-disable @typescript-eslint/naming-convention */
|
|
56
|
-
const sanitizeDateRange = (
|
|
57
|
-
time_selection_a: string,
|
|
58
|
-
from_a: number,
|
|
59
|
-
to_a: number
|
|
60
|
-
): {time_selection_a: string; from_a: number; to_a: number} => {
|
|
61
|
-
const range = DateTimeRange.fromRangeKey(time_selection_a, from_a, to_a);
|
|
62
|
-
if (from_a == null && to_a == null) {
|
|
63
|
-
from_a = range.getFromMs();
|
|
64
|
-
to_a = range.getToMs();
|
|
65
|
-
}
|
|
66
|
-
return {time_selection_a: range.getRangeKey(), from_a, to_a};
|
|
67
|
-
};
|
|
68
|
-
/* eslint-enable @typescript-eslint/naming-convention */
|
|
69
|
-
|
|
70
|
-
export const filterEmptyParams = (o: Record<string, any>): Record<string, any> => {
|
|
71
|
-
return Object.fromEntries(
|
|
72
|
-
Object.entries(o)
|
|
73
|
-
.filter(
|
|
74
|
-
([_, value]) =>
|
|
75
|
-
value !== '' && value !== undefined && (Array.isArray(value) ? value.length > 0 : true)
|
|
76
|
-
)
|
|
77
|
-
.map(([key, value]) => {
|
|
78
|
-
if (typeof value === 'string') {
|
|
79
|
-
return [key, value];
|
|
80
|
-
}
|
|
81
|
-
if (Array.isArray(value)) {
|
|
82
|
-
return [key, value];
|
|
83
|
-
}
|
|
84
|
-
return [key, value];
|
|
85
|
-
})
|
|
86
|
-
);
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const filterSuffix = (
|
|
90
|
-
o: {[key: string]: string | string[] | undefined},
|
|
91
|
-
suffix: string
|
|
92
|
-
): {[key: string]: string | string[] | undefined} =>
|
|
93
|
-
Object.fromEntries(
|
|
94
|
-
Object.entries(o)
|
|
95
|
-
.filter(([key]) => !key.endsWith(suffix))
|
|
96
|
-
.map(([key, value]) => {
|
|
97
|
-
return [key, value];
|
|
98
|
-
})
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
const swapQueryParameters = (o: {
|
|
102
|
-
[key: string]: string | string[] | undefined;
|
|
103
|
-
}): {[key: string]: string | string[] | undefined} => {
|
|
104
|
-
Object.entries(o).forEach(([key, value]) => {
|
|
105
|
-
if (key.endsWith('_b')) {
|
|
106
|
-
o[key.slice(0, -2) + '_a'] = value;
|
|
107
|
-
}
|
|
108
|
-
});
|
|
109
|
-
return o;
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const ProfileExplorerApp = ({
|
|
113
|
-
queryClient,
|
|
114
|
-
queryParams,
|
|
115
|
-
navigateTo,
|
|
116
|
-
}: ProfileExplorerProps): JSX.Element => {
|
|
44
|
+
const ProfileExplorerApp = ({queryClient, navigateTo}: ProfileExplorerProps): JSX.Element => {
|
|
117
45
|
const {
|
|
118
46
|
loading: hasProfileDataLoading,
|
|
119
47
|
data: hasProfileData,
|
|
@@ -121,6 +49,7 @@ const ProfileExplorerApp = ({
|
|
|
121
49
|
} = useHasProfileData(queryClient);
|
|
122
50
|
|
|
123
51
|
const {loader, noDataPrompt, onError, authenticationErrorMessage} = useParcaContext();
|
|
52
|
+
const {isCompareMode} = useCompareModeMeta();
|
|
124
53
|
|
|
125
54
|
useEffect(() => {
|
|
126
55
|
if (hasProfileDataError !== undefined && hasProfileDataError !== null) {
|
|
@@ -128,72 +57,6 @@ const ProfileExplorerApp = ({
|
|
|
128
57
|
}
|
|
129
58
|
}, [hasProfileDataError, onError]);
|
|
130
59
|
|
|
131
|
-
/* eslint-disable @typescript-eslint/naming-convention */
|
|
132
|
-
let {
|
|
133
|
-
from_a,
|
|
134
|
-
to_a,
|
|
135
|
-
merge_from_a,
|
|
136
|
-
merge_to_a,
|
|
137
|
-
time_selection_a,
|
|
138
|
-
compare_a,
|
|
139
|
-
sum_by_a,
|
|
140
|
-
from_b,
|
|
141
|
-
to_b,
|
|
142
|
-
merge_from_b,
|
|
143
|
-
merge_to_b,
|
|
144
|
-
time_selection_b,
|
|
145
|
-
compare_b,
|
|
146
|
-
sum_by_b,
|
|
147
|
-
} = queryParams;
|
|
148
|
-
|
|
149
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
150
|
-
const expression_a = getExpressionAsAString(queryParams.expression_a);
|
|
151
|
-
|
|
152
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
153
|
-
const expression_b = getExpressionAsAString(queryParams.expression_b);
|
|
154
|
-
|
|
155
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
156
|
-
const selection_a = getExpressionAsAString(queryParams.selection_a);
|
|
157
|
-
|
|
158
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
159
|
-
const selection_b = getExpressionAsAString(queryParams.selection_b);
|
|
160
|
-
|
|
161
|
-
/* eslint-enable @typescript-eslint/naming-convention */
|
|
162
|
-
const [profileA, setProfileA] = useState<ProfileSelection | null>(null);
|
|
163
|
-
const [profileB, setProfileB] = useState<ProfileSelection | null>(null);
|
|
164
|
-
|
|
165
|
-
const resetStateOnProfileTypeChange = useResetStateOnProfileTypeChange();
|
|
166
|
-
const resetFlameGraphState = useResetFlameGraphState();
|
|
167
|
-
|
|
168
|
-
const sumByA = useSumByFromParams(sum_by_a);
|
|
169
|
-
const sumByB = useSumByFromParams(sum_by_b);
|
|
170
|
-
|
|
171
|
-
useEffect(() => {
|
|
172
|
-
const mergeFrom = merge_from_a ?? undefined;
|
|
173
|
-
const mergeTo = merge_to_a ?? undefined;
|
|
174
|
-
const profileA = ProfileSelectionFromParams(
|
|
175
|
-
mergeFrom as string,
|
|
176
|
-
mergeTo as string,
|
|
177
|
-
selection_a
|
|
178
|
-
);
|
|
179
|
-
|
|
180
|
-
setProfileA(profileA);
|
|
181
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
182
|
-
}, [merge_from_a, merge_to_a, selection_a]);
|
|
183
|
-
|
|
184
|
-
useEffect(() => {
|
|
185
|
-
const mergeFrom = merge_from_b ?? undefined;
|
|
186
|
-
const mergeTo = merge_to_b ?? undefined;
|
|
187
|
-
const profileB = ProfileSelectionFromParams(
|
|
188
|
-
mergeFrom as string,
|
|
189
|
-
mergeTo as string,
|
|
190
|
-
selection_b
|
|
191
|
-
);
|
|
192
|
-
|
|
193
|
-
setProfileB(profileB);
|
|
194
|
-
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
195
|
-
}, [merge_from_b, merge_to_b, selection_b]);
|
|
196
|
-
|
|
197
60
|
if (hasProfileDataLoading) {
|
|
198
61
|
return <>{loader}</>;
|
|
199
62
|
}
|
|
@@ -213,201 +76,14 @@ const ProfileExplorerApp = ({
|
|
|
213
76
|
return <ErrorContent errorMessage={capitalizeOnlyFirstLetter(hasProfileDataError.message)} />;
|
|
214
77
|
}
|
|
215
78
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
from_a = sanitizedRange.from_a;
|
|
219
|
-
to_a = sanitizedRange.to_a;
|
|
220
|
-
|
|
221
|
-
if ((queryParams?.expression_a ?? '') !== '') queryParams.expression_a = safeDecode(expression_a);
|
|
222
|
-
if ((queryParams?.expression_b ?? '') !== '') queryParams.expression_b = safeDecode(expression_b);
|
|
223
|
-
|
|
224
|
-
const selectProfile = (p: ProfileSelection, suffix: string): void => {
|
|
225
|
-
return navigateTo('/', {
|
|
226
|
-
...queryParams,
|
|
227
|
-
...SuffixParams(p.HistoryParams(), suffix),
|
|
228
|
-
});
|
|
229
|
-
};
|
|
230
|
-
|
|
231
|
-
const selectProfileA = (p: ProfileSelection): void => {
|
|
232
|
-
return selectProfile(p, '_a');
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
const selectProfileB = (p: ProfileSelection): void => {
|
|
236
|
-
return selectProfile(p, '_b');
|
|
237
|
-
};
|
|
238
|
-
|
|
239
|
-
const queryA = {
|
|
240
|
-
expression: expression_a,
|
|
241
|
-
from: parseInt(from_a as string),
|
|
242
|
-
to: parseInt(to_a as string),
|
|
243
|
-
timeSelection: time_selection_a as string,
|
|
244
|
-
sumBy: sumByA,
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
// Show the SingleProfileExplorer when not comparing
|
|
248
|
-
if (compare_a !== 'true' && compare_b !== 'true') {
|
|
249
|
-
const selectQuery = (q: QuerySelection): void => {
|
|
250
|
-
const profileNameAfter = Query.parse(q.expression).profileName();
|
|
251
|
-
if (profileA != null) {
|
|
252
|
-
if (profileA.ProfileName() !== profileNameAfter) {
|
|
253
|
-
// Reset required state when the profile type changes.
|
|
254
|
-
resetStateOnProfileTypeChange();
|
|
255
|
-
} else {
|
|
256
|
-
// Reset the state when a new search is performed.
|
|
257
|
-
resetFlameGraphState();
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
const mergeParams =
|
|
262
|
-
q.mergeFrom !== undefined && q.mergeTo !== undefined
|
|
263
|
-
? {
|
|
264
|
-
merge_from_a: q.mergeFrom,
|
|
265
|
-
merge_to_a: q.mergeTo,
|
|
266
|
-
selection_a: q.expression,
|
|
267
|
-
}
|
|
268
|
-
: {};
|
|
269
|
-
return navigateTo(
|
|
270
|
-
'/',
|
|
271
|
-
// Filtering the _a suffix causes us to reset potential profile
|
|
272
|
-
// selection when running a new query.
|
|
273
|
-
filterEmptyParams({
|
|
274
|
-
...filterSuffix(queryParams, '_a'),
|
|
275
|
-
...{
|
|
276
|
-
expression_a: q.expression,
|
|
277
|
-
from_a: q.from.toString(),
|
|
278
|
-
to_a: q.to.toString(),
|
|
279
|
-
time_selection_a: q.timeSelection,
|
|
280
|
-
sum_by_a: sumByToParam(q.sumBy),
|
|
281
|
-
...mergeParams,
|
|
282
|
-
},
|
|
283
|
-
})
|
|
284
|
-
);
|
|
285
|
-
};
|
|
286
|
-
|
|
287
|
-
const selectProfile = (p: ProfileSelection): void => {
|
|
288
|
-
return navigateTo('/', {
|
|
289
|
-
...queryParams,
|
|
290
|
-
...SuffixParams(p.HistoryParams(), '_a'),
|
|
291
|
-
});
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
return (
|
|
295
|
-
<ProfileExplorerSingle
|
|
296
|
-
queryClient={queryClient}
|
|
297
|
-
query={queryA}
|
|
298
|
-
profile={profileA}
|
|
299
|
-
selectQuery={selectQuery}
|
|
300
|
-
selectProfile={selectProfile}
|
|
301
|
-
navigateTo={navigateTo}
|
|
302
|
-
/>
|
|
303
|
-
);
|
|
79
|
+
if (isCompareMode) {
|
|
80
|
+
return <ProfileExplorerCompare queryClient={queryClient} navigateTo={navigateTo} />;
|
|
304
81
|
}
|
|
305
82
|
|
|
306
|
-
|
|
307
|
-
expression: expression_b,
|
|
308
|
-
from: parseInt(from_b as string),
|
|
309
|
-
to: parseInt(to_b as string),
|
|
310
|
-
timeSelection: time_selection_b as string,
|
|
311
|
-
sumBy: sumByB,
|
|
312
|
-
};
|
|
313
|
-
|
|
314
|
-
const selectQueryA = (q: QuerySelection): void => {
|
|
315
|
-
const mergeParams =
|
|
316
|
-
q.mergeFrom !== undefined && q.mergeTo !== undefined
|
|
317
|
-
? {
|
|
318
|
-
merge_from_a: q.mergeFrom,
|
|
319
|
-
merge_to_a: q.mergeTo,
|
|
320
|
-
selection_a: q.expression,
|
|
321
|
-
}
|
|
322
|
-
: {};
|
|
323
|
-
return navigateTo(
|
|
324
|
-
'/',
|
|
325
|
-
// Filtering the _a suffix causes us to reset potential profile
|
|
326
|
-
// selection when running a new query.
|
|
327
|
-
filterEmptyParams({
|
|
328
|
-
...filterSuffix(queryParams, '_a'),
|
|
329
|
-
...{
|
|
330
|
-
compare_a: 'true',
|
|
331
|
-
expression_a: q.expression,
|
|
332
|
-
expression_b,
|
|
333
|
-
selection_b,
|
|
334
|
-
from_a: q.from.toString(),
|
|
335
|
-
to_a: q.to.toString(),
|
|
336
|
-
time_selection_a: q.timeSelection,
|
|
337
|
-
sum_by_a: sumByToParam(q.sumBy),
|
|
338
|
-
...mergeParams,
|
|
339
|
-
},
|
|
340
|
-
})
|
|
341
|
-
);
|
|
342
|
-
};
|
|
343
|
-
|
|
344
|
-
const selectQueryB = (q: QuerySelection): void => {
|
|
345
|
-
const mergeParams =
|
|
346
|
-
q.mergeFrom !== undefined && q.mergeTo !== undefined
|
|
347
|
-
? {
|
|
348
|
-
merge_from_b: q.mergeFrom,
|
|
349
|
-
merge_to_b: q.mergeTo,
|
|
350
|
-
selection_b: q.expression,
|
|
351
|
-
}
|
|
352
|
-
: {};
|
|
353
|
-
return navigateTo(
|
|
354
|
-
'/',
|
|
355
|
-
// Filtering the _b suffix causes us to reset potential profile
|
|
356
|
-
// selection when running a new query.
|
|
357
|
-
filterEmptyParams({
|
|
358
|
-
...filterSuffix(queryParams, '_b'),
|
|
359
|
-
...{
|
|
360
|
-
compare_b: 'true',
|
|
361
|
-
expression_b: q.expression,
|
|
362
|
-
expression_a,
|
|
363
|
-
selection_a,
|
|
364
|
-
from_b: q.from.toString(),
|
|
365
|
-
to_b: q.to.toString(),
|
|
366
|
-
time_selection_b: q.timeSelection,
|
|
367
|
-
sum_by_b: sumByToParam(q.sumBy),
|
|
368
|
-
...mergeParams,
|
|
369
|
-
},
|
|
370
|
-
})
|
|
371
|
-
);
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
const closeProfile = (card: string): void => {
|
|
375
|
-
let newQueryParameters = queryParams;
|
|
376
|
-
if (card === 'A') {
|
|
377
|
-
newQueryParameters = swapQueryParameters(queryParams);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
return navigateTo('/', {
|
|
381
|
-
...filterSuffix(newQueryParameters, '_b'),
|
|
382
|
-
...{
|
|
383
|
-
compare_a: 'false',
|
|
384
|
-
compare_b: 'false',
|
|
385
|
-
},
|
|
386
|
-
});
|
|
387
|
-
};
|
|
388
|
-
|
|
389
|
-
return (
|
|
390
|
-
<ProfileExplorerCompare
|
|
391
|
-
queryClient={queryClient}
|
|
392
|
-
queryA={queryA}
|
|
393
|
-
queryB={queryB}
|
|
394
|
-
profileA={profileA}
|
|
395
|
-
profileB={profileB}
|
|
396
|
-
selectQueryA={selectQueryA}
|
|
397
|
-
selectQueryB={selectQueryB}
|
|
398
|
-
selectProfileA={selectProfileA}
|
|
399
|
-
selectProfileB={selectProfileB}
|
|
400
|
-
closeProfile={closeProfile}
|
|
401
|
-
navigateTo={navigateTo}
|
|
402
|
-
/>
|
|
403
|
-
);
|
|
83
|
+
return <ProfileExplorerSingle queryClient={queryClient} navigateTo={navigateTo} />;
|
|
404
84
|
};
|
|
405
85
|
|
|
406
|
-
const ProfileExplorer = ({
|
|
407
|
-
queryClient,
|
|
408
|
-
queryParams,
|
|
409
|
-
navigateTo,
|
|
410
|
-
}: ProfileExplorerProps): JSX.Element => {
|
|
86
|
+
const ProfileExplorer = ({queryClient, navigateTo}: ProfileExplorerProps): JSX.Element => {
|
|
411
87
|
const {additionalFlamegraphColorProfiles} = useParcaContext();
|
|
412
88
|
|
|
413
89
|
const {store: reduxStore} = useMemo(() => {
|
|
@@ -417,11 +93,7 @@ const ProfileExplorer = ({
|
|
|
417
93
|
return (
|
|
418
94
|
<Provider store={reduxStore}>
|
|
419
95
|
<KeyDownProvider>
|
|
420
|
-
<ProfileExplorerApp
|
|
421
|
-
queryClient={queryClient}
|
|
422
|
-
queryParams={queryParams}
|
|
423
|
-
navigateTo={navigateTo}
|
|
424
|
-
/>
|
|
96
|
+
<ProfileExplorerApp queryClient={queryClient} navigateTo={navigateTo} />
|
|
425
97
|
</KeyDownProvider>
|
|
426
98
|
</Provider>
|
|
427
99
|
);
|
|
@@ -77,35 +77,32 @@ const createProfileContextMenuItems = (
|
|
|
77
77
|
label: 'Add to query',
|
|
78
78
|
icon: 'material-symbols:add',
|
|
79
79
|
createDynamicItems: (closestPoint, _series) => {
|
|
80
|
+
const noLabelsAvailable = [
|
|
81
|
+
{
|
|
82
|
+
id: 'no-labels-available',
|
|
83
|
+
label: 'No labels available',
|
|
84
|
+
icon: 'ph:warning',
|
|
85
|
+
disabled: () => true,
|
|
86
|
+
onClick: () => {}, // No-op for disabled item
|
|
87
|
+
},
|
|
88
|
+
];
|
|
80
89
|
if (closestPoint == null || data.length === 0 || data[closestPoint.seriesIndex] == null) {
|
|
81
|
-
return
|
|
82
|
-
{
|
|
83
|
-
id: 'no-labels-available',
|
|
84
|
-
label: 'No labels available',
|
|
85
|
-
icon: 'ph:warning',
|
|
86
|
-
disabled: () => true,
|
|
87
|
-
onClick: () => {}, // No-op for disabled item
|
|
88
|
-
},
|
|
89
|
-
];
|
|
90
|
+
return noLabelsAvailable;
|
|
90
91
|
}
|
|
91
92
|
|
|
92
93
|
const originalSeriesData = data[closestPoint.seriesIndex];
|
|
93
94
|
if (originalSeriesData.labelset?.labels == null) {
|
|
94
|
-
return
|
|
95
|
-
{
|
|
96
|
-
id: 'no-labels-available',
|
|
97
|
-
label: 'No labels available',
|
|
98
|
-
icon: 'ph:warning',
|
|
99
|
-
disabled: () => true,
|
|
100
|
-
onClick: () => {}, // No-op for disabled item
|
|
101
|
-
},
|
|
102
|
-
];
|
|
95
|
+
return noLabelsAvailable;
|
|
103
96
|
}
|
|
104
97
|
|
|
105
98
|
const labels = originalSeriesData.labelset.labels.filter(
|
|
106
99
|
(label: Label) => label.name !== '__name__'
|
|
107
100
|
);
|
|
108
101
|
|
|
102
|
+
if (labels.length === 0) {
|
|
103
|
+
return noLabelsAvailable;
|
|
104
|
+
}
|
|
105
|
+
|
|
109
106
|
return labels.map((label: Label) => ({
|
|
110
107
|
id: `add-label-${label.name}`,
|
|
111
108
|
label: (
|
|
@@ -207,13 +204,12 @@ const ProfileMetricsGraph = ({
|
|
|
207
204
|
onPointClick,
|
|
208
205
|
comparing = false,
|
|
209
206
|
sumBy,
|
|
210
|
-
sumByLoading,
|
|
211
207
|
}: ProfileMetricsGraphProps): JSX.Element => {
|
|
212
208
|
const {
|
|
213
209
|
isLoading: metricsGraphLoading,
|
|
214
210
|
response,
|
|
215
211
|
error,
|
|
216
|
-
} = useQueryRange(queryClient, queryExpression, from, to, sumBy,
|
|
212
|
+
} = useQueryRange(queryClient, queryExpression, from, to, sumBy, queryExpression === '');
|
|
217
213
|
const {onError, perf, authenticationErrorMessage, isDarkMode, timezone} = useParcaContext();
|
|
218
214
|
const {width, height, margin, heightStyle} = useMetricsGraphDimensions(comparing);
|
|
219
215
|
const [showAllSeriesForResponse, setShowAllSeriesForResponse] = useState<typeof response | null>(
|
|
@@ -14,10 +14,10 @@
|
|
|
14
14
|
import cx from 'classnames';
|
|
15
15
|
|
|
16
16
|
import {Label, QueryServiceClient} from '@parca/client';
|
|
17
|
-
import {DateTimeRange} from '@parca/components';
|
|
17
|
+
import {DateTimeRange, useURLStateBatch} from '@parca/components';
|
|
18
18
|
import {Query} from '@parca/parser';
|
|
19
19
|
|
|
20
|
-
import {
|
|
20
|
+
import {ProfileSelection} from '..';
|
|
21
21
|
import UtilizationMetricsGraph from '../MetricsGraph/UtilizationMetrics';
|
|
22
22
|
import AreaChart from '../MetricsGraph/UtilizationMetrics/Throughput';
|
|
23
23
|
import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
|
|
@@ -37,9 +37,9 @@ interface MetricsGraphSectionProps {
|
|
|
37
37
|
queryExpressionString: string;
|
|
38
38
|
setTimeRangeSelection: (range: DateTimeRange) => void;
|
|
39
39
|
selectQuery: (query: QuerySelection) => void;
|
|
40
|
-
|
|
40
|
+
setProfileSelection: (mergeFrom: bigint, mergeTo: bigint, query: Query) => void;
|
|
41
41
|
query: Query;
|
|
42
|
-
setNewQueryExpression: (queryExpression: string) => void;
|
|
42
|
+
setNewQueryExpression: (queryExpression: string, commit?: boolean) => void;
|
|
43
43
|
setQueryExpression: (updateTs?: boolean) => void;
|
|
44
44
|
utilizationMetrics?: Array<{
|
|
45
45
|
name: string;
|
|
@@ -63,7 +63,7 @@ export function MetricsGraphSection({
|
|
|
63
63
|
queryExpressionString,
|
|
64
64
|
setTimeRangeSelection,
|
|
65
65
|
selectQuery,
|
|
66
|
-
|
|
66
|
+
setProfileSelection,
|
|
67
67
|
query,
|
|
68
68
|
setNewQueryExpression,
|
|
69
69
|
utilizationMetrics,
|
|
@@ -71,6 +71,7 @@ export function MetricsGraphSection({
|
|
|
71
71
|
onUtilizationSeriesSelect,
|
|
72
72
|
}: MetricsGraphSectionProps): JSX.Element {
|
|
73
73
|
const resetStateOnSeriesChange = useResetStateOnSeriesChange();
|
|
74
|
+
const batchUpdates = useURLStateBatch();
|
|
74
75
|
const handleTimeRangeChange = (range: DateTimeRange): void => {
|
|
75
76
|
const from = range.getFromMs();
|
|
76
77
|
const to = range.getToMs();
|
|
@@ -118,7 +119,8 @@ export function MetricsGraphSection({
|
|
|
118
119
|
|
|
119
120
|
if (hasChanged) {
|
|
120
121
|
// TODO: Change this to store the query object
|
|
121
|
-
|
|
122
|
+
// Pass commit: true to immediately apply the filter when clicking on metrics graph labels
|
|
123
|
+
setNewQueryExpression(newQuery.toString(), true);
|
|
122
124
|
}
|
|
123
125
|
};
|
|
124
126
|
|
|
@@ -129,6 +131,7 @@ export function MetricsGraphSection({
|
|
|
129
131
|
duration: number
|
|
130
132
|
): void => {
|
|
131
133
|
let query = Query.parse(queryExpression);
|
|
134
|
+
|
|
132
135
|
labels.forEach(l => {
|
|
133
136
|
const [newQuery, updated] = query.setMatcher(l.name, l.value);
|
|
134
137
|
if (updated) {
|
|
@@ -138,9 +141,10 @@ export function MetricsGraphSection({
|
|
|
138
141
|
|
|
139
142
|
const mergeFrom = timestamp;
|
|
140
143
|
const mergeTo = query.profileType().delta ? mergeFrom + BigInt(duration) : mergeFrom;
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
batchUpdates(() => {
|
|
145
|
+
resetStateOnSeriesChange(); // reset some state when a new series is selected
|
|
146
|
+
setProfileSelection(mergeFrom, mergeTo, query);
|
|
147
|
+
});
|
|
144
148
|
};
|
|
145
149
|
|
|
146
150
|
const UtilizationGraphToShow = ({
|
|
@@ -235,7 +239,7 @@ export function MetricsGraphSection({
|
|
|
235
239
|
{showMetricsGraph && (
|
|
236
240
|
<>
|
|
237
241
|
<div style={{height: heightStyle}}>
|
|
238
|
-
{querySelection.expression !== '' &&
|
|
242
|
+
{(querySelection.expression !== '' || defaultSumByLoading) &&
|
|
239
243
|
querySelection.from !== undefined &&
|
|
240
244
|
querySelection.to !== undefined ? (
|
|
241
245
|
<>
|