@parca/profile 0.16.89 → 0.16.90

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 CHANGED
@@ -3,6 +3,10 @@
3
3
  All notable changes to this project will be documented in this file.
4
4
  See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
5
5
 
6
+ ## 0.16.90 (2022-12-22)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
6
10
  ## 0.16.89 (2022-12-20)
7
11
 
8
12
  # 0.15.0 (2022-12-20)
@@ -152,7 +152,7 @@ export default function IcicleGraph(_a) {
152
152
  if (ref.current != null) {
153
153
  setHeight(ref === null || ref === void 0 ? void 0 : ref.current.getBoundingClientRect().height);
154
154
  }
155
- }, [width]);
155
+ }, [width, graph]);
156
156
  var total = useMemo(function () { return parseFloat(graph.total); }, [graph.total]);
157
157
  var xScale = useMemo(function () {
158
158
  if (width === undefined) {
@@ -1,11 +1,13 @@
1
1
  /// <reference types="react" />
2
2
  import { Flamegraph } from '@parca/client';
3
+ export type ResizeHandler = (width: number, height: number) => void;
3
4
  interface ProfileIcicleGraphProps {
4
5
  width?: number;
5
6
  graph: Flamegraph | undefined;
6
7
  sampleUnit: string;
7
8
  curPath: string[] | [];
8
9
  setNewCurPath: (path: string[]) => void;
10
+ onContainerResize?: ResizeHandler;
9
11
  }
10
- declare const ProfileIcicleGraph: ({ graph, curPath, setNewCurPath, sampleUnit, }: ProfileIcicleGraphProps) => JSX.Element;
12
+ declare const ProfileIcicleGraph: ({ graph, curPath, setNewCurPath, sampleUnit, onContainerResize, }: ProfileIcicleGraphProps) => JSX.Element;
11
13
  export default ProfileIcicleGraph;
@@ -14,15 +14,39 @@ import { useAppSelector, selectCompareMode } from '@parca/store';
14
14
  import { useContainerDimensions } from '@parca/dynamicsize';
15
15
  import DiffLegend from '../components/DiffLegend';
16
16
  import IcicleGraph from '../IcicleGraph';
17
+ import { useEffect, useMemo } from 'react';
18
+ var numberFormatter = new Intl.NumberFormat('en-US');
17
19
  var ProfileIcicleGraph = function (_a) {
18
- var graph = _a.graph, curPath = _a.curPath, setNewCurPath = _a.setNewCurPath, sampleUnit = _a.sampleUnit;
20
+ var graph = _a.graph, curPath = _a.curPath, setNewCurPath = _a.setNewCurPath, sampleUnit = _a.sampleUnit, onContainerResize = _a.onContainerResize;
19
21
  var compareMode = useAppSelector(selectCompareMode);
20
22
  var _b = useContainerDimensions(), ref = _b.ref, dimensions = _b.dimensions;
23
+ useEffect(function () {
24
+ if (dimensions === undefined)
25
+ return;
26
+ if (onContainerResize === undefined)
27
+ return;
28
+ onContainerResize(dimensions.width, dimensions.height);
29
+ }, [dimensions, onContainerResize]);
30
+ var _c = useMemo(function () {
31
+ if (graph === undefined || graph.untrimmedTotal === '0') {
32
+ return [BigInt(0), '0'];
33
+ }
34
+ var untrimmedTotal = BigInt(graph.untrimmedTotal);
35
+ var total = BigInt(graph.total);
36
+ var trimDifference = untrimmedTotal - total;
37
+ var trimmedPercentage = (total * BigInt(100)) / untrimmedTotal;
38
+ return [
39
+ trimDifference,
40
+ trimmedPercentage.toString(),
41
+ numberFormatter.format(total),
42
+ numberFormatter.format(untrimmedTotal),
43
+ ];
44
+ }, [graph]), trimDifference = _c[0], trimmedPercentage = _c[1], formattedTotal = _c[2], formattedUntrimmedTotal = _c[3];
21
45
  if (graph === undefined)
22
46
  return _jsx("div", { children: "no data..." });
23
47
  var total = graph.total;
24
48
  if (parseFloat(total) === 0)
25
49
  return _jsx(_Fragment, { children: "Profile has no samples" });
26
- return (_jsxs(_Fragment, { children: [compareMode && _jsx(DiffLegend, {}), _jsx("div", __assign({ ref: ref }, { children: _jsx(IcicleGraph, { width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, graph: graph, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit }) }))] }));
50
+ return (_jsxs(_Fragment, { children: [compareMode && _jsx(DiffLegend, {}), trimDifference > BigInt(0) ? (_jsxs("p", __assign({ className: "my-2 text-sm" }, { children: ["Showing ", formattedTotal, "(", trimmedPercentage, "%) out of ", formattedUntrimmedTotal, " samples"] }))) : null, _jsx("div", __assign({ ref: ref }, { children: _jsx(IcicleGraph, { width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, graph: graph, curPath: curPath, setCurPath: setNewCurPath, sampleUnit: sampleUnit }) }))] }));
27
51
  };
28
52
  export default ProfileIcicleGraph;
@@ -1,5 +1,6 @@
1
1
  /// <reference types="react" />
2
2
  import { QueryServiceClient, Flamegraph, Top, Callgraph as CallgraphType } from '@parca/client';
3
+ import { ResizeHandler } from '../ProfileIcicleGraph';
3
4
  import { ProfileSource } from '../ProfileSource';
4
5
  import '../ProfileView.styles.css';
5
6
  type NavigateFunction = (path: string, queryParams: any) => void;
@@ -34,7 +35,8 @@ export interface ProfileViewProps {
34
35
  navigateTo?: NavigateFunction;
35
36
  compare?: boolean;
36
37
  onDownloadPProf: () => void;
38
+ onFlamegraphContainerResize?: ResizeHandler;
37
39
  }
38
40
  export declare const useProfileVisState: () => ProfileVisState;
39
- export declare const ProfileView: ({ flamegraphData, topTableData, callgraphData, sampleUnit, profileSource, queryClient, navigateTo, profileVisState, onDownloadPProf, }: ProfileViewProps) => JSX.Element;
41
+ export declare const ProfileView: ({ flamegraphData, topTableData, callgraphData, sampleUnit, profileSource, queryClient, navigateTo, profileVisState, onDownloadPProf, onFlamegraphContainerResize, }: ProfileViewProps) => JSX.Element;
40
42
  export {};
@@ -36,6 +36,7 @@ import ProfileIcicleGraph from '../ProfileIcicleGraph';
36
36
  import TopTable from '../TopTable';
37
37
  import useDelayedLoader from '../useDelayedLoader';
38
38
  import '../ProfileView.styles.css';
39
+ import useUserPreference, { USER_PREFERENCES } from '@parca/functions/useUserPreference';
39
40
  function arrayEquals(a, b) {
40
41
  return (Array.isArray(a) &&
41
42
  Array.isArray(b) &&
@@ -57,7 +58,7 @@ export var useProfileVisState = function () {
57
58
  return { currentView: currentView, setCurrentView: setCurrentView };
58
59
  };
59
60
  export var ProfileView = function (_a) {
60
- var flamegraphData = _a.flamegraphData, topTableData = _a.topTableData, callgraphData = _a.callgraphData, sampleUnit = _a.sampleUnit, profileSource = _a.profileSource, queryClient = _a.queryClient, navigateTo = _a.navigateTo, profileVisState = _a.profileVisState, onDownloadPProf = _a.onDownloadPProf;
61
+ var flamegraphData = _a.flamegraphData, topTableData = _a.topTableData, callgraphData = _a.callgraphData, sampleUnit = _a.sampleUnit, profileSource = _a.profileSource, queryClient = _a.queryClient, navigateTo = _a.navigateTo, profileVisState = _a.profileVisState, onDownloadPProf = _a.onDownloadPProf, onFlamegraphContainerResize = _a.onFlamegraphContainerResize;
61
62
  var dispatch = useAppDispatch();
62
63
  var _b = useContainerDimensions(), ref = _b.ref, dimensions = _b.dimensions;
63
64
  var _c = useState([]), curPath = _c[0], setCurPath = _c[1];
@@ -66,7 +67,7 @@ export var ProfileView = function (_a) {
66
67
  var currentSearchString = useAppSelector(selectSearchNodeString);
67
68
  var filterByFunctionString = useAppSelector(selectFilterByFunction);
68
69
  var callgraphEnabled = useUIFeatureFlag('callgraph')[0];
69
- var highlightAfterFilteringEnabled = useUIFeatureFlag('highlightAfterFiltering')[0];
70
+ var highlightAfterFilteringEnabled = useUserPreference(USER_PREFERENCES.HIGHTLIGHT_AFTER_FILTERING.key)[0];
70
71
  var _d = useParcaContext(), loader = _d.loader, perf = _d.perf;
71
72
  useEffect(function () {
72
73
  // Reset the current path when the profile source changes
@@ -137,5 +138,5 @@ export var ProfileView = function (_a) {
137
138
  return (_jsx(_Fragment, { children: _jsx("div", __assign({ className: "py-3" }, { children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", __assign({ className: "flex py-3 w-full" }, { children: [_jsxs("div", __assign({ className: "w-2/5 flex space-x-4" }, { children: [_jsxs("div", __assign({ className: "flex space-x-1" }, { children: [profileSource != null && queryClient != null ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient, disabled: isLoading })) : null, _jsx(Button, __assign({ color: "neutral", onClick: function (e) {
138
139
  e.preventDefault();
139
140
  onDownloadPProf();
140
- }, disabled: isLoading }, { children: "Download pprof" }))] })), _jsx(FilterByFunctionButton, {})] })), _jsxs("div", __assign({ className: "flex ml-auto gap-2" }, { children: [_jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })), callgraphEnabled ? (_jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Callgraph" }))) : null, _jsxs("div", __assign({ className: "flex" }, { children: [_jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] }))] })), isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(Profiler, __assign({ id: "icicleGraph", onRender: perf === null || perf === void 0 ? void 0 : perf.onRender }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit }) })) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(Callgraph, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, colorRange: colorRange })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] })))] }) }) })) }));
141
+ }, disabled: isLoading }, { children: "Download pprof" }))] })), _jsx(FilterByFunctionButton, {})] })), _jsxs("div", __assign({ className: "flex ml-auto gap-2" }, { children: [_jsx(Button, __assign({ color: "neutral", onClick: resetIcicleGraph, disabled: curPath.length === 0, className: "whitespace-nowrap text-ellipsis" }, { children: "Reset View" })), callgraphEnabled ? (_jsx(Button, __assign({ variant: "".concat(currentView === 'callgraph' ? 'primary' : 'neutral'), onClick: function () { return switchProfileView('callgraph'); }, className: "whitespace-nowrap text-ellipsis" }, { children: "Callgraph" }))) : null, _jsxs("div", __assign({ className: "flex" }, { children: [_jsx(Button, __assign({ variant: "".concat(currentView === 'table' ? 'primary' : 'neutral'), className: "items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('table'); } }, { children: "Table" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'both' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-tr-none rounded-bl-none rounded-br-none border-l-0 border-r-0 w-auto px-8 whitespace-nowrap no-outline-on-buttons text-ellipsis", onClick: function () { return switchProfileView('both'); } }, { children: "Both" })), _jsx(Button, __assign({ variant: "".concat(currentView === 'icicle' ? 'primary' : 'neutral'), className: "items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons", onClick: function () { return switchProfileView('icicle'); } }, { children: "Icicle Graph" }))] }))] }))] })), isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsxs("div", __assign({ ref: ref, className: "flex space-x-4 justify-between w-full" }, { children: [currentView === 'icicle' && (flamegraphData === null || flamegraphData === void 0 ? void 0 : flamegraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(Profiler, __assign({ id: "icicleGraph", onRender: perf === null || perf === void 0 ? void 0 : perf.onRender }, { children: _jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit, onContainerResize: onFlamegraphContainerResize }) })) }))), currentView === 'callgraph' && (callgraphData === null || callgraphData === void 0 ? void 0 : callgraphData.data) != null && (_jsx("div", __assign({ className: "w-full" }, { children: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) !== undefined && (_jsx(Callgraph, { graph: callgraphData.data, sampleUnit: sampleUnit, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, colorRange: colorRange })) }))), currentView === 'table' && topTableData != null && (_jsx("div", __assign({ className: "w-full" }, { children: _jsx(TopTable, { data: topTableData.data, sampleUnit: sampleUnit }) }))), currentView === 'both' && (_jsxs(_Fragment, { children: [_jsx("div", __assign({ className: "w-1/2" }, { children: _jsx(TopTable, { data: topTableData === null || topTableData === void 0 ? void 0 : topTableData.data, sampleUnit: sampleUnit }) })), _jsx("div", __assign({ className: "w-1/2" }, { children: flamegraphData != null && (_jsx(ProfileIcicleGraph, { curPath: curPath, setNewCurPath: setNewCurPath, graph: flamegraphData.data, sampleUnit: sampleUnit })) }))] }))] })))] }) }) })) }));
141
142
  };
