@parca/profile 0.16.251 → 0.16.253
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/GraphTooltipArrow/Content.js +20 -89
- package/dist/GraphTooltipArrow/DockedGraphTooltip/index.d.ts +12 -0
- package/dist/GraphTooltipArrow/DockedGraphTooltip/index.js +63 -0
- package/dist/GraphTooltipArrow/useGraphTooltip/index.d.ts +19 -0
- package/dist/GraphTooltipArrow/useGraphTooltip/index.js +38 -0
- package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.d.ts +19 -0
- package/dist/GraphTooltipArrow/useGraphTooltipMetaInfo/index.js +94 -0
- package/dist/ProfileIcicleGraph/IcicleGraph/index.js +2 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +6 -6
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +4 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts +1 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +8 -1
- package/dist/ProfileIcicleGraph/index.js +16 -2
- package/dist/styles.css +1 -1
- package/package.json +6 -6
- package/src/GraphTooltipArrow/Content.tsx +39 -130
- package/src/GraphTooltipArrow/DockedGraphTooltip/index.tsx +249 -0
- package/src/GraphTooltipArrow/useGraphTooltip/index.ts +76 -0
- package/src/GraphTooltipArrow/useGraphTooltipMetaInfo/index.ts +153 -0
- package/src/ProfileIcicleGraph/IcicleGraph/index.tsx +4 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +26 -17
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +26 -10
- package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +15 -1
- package/src/ProfileIcicleGraph/index.tsx +47 -10
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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.253](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.252...@parca/profile@0.16.253) (2023-09-11)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## 0.16.252 (2023-09-11)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.251](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.250...@parca/profile@0.16.251) (2023-09-07)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -15,27 +15,31 @@ import { useState } from 'react';
|
|
|
15
15
|
import cx from 'classnames';
|
|
16
16
|
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
17
17
|
import { Tooltip } from 'react-tooltip';
|
|
18
|
-
import {
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
21
|
-
import { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_FILE_NAME, FIELD_FUNCTION_START_LINE, FIELD_LOCATION_ADDRESS, FIELD_LOCATION_LINE, FIELD_MAPPING_BUILD_ID, FIELD_MAPPING_FILE, } from '../ProfileIcicleGraph/IcicleGraphArrow';
|
|
22
|
-
import { arrowToString, nodeLabel } from '../ProfileIcicleGraph/IcicleGraphArrow/utils';
|
|
23
|
-
import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
|
|
24
|
-
import { useQuery } from '../useQuery';
|
|
18
|
+
import { Button, IconButton, useParcaContext } from '@parca/components';
|
|
19
|
+
import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
|
|
20
|
+
import { getLastItem } from '@parca/utilities';
|
|
25
21
|
import { hexifyAddress, truncateString, truncateStringReverse } from '../utils';
|
|
26
22
|
import { ExpandOnHover } from './ExpandOnHoverValue';
|
|
23
|
+
import { useGraphTooltip } from './useGraphTooltip';
|
|
24
|
+
import { useGraphTooltipMetaInfo } from './useGraphTooltipMetaInfo';
|
|
27
25
|
let timeoutHandle = null;
|
|
28
26
|
const NoData = () => {
|
|
29
27
|
return _jsx("span", { className: "rounded bg-gray-200 px-2 dark:bg-gray-800", children: "Not available" });
|
|
30
28
|
};
|
|
31
29
|
const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, level, isFixed, navigateTo, }) => {
|
|
32
30
|
const [isCopied, setIsCopied] = useState(false);
|
|
33
|
-
|
|
31
|
+
const graphTooltipData = useGraphTooltip({
|
|
32
|
+
table,
|
|
33
|
+
unit,
|
|
34
|
+
total,
|
|
35
|
+
totalUnfiltered,
|
|
36
|
+
row,
|
|
37
|
+
level,
|
|
38
|
+
});
|
|
39
|
+
const [_, setIsDocked] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
|
|
40
|
+
if (graphTooltipData === null) {
|
|
34
41
|
return _jsx(_Fragment, {});
|
|
35
42
|
}
|
|
36
|
-
const locationAddress = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row) ?? 0n;
|
|
37
|
-
const cumulative = BigInt(table.getChild(FIELD_CUMULATIVE)?.get(row)) ?? 0n;
|
|
38
|
-
const diff = BigInt(table.getChild(FIELD_DIFF)?.get(row)) ?? 0n;
|
|
39
43
|
const onCopy = () => {
|
|
40
44
|
setIsCopied(true);
|
|
41
45
|
if (timeoutHandle !== null) {
|
|
@@ -43,89 +47,16 @@ const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, le
|
|
|
43
47
|
}
|
|
44
48
|
timeoutHandle = setTimeout(() => setIsCopied(false), 3000);
|
|
45
49
|
};
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
const diffSign = diff > 0 ? '+' : '';
|
|
49
|
-
const diffValueText = diffSign + valueFormatter(diff, unit, 1);
|
|
50
|
-
const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
|
|
51
|
-
const diffText = `${diffValueText} (${diffPercentageText})`;
|
|
52
|
-
const name = nodeLabel(table, row, level, false);
|
|
53
|
-
const getTextForCumulative = (hoveringNodeCumulative) => {
|
|
54
|
-
const filtered = totalUnfiltered > total
|
|
55
|
-
? ` / ${(100 * divide(hoveringNodeCumulative, total)).toFixed(2)}% of filtered`
|
|
56
|
-
: '';
|
|
57
|
-
return `${valueFormatter(hoveringNodeCumulative, unit, 2)}
|
|
58
|
-
(${(100 * divide(hoveringNodeCumulative, totalUnfiltered)).toFixed(2)}%${filtered})`;
|
|
59
|
-
};
|
|
60
|
-
return (_jsx("div", { className: `flex text-sm ${isFixed ? 'w-full' : ''}`, children: _jsx("div", { className: `m-auto w-full ${isFixed ? 'w-full' : ''}`, children: _jsxs("div", { className: "min-h-52 flex w-[500px] flex-col justify-between rounded-lg border border-gray-300 bg-gray-50 p-3 shadow-lg dark:border-gray-500 dark:bg-gray-900", children: [_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "mx-2", children: [_jsx("div", { className: "flex h-10 items-center break-all font-semibold", children: row === 0 ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: name !== '' ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: name, children: _jsx("button", { className: "cursor-pointer text-left", children: name }) })) : (_jsx(_Fragment, { children: locationAddress !== 0n ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer text-left", children: hexifyAddress(locationAddress) }) })) : (_jsx("p", { children: "unknown" })) })) })) }), _jsx("table", { className: "my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Cumulative" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: getTextForCumulative(cumulative), children: _jsx("button", { className: "cursor-pointer", children: getTextForCumulative(cumulative) }) }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: diffText, children: _jsx("button", { className: "cursor-pointer", children: diffText }) }) })] })), _jsx(TooltipMetaInfo, { table: table, row: row, onCopy: onCopy, navigateTo: navigateTo })] }) })] }) }), _jsx("span", { className: "mx-2 block text-xs text-gray-500", children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' })] }) }) }));
|
|
50
|
+
const { name, locationAddress, cumulativeText, diffText, diff, row: rowNumber } = graphTooltipData;
|
|
51
|
+
return (_jsx("div", { className: `flex text-sm ${isFixed ? 'w-full' : ''}`, children: _jsx("div", { className: `m-auto w-full ${isFixed ? 'w-full' : ''}`, children: _jsxs("div", { className: "min-h-52 flex w-[500px] flex-col justify-between rounded-lg border border-gray-300 bg-gray-50 p-3 shadow-lg dark:border-gray-500 dark:bg-gray-900", children: [_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "mx-2", children: [_jsxs("div", { className: "flex h-10 items-start justify-between gap-4 break-all font-semibold", children: [row === 0 ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: name !== '' ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: name, children: _jsx("button", { className: "cursor-pointer text-left", children: name }) })) : (_jsx(_Fragment, { children: locationAddress !== 0n ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer text-left", children: hexifyAddress(locationAddress) }) })) : (_jsx("p", { children: "unknown" })) })) })), _jsx(IconButton, { onClick: () => setIsDocked(true), icon: "mdi:dock-bottom", title: "Dock MetaInfo Panel" })] }), _jsx("table", { className: "my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Cumulative" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: cumulativeText, children: _jsx("button", { className: "cursor-pointer", children: cumulativeText }) }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: diffText, children: _jsx("button", { className: "cursor-pointer", children: diffText }) }) })] })), _jsx(TooltipMetaInfo, { table: table, row: rowNumber, onCopy: onCopy, navigateTo: navigateTo })] }) })] }) }), _jsx("span", { className: "mx-2 block text-xs text-gray-500", children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' })] }) }) }));
|
|
61
52
|
};
|
|
62
53
|
const TooltipMetaInfo = ({ table,
|
|
63
54
|
// total,
|
|
64
55
|
// totalUnfiltered,
|
|
65
56
|
onCopy, row, navigateTo, }) => {
|
|
66
|
-
const mappingFile =
|
|
67
|
-
const
|
|
68
|
-
const locationAddress = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row) ?? 0n;
|
|
69
|
-
const locationLine = table.getChild(FIELD_LOCATION_LINE)?.get(row) ?? 0n;
|
|
70
|
-
const functionFilename = arrowToString(table.getChild(FIELD_FUNCTION_FILE_NAME)?.get(row)) ?? '';
|
|
71
|
-
const functionStartLine = table.getChild(FIELD_FUNCTION_START_LINE)?.get(row) ?? 0n;
|
|
72
|
-
const lineNumber = locationLine !== 0n ? locationLine : functionStartLine !== 0n ? functionStartLine : undefined;
|
|
73
|
-
const pprofLabelPrefix = 'pprof_labels.';
|
|
74
|
-
const labelColumnNames = table.schema.fields.filter(field => field.name.startsWith(pprofLabelPrefix));
|
|
75
|
-
const { queryServiceClient, enableSourcesView } = useParcaContext();
|
|
76
|
-
const { profileSource } = useProfileViewContext();
|
|
77
|
-
const { isLoading: sourceLoading, response: sourceResponse } = useQuery(queryServiceClient, profileSource, QueryRequest_ReportType.SOURCE, {
|
|
78
|
-
skip: enableSourcesView === false ||
|
|
79
|
-
profileSource === undefined ||
|
|
80
|
-
// eslint-disable-next-line no-extra-boolean-cast
|
|
81
|
-
!Boolean(mappingBuildID) ||
|
|
82
|
-
// eslint-disable-next-line no-extra-boolean-cast
|
|
83
|
-
!Boolean(functionFilename),
|
|
84
|
-
sourceBuildID: mappingBuildID,
|
|
85
|
-
sourceFilename: functionFilename,
|
|
86
|
-
sourceOnly: true,
|
|
87
|
-
});
|
|
88
|
-
const isSourceAvailable = !sourceLoading && sourceResponse?.report != null;
|
|
89
|
-
const getTextForFile = () => {
|
|
90
|
-
if (functionFilename === '')
|
|
91
|
-
return '<unknown>';
|
|
92
|
-
return `${functionFilename} ${lineNumber !== undefined ? ` +${lineNumber.toString()}` : ''}`;
|
|
93
|
-
};
|
|
94
|
-
const file = getTextForFile();
|
|
95
|
-
const labelPairs = labelColumnNames
|
|
96
|
-
.map((field, i) => [
|
|
97
|
-
labelColumnNames[i].name.slice(pprofLabelPrefix.length),
|
|
98
|
-
arrowToString(table.getChild(field.name)?.get(row)) ?? '',
|
|
99
|
-
])
|
|
100
|
-
.filter(value => value[1] !== '');
|
|
57
|
+
const { labelPairs, functionFilename, file, openFile, isSourceAvailable, locationAddress, mappingFile, mappingBuildID, } = useGraphTooltipMetaInfo({ table, row, navigateTo });
|
|
58
|
+
const { enableSourcesView } = useParcaContext();
|
|
101
59
|
const labels = labelPairs.map((l) => (_jsx("span", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: `${l[0]}="${l[1]}"` }, l[0])));
|
|
102
|
-
|
|
103
|
-
param: 'dashboard_items',
|
|
104
|
-
navigateTo,
|
|
105
|
-
});
|
|
106
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
107
|
-
const [unusedBuildId, setSourceBuildId] = useURLState({
|
|
108
|
-
param: 'source_buildid',
|
|
109
|
-
navigateTo,
|
|
110
|
-
});
|
|
111
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
112
|
-
const [unusedFilename, setSourceFilename] = useURLState({
|
|
113
|
-
param: 'source_filename',
|
|
114
|
-
navigateTo,
|
|
115
|
-
});
|
|
116
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
117
|
-
const [unusedLine, setSourceLine] = useURLState({
|
|
118
|
-
param: 'source_line',
|
|
119
|
-
navigateTo,
|
|
120
|
-
});
|
|
121
|
-
const openFile = () => {
|
|
122
|
-
setDashboardItems([dashboardItems[0], 'source']);
|
|
123
|
-
setSourceBuildId(mappingBuildID);
|
|
124
|
-
setSourceFilename(functionFilename);
|
|
125
|
-
if (lineNumber !== undefined) {
|
|
126
|
-
setSourceLine(lineNumber.toString());
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsxs("div", { className: "flex gap-4", children: [_jsx(CopyToClipboard, { onCopy: onCopy, text: file, children: _jsx("button", { className: "cursor-pointer whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 30) }) }) }), _jsxs("div", { className: cx('flex gap-2', { hidden: enableSourcesView === false }), children: [_jsx("div", { "data-tooltip-id": "open-source-button-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsx(Button, { variant: 'neutral', onClick: () => openFile(), className: "shrink-0", disabled: !isSourceAvailable, children: "open" }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "open-source-button-help" }) : null] })] })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer", children: hexifyAddress(locationAddress) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: mappingFile === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingFile, children: _jsx("button", { className: "cursor-pointer", children: getLastItem(mappingFile) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: mappingBuildID === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingBuildID, children: _jsx("button", { className: "cursor-pointer", children: truncateString(getLastItem(mappingBuildID), 28) }) })) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
|
|
60
|
+
return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsxs("div", { className: "flex gap-4", children: [_jsx(CopyToClipboard, { onCopy: onCopy, text: file, children: _jsx("button", { className: "cursor-pointer whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 30) }) }) }), _jsxs("div", { className: cx('flex gap-2', { hidden: enableSourcesView === false }), children: [_jsx("div", { "data-tooltip-id": "open-source-button-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsx(Button, { variant: 'neutral', onClick: () => openFile(), className: "shrink-0", disabled: !isSourceAvailable, children: "open" }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "open-source-button-help" }) : null] })] })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer", children: hexifyAddress(locationAddress) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: mappingFile === null ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingFile, children: _jsx("button", { className: "cursor-pointer", children: getLastItem(mappingFile) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: mappingBuildID === null ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingBuildID, children: _jsx("button", { className: "cursor-pointer", children: truncateString(getLastItem(mappingBuildID), 28) }) })) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
|
|
130
61
|
};
|
|
131
62
|
export default GraphTooltipArrowContent;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/// <reference types="react" />
|
|
2
|
+
import { Table } from 'apache-arrow';
|
|
3
|
+
interface Props {
|
|
4
|
+
table: Table<any>;
|
|
5
|
+
unit: string;
|
|
6
|
+
total: bigint;
|
|
7
|
+
totalUnfiltered: bigint;
|
|
8
|
+
row: number | null;
|
|
9
|
+
level: number;
|
|
10
|
+
}
|
|
11
|
+
export declare const DockedGraphTooltip: ({ table, unit, total, totalUnfiltered, row, level, }: Props) => JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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 { useState } from 'react';
|
|
15
|
+
import cx from 'classnames';
|
|
16
|
+
import { CopyToClipboard } from 'react-copy-to-clipboard';
|
|
17
|
+
import { Tooltip } from 'react-tooltip';
|
|
18
|
+
import { useWindowSize } from 'react-use';
|
|
19
|
+
import { Button, IconButton, useParcaContext } from '@parca/components';
|
|
20
|
+
import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
|
|
21
|
+
import { getLastItem } from '@parca/utilities';
|
|
22
|
+
import { hexifyAddress, truncateString, truncateStringReverse } from '../../utils';
|
|
23
|
+
import { ExpandOnHover } from '../ExpandOnHoverValue';
|
|
24
|
+
import { useGraphTooltip } from '../useGraphTooltip';
|
|
25
|
+
import { useGraphTooltipMetaInfo } from '../useGraphTooltipMetaInfo';
|
|
26
|
+
let timeoutHandle = null;
|
|
27
|
+
const InfoSection = ({ title, value, onCopy, copyText, minWidth = '', }) => {
|
|
28
|
+
return (_jsxs("div", { className: cx('flex shrink-0 flex-col gap-1 p-2', { [minWidth]: minWidth != null }), children: [_jsx("p", { className: "text-sm font-medium leading-5 text-gray-500 dark:text-gray-400", children: title }), _jsx("div", { className: "text-lg font-normal text-gray-900 dark:text-gray-50", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: copyText, children: _jsx("button", { children: value }) }) })] }));
|
|
29
|
+
};
|
|
30
|
+
export const DockedGraphTooltip = ({ table, unit, total, totalUnfiltered, row, level, }) => {
|
|
31
|
+
let { width } = useWindowSize();
|
|
32
|
+
const { profileExplorer, navigateTo, enableSourcesView } = useParcaContext();
|
|
33
|
+
const { PaddingX } = profileExplorer ?? { PaddingX: 0 };
|
|
34
|
+
width = width - PaddingX - 24;
|
|
35
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
36
|
+
const onCopy = () => {
|
|
37
|
+
setIsCopied(true);
|
|
38
|
+
if (timeoutHandle !== null) {
|
|
39
|
+
clearTimeout(timeoutHandle);
|
|
40
|
+
}
|
|
41
|
+
timeoutHandle = setTimeout(() => setIsCopied(false), 3000);
|
|
42
|
+
};
|
|
43
|
+
const graphTooltipData = useGraphTooltip({
|
|
44
|
+
table,
|
|
45
|
+
unit,
|
|
46
|
+
total,
|
|
47
|
+
totalUnfiltered,
|
|
48
|
+
row,
|
|
49
|
+
level,
|
|
50
|
+
});
|
|
51
|
+
const { labelPairs, functionFilename, file, openFile, isSourceAvailable, locationAddress, mappingFile, mappingBuildID, } = useGraphTooltipMetaInfo({ table, row: row ?? 0, navigateTo });
|
|
52
|
+
const [_, setIsDocked] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
|
|
53
|
+
if (graphTooltipData === null) {
|
|
54
|
+
return _jsx(_Fragment, {});
|
|
55
|
+
}
|
|
56
|
+
const { name, cumulativeText, diffText, diff } = graphTooltipData;
|
|
57
|
+
const labels = labelPairs.map((l) => (_jsx("span", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: `${l[0]}="${l[1]}"` }, l[0])));
|
|
58
|
+
const addressText = locationAddress !== 0n ? hexifyAddress(locationAddress) : 'unknown';
|
|
59
|
+
const fileText = functionFilename !== '' ? file : 'Not available';
|
|
60
|
+
return (_jsx("div", { className: "fixed bottom-0 z-20 overflow-hidden rounded-t-lg border-l border-r border-t border-gray-400 bg-white bg-opacity-90 px-8 py-3 dark:border-gray-600 dark:bg-black dark:bg-opacity-80", style: { width }, children: _jsxs("div", { className: "flex flex-col gap-4", children: [_jsxs("div", { className: "flex justify-between gap-4", children: [row === 0 ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: name !== '' ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: name, children: _jsx("button", { className: "cursor-pointer text-left", children: name }) })) : (_jsx(_Fragment, { children: locationAddress !== 0n ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer text-left", children: hexifyAddress(locationAddress) }) })) : (_jsx("p", { children: "unknown" })) })) })), _jsx(IconButton, { onClick: () => setIsDocked(false), icon: "mdi:dock-window", title: "Undock MetaInfo Panel" })] }), _jsxs("div", { className: "flex justify-between gap-3", children: [_jsx(InfoSection, { title: "Cumulative", value: cumulativeText, onCopy: onCopy, copyText: cumulativeText, minWidth: "w-44" }), diff !== 0n ? (_jsx(InfoSection, { title: "Diff", value: diffText, onCopy: onCopy, copyText: diffText, minWidth: "w-44" })) : null, _jsx(InfoSection, { title: "File", value: _jsxs("div", { className: "flex gap-2", children: [_jsx(ExpandOnHover, { value: fileText, displayValue: truncateStringReverse(fileText, 45) }), _jsxs("div", { className: cx('flex items-center gap-2', {
|
|
61
|
+
hidden: enableSourcesView === false || functionFilename === '',
|
|
62
|
+
}), children: [_jsx("div", { "data-tooltip-id": "open-source-button-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsx(Button, { variant: 'neutral', onClick: () => openFile(), className: "shrink-0", disabled: !isSourceAvailable, children: "open" }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "open-source-button-help" }) : null] })] }), onCopy: onCopy, copyText: file, minWidth: 'w-[460px]' }), _jsx(InfoSection, { title: "Address", value: addressText, onCopy: onCopy, copyText: addressText, minWidth: "w-44" }), _jsx(InfoSection, { title: "Binary", value: (mappingFile != null ? getLastItem(mappingFile) : null) ?? 'Not available', onCopy: onCopy, copyText: mappingFile ?? 'Not available', minWidth: "w-44" }), _jsx(InfoSection, { title: "Build ID", value: truncateString(getLastItem(mappingBuildID) ?? 'Not available', 28), onCopy: onCopy, copyText: mappingBuildID ?? 'Not available' })] }), _jsx("div", { children: _jsx("div", { className: "flex h-5 gap-1", children: labels }) }), _jsx("span", { className: "mx-2 block text-xs text-gray-500", children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' })] }) }));
|
|
63
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Table } from 'apache-arrow';
|
|
2
|
+
interface Props {
|
|
3
|
+
table: Table<any>;
|
|
4
|
+
unit: string;
|
|
5
|
+
total: bigint;
|
|
6
|
+
totalUnfiltered: bigint;
|
|
7
|
+
row: number | null;
|
|
8
|
+
level: number;
|
|
9
|
+
}
|
|
10
|
+
interface GraphTooltipData {
|
|
11
|
+
name: string;
|
|
12
|
+
locationAddress: bigint;
|
|
13
|
+
cumulativeText: string;
|
|
14
|
+
diffText: string;
|
|
15
|
+
diff: bigint;
|
|
16
|
+
row: number;
|
|
17
|
+
}
|
|
18
|
+
export declare const useGraphTooltip: ({ table, unit, total, totalUnfiltered, row, level, }: Props) => GraphTooltipData | null;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
import { divide, valueFormatter } from '@parca/utilities';
|
|
14
|
+
import { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_LOCATION_ADDRESS, } from '../../ProfileIcicleGraph/IcicleGraphArrow';
|
|
15
|
+
import { getTextForCumulative, nodeLabel } from '../../ProfileIcicleGraph/IcicleGraphArrow/utils';
|
|
16
|
+
export const useGraphTooltip = ({ table, unit, total, totalUnfiltered, row, level, }) => {
|
|
17
|
+
if (row === null) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const locationAddress = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row) ?? 0n;
|
|
21
|
+
const cumulative = BigInt(table.getChild(FIELD_CUMULATIVE)?.get(row)) ?? 0n;
|
|
22
|
+
const diff = BigInt(table.getChild(FIELD_DIFF)?.get(row)) ?? 0n;
|
|
23
|
+
const prevValue = cumulative - diff;
|
|
24
|
+
const diffRatio = diff !== 0n ? divide(diff, prevValue) : 0;
|
|
25
|
+
const diffSign = diff > 0 ? '+' : '';
|
|
26
|
+
const diffValueText = diffSign + valueFormatter(diff, unit, 1);
|
|
27
|
+
const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
|
|
28
|
+
const diffText = `${diffValueText} (${diffPercentageText})`;
|
|
29
|
+
const name = nodeLabel(table, row, level, false);
|
|
30
|
+
return {
|
|
31
|
+
name,
|
|
32
|
+
locationAddress,
|
|
33
|
+
cumulativeText: getTextForCumulative(cumulative, totalUnfiltered, total, unit),
|
|
34
|
+
diffText,
|
|
35
|
+
diff,
|
|
36
|
+
row,
|
|
37
|
+
};
|
|
38
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Table } from 'apache-arrow';
|
|
2
|
+
import type { NavigateFunction } from '@parca/utilities';
|
|
3
|
+
interface Props {
|
|
4
|
+
table: Table<any>;
|
|
5
|
+
row: number;
|
|
6
|
+
navigateTo: NavigateFunction;
|
|
7
|
+
}
|
|
8
|
+
interface GraphTooltipMetaInfoData {
|
|
9
|
+
labelPairs: Array<[string, string]>;
|
|
10
|
+
functionFilename: string;
|
|
11
|
+
file: string;
|
|
12
|
+
openFile: () => void;
|
|
13
|
+
isSourceAvailable: boolean;
|
|
14
|
+
locationAddress: bigint;
|
|
15
|
+
mappingFile: string | null;
|
|
16
|
+
mappingBuildID: string | null;
|
|
17
|
+
}
|
|
18
|
+
export declare const useGraphTooltipMetaInfo: ({ table, row, navigateTo, }: Props) => GraphTooltipMetaInfoData;
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Copyright 2022 The Parca Authors
|
|
2
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
3
|
+
// you may not use this file except in compliance with the License.
|
|
4
|
+
// You may obtain a copy of the License at
|
|
5
|
+
//
|
|
6
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
7
|
+
//
|
|
8
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
9
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
10
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
11
|
+
// See the License for the specific language governing permissions and
|
|
12
|
+
// limitations under the License.
|
|
13
|
+
import { QueryRequest_ReportType } from '@parca/client';
|
|
14
|
+
import { useParcaContext, useURLState } from '@parca/components';
|
|
15
|
+
import { FIELD_FUNCTION_FILE_NAME, FIELD_FUNCTION_START_LINE, FIELD_LOCATION_ADDRESS, FIELD_LOCATION_LINE, FIELD_MAPPING_BUILD_ID, FIELD_MAPPING_FILE, } from '../../ProfileIcicleGraph/IcicleGraphArrow';
|
|
16
|
+
import { arrowToString } from '../../ProfileIcicleGraph/IcicleGraphArrow/utils';
|
|
17
|
+
import { useProfileViewContext } from '../../ProfileView/ProfileViewContext';
|
|
18
|
+
import { useQuery } from '../../useQuery';
|
|
19
|
+
export const useGraphTooltipMetaInfo = ({ table, row, navigateTo, }) => {
|
|
20
|
+
const mappingFile = arrowToString(table.getChild(FIELD_MAPPING_FILE)?.get(row));
|
|
21
|
+
const mappingBuildID = arrowToString(table.getChild(FIELD_MAPPING_BUILD_ID)?.get(row));
|
|
22
|
+
const locationAddress = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row) ?? 0n;
|
|
23
|
+
const locationLine = table.getChild(FIELD_LOCATION_LINE)?.get(row) ?? 0n;
|
|
24
|
+
const functionFilename = arrowToString(table.getChild(FIELD_FUNCTION_FILE_NAME)?.get(row)) ?? '';
|
|
25
|
+
const functionStartLine = table.getChild(FIELD_FUNCTION_START_LINE)?.get(row) ?? 0n;
|
|
26
|
+
const lineNumber = locationLine !== 0n ? locationLine : functionStartLine !== 0n ? functionStartLine : undefined;
|
|
27
|
+
const pprofLabelPrefix = 'pprof_labels.';
|
|
28
|
+
const labelColumnNames = table.schema.fields.filter(field => field.name.startsWith(pprofLabelPrefix));
|
|
29
|
+
const { queryServiceClient, enableSourcesView } = useParcaContext();
|
|
30
|
+
const { profileSource } = useProfileViewContext();
|
|
31
|
+
const { isLoading: sourceLoading, response: sourceResponse } = useQuery(queryServiceClient, profileSource, QueryRequest_ReportType.SOURCE, {
|
|
32
|
+
skip: enableSourcesView === false ||
|
|
33
|
+
profileSource === undefined ||
|
|
34
|
+
// eslint-disable-next-line no-extra-boolean-cast
|
|
35
|
+
!Boolean(mappingBuildID) ||
|
|
36
|
+
// eslint-disable-next-line no-extra-boolean-cast
|
|
37
|
+
!Boolean(functionFilename),
|
|
38
|
+
sourceBuildID: mappingBuildID !== null ? mappingBuildID : undefined,
|
|
39
|
+
sourceFilename: functionFilename,
|
|
40
|
+
sourceOnly: true,
|
|
41
|
+
});
|
|
42
|
+
const isSourceAvailable = !sourceLoading && sourceResponse?.report != null;
|
|
43
|
+
const getTextForFile = () => {
|
|
44
|
+
if (functionFilename === '')
|
|
45
|
+
return '<unknown>';
|
|
46
|
+
return `${functionFilename} ${lineNumber !== undefined ? ` +${lineNumber.toString()}` : ''}`;
|
|
47
|
+
};
|
|
48
|
+
const file = getTextForFile();
|
|
49
|
+
const labelPairs = labelColumnNames
|
|
50
|
+
.map((field, i) => [
|
|
51
|
+
labelColumnNames[i].name.slice(pprofLabelPrefix.length),
|
|
52
|
+
arrowToString(table.getChild(field.name)?.get(row)) ?? '',
|
|
53
|
+
])
|
|
54
|
+
.filter(value => value[1] !== '');
|
|
55
|
+
const [dashboardItems, setDashboardItems] = useURLState({
|
|
56
|
+
param: 'dashboard_items',
|
|
57
|
+
navigateTo,
|
|
58
|
+
});
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
60
|
+
const [unusedBuildId, setSourceBuildId] = useURLState({
|
|
61
|
+
param: 'source_buildid',
|
|
62
|
+
navigateTo,
|
|
63
|
+
});
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
65
|
+
const [unusedFilename, setSourceFilename] = useURLState({
|
|
66
|
+
param: 'source_filename',
|
|
67
|
+
navigateTo,
|
|
68
|
+
});
|
|
69
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
70
|
+
const [unusedLine, setSourceLine] = useURLState({
|
|
71
|
+
param: 'source_line',
|
|
72
|
+
navigateTo,
|
|
73
|
+
});
|
|
74
|
+
const openFile = () => {
|
|
75
|
+
setDashboardItems([dashboardItems[0], 'source']);
|
|
76
|
+
if (mappingBuildID != null) {
|
|
77
|
+
setSourceBuildId(mappingBuildID);
|
|
78
|
+
}
|
|
79
|
+
setSourceFilename(functionFilename);
|
|
80
|
+
if (lineNumber !== undefined) {
|
|
81
|
+
setSourceLine(lineNumber.toString());
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
return {
|
|
85
|
+
labelPairs,
|
|
86
|
+
functionFilename,
|
|
87
|
+
file,
|
|
88
|
+
openFile,
|
|
89
|
+
isSourceAvailable,
|
|
90
|
+
locationAddress,
|
|
91
|
+
mappingBuildID,
|
|
92
|
+
mappingFile,
|
|
93
|
+
};
|
|
94
|
+
};
|
|
@@ -26,6 +26,7 @@ export const IcicleGraph = memo(function IcicleGraph({ graph, total, filtered, w
|
|
|
26
26
|
const coloredGraph = useColoredGraph(graph);
|
|
27
27
|
const currentSearchString = selectQueryParam('search_string') ?? '';
|
|
28
28
|
const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
29
|
+
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
29
30
|
useEffect(() => {
|
|
30
31
|
if (ref.current != null) {
|
|
31
32
|
setHeight(ref?.current.getBoundingClientRect().height);
|
|
@@ -40,6 +41,6 @@ export const IcicleGraph = memo(function IcicleGraph({ graph, total, filtered, w
|
|
|
40
41
|
if (coloredGraph.root === undefined || width === undefined) {
|
|
41
42
|
return _jsx(_Fragment, {});
|
|
42
43
|
}
|
|
43
|
-
return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ColorStackLegend, { navigateTo: navigateTo, compareMode: compareMode }), _jsx(GraphTooltip, { unit: sampleUnit, total: total, totalUnfiltered: total + filtered, contextElement: svg.current, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function }), _jsx("svg", { className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg, children: _jsx("g", { ref: ref, children: _jsx("g", { transform: 'translate(0, 0)', children: _jsx(IcicleNode, { x: 0, y: 0, totalWidth: width, height: RowHeight, setCurPath: setCurPath, curPath: curPath, data: coloredGraph.root, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, compareMode: compareMode }) }) }) })] }));
|
|
44
|
+
return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [isColorStackLegendEnabled && (_jsx(ColorStackLegend, { navigateTo: navigateTo, compareMode: compareMode })), _jsx(GraphTooltip, { unit: sampleUnit, total: total, totalUnfiltered: total + filtered, contextElement: svg.current, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function }), _jsx("svg", { className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg, children: _jsx("g", { ref: ref, children: _jsx("g", { transform: 'translate(0, 0)', children: _jsx(IcicleNode, { x: 0, y: 0, totalWidth: width, height: RowHeight, setCurPath: setCurPath, curPath: curPath, data: coloredGraph.root, strings: coloredGraph.stringTable, mappings: coloredGraph.mapping, locations: coloredGraph.locations, functions: coloredGraph.function, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, compareMode: compareMode }) }) }) })] }));
|
|
44
45
|
});
|
|
45
46
|
export default IcicleGraph;
|
|
@@ -40,10 +40,10 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
|
|
|
40
40
|
if (colorProfileName === 'default' || compareMode) {
|
|
41
41
|
return _jsx(_Fragment, {});
|
|
42
42
|
}
|
|
43
|
-
return (_jsx("div", { className: "my-
|
|
43
|
+
return (_jsx("div", { className: "my-4 flex w-full flex-wrap justify-start", children: stackColorArray.map(([feature, color]) => {
|
|
44
44
|
const filteringAllowed = feature !== EVERYTHING_ELSE;
|
|
45
45
|
const isHighlighted = currentSearchString === feature;
|
|
46
|
-
return (_jsxs("div", { className: cx('flex items-center justify-between
|
|
46
|
+
return (_jsxs("div", { className: cx('flex-no-wrap mb-1 flex w-1/5 items-center justify-between text-ellipsis p-1', {
|
|
47
47
|
'cursor-pointer': filteringAllowed,
|
|
48
48
|
'bg-gray-200 dark:bg-gray-800': isHighlighted,
|
|
49
49
|
}), onClick: () => {
|
|
@@ -55,10 +55,10 @@ const ColorStackLegend = ({ mappingColors, navigateTo, compareMode = false, }) =
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
setSearchString(feature);
|
|
58
|
-
}, children: [_jsxs("div", { className: "flex items-center", children: [_jsx("div", { className: "mr-1 inline-block h-4 w-4", style: { backgroundColor: color } }), _jsx("
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
58
|
+
}, children: [_jsxs("div", { className: "flex w-11/12 items-center justify-start", children: [_jsx("div", { className: "flex w-5 items-center", children: _jsx("div", { className: "mr-1 inline-block h-4 w-4", style: { backgroundColor: color } }) }), _jsx("div", { className: "shrink overflow-hidden text-ellipsis whitespace-nowrap text-sm hover:whitespace-normal", children: feature })] }), _jsx("div", { className: "flex w-1/12 justify-end", children: isHighlighted && (_jsx(Icon, { icon: "radix-icons:cross-circled", onClick: e => {
|
|
59
|
+
setSearchString('');
|
|
60
|
+
e.stopPropagation();
|
|
61
|
+
} })) })] }, feature));
|
|
62
62
|
}) }));
|
|
63
63
|
};
|
|
64
64
|
export default ColorStackLegend;
|
|
@@ -18,6 +18,7 @@ import { getColorForFeature, selectDarkMode, setHoveringNode, useAppDispatch, us
|
|
|
18
18
|
import { getLastItem, scaleLinear, selectQueryParam, } from '@parca/utilities';
|
|
19
19
|
import GraphTooltipArrow from '../../GraphTooltipArrow';
|
|
20
20
|
import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
|
|
21
|
+
import { DockedGraphTooltip } from '../../GraphTooltipArrow/DockedGraphTooltip';
|
|
21
22
|
import ColorStackLegend from './ColorStackLegend';
|
|
22
23
|
import { IcicleNode, RowHeight } from './IcicleGraphNodes';
|
|
23
24
|
import { arrowToString, extractFeature } from './utils';
|
|
@@ -36,6 +37,7 @@ export const FIELD_DIFF = 'diff';
|
|
|
36
37
|
export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, filtered, width, setCurPath, curPath, sampleUnit, navigateTo, sortBy, }) {
|
|
37
38
|
const dispatch = useAppDispatch();
|
|
38
39
|
const [colorProfile] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
|
|
40
|
+
const [dockedMetainfo] = useUserPreference(USER_PREFERENCES.GRAPH_METAINFO_DOCKED.key);
|
|
39
41
|
const isDarkMode = useAppSelector(selectDarkMode);
|
|
40
42
|
const table = useMemo(() => {
|
|
41
43
|
return tableFromIPC(arrow.record);
|
|
@@ -47,6 +49,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
47
49
|
const ref = useRef(null);
|
|
48
50
|
const currentSearchString = selectQueryParam('search_string') ?? '';
|
|
49
51
|
const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
52
|
+
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
50
53
|
const mappings = useMemo(() => {
|
|
51
54
|
// Read the mappings from the dictionary that contains all mapping strings.
|
|
52
55
|
// This is great, as might only have a dozen or so mappings,
|
|
@@ -133,6 +136,6 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
133
136
|
if (table.numRows === 0 || width === undefined) {
|
|
134
137
|
return _jsx(_Fragment, {});
|
|
135
138
|
}
|
|
136
|
-
return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode }), _jsx(GraphTooltipArrow, { contextElement: svg.current, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, unit: sampleUnit, navigateTo: navigateTo }) }), root] }));
|
|
139
|
+
return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [isColorStackLegendEnabled && (_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode })), dockedMetainfo ? (_jsx(DockedGraphTooltip, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, total: total, totalUnfiltered: total + filtered, unit: sampleUnit })) : (_jsx(GraphTooltipArrow, { contextElement: svg.current, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, unit: sampleUnit, navigateTo: navigateTo }) })), root] }));
|
|
137
140
|
});
|
|
138
141
|
export default IcicleGraphArrow;
|
|
@@ -2,4 +2,5 @@ import { Table } from 'apache-arrow';
|
|
|
2
2
|
import { type Feature } from '@parca/store';
|
|
3
3
|
export declare function nodeLabel(table: Table<any>, row: number, level: number, showBinaryName: boolean): string;
|
|
4
4
|
export declare const extractFeature: (mapping: string) => Feature;
|
|
5
|
+
export declare const getTextForCumulative: (hoveringNodeCumulative: bigint, totalUnfiltered: bigint, total: bigint, unit: string) => string;
|
|
5
6
|
export declare const arrowToString: (buffer: any) => string | null;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
import { EVERYTHING_ELSE, FEATURE_TYPES } from '@parca/store';
|
|
14
|
-
import { getLastItem } from '@parca/utilities';
|
|
14
|
+
import { divide, getLastItem, valueFormatter } from '@parca/utilities';
|
|
15
15
|
import { hexifyAddress } from '../../utils';
|
|
16
16
|
import { FIELD_FUNCTION_NAME, FIELD_LABELS_ONLY, FIELD_LOCATION_ADDRESS, FIELD_MAPPING_FILE, } from './index';
|
|
17
17
|
export function nodeLabel(table, row, level, showBinaryName) {
|
|
@@ -53,6 +53,13 @@ export const extractFeature = (mapping) => {
|
|
|
53
53
|
}
|
|
54
54
|
return { name: EVERYTHING_ELSE, type: FEATURE_TYPES.Misc };
|
|
55
55
|
};
|
|
56
|
+
export const getTextForCumulative = (hoveringNodeCumulative, totalUnfiltered, total, unit) => {
|
|
57
|
+
const filtered = totalUnfiltered > total
|
|
58
|
+
? ` / ${(100 * divide(hoveringNodeCumulative, total)).toFixed(2)}% of filtered`
|
|
59
|
+
: '';
|
|
60
|
+
return `${valueFormatter(hoveringNodeCumulative, unit, 2)}
|
|
61
|
+
(${(100 * divide(hoveringNodeCumulative, totalUnfiltered)).toFixed(2)}%${filtered})`;
|
|
62
|
+
};
|
|
56
63
|
export const arrowToString = (buffer) => {
|
|
57
64
|
if (buffer == null || typeof buffer === 'string') {
|
|
58
65
|
return buffer;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx,
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } 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.
|
|
@@ -15,11 +15,25 @@ import { Fragment, useCallback, useEffect, useMemo } from 'react';
|
|
|
15
15
|
import { Menu, Transition } from '@headlessui/react';
|
|
16
16
|
import { Icon } from '@iconify/react';
|
|
17
17
|
import { Button, Select, useParcaContext, useURLState } from '@parca/components';
|
|
18
|
+
import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
|
|
18
19
|
import { divide, selectQueryParam } from '@parca/utilities';
|
|
19
20
|
import DiffLegend from '../components/DiffLegend';
|
|
20
21
|
import IcicleGraph from './IcicleGraph';
|
|
21
22
|
import IcicleGraphArrow, { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_NAME, FIELD_LABELS, } from './IcicleGraphArrow';
|
|
22
23
|
const numberFormatter = new Intl.NumberFormat('en-US');
|
|
24
|
+
const ShowHideLegendButton = ({ navigateTo }) => {
|
|
25
|
+
const [colorStackLegend, setStoreColorStackLegend] = useURLState({
|
|
26
|
+
param: 'color_stack_legend',
|
|
27
|
+
navigateTo,
|
|
28
|
+
});
|
|
29
|
+
const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
30
|
+
const isColorStackLegendEnabled = colorStackLegend === 'true';
|
|
31
|
+
const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
|
|
32
|
+
const setColorStackLegend = useCallback((value) => {
|
|
33
|
+
setStoreColorStackLegend(value);
|
|
34
|
+
}, [setStoreColorStackLegend]);
|
|
35
|
+
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 })] })) }));
|
|
36
|
+
};
|
|
23
37
|
const GroupAndSortActionButtons = ({ navigateTo }) => {
|
|
24
38
|
const [storeSortBy = FIELD_FUNCTION_NAME, setStoreSortBy] = useURLState({
|
|
25
39
|
param: 'sort_by',
|
|
@@ -78,7 +92,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
78
92
|
if (setActionButtons === undefined) {
|
|
79
93
|
return;
|
|
80
94
|
}
|
|
81
|
-
setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full items-end justify-between gap-2", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), _jsx(
|
|
95
|
+
setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full items-end justify-between gap-2", children: [arrow !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), _jsx(ShowHideLegendButton, { navigateTo: navigateTo }), _jsx(Button, { variant: "neutral", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, children: "Reset View" })] }) }));
|
|
82
96
|
}, [navigateTo, arrow, curPath, setNewCurPath, setActionButtons]);
|
|
83
97
|
if (loading) {
|
|
84
98
|
return _jsx("div", { className: "h-96", children: loader });
|