@parca/profile 0.16.335 → 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 +4 -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 +4 -3
- 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
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [0.16.336](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.335...@parca/profile@0.16.336) (2024-02-01)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
6
10
|
## [0.16.335](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.334...@parca/profile@0.16.335) (2024-01-22)
|
|
7
11
|
|
|
8
12
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Copyright 2022 The Parca Authors
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { Menu, Transition } from '@headlessui/react';
|
|
15
|
+
import { Icon } from '@iconify/react';
|
|
16
|
+
import { FIELD_FUNCTION_FILE_NAME, FIELD_FUNCTION_NAME, FIELD_LABELS, FIELD_LOCATION_ADDRESS, FIELD_MAPPING_FILE, } from '../IcicleGraphArrow';
|
|
17
|
+
const groupByOptions = [
|
|
18
|
+
{
|
|
19
|
+
value: FIELD_FUNCTION_NAME,
|
|
20
|
+
label: 'Function Name',
|
|
21
|
+
description: 'Stacktraces are grouped by function names.',
|
|
22
|
+
disabled: true,
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
value: FIELD_LABELS,
|
|
26
|
+
label: 'Labels',
|
|
27
|
+
description: 'Stacktraces are grouped by pprof labels.',
|
|
28
|
+
disabled: false,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
value: FIELD_FUNCTION_FILE_NAME,
|
|
32
|
+
label: 'Filename',
|
|
33
|
+
description: 'Stacktraces are grouped by filenames.',
|
|
34
|
+
disabled: false,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
value: FIELD_LOCATION_ADDRESS,
|
|
38
|
+
label: 'Address',
|
|
39
|
+
description: 'Stacktraces are grouped by addresses.',
|
|
40
|
+
disabled: false,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: FIELD_MAPPING_FILE,
|
|
44
|
+
label: 'Binary',
|
|
45
|
+
description: 'Stacktraces are grouped by binaries.',
|
|
46
|
+
disabled: false,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
const GroupByDropdown = ({ groupBy, toggleGroupBy, }) => {
|
|
50
|
+
const label = groupBy.length === 0
|
|
51
|
+
? 'Nothing'
|
|
52
|
+
: groupBy.length === 1
|
|
53
|
+
? groupByOptions.find(option => option.value === groupBy[0])?.label
|
|
54
|
+
: 'Multiple';
|
|
55
|
+
return (_jsxs("div", { className: "relative", children: [_jsx("label", { className: "text-sm", children: "Group" }), _jsxs(Menu, { as: "div", className: "relative text-left", children: [_jsxs(Menu.Button, { className: "relative w-max cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] 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", children: [_jsx("span", { className: "block overflow-x-hidden text-ellipsis", children: label }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }), _jsx(Transition, { as: "div", leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(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", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsx("div", { className: "space-y-5", children: groupByOptions.map(({ value, label, description, disabled }) => (_jsxs("div", { className: "relative flex items-start", children: [_jsx("div", { className: "flex h-6 items-center", children: _jsx("input", { id: value, name: value, type: "checkbox", disabled: disabled, className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", checked: groupBy.includes(value), onChange: () => toggleGroupBy(value) }) }), _jsxs("div", { className: "ml-3 text-sm leading-6", children: [_jsx("label", { htmlFor: value, className: "font-medium text-gray-900 dark:text-gray-200", children: label }), _jsx("p", { className: "text-gray-500 dark:text-gray-400", children: description })] })] }, value))) }) }) }) }) })] })] }));
|
|
56
|
+
};
|
|
57
|
+
export default GroupByDropdown;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
declare const RuntimeFilterDropdown: ({ showRuntimeRuby, toggleShowRuntimeRuby, showRuntimePython, toggleShowRuntimePython, showInterpretedOnly, toggleShowInterpretedOnly, }: {
|
|
3
|
+
showRuntimeRuby: boolean;
|
|
4
|
+
toggleShowRuntimeRuby: () => void;
|
|
5
|
+
showRuntimePython: boolean;
|
|
6
|
+
toggleShowRuntimePython: () => void;
|
|
7
|
+
showInterpretedOnly: boolean;
|
|
8
|
+
toggleShowInterpretedOnly: () => void;
|
|
9
|
+
}) => React.JSX.Element;
|
|
10
|
+
export default RuntimeFilterDropdown;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Copyright 2022 The Parca Authors
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { Fragment } from 'react';
|
|
15
|
+
import { Menu, Transition } from '@headlessui/react';
|
|
16
|
+
import { Icon } from '@iconify/react';
|
|
17
|
+
const RuntimeToggle = ({ id, state, toggle, label, description, }) => {
|
|
18
|
+
return (_jsxs("div", { className: "relative flex items-start", children: [_jsx("div", { className: "flex h-6 items-center", children: _jsx("input", { id: id, name: id, type: "checkbox", className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", checked: state, onChange: () => toggle() }) }), _jsxs("div", { className: "ml-3 text-sm leading-6", children: [_jsx("label", { htmlFor: id, className: "font-medium text-gray-900 dark:text-gray-200", children: label }), _jsx("p", { className: "text-gray-500 dark:text-gray-400", children: description })] })] }, id));
|
|
19
|
+
};
|
|
20
|
+
const RuntimeFilterDropdown = ({ showRuntimeRuby, toggleShowRuntimeRuby, showRuntimePython, toggleShowRuntimePython, showInterpretedOnly, toggleShowInterpretedOnly, }) => {
|
|
21
|
+
return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Runtimes" }), _jsxs(Menu, { as: "div", className: "relative text-left", children: [_jsx("div", { children: _jsxs(Menu.Button, { className: "relative w-full cursor-default rounded-md border bg-white py-2 pl-3 pr-[1.7rem] 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", children: [_jsx("span", { className: "block overflow-x-hidden text-ellipsis", children: "Runtimes" }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }) }), _jsx(Transition, { as: Fragment, leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(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", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsxs("div", { className: "space-y-5", children: [_jsx(RuntimeToggle, { id: "show-runtime-ruby", state: showRuntimeRuby, toggle: toggleShowRuntimeRuby, label: "Ruby", description: "Show Ruby runtime functions." }), _jsx(RuntimeToggle, { id: "show-runtime-python", state: showRuntimePython, toggle: toggleShowRuntimePython, label: "Python", description: "Show Python runtime functions." }), _jsx(RuntimeToggle, { id: "show-interpreted-only", state: showInterpretedOnly, toggle: toggleShowInterpretedOnly, label: "Interpreted Only", description: "Show only interpreted functions." })] }) }) }) }) })] })] }));
|
|
22
|
+
};
|
|
23
|
+
export default RuntimeFilterDropdown;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Copyright 2022 The Parca Authors
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { Select } from '@parca/components';
|
|
15
|
+
import { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_NAME } from '../IcicleGraphArrow';
|
|
16
|
+
const SortBySelect = ({ sortBy, setSortBy, compareMode, }) => {
|
|
17
|
+
return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Sort" }), _jsx(Select, { className: "!px-3", items: [
|
|
18
|
+
{
|
|
19
|
+
key: FIELD_FUNCTION_NAME,
|
|
20
|
+
disabled: false,
|
|
21
|
+
element: {
|
|
22
|
+
active: _jsx(_Fragment, { children: "Function" }),
|
|
23
|
+
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "Function" }) })),
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
key: FIELD_CUMULATIVE,
|
|
28
|
+
disabled: false,
|
|
29
|
+
element: {
|
|
30
|
+
active: _jsx(_Fragment, { children: "Cumulative" }),
|
|
31
|
+
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "Cumulative" }) })),
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
key: FIELD_DIFF,
|
|
36
|
+
disabled: !compareMode,
|
|
37
|
+
element: {
|
|
38
|
+
active: _jsx(_Fragment, { children: "Diff" }),
|
|
39
|
+
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "Diff" }) })),
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
], selectedKey: sortBy, onSelection: key => setSortBy(key), placeholder: 'Sort By', primary: false, disabled: false })] }));
|
|
43
|
+
};
|
|
44
|
+
export default SortBySelect;
|
|
@@ -15,6 +15,7 @@ interface ProfileIcicleGraphProps {
|
|
|
15
15
|
loading: boolean;
|
|
16
16
|
setActionButtons?: (buttons: React.JSX.Element) => void;
|
|
17
17
|
error?: any;
|
|
18
|
+
isHalfScreen: boolean;
|
|
18
19
|
}
|
|
19
|
-
declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, sampleUnit, navigateTo, loading, setActionButtons, error, width, }: ProfileIcicleGraphProps) => JSX.Element;
|
|
20
|
+
declare const ProfileIcicleGraph: ({ graph, arrow, total, filtered, curPath, setNewCurPath, sampleUnit, navigateTo, loading, setActionButtons, error, width, isHalfScreen, }: ProfileIcicleGraphProps) => JSX.Element;
|
|
20
21
|
export default ProfileIcicleGraph;
|
|
@@ -11,21 +11,24 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
11
11
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
|
-
import {
|
|
15
|
-
import { Menu, Transition } from '@headlessui/react';
|
|
14
|
+
import { useCallback, useEffect, useMemo } from 'react';
|
|
16
15
|
import { Icon } from '@iconify/react';
|
|
17
|
-
import {
|
|
16
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
17
|
+
import { Button, IcicleActionButtonPlaceholder, IcicleGraphSkeleton, IconButton, useParcaContext, useURLState, } from '@parca/components';
|
|
18
18
|
import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
|
|
19
19
|
import { capitalizeOnlyFirstLetter, divide } from '@parca/utilities';
|
|
20
20
|
import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
|
|
21
21
|
import DiffLegend from '../components/DiffLegend';
|
|
22
|
+
import GroupByDropdown from './ActionButtons/GroupByDropdown';
|
|
23
|
+
import RuntimeFilterDropdown from './ActionButtons/RuntimeFilterDropdown';
|
|
24
|
+
import SortBySelect from './ActionButtons/SortBySelect';
|
|
22
25
|
import IcicleGraph from './IcicleGraph';
|
|
23
|
-
import IcicleGraphArrow, {
|
|
26
|
+
import IcicleGraphArrow, { FIELD_FUNCTION_NAME } from './IcicleGraphArrow';
|
|
24
27
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
25
28
|
const ErrorContent = ({ errorMessage }) => {
|
|
26
29
|
return _jsx("div", { className: "flex justify-center p-10", children: errorMessage });
|
|
27
30
|
};
|
|
28
|
-
const ShowHideLegendButton = ({ navigateTo }) => {
|
|
31
|
+
const ShowHideLegendButton = ({ navigateTo, isHalfScreen, }) => {
|
|
29
32
|
const [colorStackLegend, setStoreColorStackLegend] = useURLState({
|
|
30
33
|
param: 'color_stack_legend',
|
|
31
34
|
navigateTo,
|
|
@@ -36,7 +39,7 @@ const ShowHideLegendButton = ({ navigateTo }) => {
|
|
|
36
39
|
const setColorStackLegend = useCallback((value) => {
|
|
37
40
|
setStoreColorStackLegend(value);
|
|
38
41
|
}, [setStoreColorStackLegend]);
|
|
39
|
-
return (_jsx(_Fragment, { children: colorProfileName === 'default' || compareMode ? null : (_jsxs(Button, { className: "gap-2", variant: "neutral", onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), children: [isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', _jsx(Icon, { icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', width: 20 })] })) }));
|
|
42
|
+
return (_jsx(_Fragment, { children: colorProfileName === 'default' || compareMode ? null : (_jsx(_Fragment, { children: isHalfScreen ? (_jsx(IconButton, { 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]", icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', toolTipText: isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true') })) : (_jsxs(Button, { className: "gap-2 w-max", variant: "neutral", onClick: () => setColorStackLegend(isColorStackLegendEnabled ? 'false' : 'true'), children: [isColorStackLegendEnabled ? 'Hide legend' : 'Show legend', _jsx(Icon, { icon: isColorStackLegendEnabled ? 'ph:eye-closed' : 'ph:eye', width: 20 })] })) })) }));
|
|
40
43
|
};
|
|
41
44
|
const GroupAndSortActionButtons = ({ navigateTo }) => {
|
|
42
45
|
const [storeSortBy = FIELD_FUNCTION_NAME, setStoreSortBy] = useURLState({
|
|
@@ -79,14 +82,8 @@ const GroupAndSortActionButtons = ({ navigateTo }) => {
|
|
|
79
82
|
});
|
|
80
83
|
return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy }), _jsx(RuntimeFilterDropdown, { showRuntimeRuby: showRuntimeRubyStr === 'true', toggleShowRuntimeRuby: () => setShowRuntimeRuby(showRuntimeRubyStr === 'true' ? 'false' : 'true'), showRuntimePython: showRuntimePythonStr === 'true', toggleShowRuntimePython: () => setShowRuntimePython(showRuntimePythonStr === 'true' ? 'false' : 'true'), showInterpretedOnly: showInterpretedOnlyStr === 'true', toggleShowInterpretedOnly: () => setShowInterpretedOnly(showInterpretedOnlyStr === 'true' ? 'false' : 'true') })] }));
|
|
81
84
|
};
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
const RuntimeFilterDropdown = ({ showRuntimeRuby, toggleShowRuntimeRuby, showRuntimePython, toggleShowRuntimePython, showInterpretedOnly, toggleShowInterpretedOnly, }) => {
|
|
86
|
-
return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Runtimes" }), _jsxs(Menu, { as: "div", className: "relative text-left", children: [_jsx("div", { children: _jsxs(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", children: [_jsx("span", { className: "ml-3 block overflow-x-hidden text-ellipsis", children: "Runtimes" }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }) }), _jsx(Transition, { as: Fragment, leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(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", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsxs("div", { className: "space-y-5", children: [_jsx(RuntimeToggle, { id: "show-runtime-ruby", state: showRuntimeRuby, toggle: toggleShowRuntimeRuby, label: "Ruby", description: "Show Ruby runtime functions." }), _jsx(RuntimeToggle, { id: "show-runtime-python", state: showRuntimePython, toggle: toggleShowRuntimePython, label: "Python", description: "Show Python runtime functions." }), _jsx(RuntimeToggle, { id: "show-interpreted-only", state: showInterpretedOnly, toggle: toggleShowInterpretedOnly, label: "Interpreted Only", description: "Show only interpreted functions." })] }) }) }) }) })] })] }));
|
|
87
|
-
};
|
|
88
|
-
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, sampleUnit, navigateTo, loading, setActionButtons, error, width, }) {
|
|
89
|
-
const { loader, onError, authenticationErrorMessage } = useParcaContext();
|
|
85
|
+
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, sampleUnit, navigateTo, loading, setActionButtons, error, width, isHalfScreen, }) {
|
|
86
|
+
const { onError, authenticationErrorMessage, isDarkMode } = useParcaContext();
|
|
90
87
|
const { compareMode } = useProfileViewContext();
|
|
91
88
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
92
89
|
param: 'sort_by',
|
|
@@ -111,13 +108,17 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
111
108
|
];
|
|
112
109
|
}, [graph, arrow, filtered, total]);
|
|
113
110
|
useEffect(() => {
|
|
111
|
+
if (loading && setActionButtons !== undefined) {
|
|
112
|
+
setActionButtons(_jsx(IcicleActionButtonPlaceholder, { isHalfScreen: isHalfScreen }));
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
114
115
|
if (setActionButtons === undefined) {
|
|
115
116
|
return;
|
|
116
117
|
}
|
|
117
|
-
setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), _jsx(ShowHideLegendButton, { navigateTo: navigateTo }), _jsx(Button, { variant: "neutral", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: "Reset View" })] }) }));
|
|
118
|
-
}, [navigateTo, arrow, curPath, setNewCurPath, setActionButtons]);
|
|
118
|
+
setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full flex-col items-start justify-between gap-2 md:flex-row md:items-end", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), _jsx(ShowHideLegendButton, { isHalfScreen: isHalfScreen, navigateTo: navigateTo }), isHalfScreen ? (_jsx(IconButton, { icon: "system-uicons:reset", disabled: curPath.length === 0, toolTipText: "Reset View", onClick: () => setNewCurPath([]), 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]" })) : (_jsxs(Button, { variant: "neutral", className: "gap-2 w-max", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: ["Reset View", _jsx(Icon, { icon: "system-uicons:reset", width: 20 })] }))] }) }));
|
|
119
|
+
}, [navigateTo, arrow, curPath, setNewCurPath, setActionButtons, loading, isHalfScreen]);
|
|
119
120
|
if (loading) {
|
|
120
|
-
return _jsx("div", { className: "h-
|
|
121
|
+
return (_jsx("div", { className: "h-auto overflow-clip", children: _jsx(IcicleGraphSkeleton, { isHalfScreen: isHalfScreen, isDarkMode: isDarkMode }) }));
|
|
121
122
|
}
|
|
122
123
|
if (error != null) {
|
|
123
124
|
onError?.(error);
|
|
@@ -133,74 +134,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
133
134
|
if (isTrimmed) {
|
|
134
135
|
console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
|
|
135
136
|
}
|
|
136
|
-
return (_jsxs(
|
|
137
|
-
};
|
|
138
|
-
const groupByOptions = [
|
|
139
|
-
{
|
|
140
|
-
value: FIELD_FUNCTION_NAME,
|
|
141
|
-
label: 'Function Name',
|
|
142
|
-
description: 'Stacktraces are grouped by function names.',
|
|
143
|
-
disabled: true,
|
|
144
|
-
},
|
|
145
|
-
{
|
|
146
|
-
value: FIELD_LABELS,
|
|
147
|
-
label: 'Labels',
|
|
148
|
-
description: 'Stacktraces are grouped by pprof labels.',
|
|
149
|
-
disabled: false,
|
|
150
|
-
},
|
|
151
|
-
{
|
|
152
|
-
value: FIELD_FUNCTION_FILE_NAME,
|
|
153
|
-
label: 'Filename',
|
|
154
|
-
description: 'Stacktraces are grouped by filenames.',
|
|
155
|
-
disabled: false,
|
|
156
|
-
},
|
|
157
|
-
{
|
|
158
|
-
value: FIELD_LOCATION_ADDRESS,
|
|
159
|
-
label: 'Address',
|
|
160
|
-
description: 'Stacktraces are grouped by addresses.',
|
|
161
|
-
disabled: false,
|
|
162
|
-
},
|
|
163
|
-
{
|
|
164
|
-
value: FIELD_MAPPING_FILE,
|
|
165
|
-
label: 'Binary',
|
|
166
|
-
description: 'Stacktraces are grouped by binaries.',
|
|
167
|
-
disabled: false,
|
|
168
|
-
},
|
|
169
|
-
];
|
|
170
|
-
const GroupByDropdown = ({ groupBy, toggleGroupBy, }) => {
|
|
171
|
-
const label = groupBy.length === 0
|
|
172
|
-
? 'Nothing'
|
|
173
|
-
: groupBy.length === 1
|
|
174
|
-
? groupByOptions.find(option => option.value === groupBy[0])?.label
|
|
175
|
-
: 'Multiple';
|
|
176
|
-
return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Group" }), _jsxs(Menu, { as: "div", className: "relative text-left", children: [_jsx("div", { children: _jsxs(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", children: [_jsx("span", { className: "ml-3 block overflow-x-hidden text-ellipsis", children: label }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }) }), _jsx(Transition, { as: Fragment, leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(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", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsx("div", { className: "space-y-5", children: groupByOptions.map(({ value, label, description, disabled }) => (_jsxs("div", { className: "relative flex items-start", children: [_jsx("div", { className: "flex h-6 items-center", children: _jsx("input", { id: value, name: value, type: "checkbox", disabled: disabled, className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", checked: groupBy.includes(value), onChange: () => toggleGroupBy(value) }) }), _jsxs("div", { className: "ml-3 text-sm leading-6", children: [_jsx("label", { htmlFor: value, className: "font-medium text-gray-900 dark:text-gray-200", children: label }), _jsx("p", { className: "text-gray-500 dark:text-gray-400", children: description })] })] }, value))) }) }) }) }) })] })] }));
|
|
177
|
-
};
|
|
178
|
-
const SortBySelect = ({ sortBy, setSortBy, compareMode, }) => {
|
|
179
|
-
return (_jsxs("div", { children: [_jsx("label", { className: "text-sm", children: "Sort" }), _jsx(Select, { items: [
|
|
180
|
-
{
|
|
181
|
-
key: FIELD_FUNCTION_NAME,
|
|
182
|
-
disabled: false,
|
|
183
|
-
element: {
|
|
184
|
-
active: _jsx(_Fragment, { children: "Function" }),
|
|
185
|
-
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "Function" }) })),
|
|
186
|
-
},
|
|
187
|
-
},
|
|
188
|
-
{
|
|
189
|
-
key: FIELD_CUMULATIVE,
|
|
190
|
-
disabled: false,
|
|
191
|
-
element: {
|
|
192
|
-
active: _jsx(_Fragment, { children: "Cumulative" }),
|
|
193
|
-
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "Cumulative" }) })),
|
|
194
|
-
},
|
|
195
|
-
},
|
|
196
|
-
{
|
|
197
|
-
key: FIELD_DIFF,
|
|
198
|
-
disabled: !compareMode,
|
|
199
|
-
element: {
|
|
200
|
-
active: _jsx(_Fragment, { children: "Diff" }),
|
|
201
|
-
expanded: (_jsx(_Fragment, { children: _jsx("span", { children: "Diff" }) })),
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
], selectedKey: sortBy, onSelection: key => setSortBy(key), placeholder: 'Sort By', primary: false, disabled: false })] }));
|
|
137
|
+
return (_jsx(AnimatePresence, { children: _jsxs(motion.div, { className: "relative h-full w-full", initial: { opacity: 0 }, animate: { opacity: 1 }, transition: { duration: 0.5 }, children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsxs("div", { className: "min-h-48", children: [graph !== undefined && (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo })), arrow !== undefined && (_jsx(IcicleGraphArrow, { width: width, arrow: arrow, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo, sortBy: storeSortBy }))] }), _jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] })] }, "icicle-graph-loaded") }));
|
|
205
138
|
};
|
|
206
139
|
export default ProfileIcicleGraph;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
// Copyright 2022 The Parca Authors
|
|
3
3
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
// you may not use this file except in compliance with the License.
|
|
@@ -12,8 +12,9 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
12
12
|
// See the License for the specific language governing permissions and
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
import { useEffect, useState } from 'react';
|
|
15
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
15
16
|
import { Duration, Timestamp } from '@parca/client';
|
|
16
|
-
import { useGrpcMetadata, useParcaContext } from '@parca/components';
|
|
17
|
+
import { MetricsGraphSkeleton, useGrpcMetadata, useParcaContext, } from '@parca/components';
|
|
17
18
|
import { Query } from '@parca/parser';
|
|
18
19
|
import { capitalizeOnlyFirstLetter, getStepDuration } from '@parca/utilities';
|
|
19
20
|
import MetricsGraph from '../MetricsGraph';
|
|
@@ -61,8 +62,8 @@ export const useQueryRange = (client, queryExpression, start, end) => {
|
|
|
61
62
|
const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to, setTimeRange, addLabelMatcher, onPointClick, comparing = false, }) => {
|
|
62
63
|
const { isLoading, response, error } = useQueryRange(queryClient, queryExpression, from, to);
|
|
63
64
|
const isLoaderVisible = useDelayedLoader(isLoading);
|
|
64
|
-
const {
|
|
65
|
-
const { width, height, margin } = useMetricsGraphDimensions(comparing);
|
|
65
|
+
const { onError, perf, authenticationErrorMessage, isDarkMode } = useParcaContext();
|
|
66
|
+
const { width, height, margin, heightStyle } = useMetricsGraphDimensions(comparing);
|
|
66
67
|
useEffect(() => {
|
|
67
68
|
if (error !== null) {
|
|
68
69
|
onError?.(error);
|
|
@@ -76,10 +77,11 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
|
|
|
76
77
|
}, [perf, response]);
|
|
77
78
|
const series = response?.series;
|
|
78
79
|
const dataAvailable = series !== null && series !== undefined && series?.length > 0;
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
const metricsGraphLoading = isLoaderVisible || (isLoading && !dataAvailable);
|
|
81
|
+
if (metricsGraphLoading) {
|
|
82
|
+
return _jsx(MetricsGraphSkeleton, { heightStyle: heightStyle, isDarkMode: isDarkMode });
|
|
81
83
|
}
|
|
82
|
-
if (error !== null) {
|
|
84
|
+
if (!metricsGraphLoading && error !== null) {
|
|
83
85
|
if (authenticationErrorMessage !== undefined && error.code === 'UNAUTHENTICATED') {
|
|
84
86
|
return _jsx(ErrorContent, { errorMessage: authenticationErrorMessage });
|
|
85
87
|
}
|
|
@@ -89,7 +91,7 @@ const ProfileMetricsGraph = ({ queryClient, queryExpression, profile, from, to,
|
|
|
89
91
|
const handleSampleClick = (timestamp, _value, labels) => {
|
|
90
92
|
onPointClick(timestamp, labels, queryExpression);
|
|
91
93
|
};
|
|
92
|
-
return (_jsx(
|
|
94
|
+
return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: _jsx(MetricsGraph, { data: series, from: from, to: to, profile: profile, setTimeRange: setTimeRange, onSampleClick: handleSampleClick, addLabelMatcher: addLabelMatcher, sampleUnit: Query.parse(queryExpression).profileType().sampleUnit, height: height, width: width, margin: margin }) }, "metrics-graph-loaded") }));
|
|
93
95
|
}
|
|
94
96
|
return _jsx(ProfileMetricsEmptyState, { message: "No data found. Try a different query." });
|
|
95
97
|
};
|
|
@@ -20,7 +20,7 @@ import ViewSelector from './ViewSelector';
|
|
|
20
20
|
export const VisualizationPanel = React.memo(function VisualizationPanel({ dashboardItem, index, isMultiPanelView, handleClosePanel, navigateTo, dragHandleProps, getDashboardItemByType, }) {
|
|
21
21
|
const [actionButtons, setActionButtons] = useState(_jsx(_Fragment, {}));
|
|
22
22
|
const { flamegraphHint } = useParcaContext();
|
|
23
|
-
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full items-
|
|
23
|
+
return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex w-full items-center justify-end gap-2 pb-2 min-h-[78px]", children: [_jsxs("div", { className: cx('flex w-full justify-between flex-col-reverse md:flex-row', isMultiPanelView && dashboardItem === 'icicle' ? 'items-end gap-x-2' : 'items-end'), children: [_jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: cx(isMultiPanelView ? '' : 'hidden', 'flex items-center'), ...dragHandleProps, children: _jsx(Icon, { className: "text-xl", icon: "material-symbols:drag-indicator" }) }), _jsx("div", { className: "flex gap-2", children: actionButtons })] }), _jsxs("div", { className: cx('flex flex-row items-center gap-4', isMultiPanelView && dashboardItem === 'icicle' && 'pb-[10px]'), children: [_jsx(ViewSelector, { defaultValue: dashboardItem, navigateTo: navigateTo, position: index }), dashboardItem === 'icicle' && flamegraphHint != null ? (_jsx("div", { className: "px-2", children: flamegraphHint })) : null] })] }), isMultiPanelView && (_jsx(IconButton, { className: "py-0", onClick: () => handleClosePanel(dashboardItem), icon: _jsx(CloseIcon, {}) }))] }), getDashboardItemByType({
|
|
24
24
|
type: dashboardItem,
|
|
25
25
|
isHalfScreen: isMultiPanelView,
|
|
26
26
|
setActionButtons,
|
|
@@ -28,7 +28,6 @@ import ProfileIcicleGraph from '../ProfileIcicleGraph';
|
|
|
28
28
|
import { SourceView } from '../SourceView';
|
|
29
29
|
import Table from '../Table';
|
|
30
30
|
import ProfileShareButton from '../components/ProfileShareButton';
|
|
31
|
-
import useDelayedLoader from '../useDelayedLoader';
|
|
32
31
|
import FilterByFunctionButton from './FilterByFunctionButton';
|
|
33
32
|
import { ProfileViewContextProvider } from './ProfileViewContext';
|
|
34
33
|
import ViewSelector from './ViewSelector';
|
|
@@ -57,7 +56,7 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
57
56
|
}, [rawDashboardItems]);
|
|
58
57
|
const isDarkMode = useAppSelector(selectDarkMode);
|
|
59
58
|
const isMultiPanelView = dashboardItems.length > 1;
|
|
60
|
-
const {
|
|
59
|
+
const { perf, profileViewExternalMainActions, profileViewExternalSubActions } = useParcaContext();
|
|
61
60
|
useEffect(() => {
|
|
62
61
|
// Reset the current path when the profile source changes
|
|
63
62
|
setCurPath([]);
|
|
@@ -69,29 +68,6 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
69
68
|
}
|
|
70
69
|
void loadGraphviz();
|
|
71
70
|
}, []);
|
|
72
|
-
const isLoading = useMemo(() => {
|
|
73
|
-
if (dashboardItems.includes('icicle')) {
|
|
74
|
-
return Boolean(flamegraphData?.loading);
|
|
75
|
-
}
|
|
76
|
-
if (dashboardItems.includes('callgraph')) {
|
|
77
|
-
return Boolean(callgraphData?.loading) || Boolean(callgraphSVG === undefined);
|
|
78
|
-
}
|
|
79
|
-
if (dashboardItems.includes('table')) {
|
|
80
|
-
return Boolean(topTableData?.loading);
|
|
81
|
-
}
|
|
82
|
-
if (dashboardItems.includes('source')) {
|
|
83
|
-
return Boolean(sourceData?.loading);
|
|
84
|
-
}
|
|
85
|
-
return false;
|
|
86
|
-
}, [
|
|
87
|
-
dashboardItems,
|
|
88
|
-
callgraphData?.loading,
|
|
89
|
-
flamegraphData?.loading,
|
|
90
|
-
topTableData?.loading,
|
|
91
|
-
sourceData?.loading,
|
|
92
|
-
callgraphSVG,
|
|
93
|
-
]);
|
|
94
|
-
const isLoaderVisible = useDelayedLoader(isLoading);
|
|
95
71
|
const maxColor = getNewSpanColor(isDarkMode);
|
|
96
72
|
const minColor = scaleLinear([isDarkMode ? 'black' : 'white', maxColor])(0.3);
|
|
97
73
|
const colorRange = [minColor, maxColor];
|
|
@@ -130,7 +106,7 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
130
106
|
return (_jsx(ConditionalWrapper, { condition: perf?.onRender != null, WrapperComponent: Profiler, wrapperProps: {
|
|
131
107
|
id: 'icicleGraph',
|
|
132
108
|
onRender: perf?.onRender,
|
|
133
|
-
}, children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, arrow: flamegraphData?.arrow, graph: flamegraphData?.data, total: total, filtered: filtered, sampleUnit: sampleUnit, navigateTo: navigateTo, loading: flamegraphData.loading, setActionButtons: setActionButtons, error: flamegraphData.error, width: dimensions?.width !== undefined
|
|
109
|
+
}, children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, arrow: flamegraphData?.arrow, graph: flamegraphData?.data, total: total, filtered: filtered, sampleUnit: sampleUnit, navigateTo: navigateTo, loading: flamegraphData.loading, setActionButtons: setActionButtons, error: flamegraphData.error, isHalfScreen: isHalfScreen, width: dimensions?.width !== undefined
|
|
134
110
|
? isHalfScreen
|
|
135
111
|
? (dimensions.width - 40) / 2
|
|
136
112
|
: dimensions.width - 16
|
|
@@ -142,7 +118,7 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
142
118
|
dimensions?.width !== undefined ? (_jsx(Callgraph, { data: callgraphData.data, svgString: callgraphSVG, sampleUnit: sampleUnit, width: isHalfScreen ? dimensions?.width / 2 : dimensions?.width })) : (_jsx(_Fragment, {}));
|
|
143
119
|
}
|
|
144
120
|
case 'table': {
|
|
145
|
-
return topTableData != null ? (_jsx(Table, { total: total, filtered: filtered, loading: topTableData.loading, data: topTableData.arrow?.record, sampleUnit: sampleUnit, navigateTo: navigateTo, setActionButtons: setActionButtons, currentSearchString: currentSearchString })) : (_jsx(_Fragment, {}));
|
|
121
|
+
return topTableData != null ? (_jsx(Table, { total: total, filtered: filtered, loading: topTableData.loading, data: topTableData.arrow?.record, sampleUnit: sampleUnit, navigateTo: navigateTo, setActionButtons: setActionButtons, currentSearchString: currentSearchString, isHalfScreen: isHalfScreen })) : (_jsx(_Fragment, {}));
|
|
146
122
|
}
|
|
147
123
|
case 'source': {
|
|
148
124
|
return sourceData != null ? (_jsx(SourceView, { loading: sourceData.loading, data: sourceData.data, total: total, filtered: filtered, setActionButtons: setActionButtons })) : (_jsx(_Fragment, {}));
|
|
@@ -183,10 +159,10 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
183
159
|
: '' })] })), profileViewExternalMainActions != null ? profileViewExternalMainActions : null] }), _jsxs("div", { className: "lg:flex flex-wrap items-center gap-2 md:justify-end hidden", children: [_jsx(FilterByFunctionButton, { navigateTo: navigateTo }), profileViewExternalSubActions != null ? profileViewExternalSubActions : null, _jsx(UserPreferences, { customButton: _jsxs(Button, { className: "gap-2", variant: "neutral", children: ["Preferences", _jsx(Icon, { icon: "pajamas:preferences", width: 20 })] }) }), profileSource !== undefined && queryClient !== undefined ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsxs(Button, { className: "gap-2", variant: "neutral", onClick: e => {
|
|
184
160
|
e.preventDefault();
|
|
185
161
|
onDownloadPProf();
|
|
186
|
-
}, disabled: pprofDownloading, children: [pprofDownloading != null && pprofDownloading ? 'Downloading...' : 'Download pprof', _jsx(Icon, { icon: "material-symbols:download", width: 20 })] }), _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel", icon: _jsx(Icon, { icon: "material-symbols:add", width: 20 }), addView: true, disabled: isMultiPanelView || dashboardItems.length < 1 })] })] }), _jsx("div", { className: "w-full", ref: ref, children:
|
|
187
|
-
return (_jsx(Draggable, { draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView, children: (provided, snapshot) => (_createElement("div", { ref: provided.innerRef, ...provided.draggableProps, key: dashboardItem, className: cx('
|
|
162
|
+
}, disabled: pprofDownloading, children: [pprofDownloading != null && pprofDownloading ? 'Downloading...' : 'Download pprof', _jsx(Icon, { icon: "material-symbols:download", width: 20 })] }), _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel", icon: _jsx(Icon, { icon: "material-symbols:add", width: 20 }), addView: true, disabled: isMultiPanelView || dashboardItems.length < 1 })] })] }), _jsx("div", { className: "w-full", ref: ref, children: _jsx(DragDropContext, { onDragEnd: onDragEnd, children: _jsx(Droppable, { droppableId: "droppable", direction: "horizontal", children: provided => (_jsx("div", { ref: provided.innerRef, className: cx('grid w-full gap-2', isMultiPanelView ? 'grid-cols-2' : 'grid-cols-1'), ...provided.droppableProps, children: dashboardItems.map((dashboardItem, index) => {
|
|
163
|
+
return (_jsx(Draggable, { draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView, children: (provided, snapshot) => (_createElement("div", { ref: provided.innerRef, ...provided.draggableProps, key: dashboardItem, className: cx('w-full rounded p-2 shadow dark:border dark:border-gray-700 dark:bg-gray-700 min-h-96', snapshot.isDragging
|
|
188
164
|
? 'bg-gray-200 dark:bg-gray-500'
|
|
189
165
|
: 'bg-white dark:bg-gray-700') },
|
|
190
166
|
_jsx(VisualizationPanel, { handleClosePanel: handleClosePanel, isMultiPanelView: isMultiPanelView, dashboardItem: dashboardItem, getDashboardItemByType: getDashboardItemByType, dragHandleProps: provided.dragHandleProps, navigateTo: navigateTo, index: index }))) }, dashboardItem));
|
|
191
|
-
}) })) }) })
|
|
167
|
+
}) })) }) }) })] }) }));
|
|
192
168
|
};
|
package/dist/SourceView/index.js
CHANGED
|
@@ -13,18 +13,19 @@ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
|
13
13
|
// limitations under the License.
|
|
14
14
|
import React, { useEffect } from 'react';
|
|
15
15
|
import { tableFromIPC } from 'apache-arrow';
|
|
16
|
-
import {
|
|
16
|
+
import { AnimatePresence, motion } from 'framer-motion';
|
|
17
|
+
import { SourceSkeleton, useParcaContext, useURLState } from '@parca/components';
|
|
17
18
|
import { ExpandOnHover } from '../GraphTooltipArrow/ExpandOnHoverValue';
|
|
18
19
|
import { truncateStringReverse } from '../utils';
|
|
19
20
|
import { Highlighter, profileAwareRenderer } from './Highlighter';
|
|
20
21
|
export const SourceView = React.memo(function SourceView({ data, loading, total, filtered, setActionButtons, }) {
|
|
21
22
|
const [sourceFileName] = useURLState({ param: 'source_filename', navigateTo: () => { } });
|
|
22
|
-
const {
|
|
23
|
+
const { isDarkMode } = useParcaContext();
|
|
23
24
|
useEffect(() => {
|
|
24
25
|
setActionButtons?.(_jsx("div", { className: "px-2", children: _jsx(ExpandOnHover, { value: sourceFileName, displayValue: truncateStringReverse(sourceFileName, 50) }) }));
|
|
25
26
|
}, [sourceFileName, setActionButtons]);
|
|
26
27
|
if (loading) {
|
|
27
|
-
return _jsx("div", { className: "h-
|
|
28
|
+
return (_jsx("div", { className: "h-auto overflow-clip", children: _jsx(SourceSkeleton, { isDarkMode: isDarkMode }) }));
|
|
28
29
|
}
|
|
29
30
|
if (data === undefined) {
|
|
30
31
|
return _jsx(_Fragment, { children: "Source code not uploaded for this build." });
|
|
@@ -32,6 +33,6 @@ export const SourceView = React.memo(function SourceView({ data, loading, total,
|
|
|
32
33
|
const table = tableFromIPC(data.record);
|
|
33
34
|
const cumulative = table.getChild('cumulative');
|
|
34
35
|
const flat = table.getChild('flat');
|
|
35
|
-
return (_jsx(Highlighter, { file: sourceFileName, content: data.source, renderer: profileAwareRenderer(cumulative, flat, total, filtered) }));
|
|
36
|
+
return (_jsx(AnimatePresence, { children: _jsx(motion.div, { className: "h-full w-full", initial: { display: 'none', opacity: 0 }, animate: { display: 'block', opacity: 1 }, transition: { duration: 0.5 }, children: _jsx(Highlighter, { file: sourceFileName, content: data.source, renderer: profileAwareRenderer(cumulative, flat, total, filtered) }) }, "source-view-loaded") }));
|
|
36
37
|
});
|
|
37
38
|
export default SourceView;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { type VisibilityState } from '@tanstack/react-table';
|
|
3
|
+
import { ColumnDef } from '.';
|
|
4
|
+
declare const ColumnsVisibility: ({ columns, visibility, setVisibility, }: {
|
|
5
|
+
columns: ColumnDef[];
|
|
6
|
+
visibility: VisibilityState;
|
|
7
|
+
setVisibility: (id: string, visible: boolean) => void;
|
|
8
|
+
}) => React.JSX.Element;
|
|
9
|
+
export default ColumnsVisibility;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
// Copyright 2022 The Parca Authors
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
import { Fragment } from 'react';
|
|
15
|
+
import { Menu, Transition } from '@headlessui/react';
|
|
16
|
+
import { Icon } from '@iconify/react';
|
|
17
|
+
const ColumnsVisibility = ({ columns, visibility, setVisibility, }) => {
|
|
18
|
+
return (_jsx("div", { children: _jsxs(Menu, { as: "div", className: "relative text-left", children: [_jsx("div", { children: _jsxs(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", children: [_jsx("span", { className: "block overflow-x-hidden text-ellipsis", children: "Columns" }), _jsx("span", { className: "pointer-events-none absolute inset-y-0 right-0 ml-3 flex items-center pr-2 text-gray-400", children: _jsx(Icon, { icon: "heroicons:chevron-down-20-solid", "aria-hidden": "true" }) })] }) }), _jsx(Transition, { as: Fragment, leave: "transition ease-in duration-100", leaveFrom: "opacity-100", leaveTo: "opacity-0", children: _jsx(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", children: _jsx("div", { className: "p-4", children: _jsx("fieldset", { children: _jsx("div", { className: "space-y-5", children: columns.map(col => (_jsxs("div", { className: "relative flex items-start", children: [_jsx("div", { className: "flex h-6 items-center", children: _jsx("input", { id: col.id, name: col.id, type: "checkbox", className: "h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-600", checked: visibility[col.id ?? ''] ?? false, onChange: () => {
|
|
19
|
+
setVisibility(col.id ?? '', !visibility[col.id ?? '']);
|
|
20
|
+
} }) }), _jsx("div", { className: "ml-3 text-sm leading-6", children: _jsx("label", { htmlFor: col.id, className: "font-medium text-gray-900 dark:text-gray-200", children: col.header }) })] }, col.id))) }) }) }) }) })] }) }));
|
|
21
|
+
};
|
|
22
|
+
export default ColumnsVisibility;
|
package/dist/Table/index.d.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Vector } from 'apache-arrow';
|
|
3
3
|
import { type NavigateFunction } from '@parca/utilities';
|
|
4
|
+
export interface ColumnDef {
|
|
5
|
+
id: string;
|
|
6
|
+
header: string;
|
|
7
|
+
accessorKey: string;
|
|
8
|
+
footer?: string;
|
|
9
|
+
cell?: (info: any) => string | number;
|
|
10
|
+
meta?: {
|
|
11
|
+
align: 'right' | 'left';
|
|
12
|
+
};
|
|
13
|
+
invertSorting?: boolean;
|
|
14
|
+
size?: number;
|
|
15
|
+
}
|
|
4
16
|
interface TableProps {
|
|
5
17
|
data?: Uint8Array;
|
|
6
18
|
total: bigint;
|
|
@@ -10,6 +22,7 @@ interface TableProps {
|
|
|
10
22
|
loading: boolean;
|
|
11
23
|
currentSearchString?: string;
|
|
12
24
|
setActionButtons?: (buttons: React.JSX.Element) => void;
|
|
25
|
+
isHalfScreen: boolean;
|
|
13
26
|
}
|
|
14
27
|
export declare const Table: React.NamedExoticComponent<TableProps>;
|
|
15
28
|
export declare const RowName: (mappingFileColumn: Vector | null, locationAddressColumn: Vector | null, functionNameColumn: Vector | null, row: number) => string;
|