@parca/profile 0.14.12 → 0.14.29

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,28 @@
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.14.29](https://github.com/parca-dev/parca/compare/ui-v0.14.28...ui-v0.14.29) (2022-08-15)
7
+
8
+
9
+
10
+ ## 0.14.21 (2022-08-10)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
14
+
15
+
16
+
17
+
18
+ ## [0.14.16](https://github.com/parca-dev/parca/compare/ui-v0.14.15...ui-v0.14.16) (2022-08-03)
19
+
20
+ **Note:** Version bump only for package @parca/profile
21
+
22
+ ## [0.14.13](https://github.com/parca-dev/parca/compare/ui-v0.14.12...ui-v0.14.13) (2022-08-03)
23
+
24
+ ## [0.14.7](https://github.com/parca-dev/parca/compare/ui-v0.14.5...ui-v0.14.7) (2022-07-27)
25
+
26
+ **Note:** Version bump only for package @parca/profile
27
+
6
28
  ## [0.14.12](https://github.com/parca-dev/parca/compare/ui-v0.14.11...ui-v0.14.12) (2022-08-02)
7
29
 
8
30
  **Note:** Version bump only for package @parca/profile
package/package.json CHANGED
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.14.12",
3
+ "version": "0.14.29",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@iconify/react": "^3.2.2",
7
- "@parca/client": "^0.14.12",
8
- "@parca/dynamicsize": "^0.14.1",
9
- "@parca/parser": "^0.14.1",
7
+ "@parca/client": "^0.14.16",
8
+ "@parca/dynamicsize": "^0.14.29",
9
+ "@parca/parser": "^0.14.29",
10
10
  "d3-scale": "^4.0.2",
11
11
  "d3-selection": "3.0.0",
12
12
  "react-copy-to-clipboard": "^5.1.0"
@@ -22,5 +22,5 @@
22
22
  "access": "public",
23
23
  "registry": "https://registry.npmjs.org/"
24
24
  },
25
- "gitHead": "252b9b4813c78624181ea55614f4339921a2ffc1"
25
+ "gitHead": "e6dabbc59485409b1fc818d5c56c7999d9a22f17"
26
26
  }
@@ -1,4 +1,18 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import React, {MouseEvent, useEffect, useRef, useState} from 'react';
15
+
2
16
  import {throttle} from 'lodash';
3
17
  import {pointer} from 'd3-selection';
4
18
  import {scaleLinear} from 'd3-scale';
