@parca/profile 0.15.17 → 0.15.24

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,57 @@
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.15.24](https://github.com/parca-dev/parca/compare/ui-v0.15.23...ui-v0.15.24) (2022-09-13)
7
+
8
+
9
+
10
+ ## [0.15.19](https://github.com/parca-dev/parca/compare/ui-v0.15.17...ui-v0.15.19) (2022-09-08)
11
+
12
+
13
+
14
+ ## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.17) (2022-09-07)
15
+
16
+
17
+ ### Bug Fixes
18
+
19
+ * **ui:** type all the things (string mode) ([e91ac9d](https://github.com/parca-dev/parca/commit/e91ac9deb8a200ed3c9a9c5d81ae71e13d49466e))
20
+
21
+
22
+
23
+ ## 0.15.10 (2022-08-30)
24
+
25
+
26
+
27
+
28
+
29
+ ## [0.15.19](https://github.com/parca-dev/parca/compare/ui-v0.15.18...ui-v0.15.19) (2022-09-08)
30
+
31
+
32
+
33
+ ## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.10...ui-v0.15.17) (2022-09-07)
34
+
35
+
36
+
37
+ ## [0.15.10](https://github.com/parca-dev/parca/compare/ui-v0.15.8...ui-v0.15.10) (2022-08-30)
38
+
39
+ **Note:** Version bump only for package @parca/profile
40
+
41
+
42
+
43
+
44
+
45
+ ## [0.15.18](https://github.com/parca-dev/parca/compare/ui-v0.15.17...ui-v0.15.18) (2022-09-08)
46
+
47
+
48
+
49
+ ## [0.15.10](https://github.com/parca-dev/parca/compare/ui-v0.15.8...ui-v0.15.10) (2022-08-30)
50
+
51
+ **Note:** Version bump only for package @parca/profile
52
+
53
+
54
+
55
+
56
+
6
57
  ## [0.15.17](https://github.com/parca-dev/parca/compare/ui-v0.15.16...ui-v0.15.17) (2022-09-07)
7
58
 
8
59
 
package/package.json CHANGED
@@ -1,17 +1,20 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.15.17",
3
+ "version": "0.15.24",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@iconify/react": "^3.2.2",
7
7
  "@parca/client": "^0.15.11",
8
8
  "@parca/dynamicsize": "^0.15.17",
9
- "@parca/functions": "^0.15.17",
10
- "@parca/parser": "^0.15.17",
9
+ "@parca/functions": "^0.15.24",
10
+ "@parca/parser": "^0.15.24",
11
11
  "d3-scale": "^4.0.2",
12
12
  "d3-selection": "3.0.0",
13
13
  "react-copy-to-clipboard": "^5.1.0"
14
14
  },
15
+ "devDependencies": {
16
+ "@types/react-copy-to-clipboard": "5.0.4"
17
+ },
15
18
  "main": "src/index.tsx",
16
19
  "scripts": {
17
20
  "test": "jest --coverage --config ../../../jest.config.js ./src/*"
@@ -23,5 +26,5 @@
23
26
  "access": "public",
24
27
  "registry": "https://registry.npmjs.org/"
25
28
  },
26
- "gitHead": "df4bacb55c1503822de9d3312ebe8b90ff17d55b"
29
+ "gitHead": "3cc65f6eecfb39e80acd6ec0888834c67a4f89d1"
27
30
  }
@@ -22,6 +22,7 @@ import {getLastItem, diffColor, isSearchMatch} from '@parca/functions';
22
22
  import {useAppSelector, selectDarkMode, selectSearchNodeString} from '@parca/store';
23
23
 
24
24
  import {hexifyAddress} from './utils';
25
+ import {HoveringNode} from '@parca/components/src/GraphTooltip';
25
26
 
26
27
  interface IcicleGraphProps {
27
28
  graph: Flamegraph;
@@ -260,7 +261,7 @@ export function IcicleGraphRootNode({
260
261
  const onClick = (): void => setCurPath([]);
261
262
  const onMouseEnter = (): void => setHoveringNode(node);
262
263
  const onMouseLeave = (): void => setHoveringNode(undefined);
263
- const path = [];
264
+ const path: string[] = [];
264
265
 
265
266
  return (
266
267
  <g transform={'translate(0, 0)'}>
@@ -336,7 +337,7 @@ export default function IcicleGraph({
336
337
  total={total}
337
338
  x={pos[0]}
338
339
  y={pos[1]}
339
- hoveringNode={hoveringNode}
340
+ hoveringNode={hoveringNode as HoveringNode}
340
341
  contextElement={svg.current}
341
342
  />
342
343
  <svg
@@ -20,7 +20,6 @@ import {
20
20
  Button,
21
21
  Card,
22
22
  SearchNodes,
23
- useGrpcMetadata,
24
23
  useParcaTheme,
25
24
  Callgraph as CallgraphComponent,
26
25
  } from '@parca/components';
@@ -31,7 +30,6 @@ import ProfileIcicleGraph from './ProfileIcicleGraph';
31
30
  import {ProfileSource} from './ProfileSource';
32
31
  import TopTable from './TopTable';
33
32
  import useDelayedLoader from './useDelayedLoader';
34
- import {downloadPprof} from './utils';
35
33
 
36
34
  import './ProfileView.styles.css';
37
35
 
@@ -55,7 +53,7 @@ interface CallgraphData {
55
53
  error?: any;
56
54
  }
57
55
 
58
- type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
56
+ export type VisualizationType = 'icicle' | 'table' | 'callgraph' | 'both';
59
57
 
60
58
  interface ProfileVisState {
61
59
  currentView: VisualizationType;
@@ -72,6 +70,7 @@ interface ProfileViewProps {
72
70
  queryClient?: QueryServiceClient;
73
71
  navigateTo?: NavigateFunction;
74
72
  compare?: boolean;
73
+ onDownloadPProf: () => void;
75
74
  }
76
75
 
77
76
  function arrayEquals<T>(a: T[], b: T[]): boolean {
@@ -83,11 +82,18 @@ function arrayEquals<T>(a: T[], b: T[]): boolean {
83
82
  );
84
83
  }
85
84
  export const useProfileVisState = (): ProfileVisState => {
86
- const router = parseParams(window.location.search);
87
- const currentViewFromURL = router.currentProfileView as string;
88
- const [currentView, setCurrentView] = useState<VisualizationType>(
89
- (currentViewFromURL as VisualizationType) ?? 'icicle'
90
- );
85
+ const [currentView, setCurrentView] = useState<VisualizationType>(() => {
86
+ if (typeof window === 'undefined') {
87
+ return 'icicle';
88
+ }
89
+ const router = parseParams(window.location.search);
90
+ const currentViewFromURL = router.currentProfileView as string;
91
+
92
+ if (currentViewFromURL != null) {
93
+ return currentViewFromURL as VisualizationType;
94
+ }
95
+ return 'icicle';
96
+ });
91
97
 
92
98
  return {currentView, setCurrentView};
93
99
  };
@@ -101,6 +107,7 @@ export const ProfileView = ({
101
107
  queryClient,
102
108
  navigateTo,
103
109
  profileVisState,
110
+ onDownloadPProf,
104
111
  }: ProfileViewProps): JSX.Element => {
105
112
  const {ref, dimensions} = useContainerDimensions();
106
113
  const [curPath, setCurPath] = useState<string[]>([]);
@@ -108,7 +115,6 @@ export const ProfileView = ({
108
115
 
109
116
  const [callgraphEnabled] = useUIFeatureFlag('callgraph');
110
117
 
111
- const metadata = useGrpcMetadata();
112
118
  const {loader} = useParcaTheme();
113
119
 
114
120
  useEffect(() => {
@@ -147,23 +153,6 @@ export const ProfileView = ({
147
153
  );
148
154
  }
149
155
 
150
- const downloadPProf = async (e: React.MouseEvent<HTMLElement>): Promise<void> => {
151
- e.preventDefault();
152
- if (profileSource == null || queryClient == null) {
153
- return;
154
- }
155
-
156
- try {
157
- const blob = await downloadPprof(profileSource.QueryRequest(), queryClient, metadata);
158
- const link = document.createElement('a');
159
- link.href = window.URL.createObjectURL(blob);
160
- link.download = 'profile.pb.gz';
161
- link.click();
162
- } catch (error) {
163
- console.error('Error while querying', error);
164
- }
165
- };
166
-
167
156
  const resetIcicleGraph = (): void => setCurPath([]);
168
157
 
169
158
  const setNewCurPath = (path: string[]): void => {
@@ -176,11 +165,12 @@ export const ProfileView = ({
176
165
  if (view == null) {
177
166
  return;
178
167
  }
179
- if (navigateTo === undefined) return;
180
-
181
168
  setCurrentView(view);
182
- const router = parseParams(window.location.search);
183
169
 
170
+ if (navigateTo === undefined) {
171
+ return;
172
+ }
173
+ const router = parseParams(window.location.search);
184
174
  navigateTo('/', {...router, ...{currentProfileView: view}});
185
175
  };
186
176
 
@@ -199,7 +189,13 @@ export const ProfileView = ({
199
189
  />
200
190
  ) : null}
201
191
 
202
- <Button color="neutral" onClick={void downloadPProf}>
192
+ <Button
193
+ color="neutral"
194
+ onClick={e => {
195
+ e.preventDefault();
196
+ onDownloadPProf();
197
+ }}
198
+ >
203
199
  Download pprof
204
200
  </Button>
205
201
  </div>
@@ -16,6 +16,8 @@ import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
16
16
  import {useQuery} from './useQuery';
17
17
  import {ProfileView, useProfileVisState} from './ProfileView';
18
18
  import {ProfileSource} from './ProfileSource';
19
+ import {downloadPprof} from './utils';
20
+ import {useGrpcMetadata} from '@parca/components';
19
21
 
20
22
  type NavigateFunction = (path: string, queryParams: any) => void;
21
23
 
@@ -32,6 +34,7 @@ export const ProfileViewWithData = ({
32
34
  navigateTo,
33
35
  }: ProfileViewWithDataProps): JSX.Element => {
34
36
  const profileVisState = useProfileVisState();
37
+ const metadata = useGrpcMetadata();
35
38
  const {currentView} = profileVisState;
36
39
  const {
37
40
  isLoading: flamegraphLoading,
@@ -59,6 +62,22 @@ export const ProfileViewWithData = ({
59
62
 
60
63
  const sampleUnit = profileSource.ProfileType().sampleUnit;
61
64
 
65
+ const downloadPProfClick = async (): Promise<void> => {
66
+ if (profileSource == null || queryClient == null) {
67
+ return;
68
+ }
69
+
70
+ try {
71
+ const blob = await downloadPprof(profileSource.QueryRequest(), queryClient, metadata);
72
+ const link = document.createElement('a');
73
+ link.href = window.URL.createObjectURL(blob);
74
+ link.download = 'profile.pb.gz';
75
+ link.click();
76
+ } catch (error) {
77
+ console.error('Error while querying', error);
78
+ }
79
+ };
80
+
62
81
  return (
63
82
  <ProfileView
64
83
  flamegraphData={{
@@ -88,6 +107,7 @@ export const ProfileViewWithData = ({
88
107
  profileSource={profileSource}
89
108
  queryClient={queryClient}
90
109
  navigateTo={navigateTo}
110
+ onDownloadPProf={() => void downloadPProfClick()}
91
111
  />
92
112
  );
93
113
  };
package/src/TopTable.tsx CHANGED
@@ -15,7 +15,7 @@ import React from 'react';
15
15
 
16
16
  import {getLastItem, valueFormatter, isSearchMatch} from '@parca/functions';
17
17
  import {useAppSelector, selectCompareMode, selectSearchNodeString} from '@parca/store';
18
- import {TopNodeMeta, Top} from '@parca/client';
18
+ import {TopNode, TopNodeMeta, Top} from '@parca/client';
19
19
 
20
20
  import {hexifyAddress} from './utils';
21
21
 
@@ -43,7 +43,10 @@ const Arrow = ({direction}: {direction: string | undefined}): JSX.Element => {
43
43
 
44
44
  const useSortableData = (
45
45
  top?: Top,
46
- config = {key: 'cumulative', direction: 'desc'}
46
+ config: {key: keyof TopNode | 'name'; direction: 'asc' | 'desc'} = {
47
+ key: 'cumulative',
48
+ direction: 'desc',
49
+ }
47
50
  ): {
48
51
  items:
49
52
  | Array<{
@@ -54,17 +57,20 @@ const useSortableData = (
54
57
  meta?: TopNodeMeta | undefined;
55
58
  }>
56
59
  | undefined;
57
- requestSort: (key: string) => void;
58
- sortConfig: {key: string; direction: string} | null;
60
+ requestSort: (key: keyof TopNode | 'name') => void;
61
+ sortConfig: {key: keyof TopNode | 'name'; direction: string} | null;
59
62
  } => {
60
- const [sortConfig, setSortConfig] = React.useState<{key: string; direction: string} | null>(
61
- config
62
- );
63
+ const [sortConfig, setSortConfig] = React.useState<{
64
+ key: keyof TopNode | 'name';
65
+ direction: string;
66
+ } | null>(config);
63
67
 
64
68
  const rawTableReport = top != null ? top.list : [];
65
69
 
66
70
  const items = rawTableReport.map(node => ({
67
71
  ...node,
72
+ // Warning: string to number can overflow
73
+ // https://github.com/timostamm/protobuf-ts/blob/master/MANUAL.md#bigint-support
68
74
  diff: Number(node.diff),
69
75
  cumulative: Number(node.cumulative),
70
76
  flat: Number(node.flat),
@@ -77,10 +83,21 @@ const useSortableData = (
77
83
  const sortableItems = [...items];
78
84
  if (sortConfig !== null) {
79
85
  sortableItems.sort((a, b) => {
80
- if (a[sortConfig.key] < b[sortConfig.key]) {
86
+ const itemA = a[sortConfig.key];
87
+ const itemB = b[sortConfig.key];
88
+ if (itemA === undefined && itemB === undefined) {
89
+ return 0;
90
+ }
91
+ if (itemA === undefined) {
92
+ return sortConfig.direction === 'asc' ? -1 : 1;
93
+ }
94
+ if (itemB === undefined) {
95
+ return sortConfig.direction === 'asc' ? 1 : -1;
96
+ }
97
+ if (itemA < itemB) {
81
98
  return sortConfig.direction === 'asc' ? -1 : 1;
82
99
  }
83
- if (a[sortConfig.key] > b[sortConfig.key]) {
100
+ if (itemA > itemB) {
84
101
  return sortConfig.direction === 'asc' ? 1 : -1;
85
102
  }
86
103
  return 0;
@@ -89,7 +106,7 @@ const useSortableData = (
89
106
  return sortableItems;
90
107
  }, [items, sortConfig]);
91
108
 
92
- const requestSort = (key: string): void => {
109
+ const requestSort = (key: keyof TopNode | 'name'): void => {
93
110
  let direction = 'desc';
94
111
  if (sortConfig != null && sortConfig.key === key && sortConfig.direction === 'desc') {
95
112
  direction = 'asc';
@@ -54,9 +54,13 @@ const ProfileShareModal = ({
54
54
  setLoading(false);
55
55
  setIsShared(true);
56
56
  } catch (err) {
57
- console.error(err);
58
- setLoading(false);
59
- setError(err.toString());
57
+ if (err instanceof Error) {
58
+ console.error(err);
59
+ setLoading(false);
60
+ // https://github.com/microsoft/TypeScript/issues/38347
61
+ // eslint-disable-next-line @typescript-eslint/no-base-to-string
62
+ setError(err.toString());
63
+ }
60
64
  }
61
65
  };
62
66