@parca/profile 0.16.198 → 0.16.200

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 (71) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/Callgraph/index.d.ts +0 -1
  3. package/dist/GraphTooltip/ExpandOnHoverValue.d.ts +0 -1
  4. package/dist/GraphTooltip/index.d.ts +0 -1
  5. package/dist/GraphTooltipArrow/ExpandOnHoverValue.d.ts +6 -0
  6. package/dist/GraphTooltipArrow/ExpandOnHoverValue.js +4 -0
  7. package/dist/GraphTooltipArrow/index.d.ts +43 -0
  8. package/dist/GraphTooltipArrow/index.js +244 -0
  9. package/dist/MatchersInput/SuggestionItem.d.ts +0 -1
  10. package/dist/MatchersInput/SuggestionsList.d.ts +0 -1
  11. package/dist/MatchersInput/index.d.ts +0 -1
  12. package/dist/MetricsCircle/index.d.ts +0 -1
  13. package/dist/MetricsGraph/MetricsTooltip/index.d.ts +0 -1
  14. package/dist/MetricsGraph/index.d.ts +5 -3
  15. package/dist/MetricsGraph/index.js +3 -8
  16. package/dist/MetricsGraph/useMetricsGraphDimensions.d.ts +9 -0
  17. package/dist/MetricsGraph/useMetricsGraphDimensions.js +29 -0
  18. package/dist/MetricsSeries/index.d.ts +0 -1
  19. package/dist/ProfileExplorer/ProfileExplorerCompare.d.ts +0 -1
  20. package/dist/ProfileExplorer/ProfileExplorerSingle.d.ts +0 -1
  21. package/dist/ProfileExplorer/index.d.ts +0 -1
  22. package/dist/ProfileIcicleGraph/IcicleGraph/ColorStackLegend.d.ts +0 -1
  23. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.d.ts +10 -0
  24. package/dist/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.js +64 -0
  25. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.d.ts +48 -0
  26. package/dist/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.js +150 -0
  27. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.d.ts +23 -0
  28. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +110 -0
  29. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.d.ts +14 -0
  30. package/dist/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.js +26 -0
  31. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.d.ts +4 -0
  32. package/dist/ProfileIcicleGraph/IcicleGraphArrow/utils.js +42 -0
  33. package/dist/ProfileIcicleGraph/index.d.ts +4 -3
  34. package/dist/ProfileIcicleGraph/index.js +7 -5
  35. package/dist/ProfileMetricsGraph/index.d.ts +4 -1
  36. package/dist/ProfileMetricsGraph/index.js +24 -14
  37. package/dist/ProfileSelector/CompareButton.d.ts +0 -1
  38. package/dist/ProfileSelector/index.d.ts +0 -1
  39. package/dist/ProfileSelector/index.js +44 -35
  40. package/dist/ProfileSelector/useAutoQuerySelector.d.ts +9 -0
  41. package/dist/ProfileSelector/useAutoQuerySelector.js +66 -0
  42. package/dist/ProfileSource.d.ts +0 -1
  43. package/dist/ProfileTypeSelector/index.d.ts +1 -1
  44. package/dist/ProfileTypeSelector/index.js +4 -3
  45. package/dist/ProfileView/FilterByFunctionButton.d.ts +0 -1
  46. package/dist/ProfileView/ViewSelector.d.ts +0 -1
  47. package/dist/ProfileView/index.d.ts +2 -1
  48. package/dist/ProfileView/index.js +3 -3
  49. package/dist/ProfileViewWithData.d.ts +0 -1
  50. package/dist/ProfileViewWithData.js +16 -7
  51. package/dist/components/DiffLegend.d.ts +0 -1
  52. package/dist/components/ProfileShareButton/ResultBox.d.ts +0 -1
  53. package/dist/components/ProfileShareButton/index.d.ts +0 -1
  54. package/dist/styles.css +1 -1
  55. package/package.json +6 -5
  56. package/src/GraphTooltipArrow/ExpandOnHoverValue.tsx +30 -0
  57. package/src/GraphTooltipArrow/index.tsx +564 -0
  58. package/src/MetricsGraph/index.tsx +24 -20
  59. package/src/MetricsGraph/useMetricsGraphDimensions.ts +42 -0
  60. package/src/ProfileIcicleGraph/IcicleGraphArrow/ColorStackLegend.tsx +109 -0
  61. package/src/ProfileIcicleGraph/IcicleGraphArrow/IcicleGraphNodes.tsx +327 -0
  62. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +212 -0
  63. package/src/ProfileIcicleGraph/IcicleGraphArrow/useNodeColor.ts +52 -0
  64. package/src/ProfileIcicleGraph/IcicleGraphArrow/utils.ts +51 -0
  65. package/src/ProfileIcicleGraph/index.tsx +34 -14
  66. package/src/ProfileMetricsGraph/index.tsx +38 -20
  67. package/src/ProfileSelector/index.tsx +66 -54
  68. package/src/ProfileSelector/useAutoQuerySelector.ts +87 -0
  69. package/src/ProfileTypeSelector/index.tsx +9 -10
  70. package/src/ProfileView/index.tsx +5 -2
  71. package/src/ProfileViewWithData.tsx +22 -7
