@parca/profile 0.16.0 → 0.16.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/Callgraph/Edge/index.d.ts +22 -0
  3. package/dist/Callgraph/Edge/index.js +30 -0
  4. package/dist/Callgraph/Node/index.d.ts +19 -0
  5. package/dist/Callgraph/Node/index.js +37 -0
  6. package/dist/Callgraph/index.d.ts +8 -0
  7. package/dist/Callgraph/index.js +137 -0
  8. package/dist/Callgraph/mockData/index.d.ts +148 -0
  9. package/dist/Callgraph/mockData/index.js +577 -0
  10. package/dist/Callgraph/utils.d.ts +19 -0
  11. package/dist/Callgraph/utils.js +82 -0
  12. package/dist/GraphTooltip/index.d.ts +19 -0
  13. package/dist/GraphTooltip/index.js +119 -0
  14. package/dist/IcicleGraph.d.ts +35 -0
  15. package/dist/IcicleGraph.js +139 -0
  16. package/dist/MatchersInput/index.d.ts +23 -0
  17. package/dist/MatchersInput/index.js +479 -0
  18. package/dist/MetricsCircle/index.d.ts +7 -0
  19. package/dist/MetricsCircle/index.js +18 -0
  20. package/dist/MetricsGraph/index.d.ts +35 -0
  21. package/dist/MetricsGraph/index.js +349 -0
  22. package/dist/MetricsSeries/index.d.ts +11 -0
  23. package/dist/MetricsSeries/index.js +21 -0
  24. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +19 -0
  25. package/dist/ProfileExplorer/ProfileExplorerCompare.js +38 -0
  26. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +15 -0
  27. package/dist/ProfileExplorer/ProfileExplorerSingle.js +19 -0
  28. package/dist/ProfileExplorer/index.d.ts +9 -0
  29. package/dist/ProfileExplorer/index.js +203 -0
  30. package/dist/ProfileIcicleGraph.d.ts +10 -0
  31. package/dist/ProfileIcicleGraph.js +28 -0
  32. package/dist/ProfileMetricsGraph/index.d.ts +22 -0
  33. package/dist/ProfileMetricsGraph/index.js +127 -0
  34. package/dist/ProfileSVG.module.css +3 -0
  35. package/dist/ProfileSelector/CompareButton.d.ts +5 -0
  36. package/dist/ProfileSelector/CompareButton.js +41 -0
  37. package/dist/ProfileSelector/MergeButton.d.ts +5 -0
  38. package/dist/ProfileSelector/MergeButton.js +41 -0
  39. package/dist/ProfileSelector/index.d.ts +29 -0
  40. package/dist/ProfileSelector/index.js +133 -0
  41. package/dist/ProfileSource.d.ts +88 -0
  42. package/dist/ProfileSource.js +239 -0
  43. package/dist/ProfileTypeSelector/index.d.ts +20 -0
  44. package/dist/ProfileTypeSelector/index.js +138 -0
  45. package/dist/ProfileView.d.ts +39 -0
  46. package/dist/ProfileView.js +111 -0
  47. package/dist/ProfileView.styles.css +3 -0
  48. package/dist/ProfileViewWithData.d.ts +11 -0
  49. package/dist/ProfileViewWithData.js +116 -0
  50. package/dist/TopTable.d.ts +9 -0
  51. package/dist/TopTable.js +140 -0
  52. package/dist/TopTable.styles.css +7 -0
  53. package/dist/components/DiffLegend.d.ts +2 -0
  54. package/dist/components/DiffLegend.js +62 -0
  55. package/dist/components/ProfileShareButton/ResultBox.d.ts +6 -0
  56. package/dist/components/ProfileShareButton/ResultBox.js +46 -0
  57. package/dist/components/ProfileShareButton/index.d.ts +7 -0
  58. package/dist/components/ProfileShareButton/index.js +119 -0
  59. package/dist/index.d.ts +13 -0
  60. package/dist/index.js +64 -0
  61. package/dist/styles.css +1 -0
  62. package/dist/useDelayedLoader.d.ts +5 -0
  63. package/dist/useDelayedLoader.js +33 -0
  64. package/dist/useQuery.d.ts +13 -0
  65. package/dist/useQuery.js +41 -0
  66. package/dist/utils.d.ts +4 -0
  67. package/dist/utils.js +83 -0
  68. package/package.json +12 -8
  69. package/src/Callgraph/Edge/index.tsx +59 -0
  70. package/src/Callgraph/Node/index.tsx +66 -0
  71. package/src/Callgraph/index.tsx +169 -0
  72. package/src/Callgraph/mockData/index.ts +605 -0
  73. package/src/Callgraph/utils.ts +116 -0
  74. package/src/GraphTooltip/index.tsx +245 -0
  75. package/src/IcicleGraph.tsx +3 -3
  76. package/src/MatchersInput/index.tsx +698 -0
  77. package/src/MetricsCircle/index.tsx +28 -0
  78. package/src/MetricsGraph/index.tsx +614 -0
  79. package/src/MetricsSeries/index.tsx +38 -0
  80. package/src/ProfileExplorer/ProfileExplorerCompare.tsx +109 -0
  81. package/src/ProfileExplorer/ProfileExplorerSingle.tsx +72 -0
  82. package/src/ProfileExplorer/index.tsx +377 -0
  83. package/src/ProfileMetricsGraph/index.tsx +143 -0
  84. package/src/ProfileSelector/CompareButton.tsx +72 -0
  85. package/src/ProfileSelector/MergeButton.tsx +72 -0
  86. package/src/ProfileSelector/index.tsx +270 -0
  87. package/src/ProfileTypeSelector/index.tsx +180 -0
  88. package/src/ProfileView.tsx +2 -7
  89. package/src/index.tsx +11 -0
  90. package/src/useQuery.tsx +1 -0
  91. package/tailwind.config.js +8 -0
  92. package/tsconfig.json +7 -3
  93. package/typings.d.ts +14 -0