@@ -53,16 +53,35 @@ import { ProfileView, useProfileVisState } from './ProfileView';
53
53
  import { downloadPprof } from './utils';
54
54
  import { useGrpcMetadata, useParcaContext } from '@parca/components';
55
55
  import { saveAsBlob } from '@parca/functions';
56
- import { useEffect } from 'react';
56
+ import { useEffect, useState } from 'react';
57
+ import useUserPreference, { USER_PREFERENCES } from '@parca/functions/useUserPreference';
57
58
  export var ProfileViewWithData = function (_a) {
58
59
  var _b, _c;
59
60
  var queryClient = _a.queryClient, profileSource = _a.profileSource, navigateTo = _a.navigateTo;
60
61
  var profileVisState = useProfileVisState();
61
62
  var metadata = useGrpcMetadata();
62
63
  var currentView = profileVisState.currentView;
63
- var _d = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
64
+ var _d = useState(0), nodeTrimThreshold = _d[0], setNodeTrimThreshold = _d[1];
65
+ var disableTrimming = useUserPreference(USER_PREFERENCES.DISABLE_GRAPH_TRIMMING.key)[0];
66
+ useEffect(function () {
67
+ if (disableTrimming) {
68
+ setNodeTrimThreshold(0);
69
+ }
70
+ }, [disableTrimming]);
71
+ var onFlamegraphContainerResize = function (width) {
72
+ if (disableTrimming || width === 0) {
73
+ return;
74
+ }
75
+ var threshold = (1 / width) * 100;
76
+ if (threshold === nodeTrimThreshold) {
77
+ return;
78
+ }
79
+ setNodeTrimThreshold(threshold);
80
+ };
81
+ var _e = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
64
82
  skip: currentView !== 'icicle' && currentView !== 'both',
65
- }), flamegraphLoading = _d.isLoading, flamegraphResponse = _d.response, flamegraphError = _d.error;
83
+ nodeTrimThreshold: nodeTrimThreshold,
84
+ }), flamegraphLoading = _e.isLoading, flamegraphResponse = _e.response, flamegraphError = _e.error;
66
85
  var perf = useParcaContext().perf;