@@ -0,0 +1,51 @@
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 {Table} from 'apache-arrow';
15
+
16
+ import {EVERYTHING_ELSE, FEATURE_TYPES, type Feature} from '@parca/store';
17
+ import {getLastItem} from '@parca/utilities';
18
+
19
+ import {hexifyAddress} from '../../utils';
20
+ import {FIELD_FUNCTION_NAME, FIELD_LOCATION_ADDRESS, FIELD_MAPPING_FILE} from './index';
21
+
22
+ export function nodeLabel(table: Table<any>, row: number, showBinaryName: boolean): string {
23
+ const functionName: string | null = table.getChild(FIELD_FUNCTION_NAME)?.get(row);
24
+ if (functionName !== null && functionName !== '') {
25
+ return functionName;
26
+ }
27
+
28
+ let mappingString = '';
29
+ if (showBinaryName) {
30
+ const mappingFile: string | null = table.getChild(FIELD_MAPPING_FILE)?.get(row) ?? '';
31
+ const binary: string | undefined = getLastItem(mappingFile ?? undefined);
32
+ if (binary != null) mappingString = `[${binary}]`;
33
+ }
34
+
35
+ const addressBigInt: bigint = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row);
36
+ const address = hexifyAddress(addressBigInt);
37
+ const fallback = `${mappingString}${address}`;
38
+ return fallback === '' ? '<unknown>' : fallback;
39
+ }
40
+
41
+ export const extractFeature = (mapping: string): Feature => {
42
+ if (mapping.startsWith('runtime') || mapping === 'root') {
43
+ return {name: 'runtime', type: FEATURE_TYPES.Runtime};
44
+ }
45
+
46
+ if (mapping != null && mapping !== '') {
47
+ return {name: mapping, type: FEATURE_TYPES.Binary};
48
+ }
49
+
50
+ return {name: EVERYTHING_ELSE, type: FEATURE_TYPES.Misc};
51
+ };
@@ -13,13 +13,16 @@
13
13
 
14
14
  import {useEffect, useMemo} from 'react';
15
15
 
16
+ import {Table} from 'apache-arrow';
17
+
16
18
  import {Flamegraph} from '@parca/client';
17
19
  import {Button} from '@parca/components';
18
20
  import {useContainerDimensions} from '@parca/hooks';
19
21
  import {divide, selectQueryParam, type NavigateFunction} from '@parca/utilities';
20
22
 
21
23
  import DiffLegend from '../components/DiffLegend';
22
- import {IcicleGraph} from './IcicleGraph';
24
+ import IcicleGraph from './IcicleGraph';
25
+ import IcicleGraphArrow from './IcicleGraphArrow';
23
26
 
24
27
  const numberFormatter = new Intl.NumberFormat('en-US');
25
28
 
@@ -27,7 +30,8 @@ export type ResizeHandler = (width: number, height: number) => void;
27
30
 