@@ -0,0 +1,72 @@
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 {useState} from 'react';
15
+ import {usePopper} from 'react-popper';
16
+ import {Button} from '@parca/components';
17
+
18
+ const CompareButton = ({
19
+ disabled,
20
+ onClick,
21
+ }: {
22
+ disabled: boolean;
23
+ onClick: () => void;
24
+ }): JSX.Element => {
25
+ const [compareHover, setCompareHover] = useState<boolean>(false);
26
+ const [comparePopperReferenceElement, setComparePopperReferenceElement] =
27
+ useState<HTMLDivElement | null>(null);
28
+ const [comparePopperElement, setComparePopperElement] = useState<HTMLDivElement | null>(null);
29
+ const {styles, attributes} = usePopper(comparePopperReferenceElement, comparePopperElement, {
30
+ placement: 'bottom',
31
+ });
32
+
33
+ const compareExplanation =
34
+ 'Compare two profiles and see the relative difference between them more clearly.';
35
+
36
+ if (disabled) return <></>;
37
+
38
+ return (
39
+ <div ref={setComparePopperReferenceElement}>
40
+ <Button
41
+ color="neutral"
42
+ disabled={disabled}
43
+ onClick={onClick}
44
+ onMouseEnter={() => setCompareHover(true)}
45
+ onMouseLeave={() => setCompareHover(false)}
46
+ >
47
+ Compare
48
+ </Button>
49
+ {compareHover && (
50
+ <div
51
+ ref={setComparePopperElement}
52
+ style={styles.popper}
53
+ {...attributes.popper}
54
+ className="z-50"
55
+ >
56
+ <div className="flex">
57
+ <div className="relative mx-2">
58
+ <svg className="text-black h-1 w-full left-0" x="0px" y="0px" viewBox="0 0 255 127.5">
59
+ <polygon className="fill-current" points="0,127.5 127.5,0 255,127.5" />
60
+ </svg>
61
+ <div className="bg-black text-white text-xs rounded py-2 px-3 right-0 w-40">
62
+ {compareExplanation}
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ )}
68
+ </div>
69
+ );
70
+ };
71
+
72
+ export default CompareButton;
@@ -0,0 +1,72 @@
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 {useState} from 'react';
15
+ import {usePopper} from 'react-popper';
16
+ import {Button} from '@parca/components';
17
+
18
+ const MergeButton = ({
19
+ disabled,
20
+ onClick,
21
+ }: {
22
+ disabled: boolean;
23
+ onClick: () => void;
24
+ }): JSX.Element => {
25
+ const [mergeHover, setMergeHover] = useState<boolean>(false);
26
+ const [mergePopperReferenceElement, setMergePopperReferenceElement] =
27
+ useState<HTMLDivElement | null>(null);
28
+ const [mergePopperElement, setMergePopperElement] = useState<HTMLDivElement | null>(null);
29
+ const {styles, attributes} = usePopper(mergePopperReferenceElement, mergePopperElement, {
30
+ placement: 'bottom',
31
+ });
32
+
33
+ const mergeExplanation =
34
+ 'Merging allows combining all profile samples of a query into a single report.';
35
+
36
+ if (disabled) return <></>;
37
+
38
+ return (
39
+ <div ref={setMergePopperReferenceElement}>
40
+ <Button
41
+ color="neutral"
42
+ disabled={disabled}
43
+ onClick={onClick}
44
+ onMouseEnter={() => setMergeHover(true)}
45
+ onMouseLeave={() => setMergeHover(false)}
46
+ >
47
+ Merge
48
+ </Button>
49
+ {mergeHover && (
50
+ <div
51
+ ref={setMergePopperElement}
52
+ style={styles.popper}
53
+ {...attributes.popper}
54
+ className="z-50"
55
+ >
56
+ <div className="flex">
57
+ <div className="relative mx-2">
58
+ <svg className="text-black h-1 w-full left-0" x="0px" y="0px" viewBox="0 0 255 127.5">
59
+ <polygon className="fill-current" points="0,127.5 127.5,0 255,127.5" />
60
+ </svg>
61
+ <div className="bg-black text-white text-xs rounded py-2 px-3 right-0 w-40 z-50">
62
+ {mergeExplanation}
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+ )}
68
+ </div>
69
+ );
70
+ };
71
+
72
+ export default MergeButton;
@@ -0,0 +1,270 @@
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 {Query} from '@parca/parser';
15
+ import {QueryServiceClient, ProfileTypesResponse} from '@parca/client';
16
+ import {RpcError} from '@protobuf-ts/runtime-rpc';
17
+ import {ProfileSelection} from '..';
18
+ import React, {useEffect, useState} from 'react';
19
+ import ProfileMetricsGraph from '../ProfileMetricsGraph';
20
+ import MatchersInput from '../MatchersInput/index';
21
+ import MergeButton from './MergeButton';
22
+ import CompareButton from './CompareButton';
23
+ import {
24
+ Card,
25
+ DateTimeRangePicker,
26
+ DateTimeRange,
27
+ Button,
28
+ ButtonGroup,
29
+ useGrpcMetadata,
30
+ } from '@parca/components';
31
+ import {CloseIcon} from '@parca/icons';
32
+ import cx from 'classnames';
33
+ import ProfileTypeSelector from '../ProfileTypeSelector/index';
34
+
35
+ export interface QuerySelection {
36
+ expression: string;
37
+ from: number;
38
+ to: number;
39
+ merge: boolean;
40
+ timeSelection: string;
41
+ }
42
+
43
+ interface ProfileSelectorProps {
44
+ queryClient: QueryServiceClient;
45
+ querySelection: QuerySelection;
46
+ selectProfile: (source: ProfileSelection) => void;
47
+ selectQuery: (query: QuerySelection) => void;
48
+ closeProfile: () => void;
49
+ enforcedProfileName: string;
50
+ profileSelection: ProfileSelection | null;
51
+ comparing: boolean;
52
+ onCompareProfile: () => void;
53
+ }
54
+
55
+ export interface IProfileTypesResult {
56
+ loading: boolean;
57
+ data?: ProfileTypesResponse;
58
+ error?: RpcError;
59
+ }
60
+
61
+ export const useProfileTypes = (client: QueryServiceClient): IProfileTypesResult => {
62
+ const [result, setResult] = useState<ProfileTypesResponse | undefined>(undefined);
63
+ const [error, setError] = useState<RpcError | undefined>(undefined);
64
+ const [loading, setLoading] = useState(true);
65
+ const metadata = useGrpcMetadata();
66
+
67
+ useEffect(() => {
68
+ if (!loading) {
69
+ return;
70
+ }
71
+ const call = client.profileTypes({}, {meta: metadata});
72
+ call.response
73
+ .then(response => setResult(response))
74
+ .catch(error => setError(error))
75
+ .finally(() => setLoading(false));
76
+ }, [client, metadata, loading]);
77
+
78
+ return {loading, data: result, error};
79
+ };
80
+
81
+ const ProfileSelector = ({
82
+ queryClient,
83
+ querySelection,
84
+ selectProfile,
85
+ selectQuery,
86
+ closeProfile,
87
+ enforcedProfileName,
88
+ profileSelection,
89
+ comparing,
90
+ onCompareProfile,
91
+ }: ProfileSelectorProps): JSX.Element => {
92
+ const {
93
+ loading: profileTypesLoading,
94
+ data: profileTypesData,
95
+ error,
96
+ } = useProfileTypes(queryClient);
97
+
98
+ const [timeRangeSelection, setTimeRangeSelection] = useState(
99
+ DateTimeRange.fromRangeKey(querySelection.timeSelection)
100
+ );
101
+ const [queryExpressionString, setQueryExpressionString] = useState(querySelection.expression);
102
+
103
+ useEffect(() => {
104
+ if (enforcedProfileName !== '') {
105
+ const [q, changed] = Query.parse(querySelection.expression).setProfileName(
106
+ enforcedProfileName
107
+ );
108
+ if (changed) {
109
+ setQueryExpressionString(q.toString());
110
+ return;
111
+ }
112
+ }
113
+ setQueryExpressionString(querySelection.expression);
114
+ }, [enforcedProfileName, querySelection.expression]);
115
+
116
+ const enforcedProfileNameQuery = (): Query => {
117
+ const pq = Query.parse(queryExpressionString);
118
+ const [q] = pq.setProfileName(enforcedProfileName);
119
+ return q;
120
+ };
121
+
122
+ const query =
123
+ enforcedProfileName !== '' ? enforcedProfileNameQuery() : Query.parse(queryExpressionString);
124
+ const selectedProfileName = query.profileName();
125
+
126
+ const setNewQueryExpression = (expr: string, merge: boolean): void => {
127
+ selectQuery({
128
+ expression: expr,
129
+ from: timeRangeSelection.getFromMs(),
130
+ to: timeRangeSelection.getToMs(),
131
+ merge,
132
+ timeSelection: timeRangeSelection.getRangeKey(),
133
+ });
134
+ };
135
+
136
+ const setQueryExpression = (): void => {
137
+ setNewQueryExpression(query.toString(), false);
138
+ };
139
+
140
+ const addLabelMatcher = (key: string, value: string): void => {
141
+ const [newQuery, changed] = Query.parse(queryExpressionString).setMatcher(key, value);
142
+ if (changed) {
143
+ setNewQueryExpression(newQuery.toString(), false);
144
+ }
145
+ };
146
+
147
+ const setMergedSelection = (): void => {
148
+ setNewQueryExpression(queryExpressionString, true);
149
+ };
150
+
151
+ const setMatchersString = (matchers: string): void => {
152
+ const newExpressionString = `${selectedProfileName}{${matchers}}`;
153
+ setQueryExpressionString(newExpressionString);
154
+ };
155
+
156
+ const setProfileName = (profileName: string | undefined): void => {
157
+ if (profileName === undefined) {
158
+ return;
159
+ }
160
+ const [newQuery, changed] = query.setProfileName(profileName);
161
+ if (changed) {
162
+ const q = newQuery.toString();
163
+ setQueryExpressionString(q);
164
+ }
165
+ };
166
+
167
+ const handleCompareClick = (): void => onCompareProfile();
168
+
169
+ const searchDisabled =
170
+ queryExpressionString === undefined ||
171
+ queryExpressionString === '' ||
172
+ queryExpressionString === '{}';
173
+
174
+ const mergeDisabled = selectedProfileName === '' || querySelection.expression === undefined;
175
+ const compareDisabled = selectedProfileName === '' || querySelection.expression === undefined;
176
+
177
+ return (
178
+ <Card>
179
+ <Card.Header className={cx(comparing && 'overflow-x-scroll')}>
180
+ <div className="flex space-x-4">
181
+ {comparing && (
182
+ <button type="button" onClick={() => closeProfile()}>
183
+ <CloseIcon />
184
+ </button>
185
+ )}
186
+ <ProfileTypeSelector
187
+ profileTypesData={profileTypesData}
188
+ loading={profileTypesLoading}
189
+ selectedKey={selectedProfileName}
190
+ onSelection={setProfileName}
191
+ error={error}
192
+ />
193
+ <MatchersInput
194
+ queryClient={queryClient}
195
+ setMatchersString={setMatchersString}
196
+ runQuery={setQueryExpression}
197
+ currentQuery={query}
198
+ />
199
+ <DateTimeRangePicker
200
+ onRangeSelection={setTimeRangeSelection}
201
+ range={timeRangeSelection}
202
+ />
203
+ {searchDisabled ? (
204
+ <div>
205
+ <Button disabled={true}>Search</Button>
206
+ </div>
207
+ ) : (
208
+ <>
209
+ <ButtonGroup style={{marginRight: 5}}>
210
+ <MergeButton disabled={mergeDisabled} onClick={setMergedSelection} />
211
+ {!comparing && (
212
+ <CompareButton disabled={compareDisabled} onClick={handleCompareClick} />
213
+ )}
214
+ </ButtonGroup>
215
+ <div>
216
+ <Button
217
+ onClick={(e: React.MouseEvent<HTMLElement>) => {
218
+ e.preventDefault();
219
+ setQueryExpression();
220
+ }}
221
+ >
222
+ Search
223
+ </Button>
224
+ </div>
225
+ </>
226
+ )}
227
+ </div>
228
+ </Card.Header>
229
+ {!querySelection.merge && (
230
+ <Card.Body>
231
+ {querySelection.expression !== undefined &&
232
+ querySelection.expression.length > 0 &&
233
+ querySelection.from !== undefined &&
234
+ querySelection.to !== undefined &&
235
+ (profileSelection == null || profileSelection.Type() !== 'merge') ? (
236
+ <ProfileMetricsGraph
237
+ queryClient={queryClient}
238
+ queryExpression={querySelection.expression}
239
+ from={querySelection.from}
240
+ to={querySelection.to}
241
+ select={selectProfile}
242
+ profile={profileSelection}
243
+ setTimeRange={(range: DateTimeRange) => {
244
+ setTimeRangeSelection(range);
245
+ selectQuery({
246
+ expression: queryExpressionString,
247
+ from: range.getFromMs(),
248
+ to: range.getToMs(),
249
+ merge: false,
250
+ timeSelection: range.getRangeKey(),
251
+ });
252
+ }}
253
+ addLabelMatcher={addLabelMatcher}
254
+ />
255
+ ) : (
256
+ <>
257
+ {(profileSelection == null || profileSelection.Type() !== 'merge') && (
258
+ <div className="my-20 text-center">
259
+ <p>Run a query, and the result will be displayed here.</p>
260
+ </div>
261
+ )}
262
+ </>
263
+ )}
264
+ </Card.Body>
265
+ )}
266
+ </Card>
267
+ );
268
+ };
269
+
270
+ export default ProfileSelector;
@@ -0,0 +1,180 @@
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, {useMemo} from 'react';
15
+ import {ProfileTypesResponse} from '@parca/client';
16
+ import {RpcError} from '@protobuf-ts/runtime-rpc';
17
+ import {SelectElement, Select} from '@parca/components';
18
+
19
+ interface WellKnownProfile {
20
+ name: string;
21
+ help: string;
22
+ }
23
+
24
+ interface WellKnownProfiles {
25
+ [key: string]: WellKnownProfile;
26
+ }
27
+
28
+ export const wellKnownProfiles: WellKnownProfiles = {
29
+ 'block:contentions:count:contentions:count': {
30
+ name: 'Block Contentions Total',
31
+ help: 'Stack traces that led to blocking on synchronization primitives.',
32
+ },
33
+ 'block:delay:nanoseconds:contentions:count': {
34
+ name: 'Block Contention Time Total',
35
+ help: 'Time delayed stack traces caused by blocking on synchronization primitives.',
36
+ },
37
+ // Unfortunately, fgprof does not set the period type and unit.
38
+ 'fgprof:samples:count::': {
39
+ name: 'Fgprof Samples Total',
40
+ help: 'CPU profile samples observed regardless of their current On/Off CPU scheduling status',
41
+ },
42
+ // Unfortunately, fgprof does not set the period type and unit.
43
+ 'fgprof:time:nanoseconds::': {
44
+ name: 'Fgprof Samples Time Total',
45
+ help: 'CPU profile measured regardless of their current On/Off CPU scheduling status in nanoseconds',
46
+ },
47
+ 'goroutine:goroutine:count:goroutine:count': {
48
+ name: 'Goroutine Created Total',
49
+ help: 'Stack traces that created all current goroutines.',
50
+ },
51
+ 'memory:alloc_objects:count:space:bytes': {
52
+ name: 'Memory Allocated Objects Total',
53
+ help: 'A sampling of all past memory allocations by objects.',
54
+ },
55
+ 'memory:alloc_space:bytes:space:bytes': {
56
+ name: 'Memory Allocated Bytes Total',
57
+ help: 'A sampling of all past memory allocations in bytes.',
58
+ },
59
+ 'memory:alloc_objects:count:space:bytes:delta': {
60
+ name: 'Memory Allocated Objects Delta',
61
+ help: 'A sampling of all memory allocations during the observation by objects.',
62
+ },
63
+ 'memory:alloc_space:bytes:space:bytes:delta': {
64
+ name: 'Memory Allocated Bytes Delta',
65
+ help: 'A sampling of all memory allocations during the observation in bytes.',
66
+ },
67
+ 'memory:inuse_objects:count:space:bytes': {
68
+ name: 'Memory In-Use Objects',
69
+ help: 'A sampling of memory allocations of live objects by objects.',
70
+ },
71
+ 'memory:inuse_space:bytes:space:bytes': {
72
+ name: 'Memory In-Use Bytes',
73
+ help: 'A sampling of memory allocations of live objects by bytes.',
74
+ },
75
+ 'mutex:contentions:count:contentions:count': {
76
+ name: 'Mutex Contentions Total',
77
+ help: 'Stack traces of holders of contended mutexes.',
78
+ },
79
+ 'mutex:delay:nanoseconds:contentions:count': {
80
+ name: 'Mutex Contention Time Total',
81
+ help: 'Time delayed stack traces caused by contended mutexes.',
82
+ },
83
+ 'process_cpu:cpu:nanoseconds:cpu:nanoseconds:delta': {
84
+ name: 'Process CPU Nanoseconds',
85
+ help: 'CPU profile measured by the process itself in nanoseconds.',
86
+ },
87
+ 'process_cpu:samples:count:cpu:nanoseconds:delta': {
88
+ name: 'Process CPU Samples',
89
+ help: 'CPU profile samples observed by the process itself.',
90
+ },
91
+ 'parca_agent_cpu:samples:count:cpu:nanoseconds:delta': {
92
+ name: 'CPU Samples',
93
+ help: 'CPU profile samples observed by Parca Agent.',
94
+ },
95
+ };
96
+
97
+ function flexibleWellKnownProfileMatching(name: string): WellKnownProfile | undefined {
98
+ const prefixExcludedName = name.split(':').slice(1).join(':');
99
+ const requiredKey = Object.keys(wellKnownProfiles).find(key => {
100
+ if (key.endsWith(prefixExcludedName)) {
101
+ return true;
102
+ }
103
+ return false;
104
+ });
105
+ return requiredKey != null ? wellKnownProfiles[requiredKey] : undefined;
106
+ }
107
+
108
+ function profileSelectElement(
109
+ name: string,
110
+ flexibleKnownProfilesDetection: boolean
111
+ ): SelectElement {
112
+ const wellKnown: WellKnownProfile | undefined = !flexibleKnownProfilesDetection
113
+ ? wellKnownProfiles[name]
114
+ : flexibleWellKnownProfileMatching(name);
115
+ if (wellKnown === undefined) return {active: <>{name}</>, expanded: <>{name}</>};
116
+
117
+ const title = wellKnown.name.replace(/ /g, '\u00a0');
118
+ return {
119
+ active: <>{title}</>,
120
+ expanded: (
121
+ <>
122
+ <span>{title}</span>
123
+ <br />
124
+ <span className="text-xs">{wellKnown.help}</span>
125
+ </>
126
+ ),
127
+ };
128
+ }
129
+
130
+ interface Props {
131
+ profileTypesData?: ProfileTypesResponse;
132
+ loading?: boolean;
133
+ error: RpcError | undefined;
134
+ selectedKey: string | undefined;
135
+ flexibleKnownProfilesDetection?: boolean;
136
+ onSelection: (value: string | undefined) => void;
137
+ }
138
+
139
+ const ProfileTypeSelector = ({
140
+ profileTypesData,
141
+ loading = false,
142
+ error,
143
+ selectedKey,
144
+ onSelection,
145
+ flexibleKnownProfilesDetection = false,
146
+ }: Props): JSX.Element => {
147
+ const profileNames = useMemo(() => {
148
+ return (error === undefined || error == null) &&
149
+ profileTypesData !== undefined &&
150
+ profileTypesData != null
151
+ ? profileTypesData.types
152
+ .map(
153
+ type =>
154
+ `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${
155
+ type.periodUnit
156
+ }${type.delta ? ':delta' : ''}`
157
+ )
158
+ .sort((a: string, b: string): number => {
159
+ return a.localeCompare(b);
160
+ })
161
+ : [];
162
+ }, [profileTypesData, error]);
163
+
164
+ const profileLabels = profileNames.map(name => ({
165
+ key: name,
166
+ element: profileSelectElement(name, flexibleKnownProfilesDetection),
167
+ }));
168
+
169
+ return (
170
+ <Select
171
+ items={profileLabels}
172
+ selectedKey={selectedKey}
173
+ onSelection={onSelection}
174
+ placeholder="Select profile..."
175
+ loading={loading}
176
+ />
177
+ );
178
+ };
179
+
180
+ export default ProfileTypeSelector;
@@ -16,13 +16,8 @@ import React, {useEffect, useMemo, useState} from 'react';
16
16
  import {parseParams} from '@parca/functions';