67
86
  useEffect(function () {
68
87
  var _a;
@@ -74,12 +93,12 @@ export var ProfileViewWithData = function (_a) {
74
93
  }
75
94
  perf === null || perf === void 0 ? void 0 : perf.markInteraction('Flamegraph Render', (_a = flamegraphResponse === null || flamegraphResponse === void 0 ? void 0 : flamegraphResponse.report) === null || _a === void 0 ? void 0 : _a.flamegraph.total);
76
95
  }, [flamegraphLoading, flamegraphResponse, perf]);
77
- var _e = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
96
+ var _f = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
78
97
  skip: currentView !== 'table' && currentView !== 'both',
79
- }), topTableLoading = _e.isLoading, topTableResponse = _e.response, topTableError = _e.error;
80
- var _f = useQuery(queryClient, profileSource, QueryRequest_ReportType.CALLGRAPH, {
98
+ }), topTableLoading = _f.isLoading, topTableResponse = _f.response, topTableError = _f.error;
99
+ var _g = useQuery(queryClient, profileSource, QueryRequest_ReportType.CALLGRAPH, {
81
100
  skip: currentView !== 'callgraph',
82
- }), callgraphLoading = _f.isLoading, callgraphResponse = _f.response, callgraphError = _f.error;
101
+ }), callgraphLoading = _g.isLoading, callgraphResponse = _g.response, callgraphError = _g.error;
83
102
  var sampleUnit = profileSource.ProfileType().sampleUnit;
