@parca/profile 0.16.302 → 0.16.304
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/MetricsGraph/index.js +12 -4
- package/dist/ProfileIcicleGraph/IcicleGraph/index.js +2 -1
- package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +2 -1
- package/dist/ProfileIcicleGraph/index.js +6 -5
- package/dist/ProfileView/ProfileViewContext.d.ts +1 -0
- package/dist/ProfileView/ProfileViewContext.js +1 -0
- package/dist/ProfileView/index.d.ts +1 -1
- package/dist/ProfileView/index.js +6 -4
- package/dist/Table/index.js +2 -2
- package/dist/TopTable/index.js +2 -2
- package/package.json +5 -5
- package/src/MetricsGraph/index.tsx +35 -24
- package/src/ProfileIcicleGraph/IcicleGraph/index.tsx +2 -2
- package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +2 -2
- package/src/ProfileIcicleGraph/index.tsx +6 -13
- package/src/ProfileView/ProfileViewContext.tsx +2 -0
- package/src/ProfileView/index.tsx +8 -3
- package/src/Table/index.tsx +2 -2
- package/src/TopTable/index.tsx +2 -2
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.304](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.303...@parca/profile@0.16.304) (2023-11-09)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @parca/profile
|
|
9
|
+
|
|
10
|
+
## 0.16.303 (2023-11-09)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @parca/profile
|
|
13
|
+
|
|
6
14
|
## [0.16.302](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.301...@parca/profile@0.16.302) (2023-11-07)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @parca/profile
|
|
@@ -17,7 +17,7 @@ import { pointer } from 'd3-selection';
|
|
|
17
17
|
import throttle from 'lodash.throttle';
|
|
18
18
|
import { useContextMenu } from 'react-contexify';
|
|
19
19
|
import { DateTimeRange } from '@parca/components';
|
|
20
|
-
import { formatDate, formatForTimespan, sanitizeHighlightedValues, valueFormatter, } from '@parca/utilities';
|
|
20
|
+
import { formatDate, formatForTimespan, getPrecision, sanitizeHighlightedValues, valueFormatter, } from '@parca/utilities';
|
|
21
21
|
import MetricsCircle from '../MetricsCircle';
|
|
22
22
|
import MetricsSeries from '../MetricsSeries';
|
|
23
23
|
import MetricsContextMenu from './MetricsContextMenu';
|
|
@@ -248,9 +248,17 @@ export const RawMetricsGraph = ({ data, from, to, profile, onSampleClick, addLab
|
|
|
248
248
|
}
|
|
249
249
|
return (_jsxs(_Fragment, { children: [_jsx(MetricsContextMenu, { onAddLabelMatcher: addLabelMatcher, menuId: MENU_ID, highlighted: highlighted, trackVisibility: trackVisibility }), highlighted != null && hovering && !dragging && pos[0] !== 0 && pos[1] !== 0 && (_jsx("div", { onMouseMove: onMouseMove, onMouseEnter: () => setHovering(true), onMouseLeave: () => setHovering(false), children: !isContextMenuOpen && (_jsx(MetricsTooltip, { x: pos[0] + margin, y: pos[1] + margin, highlighted: highlighted, contextElement: graph.current, sampleUnit: sampleUnit, delta: isDeltaType })) })), _jsx("div", { ref: graph, onMouseEnter: function () {
|
|
250
250
|
setHovering(true);
|
|
251
|
-
}, onMouseLeave: () => setHovering(false), onContextMenu: displayMenu, children: _jsxs("svg", { width: `${width}px`, height: `${height + margin}px`, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onMouseMove: onMouseMove, children: [_jsx("g", { transform: `translate(${margin}, 0)`, children: dragging && (_jsx("g", { className: "zoom-time-rect", children: _jsx("rect", { className: "bar", x: pos[0] - relPos < 0 ? pos[0] : relPos, y: 0, height: height, width: Math.abs(pos[0] - relPos), fill: 'rgba(0, 0, 0, 0.125)' }) })) }), _jsxs("g", { transform: `translate(${margin * 1.5}, ${margin / 1.5})`, children: [_jsxs("g", { className: "y axis", textAnchor: "end", fontSize: "10", fill: "none", children: [yScale.ticks(5).map((d, i) =>
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
}, onMouseLeave: () => setHovering(false), onContextMenu: displayMenu, children: _jsxs("svg", { width: `${width}px`, height: `${height + margin}px`, onMouseDown: onMouseDown, onMouseUp: onMouseUp, onMouseMove: onMouseMove, children: [_jsx("g", { transform: `translate(${margin}, 0)`, children: dragging && (_jsx("g", { className: "zoom-time-rect", children: _jsx("rect", { className: "bar", x: pos[0] - relPos < 0 ? pos[0] : relPos, y: 0, height: height, width: Math.abs(pos[0] - relPos), fill: 'rgba(0, 0, 0, 0.125)' }) })) }), _jsxs("g", { transform: `translate(${margin * 1.5}, ${margin / 1.5})`, children: [_jsxs("g", { className: "y axis", textAnchor: "end", fontSize: "10", fill: "none", children: [yScale.ticks(5).map((d, i, allTicks) => {
|
|
252
|
+
let decimals = 2;
|
|
253
|
+
const intervalBetweenTicks = allTicks[1] - allTicks[0];
|
|
254
|
+
if (intervalBetweenTicks < 1) {
|
|
255
|
+
const precision = getPrecision(intervalBetweenTicks);
|
|
256
|
+
decimals = precision;
|
|
257
|
+
}
|
|
258
|
+
return (_jsxs(Fragment, { children: [_jsxs("g", { className: "tick",
|
|
259
|
+
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
|
260
|
+
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, sampleUnit, 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()}`));
|
|
261
|
+
}), _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",
|
|
254
262
|
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
|
255
263
|
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
|
|
256
264
|
? lineStrokeHover
|
|
@@ -15,6 +15,7 @@ import { memo, useEffect, useMemo, useRef, useState } from 'react';
|
|
|
15
15
|
import { setHoveringNode, useAppDispatch } from '@parca/store';
|
|
16
16
|
import { scaleLinear, selectQueryParam } from '@parca/utilities';
|
|
17
17
|
import GraphTooltip from '../../GraphTooltip';
|
|
18
|
+
import { useProfileViewContext } from '../../ProfileView/ProfileViewContext';
|
|
18
19
|
import ColorStackLegend from './ColorStackLegend';
|
|
19
20
|
import { IcicleNode, RowHeight } from './IcicleGraphNodes';
|
|
20
21
|
import useColoredGraph from './useColoredGraph';
|
|
@@ -25,7 +26,7 @@ export const IcicleGraph = memo(function IcicleGraph({ graph, total, filtered, w
|
|
|
25
26
|
const ref = useRef(null);
|
|
26
27
|
const coloredGraph = useColoredGraph(graph);
|
|
27
28
|
const currentSearchString = selectQueryParam('search_string') ?? '';
|
|
28
|
-
const compareMode =
|
|
29
|
+
const { compareMode } = useProfileViewContext();
|
|
29
30
|
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
30
31
|
useEffect(() => {
|
|
31
32
|
if (ref.current != null) {
|
|
@@ -20,6 +20,7 @@ import { getLastItem, scaleLinear, selectQueryParam, } from '@parca/utilities';
|
|
|
20
20
|
import GraphTooltipArrow from '../../GraphTooltipArrow';
|
|
21
21
|
import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
|
|
22
22
|
import { DockedGraphTooltip } from '../../GraphTooltipArrow/DockedGraphTooltip';
|
|
23
|
+
import { useProfileViewContext } from '../../ProfileView/ProfileViewContext';
|
|
23
24
|
import ColorStackLegend from './ColorStackLegend';
|
|
24
25
|
import ContextMenu from './ContextMenu';
|
|
25
26
|
import { IcicleNode, RowHeight } from './IcicleGraphNodes';
|
|
@@ -53,7 +54,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ arrow, total, f
|
|
|
53
54
|
const svg = useRef(null);
|
|
54
55
|
const ref = useRef(null);
|
|
55
56
|
const currentSearchString = selectQueryParam('search_string') ?? '';
|
|
56
|
-
const compareMode =
|
|
57
|
+
const { compareMode } = useProfileViewContext();
|
|
57
58
|
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
58
59
|
const mappings = useMemo(() => {
|
|
59
60
|
// Read the mappings from the dictionary that contains all mapping strings.
|
|
@@ -16,7 +16,8 @@ import { Menu, Transition } from '@headlessui/react';
|
|
|
16
16
|
import { Icon } from '@iconify/react';
|
|
17
17
|
import { Button, Select, useParcaContext, useURLState } from '@parca/components';
|
|
18
18
|
import { USER_PREFERENCES, useUserPreference } from '@parca/hooks';
|
|
19
|
-
import { capitalizeOnlyFirstLetter, divide
|
|
19
|
+
import { capitalizeOnlyFirstLetter, divide } from '@parca/utilities';
|
|
20
|
+
import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
|
|
20
21
|
import DiffLegend from '../components/DiffLegend';
|
|
21
22
|
import IcicleGraph from './IcicleGraph';
|
|
22
23
|
import IcicleGraphArrow, { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_FILE_NAME, FIELD_FUNCTION_NAME, FIELD_LABELS, FIELD_LOCATION_ADDRESS, FIELD_MAPPING_FILE, } from './IcicleGraphArrow';
|
|
@@ -29,7 +30,7 @@ const ShowHideLegendButton = ({ navigateTo }) => {
|
|
|
29
30
|
param: 'color_stack_legend',
|
|
30
31
|
navigateTo,
|
|
31
32
|
});
|
|
32
|
-
const compareMode =
|
|
33
|
+
const { compareMode } = useProfileViewContext();
|
|
33
34
|
const isColorStackLegendEnabled = colorStackLegend === 'true';
|
|
34
35
|
const [colorProfileName] = useUserPreference(USER_PREFERENCES.FLAMEGRAPH_COLOR_PROFILE.key);
|
|
35
36
|
const setColorStackLegend = useCallback((value) => {
|
|
@@ -42,7 +43,7 @@ const GroupAndSortActionButtons = ({ navigateTo }) => {
|
|
|
42
43
|
param: 'sort_by',
|
|
43
44
|
navigateTo,
|
|
44
45
|
});
|
|
45
|
-
const compareMode =
|
|
46
|
+
const { compareMode } = useProfileViewContext();
|
|
46
47
|
const [storeGroupBy = [FIELD_FUNCTION_NAME], setStoreGroupBy] = useURLState({
|
|
47
48
|
param: 'group_by',
|
|
48
49
|
navigateTo,
|
|
@@ -86,7 +87,7 @@ const RuntimeFilterDropdown = ({ showRuntimeRuby, toggleShowRuntimeRuby, showRun
|
|
|
86
87
|
};
|
|
87
88
|
const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, total, filtered, curPath, setNewCurPath, sampleUnit, navigateTo, loading, setActionButtons, error, width, }) {
|
|
88
89
|
const { loader, onError, authenticationErrorMessage } = useParcaContext();
|
|
89
|
-
const compareMode =
|
|
90
|
+
const { compareMode } = useProfileViewContext();
|
|
90
91
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
91
92
|
param: 'sort_by',
|
|
92
93
|
navigateTo,
|
|
@@ -132,7 +133,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, arrow, to
|
|
|
132
133
|
if (isTrimmed) {
|
|
133
134
|
console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
|
|
134
135
|
}
|
|
135
|
-
return (_jsxs("div", { className: "relative", children: [compareMode
|
|
136
|
+
return (_jsxs("div", { className: "relative", children: [compareMode ? _jsx(DiffLegend, {}) : null, _jsxs("div", { className: "min-h-48", children: [graph !== undefined && (_jsx(IcicleGraph, { width: width, graph: graph, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo })), arrow !== undefined && (_jsx(IcicleGraphArrow, { width: width, arrow: arrow, total: total, filtered: filtered, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit, navigateTo: navigateTo, sortBy: storeSortBy }))] }), _jsxs("p", { className: "my-2 text-xs", children: ["Showing ", totalFormatted, ' ', isFiltered ? (_jsxs("span", { children: ["(", filteredPercentage, "%) filtered of ", totalUnfilteredFormatted, ' '] })) : (_jsx(_Fragment, {})), "values.", ' '] })] }));
|
|
136
137
|
};
|
|
137
138
|
const groupByOptions = [
|
|
138
139
|
{
|
|
@@ -15,6 +15,7 @@ import { createContext, useContext } from 'react';
|
|
|
15
15
|
export const defaultValue = {
|
|
16
16
|
profileSource: undefined,
|
|
17
17
|
sampleUnit: 'bytes',
|
|
18
|
+
compareMode: false,
|
|
18
19
|
};
|
|
19
20
|
const ProfileViewContext = createContext(defaultValue);
|
|
20
21
|
export const ProfileViewContextProvider = ({ children, value, }) => {
|
|
@@ -47,5 +47,5 @@ export interface ProfileViewProps {
|
|
|
47
47
|
onDownloadPProf: () => void;
|
|
48
48
|
pprofDownloading?: boolean;
|
|
49
49
|
}
|
|
50
|
-
export declare const ProfileView: ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, }: ProfileViewProps) => JSX.Element;
|
|
50
|
+
export declare const ProfileView: ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, compare, }: ProfileViewProps) => JSX.Element;
|
|
51
51
|
export {};
|
|
@@ -21,7 +21,7 @@ import { DragDropContext, Draggable, Droppable, } from 'react-beautiful-dnd';
|
|
|
21
21
|
import { Button, ConditionalWrapper, KeyDownProvider, UserPreferences, useParcaContext, useURLState, } from '@parca/components';
|
|
22
22
|
import { useContainerDimensions } from '@parca/hooks';
|
|
23
23
|
import { selectDarkMode, useAppSelector } from '@parca/store';
|
|
24
|
-
import { getNewSpanColor } from '@parca/utilities';
|
|
24
|
+
import { getNewSpanColor, selectQueryParam } from '@parca/utilities';
|
|
25
25
|
import { Callgraph } from '../';
|
|
26
26
|
import { jsonToDot } from '../Callgraph/utils';
|
|
27
27
|
import ProfileIcicleGraph from '../ProfileIcicleGraph';
|
|
@@ -39,7 +39,7 @@ function arrayEquals(a, b) {
|
|
|
39
39
|
a.length === b.length &&
|
|
40
40
|
a.every((val, index) => val === b[index]));
|
|
41
41
|
}
|
|
42
|
-
export const ProfileView = ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, }) => {
|
|
42
|
+
export const ProfileView = ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, compare, }) => {
|
|
43
43
|
const { ref, dimensions } = useContainerDimensions();
|
|
44
44
|
const [curPath, setCurPath] = useState([]);
|
|
45
45
|
const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
|
|
@@ -171,9 +171,11 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
|
|
|
171
171
|
const profileSourceString = profileSource?.toString();
|
|
172
172
|
const hasProfileSource = profileSource !== undefined && profileSourceString !== '';
|
|
173
173
|
const headerParts = profileSourceString?.split('"') ?? [];
|
|
174
|
-
|
|
174
|
+
const compareMode = compare === true ||
|
|
175
|
+
(selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true');
|
|
176
|
+
return (_jsx(KeyDownProvider, { children: _jsxs(ProfileViewContextProvider, { value: { profileSource, sampleUnit, compareMode }, children: [_jsxs("div", { className: cx('mb-4 flex w-full items-center', hasProfileSource ? 'justify-between' : 'justify-end'), children: [hasProfileSource && (_jsxs("div", { className: "max-w-[300px]", children: [_jsx("div", { className: "text-sm font-medium capitalize", children: headerParts.length > 0 ? headerParts[0].replace(/"/g, '') : '' }), _jsx("div", { className: "text-xs", children: headerParts.length > 1
|
|
175
177
|
? headerParts[headerParts.length - 1].replace(/"/g, '')
|
|
176
|
-
: '' })] })), _jsxs("div", { className: "flex items-center md:justify-end
|
|
178
|
+
: '' })] })), _jsxs("div", { className: "flex flex-wrap items-center gap-2 md:justify-end", children: [_jsx(FilterByFunctionButton, { navigateTo: navigateTo }), _jsx(UserPreferences, { customButton: _jsxs(Button, { className: "gap-2", variant: "neutral", children: ["Preferences", _jsx(Icon, { icon: "pajamas:preferences", width: 20 })] }) }), profileSource !== undefined && queryClient !== undefined ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsxs(Button, { className: "gap-2", variant: "neutral", onClick: e => {
|
|
177
179
|
e.preventDefault();
|
|
178
180
|
onDownloadPProf();
|
|
179
181
|
}, disabled: pprofDownloading, children: [pprofDownloading != null && pprofDownloading ? 'Downloading...' : 'Download pprof', _jsx(Icon, { icon: "material-symbols:download", width: 20 })] }), _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel", icon: _jsx(Icon, { icon: "material-symbols:add", width: 20 }), addView: true, disabled: isMultiPanelView || dashboardItems.length < 1 })] })] }), _jsx("div", { className: "w-full", ref: ref, children: isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsx(DragDropContext, { onDragEnd: onDragEnd, children: _jsx(Droppable, { droppableId: "droppable", direction: "horizontal", children: provided => (_jsx("div", { ref: provided.innerRef, className: cx('grid w-full gap-2', isMultiPanelView ? 'grid-cols-2' : 'grid-cols-1'), ...provided.droppableProps, children: dashboardItems.map((dashboardItem, index) => {
|
package/dist/Table/index.js
CHANGED
|
@@ -17,6 +17,7 @@ import { Icon } from '@iconify/react';
|
|
|
17
17
|
import { tableFromIPC } from 'apache-arrow';
|
|
18
18
|
import { Button, Table as TableComponent, useURLState } from '@parca/components';
|
|
19
19
|
import { getLastItem, isSearchMatch, parseParams, valueFormatter, } from '@parca/utilities';
|
|
20
|
+
import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
|
|
20
21
|
import { hexifyAddress } from '../utils';
|
|
21
22
|
const FIELD_MAPPING_FILE = 'mapping_file';
|
|
22
23
|
const FIELD_LOCATION_ADDRESS = 'location_address';
|
|
@@ -30,9 +31,8 @@ const FIELD_CUMULATIVE_DIFF = 'cumulative_diff';
|
|
|
30
31
|
export const Table = React.memo(function Table({ data, total, filtered, sampleUnit: unit, navigateTo, loading, currentSearchString, setActionButtons, }) {
|
|
31
32
|
const router = parseParams(window?.location.search);
|
|
32
33
|
const [rawDashboardItems] = useURLState({ param: 'dashboard_items' });
|
|
33
|
-
const [rawcompareMode] = useURLState({ param: 'compare_a' });
|
|
34
34
|
const [filterByFunctionInput] = useURLState({ param: 'filter_by_function' });
|
|
35
|
-
const compareMode =
|
|
35
|
+
const { compareMode } = useProfileViewContext();
|
|
36
36
|
const dashboardItems = useMemo(() => {
|
|
37
37
|
if (rawDashboardItems !== undefined) {
|
|
38
38
|
return rawDashboardItems;
|
package/dist/TopTable/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import React, { useCallback, useEffect, useMemo } from 'react';
|
|
|
15
15
|
import { createColumnHelper } from '@tanstack/react-table';
|
|
16
16
|
import { Button, Table, useURLState } from '@parca/components';
|
|
17
17
|
import { getLastItem, isSearchMatch, parseParams, valueFormatter, } from '@parca/utilities';
|
|
18
|
+
import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
|
|
18
19
|
import { hexifyAddress } from '../utils';
|
|
19
20
|
export const RowLabel = (meta) => {
|
|
20
21
|
if (meta === undefined)
|
|
@@ -38,8 +39,7 @@ const addPlusSign = (num) => {
|
|
|
38
39
|
export const TopTable = React.memo(function TopTable({ data: top, sampleUnit: unit, navigateTo, loading, currentSearchString, setActionButtons, }) {
|
|
39
40
|
const router = parseParams(window?.location.search);
|
|
40
41
|
const [rawDashboardItems] = useURLState({ param: 'dashboard_items' });
|
|
41
|
-
const
|
|
42
|
-
const compareMode = rawcompareMode === undefined ? false : rawcompareMode === 'true';
|
|
42
|
+
const { compareMode } = useProfileViewContext();
|
|
43
43
|
const dashboardItems = useMemo(() => {
|
|
44
44
|
if (rawDashboardItems !== undefined) {
|
|
45
45
|
return rawDashboardItems;
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@parca/profile",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.304",
|
|
4
4
|
"description": "Profile viewing libraries",
|
|
5
5
|
"dependencies": {
|
|
6
6
|
"@parca/client": "^0.16.98",
|
|
7
|
-
"@parca/components": "^0.16.
|
|
7
|
+
"@parca/components": "^0.16.226",
|
|
8
8
|
"@parca/dynamicsize": "^0.16.60",
|
|
9
9
|
"@parca/hooks": "^0.0.35",
|
|
10
10
|
"@parca/parser": "^0.16.66",
|
|
11
|
-
"@parca/store": "^0.16.
|
|
12
|
-
"@parca/utilities": "^0.0.
|
|
11
|
+
"@parca/store": "^0.16.116",
|
|
12
|
+
"@parca/utilities": "^0.0.44",
|
|
13
13
|
"@tanstack/react-query": "^4.0.5",
|
|
14
14
|
"@types/react-beautiful-dnd": "^13.1.3",
|
|
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": "a342b32b3ed89bee6659d1f4fd073c6bcd52cb16"
|
|
54
54
|
}
|
|
@@ -23,6 +23,7 @@ import {DateTimeRange} from '@parca/components';
|
|
|
23
23
|
import {
|
|
24
24
|
formatDate,
|
|
25
25
|
formatForTimespan,
|
|
26
|
+
getPrecision,
|
|
26
27
|
sanitizeHighlightedValues,
|
|
27
28
|
valueFormatter,
|
|
28
29
|
} from '@parca/utilities';
|
|
@@ -450,30 +451,40 @@ export const RawMetricsGraph = ({
|
|
|
450
451
|
</g>
|
|
451
452
|
<g transform={`translate(${margin * 1.5}, ${margin / 1.5})`}>
|
|
452
453
|
<g className="y axis" textAnchor="end" fontSize="10" fill="none">
|
|
453
|
-
{yScale.ticks(5).map((d, i) =>
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
x2={
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
454
|
+
{yScale.ticks(5).map((d, i, allTicks) => {
|
|
455
|
+
let decimals = 2;
|
|
456
|
+
const intervalBetweenTicks = allTicks[1] - allTicks[0];
|
|
457
|
+
|
|
458
|
+
if (intervalBetweenTicks < 1) {
|
|
459
|
+
const precision = getPrecision(intervalBetweenTicks);
|
|
460
|
+
decimals = precision;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return (
|
|
464
|
+
<Fragment key={`${i.toString()}-${d.toString()}`}>
|
|
465
|
+
<g
|
|
466
|
+
key={`tick-${i}`}
|
|
467
|
+
className="tick"
|
|
468
|
+
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
|
469
|
+
transform={`translate(0, ${yScale(d)})`}
|
|
470
|
+
>
|
|
471
|
+
<line className="stroke-gray-300 dark:stroke-gray-500" x2={-6} />
|
|
472
|
+
<text fill="currentColor" x={-9} dy={'0.32em'}>
|
|
473
|
+
{valueFormatter(d, sampleUnit, decimals)}
|
|
474
|
+
</text>
|
|
475
|
+
</g>
|
|
476
|
+
<g key={`grid-${i}`}>
|
|
477
|
+
<line
|
|
478
|
+
className="stroke-gray-300 dark:stroke-gray-500"
|
|
479
|
+
x1={xScale(from)}
|
|
480
|
+
x2={xScale(to)}
|
|
481
|
+
y1={yScale(d)}
|
|
482
|
+
y2={yScale(d)}
|
|
483
|
+
/>
|
|
484
|
+
</g>
|
|
485
|
+
</Fragment>
|
|
486
|
+
);
|
|
487
|
+
})}
|
|
477
488
|
<line
|
|
478
489
|
className="stroke-gray-300 dark:stroke-gray-500"
|
|
479
490
|
x1={0}
|
|
@@ -18,6 +18,7 @@ import {setHoveringNode, useAppDispatch} from '@parca/store';
|
|
|
18
18
|
import {scaleLinear, selectQueryParam, type NavigateFunction} from '@parca/utilities';
|
|
19
19
|
|
|
20
20
|
import GraphTooltip from '../../GraphTooltip';
|
|
21
|
+
import {useProfileViewContext} from '../../ProfileView/ProfileViewContext';
|
|
21
22
|
import ColorStackLegend from './ColorStackLegend';
|
|
22
23
|
import {IcicleNode, RowHeight} from './IcicleGraphNodes';
|
|
23
24
|
import useColoredGraph from './useColoredGraph';
|
|
@@ -50,8 +51,7 @@ export const IcicleGraph = memo(function IcicleGraph({
|
|
|
50
51
|
|
|
51
52
|
const coloredGraph = useColoredGraph(graph);
|
|
52
53
|
const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
|
|
53
|
-
const compareMode
|
|
54
|
-
selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
54
|
+
const {compareMode} = useProfileViewContext();
|
|
55
55
|
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
56
56
|
|
|
57
57
|
useEffect(() => {
|
|
@@ -36,6 +36,7 @@ import {
|
|
|
36
36
|
import GraphTooltipArrow from '../../GraphTooltipArrow';
|
|
37
37
|
import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
|
|
38
38
|
import {DockedGraphTooltip} from '../../GraphTooltipArrow/DockedGraphTooltip';
|
|
39
|
+
import {useProfileViewContext} from '../../ProfileView/ProfileViewContext';
|
|
39
40
|
import ColorStackLegend from './ColorStackLegend';
|
|
40
41
|
import ContextMenu from './ContextMenu';
|
|
41
42
|
import {IcicleNode, RowHeight, mappingColors} from './IcicleGraphNodes';
|
|
@@ -98,8 +99,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
|
|
|
98
99
|
const ref = useRef<SVGGElement>(null);
|
|
99
100
|
|
|
100
101
|
const currentSearchString = (selectQueryParam('search_string') as string) ?? '';
|
|
101
|
-
const compareMode
|
|
102
|
-
selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
102
|
+
const {compareMode} = useProfileViewContext();
|
|
103
103
|
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';
|
|
104
104
|
|
|
105
105
|
const mappings = useMemo(() => {
|
|
@@ -19,13 +19,9 @@ import {Icon} from '@iconify/react';
|
|
|
19
19
|
import {Flamegraph, FlamegraphArrow} from '@parca/client';
|
|
20
20
|
import {Button, Select, useParcaContext, useURLState} from '@parca/components';
|
|
21
21
|
import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
|
|
22
|
-
import {
|
|
23
|
-
capitalizeOnlyFirstLetter,
|
|
24
|
-
divide,
|
|
25
|
-
selectQueryParam,
|
|
26
|
-
type NavigateFunction,
|
|
27
|
-
} from '@parca/utilities';
|
|
22
|
+
import {capitalizeOnlyFirstLetter, divide, type NavigateFunction} from '@parca/utilities';
|
|
28
23
|
|
|
24
|
+
import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
|
|
29
25
|
import DiffLegend from '../components/DiffLegend';
|
|
30
26
|
import IcicleGraph from './IcicleGraph';
|
|
31
27
|
import IcicleGraphArrow, {
|
|
@@ -67,8 +63,7 @@ const ShowHideLegendButton = ({navigateTo}: {navigateTo?: NavigateFunction}): JS
|
|
|
67
63
|
navigateTo,
|
|
68
64
|
});
|
|
69
65
|
|
|
70
|
-
const compareMode
|
|
71
|
-
selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
66
|
+
const {compareMode} = useProfileViewContext();
|
|
72
67
|
|
|
73
68
|
const isColorStackLegendEnabled = colorStackLegend === 'true';
|
|
74
69
|
|
|
@@ -104,8 +99,7 @@ const GroupAndSortActionButtons = ({navigateTo}: {navigateTo?: NavigateFunction}
|
|
|
104
99
|
param: 'sort_by',
|
|
105
100
|
navigateTo,
|
|
106
101
|
});
|
|
107
|
-
const compareMode
|
|
108
|
-
selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
102
|
+
const {compareMode} = useProfileViewContext();
|
|
109
103
|
|
|
110
104
|
const [storeGroupBy = [FIELD_FUNCTION_NAME], setStoreGroupBy] = useURLState({
|
|
111
105
|
param: 'group_by',
|
|
@@ -298,8 +292,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
298
292
|
width,
|
|
299
293
|
}: ProfileIcicleGraphProps): JSX.Element {
|
|
300
294
|
const {loader, onError, authenticationErrorMessage} = useParcaContext();
|
|
301
|
-
const compareMode
|
|
302
|
-
selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
|
|
295
|
+
const {compareMode} = useProfileViewContext();
|
|
303
296
|
|
|
304
297
|
const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
|
|
305
298
|
param: 'sort_by',
|
|
@@ -383,7 +376,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
|
|
|
383
376
|
|
|
384
377
|
return (
|
|
385
378
|
<div className="relative">
|
|
386
|
-
{compareMode
|
|
379
|
+
{compareMode ? <DiffLegend /> : null}
|
|
387
380
|
<div className="min-h-48">
|
|
388
381
|
{graph !== undefined && (
|
|
389
382
|
<IcicleGraph
|
|
@@ -18,11 +18,13 @@ import {ProfileSource} from '../ProfileSource';
|
|
|
18
18
|
interface Props {
|
|
19
19
|
profileSource?: ProfileSource;
|
|
20
20
|
sampleUnit: string;
|
|
21
|
+
compareMode: boolean;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
export const defaultValue: Props = {
|
|
24
25
|
profileSource: undefined,
|
|
25
26
|
sampleUnit: 'bytes',
|
|
27
|
+
compareMode: false,
|
|
26
28
|
};
|
|
27
29
|
|
|
28
30
|
const ProfileViewContext = createContext<Props>(defaultValue);
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
} from '@parca/components';
|
|
45
45
|
import {useContainerDimensions} from '@parca/hooks';
|
|
46
46
|
import {selectDarkMode, useAppSelector} from '@parca/store';
|
|
47
|
-
import {getNewSpanColor} from '@parca/utilities';
|
|
47
|
+
import {getNewSpanColor, selectQueryParam} from '@parca/utilities';
|
|
48
48
|
|
|
49
49
|
import {Callgraph} from '../';
|
|
50
50
|
import {jsonToDot} from '../Callgraph/utils';
|
|
@@ -131,6 +131,7 @@ export const ProfileView = ({
|
|
|
131
131
|
navigateTo,
|
|
132
132
|
onDownloadPProf,
|
|
133
133
|
pprofDownloading,
|
|
134
|
+
compare,
|
|
134
135
|
}: ProfileViewProps): JSX.Element => {
|
|
135
136
|
const {ref, dimensions} = useContainerDimensions();
|
|
136
137
|
const [curPath, setCurPath] = useState<string[]>([]);
|
|
@@ -352,9 +353,13 @@ export const ProfileView = ({
|
|
|
352
353
|
const hasProfileSource = profileSource !== undefined && profileSourceString !== '';
|
|
353
354
|
const headerParts = profileSourceString?.split('"') ?? [];
|
|
354
355
|
|
|
356
|
+
const compareMode =
|
|
357
|
+
compare === true ||
|
|
358
|
+
(selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true');
|
|
359
|
+
|
|
355
360
|
return (
|
|
356
361
|
<KeyDownProvider>
|
|
357
|
-
<ProfileViewContextProvider value={{profileSource, sampleUnit}}>
|
|
362
|
+
<ProfileViewContextProvider value={{profileSource, sampleUnit, compareMode}}>
|
|
358
363
|
<div
|
|
359
364
|
className={cx(
|
|
360
365
|
'mb-4 flex w-full items-center',
|
|
@@ -374,7 +379,7 @@ export const ProfileView = ({
|
|
|
374
379
|
</div>
|
|
375
380
|
)}
|
|
376
381
|
|
|
377
|
-
<div className="flex items-center md:justify-end
|
|
382
|
+
<div className="flex flex-wrap items-center gap-2 md:justify-end">
|
|
378
383
|
<FilterByFunctionButton navigateTo={navigateTo} />
|
|
379
384
|
<UserPreferences
|
|
380
385
|
customButton={
|
package/src/Table/index.tsx
CHANGED
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
type NavigateFunction,
|
|
28
28
|
} from '@parca/utilities';
|
|
29
29
|
|
|
30
|
+
import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
|
|
30
31
|
import {hexifyAddress} from '../utils';
|
|
31
32
|
|
|
32
33
|
const FIELD_MAPPING_FILE = 'mapping_file';
|
|
@@ -84,10 +85,9 @@ export const Table = React.memo(function Table({
|
|
|
84
85
|
}: TableProps): React.JSX.Element {
|
|
85
86
|
const router = parseParams(window?.location.search);
|
|
86
87
|
const [rawDashboardItems] = useURLState({param: 'dashboard_items'});
|
|
87
|
-
const [rawcompareMode] = useURLState({param: 'compare_a'});
|
|
88
88
|
const [filterByFunctionInput] = useURLState({param: 'filter_by_function'});
|
|
89
89
|
|
|
90
|
-
const compareMode
|
|
90
|
+
const {compareMode} = useProfileViewContext();
|
|
91
91
|
|
|
92
92
|
const dashboardItems = useMemo(() => {
|
|
93
93
|
if (rawDashboardItems !== undefined) {
|
package/src/TopTable/index.tsx
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
type NavigateFunction,
|
|
26
26
|
} from '@parca/utilities';
|
|
27
27
|
|
|
28
|
+
import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
|
|
28
29
|
import {hexifyAddress} from '../utils';
|
|
29
30
|
|
|
30
31
|
interface TopTableProps {
|
|
@@ -72,9 +73,8 @@ export const TopTable = React.memo(function TopTable({
|
|
|
72
73
|
}: TopTableProps): JSX.Element {
|
|
73
74
|
const router = parseParams(window?.location.search);
|
|
74
75
|
const [rawDashboardItems] = useURLState({param: 'dashboard_items'});
|
|
75
|
-
const [rawcompareMode] = useURLState({param: 'compare_a'});
|
|
76
76
|
|
|
77
|
-
const compareMode
|
|
77
|
+
const {compareMode} = useProfileViewContext();
|
|
78
78
|
|
|
79
79
|
const dashboardItems = useMemo(() => {
|
|
80
80
|
if (rawDashboardItems !== undefined) {
|