17
17
  import useUIFeatureFlag from '@parca/functions/useUIFeatureFlag';
18
18
  import {QueryServiceClient, Flamegraph, Top, Callgraph} from '@parca/client';
19
- import {
20
- Button,
21
- Card,
22
- SearchNodes,
23
- useParcaTheme,
24
- Callgraph as CallgraphComponent,
25
- } from '@parca/components';
19
+ import {Button, Card, SearchNodes, useParcaTheme} from '@parca/components';
20
+ import {Callgraph as CallgraphComponent} from './';
26
21
  import {useContainerDimensions} from '@parca/dynamicsize';
27
22
 
28
23
  import ProfileShareButton from './components/ProfileShareButton';
package/src/index.tsx CHANGED
@@ -11,9 +11,20 @@
11
11
  // See the License for the specific language governing permissions and
12
12
  // limitations under the License.
13
13
 
14
+ import React from 'react';
15
+ import type {Props as CallgraphProps} from '@parca/profile/src/Callgraph';
16
+ import ProfileExplorer from './ProfileExplorer';
17
+ import ProfileTypeSelector from './ProfileTypeSelector';
18
+
14
19
  export * from './IcicleGraph';
15
20
  export * from './ProfileIcicleGraph';
16
21
  export * from './ProfileSource';