84
103
  var downloadPProfClick = function () { return __awaiter(void 0, void 0, void 0, function () {
85
104
  var blob, error_1;
@@ -121,6 +140,6 @@ export var ProfileViewWithData = function (_a) {
121
140
  ? (_c = callgraphResponse === null || callgraphResponse === void 0 ? void 0 : callgraphResponse.report) === null || _c === void 0 ? void 0 : _c.callgraph
122
141
  : undefined,
123
142
  error: callgraphError,
124
- }, profileVisState: profileVisState, sampleUnit: sampleUnit, profileSource: profileSource, queryClient: queryClient, navigateTo: navigateTo, onDownloadPProf: function () { return void downloadPProfClick(); } }));
143
+ }, profileVisState: profileVisState, sampleUnit: sampleUnit, profileSource: profileSource, queryClient: queryClient, navigateTo: navigateTo, onDownloadPProf: function () { return void downloadPProfClick(); }, onFlamegraphContainerResize: onFlamegraphContainerResize }));
125
144
  };
126
145
  export default ProfileViewWithData;
@@ -8,6 +8,7 @@ export interface IQueryResult {
8
8
  }
9
9
  interface UseQueryOptions {
10
10
  skip?: boolean;
11
+ nodeTrimThreshold?: number;
11
12
  }
