@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.
Files changed (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/Callgraph/utils.d.ts +6 -6
  3. package/dist/GraphTooltip/index.d.ts +5 -5
  4. package/dist/GraphTooltipArrow/Content.js +3 -2
  5. package/dist/GraphTooltipArrow/useGraphTooltip/index.d.ts +1 -0
  6. package/dist/GraphTooltipArrow/useGraphTooltip/index.js +27 -8
  7. package/dist/MetricsGraph/MetricsTooltip/index.js +4 -4
  8. package/dist/MetricsGraph/index.js +3 -2
  9. package/dist/ProfileIcicleGraph/ActionButtons/GroupByDropdown.d.ts +0 -1
  10. package/dist/ProfileIcicleGraph/ActionButtons/RuntimeFilterDropdown.d.ts +0 -1
  11. package/dist/ProfileIcicleGraph/ActionButtons/SortBySelect.d.ts +0 -1
  12. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +2 -0
  13. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +62 -9
  14. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +2 -0
  15. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +4 -1
  16. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts +3 -1
  17. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.js +5 -2
  18. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts +1 -0
  19. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +3 -0
  20. package/dist/ProfileSource.d.ts +2 -5
  21. package/dist/ProfileSource.js +9 -7
  22. package/dist/ProfileView/ProfileViewContext.d.ts +1 -1
  23. package/dist/ProfileView/index.js +2 -1
  24. package/dist/Table/ColumnsVisibility.d.ts +0 -1
  25. package/package.json +6 -6
  26. package/src/GraphTooltipArrow/Content.tsx +21 -1
  27. package/src/GraphTooltipArrow/useGraphTooltip/index.ts +35 -7
  28. package/src/MetricsGraph/MetricsTooltip/index.tsx +11 -4
  29. package/src/MetricsGraph/index.tsx +3 -2
  30. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +73 -4
  31. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +4 -0
  32. package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +9 -1
  33. package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +11 -0
  34. package/src/ProfileSource.tsx +11 -15
  35. 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?: ((pos: number) => number) | undefined;
6
- yScale?: ((pos: number) => number) | undefined;
7
- source?: number[] | undefined;
8
- target?: number[] | undefined;
9
- isSelfLoop?: boolean | undefined;
10
- offset?: number | undefined;
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[] | undefined;
35
- mappings?: Mapping[] | undefined;
36
- locations?: Location[] | undefined;
37
- functions?: ParcaFunction[] | undefined;
38
- type?: string | undefined;
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,6 +11,7 @@ interface GraphTooltipData {
11
11
  name: string;
12
12
  locationAddress: bigint;
13
13
  cumulativeText: string;
14
+ cumulativePerSecondText: string;
14
15
  diffText: string;
15
16
  diff: bigint;
16
17
  row: number;
@@ -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 prevValue = cumulative - diff;
28
- const diffRatio = diff !== 0n ? divide(diff, prevValue) : 0;
29
- const diffSign = diff > 0 ? '+' : '';
30
- const diffValueText = diffSign + valueFormatter(diff, unit, 1);
31
- const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
32
- const diffText = `${diffValueText} (${diffPercentageText})`;
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, timeFormat) })] })] }) }) }), _jsx("span", { className: "my-2 block text-gray-500", children: highlighted.labels
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 GroupByDropdown: ({ groupBy, toggleGroupBy, }: {
3
2
  groupBy: string[];
4
3
  toggleGroupBy: (key: string) => void;
@@ -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;
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  declare const SortBySelect: ({ sortBy, setSortBy, compareMode, }: {
3
2
  sortBy: string;
4
3
  setSortBy: (key: string) => 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 (aDiff !== null && bDiff !== null) {
107
- return Number(bDiff - aDiff);
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 (aDiff === null && bDiff !== null) {
160
+ if (aRatio === null && bRatio !== null) {
110
161
  return -1;
111
162
  }
112
- if (aDiff !== null && bDiff === null) {
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;
@@ -5,8 +5,7 @@ export interface ProfileSource {
5
5
  QueryRequest: () => QueryRequest;
6
6
  ProfileType: () => ProfileType;
7
7
  DiffSelection: () => ProfileDiffSelection;
8
- Describe: () => JSX.Element;
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
  }
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
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
- export const timeFormat = "yyyy-MM-dd HH:mm:ss '(UTC)'";
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 | undefined;
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 ||
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { type VisibilityState } from '@tanstack/react-table';
3
2
  import { ColumnDef } from '.';
4
3
  declare const ColumnsVisibility: ({ columns, visibility, setVisibility, }: {
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.359",
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.265",
7
+ "@parca/components": "^0.16.267",
8
8
  "@parca/dynamicsize": "^0.16.61",
9
- "@parca/hooks": "^0.0.46",
9
+ "@parca/hooks": "^0.0.48",
10
10
  "@parca/parser": "^0.16.69",
11
- "@parca/store": "^0.16.135",
12
- "@parca/utilities": "^0.0.63",
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": "066b31fc628e392e1cd252e14082d4d2c2729ae1"
53
+ "gitHead": "d900cb1de5375362e3e211809c98990a6e686140"
54
54
  }
@@ -61,7 +61,17 @@ const GraphTooltipArrowContent = ({
61
61
  return <></>;
62
62
  }
63
63
 
64
- const {name, locationAddress, cumulativeText, diffText, diff, row: rowNumber} = graphTooltipData;
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 {getTextForCumulative, nodeLabel} from '../../ProfileIcicleGraph/IcicleGraphArrow/utils';
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
- const prevValue = cumulative - diff;
67
- const diffRatio = diff !== 0n ? divide(diff, prevValue) : 0;
68
- const diffSign = diff > 0 ? '+' : '';
69
- const diffValueText = diffSign + valueFormatter(diff, unit, 1);
70
- const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
71
- const diffText = `${diffValueText} (${diffPercentageText})`;
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">{formatDate(highlighted.timestamp, timeFormat)}</td>
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 (aDiff !== null && bDiff !== null) {
273
- return Number(bDiff - aDiff);
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 (aDiff === null && bDiff !== null) {
341
+ if (aRatio === null && bRatio !== null) {
276
342
  return -1;
277
343
  }
278
- if (aDiff !== null && bDiff === null) {
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;
@@ -27,8 +27,7 @@ export interface ProfileSource {
27
27
  QueryRequest: () => QueryRequest;
28
28
  ProfileType: () => ProfileType;
29
29
  DiffSelection: () => ProfileDiffSelection;
30
- Describe: () => JSX.Element;
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
- export const timeFormat = "yyyy-MM-dd HH:mm:ss '(UTC)'";
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 &quot;{this.query.toString()}&quot; 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
- )} to ${formatDate(this.mergeTo, timeFormat)}`;
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