@parca/profile 0.16.359 → 0.16.361
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/Callgraph/utils.d.ts +6 -6
- package/dist/GraphTooltip/index.d.ts +5 -5
- package/dist/GraphTooltipArrow/Content.js +3 -2
- package/dist/GraphTooltipArrow/useGraphTooltip/index.d.ts +1 -0
- package/dist/GraphTooltipArrow/useGraphTooltip/index.js +27 -8
- package/dist/MetricsGraph/MetricsTooltip/index.js +4 -4
- package/dist/MetricsGraph/index.js +3 -2
- package/dist/ProfileIcicleGraph/ActionButtons/GroupByDropdown.d.ts +0 -1
- package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.d.ts +0 -1
- package/dist/ProfileIcicleGraph/ActionButtons/SortBySelect.d.ts +0 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +2 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +62 -9
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +4 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts +3 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.js +5 -2
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts +1 -0
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +3 -0
- package/dist/ProfileSource.d.ts +2 -5
- package/dist/ProfileSource.js +9 -7
- package/dist/ProfileView/ProfileViewContext.d.ts +1 -1
- package/dist/ProfileView/index.js +2 -1
- package/dist/Table/ColumnsVisibility.d.ts +0 -1
- package/package.json +6 -6
- package/src/GraphTooltipArrow/Content.tsx +21 -1
- package/src/GraphTooltipArrow/useGraphTooltip/index.ts +35 -7
- package/src/MetricsGraph/MetricsTooltip/index.tsx +11 -4
- package/src/MetricsGraph/index.tsx +3 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +73 -4
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +4 -0
- package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +9 -1
- package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +11 -0
- package/src/ProfileSource.tsx +11 -15
- package/src/ProfileView/index.tsx +2 -1
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.361](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.360...@parca/profile@0.16.361) (2024-04-26)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## 0.16.360 (2024-04-25)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## 0.16.359 (2024-04-15)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -2,12 +2,12 @@ import { CallgraphEdge, CallgraphNode } from '@parca/client';
|
|
|
2
2
|
export declare const pixelsToInches: (pixels: number) => number;
|
|
3
3
|
export declare const getCurvePoints: ({ pos, xScale, yScale, source, target, offset, isSelfLoop, }: {
|
|
4
4
|
pos: string;
|
|
5
|
-
xScale?: (
|
|
6
|
-
yScale?: (
|
|
7
|
-
source?: number[]
|
|
8
|
-
target?: number[]
|
|
9
|
-
isSelfLoop?: boolean
|
|
10
|
-
offset?: number
|
|
5
|
+
xScale?: (pos: number) => number;
|
|
6
|
+
yScale?: (pos: number) => number;
|
|
7
|
+
source?: number[];
|
|
8
|
+
target?: number[];
|
|
9
|
+
isSelfLoop?: boolean;
|
|
10
|
+
offset?: number;
|
|
11
11
|
}) => number[];
|
|
12
12
|
export declare const jsonToDot: ({ graph, colorRange, }: {
|
|
13
13
|
graph: {
|
|
@@ -31,11 +31,11 @@ export declare const GraphTooltipContent: ({ hoveringNode, unit, total, totalUnf
|
|
|
31
31
|
total: bigint;
|
|
32
32
|
totalUnfiltered: bigint;
|
|
33
33
|
isFixed: boolean;
|
|
34
|
-
strings?: string[]
|
|
35
|
-
mappings?: Mapping[]
|
|
36
|
-
locations?: Location[]
|
|
37
|
-
functions?: ParcaFunction[]
|
|
38
|
-
type?: string
|
|
34
|
+
strings?: string[];
|
|
35
|
+
mappings?: Mapping[];
|
|
36
|
+
locations?: Location[];
|
|
37
|
+
functions?: ParcaFunction[];
|
|
38
|
+
type?: string;
|
|
39
39
|
}) => JSX.Element;
|
|
40
40
|
declare const GraphTooltip: ({ x, y, unit, total, totalUnfiltered, hoveringNode: hoveringNodeProp, contextElement, isFixed, virtualContextElement, strings, mappings, locations, functions, type, }: GraphTooltipProps) => JSX.Element;
|
|
41
41
|
export default GraphTooltip;
|
|
@@ -20,12 +20,13 @@ const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, le
|
|
|
20
20
|
if (graphTooltipData === null) {
|
|
21
21
|
return _jsx(_Fragment, {});
|
|
22
22
|
}
|
|
23
|
-
const { name, locationAddress, cumulativeText, diffText, diff, row: rowNumber } = graphTooltipData;
|
|
23
|
+
const { name, locationAddress, cumulativeText, cumulativePerSecondText, diffText, diff, row: rowNumber, } = graphTooltipData;
|
|
24
|
+
const delta = unit === 'nanoseconds';
|
|
24
25
|
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-start justify-between gap-4 break-all font-semibold", children: row === 0 ? (_jsx("p", { children: "root" })) : (_jsx("p", { children: name !== ''
|
|
25
26
|
? name
|
|
26
27
|
: locationAddress !== 0n
|
|
27
28
|
? hexifyAddress(locationAddress)
|
|
28
|
-
: '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("div", { children: cumulativeText }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx("div", { children: diffText }) })] })), _jsx(TooltipMetaInfo, { table: table, row: rowNumber, navigateTo: navigateTo })] }) })] }) }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to show context menu" })] })] }) }) }));
|
|
29
|
+
: 'unknown' })) }), _jsx("table", { className: "my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300", children: _jsxs("tbody", { children: [delta ? (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Per Second" }), _jsx("td", { className: "w-3/4", children: _jsx("div", { children: cumulativePerSecondText }) })] })) : (_jsx(_Fragment, {})), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Cumulative" }), _jsx("td", { className: "w-3/4", children: _jsx("div", { children: cumulativeText }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx("div", { children: diffText }) })] })), _jsx(TooltipMetaInfo, { table: table, row: rowNumber, navigateTo: navigateTo })] }) })] }) }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to show context menu" })] })] }) }) }));
|
|
29
30
|
};
|
|
30
31
|
const TooltipMetaInfo = ({ table, row, navigateTo, }) => {
|
|
31
32
|
const { labelPairs, functionFilename, file, locationAddress, mappingFile, mappingBuildID, inlined, } = useGraphTooltipMetaInfo({ table, row, navigateTo });
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
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';
|
|
14
|
+
import { FIELD_CUMULATIVE, FIELD_CUMULATIVE_PER_SECOND, FIELD_DIFF, FIELD_DIFF_PER_SECOND, FIELD_LOCATION_ADDRESS, } from '../../ProfileIcicleGraph/IcicleGraphArrow';
|
|
15
|
+
import { getTextForCumulative, getTextForCumulativePerSecond, nodeLabel, } from '../../ProfileIcicleGraph/IcicleGraphArrow/utils';
|
|
16
16
|
export const useGraphTooltip = ({ table, unit, total, totalUnfiltered, row, level, }) => {
|
|
17
17
|
if (row === null) {
|
|
18
18
|
return null;
|
|
@@ -21,20 +21,39 @@ export const useGraphTooltip = ({ table, unit, total, totalUnfiltered, row, leve
|
|
|
21
21
|
const cumulative = table.getChild(FIELD_CUMULATIVE)?.get(row) !== null
|
|
22
22
|
? BigInt(table.getChild(FIELD_CUMULATIVE)?.get(row))
|
|
23
23
|
: 0n;
|
|
24
|
+
const cumulativePerSecond = table.getChild(FIELD_CUMULATIVE_PER_SECOND)?.get(row) !== null
|
|
25
|
+
? table.getChild(FIELD_CUMULATIVE_PER_SECOND)?.get(row)
|
|
26
|
+
: 0;
|
|
24
27
|
const diff = table.getChild(FIELD_DIFF)?.get(row) !== null
|
|
25
28
|
? BigInt(table.getChild(FIELD_DIFF)?.get(row))
|
|
26
29
|
: 0n;
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const diffPerSecond = table.getChild(FIELD_DIFF_PER_SECOND)?.get(row) !== null
|
|
31
|
+
? table.getChild(FIELD_DIFF_PER_SECOND)?.get(row)
|
|
32
|
+
: 0;
|
|
33
|
+
const delta = unit === 'nanoseconds';
|
|
34
|
+
let diffText = '';
|
|
35
|
+
if (delta) {
|
|
36
|
+
const prevValue = cumulativePerSecond - diffPerSecond;
|
|
37
|
+
const diffRatio = diffPerSecond !== 0 ? diffPerSecond / prevValue : 0;
|
|
38
|
+
const diffSign = diffPerSecond > 0 ? '+' : '';
|
|
39
|
+
const diffValueText = diffSign + valueFormatter(diffPerSecond, 'CPU Cores', 5);
|
|
40
|
+
const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
|
|
41
|
+
diffText = `${diffValueText} (${diffPercentageText})`;
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const prevValue = cumulative - diff;
|
|
45
|
+
const diffRatio = diff !== 0n ? divide(diff, prevValue) : 0;
|
|
46
|
+
const diffSign = diff > 0 ? '+' : '';
|
|
47
|
+
const diffValueText = diffSign + valueFormatter(diff, unit, 1);
|
|
48
|
+
const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
|
|
49
|
+
diffText = `${diffValueText} (${diffPercentageText})`;
|
|
50
|
+
}
|
|
33
51
|
const name = nodeLabel(table, row, level, false);
|
|
34
52
|
return {
|
|
35
53
|
name,
|
|
36
54
|
locationAddress,
|
|
37
55
|
cumulativeText: getTextForCumulative(cumulative, totalUnfiltered, total, unit),
|
|
56
|
+
cumulativePerSecondText: getTextForCumulativePerSecond(cumulativePerSecond, unit),
|
|
38
57
|
diffText,
|
|
39
58
|
diff,
|
|
40
59
|
row,
|
|
@@ -14,9 +14,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
14
14
|
import { useEffect, useState } from 'react';
|
|
15
15
|
import { Icon } from '@iconify/react';
|
|
16
16
|
import { usePopper } from 'react-popper';
|
|
17
|
-
import { TextWithTooltip } from '@parca/components';
|
|
18
|
-
import { formatDate, valueFormatter } from '@parca/utilities';
|
|
19
|
-
import { timeFormat } from '../../';
|
|
17
|
+
import { TextWithTooltip, useParcaContext } from '@parca/components';
|
|
18
|
+
import { formatDate, timePattern, valueFormatter } from '@parca/utilities';
|
|
20
19
|
const virtualElement = {
|
|
21
20
|
getBoundingClientRect: () => {
|
|
22
21
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
@@ -44,6 +43,7 @@ function generateGetBoundingClientRect(contextElement, x = 0, y = 0) {
|
|
|
44
43
|
});
|
|
45
44
|
}
|
|
46
45
|
const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta, }) => {
|
|
46
|
+
const { timezone } = useParcaContext();
|
|
47
47
|
const [popperElement, setPopperElement] = useState(null);
|
|
48
48
|
const { styles, attributes, ...popperProps } = usePopper(virtualElement, popperElement, {
|
|
49
49
|
placement: 'auto-start',
|
|
@@ -73,7 +73,7 @@ const MetricsTooltip = ({ x, y, highlighted, contextElement, sampleUnit, delta,
|
|
|
73
73
|
}, [x, y, contextElement, update]);
|
|
74
74
|
const nameLabel = highlighted?.labels.find(e => e.name === '__name__');
|
|
75
75
|
const highlightedNameLabel = nameLabel !== undefined ? nameLabel : { name: '', value: '' };
|
|
76
|
-
return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: _jsx("div", { className: "flex max-w-md", children: _jsx("div", { className: "m-auto", children: _jsx("div", { className: "rounded-lg border-gray-300 bg-gray-50 p-3 opacity-90 shadow-lg dark:border-gray-500 dark:bg-gray-900", style: { borderWidth: 1 }, children: _jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "ml-2 mr-6", children: [_jsx("span", { className: "font-semibold", children: highlightedNameLabel.value }), _jsx("span", { className: "my-2 block text-gray-700 dark:text-gray-300", children: _jsx("table", { className: "table-auto", children: _jsxs("tbody", { children: [delta ? (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Per Second" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit === 'nanoseconds' ? 'CPU Cores' : sampleUnit, 5) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })] })) : (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] })), highlighted.duration > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Duration" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.duration, 'nanoseconds', 2) })] })), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(highlighted.timestamp,
|
|
76
|
+
return (_jsx("div", { ref: setPopperElement, style: styles.popper, ...attributes.popper, className: "z-10", children: _jsx("div", { className: "flex max-w-md", children: _jsx("div", { className: "m-auto", children: _jsx("div", { className: "rounded-lg border-gray-300 bg-gray-50 p-3 opacity-90 shadow-lg dark:border-gray-500 dark:bg-gray-900", style: { borderWidth: 1 }, children: _jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "ml-2 mr-6", children: [_jsx("span", { className: "font-semibold", children: highlightedNameLabel.value }), _jsx("span", { className: "my-2 block text-gray-700 dark:text-gray-300", children: _jsx("table", { className: "table-auto", children: _jsxs("tbody", { children: [delta ? (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Per Second" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit === 'nanoseconds' ? 'CPU Cores' : sampleUnit, 5) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Total" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.value, sampleUnit, 2) })] })] })) : (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Value" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.valuePerSecond, sampleUnit, 5) })] })), highlighted.duration > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Duration" }), _jsx("td", { className: "w-3/4", children: valueFormatter(highlighted.duration, 'nanoseconds', 2) })] })), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "At" }), _jsx("td", { className: "w-3/4", children: formatDate(highlighted.timestamp, timePattern(timezone), timezone) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
|
|
77
77
|
.filter((label) => label.name !== '__name__')
|
|
78
78
|
.map((label) => (_jsx("div", { 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: _jsx(TextWithTooltip, { text: `${label.name}="${label.value}"`, maxTextLength: 37, id: `tooltip-${label.name}-${label.value}` }) }, label.name))) }), _jsxs("div", { className: "flex w-full items-center gap-1 text-xs text-gray-500", children: [_jsx(Icon, { icon: "iconoir:mouse-button-right" }), _jsx("div", { children: "Right click to add labels to query." })] })] }) }) }) }) }) }));
|
|
79
79
|
};
|
|
@@ -16,7 +16,7 @@ import * as d3 from 'd3';
|
|
|
16
16
|
import { pointer } from 'd3-selection';
|
|
17
17
|
import throttle from 'lodash.throttle';
|
|
18
18
|
import { useContextMenu } from 'react-contexify';
|
|
19
|
-
import { DateTimeRange } from '@parca/components';
|
|
19
|
+
import { DateTimeRange, useParcaContext } from '@parca/components';
|
|
20
20
|
import { formatDate, formatForTimespan, getPrecision, sanitizeHighlightedValues, valueFormatter, } from '@parca/utilities';
|
|
21
21
|
import MetricsCircle from '../MetricsCircle';
|
|
22
22
|
import MetricsSeries from '../MetricsSeries';
|
|
@@ -37,6 +37,7 @@ export const parseValue = (value) => {
|
|
|
37
37
|
const lineStroke = '1px';
|
|
38
38
|
const lineStrokeHover = '2px';
|
|
39
39
|
export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLabelMatcher, setTimeRange, width, height = 50, margin = 0, sampleUnit, }) => {
|
|
40
|
+
const { timezone } = useParcaContext();
|
|
40
41
|
const graph = useRef(null);
|
|
41
42
|
const [dragging, setDragging] = useState(false);
|
|
42
43
|
const [hovering, setHovering] = useState(false);
|
|
@@ -265,7 +266,7 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLab
|
|
|
265
266
|
transform: `translate(0, ${yScale(d)})`, children: [_jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x2: -6 }), _jsx("text", { fill: "currentColor", x: -9, dy: '0.32em', children: valueFormatter(d, yAxisUnit, decimals) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: yScale(d), y2: yScale(d) }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`));
|
|
266
267
|
}), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: 0, x2: 0, y1: 0, y2: height - margin }), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(to), x2: xScale(to), y1: 0, y2: height - margin }), _jsx("g", { transform: `translate(${-margin}, ${(height - margin) / 2}) rotate(270)`, children: _jsx("text", { fill: "currentColor", dy: "-0.7em", className: "text-sm capitalize", textAnchor: "middle", children: yAxisLabel }) })] }), _jsxs("g", { className: "x axis", fill: "none", fontSize: "10", textAnchor: "middle", transform: `translate(0,${height - margin})`, children: [xScale.ticks(5).map((d, i) => (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
|
|
267
268
|
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
|
268
|
-
transform: `translate(${xScale(d)}, 0)`, children: [_jsx("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-500" }), _jsx("text", { fill: "currentColor", dy: ".71em", y: 9, children: formatDate(d, formatForTimespan(from, to)) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(d), x2: xScale(d), y1: 0, y2: -height + margin }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`))), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: 0, y2: 0 }), _jsx("g", { transform: `translate(${(width - 2.5 * margin) / 2}, ${margin / 2})`, children: _jsx("text", { fill: "currentColor", dy: ".71em", y: 5, className: "text-sm", children: "Time" }) })] }), _jsx("g", { className: "lines fill-transparent", children: series.map((s, i) => (_jsx("g", { className: "line", children: _jsx(MetricsSeries, { data: s, line: l, color: color(i.toString()), strokeWidth: hovering && highlighted != null && i === highlighted.seriesIndex
|
|
269
|
+
transform: `translate(${xScale(d)}, 0)`, children: [_jsx("line", { y2: 6, className: "stroke-gray-300 dark:stroke-gray-500" }), _jsx("text", { fill: "currentColor", dy: ".71em", y: 9, children: formatDate(d, formatForTimespan(from, to), timezone) })] }, `tick-${i}`), _jsx("g", { children: _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(d), x2: xScale(d), y1: 0, y2: -height + margin }) }, `grid-${i}`)] }, `${i.toString()}-${d.toString()}`))), _jsx("line", { className: "stroke-gray-300 dark:stroke-gray-500", x1: xScale(from), x2: xScale(to), y1: 0, y2: 0 }), _jsx("g", { transform: `translate(${(width - 2.5 * margin) / 2}, ${margin / 2})`, children: _jsx("text", { fill: "currentColor", dy: ".71em", y: 5, className: "text-sm", children: "Time" }) })] }), _jsx("g", { className: "lines fill-transparent", children: series.map((s, i) => (_jsx("g", { className: "line", children: _jsx(MetricsSeries, { data: s, line: l, color: color(i.toString()), strokeWidth: hovering && highlighted != null && i === highlighted.seriesIndex
|
|
269
270
|
? lineStrokeHover
|
|
270
271
|
: lineStroke, xScale: xScale, yScale: yScale }) }, i))) }), hovering && highlighted != null && (_jsx("g", { className: "circle-group", ref: metricPointRef, style: { fill: color(highlighted.seriesIndex.toString()) }, children: _jsx(MetricsCircle, { cx: highlighted.x, cy: highlighted.y }) })), selected != null && (_jsx("g", { className: "circle-group", style: selected?.seriesIndex != null
|
|
271
272
|
? { fill: color(selected.seriesIndex.toString()) }
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="react" />
|
|
2
1
|
declare const RuntimeFilterDropdown: ({ showRuntimeRuby, toggleShowRuntimeRuby, showRuntimePython, toggleShowRuntimePython, showInterpretedOnly, toggleShowInterpretedOnly, }: {
|
|
3
2
|
showRuntimeRuby: boolean;
|
|
4
3
|
toggleShowRuntimeRuby: () => void;
|
|
@@ -22,6 +22,7 @@ interface IcicleGraphNodesProps {
|
|
|
22
22
|
sortBy: string;
|
|
23
23
|
darkMode: boolean;
|
|
24
24
|
compareMode: boolean;
|
|
25
|
+
delta: boolean;
|
|
25
26
|
isContextMenuOpen: boolean;
|
|
26
27
|
hoveringName: string | null;
|
|
27
28
|
setHoveringName: (name: string | null) => void;
|
|
@@ -54,6 +55,7 @@ interface IcicleNodeProps {
|
|
|
54
55
|
sortBy: string;
|
|
55
56
|
darkMode: boolean;
|
|
56
57
|
compareMode: boolean;
|
|
58
|
+
delta: boolean;
|
|
57
59
|
isContextMenuOpen: boolean;
|
|
58
60
|
hoveringName: string | null;
|
|
59
61
|
setHoveringName: (name: string | null) => void;
|
|
@@ -16,11 +16,11 @@ import cx from 'classnames';
|
|
|
16
16
|
import { selectBinaries, useAppSelector } from '@parca/store';
|
|
17
17
|
import { isSearchMatch, scaleLinear } from '@parca/utilities';
|
|
18
18
|
import 'react-contexify/dist/ReactContexify.css';
|
|
19
|
-
import { FIELD_CHILDREN, FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_NAME, FIELD_MAPPING_FILE, } from './index';
|
|
19
|
+
import { FIELD_CHILDREN, FIELD_CUMULATIVE, FIELD_CUMULATIVE_PER_SECOND, FIELD_DIFF, FIELD_DIFF_PER_SECOND, FIELD_FUNCTION_NAME, FIELD_MAPPING_FILE, } from './index';
|
|
20
20
|
import useNodeColor from './useNodeColor';
|
|
21
21
|
import { arrowToString, nodeLabel } from './utils';
|
|
22
22
|
export const RowHeight = 26;
|
|
23
|
-
export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({ table, childRows, mappingColors, x, y, xScale, total, totalWidth, level, path, setCurPath, setHoveringRow, setHoveringLevel, curPath, sortBy, searchString, darkMode, compareMode, isContextMenuOpen, hoveringName, setHoveringName, hoveringRow, colorForSimilarNodes, highlightSimilarStacksPreference, }) {
|
|
23
|
+
export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({ table, childRows, mappingColors, x, y, xScale, total, totalWidth, level, path, setCurPath, setHoveringRow, setHoveringLevel, curPath, sortBy, searchString, darkMode, compareMode, delta, isContextMenuOpen, hoveringName, setHoveringName, hoveringRow, colorForSimilarNodes, highlightSimilarStacksPreference, }) {
|
|
24
24
|
const cumulatives = table.getChild(FIELD_CUMULATIVE);
|
|
25
25
|
if (childRows === undefined || childRows.length === 0) {
|
|
26
26
|
return _jsx(_Fragment, {});
|
|
@@ -35,7 +35,7 @@ export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({ tab
|
|
|
35
35
|
const xStart = Math.floor(xScale(BigInt(childrenCumulative)));
|
|
36
36
|
const c = BigInt(cumulatives?.get(child));
|
|
37
37
|
childrenCumulative += c;
|
|
38
|
-
childrenElements.push(_jsx(IcicleNode, { table: table, row: child, mappingColors: mappingColors, x: xStart, y: 0, totalWidth: totalWidth, height: RowHeight, path: path, setCurPath: setCurPath, setHoveringRow: setHoveringRow, setHoveringLevel: setHoveringLevel, level: level, curPath: curPath, total: total, xScale: xScale, sortBy: sortBy, searchString: searchString, darkMode: darkMode, compareMode: compareMode, isContextMenuOpen: isContextMenuOpen, hoveringName: hoveringName, setHoveringName: setHoveringName, hoveringRow: hoveringRow, colorForSimilarNodes: colorForSimilarNodes, highlightSimilarStacksPreference: highlightSimilarStacksPreference }, `node-${level}-${i}`));
|
|
38
|
+
childrenElements.push(_jsx(IcicleNode, { table: table, row: child, mappingColors: mappingColors, x: xStart, y: 0, totalWidth: totalWidth, height: RowHeight, path: path, setCurPath: setCurPath, setHoveringRow: setHoveringRow, setHoveringLevel: setHoveringLevel, level: level, curPath: curPath, total: total, xScale: xScale, sortBy: sortBy, searchString: searchString, darkMode: darkMode, compareMode: compareMode, delta: delta, isContextMenuOpen: isContextMenuOpen, hoveringName: hoveringName, setHoveringName: setHoveringName, hoveringRow: hoveringRow, colorForSimilarNodes: colorForSimilarNodes, highlightSimilarStacksPreference: highlightSimilarStacksPreference }, `node-${level}-${i}`));
|
|
39
39
|
});
|
|
40
40
|
return _jsx("g", { transform: `translate(${x}, ${y})`, children: childrenElements });
|
|
41
41
|
});
|
|
@@ -48,17 +48,21 @@ const fadedIcicleRectStyles = {
|
|
|
48
48
|
transition: 'opacity .15s linear',
|
|
49
49
|
opacity: '0.5',
|
|
50
50
|
};
|
|
51
|
-
export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, mappingColors, x, y, height, setCurPath, curPath, level, path, total, totalWidth, xScale, isRoot = false, searchString, setHoveringRow, setHoveringLevel, sortBy, darkMode, compareMode, isContextMenuOpen, hoveringName, setHoveringName, hoveringRow, colorForSimilarNodes, highlightSimilarStacksPreference, }) {
|
|
51
|
+
export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, mappingColors, x, y, height, setCurPath, curPath, level, path, total, totalWidth, xScale, isRoot = false, searchString, setHoveringRow, setHoveringLevel, sortBy, darkMode, compareMode, delta, isContextMenuOpen, hoveringName, setHoveringName, hoveringRow, colorForSimilarNodes, highlightSimilarStacksPreference, }) {
|
|
52
52
|
// get the columns to read from
|
|
53
53
|
const mappingColumn = table.getChild(FIELD_MAPPING_FILE);
|
|
54
54
|
const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
|
|
55
55
|
const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
|
|
56
|
+
const cumulativePerSecondColumn = table.getChild(FIELD_CUMULATIVE_PER_SECOND);
|
|
56
57
|
const diffColumn = table.getChild(FIELD_DIFF);
|
|
58
|
+
const diffPerSecondColumn = table.getChild(FIELD_DIFF_PER_SECOND);
|
|
57
59
|
// get the actual values from the columns
|
|
58
60
|
const mappingFile = arrowToString(mappingColumn?.get(row));
|
|
59
61
|
const functionName = arrowToString(functionNameColumn?.get(row));
|
|
60
62
|
const cumulative = cumulativeColumn?.get(row) !== null ? BigInt(cumulativeColumn?.get(row)) : 0n;
|
|
63
|
+
const cumulativePerSecond = cumulativePerSecondColumn?.get(row) != null ? cumulativePerSecondColumn.get(row) : 0;
|
|
61
64
|
const diff = diffColumn?.get(row) !== null ? BigInt(diffColumn?.get(row)) : null;
|
|
65
|
+
const diffPerSecond = diffPerSecondColumn?.get(row) != null ? diffPerSecondColumn.get(row) : null;
|
|
62
66
|
const childRows = Array.from(table.getChild(FIELD_CHILDREN)?.get(row) ?? []);
|
|
63
67
|
const highlightedNodes = useMemo(() => {
|
|
64
68
|
if (!highlightSimilarStacksPreference) {
|
|
@@ -93,6 +97,14 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, map
|
|
|
93
97
|
});
|
|
94
98
|
break;
|
|
95
99
|
case FIELD_CUMULATIVE:
|
|
100
|
+
if (delta) {
|
|
101
|
+
childRows.sort((a, b) => {
|
|
102
|
+
const aCumulativePerSecond = cumulativePerSecondColumn?.get(a);
|
|
103
|
+
const bCumulativePerSecond = cumulativePerSecondColumn?.get(b);
|
|
104
|
+
return bCumulativePerSecond - aCumulativePerSecond;
|
|
105
|
+
});
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
96
108
|
childRows.sort((a, b) => {
|
|
97
109
|
const aCumulative = cumulativeColumn?.get(a);
|
|
98
110
|
const bCumulative = cumulativeColumn?.get(b);
|
|
@@ -101,15 +113,54 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, map
|
|
|
101
113
|
break;
|
|
102
114
|
case FIELD_DIFF:
|
|
103
115
|
childRows.sort((a, b) => {
|
|
116
|
+
if (delta) {
|
|
117
|
+
let aRatio = null;
|
|
118
|
+
let bRatio = null;
|
|
119
|
+
const aDiff = diffPerSecondColumn?.get(a);
|
|
120
|
+
if (aDiff !== null) {
|
|
121
|
+
const cumulative = cumulativePerSecondColumn?.get(a);
|
|
122
|
+
const prev = cumulative - aDiff;
|
|
123
|
+
aRatio = aDiff / prev;
|
|
124
|
+
}
|
|
125
|
+
const bDiff = diffPerSecondColumn?.get(b);
|
|
126
|
+
if (bDiff !== null) {
|
|
127
|
+
const cumulative = cumulativePerSecondColumn?.get(b);
|
|
128
|
+
const prev = cumulative - bDiff;
|
|
129
|
+
bRatio = bDiff / prev;
|
|
130
|
+
}
|
|
131
|
+
if (aRatio !== null && bRatio !== null) {
|
|
132
|
+
return bRatio - aRatio;
|
|
133
|
+
}
|
|
134
|
+
if (aRatio === null && bRatio !== null) {
|
|
135
|
+
return -1;
|
|
136
|
+
}
|
|
137
|
+
if (aRatio !== null && bRatio === null) {
|
|
138
|
+
return 1;
|
|
139
|
+
}
|
|
140
|
+
// both are null
|
|
141
|
+
return 0;
|
|
142
|
+
}
|
|
143
|
+
let aRatio = null;
|
|
104
144
|
const aDiff = diffColumn?.get(a);
|
|
145
|
+
if (aDiff !== null) {
|
|
146
|
+
const cumulative = cumulativeColumn?.get(a) ?? 0n;
|
|
147
|
+
const prev = cumulative - aDiff;
|
|
148
|
+
aRatio = Number(aDiff) / Number(prev);
|
|
149
|
+
}
|
|
150
|
+
let bRatio = null;
|
|
105
151
|
const bDiff = diffColumn?.get(b);
|
|
106
|
-
if (
|
|
107
|
-
|
|
152
|
+
if (bDiff !== null) {
|
|
153
|
+
const cumulative = cumulativeColumn?.get(b) ?? 0n;
|
|
154
|
+
const prev = cumulative - bDiff;
|
|
155
|
+
bRatio = Number(bDiff) / Number(prev);
|
|
156
|
+
}
|
|
157
|
+
if (aRatio !== null && bRatio !== null) {
|
|
158
|
+
return bRatio - aRatio;
|
|
108
159
|
}
|
|
109
|
-
if (
|
|
160
|
+
if (aRatio === null && bRatio !== null) {
|
|
110
161
|
return -1;
|
|
111
162
|
}
|
|
112
|
-
if (
|
|
163
|
+
if (aRatio !== null && bRatio === null) {
|
|
113
164
|
return 1;
|
|
114
165
|
}
|
|
115
166
|
// both are null
|
|
@@ -122,7 +173,9 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, map
|
|
|
122
173
|
isDarkMode: darkMode,
|
|
123
174
|
compareMode,
|
|
124
175
|
cumulative,
|
|
176
|
+
cumulativePerSecond,
|
|
125
177
|
diff,
|
|
178
|
+
diffPerSecond,
|
|
126
179
|
mappingColors,
|
|
127
180
|
mappingFile,
|
|
128
181
|
functionName,
|
|
@@ -172,5 +225,5 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, map
|
|
|
172
225
|
? `${colorForSimilarNodes} stroke-[3] [stroke-dasharray:6,4] [stroke-linecap:round] [stroke-linejoin:round] h-6`
|
|
173
226
|
: 'stroke-white dark:stroke-gray-700', {
|
|
174
227
|
'opacity-50': isHighlightEnabled && !isHighlighted,
|
|
175
|
-
}) }), width > 5 && (_jsx("svg", { width: width - 5, height: height, children: _jsx("text", { x: 5, y: 15, style: { fontSize: '12px' }, children: name }) }))] }), childRows.length > 0 && (_jsx(IcicleGraphNodes, { table: table, row: row, mappingColors: mappingColors, childRows: childRows, x: x, y: RowHeight, xScale: newXScale, total: total, totalWidth: totalWidth, level: nextLevel, path: nextPath, curPath: nextCurPath, setCurPath: setCurPath, setHoveringRow: setHoveringRow, setHoveringLevel: setHoveringLevel, searchString: searchString, sortBy: sortBy, darkMode: darkMode, compareMode: compareMode, isContextMenuOpen: isContextMenuOpen, hoveringName: hoveringName, setHoveringName: setHoveringName, hoveringRow: hoveringRow, colorForSimilarNodes: colorForSimilarNodes, highlightSimilarStacksPreference: highlightSimilarStacksPreference }))] }));
|
|
228
|
+
}) }), width > 5 && (_jsx("svg", { width: width - 5, height: height, children: _jsx("text", { x: 5, y: 15, style: { fontSize: '12px' }, children: name }) }))] }), childRows.length > 0 && (_jsx(IcicleGraphNodes, { table: table, row: row, mappingColors: mappingColors, childRows: childRows, x: x, y: RowHeight, xScale: newXScale, total: total, totalWidth: totalWidth, level: nextLevel, path: nextPath, curPath: nextCurPath, setCurPath: setCurPath, setHoveringRow: setHoveringRow, setHoveringLevel: setHoveringLevel, searchString: searchString, sortBy: sortBy, darkMode: darkMode, delta: delta, compareMode: compareMode, isContextMenuOpen: isContextMenuOpen, hoveringName: hoveringName, setHoveringName: setHoveringName, hoveringRow: hoveringRow, colorForSimilarNodes: colorForSimilarNodes, highlightSimilarStacksPreference: highlightSimilarStacksPreference }))] }));
|
|
176
229
|
});
|
|
@@ -14,7 +14,9 @@ export declare const FIELD_FUNCTION_START_LINE = "function_startline";
|
|
|
14
14
|
export declare const FIELD_CHILDREN = "children";
|
|
15
15
|
export declare const FIELD_LABELS = "labels";
|
|
16
16
|
export declare const FIELD_CUMULATIVE = "cumulative";
|
|
17
|
+
export declare const FIELD_CUMULATIVE_PER_SECOND = "cumulative_per_second";
|
|
17
18
|
export declare const FIELD_DIFF = "diff";
|
|
19
|
+
export declare const FIELD_DIFF_PER_SECOND = "diff_per_second";
|
|
18
20
|
interface IcicleGraphArrowProps {
|
|
19
21
|
arrow: FlamegraphArrow;
|
|
20
22
|
total: bigint;
|
|
@@ -38,7 +38,9 @@ export const FIELD_FUNCTION_START_LINE = 'function_startline';
|
|
|
38
38
|
export const FIELD_CHILDREN = 'children';
|
|
39
39
|
export const FIELD_LABELS = 'labels';
|
|
40
40
|
export const FIELD_CUMULATIVE = 'cumulative';
|
|
41
|
+
export const FIELD_CUMULATIVE_PER_SECOND = 'cumulative_per_second';
|
|
41
42
|
export const FIELD_DIFF = 'diff';
|
|
43
|
+
export const FIELD_DIFF_PER_SECOND = 'diff_per_second';
|
|
42
44
|
export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, filtered, width, setCurPath, curPath, sampleUnit, navigateTo, sortBy, }) {
|
|
43
45
|
const [isContextMenuOpen, setIsContextMenuOpen] = useState(false);
|
|
44
46
|
const dispatch = useAppDispatch();
|
|
@@ -139,13 +141,14 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
139
141
|
};
|
|
140
142
|
// useMemo for the root graph as it otherwise renders the whole graph if the hoveringRow changes.
|
|
141
143
|
const root = useMemo(() => {
|
|
142
|
-
return (_jsx("svg", { className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg, onContextMenu: displayMenu, children: _jsx("g", { ref: ref, children: _jsx("g", { transform: 'translate(0, 0)', children: _jsx(IcicleNode, { table: table, row: 0, mappingColors: mappingColors, x: 0, y: 0, totalWidth: width ?? 1, height: RowHeight, setCurPath: setCurPath, curPath: curPath, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, setHoveringRow: setHoveringRow, setHoveringLevel: setHoveringLevel, sortBy: sortBy, darkMode: isDarkMode, compareMode: compareMode, isContextMenuOpen: isContextMenuOpen, hoveringName: hoveringName, setHoveringName: setHoveringName, hoveringRow: hoveringRow, colorForSimilarNodes: colorForSimilarNodes, highlightSimilarStacksPreference: highlightSimilarStacksPreference }) }) }) }));
|
|
144
|
+
return (_jsx("svg", { className: "font-robotoMono", width: width, height: height, preserveAspectRatio: "xMinYMid", ref: svg, onContextMenu: displayMenu, children: _jsx("g", { ref: ref, children: _jsx("g", { transform: 'translate(0, 0)', children: _jsx(IcicleNode, { table: table, row: 0, mappingColors: mappingColors, x: 0, y: 0, totalWidth: width ?? 1, height: RowHeight, setCurPath: setCurPath, curPath: curPath, total: total, xScale: xScale, path: [], level: 0, isRoot: true, searchString: currentSearchString, setHoveringRow: setHoveringRow, setHoveringLevel: setHoveringLevel, sortBy: sortBy, darkMode: isDarkMode, compareMode: compareMode, delta: sampleUnit === 'nanoseconds', isContextMenuOpen: isContextMenuOpen, hoveringName: hoveringName, setHoveringName: setHoveringName, hoveringRow: hoveringRow, colorForSimilarNodes: colorForSimilarNodes, highlightSimilarStacksPreference: highlightSimilarStacksPreference }) }) }) }));
|
|
143
145
|
}, [
|
|
144
146
|
compareMode,
|
|
145
147
|
curPath,
|
|
146
148
|
currentSearchString,
|
|
147
149
|
height,
|
|
148
150
|
isDarkMode,
|
|
151
|
+
sampleUnit,
|
|
149
152
|
mappingColors,
|
|
150
153
|
setCurPath,
|
|
151
154
|
sortBy,
|
|
@@ -5,10 +5,12 @@ interface Props {
|
|
|
5
5
|
isDarkMode: boolean;
|
|
6
6
|
compareMode: boolean;
|
|
7
7
|
cumulative: bigint;
|
|
8
|
+
cumulativePerSecond: number | null;
|
|
8
9
|
diff: bigint | null;
|
|
10
|
+
diffPerSecond: number | null;
|
|
9
11
|
mappingColors: mappingColors;
|
|
10
12
|
functionName: string | null;
|
|
11
13
|
mappingFile: string | null;
|
|
12
14
|
}
|
|
13
|
-
declare const useNodeColor: ({ isDarkMode, compareMode, cumulative, diff, mappingColors, functionName, mappingFile, }: Props) => string;
|
|
15
|
+
declare const useNodeColor: ({ isDarkMode, compareMode, cumulative, cumulativePerSecond, diff, diffPerSecond, mappingColors, functionName, mappingFile, }: Props) => string;
|
|
14
16
|
export default useNodeColor;
|
|
@@ -11,9 +11,12 @@
|
|
|
11
11
|
// See the License for the specific language governing permissions and
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
import { EVERYTHING_ELSE } from '@parca/store';
|
|
14
|
-
import { diffColor, getLastItem } from '@parca/utilities';
|
|
15
|
-
const useNodeColor = ({ isDarkMode, compareMode, cumulative, diff, mappingColors, functionName, mappingFile, }) => {
|
|
14
|
+
import { diffColor, diffColorPerSecond, getLastItem } from '@parca/utilities';
|
|
15
|
+
const useNodeColor = ({ isDarkMode, compareMode, cumulative, cumulativePerSecond, diff, diffPerSecond, mappingColors, functionName, mappingFile, }) => {
|
|
16
16
|
if (compareMode) {
|
|
17
|
+
if (cumulativePerSecond !== null && diffPerSecond !== null) {
|
|
18
|
+
return diffColorPerSecond(diffPerSecond, cumulativePerSecond, isDarkMode);
|
|
19
|
+
}
|
|
17
20
|
return diffColor(diff ?? 0n, cumulative, isDarkMode);
|
|
18
21
|
}
|
|
19
22
|
// To get the color we first check if the function name starts with 'runtime'.
|
|
@@ -3,4 +3,5 @@ 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
5
|
export declare const getTextForCumulative: (hoveringNodeCumulative: bigint, totalUnfiltered: bigint, total: bigint, unit: string) => string;
|
|
6
|
+
export declare const getTextForCumulativePerSecond: (hoveringNodeCumulative: number, unit: string) => string;
|
|
6
7
|
export declare const arrowToString: (buffer: any) => string | null;
|
|
@@ -60,6 +60,9 @@ export const getTextForCumulative = (hoveringNodeCumulative, totalUnfiltered, to
|
|
|
60
60
|
return `${valueFormatter(hoveringNodeCumulative, unit, 2)}
|
|
61
61
|
(${(100 * divide(hoveringNodeCumulative, totalUnfiltered)).toFixed(2)}%${filtered})`;
|
|
62
62
|
};
|
|
63
|
+
export const getTextForCumulativePerSecond = (hoveringNodeCumulative, unit) => {
|
|
64
|
+
return `${valueFormatter(hoveringNodeCumulative, unit === 'nanoseconds' ? 'CPU Cores' : unit, 5)}`;
|
|
65
|
+
};
|
|
63
66
|
export const arrowToString = (buffer) => {
|
|
64
67
|
if (buffer == null || typeof buffer === 'string') {
|
|
65
68
|
return buffer;
|
package/dist/ProfileSource.d.ts
CHANGED
|
@@ -5,8 +5,7 @@ export interface ProfileSource {
|
|
|
5
5
|
QueryRequest: () => QueryRequest;
|
|
6
6
|
ProfileType: () => ProfileType;
|
|
7
7
|
DiffSelection: () => ProfileDiffSelection;
|
|
8
|
-
|
|
9
|
-
toString: () => string;
|
|
8
|
+
toString: (timezone?: string) => string;
|
|
10
9
|
}
|
|
11
10
|
export interface ProfileSelection {
|
|
12
11
|
ProfileName: () => string;
|
|
@@ -16,7 +15,6 @@ export interface ProfileSelection {
|
|
|
16
15
|
ProfileSource: () => ProfileSource;
|
|
17
16
|
Type: () => string;
|
|
18
17
|
}
|
|
19
|
-
export declare const timeFormat = "yyyy-MM-dd HH:mm:ss '(UTC)'";
|
|
20
18
|
export declare function ParamsString(params: {
|
|
21
19
|
[key: string]: string;
|
|
22
20
|
}): string;
|
|
@@ -60,7 +58,6 @@ export declare class MergedProfileSource implements ProfileSource {
|
|
|
60
58
|
DiffSelection(): ProfileDiffSelection;
|
|
61
59
|
QueryRequest(): QueryRequest;
|
|
62
60
|
ProfileType(): ProfileType;
|
|
63
|
-
Describe(): JSX.Element;
|
|
64
61
|
stringMatchers(): string[];
|
|
65
|
-
toString(): string;
|
|
62
|
+
toString(timezone?: string): string;
|
|
66
63
|
}
|
package/dist/ProfileSource.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, Fragment as _Fragment
|
|
1
|
+
import { jsx as _jsx, 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.
|
|
@@ -14,7 +14,12 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
14
14
|
import { ProfileDiffSelection_Mode, QueryRequest_Mode, QueryRequest_ReportType, Timestamp, } from '@parca/client';
|
|
15
15
|
import { ProfileType, Query } from '@parca/parser';
|
|
16
16
|
import { formatDate } from '@parca/utilities';
|
|
17
|
-
|
|
17
|
+
const timeFormat = (timezone) => {
|
|
18
|
+
if (timezone !== undefined) {
|
|
19
|
+
return 'yyyy-MM-dd HH:mm:ss';
|
|
20
|
+
}
|
|
21
|
+
return "yyyy-MM-dd HH:mm:ss '(UTC)'";
|
|
22
|
+
};
|
|
18
23
|
export function ParamsString(params) {
|
|
19
24
|
return Object.keys(params)
|
|
20
25
|
.map(function (key) {
|
|
@@ -153,15 +158,12 @@ export class MergedProfileSource {
|
|
|
153
158
|
ProfileType() {
|
|
154
159
|
return ProfileType.fromString(Query.parse(this.query.toString()).profileName());
|
|
155
160
|
}
|
|
156
|
-
Describe() {
|
|
157
|
-
return (_jsxs("a", { children: ["Merge of \"", this.query.toString(), "\" from ", formatDate(this.mergeFrom, timeFormat), ' ', "to ", formatDate(this.mergeTo, timeFormat)] }));
|
|
158
|
-
}
|
|
159
161
|
stringMatchers() {
|
|
160
162
|
return this.query.matchers
|
|
161
163
|
.filter((m) => m.key !== '__name__')
|
|
162
164
|
.map((m) => `${m.key}=${m.value}`);
|
|
163
165
|
}
|
|
164
|
-
toString() {
|
|
165
|
-
return `merged profiles of query "${this.query.toString()}" from ${formatDate(this.mergeFrom, timeFormat)} to ${formatDate(this.mergeTo, timeFormat)}`;
|
|
166
|
+
toString(timezone) {
|
|
167
|
+
return `merged profiles of query "${this.query.toString()}" from ${formatDate(this.mergeFrom, timeFormat(timezone), timezone)} to ${formatDate(this.mergeTo, timeFormat(timezone), timezone)}`;
|
|
166
168
|
}
|
|
167
169
|
}
|
|
@@ -9,7 +9,7 @@ export declare const defaultValue: Props;
|
|
|
9
9
|
declare const ProfileViewContext: import("react").Context<Props>;
|
|
10
10
|
export declare const ProfileViewContextProvider: ({ children, value, }: {
|
|
11
11
|
children: ReactNode;
|
|
12
|
-
value?: Props
|
|
12
|
+
value?: Props;
|
|
13
13
|
}) => JSX.Element;
|
|
14
14
|
export declare const useProfileViewContext: () => Props;
|
|
15
15
|
export default ProfileViewContext;
|
|
@@ -39,6 +39,7 @@ function arrayEquals(a, b) {
|
|
|
39
39
|
a.every((val, index) => val === b[index]));
|
|
40
40
|
}
|
|
41
41
|
export const ProfileView = ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, compare, }) => {
|
|
42
|
+
const { timezone } = useParcaContext();
|
|
42
43
|
const { ref, dimensions } = useContainerDimensions();
|
|
43
44
|
const [curPath, setCurPath] = useState([]);
|
|
44
45
|
const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
|
|
@@ -144,7 +145,7 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
144
145
|
}
|
|
145
146
|
};
|
|
146
147
|
// TODO: this is just a placeholder, we need to replace with an actually informative and accurate title (cc @metalmatze)
|
|
147
|
-
const profileSourceString = profileSource?.toString();
|
|
148
|
+
const profileSourceString = profileSource?.toString(timezone);
|
|
148
149
|
const hasProfileSource = profileSource !== undefined && profileSourceString !== '';
|
|
149
150
|
const headerParts = profileSourceString?.split('"') ?? [];
|
|
150
151
|
const compareMode = compare === true ||
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.361",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@parca/client": "^0.16.107",
|
|
7
|
-
"@parca/components": "^0.16.
|
|
7
|
+
"@parca/components": "^0.16.267",
|
|
8
8
|
"@parca/dynamicsize": "^0.16.61",
|
|
9
|
-
"@parca/hooks": "^0.0.
|
|
9
|
+
"@parca/hooks": "^0.0.48",
|
|
10
10
|
"@parca/parser": "^0.16.69",
|
|
11
|
-
"@parca/store": "^0.16.
|
|
12
|
-
"@parca/utilities": "^0.0.
|
|
11
|
+
"@parca/store": "^0.16.137",
|
|
12
|
+
"@parca/utilities": "^0.0.65",
|
|
13
13
|
"@tanstack/react-query": "^4.0.5",
|
|
14
14
|
"@types/react-beautiful-dnd": "^13.1.8",
|
|
15
15
|
"apache-arrow": "^12.0.0",
|
|
@@ -50,5 +50,5 @@
|
|
|
50
50
|
"access": "public",
|
|
51
51
|
"registry": "https://registry.npmjs.org/"
|
|
52
52
|
},
|
|
53
|
-
"gitHead": "
|
|
53
|
+
"gitHead": "d900cb1de5375362e3e211809c98990a6e686140"
|
|
54
54
|
}
|
|
@@ -61,7 +61,17 @@ const GraphTooltipArrowContent = ({
|
|
|
61
61
|
return <></>;
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
const {
|
|
64
|
+
const {
|
|
65
|
+
name,
|
|
66
|
+
locationAddress,
|
|
67
|
+
cumulativeText,
|
|
68
|
+
cumulativePerSecondText,
|
|
69
|
+
diffText,
|
|
70
|
+
diff,
|
|
71
|
+
row: rowNumber,
|
|
72
|
+
} = graphTooltipData;
|
|
73
|
+
|
|
74
|
+
const delta = unit === 'nanoseconds';
|
|
65
75
|
|
|
66
76
|
return (
|
|
67
77
|
<div className={`flex text-sm ${isFixed ? 'w-full' : ''}`}>
|
|
@@ -84,6 +94,16 @@ const GraphTooltipArrowContent = ({
|
|
|
84
94
|
</div>
|
|
85
95
|
<table className="my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300">
|
|
86
96
|
<tbody>
|
|
97
|
+
{delta ? (
|
|
98
|
+
<tr>
|
|
99
|
+
<td className="w-1/4">Per Second</td>
|
|
100
|
+
<td className="w-3/4">
|
|
101
|
+
<div>{cumulativePerSecondText}</div>
|
|
102
|
+
</td>
|
|
103
|
+
</tr>
|
|
104
|
+
) : (
|
|
105
|
+
<></>
|
|
106
|
+
)}
|
|
87
107
|
<tr>
|
|
88
108
|
<td className="w-1/4">Cumulative</td>
|
|
89
109
|
|
|
@@ -17,10 +17,16 @@ import {divide, valueFormatter} from '@parca/utilities';
|
|
|
17
17
|
|
|
18
18
|
import {
|
|
19
19
|
FIELD_CUMULATIVE,
|
|
20
|
+
FIELD_CUMULATIVE_PER_SECOND,
|
|
20
21
|
FIELD_DIFF,
|
|
22
|
+
FIELD_DIFF_PER_SECOND,
|
|
21
23
|
FIELD_LOCATION_ADDRESS,
|
|
22
24
|
} from '../../ProfileIcicleGraph/IcicleGraphArrow';
|
|
23
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
getTextForCumulative,
|
|
27
|
+
getTextForCumulativePerSecond,
|
|
28
|
+
nodeLabel,
|
|
29
|
+
} from '../../ProfileIcicleGraph/IcicleGraphArrow/utils';
|
|
24
30
|
|
|
25
31
|
interface Props {
|
|
26
32
|
table: Table<any>;
|
|
@@ -35,6 +41,7 @@ interface GraphTooltipData {
|
|
|
35
41
|
name: string;
|
|
36
42
|
locationAddress: bigint;
|
|
37
43
|
cumulativeText: string;
|
|
44
|
+
cumulativePerSecondText: string;
|
|
38
45
|
diffText: string;
|
|
39
46
|
diff: bigint;
|
|
40
47
|
row: number;
|
|
@@ -58,17 +65,37 @@ export const useGraphTooltip = ({
|
|
|
58
65
|
table.getChild(FIELD_CUMULATIVE)?.get(row) !== null
|
|
59
66
|
? BigInt(table.getChild(FIELD_CUMULATIVE)?.get(row))
|
|
60
67
|
: 0n;
|
|
68
|
+
const cumulativePerSecond: number =
|
|
69
|
+
table.getChild(FIELD_CUMULATIVE_PER_SECOND)?.get(row) !== null
|
|
70
|
+
? table.getChild(FIELD_CUMULATIVE_PER_SECOND)?.get(row)
|
|
71
|
+
: 0;
|
|
61
72
|
const diff: bigint =
|
|
62
73
|
table.getChild(FIELD_DIFF)?.get(row) !== null
|
|
63
74
|
? BigInt(table.getChild(FIELD_DIFF)?.get(row))
|
|
64
75
|
: 0n;
|
|
76
|
+
const diffPerSecond: number =
|
|
77
|
+
table.getChild(FIELD_DIFF_PER_SECOND)?.get(row) !== null
|
|
78
|
+
? table.getChild(FIELD_DIFF_PER_SECOND)?.get(row)
|
|
79
|
+
: 0;
|
|
80
|
+
|
|
81
|
+
const delta = unit === 'nanoseconds';
|
|
65
82
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
83
|
+
let diffText = '';
|
|
84
|
+
if (delta) {
|
|
85
|
+
const prevValue = cumulativePerSecond - diffPerSecond;
|
|
86
|
+
const diffRatio = diffPerSecond !== 0 ? diffPerSecond / prevValue : 0;
|
|
87
|
+
const diffSign = diffPerSecond > 0 ? '+' : '';
|
|
88
|
+
const diffValueText = diffSign + valueFormatter(diffPerSecond, 'CPU Cores', 5);
|
|
89
|
+
const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
|
|
90
|
+
diffText = `${diffValueText} (${diffPercentageText})`;
|
|
91
|
+
} else {
|
|
92
|
+
const prevValue = cumulative - diff;
|
|
93
|
+
const diffRatio = diff !== 0n ? divide(diff, prevValue) : 0;
|
|
94
|
+
const diffSign = diff > 0 ? '+' : '';
|
|
95
|
+
const diffValueText = diffSign + valueFormatter(diff, unit, 1);
|
|
96
|
+
const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
|
|
97
|
+
diffText = `${diffValueText} (${diffPercentageText})`;
|
|
98
|
+
}
|
|
72
99
|
|
|
73
100
|
const name = nodeLabel(table, row, level, false);
|
|
74
101
|
|
|
@@ -76,6 +103,7 @@ export const useGraphTooltip = ({
|
|
|
76
103
|
name,
|
|
77
104
|
locationAddress,
|
|
78
105
|
cumulativeText: getTextForCumulative(cumulative, totalUnfiltered, total, unit),
|
|
106
|
+
cumulativePerSecondText: getTextForCumulativePerSecond(cumulativePerSecond, unit),
|
|
79
107
|
diffText,
|
|
80
108
|
diff,
|
|
81
109
|
row,
|
|
@@ -18,11 +18,10 @@ import type {VirtualElement} from '@popperjs/core';
|
|
|
18
18
|
import {usePopper} from 'react-popper';
|
|
19
19
|
|
|
20
20
|
import {Label} from '@parca/client';
|
|
21
|
-
import {TextWithTooltip} from '@parca/components';
|
|
22
|
-
import {formatDate, valueFormatter} from '@parca/utilities';
|
|
21
|
+
import {TextWithTooltip, useParcaContext} from '@parca/components';
|
|
22
|
+
import {formatDate, timePattern, valueFormatter} from '@parca/utilities';
|
|
23
23
|
|
|
24
24
|
import {HighlightedSeries} from '../';
|
|
25
|
-
import {timeFormat} from '../../';
|
|
26
25
|
|
|
27
26
|
interface Props {
|
|
28
27
|
x: number;
|
|
@@ -69,6 +68,8 @@ const MetricsTooltip = ({
|
|
|
69
68
|
sampleUnit,
|
|
70
69
|
delta,
|
|
71
70
|
}: Props): JSX.Element => {
|
|
71
|
+
const {timezone} = useParcaContext();
|
|
72
|
+
|
|
72
73
|
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
|
|
73
74
|
|
|
74
75
|
const {styles, attributes, ...popperProps} = usePopper(virtualElement, popperElement, {
|
|
@@ -154,7 +155,13 @@ const MetricsTooltip = ({
|
|
|
154
155
|
)}
|
|
155
156
|
<tr>
|
|
156
157
|
<td className="w-1/4">At</td>
|
|
157
|
-
<td className="w-3/4">
|
|
158
|
+
<td className="w-3/4">
|
|
159
|
+
{formatDate(
|
|
160
|
+
highlighted.timestamp,
|
|
161
|
+
timePattern(timezone as string),
|
|
162
|
+
timezone
|
|
163
|
+
)}
|
|
164
|
+
</td>
|
|
158
165
|
</tr>
|
|
159
166
|
</tbody>
|
|
160
167
|
</table>
|
|
@@ -19,7 +19,7 @@ import throttle from 'lodash.throttle';
|
|
|
19
19
|
import {useContextMenu} from 'react-contexify';
|
|
20
20
|
|
|
21
21
|
import {Label, MetricsSample, MetricsSeries as MetricsSeriesPb} from '@parca/client';
|
|
22
|
-
import {DateTimeRange} from '@parca/components';
|
|
22
|
+
import {DateTimeRange, useParcaContext} from '@parca/components';
|
|
23
23
|
import {
|
|
24
24
|
formatDate,
|
|
25
25
|
formatForTimespan,
|
|
@@ -132,6 +132,7 @@ export const RawMetricsGraph = ({
|
|
|
132
132
|
margin = 0,
|
|
133
133
|
sampleUnit,
|
|
134
134
|
}: Props): JSX.Element => {
|
|
135
|
+
const {timezone} = useParcaContext();
|
|
135
136
|
const graph = useRef(null);
|
|
136
137
|
const [dragging, setDragging] = useState(false);
|
|
137
138
|
const [hovering, setHovering] = useState(false);
|
|
@@ -537,7 +538,7 @@ export const RawMetricsGraph = ({
|
|
|
537
538
|
>
|
|
538
539
|
<line y2={6} className="stroke-gray-300 dark:stroke-gray-500" />
|
|
539
540
|
<text fill="currentColor" dy=".71em" y={9}>
|
|
540
|
-
{formatDate(d, formatForTimespan(from, to))}
|
|
541
|
+
{formatDate(d, formatForTimespan(from, to), timezone)}
|
|
541
542
|
</text>
|
|
542
543
|
</g>
|
|
543
544
|
<g key={`grid-${i}`}>
|
|
@@ -24,7 +24,9 @@ import 'react-contexify/dist/ReactContexify.css';
|
|
|
24
24
|
import {
|
|
25
25
|
FIELD_CHILDREN,
|
|
26
26
|
FIELD_CUMULATIVE,
|
|
27
|
+
FIELD_CUMULATIVE_PER_SECOND,
|
|
27
28
|
FIELD_DIFF,
|
|
29
|
+
FIELD_DIFF_PER_SECOND,
|
|
28
30
|
FIELD_FUNCTION_NAME,
|
|
29
31
|
FIELD_MAPPING_FILE,
|
|
30
32
|
} from './index';
|
|
@@ -53,6 +55,7 @@ interface IcicleGraphNodesProps {
|
|
|
53
55
|
sortBy: string;
|
|
54
56
|
darkMode: boolean;
|
|
55
57
|
compareMode: boolean;
|
|
58
|
+
delta: boolean;
|
|
56
59
|
isContextMenuOpen: boolean;
|
|
57
60
|
hoveringName: string | null;
|
|
58
61
|
setHoveringName: (name: string | null) => void;
|
|
@@ -80,6 +83,7 @@ export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({
|
|
|
80
83
|
searchString,
|
|
81
84
|
darkMode,
|
|
82
85
|
compareMode,
|
|
86
|
+
delta,
|
|
83
87
|
isContextMenuOpen,
|
|
84
88
|
hoveringName,
|
|
85
89
|
setHoveringName,
|
|
@@ -127,6 +131,7 @@ export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({
|
|
|
127
131
|
searchString={searchString}
|
|
128
132
|
darkMode={darkMode}
|
|
129
133
|
compareMode={compareMode}
|
|
134
|
+
delta={delta}
|
|
130
135
|
isContextMenuOpen={isContextMenuOpen}
|
|
131
136
|
hoveringName={hoveringName}
|
|
132
137
|
setHoveringName={setHoveringName}
|
|
@@ -165,6 +170,7 @@ interface IcicleNodeProps {
|
|
|
165
170
|
sortBy: string;
|
|
166
171
|
darkMode: boolean;
|
|
167
172
|
compareMode: boolean;
|
|
173
|
+
delta: boolean;
|
|
168
174
|
isContextMenuOpen: boolean;
|
|
169
175
|
hoveringName: string | null;
|
|
170
176
|
setHoveringName: (name: string | null) => void;
|
|
@@ -204,6 +210,7 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
|
|
|
204
210
|
sortBy,
|
|
205
211
|
darkMode,
|
|
206
212
|
compareMode,
|
|
213
|
+
delta,
|
|
207
214
|
isContextMenuOpen,
|
|
208
215
|
hoveringName,
|
|
209
216
|
setHoveringName,
|
|
@@ -215,12 +222,18 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
|
|
|
215
222
|
const mappingColumn = table.getChild(FIELD_MAPPING_FILE);
|
|
216
223
|
const functionNameColumn = table.getChild(FIELD_FUNCTION_NAME);
|
|
217
224
|
const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
|
|
225
|
+
const cumulativePerSecondColumn = table.getChild(FIELD_CUMULATIVE_PER_SECOND);
|
|
218
226
|
const diffColumn = table.getChild(FIELD_DIFF);
|
|
227
|
+
const diffPerSecondColumn = table.getChild(FIELD_DIFF_PER_SECOND);
|
|
219
228
|
// get the actual values from the columns
|
|
220
229
|
const mappingFile: string | null = arrowToString(mappingColumn?.get(row));
|
|
221
230
|
const functionName: string | null = arrowToString(functionNameColumn?.get(row));
|
|
222
231
|
const cumulative = cumulativeColumn?.get(row) !== null ? BigInt(cumulativeColumn?.get(row)) : 0n;
|
|
232
|
+
const cumulativePerSecond: number | null =
|
|
233
|
+
cumulativePerSecondColumn?.get(row) != null ? cumulativePerSecondColumn.get(row) : 0;
|
|
223
234
|
const diff: bigint | null = diffColumn?.get(row) !== null ? BigInt(diffColumn?.get(row)) : null;
|
|
235
|
+
const diffPerSecond: number | null =
|
|
236
|
+
diffPerSecondColumn?.get(row) != null ? diffPerSecondColumn.get(row) : null;
|
|
224
237
|
const childRows: number[] = Array.from(table.getChild(FIELD_CHILDREN)?.get(row) ?? []);
|
|
225
238
|
|
|
226
239
|
const highlightedNodes = useMemo(() => {
|
|
@@ -259,6 +272,15 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
|
|
|
259
272
|
});
|
|
260
273
|
break;
|
|
261
274
|
case FIELD_CUMULATIVE:
|
|
275
|
+
if (delta) {
|
|
276
|
+
childRows.sort((a, b) => {
|
|
277
|
+
const aCumulativePerSecond = cumulativePerSecondColumn?.get(a);
|
|
278
|
+
const bCumulativePerSecond = cumulativePerSecondColumn?.get(b);
|
|
279
|
+
return bCumulativePerSecond - aCumulativePerSecond;
|
|
280
|
+
});
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
|
|
262
284
|
childRows.sort((a, b) => {
|
|
263
285
|
const aCumulative: bigint = cumulativeColumn?.get(a);
|
|
264
286
|
const bCumulative: bigint = cumulativeColumn?.get(b);
|
|
@@ -267,15 +289,59 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
|
|
|
267
289
|
break;
|
|
268
290
|
case FIELD_DIFF:
|
|
269
291
|
childRows.sort((a, b) => {
|
|
292
|
+
if (delta) {
|
|
293
|
+
let aRatio: number | null = null;
|
|
294
|
+
let bRatio: number | null = null;
|
|
295
|
+
|
|
296
|
+
const aDiff: number | null = diffPerSecondColumn?.get(a);
|
|
297
|
+
if (aDiff !== null) {
|
|
298
|
+
const cumulative: number = cumulativePerSecondColumn?.get(a);
|
|
299
|
+
const prev = cumulative - aDiff;
|
|
300
|
+
aRatio = aDiff / prev;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const bDiff: number | null = diffPerSecondColumn?.get(b);
|
|
304
|
+
if (bDiff !== null) {
|
|
305
|
+
const cumulative: number = cumulativePerSecondColumn?.get(b);
|
|
306
|
+
const prev = cumulative - bDiff;
|
|
307
|
+
bRatio = bDiff / prev;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (aRatio !== null && bRatio !== null) {
|
|
311
|
+
return bRatio - aRatio;
|
|
312
|
+
}
|
|
313
|
+
if (aRatio === null && bRatio !== null) {
|
|
314
|
+
return -1;
|
|
315
|
+
}
|
|
316
|
+
if (aRatio !== null && bRatio === null) {
|
|
317
|
+
return 1;
|
|
318
|
+
}
|
|
319
|
+
// both are null
|
|
320
|
+
return 0;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
let aRatio: number | null = null;
|
|
270
324
|
const aDiff: bigint | null = diffColumn?.get(a);
|
|
325
|
+
if (aDiff !== null) {
|
|
326
|
+
const cumulative: bigint = cumulativeColumn?.get(a) ?? 0n;
|
|
327
|
+
const prev: bigint = cumulative - aDiff;
|
|
328
|
+
aRatio = Number(aDiff) / Number(prev);
|
|
329
|
+
}
|
|
330
|
+
let bRatio: number | null = null;
|
|
271
331
|
const bDiff: bigint | null = diffColumn?.get(b);
|
|
272
|
-
if (
|
|
273
|
-
|
|
332
|
+
if (bDiff !== null) {
|
|
333
|
+
const cumulative: bigint = cumulativeColumn?.get(b) ?? 0n;
|
|
334
|
+
const prev: bigint = cumulative - bDiff;
|
|
335
|
+
bRatio = Number(bDiff) / Number(prev);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
if (aRatio !== null && bRatio !== null) {
|
|
339
|
+
return bRatio - aRatio;
|
|
274
340
|
}
|
|
275
|
-
if (
|
|
341
|
+
if (aRatio === null && bRatio !== null) {
|
|
276
342
|
return -1;
|
|
277
343
|
}
|
|
278
|
-
if (
|
|
344
|
+
if (aRatio !== null && bRatio === null) {
|
|
279
345
|
return 1;
|
|
280
346
|
}
|
|
281
347
|
// both are null
|
|
@@ -289,7 +355,9 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
|
|
|
289
355
|
isDarkMode: darkMode,
|
|
290
356
|
compareMode,
|
|
291
357
|
cumulative,
|
|
358
|
+
cumulativePerSecond,
|
|
292
359
|
diff,
|
|
360
|
+
diffPerSecond,
|
|
293
361
|
mappingColors,
|
|
294
362
|
mappingFile,
|
|
295
363
|
functionName,
|
|
@@ -393,6 +461,7 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
|
|
|
393
461
|
searchString={searchString}
|
|
394
462
|
sortBy={sortBy}
|
|
395
463
|
darkMode={darkMode}
|
|
464
|
+
delta={delta}
|
|
396
465
|
compareMode={compareMode}
|
|
397
466
|
isContextMenuOpen={isContextMenuOpen}
|
|
398
467
|
hoveringName={hoveringName}
|
|
@@ -49,7 +49,9 @@ export const FIELD_FUNCTION_START_LINE = 'function_startline';
|
|
|
49
49
|
export const FIELD_CHILDREN = 'children';
|
|
50
50
|
export const FIELD_LABELS = 'labels';
|
|
51
51
|
export const FIELD_CUMULATIVE = 'cumulative';
|
|
52
|
+
export const FIELD_CUMULATIVE_PER_SECOND = 'cumulative_per_second';
|
|
52
53
|
export const FIELD_DIFF = 'diff';
|
|
54
|
+
export const FIELD_DIFF_PER_SECOND = 'diff_per_second';
|
|
53
55
|
|
|
54
56
|
interface IcicleGraphArrowProps {
|
|
55
57
|
arrow: FlamegraphArrow;
|
|
@@ -229,6 +231,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
229
231
|
sortBy={sortBy}
|
|
230
232
|
darkMode={isDarkMode}
|
|
231
233
|
compareMode={compareMode}
|
|
234
|
+
delta={sampleUnit === 'nanoseconds'}
|
|
232
235
|
isContextMenuOpen={isContextMenuOpen}
|
|
233
236
|
hoveringName={hoveringName}
|
|
234
237
|
setHoveringName={setHoveringName}
|
|
@@ -246,6 +249,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
246
249
|
currentSearchString,
|
|
247
250
|
height,
|
|
248
251
|
isDarkMode,
|
|
252
|
+
sampleUnit,
|
|
249
253
|
mappingColors,
|
|
250
254
|
setCurPath,
|
|
251
255
|
sortBy,
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// limitations under the License.
|
|
13
13
|
|
|
14
14
|
import {EVERYTHING_ELSE} from '@parca/store';
|
|
15
|
-
import {diffColor, getLastItem} from '@parca/utilities';
|
|
15
|
+
import {diffColor, diffColorPerSecond, getLastItem} from '@parca/utilities';
|
|
16
16
|
|
|
17
17
|
interface mappingColors {
|
|
18
18
|
[key: string]: string;
|
|
@@ -22,7 +22,9 @@ interface Props {
|
|
|
22
22
|
isDarkMode: boolean;
|
|
23
23
|
compareMode: boolean;
|
|
24
24
|
cumulative: bigint;
|
|
25
|
+
cumulativePerSecond: number | null;
|
|
25
26
|
diff: bigint | null;
|
|
27
|
+
diffPerSecond: number | null;
|
|
26
28
|
mappingColors: mappingColors;
|
|
27
29
|
functionName: string | null;
|
|
28
30
|
mappingFile: string | null;
|
|
@@ -32,12 +34,18 @@ const useNodeColor = ({
|
|
|
32
34
|
isDarkMode,
|
|
33
35
|
compareMode,
|
|
34
36
|
cumulative,
|
|
37
|
+
cumulativePerSecond,
|
|
35
38
|
diff,
|
|
39
|
+
diffPerSecond,
|
|
36
40
|
mappingColors,
|
|
37
41
|
functionName,
|
|
38
42
|
mappingFile,
|
|
39
43
|
}: Props): string => {
|
|
40
44
|
if (compareMode) {
|
|
45
|
+
if (cumulativePerSecond !== null && diffPerSecond !== null) {
|
|
46
|
+
return diffColorPerSecond(diffPerSecond, cumulativePerSecond, isDarkMode);
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
return diffColor(diff ?? 0n, cumulative, isDarkMode);
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -91,6 +91,17 @@ export const getTextForCumulative = (
|
|
|
91
91
|
(${(100 * divide(hoveringNodeCumulative, totalUnfiltered)).toFixed(2)}%${filtered})`;
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
export const getTextForCumulativePerSecond = (
|
|
95
|
+
hoveringNodeCumulative: number,
|
|
96
|
+
unit: string
|
|
97
|
+
): string => {
|
|
98
|
+
return `${valueFormatter(
|
|
99
|
+
hoveringNodeCumulative,
|
|
100
|
+
unit === 'nanoseconds' ? 'CPU Cores' : unit,
|
|
101
|
+
5
|
|
102
|
+
)}`;
|
|
103
|
+
};
|
|
104
|
+
|
|
94
105
|
export const arrowToString = (buffer: any): string | null => {
|
|
95
106
|
if (buffer == null || typeof buffer === 'string') {
|
|
96
107
|
return buffer;
|
package/src/ProfileSource.tsx
CHANGED
|
@@ -27,8 +27,7 @@ export interface ProfileSource {
|
|
|
27
27
|
QueryRequest: () => QueryRequest;
|
|
28
28
|
ProfileType: () => ProfileType;
|
|
29
29
|
DiffSelection: () => ProfileDiffSelection;
|
|
30
|
-
|
|
31
|
-
toString: () => string;
|
|
30
|
+
toString: (timezone?: string) => string;
|
|
32
31
|
}
|
|
33
32
|
|
|
34
33
|
export interface ProfileSelection {
|
|
@@ -37,8 +36,13 @@ export interface ProfileSelection {
|
|
|
37
36
|
ProfileSource: () => ProfileSource;
|
|
38
37
|
Type: () => string;
|
|
39
38
|
}
|
|
39
|
+
const timeFormat = (timezone?: string): string => {
|
|
40
|
+
if (timezone !== undefined) {
|
|
41
|
+
return 'yyyy-MM-dd HH:mm:ss';
|
|
42
|
+
}
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
return "yyyy-MM-dd HH:mm:ss '(UTC)'";
|
|
45
|
+
};
|
|
42
46
|
|
|
43
47
|
export function ParamsString(params: {[key: string]: string}): string {
|
|
44
48
|
return Object.keys(params)
|
|
@@ -229,25 +233,17 @@ export class MergedProfileSource implements ProfileSource {
|
|
|
229
233
|
return ProfileType.fromString(Query.parse(this.query.toString()).profileName());
|
|
230
234
|
}
|
|
231
235
|
|
|
232
|
-
Describe(): JSX.Element {
|
|
233
|
-
return (
|
|
234
|
-
<a>
|
|
235
|
-
Merge of "{this.query.toString()}" from {formatDate(this.mergeFrom, timeFormat)}{' '}
|
|
236
|
-
to {formatDate(this.mergeTo, timeFormat)}
|
|
237
|
-
</a>
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
236
|
stringMatchers(): string[] {
|
|
242
237
|
return this.query.matchers
|
|
243
238
|
.filter((m: Matcher) => m.key !== '__name__')
|
|
244
239
|
.map((m: Matcher) => `${m.key}=${m.value}`);
|
|
245
240
|
}
|
|
246
241
|
|
|
247
|
-
toString(): string {
|
|
242
|
+
toString(timezone?: string): string {
|
|
248
243
|
return `merged profiles of query "${this.query.toString()}" from ${formatDate(
|
|
249
244
|
this.mergeFrom,
|
|
250
|
-
timeFormat
|
|
251
|
-
|
|
245
|
+
timeFormat(timezone),
|
|
246
|
+
timezone
|
|
247
|
+
)} to ${formatDate(this.mergeTo, timeFormat(timezone), timezone)}`;
|
|
252
248
|
}
|
|
253
249
|
}
|
|
@@ -132,6 +132,7 @@ export const ProfileView = ({
|
|
|
132
132
|
pprofDownloading,
|
|
133
133
|
compare,
|
|
134
134
|
}: ProfileViewProps): JSX.Element => {
|
|
135
|
+
const {timezone} = useParcaContext();
|
|
135
136
|
const {ref, dimensions} = useContainerDimensions();
|
|
136
137
|
const [curPath, setCurPath] = useState<string[]>([]);
|
|
137
138
|
const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
|
|
@@ -325,7 +326,7 @@ export const ProfileView = ({
|
|
|
325
326
|
};
|
|
326
327
|
|
|
327
328
|
// TODO: this is just a placeholder, we need to replace with an actually informative and accurate title (cc @metalmatze)
|
|
328
|
-
const profileSourceString = profileSource?.toString();
|
|
329
|
+
const profileSourceString = profileSource?.toString(timezone);
|
|
329
330
|
const hasProfileSource = profileSource !== undefined && profileSourceString !== '';
|
|
330
331
|
const headerParts = profileSourceString?.split('"') ?? [];
|
|
331
332
|
|