17
22
  export * from './ProfileView';
18
23
  export * from './ProfileViewWithData';
19
24
  export * from './utils';
25
+
26
+ export type {CallgraphProps};
27
+
28
+ const Callgraph = React.lazy(async () => await import('@parca/profile/src/Callgraph'));
29
+
30
+ export {Callgraph, ProfileExplorer, ProfileTypeSelector};
package/src/useQuery.tsx CHANGED
@@ -53,6 +53,7 @@ export const useQuery = (
53
53
  isLoading: true,
54
54
  });
55
55
  const req = profileSource.QueryRequest();
56
+ console.log('req', req);
56
57
  req.reportType = reportType;
57
58
 
58
59
  const call = client.query(req, {meta: metadata});
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ content: ['./src/**/*.{html,js,jsx,ts,tsx}'],
3
+ darkMode: 'class',
4
+ theme: {
5
+ extend: {},
6
+ },
7
+ plugins: [],
8
+ };
package/tsconfig.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "extends": "../../../config/next.tsconfig.json",
3
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
3
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "typings.d.ts"],
4
4
  "compilerOptions": {
5
- "baseUrl": "./src"
5
+ "baseUrl": "./src",
6
+ "jsx": "react-jsx",
7
+ "outDir": "dist",
8
+ "noEmit": false,
9
+ "declaration": true
6
10
  },
7
- "exclude": ["node_modules"]
11
+ "exclude": ["node_modules", "dist", "dist/**/*.d.ts", "**/*.test.ts"]
8
12
  }
package/typings.d.ts ADDED
@@ -0,0 +1,14 @@
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
+ declare module '*.svg';