28
31
  interface ProfileIcicleGraphProps {
29
32
  width?: number;
30
- graph: Flamegraph | undefined;
33
+ graph?: Flamegraph;
34
+ table?: Table<any>;
31
35
  total: bigint;
32
36
  filtered: bigint;
33
37
  sampleUnit: string;
@@ -40,6 +44,7 @@ interface ProfileIcicleGraphProps {
40
44
 
41
45
  const ProfileIcicleGraph = ({
42
46
  graph,
47
+ table,
43
48
  total,
44
49
  filtered,
45
50
  curPath,
@@ -66,7 +71,8 @@ const ProfileIcicleGraph = ({
66
71
  return ['0', '0', false, '0', '0', false, '0', '0'];
67
72
  }
68
73
 
69
- const trimmed = graph.trimmed;
74
+ // const trimmed = graph.trimmed;
75
+ const trimmed = 0n;
70
76
 
71
77
  const totalUnfiltered = total + filtered;
72
78
  // safeguard against division by zero
@@ -101,7 +107,7 @@ const ProfileIcicleGraph = ({
101
107
  );
102
108
  }, [setNewCurPath, curPath, setActionButtons]);
103
109
 
104
- if (graph === undefined) return <div>no data...</div>;
110
+ if (graph === undefined && table === undefined) return <div>no data...</div>;
105
111
 
106
112
  if (total === 0n && !loading) return <>Profile has no samples</>;
107
113
 
@@ -113,16 +119,30 @@ const ProfileIcicleGraph = ({
113
119
  <div className="relative">
114
120
  {compareMode && <DiffLegend />}
115
121
  <div ref={ref}>
116
- <IcicleGraph
117
- width={dimensions?.width}
118
- graph={graph}
119
- total={total}
120
- filtered={filtered}
121
- curPath={curPath}
122
- setCurPath={setNewCurPath}
123
- sampleUnit={sampleUnit}
124
- navigateTo={navigateTo}
125
- />
122
+ {graph !== undefined && (
123
+ <IcicleGraph
124
+ width={dimensions?.width}
125
+ graph={graph}
126
+ total={total}
127
+ filtered={filtered}
128
+ curPath={curPath}
129
+ setCurPath={setNewCurPath}
130
+ sampleUnit={sampleUnit}
131
+ navigateTo={navigateTo}
132
+ />
133
+ )}
134
+ {table !== undefined && (
135
+ <IcicleGraphArrow
136
+ width={dimensions?.width}
137
+ table={table}
138
+ total={total}
139
+ filtered={filtered}
140
+ curPath={curPath}
141
+ setCurPath={setNewCurPath}
142
+ sampleUnit={sampleUnit}
143
+ navigateTo={navigateTo}
144
+ />
145
+ )}
126
146
  </div>
127
147
  <p className="my-2 text-xs">
128
148
  Showing {totalFormatted}{' '}
@@ -22,8 +22,21 @@ import {getStepDuration} from '@parca/utilities';
22
22
 
23
23
  import {MergedProfileSelection, ProfileSelection} from '..';
24
24
  import MetricsGraph from '../MetricsGraph';
25
+ import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
25
26
  import useDelayedLoader from '../useDelayedLoader';
26
27
 
28
+ interface ProfileMetricsEmptyStateProps {
29
+ message: string;
30
+ }
31
+
32
+ export const ProfileMetricsEmptyState = ({message}: ProfileMetricsEmptyStateProps): JSX.Element => {
33
+ return (
34
+ <div className="flex h-full w-full flex-col items-center justify-center">
35
+ <p>{message}</p>
36
+ </div>
37
+ );
38
+ };
39
+
27
40
  interface ProfileMetricsGraphProps {
28
41
  queryClient: QueryServiceClient;
29
42
  queryExpression: string;
@@ -52,15 +65,12 @@ export const useQueryRange = (
52
65
  isLoading: true,
53
66
  error: null,
54
67
  });
68
+ const [isLoading, setLoading] = useState<boolean>(true);
55
69
  const metadata = useGrpcMetadata();
56
70
 
57
71
  useEffect(() => {
58
72
  void (async () => {
59
- setState({
60
- response: null,
61
- isLoading: true,
62
- error: null,
63
- });
73
+ setLoading(true);
64
74
 
65
75
  const stepDuration = getStepDuration(start, end);
66
76
  const call = client.queryRange(
@@ -75,12 +85,19 @@ export const useQueryRange = (
75
85
  );
76
86
 
77
87
  call.response
78
- .then(response => setState({response, isLoading: false, error: null}))
79
- .catch(error => setState({response: null, isLoading: false, error}));
88
+ .then(response => {
89
+ setState({response, isLoading: false, error: null});
90
+ setLoading(false);
91
+ return null;
92
+ })
93
+ .catch(error => {
94
+ setState({response: null, isLoading: false, error});
95
+ setLoading(false);
96
+ });
80
97
  })();
81
98
  }, [client, queryExpression, start, end, metadata]);
82
99
 
83
- return state;
100
+ return {...state, isLoading};
84
101
  };
85
102
 
86
103
  const ProfileMetricsGraph = ({
@@ -96,6 +113,7 @@ const ProfileMetricsGraph = ({
96
113
  const {isLoading, response, error} = useQueryRange(queryClient, queryExpression, from, to);
97
114
  const isLoaderVisible = useDelayedLoader(isLoading);
98
115
  const {loader, onError, perf} = useParcaContext();
116
+ const {width, height, margin, marginRight} = useMetricsGraphDimensions();
99
117
 
100
118
  useEffect(() => {
101
119
  if (error !== null) {
@@ -111,7 +129,10 @@ const ProfileMetricsGraph = ({
111
129
  perf?.markInteraction('Metrics graph render', response.series[0].samples.length);
112
130
  }, [perf, response]);
113
131
 
114
- if (isLoaderVisible) {
132
+ const series = response?.series;
133
+ const dataAvailable = series !== null && series !== undefined && series?.length > 0;
134
+
135
+ if (isLoaderVisible || (isLoading && !dataAvailable)) {
115
136
  return <>{loader}</>;
116
137
  }
117
138
 
@@ -127,15 +148,14 @@ const ProfileMetricsGraph = ({
127
148
  );
128
149
  }
129
150
 
130
- const series = response?.series;
131
- if (series !== null && series !== undefined && series?.length > 0) {
151
+ if (dataAvailable) {
132
152
  const handleSampleClick = (timestamp: number, _value: number, labels: Label[]): void => {
133
153
  onPointClick(timestamp, labels, queryExpression);
134
154
  };
135
155
 
136
156
  return (
137
157
  <div
138
- className="rounded border-gray-300 dark:border-gray-500 dark:bg-gray-700"
158
+ className="h-full w-full rounded border-gray-300 dark:border-gray-500 dark:bg-gray-700"
139
159
  style={{borderWidth: 1}}
140
160
  >
141
161
  <MetricsGraph
@@ -146,19 +166,17 @@ const ProfileMetricsGraph = ({
146
166
  setTimeRange={setTimeRange}
147
167
  onSampleClick={handleSampleClick}
148
168
  onLabelClick={addLabelMatcher}
149
- width={0}
150
169
  sampleUnit={Query.parse(queryExpression).profileType().sampleUnit}
170
+ height={height}
171
+ width={width}
172
+ margin={margin}
173
+ marginRight={marginRight}
151
174
  />
152
175
  </div>
153
176
  );
154
177
  }
155
- return (
156
- <div className="grid grid-cols-1">
157
- <div className="flex justify-center py-20">
158
- <p className="m-0">No data found. Try a different query.</p>
159
- </div>
160
- </div>
161
- );
178
+
179
+ return <ProfileMetricsEmptyState message="No data found. Try a different query." />;
162
180
  };
163
181
 
164
182
  export default ProfileMetricsGraph;
@@ -31,9 +31,11 @@ import {getStepDuration, getStepDurationInMilliseconds} from '@parca/utilities';
31
31
 
32
32
  import {MergedProfileSelection, ProfileSelection} from '..';
33
33
  import MatchersInput from '../MatchersInput/index';
34
- import ProfileMetricsGraph from '../ProfileMetricsGraph';
34
+ import {useMetricsGraphDimensions} from '../MetricsGraph/useMetricsGraphDimensions';
35
+ import ProfileMetricsGraph, {ProfileMetricsEmptyState} from '../ProfileMetricsGraph';
35
36
  import ProfileTypeSelector from '../ProfileTypeSelector/index';
36
37
  import CompareButton from './CompareButton';
38
+ import {useAutoQuerySelector} from './useAutoQuerySelector';
37
39
 
38
40
  export interface QuerySelection {
39
41
  expression: string;
@@ -98,6 +100,7 @@ const ProfileSelector = ({
98
100
  data: profileTypesData,
99
101
  error,
100
102
  } = useProfileTypes(queryClient);
103
+ const {heightStyle} = useMetricsGraphDimensions();
101
104
 
102
105
  const [timeRangeSelection, setTimeRangeSelection] = useState(
103
106
  DateTimeRange.fromRangeKey(querySelection.timeSelection)
@@ -179,6 +182,13 @@ const ProfileSelector = ({
179
182
  }
180
183
  };
181
184
 
185
+ useAutoQuerySelector({
186
+ selectedProfileName,
187
+ profileTypesData,
188
+ setProfileName,
189
+ setQueryExpression,
190
+ });
191
+
182
192
  const handleCompareClick = (): void => onCompareProfile();
183
193
 
184
194
  const searchDisabled =
@@ -236,61 +246,63 @@ const ProfileSelector = ({
236
246
  </Card.Header>
237
247
  {
238
248
  <Card.Body>
239
- {querySelection.expression !== undefined &&
240
- querySelection.expression.length > 0 &&
241
- querySelection.from !== undefined &&
242
- querySelection.to !== undefined ? (
243
- <ProfileMetricsGraph
244
- queryClient={queryClient}
245
- queryExpression={querySelection.expression}
246
- from={querySelection.from}
247
- to={querySelection.to}
248
- profile={profileSelection}
249
- setTimeRange={(range: DateTimeRange) => {
250
- const from = range.getFromMs();
251
- const to = range.getToMs();
252
- let mergedProfileParams = {};
253
- if (query.profileType().delta) {
254
- mergedProfileParams = {mergeFrom: from, mergeTo: to};
255
- }
256
- setTimeRangeSelection(range);
257
- selectQuery({
258
- expression: queryExpressionString,
259
- from,
260
- to,
261
- timeSelection: range.getRangeKey(),
262
- ...mergedProfileParams,
263
- });
264
- }}
265
- addLabelMatcher={addLabelMatcher}
266
- onPointClick={(timestamp, labels, queryExpression) => {
267
- // TODO: Pass the query object via click rather than queryExpression
268
- let query = Query.parse(queryExpression);
269
- labels.forEach(l => {
270
- const [newQuery, updated] = query.setMatcher(l.name, l.value);
271
- if (updated) {
272
- query = newQuery;
249
+ <div style={{height: heightStyle}}>
250
+ {querySelection.expression !== undefined &&
251
+ querySelection.expression.length > 0 &&
252
+ querySelection.from !== undefined &&
253
+ querySelection.to !== undefined ? (
254
+ <ProfileMetricsGraph
255
+ queryClient={queryClient}
256
+ queryExpression={querySelection.expression}
257
+ from={querySelection.from}
258
+ to={querySelection.to}
259
+ profile={profileSelection}
260
+ setTimeRange={(range: DateTimeRange) => {
261
+ const from = range.getFromMs();
262
+ const to = range.getToMs();
263
+ let mergedProfileParams = {};
264
+ if (query.profileType().delta) {
265
+ mergedProfileParams = {mergeFrom: from, mergeTo: to};
273
266
  }
274
- });
267
+ setTimeRangeSelection(range);
268
+ selectQuery({
269
+ expression: queryExpressionString,
270
+ from,
271
+ to,
272
+ timeSelection: range.getRangeKey(),
273
+ ...mergedProfileParams,
274
+ });
275
+ }}
276
+ addLabelMatcher={addLabelMatcher}
277
+ onPointClick={(timestamp, labels, queryExpression) => {
278
+ // TODO: Pass the query object via click rather than queryExpression
279
+ let query = Query.parse(queryExpression);
280
+ labels.forEach(l => {
281
+ const [newQuery, updated] = query.setMatcher(l.name, l.value);
282
+ if (updated) {
283
+ query = newQuery;
284
+ }
285
+ });
275
286
 
276
- const stepDuration = getStepDuration(querySelection.from, querySelection.to);
277
- const stepDurationInMilliseconds = getStepDurationInMilliseconds(stepDuration);
278
- const mergeFrom = timestamp;
279
- const mergeTo = query.profileType().delta
280
- ? mergeFrom + stepDurationInMilliseconds
281
- : mergeFrom;
282
- selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
283
- }}
284
- />
285
- ) : (
286
- <>
287
- {profileSelection == null && (
288
- <div className="my-20 text-center">
289
- <p>Run a query, and the result will be displayed here.</p>
290
- </div>
291
- )}
292
- </>
293
- )}
287
+ const stepDuration = getStepDuration(querySelection.from, querySelection.to);
288
+ const stepDurationInMilliseconds = getStepDurationInMilliseconds(stepDuration);
289
+ const mergeFrom = timestamp;
290
+ const mergeTo = query.profileType().delta
291
+ ? mergeFrom + stepDurationInMilliseconds
292
+ : mergeFrom;
293
+ selectProfile(new MergedProfileSelection(mergeFrom, mergeTo, query));
294
+ }}
295
+ />
296
+ ) : (
297
+ <>
298
+ {profileSelection == null ? (
299
+ <ProfileMetricsEmptyState
300
+ message={`Please select a profile type and click "Search" to begin.`}
301
+ />
302
+ ) : null}
303
+ </>
304
+ )}
305
+ </div>
294
306
  </Card.Body>
295
307
  }
296
308
  </Card>
@@ -0,0 +1,87 @@
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} from 'react';
15
+
16
+ import {ProfileTypesResponse} from '@parca/client';
17
+ import {selectAutoQuery, setAutoQuery, useAppDispatch, useAppSelector} from '@parca/store';
18
+
19
+ import {constructProfileName} from '../ProfileTypeSelector';
20
+
21
+ interface Props {
22
+ selectedProfileName: string;
23
+ profileTypesData: ProfileTypesResponse | undefined;
24
+ setProfileName: (name: string) => void;
25
+ setQueryExpression: () => void;
26
+ }
27
+
28
+ export const useAutoQuerySelector = ({
29
+ selectedProfileName,
30
+ profileTypesData,
31
+ setProfileName,
32
+ setQueryExpression,
33
+ }: Props): void => {
34
+ const autoQuery = useAppSelector(selectAutoQuery);
35
+ const dispatch = useAppDispatch();
36
+
37
+ // Effect to load some initial data on load when is no selection
38
+ useEffect(() => {
39
+ void (async () => {
40
+ if (selectedProfileName.length > 0) {
41
+ return;
42
+ }
43
+ if (profileTypesData?.types == null || profileTypesData.types.length < 1) {
44
+ return;
45
+ }
46
+ if (autoQuery === 'true') {
47
+ // Autoquery already enabled.
48
+ return;
49
+ }
50
+ dispatch(setAutoQuery('true'));
51
+ let profileType = profileTypesData.types.find(type => type.name === 'parca_agent_cpu');
52
+ if (profileType == null) {
53
+ profileType = profileTypesData.types[0];
54
+ }
55
+ setProfileName(constructProfileName(profileType));
56
+ })();
57
+ }, [
58
+ profileTypesData,
59
+ selectedProfileName,
60
+ autoQuery,
61
+ dispatch,
62
+ setQueryExpression,
63
+ setProfileName,
64
+ ]);
65
+
66
+ useEffect(() => {
67
+ void (async () => {
68
+ if (
69
+ autoQuery !== 'true' ||
70
+ profileTypesData?.types == null ||
71
+ profileTypesData.types.length < 1 ||
72
+ selectedProfileName.length === 0
73
+ ) {
74
+ return;
75
+ }
76
+ setQueryExpression();
77
+ dispatch(setAutoQuery('false'));
78
+ })();
79
+ }, [
80
+ profileTypesData,
81
+ setQueryExpression,
82
+ autoQuery,
83
+ setProfileName,
84
+ dispatch,
85
+ selectedProfileName,
86
+ ]);
87
+ };
@@ -128,17 +128,16 @@ export function profileSelectElement(
128
128
  };
129
129
  }
130
130
 
131
+ export const constructProfileName = (type: ProfileType): string => {
132
+ return `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${
133
+ type.delta ? ':delta' : ''
134
+ }`;
135
+ };
136
+
131
137
  export const normalizeProfileTypesData = (types: ProfileType[]): string[] => {
132
- return types
133
- .map(
134
- type =>
135
- `${type.name}:${type.sampleType}:${type.sampleUnit}:${type.periodType}:${type.periodUnit}${
136
- type.delta ? ':delta' : ''
137
- }`
138
- )
139
- .sort((a: string, b: string): number => {
140
- return a.localeCompare(b);
141
- });
138
+ return types.map(constructProfileName).sort((a: string, b: string): number => {
139
+ return a.localeCompare(b);
140
+ });
142
141
  };
143
142
 
144
143
  interface Props {
@@ -13,6 +13,7 @@
13
13
 
14
14
  import {Profiler, ProfilerProps, useEffect, useMemo, useState} from 'react';
15
15
 
16
+ import {Table} from 'apache-arrow';
16
17
  import cx from 'classnames';
17
18
  import {scaleLinear} from 'd3';
18
19
  import graphviz from 'graphviz-wasm';
@@ -53,6 +54,7 @@ type NavigateFunction = (path: string, queryParams: any, options?: {replace?: bo
53
54
  export interface FlamegraphData {
54
55
  loading: boolean;
55
56
  data?: Flamegraph;
57
+ table?: Table<any>;
56
58
  total?: bigint;
57
59
  filtered?: bigint;
58
60
  error?: any;
@@ -113,7 +115,7 @@ export const ProfileView = ({
113
115
  }: ProfileViewProps): JSX.Element => {
114
116
  const {ref, dimensions} = useContainerDimensions();
115
117
  const [curPath, setCurPath] = useState<string[]>([]);
116
- const [rawDashboardItems, setDashboardItems] = useURLState({
118
+ const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
117
119
  param: 'dashboard_items',
118
120
  navigateTo,
119
121
  });
@@ -230,7 +232,7 @@ export const ProfileView = ({
230
232
  }): JSX.Element => {
231
233
  switch (type) {
232
234
  case 'icicle': {
233
- return flamegraphData?.data != null ? (
235
+ return flamegraphData?.table !== undefined || flamegraphData.data !== undefined ? (
234
236
  <ConditionalWrapper<ProfilerProps>
235
237
  condition={perf?.onRender != null}
236
238
  WrapperComponent={Profiler}
@@ -242,6 +244,7 @@ export const ProfileView = ({
242
244
  <ProfileIcicleGraph
243
245
  curPath={curPath}
244
246
  setNewCurPath={setNewCurPath}
247
+ table={flamegraphData.table}
245
248
  graph={flamegraphData.data}
246
249
  total={total}
247
250
  filtered={filtered}
@@ -13,9 +13,11 @@
13
13
 
14
14
  import {useEffect, useMemo, useState} from 'react';
15
15
 
16
+ import {tableFromIPC} from 'apache-arrow';
17
+
16
18
  import {QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
17
19
  import {useGrpcMetadata, useParcaContext, useURLState} from '@parca/components';
18
- import {USER_PREFERENCES, useUserPreference} from '@parca/hooks';
20
+ import {USER_PREFERENCES, useUIFeatureFlag, useUserPreference} from '@parca/hooks';
19
21
  import {saveAsBlob, type NavigateFunction} from '@parca/utilities';
20
22
 
21
23
  import {ProfileSource} from './ProfileSource';
@@ -36,8 +38,10 @@ export const ProfileViewWithData = ({
36
38
  navigateTo,
37
39
  }: ProfileViewWithDataProps): JSX.Element => {
38
40
  const metadata = useGrpcMetadata();
39
- const [dashboardItems] = useURLState({param: 'dashboard_items', navigateTo});
41
+ const [dashboardItems = ['icicle']] = useURLState({param: 'dashboard_items', navigateTo});
42
+
40
43
  const [enableTrimming] = useUserPreference<boolean>(USER_PREFERENCES.ENABLE_GRAPH_TRIMMING.key);
44
+ const [arrowFlamegraphEnabled] = useUIFeatureFlag('flamegraph-arrow');
41
45
  const [pprofDownloading, setPprofDownloading] = useState<boolean>(false);
42
46
 
43
47
  const nodeTrimThreshold = useMemo(() => {
@@ -53,11 +57,15 @@ export const ProfileViewWithData = ({
53
57
  return (1 / width) * 100;
54
58
  }, [enableTrimming]);
55
59
 
60
+ const reportType = arrowFlamegraphEnabled
61
+ ? QueryRequest_ReportType.FLAMEGRAPH_ARROW
62
+ : QueryRequest_ReportType.FLAMEGRAPH_TABLE;
63
+
56
64
  const {
57
65
  isLoading: flamegraphLoading,
58
66
  response: flamegraphResponse,
59
67
  error: flamegraphError,
60
- } = useQuery(queryClient, profileSource, QueryRequest_ReportType.FLAMEGRAPH_TABLE, {
68
+ } = useQuery(queryClient, profileSource, reportType, {
61
69
  skip: !dashboardItems.includes('icicle'),
62
70
  nodeTrimThreshold,
63
71
  });
@@ -80,16 +88,19 @@ export const ProfileViewWithData = ({
80
88
  });
81
89
 
82
90
  useEffect(() => {
83
- if (!flamegraphLoading && flamegraphResponse?.report.oneofKind === 'flamegraph') {
84
- perf?.markInteraction('Flamegraph render', flamegraphResponse.report.flamegraph.total);
91
+ if (
92
+ (!flamegraphLoading && flamegraphResponse?.report.oneofKind === 'flamegraph') ||
93
+ flamegraphResponse?.report.oneofKind === 'flamegraphArrow'
94
+ ) {
95
+ perf?.markInteraction('Flamegraph render', flamegraphResponse.total);
85
96
  }
86
97
 
87
98
  if (!topTableLoading && topTableResponse?.report.oneofKind === 'top') {
88
- perf?.markInteraction('Top table render', topTableResponse?.report?.top.total);
99
+ perf?.markInteraction('Top table render', topTableResponse.total);
89
100
  }
90
101
 
91
102
  if (!callgraphLoading && callgraphResponse?.report.oneofKind === 'callgraph') {
92
- perf?.markInteraction('Callgraph render', callgraphResponse?.report?.callgraph.cumulative);
103
+ perf?.markInteraction('Callgraph render', callgraphResponse.total);
93
104
  }
94
105
  }, [
95
106
  flamegraphLoading,
@@ -144,6 +155,10 @@ export const ProfileViewWithData = ({
144
155
  flamegraphResponse?.report.oneofKind === 'flamegraph'
145
156
  ? flamegraphResponse?.report?.flamegraph
146
157
  : undefined,
158
+ table:
159
+ flamegraphResponse?.report.oneofKind === 'flamegraphArrow'
160
+ ? tableFromIPC(flamegraphResponse?.report?.flamegraphArrow.record)
161
+ : undefined,
147
162
  total: BigInt(flamegraphResponse?.total ?? '0'),
148
163
  filtered: BigInt(flamegraphResponse?.filtered ?? '0'),
149
164
  error: flamegraphError,