@@ -185,7 +199,7 @@ export function IcicleGraphNodes({
185
199
  const onMouseLeave = () => setHoveringNode(undefined);
186
200
 
187
201
  return (
188
- <React.Fragment>
202
+ <React.Fragment key={`node-${key}`}>
189
203
  <IcicleRect
190
204
  key={`rect-${key}`}
191
205
  x={xStart}
@@ -1,3 +1,16 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import {Flamegraph} from '@parca/client';
2
15
  import {useAppSelector, selectCompareMode} from '@parca/store';
3
16
  import {useContainerDimensions} from '@parca/dynamicsize';
@@ -20,13 +33,12 @@ const ProfileIcicleGraph = ({
20
33
  sampleUnit,
21
34
  }: ProfileIcicleGraphProps) => {
22
35
  const compareMode = useAppSelector(selectCompareMode);
36
+ const {ref, dimensions} = useContainerDimensions();
23
37
 
24
38
  if (graph === undefined) return <div>no data...</div>;
25
39
  const total = graph.total;
26
40
  if (parseFloat(total) === 0) return <>Profile has no samples</>;
27
41
 
28
- const {ref, dimensions} = useContainerDimensions();
29
-
30
42
  return (
31
43
  <>
32
44
  {compareMode && <DiffLegend />}
@@ -1,3 +1,16 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import React from 'react';
2
15
  import {formatDate} from '@parca/functions';
3
16
  import {Query, ProfileType} from '@parca/parser';
@@ -1,22 +1,59 @@
1
- import React, {useEffect, useState} from 'react';
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import React, {useEffect, useMemo, useState} from 'react';
15
+
2
16
  import {parseParams} from '@parca/functions';
3
- import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
17
+ import {QueryServiceClient, Flamegraph, Top} from '@parca/client';
4
18
  import {Button, Card, SearchNodes, useGrpcMetadata, useParcaTheme} from '@parca/components';
5
19
 
6
20
  import ProfileShareButton from './components/ProfileShareButton';
7
21
  import ProfileIcicleGraph from './ProfileIcicleGraph';
8
22
  import {ProfileSource} from './ProfileSource';
9
- import {useQuery} from './useQuery';
10
23
  import TopTable from './TopTable';
24
+ import useDelayedLoader from './useDelayedLoader';
11
25
  import {downloadPprof} from './utils';
12
26
 
13
27
  import './ProfileView.styles.css';
14
28
 
15
29
  type NavigateFunction = (path: string, queryParams: any) => void;
16
30
 
31
+ interface FlamegraphData {
32
+ loading: boolean;
33
+ data?: Flamegraph;
34
+ error?: any;
35
+ }
36
+
37
+ interface TopTableData {
38
+ loading: boolean;
39
+ data?: Top;
40
+ error?: any;
41
+ }
42
+
43
+ type VisualizationType = 'icicle' | 'table' | 'both';
44
+
45
+ interface ProfileVisState {
46
+ currentView: VisualizationType;
47
+ setCurrentView: (view: VisualizationType) => void;
48
+ }
49
+
17
50
  interface ProfileViewProps {
18
- queryClient: QueryServiceClient;
19
- profileSource: ProfileSource;
51
+ flamegraphData?: FlamegraphData;
52
+ topTableData?: TopTableData;
53
+ sampleUnit: string;
54
+ profileVisState: ProfileVisState;
55
+ profileSource?: ProfileSource;
56
+ queryClient?: QueryServiceClient;
20
57
  navigateTo?: NavigateFunction;
21
58
  compare?: boolean;
22
59
  }
@@ -29,22 +66,28 @@ function arrayEquals(a, b): boolean {
29
66
  a.every((val, index) => val === b[index])
30
67
  );
31
68
  }
69
+ export const useProfileVisState = (): ProfileVisState => {
70
+ const router = parseParams(window.location.search);
71
+ const currentViewFromURL = router.currentProfileView as string;
72
+ const [currentView, setCurrentView] = useState<VisualizationType>(
73
+ (currentViewFromURL as VisualizationType) || 'icicle'
74
+ );
75
+
76
+ return {currentView, setCurrentView};
77
+ };
32
78
 
33
79
  export const ProfileView = ({
34
- queryClient,
80
+ flamegraphData,
81
+ topTableData,
82
+ sampleUnit,
35
83
  profileSource,
84
+ queryClient,
36
85
  navigateTo,
86
+ profileVisState,
37
87
  }: ProfileViewProps): JSX.Element => {
38
- const router = parseParams(window.location.search);
39
- const currentViewFromURL = router.currentProfileView as string;
40
88
  const [curPath, setCurPath] = useState<string[]>([]);
41
- const [isLoaderVisible, setIsLoaderVisible] = useState<boolean>(false);
42
- const {isLoading, response, error} = useQuery(
43
- queryClient,
44
- profileSource,
45
- QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED
46
- );
47
- const [currentView, setCurrentView] = useState<string | undefined>(currentViewFromURL);
89
+ const {currentView, setCurrentView} = profileVisState;
90
+
48
91
  const metadata = useGrpcMetadata();
49
92
  const {loader} = useParcaTheme();
50
93
 
@@ -53,29 +96,39 @@ export const ProfileView = ({
53
96
  setCurPath([]);
54
97
  }, [profileSource]);
55
98
 
56
- useEffect(() => {
57
- let showLoaderTimeout;
58
- if (isLoading && !isLoaderVisible) {
59
- // if the request takes longer than half a second, show the loading icon
60
- showLoaderTimeout = setTimeout(() => {
61
- setIsLoaderVisible(true);
62
- }, 500);
63
- } else {
64
- setIsLoaderVisible(false);
99
+ const isLoading = useMemo(() => {
100
+ if (currentView === 'icicle') {
101
+ return !!flamegraphData?.loading;
102
+ }
103
+ if (currentView === 'table') {
104
+ return !!topTableData?.loading;
105
+ }
106
+ if (currentView === 'both') {
107
+ return !!flamegraphData?.loading || !!topTableData?.loading;
65
108
  }
66
- return () => clearTimeout(showLoaderTimeout);
67
- }, [isLoading]);
109
+ return false;
110
+ }, [currentView, flamegraphData?.loading, topTableData?.loading]);
111
+
112
+ const isLoaderVisible = useDelayedLoader(isLoading);
68
113
 
69
114
  if (isLoaderVisible) {
70
115
  return <>{loader}</>;
71
116
  }
72
117
 
73
- if (error !== null) {
74
- return <div className="p-10 flex justify-center">An error occurred: {error.message}</div>;
118
+ if (flamegraphData?.error != null) {
119
+ console.error('Error: ', flamegraphData?.error);
120
+ return (
121
+ <div className="p-10 flex justify-center">
122
+ An error occurred: {flamegraphData?.error.message}
123
+ </div>
124
+ );
75
125
  }
76
126
 
77
127
  const downloadPProf = async (e: React.MouseEvent<HTMLElement>) => {
78
128
  e.preventDefault();
129
+ if (!profileSource || !queryClient) {
130
+ return;
131
+ }
79
132
 
80
133
  try {
81
134
  const blob = await downloadPprof(profileSource.QueryRequest(), queryClient, metadata);
@@ -96,16 +149,18 @@ export const ProfileView = ({
96
149
  }
97
150
  };
98
151
 
99
- const switchProfileView = (view: string) => {
152
+ const switchProfileView = (view: VisualizationType) => {
153
+ if (view == null) {
154
+ return;
155
+ }
100
156
  if (navigateTo === undefined) return;
101
157
 
102
158
  setCurrentView(view);
159
+ const router = parseParams(window.location.search);
103
160
 
104
161
  navigateTo('/', {...router, ...{currentProfileView: view}});
105
162
  };
106
163
 
107
- const sampleUnit = profileSource.ProfileType().sampleUnit;
108
-
109
164
  return (
110
165
  <>
111
166
  <div className="py-3">
@@ -114,10 +169,12 @@ export const ProfileView = ({
114
169
  <div className="flex py-3 w-full">
115
170
  <div className="w-2/5 flex space-x-4">
116
171
  <div className="flex space-x-1">
117
- <ProfileShareButton
118
- queryRequest={profileSource.QueryRequest()}
119
- queryClient={queryClient}
120
- />
172
+ {profileSource && queryClient ? (
173
+ <ProfileShareButton
174
+ queryRequest={profileSource.QueryRequest()}
175
+ queryClient={queryClient}
176
+ />
177
+ ) : null}
121
178
 
122
179
  <Button color="neutral" onClick={downloadPProf}>
123
180
  Download pprof
@@ -149,7 +206,7 @@ export const ProfileView = ({
149
206
 
150
207
  <Button
151
208
  variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
152
- 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 no-outline-on-buttons text-ellipsis"
209
+ 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"
153
210
  onClick={() => switchProfileView('both')}
154
211
  >
155
212
  Both
@@ -166,45 +223,35 @@ export const ProfileView = ({
166
223
  </div>
167
224
 
168
225
  <div className="flex space-x-4 justify-between">
169
- {currentView === 'icicle' &&
170
- response !== null &&
171
- response.report.oneofKind === 'flamegraph' && (
172
- <div className="w-full">
173
- <ProfileIcicleGraph
174
- curPath={curPath}
175
- setNewCurPath={setNewCurPath}
176
- graph={response.report.flamegraph}
177
- sampleUnit={sampleUnit}
178
- />
179
- </div>
180
- )}
181
-
182
- {currentView === 'table' && (
226
+ {currentView === 'icicle' && flamegraphData?.data != null && (
183
227
  <div className="w-full">
184
- <TopTable
185
- queryClient={queryClient}
186
- profileSource={profileSource}
228
+ <ProfileIcicleGraph
229
+ curPath={curPath}
230
+ setNewCurPath={setNewCurPath}
231
+ graph={flamegraphData.data}
187
232
  sampleUnit={sampleUnit}
188
233
  />
189
234
  </div>
190
235
  )}
191
236
 
237
+ {currentView === 'table' && topTableData != null && (
238
+ <div className="w-full">
239
+ <TopTable data={topTableData.data} sampleUnit={sampleUnit} />
240
+ </div>
241
+ )}
242
+
192
243
  {currentView === 'both' && (
193
244
  <>
194
245
  <div className="w-1/2">
195
- <TopTable
196
- queryClient={queryClient}
197
- profileSource={profileSource}
198
- sampleUnit={sampleUnit}
199
- />
246
+ <TopTable data={topTableData?.data} sampleUnit={sampleUnit} />
200
247
  </div>
201
248
 
202
249
  <div className="w-1/2">
203
- {response !== null && response.report.oneofKind === 'flamegraph' && (
250
+ {flamegraphData != null && (
204
251
  <ProfileIcicleGraph
205
252
  curPath={curPath}
206
253
  setNewCurPath={setNewCurPath}
207
- graph={response.report.flamegraph}
254
+ graph={flamegraphData.data}
208
255
  sampleUnit={sampleUnit}
209
256
  />
210
257
  )}
@@ -0,0 +1,79 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
15
+
16
+ import {useQuery} from './useQuery';
17
+ import {ProfileView, useProfileVisState} from './ProfileView';
18
+ import {ProfileSource} from './ProfileSource';
19
+
20
+ type NavigateFunction = (path: string, queryParams: any) => void;
21
+
22
+ interface ProfileViewWithDataProps {
23
+ queryClient: QueryServiceClient;
24
+ profileSource: ProfileSource;
25
+ navigateTo?: NavigateFunction;
26
+ compare?: boolean;
27
+ }
28
+
29
+ export const ProfileViewWithData = ({
30
+ queryClient,
31
+ profileSource,
32
+ navigateTo,
33
+ }: ProfileViewWithDataProps) => {
34
+ const profileVisState = useProfileVisState();
35
+ const {currentView} = profileVisState;
36
+ const {
37
+ isLoading: flamegraphLoading,
38
+ response: flamegraphResponse,
39
+ error: flamegraphError,
40
+ } = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED, {
41
+ skip: currentView != 'icicle' && currentView != 'both',
42
+ });
43
+
44
+ const {
45
+ isLoading: topTableLoading,
46
+ response: topTableResponse,
47
+ error: topTableError,
48
+ } = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
49
+ skip: currentView != 'table' && currentView != 'both',
50
+ });
51
+
52
+ const sampleUnit = profileSource.ProfileType().sampleUnit;
53
+
54
+ return (
55
+ <ProfileView
56
+ flamegraphData={{
57
+ loading: flamegraphLoading,
58
+ data:
59
+ flamegraphResponse?.report.oneofKind === 'flamegraph'
60
+ ? flamegraphResponse?.report?.flamegraph
61
+ : undefined,
62
+ error: flamegraphError,
63
+ }}
64
+ topTableData={{
65
+ loading: topTableLoading,
66
+ data:
67
+ topTableResponse?.report.oneofKind === 'top' ? topTableResponse.report.top : undefined,
68
+ error: topTableError,
69
+ }}
70
+ profileVisState={profileVisState}
71
+ sampleUnit={sampleUnit}
72
+ profileSource={profileSource}
73
+ queryClient={queryClient}
74
+ navigateTo={navigateTo}
75
+ />
76
+ );
77
+ };
78
+
79
+ export default ProfileViewWithData;
package/src/TopTable.tsx CHANGED
@@ -1,21 +1,28 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import React from 'react';
2
- import {getLastItem, valueFormatter, isSearchMatch, SEARCH_STRING_COLOR} from '@parca/functions';
15
+
16
+ import {getLastItem, valueFormatter, isSearchMatch} from '@parca/functions';
3
17
  import {useAppSelector, selectCompareMode, selectSearchNodeString} from '@parca/store';
4
- import {
5
- QueryResponse,
6
- QueryServiceClient,
7
- QueryRequest_ReportType,
8
- TopNodeMeta,
9
- } from '@parca/client';
10
- import {ProfileSource} from './ProfileSource';
11
- import {useQuery} from './useQuery';
18
+ import {TopNodeMeta, Top} from '@parca/client';
19
+
12
20
  import {hexifyAddress} from './utils';
13
21
 
14
22
  import './TopTable.styles.css';
15
23
 
16
- interface ProfileViewProps {
17
- queryClient: QueryServiceClient;
18
- profileSource: ProfileSource;
24
+ interface TopTableProps {
25
+ data?: Top;
19
26
  sampleUnit: string;
20
27
  }
21
28
 
@@ -29,21 +36,17 @@ const Arrow = ({direction}: {direction: string | undefined}) => {
29
36
  width="11"
30
37
  xmlns="http://www.w3.org/2000/svg"
31
38
  >
32
- <path clip-rule="evenodd" d="m.573997 0 5.000003 10 5-10h-9.999847z" fill-rule="evenodd" />
39
+ <path clipRule="evenodd" d="m.573997 0 5.000003 10 5-10h-9.999847z" fillRule="evenodd" />
33
40
  </svg>
34
41
  );
35
42
  };
36
43
 
37
- const useSortableData = (
38
- response: QueryResponse | null,
39
- config = {key: 'cumulative', direction: 'desc'}
40
- ) => {
44
+ const useSortableData = (top?: Top, config = {key: 'cumulative', direction: 'desc'}) => {
41
45
  const [sortConfig, setSortConfig] = React.useState<{key: string; direction: string} | null>(
42
46
  config
43
47
  );
44
48
 
45
- const rawTableReport =
46
- response !== null && response.report.oneofKind === 'top' ? response.report.top.list : [];
49
+ const rawTableReport = top ? top.list : [];
47
50
 
48
51
  const items = rawTableReport.map(node => ({
49
52
  ...node,
@@ -98,25 +101,15 @@ export const RowLabel = (meta: TopNodeMeta | undefined): string => {
98
101
  return fallback === '' ? '<unknown>' : fallback;
99
102
  };
100
103
 
101
- export const TopTable = ({
102
- queryClient,
103
- profileSource,
104
- sampleUnit,
105
- }: ProfileViewProps): JSX.Element => {
106
- const {response, error} = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP);
107
- const {items, requestSort, sortConfig} = useSortableData(response);
104
+ export const TopTable = ({data: top, sampleUnit}: TopTableProps): JSX.Element => {
105
+ const {items, requestSort, sortConfig} = useSortableData(top);
108
106
  const currentSearchString = useAppSelector(selectSearchNodeString);
109
107
 
110
108
  const compareMode = useAppSelector(selectCompareMode);
111
109
 
112
110
  const unit = sampleUnit;
113
111
 
114
- if (error != null) {
115
- return <div className="p-10 flex justify-center">An error occurred: {error.message}</div>;
116
- }
117
-
118
- const total =
119
- response !== null && response.report.oneofKind === 'top' ? response.report.top.list.length : 0;
112
+ const total = top ? top.list.length : 0;
120
113
  if (total === 0) return <>Profile has no samples</>;
121
114
 
122
115
  const getClassNamesFor = name => {
@@ -1,3 +1,16 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import {SuffixParams, ParseLabels} from '../ProfileSource';
2
15
 
3
16
  test('prefixes keys', () => {
@@ -1,3 +1,16 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import {Fragment, useState} from 'react';
2
15
  import {Popover, Transition} from '@headlessui/react';
3
16
  import {useAppSelector, selectDarkMode} from '@parca/store';
@@ -1,3 +1,16 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import {useState} from 'react';
2
15
  import cx from 'classnames';
3
16
  import {Icon} from '@iconify/react';
@@ -1,3 +1,16 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import {useState} from 'react';
2
15
  import {Button, Modal, useGrpcMetadata} from '@parca/components';
3
16
  import {Icon} from '@iconify/react';
package/src/index.tsx CHANGED
@@ -1,5 +1,19 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  export * from './IcicleGraph';
2
15
  export * from './ProfileIcicleGraph';
3
16
  export * from './ProfileSource';
4
17
  export * from './ProfileView';
18
+ export * from './ProfileViewWithData';
5
19
  export * from './utils';
@@ -0,0 +1,39 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {useEffect, useState} from 'react';
15
+
16
+ interface DelayedLoaderOptions {
17
+ delay?: number;
18
+ }
19
+
20
+ const useDelayedLoader = (isLoading = false, options?: DelayedLoaderOptions) => {
21
+ const {delay = 500} = options || {};
22
+ const [isLoaderVisible, setIsLoaderVisible] = useState<boolean>(false);
23
+ useEffect(() => {
24
+ let showLoaderTimeout;
25
+ if (isLoading && !isLoaderVisible) {
26
+ // if the request takes longer than half a second, show the loading icon
27
+ showLoaderTimeout = setTimeout(() => {
28
+ setIsLoaderVisible(true);
29
+ }, delay);
30
+ } else {
31
+ setIsLoaderVisible(false);
32
+ }
33
+ return () => clearTimeout(showLoaderTimeout);
34
+ }, [isLoading]);
35
+
36
+ return isLoaderVisible;
37
+ };
38
+
39
+ export default useDelayedLoader;
package/src/useQuery.tsx CHANGED
@@ -1,7 +1,22 @@
1
- import React, {useEffect, useState} from 'react';
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
14
+ import {useEffect, useState} from 'react';
15
+
2
16
  import {QueryServiceClient, QueryResponse, QueryRequest_ReportType} from '@parca/client';
3
17
  import {RpcError} from '@protobuf-ts/runtime-rpc';
4
18
  import {useGrpcMetadata} from '@parca/components';
19
+
5
20
  import {ProfileSource} from './ProfileSource';
6
21
 
7
22
  export interface IQueryResult {
@@ -10,11 +25,17 @@ export interface IQueryResult {
10
25
  isLoading: boolean;
11
26
  }
12
27
 
28
+ interface UseQueryOptions {
29
+ skip?: boolean;
30
+ }
31
+
13
32
  export const useQuery = (
14
33
  client: QueryServiceClient,
15
34
  profileSource: ProfileSource,
16
- reportType: QueryRequest_ReportType
35
+ reportType: QueryRequest_ReportType,
36
+ options?: UseQueryOptions
17
37
  ): IQueryResult => {
38
+ const {skip = false} = options || {};
18
39
  const [result, setResult] = useState<IQueryResult>({
19
40
  response: null,
20
41
  error: null,
@@ -23,6 +44,9 @@ export const useQuery = (
23
44
  const metadata = useGrpcMetadata();
24
45
 
25
46
  useEffect(() => {
47
+ if (skip) {
48
+ return;
49
+ }
26
50
  setResult({
27
51
  response: null,
28
52
  error: null,
package/src/utils.ts CHANGED
@@ -1,3 +1,16 @@
1
+ // Copyright 2022 The Parca Authors
2
+ // Licensed under the Apache License, Version 2.0 (the "License");
3
+ // you may not use this file except in compliance with the License.
4
+ // You may obtain a copy of the License at
5
+ //
6
+ // http://www.apache.org/licenses/LICENSE-2.0
7
+ //
8
+ // Unless required by applicable law or agreed to in writing, software
9
+ // distributed under the License is distributed on an "AS IS" BASIS,
10
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11
+ // See the License for the specific language governing permissions and
12
+ // limitations under the License.
13
+
1
14
  import {QueryRequest, QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
2
15
  import {RpcMetadata} from '@protobuf-ts/runtime-rpc';
3
16