@parca/profile 0.16.334 → 0.16.336
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/ProfileIcicleGraph/ActionButtons/GroupByDropdown.d.ts +6 -0
- package/dist/ProfileIcicleGraph/ActionButtons/GroupByDropdown.js +57 -0
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.d.ts +10 -0
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.js +23 -0
- package/dist/ProfileIcicleGraph/ActionButtons/SortBySelect.d.ts +7 -0
- package/dist/ProfileIcicleGraph/ActionButtons/SortBySelect.js +44 -0
- package/dist/ProfileIcicleGraph/index.d.ts +2 -1
- package/dist/ProfileIcicleGraph/index.js +19 -86
- package/dist/ProfileMetricsGraph/index.js +10 -8
- package/dist/ProfileView/VisualizationPanel.js +1 -1
- package/dist/ProfileView/index.js +6 -30
- package/dist/SourceView/index.js +5 -4
- package/dist/Table/ColumnsVisibility.d.ts +9 -0
- package/dist/Table/ColumnsVisibility.js +22 -0
- package/dist/Table/index.d.ts +13 -0
- package/dist/Table/index.js +13 -12
- package/dist/styles.css +1 -1
- package/package.json +6 -5
- package/src/ProfileIcicleGraph/ActionButtons/GroupByDropdown.tsx +127 -0
- package/src/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.tsx +123 -0
- package/src/ProfileIcicleGraph/ActionButtons/SortBySelect.tsx +80 -0
- package/src/ProfileIcicleGraph/index.tsx +123 -341
- package/src/ProfileMetricsGraph/index.tsx +37 -21
- package/src/ProfileView/VisualizationPanel.tsx +19 -8
- package/src/ProfileView/index.tsx +52 -81
- package/src/SourceView/index.tsx +23 -8
- package/src/Table/ColumnsVisibility.tsx +87 -0
- package/src/Table/index.tsx +53 -86
- package/dist/QueryBrowser/index.d.ts +0 -12
- package/dist/QueryBrowser/index.js +0 -51
|
@@ -11,28 +11,30 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
|
-
import React, {
|
|
14
|
+
import React, {useCallback, useEffect, useMemo} from 'react';
|
|
15
15
|
|
|
16
|
-
import {Menu, Transition} from '@headlessui/react';
|
|
17
16
|
import {Icon} from '@iconify/react';
|
|
17
|
+
import {AnimatePresence, motion} from 'framer-motion';
|
|
18
18
|
|
|
19
19
|
import {Flamegraph, FlamegraphArrow} from '@parca/client';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
Button,
|
|
22
|
+
IcicleActionButtonPlaceholder,
|
|
23
|
+
IcicleGraphSkeleton,
|
|
24
|
+
IconButton,
|
|
25
|
+
useParcaContext,
|
|
26
|
+
useURLState,
|
|
27
|
+
} from '@parca/components';
|
|
21
28
|
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
22
29
|
import {capitalizeOnlyFirstLetter, divide, type NavigateFunction} from '@parca/utilities';
|
|
23
30
|
|
|
24
31
|
import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
|
|
25
32
|
import DiffLegend from '../components/DiffLegend';
|
|
33
|
+
import GroupByDropdown from './ActionButtons/GroupByDropdown';
|
|
34
|
+
import RuntimeFilterDropdown from './ActionButtons/RuntimeFilterDropdown';
|
|
35
|
+
import SortBySelect from './ActionButtons/SortBySelect';
|
|
26
36
|
import IcicleGraph from './IcicleGraph';
|
|
27
|
-
import IcicleGraphArrow, {
|
|
28
|
-
FIELD_CUMULATIVE,
|
|
29
|
-
FIELD_DIFF,
|
|
30
|
-
FIELD_FUNCTION_FILE_NAME,
|
|
31
|
-
FIELD_FUNCTION_NAME,
|
|
32
|
-
FIELD_LABELS,
|
|
33
|
-
FIELD_LOCATION_ADDRESS,
|
|
34
|
-
FIELD_MAPPING_FILE,
|
|
35
|
-
} from './IcicleGraphArrow';
|
|
37
|
+
import IcicleGraphArrow, {FIELD_FUNCTION_NAME} from './IcicleGraphArrow';
|
|
36
38
|
|
|
37
39
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
38
40
|
|
|
@@ -51,13 +53,20 @@ interface ProfileIcicleGraphProps {
|
|
|
51
53
|
loading: boolean;
|
|
52
54
|
setActionButtons?: (buttons: React.JSX.Element) => void;
|
|
53
55
|
error?: any;
|
|
56
|
+
isHalfScreen: boolean;
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
|
|
57
60
|
return <div className="flex justify-center p-10">{errorMessage}</div>;
|
|
58
61
|
};
|
|
59
62
|
|
|
60
|
-
const ShowHideLegendButton = ({
|
|
63
|
+
const ShowHideLegendButton = ({
|
|
64
|
+
navigateTo,
|
|
65
|
+
isHalfScreen,
|
|
66
|
+
}: {
|
|
67
|
+
navigateTo?: NavigateFunction;
|
|
68
|
+
isHalfScreen: boolean;
|
|
69
|
+
}): JSX.Element => {
|
|
61
70
|
const [colorStackLegend, setStoreColorStackLegend] = useURLState({
|
|
62
71
|
param: 'color_stack_legend',
|
|
63
72
|
navigateTo,
|
|
@@ -81,14 +90,25 @@ const ShowHideLegendButton = ({navigateTo}: {navigateTo?: NavigateFunction}): JS
|
|
|
81
90
|
return (
|
|
82
91
|
<>
|
|
83
92
|
{colorProfileName === 'default' || compareMode ? null : (
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
93
|
+
<>
|
|
94
|
+
{isHalfScreen ? (
|
|
95
|
+
<IconButton
|
|
96
|
+
className="rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center !py-2 !px-3 cursor-pointer min-h-[38px]"
|
|
97
|
+
icon={isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye'}
|
|
98
|
+
toolTipText={isColorStackLegendEnabled ? 'Hide legend' : 'Show legend'}
|
|
99
|
+
onClick={() => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true')}
|
|
100
|
+
/>
|
|
101
|
+
) : (
|
|
102
|
+
<Button
|
|
103
|
+
className="gap-2 w-max"
|
|
104
|
+
variant="neutral"
|
|
105
|
+
onClick={() => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true')}
|
|
106
|
+
>
|
|
107
|
+
{isColorStackLegendEnabled ? 'Hide legend' : 'Show legend'}
|
|
108
|
+
<Icon icon={isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye'} width={20} />
|
|
109
|
+
</Button>
|
|
110
|
+
)}
|
|
111
|
+
</>
|
|
92
112
|
)}
|
|
93
113
|
</>
|
|
94
114
|
);
|
|
@@ -173,110 +193,6 @@ const GroupAndSortActionButtons = ({navigateTo}: {navigateTo?: NavigateFunction}
|
|
|
173
193
|
);
|
|
174
194
|
};
|
|
175
195
|
|
|
176
|
-
const RuntimeToggle = ({
|
|
177
|
-
id,
|
|
178
|
-
state,
|
|
179
|
-
toggle,
|
|
180
|
-
label,
|
|
181
|
-
description,
|
|
182
|
-
}: {
|
|
183
|
-
id: string;
|
|
184
|
-
state: boolean;
|
|
185
|
-
toggle: () => void;
|
|
186
|
-
label: string;
|
|
187
|
-
description: string;
|
|
188
|
-
}): JSX.Element => {
|
|
189
|
-
return (
|
|
190
|
-
<div key={id} className="relative flex items-start">
|
|
191
|
-
<div className="flex h-6 items-center">
|
|
192
|
-
<input
|
|
193
|
-
id={id}
|
|
194
|
-
name={id}
|
|
195
|
-
type="checkbox"
|
|
196
|
-
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
|
|
197
|
-
checked={state}
|
|
198
|
-
onChange={() => toggle()}
|
|
199
|
-
/>
|
|
200
|
-
</div>
|
|
201
|
-
<div className="ml-3 text-sm leading-6">
|
|
202
|
-
<label htmlFor={id} className="font-medium text-gray-900 dark:text-gray-200">
|
|
203
|
-
{label}
|
|
204
|
-
</label>
|
|
205
|
-
<p className="text-gray-500 dark:text-gray-400">{description}</p>
|
|
206
|
-
</div>
|
|
207
|
-
</div>
|
|
208
|
-
);
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
const RuntimeFilterDropdown = ({
|
|
212
|
-
showRuntimeRuby,
|
|
213
|
-
toggleShowRuntimeRuby,
|
|
214
|
-
showRuntimePython,
|
|
215
|
-
toggleShowRuntimePython,
|
|
216
|
-
showInterpretedOnly,
|
|
217
|
-
toggleShowInterpretedOnly,
|
|
218
|
-
}: {
|
|
219
|
-
showRuntimeRuby: boolean;
|
|
220
|
-
toggleShowRuntimeRuby: () => void;
|
|
221
|
-
showRuntimePython: boolean;
|
|
222
|
-
toggleShowRuntimePython: () => void;
|
|
223
|
-
showInterpretedOnly: boolean;
|
|
224
|
-
toggleShowInterpretedOnly: () => void;
|
|
225
|
-
}): React.JSX.Element => {
|
|
226
|
-
return (
|
|
227
|
-
<div>
|
|
228
|
-
<label className="text-sm">Runtimes</label>
|
|
229
|
-
<Menu as="div" className="relative text-left">
|
|
230
|
-
<div>
|
|
231
|
-
<Menu.Button className="relative w-full cursor-default rounded-md border bg-white py-2 pl-3 pr-10 text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm">
|
|
232
|
-
<span className="ml-3 block overflow-x-hidden text-ellipsis">Runtimes</span>
|
|
233
|
-
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400">
|
|
234
|
-
<Icon icon="heroicons:chevron-down-20-solid" aria-hidden="true" />
|
|
235
|
-
</span>
|
|
236
|
-
</Menu.Button>
|
|
237
|
-
</div>
|
|
238
|
-
|
|
239
|
-
<Transition
|
|
240
|
-
as={Fragment}
|
|
241
|
-
leave="transition ease-in duration-100"
|
|
242
|
-
leaveFrom="opacity-100"
|
|
243
|
-
leaveTo="opacity-0"
|
|
244
|
-
>
|
|
245
|
-
<Menu.Items className="absolute left-0 z-10 mt-1 min-w-[400px] overflow-auto rounded-md bg-gray-50 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none dark:border-gray-600 dark:bg-gray-900 dark:ring-white dark:ring-opacity-20 sm:text-sm">
|
|
246
|
-
<div className="p-4">
|
|
247
|
-
<fieldset>
|
|
248
|
-
<div className="space-y-5">
|
|
249
|
-
<RuntimeToggle
|
|
250
|
-
id="show-runtime-ruby"
|
|
251
|
-
state={showRuntimeRuby}
|
|
252
|
-
toggle={toggleShowRuntimeRuby}
|
|
253
|
-
label="Ruby"
|
|
254
|
-
description="Show Ruby runtime functions."
|
|
255
|
-
/>
|
|
256
|
-
<RuntimeToggle
|
|
257
|
-
id="show-runtime-python"
|
|
258
|
-
state={showRuntimePython}
|
|
259
|
-
toggle={toggleShowRuntimePython}
|
|
260
|
-
label="Python"
|
|
261
|
-
description="Show Python runtime functions."
|
|
262
|
-
/>
|
|
263
|
-
<RuntimeToggle
|
|
264
|
-
id="show-interpreted-only"
|
|
265
|
-
state={showInterpretedOnly}
|
|
266
|
-
toggle={toggleShowInterpretedOnly}
|
|
267
|
-
label="Interpreted Only"
|
|
268
|
-
description="Show only interpreted functions."
|
|
269
|
-
/>
|
|
270
|
-
</div>
|
|
271
|
-
</fieldset>
|
|
272
|
-
</div>
|
|
273
|
-
</Menu.Items>
|
|
274
|
-
</Transition>
|
|
275
|
-
</Menu>
|
|
276
|
-
</div>
|
|
277
|
-
);
|
|
278
|
-
};
|
|
279
|
-
|
|
280
196
|
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
281
197
|
graph,
|
|
282
198
|
arrow,
|
|
@@ -290,8 +206,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
290
206
|
setActionButtons,
|
|
291
207
|
error,
|
|
292
208
|
width,
|
|
209
|
+
isHalfScreen,
|
|
293
210
|
}: ProfileIcicleGraphProps): JSX.Element {
|
|
294
|
-
const {
|
|
211
|
+
const {onError, authenticationErrorMessage, isDarkMode} = useParcaContext();
|
|
295
212
|
const {compareMode} = useProfileViewContext();
|
|
296
213
|
|
|
297
214
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
@@ -330,28 +247,50 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
330
247
|
}, [graph, arrow, filtered, total]);
|
|
331
248
|
|
|
332
249
|
useEffect(() => {
|
|
250
|
+
if (loading && setActionButtons !== undefined) {
|
|
251
|
+
setActionButtons(<IcicleActionButtonPlaceholder isHalfScreen={isHalfScreen} />);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
333
255
|
if (setActionButtons === undefined) {
|
|
334
256
|
return;
|
|
335
257
|
}
|
|
258
|
+
|
|
336
259
|
setActionButtons(
|
|
337
260
|
<div className="flex w-full justify-end gap-2 pb-2">
|
|
338
261
|
<div className="ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end">
|
|
339
262
|
{arrow !== undefined && <GroupAndSortActionButtons navigateTo={navigateTo} />}
|
|
340
|
-
<ShowHideLegendButton navigateTo={navigateTo} />
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
263
|
+
<ShowHideLegendButton isHalfScreen={isHalfScreen} navigateTo={navigateTo} />
|
|
264
|
+
{isHalfScreen ? (
|
|
265
|
+
<IconButton
|
|
266
|
+
icon="system-uicons:reset"
|
|
267
|
+
disabled={curPath.length === 0}
|
|
268
|
+
toolTipText="Reset View"
|
|
269
|
+
onClick={() => setNewCurPath([])}
|
|
270
|
+
className="rounded-md focus:outline-none focus:ring-2 focus:ring-offset-2 items-center flex border border-gray-200 dark:border-gray-600 dark:text-white justify-center py-2 px-3 cursor-pointer min-h-[38px]"
|
|
271
|
+
/>
|
|
272
|
+
) : (
|
|
273
|
+
<Button
|
|
274
|
+
variant="neutral"
|
|
275
|
+
className="gap-2 w-max"
|
|
276
|
+
onClick={() => setNewCurPath([])}
|
|
277
|
+
disabled={curPath.length === 0}
|
|
278
|
+
>
|
|
279
|
+
Reset View
|
|
280
|
+
<Icon icon="system-uicons:reset" width={20} />
|
|
281
|
+
</Button>
|
|
282
|
+
)}
|
|
348
283
|
</div>
|
|
349
284
|
</div>
|
|
350
285
|
);
|
|
351
|
-
}, [navigateTo, arrow, curPath, setNewCurPath, setActionButtons]);
|
|
286
|
+
}, [navigateTo, arrow, curPath, setNewCurPath, setActionButtons, loading, isHalfScreen]);
|
|
352
287
|
|
|
353
288
|
if (loading) {
|
|
354
|
-
return
|
|
289
|
+
return (
|
|
290
|
+
<div className="h-auto overflow-clip">
|
|
291
|
+
<IcicleGraphSkeleton isHalfScreen={isHalfScreen} isDarkMode={isDarkMode} />
|
|
292
|
+
</div>
|
|
293
|
+
);
|
|
355
294
|
}
|
|
356
295
|
|
|
357
296
|
if (error != null) {
|
|
@@ -375,212 +314,55 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
375
314
|
}
|
|
376
315
|
|
|
377
316
|
return (
|
|
378
|
-
<
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
) : (
|
|
414
|
-
<></>
|
|
415
|
-
)}
|
|
416
|
-
values.{' '}
|
|
417
|
-
</p>
|
|
418
|
-
</div>
|
|
419
|
-
);
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
const groupByOptions = [
|
|
423
|
-
{
|
|
424
|
-
value: FIELD_FUNCTION_NAME,
|
|
425
|
-
label: 'Function Name',
|
|
426
|
-
description: 'Stacktraces are grouped by function names.',
|
|
427
|
-
disabled: true,
|
|
428
|
-
},
|
|
429
|
-
{
|
|
430
|
-
value: FIELD_LABELS,
|
|
431
|
-
label: 'Labels',
|
|
432
|
-
description: 'Stacktraces are grouped by pprof labels.',
|
|
433
|
-
disabled: false,
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
value: FIELD_FUNCTION_FILE_NAME,
|
|
437
|
-
label: 'Filename',
|
|
438
|
-
description: 'Stacktraces are grouped by filenames.',
|
|
439
|
-
disabled: false,
|
|
440
|
-
},
|
|
441
|
-
{
|
|
442
|
-
value: FIELD_LOCATION_ADDRESS,
|
|
443
|
-
label: 'Address',
|
|
444
|
-
description: 'Stacktraces are grouped by addresses.',
|
|
445
|
-
disabled: false,
|
|
446
|
-
},
|
|
447
|
-
{
|
|
448
|
-
value: FIELD_MAPPING_FILE,
|
|
449
|
-
label: 'Binary',
|
|
450
|
-
description: 'Stacktraces are grouped by binaries.',
|
|
451
|
-
disabled: false,
|
|
452
|
-
},
|
|
453
|
-
];
|
|
454
|
-
|
|
455
|
-
const GroupByDropdown = ({
|
|
456
|
-
groupBy,
|
|
457
|
-
toggleGroupBy,
|
|
458
|
-
}: {
|
|
459
|
-
groupBy: string[];
|
|
460
|
-
toggleGroupBy: (key: string) => void;
|
|
461
|
-
}): React.JSX.Element => {
|
|
462
|
-
const label =
|
|
463
|
-
groupBy.length === 0
|
|
464
|
-
? 'Nothing'
|
|
465
|
-
: groupBy.length === 1
|
|
466
|
-
? groupByOptions.find(option => option.value === groupBy[0])?.label
|
|
467
|
-
: 'Multiple';
|
|
468
|
-
|
|
469
|
-
return (
|
|
470
|
-
<div>
|
|
471
|
-
<label className="text-sm">Group</label>
|
|
472
|
-
<Menu as="div" className="relative text-left">
|
|
473
|
-
<div>
|
|
474
|
-
<Menu.Button className="relative w-full cursor-default rounded-md border bg-white py-2 pl-3 pr-10 text-left text-sm shadow-sm focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 dark:border-gray-600 dark:bg-gray-900 sm:text-sm">
|
|
475
|
-
<span className="ml-3 block overflow-x-hidden text-ellipsis">{label}</span>
|
|
476
|
-
<span className="pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400">
|
|
477
|
-
<Icon icon="heroicons:chevron-down-20-solid" aria-hidden="true" />
|
|
478
|
-
</span>
|
|
479
|
-
</Menu.Button>
|
|
317
|
+
<AnimatePresence>
|
|
318
|
+
<motion.div
|
|
319
|
+
className="relative h-full w-full"
|
|
320
|
+
key="icicle-graph-loaded"
|
|
321
|
+
initial={{opacity: 0}}
|
|
322
|
+
animate={{opacity: 1}}
|
|
323
|
+
transition={{duration: 0.5}}
|
|
324
|
+
>
|
|
325
|
+
{compareMode ? <DiffLegend /> : null}
|
|
326
|
+
<div className="min-h-48">
|
|
327
|
+
{graph !== undefined && (
|
|
328
|
+
<IcicleGraph
|
|
329
|
+
width={width}
|
|
330
|
+
graph={graph}
|
|
331
|
+
total={total}
|
|
332
|
+
filtered={filtered}
|
|
333
|
+
curPath={curPath}
|
|
334
|
+
setCurPath={setNewCurPath}
|
|
335
|
+
sampleUnit={sampleUnit}
|
|
336
|
+
navigateTo={navigateTo}
|
|
337
|
+
/>
|
|
338
|
+
)}
|
|
339
|
+
{arrow !== undefined && (
|
|
340
|
+
<IcicleGraphArrow
|
|
341
|
+
width={width}
|
|
342
|
+
arrow={arrow}
|
|
343
|
+
total={total}
|
|
344
|
+
filtered={filtered}
|
|
345
|
+
curPath={curPath}
|
|
346
|
+
setCurPath={setNewCurPath}
|
|
347
|
+
sampleUnit={sampleUnit}
|
|
348
|
+
navigateTo={navigateTo}
|
|
349
|
+
sortBy={storeSortBy as string}
|
|
350
|
+
/>
|
|
351
|
+
)}
|
|
480
352
|
</div>
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
<div className="flex h-6 items-center">
|
|
495
|
-
<input
|
|
496
|
-
id={value}
|
|
497
|
-
name={value}
|
|
498
|
-
type="checkbox"
|
|
499
|
-
disabled={disabled}
|
|
500
|
-
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600"
|
|
501
|
-
checked={groupBy.includes(value)}
|
|
502
|
-
onChange={() => toggleGroupBy(value)}
|
|
503
|
-
/>
|
|
504
|
-
</div>
|
|
505
|
-
<div className="ml-3 text-sm leading-6">
|
|
506
|
-
<label
|
|
507
|
-
htmlFor={value}
|
|
508
|
-
className="font-medium text-gray-900 dark:text-gray-200"
|
|
509
|
-
>
|
|
510
|
-
{label}
|
|
511
|
-
</label>
|
|
512
|
-
<p className="text-gray-500 dark:text-gray-400">{description}</p>
|
|
513
|
-
</div>
|
|
514
|
-
</div>
|
|
515
|
-
))}
|
|
516
|
-
</div>
|
|
517
|
-
</fieldset>
|
|
518
|
-
</div>
|
|
519
|
-
</Menu.Items>
|
|
520
|
-
</Transition>
|
|
521
|
-
</Menu>
|
|
522
|
-
</div>
|
|
523
|
-
);
|
|
524
|
-
};
|
|
525
|
-
|
|
526
|
-
const SortBySelect = ({
|
|
527
|
-
sortBy,
|
|
528
|
-
setSortBy,
|
|
529
|
-
compareMode,
|
|
530
|
-
}: {
|
|
531
|
-
sortBy: string;
|
|
532
|
-
setSortBy: (key: string) => void;
|
|
533
|
-
compareMode: boolean;
|
|
534
|
-
}): React.JSX.Element => {
|
|
535
|
-
return (
|
|
536
|
-
<div>
|
|
537
|
-
<label className="text-sm">Sort</label>
|
|
538
|
-
<Select
|
|
539
|
-
items={[
|
|
540
|
-
{
|
|
541
|
-
key: FIELD_FUNCTION_NAME,
|
|
542
|
-
disabled: false,
|
|
543
|
-
element: {
|
|
544
|
-
active: <>Function</>,
|
|
545
|
-
expanded: (
|
|
546
|
-
<>
|
|
547
|
-
<span>Function</span>
|
|
548
|
-
</>
|
|
549
|
-
),
|
|
550
|
-
},
|
|
551
|
-
},
|
|
552
|
-
{
|
|
553
|
-
key: FIELD_CUMULATIVE,
|
|
554
|
-
disabled: false,
|
|
555
|
-
element: {
|
|
556
|
-
active: <>Cumulative</>,
|
|
557
|
-
expanded: (
|
|
558
|
-
<>
|
|
559
|
-
<span>Cumulative</span>
|
|
560
|
-
</>
|
|
561
|
-
),
|
|
562
|
-
},
|
|
563
|
-
},
|
|
564
|
-
{
|
|
565
|
-
key: FIELD_DIFF,
|
|
566
|
-
disabled: !compareMode,
|
|
567
|
-
element: {
|
|
568
|
-
active: <>Diff</>,
|
|
569
|
-
expanded: (
|
|
570
|
-
<>
|
|
571
|
-
<span>Diff</span>
|
|
572
|
-
</>
|
|
573
|
-
),
|
|
574
|
-
},
|
|
575
|
-
},
|
|
576
|
-
]}
|
|
577
|
-
selectedKey={sortBy}
|
|
578
|
-
onSelection={key => setSortBy(key)}
|
|
579
|
-
placeholder={'Sort By'}
|
|
580
|
-
primary={false}
|
|
581
|
-
disabled={false}
|
|
582
|
-
/>
|
|
583
|
-
</div>
|
|
353
|
+
<p className="my-2 text-xs">
|
|
354
|
+
Showing {totalFormatted}{' '}
|
|
355
|
+
{isFiltered ? (
|
|
356
|
+
<span>
|
|
357
|
+
({filteredPercentage}%) filtered of {totalUnfilteredFormatted}{' '}
|
|
358
|
+
</span>
|
|
359
|
+
) : (
|
|
360
|
+
<></>
|
|
361
|
+
)}
|
|
362
|
+
values.{' '}
|
|
363
|
+
</p>
|
|
364
|
+
</motion.div>
|
|
365
|
+
</AnimatePresence>
|
|
584
366
|
);
|
|
585
367
|
};
|
|
586
368
|
|
|
@@ -14,9 +14,15 @@
|
|
|
14
14
|
import {useEffect, useState} from 'react';
|
|
15
15
|
|
|
16
16
|
import {RpcError} from '@protobuf-ts/runtime-rpc';
|
|
17
|
+
import {AnimatePresence, motion} from 'framer-motion';
|
|
17
18
|
|
|
18
19
|
import {Duration, Label, QueryRangeResponse, QueryServiceClient, Timestamp} from '@parca/client';
|
|
19
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
DateTimeRange,
|
|
22
|
+
MetricsGraphSkeleton,
|
|
23
|
+
useGrpcMetadata,
|
|
24
|
+
useParcaContext,
|
|
25
|
+
} from '@parca/components';
|
|
20
26
|
import {Query} from '@parca/parser';
|
|
21
27
|
import {capitalizeOnlyFirstLetter, getStepDuration} from '@parca/utilities';
|
|
22
28
|
|
|
@@ -127,8 +133,8 @@ const ProfileMetricsGraph = ({
|
|
|
127
133
|
}: ProfileMetricsGraphProps): JSX.Element => {
|
|
128
134
|
const {isLoading, response, error} = useQueryRange(queryClient, queryExpression, from, to);
|
|
129
135
|
const isLoaderVisible = useDelayedLoader(isLoading);
|
|
130
|
-
const {
|
|
131
|
-
const {width, height, margin} = useMetricsGraphDimensions(comparing);
|
|
136
|
+
const {onError, perf, authenticationErrorMessage, isDarkMode} = useParcaContext();
|
|
137
|
+
const {width, height, margin, heightStyle} = useMetricsGraphDimensions(comparing);
|
|
132
138
|
|
|
133
139
|
useEffect(() => {
|
|
134
140
|
if (error !== null) {
|
|
@@ -147,11 +153,13 @@ const ProfileMetricsGraph = ({
|
|
|
147
153
|
const series = response?.series;
|
|
148
154
|
const dataAvailable = series !== null && series !== undefined && series?.length > 0;
|
|
149
155
|
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
const metricsGraphLoading = isLoaderVisible || (isLoading && !dataAvailable);
|
|
157
|
+
|
|
158
|
+
if (metricsGraphLoading) {
|
|
159
|
+
return <MetricsGraphSkeleton heightStyle={heightStyle} isDarkMode={isDarkMode} />;
|
|
152
160
|
}
|
|
153
161
|
|
|
154
|
-
if (error !== null) {
|
|
162
|
+
if (!metricsGraphLoading && error !== null) {
|
|
155
163
|
if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
|
|
156
164
|
return <ErrorContent errorMessage={authenticationErrorMessage} />;
|
|
157
165
|
}
|
|
@@ -165,21 +173,29 @@ const ProfileMetricsGraph = ({
|
|
|
165
173
|
};
|
|
166
174
|
|
|
167
175
|
return (
|
|
168
|
-
<
|
|
169
|
-
<
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
176
|
+
<AnimatePresence>
|
|
177
|
+
<motion.div
|
|
178
|
+
className="h-full w-full"
|
|
179
|
+
key="metrics-graph-loaded"
|
|
180
|
+
initial={{display: 'none', opacity: 0}}
|
|
181
|
+
animate={{display: 'block', opacity: 1}}
|
|
182
|
+
transition={{duration: 0.5}}
|
|
183
|
+
>
|
|
184
|
+
<MetricsGraph
|
|
185
|
+
data={series}
|
|
186
|
+
from={from}
|
|
187
|
+
to={to}
|
|
188
|
+
profile={profile as MergedProfileSelection}
|
|
189
|
+
setTimeRange={setTimeRange}
|
|
190
|
+
onSampleClick={handleSampleClick}
|
|
191
|
+
addLabelMatcher={addLabelMatcher}
|
|
192
|
+
sampleUnit={Query.parse(queryExpression).profileType().sampleUnit}
|
|
193
|
+
height={height}
|
|
194
|
+
width={width}
|
|
195
|
+
margin={margin}
|
|
196
|
+
/>
|
|
197
|
+
</motion.div>
|
|
198
|
+
</AnimatePresence>
|
|
183
199
|
);
|
|
184
200
|
}
|
|
185
201
|
|
|
@@ -51,22 +51,33 @@ export const VisualizationPanel = React.memo(function VisualizationPanel({
|
|
|
51
51
|
|
|
52
52
|
return (
|
|
53
53
|
<>
|
|
54
|
-
<div className="flex w-full items-
|
|
55
|
-
<div
|
|
56
|
-
|
|
54
|
+
<div className="flex w-full items-center justify-end gap-2 pb-2 min-h-[78px]">
|
|
55
|
+
<div
|
|
56
|
+
className={cx(
|
|
57
|
+
'flex w-full justify-between flex-col-reverse md:flex-row',
|
|
58
|
+
isMultiPanelView && dashboardItem === 'icicle' ? 'items-end gap-x-2' : 'items-end'
|
|
59
|
+
)}
|
|
60
|
+
>
|
|
61
|
+
<div className="flex items-center">
|
|
57
62
|
<div
|
|
58
63
|
className={cx(isMultiPanelView ? '' : 'hidden', 'flex items-center')}
|
|
59
64
|
{...dragHandleProps}
|
|
60
65
|
>
|
|
61
66
|
<Icon className="text-xl" icon="material-symbols:drag-indicator" />
|
|
62
67
|
</div>
|
|
63
|
-
<div>{actionButtons}</div>
|
|
68
|
+
<div className="flex gap-2">{actionButtons}</div>
|
|
64
69
|
</div>
|
|
65
|
-
<div
|
|
70
|
+
<div
|
|
71
|
+
className={cx(
|
|
72
|
+
'flex flex-row items-center gap-4',
|
|
73
|
+
isMultiPanelView && dashboardItem === 'icicle' && 'pb-[10px]'
|
|
74
|
+
)}
|
|
75
|
+
>
|
|
66
76
|
<ViewSelector defaultValue={dashboardItem} navigateTo={navigateTo} position={index} />
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
77
|
+
|
|
78
|
+
{dashboardItem === 'icicle' && flamegraphHint != null ? (
|
|
79
|
+
<div className="px-2">{flamegraphHint}</div>
|
|
80
|
+
) : null}
|
|
70
81
|
</div>
|
|
71
82
|
</div>
|
|
72
83
|
{isMultiPanelView && (
|