12
13
  export declare const useQuery: (client: QueryServiceClient, profileSource: ProfileSource, reportType: QueryRequest_ReportType, options?: UseQueryOptions) => IQueryResult;
13
14
  export {};
package/dist/useQuery.js CHANGED
@@ -52,7 +52,7 @@ export var useQuery = function (client, profileSource, reportType, options) {
52
52
  var _a = (options !== null && options !== void 0 ? options : {}).skip, skip = _a === void 0 ? false : _a;
53
53
  var metadata = useGrpcMetadata();
54
54
  var _b = useGrpcQuery({
55
- key: ['query', profileSource, reportType],
55
+ key: ['query', profileSource, reportType, options === null || options === void 0 ? void 0 : options.nodeTrimThreshold],
56
56
  queryFn: function () { return __awaiter(void 0, void 0, void 0, function () {
57
57
  var req, response;
58
58
  return __generator(this, function (_a) {
@@ -60,6 +60,7 @@ export var useQuery = function (client, profileSource, reportType, options) {
60
60
  case 0:
61
61
  req = profileSource.QueryRequest();
62
62
  req.reportType = reportType;
63
+ req.nodeTrimThreshold = options === null || options === void 0 ? void 0 : options.nodeTrimThreshold;
63
64
  return [4 /*yield*/, client.query(req, { meta: metadata })];
64
65
  case 1:
65
66
  response = (_a.sent()).response;
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.89",
3
+ "version": "0.16.90",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
- "@parca/client": "^0.16.61",
7
- "@parca/components": "^0.16.78",
6
+ "@parca/client": "^0.16.62",
7
+ "@parca/components": "^0.16.79",
8
8
  "@parca/dynamicsize": "^0.16.52",
9
- "@parca/functions": "^0.16.54",
9
+ "@parca/functions": "^0.16.55",
10
10
  "@parca/parser": "^0.16.52",
11
11
  "@parca/store": "^0.16.51",
12
12
  "d3": "7.7.0",
@@ -42,5 +42,5 @@
42
42
  "access": "public",
43
43
  "registry": "https://registry.npmjs.org/"
44
44
  },
45
- "gitHead": "de3039d619347aa29a374871a6cbb4f53aa72d6a"
45
+ "gitHead": "4a9035ae2d347b9e419e81ed27d587658c8f3fdd"
46
46
  }
@@ -383,7 +383,7 @@ export default function IcicleGraph({
383
383
  if (ref.current != null) {
384
384
  setHeight(ref?.current.getBoundingClientRect().height);
385
385
  }
386
- }, [width]);
386
+ }, [width, graph]);
387
387
 
388
388
  const total = useMemo(() => parseFloat(graph.total), [graph.total]);
389
389
  const xScale = useMemo(() => {
@@ -17,6 +17,11 @@ import {useContainerDimensions} from '@parca/dynamicsize';
17
17
 
18
18
  import DiffLegend from '../components/DiffLegend';
19
19
  import IcicleGraph from '../IcicleGraph';
20
+ import {useEffect, useMemo} from 'react';
21
+
22
+ const numberFormatter = new Intl.NumberFormat('en-US');
23
+
24
+ export type ResizeHandler = (width: number, height: number) => void;
20
25
 
21
26
  interface ProfileIcicleGraphProps {
22
27
  width?: number;
@@ -24,6 +29,7 @@ interface ProfileIcicleGraphProps {
24
29
  sampleUnit: string;
25
30
  curPath: string[] | [];
26
31
  setNewCurPath: (path: string[]) => void;
32
+ onContainerResize?: ResizeHandler;
27
33
  }
28
34
 
29
35
  const ProfileIcicleGraph = ({
@@ -31,10 +37,38 @@ const ProfileIcicleGraph = ({
31
37
  curPath,
32
38
  setNewCurPath,
33
39
  sampleUnit,
40
+ onContainerResize,
34
41
  }: ProfileIcicleGraphProps): JSX.Element => {
35
42
  const compareMode = useAppSelector(selectCompareMode);
36
43
  const {ref, dimensions} = useContainerDimensions();
37
44
 
45
+ useEffect(() => {
46
+ if (dimensions === undefined) return;
47
+ if (onContainerResize === undefined) return;
48
+
49
+ onContainerResize(dimensions.width, dimensions.height);
50
+ }, [dimensions, onContainerResize]);
51
+
52
+ const [trimDifference, trimmedPercentage, formattedTotal, formattedUntrimmedTotal] =
53
+ useMemo(() => {
54
+ if (graph === undefined || graph.untrimmedTotal === '0') {
55
+ return [BigInt(0), '0'];
56
+ }
57
+
58
+ const untrimmedTotal = BigInt(graph.untrimmedTotal);
59
+ const total = BigInt(graph.total);
60
+
61
+ const trimDifference = untrimmedTotal - total;
62
+ const trimmedPercentage = (total * BigInt(100)) / untrimmedTotal;
63
+
64
+ return [
65
+ trimDifference,
66
+ trimmedPercentage.toString(),
67
+ numberFormatter.format(total),
68
+ numberFormatter.format(untrimmedTotal),
69
+ ];
70
+ }, [graph]);
71
+
38
72
  if (graph === undefined) return <div>no data...</div>;
39
73
  const total = graph.total;
40
74
  if (parseFloat(total) === 0) return <>Profile has no samples</>;
@@ -42,6 +76,11 @@ const ProfileIcicleGraph = ({
42
76
  return (
43
77
  <>
44
78
  {compareMode && <DiffLegend />}
79
+ {trimDifference > BigInt(0) ? (
80
+ <p className="my-2 text-sm">
81
+ Showing {formattedTotal}({trimmedPercentage}%) out of {formattedUntrimmedTotal} samples
82
+ </p>
83
+ ) : null}
45
84
  <div ref={ref}>
46
85
  <IcicleGraph
47
86
  width={dimensions?.width}
@@ -31,12 +31,13 @@ import {
31
31
  import {Callgraph} from '../';
32
32
  import ProfileShareButton from '../components/ProfileShareButton';
33
33
  import FilterByFunctionButton from './FilterByFunctionButton';
34
- import ProfileIcicleGraph from '../ProfileIcicleGraph';
34
+ import ProfileIcicleGraph, {ResizeHandler} from '../ProfileIcicleGraph';
35
35
  import {ProfileSource} from '../ProfileSource';
36
36
  import TopTable from '../TopTable';
37
37
  import useDelayedLoader from '../useDelayedLoader';
38
38
 
39
39
  import '../ProfileView.styles.css';
40
+ import useUserPreference, {USER_PREFERENCES} from '@parca/functions/useUserPreference';
40
41
 
41
42
  type NavigateFunction = (path: string, queryParams: any) => void;
42
43
 
@@ -76,6 +77,7 @@ export interface ProfileViewProps {
76
77
  navigateTo?: NavigateFunction;
77
78
  compare?: boolean;
78
79
  onDownloadPProf: () => void;
80
+ onFlamegraphContainerResize?: ResizeHandler;
79
81
  }
80
82
 
81
83
  function arrayEquals<T>(a: T[], b: T[]): boolean {
@@ -113,6 +115,7 @@ export const ProfileView = ({
113
115
  navigateTo,
114
116
  profileVisState,
115
117
  onDownloadPProf,
118
+ onFlamegraphContainerResize,
116
119
  }: ProfileViewProps): JSX.Element => {
117
120
  const dispatch = useAppDispatch();
118
121
  const {ref, dimensions} = useContainerDimensions();
@@ -123,7 +126,9 @@ export const ProfileView = ({
123
126
  const filterByFunctionString = useAppSelector(selectFilterByFunction);
124
127
 
125
128
  const [callgraphEnabled] = useUIFeatureFlag('callgraph');
126
- const [highlightAfterFilteringEnabled] = useUIFeatureFlag('highlightAfterFiltering');
129
+ const [highlightAfterFilteringEnabled] = useUserPreference<boolean>(
130
+ USER_PREFERENCES.HIGHTLIGHT_AFTER_FILTERING.key
131
+ );
127
132
 
128
133
  const {loader, perf} = useParcaContext();
129
134
 
@@ -305,6 +310,7 @@ export const ProfileView = ({
305
310
  setNewCurPath={setNewCurPath}
306
311
  graph={flamegraphData.data}
307
312
  sampleUnit={sampleUnit}
313
+ onContainerResize={onFlamegraphContainerResize}
308
314
  />
309
315
  </Profiler>
310
316
  </div>
@@ -19,7 +19,8 @@ import {ProfileSource} from './ProfileSource';
19
19
  import {downloadPprof} from './utils';
20
20
  import {useGrpcMetadata, useParcaContext} from '@parca/components';
21
21
  import {saveAsBlob} from '@parca/functions';
22
- import {useEffect} from 'react';
22
+ import {useEffect, useState} from 'react';
23
+ import useUserPreference, {USER_PREFERENCES} from '@parca/functions/useUserPreference';
23
24
 
24
25
  export type NavigateFunction = (path: string, queryParams: any) => void;
25
26
 
@@ -38,12 +39,33 @@ export const ProfileViewWithData = ({
38
39
  const profileVisState = useProfileVisState();
39
40
  const metadata = useGrpcMetadata();
40
41
  const {currentView} = profileVisState;
42
+ const [nodeTrimThreshold, setNodeTrimThreshold] = useState<number>(0);
43
+ const [disableTrimming] = useUserPreference<boolean>(USER_PREFERENCES.DISABLE_GRAPH_TRIMMING.key);
44
+
45
+ useEffect(() => {
46
+ if (disableTrimming) {
47
+ setNodeTrimThreshold(0);
48
+ }
49
+ }, [disableTrimming]);
50
+
51
+ const onFlamegraphContainerResize = (width: number): void => {
52
+ if (disableTrimming || width === 0) {
53
+ return;
54
+ }
55
+ const threshold = (1 / width) * 100;
56
+ if (threshold === nodeTrimThreshold) {
57
+ return;
58
+ }
59
+ setNodeTrimThreshold(threshold);
60
+ };
61
+
41
62
  const {
42
63
  isLoading: flamegraphLoading,
43
64
  response: flamegraphResponse,
44
65
  error: flamegraphError,
45
66
  } = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
46
67
  skip: currentView !== 'icicle' && currentView !== 'both',
68
+ nodeTrimThreshold,
47
69
  });
48
70
  const {perf} = useParcaContext();
49
71
 
@@ -120,6 +142,7 @@ export const ProfileViewWithData = ({
120
142
  queryClient={queryClient}
121
143
  navigateTo={navigateTo}
122
144
  onDownloadPProf={() => void downloadPProfClick()}
145
+ onFlamegraphContainerResize={onFlamegraphContainerResize}
123
146
  />
124
147
  );
125
148
  };
package/src/useQuery.tsx CHANGED
@@ -26,6 +26,7 @@ export interface IQueryResult {
26
26
 
27
27
  interface UseQueryOptions {
28
28
  skip?: boolean;
29
+ nodeTrimThreshold?: number;
29
30
  }
30
31
 
31
32
  export const useQuery = (
@@ -37,10 +38,11 @@ export const useQuery = (
37
38
  const {skip = false} = options ?? {};
38
39
  const metadata = useGrpcMetadata();
39
40
  const {data, isLoading, error} = useGrpcQuery<QueryResponse | undefined>({
40
- key: ['query', profileSource, reportType],
41
+ key: ['query', profileSource, reportType, options?.nodeTrimThreshold],
41
42
  queryFn: async () => {
42
43
  const req = profileSource.QueryRequest();
43
44
  req.reportType = reportType;
45
+ req.nodeTrimThreshold = options?.nodeTrimThreshold;
44
46
 
45
47
  const {response} = await client.query(req, {meta: metadata});
46
48
  return response;