@parca/profile 0.16.227 → 0.16.229

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 (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.d.ts +3 -1
  3. package/dist/GraphTooltipArrow/Content.js +44 -6
  4. package/dist/GraphTooltipArrow/ExpandOnHoverValue.js +1 -1
  5. package/dist/MetricsGraph/useMetricsGraphDimensions.js +9 -0
  6. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +1 -1
  7. package/dist/ProfileView/ProfileViewContext.d.ts +14 -0
  8. package/dist/ProfileView/ProfileViewContext.js +30 -0
  9. package/dist/ProfileView/ViewSelector.js +1 -0
  10. package/dist/ProfileView/index.d.ts +11 -4
  11. package/dist/ProfileView/index.js +21 -12
  12. package/dist/ProfileViewWithData.js +35 -11
  13. package/dist/SourceView/Highlighter.d.ts +16 -0
  14. package/dist/SourceView/Highlighter.js +80 -0
  15. package/dist/SourceView/LineNo.d.ts +6 -0
  16. package/dist/SourceView/LineNo.js +23 -0
  17. package/dist/SourceView/index.d.ts +11 -0
  18. package/dist/SourceView/index.js +37 -0
  19. package/dist/Table/index.d.ts +14 -0
  20. package/dist/Table/index.js +184 -0
  21. package/dist/styles.css +1 -1
  22. package/dist/useQuery.d.ts +3 -0
  23. package/dist/useQuery.js +17 -1
  24. package/package.json +10 -8
  25. package/src/GraphTooltipArrow/Content.tsx +88 -9
  26. package/src/GraphTooltipArrow/ExpandOnHoverValue.tsx +1 -1
  27. package/src/MetricsGraph/useMetricsGraphDimensions.ts +9 -0
  28. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +1 -0
  29. package/src/ProfileView/ProfileViewContext.tsx +52 -0
  30. package/src/ProfileView/ViewSelector.tsx +1 -0
  31. package/src/ProfileView/index.tsx +136 -99
  32. package/src/ProfileViewWithData.tsx +48 -15
  33. package/src/SourceView/Highlighter.tsx +196 -0
  34. package/src/SourceView/LineNo.tsx +31 -0
  35. package/src/SourceView/index.tsx +74 -0
  36. package/src/Table/index.tsx +285 -0
  37. package/src/useQuery.tsx +20 -1
@@ -0,0 +1,52 @@
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 {ReactNode, createContext, useContext} from 'react';
15
+
16
+ import {ProfileSource} from '../ProfileSource';
17
+
18
+ interface Props {
19
+ profileSource?: ProfileSource;
20
+ sampleUnit: string;
21
+ }
22
+
23
+ export const defaultValue: Props = {
24
+ profileSource: undefined,
25
+ sampleUnit: 'bytes',
26
+ };
27
+
28
+ const ProfileViewContext = createContext<Props>(defaultValue);
29
+
30
+ export const ProfileViewContextProvider = ({
31
+ children,
32
+ value,
33
+ }: {
34
+ children: ReactNode;
35
+ value?: Props;
36
+ }): JSX.Element => {
37
+ return (
38
+ <ProfileViewContext.Provider value={{...defaultValue, ...(value ?? {})}}>
39
+ {children}
40
+ </ProfileViewContext.Provider>
41
+ );
42
+ };
43
+
44
+ export const useProfileViewContext = (): Props => {
45
+ const context = useContext(ProfileViewContext);
46
+ if (context == null) {
47
+ return defaultValue;
48
+ }
49
+ return context;
50
+ };
51
+
52
+ export default ProfileViewContext;
@@ -43,6 +43,7 @@ const ViewSelector = ({
43
43
  const allItems: Array<{key: string; canBeSelected: boolean; supportingText?: string}> = [
44
44
  {key: 'table', canBeSelected: !dashboardItems.includes('table')},
45
45
  {key: 'icicle', canBeSelected: !dashboardItems.includes('icicle')},
46
+ {key: 'source', canBeSelected: false},
46
47
  ];
47
48
  if (callgraphEnabled) {
48
49
  allItems.push({
@@ -13,7 +13,7 @@
13
13
 
14
14
  import {Profiler, ProfilerProps, useEffect, useMemo, useState} from 'react';
15
15
 
16
- import {Table} from 'apache-arrow';
16
+ import {Table as ArrowTable} from 'apache-arrow';
17
17
  import cx from 'classnames';
18
18
  import {scaleLinear} from 'd3';
19
19
  import graphviz from 'graphviz-wasm';
@@ -25,7 +25,14 @@ import {
25
25
  type DropResult,
26
26
  } from 'react-beautiful-dnd';
27
27
 
28
- import {Callgraph as CallgraphType, Flamegraph, QueryServiceClient, Top} from '@parca/client';
28
+ import {
29
+ Callgraph as CallgraphType,
30
+ Flamegraph,
31
+ QueryServiceClient,
32
+ Source,
33
+ TableArrow,
34
+ Top,
35
+ } from '@parca/client';
29
36
  import {
30
37
  Button,
31
38
  Card,
@@ -42,10 +49,12 @@ import {Callgraph} from '../';
42
49
  import {jsonToDot} from '../Callgraph/utils';
43
50
  import ProfileIcicleGraph from '../ProfileIcicleGraph';
44
51
  import {ProfileSource} from '../ProfileSource';
45
- import {TopTable} from '../TopTable';
52
+ import {SourceView} from '../SourceView';
53
+ import Table from '../Table';
46
54
  import ProfileShareButton from '../components/ProfileShareButton';
47
55
  import useDelayedLoader from '../useDelayedLoader';
48
56
  import FilterByFunctionButton from './FilterByFunctionButton';
57
+ import {ProfileViewContextProvider} from './ProfileViewContext';
49
58
  import ViewSelector from './ViewSelector';
50
59
  import {VisualizationPanel} from './VisualizationPanel';
51
60
 
@@ -54,7 +63,7 @@ type NavigateFunction = (path: string, queryParams: any, options?: {replace?: bo
54
63
  export interface FlamegraphData {
55
64
  loading: boolean;
56
65
  data?: Flamegraph;
57
- table?: Table<any>;
66
+ table?: ArrowTable<any>;
58
67
  total?: bigint;
59
68
  filtered?: bigint;
60
69
  error?: any;
@@ -62,7 +71,8 @@ export interface FlamegraphData {
62
71
 
63
72
  export interface TopTableData {
64
73
  loading: boolean;
65
- data?: Top;
74
+ arrow?: TableArrow;
75
+ data?: Top; // TODO: Remove this once we only have arrow support
66
76
  total?: bigint;
67
77
  filtered?: bigint;
68
78
  error?: any;
@@ -76,12 +86,19 @@ interface CallgraphData {
76
86
  error?: any;
77
87
  }
78
88
 
89
+ interface SourceData {
90
+ loading: boolean;
91
+ data?: Source;
92
+ error?: any;
93
+ }
94
+
79
95
  export interface ProfileViewProps {
80
96
  total: bigint;
81
97
  filtered: bigint;
82
98
  flamegraphData?: FlamegraphData;
83
99
  topTableData?: TopTableData;
84
100
  callgraphData?: CallgraphData;
101
+ sourceData?: SourceData;
85
102
  sampleUnit: string;
86
103
  profileSource?: ProfileSource;
87
104
  queryClient?: QueryServiceClient;
@@ -106,6 +123,7 @@ export const ProfileView = ({
106
123
  flamegraphData,
107
124
  topTableData,
108
125
  callgraphData,
126
+ sourceData,
109
127
  sampleUnit,
110
128
  profileSource,
111
129
  queryClient,
@@ -158,12 +176,16 @@ export const ProfileView = ({
158
176
  if (dashboardItems.includes('table')) {
159
177
  return Boolean(topTableData?.loading);
160
178
  }
179
+ if (dashboardItems.includes('source')) {
180
+ return Boolean(sourceData?.loading);
181
+ }
161
182
  return false;
162
183
  }, [
163
184
  dashboardItems,
164
185
  callgraphData?.loading,
165
186
  flamegraphData?.loading,
166
187
  topTableData?.loading,
188
+ sourceData?.loading,
167
189
  callgraphSVG,
168
190
  ]);
169
191
 
@@ -274,9 +296,9 @@ export const ProfileView = ({
274
296
  }
275
297
  case 'table': {
276
298
  return topTableData != null ? (
277
- <TopTable
299
+ <Table
278
300
  loading={topTableData.loading}
279
- data={topTableData.data}
301
+ data={topTableData.arrow?.record}
280
302
  sampleUnit={sampleUnit}
281
303
  navigateTo={navigateTo}
282
304
  setActionButtons={setActionButtons}
@@ -286,6 +308,19 @@ export const ProfileView = ({
286
308
  <></>
287
309
  );
288
310
  }
311
+ case 'source': {
312
+ return sourceData != null ? (
313
+ <SourceView
314
+ loading={sourceData.loading}
315
+ data={sourceData.data}
316
+ total={total}
317
+ filtered={filtered}
318
+ setActionButtons={setActionButtons}
319
+ />
320
+ ) : (
321
+ <></>
322
+ );
323
+ }
289
324
  default: {
290
325
  return <></>;
291
326
  }
@@ -314,102 +349,104 @@ export const ProfileView = ({
314
349
 
315
350
  return (
316
351
  <KeyDownProvider>
317
- <div className="py-3">
318
- <Card>
319
- <Card.Body>
320
- <div className="flex w-full py-3">
321
- <div className="flex space-x-4 lg:w-1/2">
322
- <div className="flex space-x-1">
323
- {profileSource !== undefined && queryClient !== undefined ? (
324
- <ProfileShareButton
325
- queryRequest={profileSource.QueryRequest()}
326
- queryClient={queryClient}
327
- />
328
- ) : null}
329
-
330
- <Button
331
- color="neutral"
332
- onClick={e => {
333
- e.preventDefault();
334
- onDownloadPProf();
335
- }}
336
- disabled={pprofDownloading}
337
- >
338
- {pprofDownloading != null && pprofDownloading
339
- ? 'Downloading'
340
- : 'Download pprof'}
341
- </Button>
352
+ <ProfileViewContextProvider value={{profileSource, sampleUnit}}>
353
+ <div className="py-3">
354
+ <Card>
355
+ <Card.Body>
356
+ <div className="flex w-full py-3">
357
+ <div className="flex space-x-4 lg:w-1/2">
358
+ <div className="flex space-x-1">
359
+ {profileSource !== undefined && queryClient !== undefined ? (
360
+ <ProfileShareButton
361
+ queryRequest={profileSource.QueryRequest()}
362
+ queryClient={queryClient}
363
+ />
364
+ ) : null}
365
+
366
+ <Button
367
+ color="neutral"
368
+ onClick={e => {
369
+ e.preventDefault();
370
+ onDownloadPProf();
371
+ }}
372
+ disabled={pprofDownloading}
373
+ >
374
+ {pprofDownloading != null && pprofDownloading
375
+ ? 'Downloading'
376
+ : 'Download pprof'}
377
+ </Button>
378
+ </div>
379
+ <FilterByFunctionButton navigateTo={navigateTo} />
380
+ </div>
381
+
382
+ <div className="ml-auto flex gap-2">
383
+ <ViewSelector
384
+ defaultValue=""
385
+ navigateTo={navigateTo}
386
+ position={-1}
387
+ placeholderText="Add panel..."
388
+ primary
389
+ addView={true}
390
+ disabled={isMultiPanelView || dashboardItems.length < 1}
391
+ />
342
392
  </div>
343
- <FilterByFunctionButton navigateTo={navigateTo} />
344
393
  </div>
345
394
 
346
- <div className="ml-auto flex gap-2">
347
- <ViewSelector
348
- defaultValue=""
349
- navigateTo={navigateTo}
350
- position={-1}
351
- placeholderText="Add panel..."
352
- primary
353
- addView={true}
354
- disabled={isMultiPanelView || dashboardItems.length < 1}
355
- />
395
+ <div className="w-full" ref={ref}>
396
+ {isLoaderVisible ? (
397
+ <>{loader}</>
398
+ ) : (
399
+ <DragDropContext onDragEnd={onDragEnd}>
400
+ <Droppable droppableId="droppable" direction="horizontal">
401
+ {provided => (
402
+ <div
403
+ ref={provided.innerRef}
404
+ className="flex w-full justify-between space-x-4"
405
+ {...provided.droppableProps}
406
+ >
407
+ {dashboardItems.map((dashboardItem, index) => {
408
+ return (
409
+ <Draggable
410
+ key={dashboardItem}
411
+ draggableId={dashboardItem}
412
+ index={index}
413
+ isDragDisabled={!isMultiPanelView}
414
+ >
415
+ {(provided, snapshot: {isDragging: boolean}) => (
416
+ <div
417
+ ref={provided.innerRef}
418
+ {...provided.draggableProps}
419
+ key={dashboardItem}
420
+ className={cx(
421
+ 'rounded border border-gray-300 p-3 dark:border-gray-500 dark:bg-gray-700',
422
+ isMultiPanelView ? 'w-1/2' : 'w-full',
423
+ snapshot.isDragging ? 'bg-gray-200' : 'bg-white'
424
+ )}
425
+ >
426
+ <VisualizationPanel
427
+ handleClosePanel={handleClosePanel}
428
+ isMultiPanelView={isMultiPanelView}
429
+ dashboardItem={dashboardItem}
430
+ getDashboardItemByType={getDashboardItemByType}
431
+ dragHandleProps={provided.dragHandleProps}
432
+ navigateTo={navigateTo}
433
+ index={index}
434
+ />
435
+ </div>
436
+ )}
437
+ </Draggable>
438
+ );
439
+ })}
440
+ </div>
441
+ )}
442
+ </Droppable>
443
+ </DragDropContext>
444
+ )}
356
445
  </div>
357
- </div>
358
-
359
- <div className="w-full" ref={ref}>
360
- {isLoaderVisible ? (
361
- <>{loader}</>
362
- ) : (
363
- <DragDropContext onDragEnd={onDragEnd}>
364
- <Droppable droppableId="droppable" direction="horizontal">
365
- {provided => (
366
- <div
367
- ref={provided.innerRef}
368
- className="flex w-full justify-between space-x-4"
369
- {...provided.droppableProps}
370
- >
371
- {dashboardItems.map((dashboardItem, index) => {
372
- return (
373
- <Draggable
374
- key={dashboardItem}
375
- draggableId={dashboardItem}
376
- index={index}
377
- isDragDisabled={!isMultiPanelView}
378
- >
379
- {(provided, snapshot: {isDragging: boolean}) => (
380
- <div
381
- ref={provided.innerRef}
382
- {...provided.draggableProps}
383
- key={dashboardItem}
384
- className={cx(
385
- 'rounded border border-gray-300 p-3 dark:border-gray-500 dark:bg-gray-700',
386
- isMultiPanelView ? 'w-1/2' : 'w-full',
387
- snapshot.isDragging ? 'bg-gray-200' : 'bg-white'
388
- )}
389
- >
390
- <VisualizationPanel
391
- handleClosePanel={handleClosePanel}
392
- isMultiPanelView={isMultiPanelView}
393
- dashboardItem={dashboardItem}
394
- getDashboardItemByType={getDashboardItemByType}
395
- dragHandleProps={provided.dragHandleProps}
396
- navigateTo={navigateTo}
397
- index={index}
398
- />
399
- </div>
400
- )}
401
- </Draggable>
402
- );
403
- })}
404
- </div>
405
- )}
406
- </Droppable>
407
- </DragDropContext>
408
- )}
409
- </div>
410
- </Card.Body>
411
- </Card>
412
- </div>
446
+ </Card.Body>
447
+ </Card>
448
+ </div>
449
+ </ProfileViewContextProvider>
413
450
  </KeyDownProvider>
414
451
  );
415
452
  };
@@ -40,6 +40,10 @@ export const ProfileViewWithData = ({
40
40
  }: ProfileViewWithDataProps): JSX.Element => {
41
41
  const metadata = useGrpcMetadata();
42
42
  const [dashboardItems = ['icicle']] = useURLState({param: 'dashboard_items', navigateTo});
43
+ const [sourceBuildID] = useURLState({param: 'source_buildid', navigateTo}) as unknown as [string];
44
+ const [sourceFilename] = useURLState({param: 'source_filename', navigateTo}) as unknown as [
45
+ string
46
+ ];
43
47
  const [groupBy = [FIELD_FUNCTION_NAME]] = useURLState({param: 'group_by', navigateTo});
44
48
 
45
49
  const [enableTrimming] = useUserPreference<boolean>(USER_PREFERENCES.ENABLE_GRAPH_TRIMMING.key);
@@ -78,10 +82,10 @@ export const ProfileViewWithData = ({
78
82
  const {perf} = useParcaContext();
79
83
 
80
84
  const {
81
- isLoading: topTableLoading,
82
- response: topTableResponse,
83
- error: topTableError,
84
- } = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
85
+ isLoading: tableLoading,
86
+ response: tableResponse,
87
+ error: tableError,
88
+ } = useQuery(queryClient, profileSource, QueryRequest_ReportType.TABLE_ARROW, {
85
89
  skip: !dashboardItems.includes('table'),
86
90
  });
87
91
 
@@ -93,6 +97,16 @@ export const ProfileViewWithData = ({
93
97
  skip: !dashboardItems.includes('callgraph'),
94
98
  });
95
99
 
100
+ const {
101
+ isLoading: sourceLoading,
102
+ response: sourceResponse,
103
+ error: sourceError,
104
+ } = useQuery(queryClient, profileSource, QueryRequest_ReportType.SOURCE, {
105
+ skip: !dashboardItems.includes('source'),
106
+ sourceBuildID,
107
+ sourceFilename,
108
+ });
109
+
96
110
  useEffect(() => {
97
111
  if (
98
112
  (!flamegraphLoading && flamegraphResponse?.report.oneofKind === 'flamegraph') ||
@@ -101,20 +115,26 @@ export const ProfileViewWithData = ({
101
115
  perf?.markInteraction('Flamegraph render', flamegraphResponse.total);
102
116
  }
103
117
 
104
- if (!topTableLoading && topTableResponse?.report.oneofKind === 'top') {
105
- perf?.markInteraction('Top table render', topTableResponse.total);
118
+ if (!tableLoading && tableResponse?.report.oneofKind === 'tableArrow') {
119
+ perf?.markInteraction('table render', tableResponse.total);
106
120
  }
107
121
 
108
122
  if (!callgraphLoading && callgraphResponse?.report.oneofKind === 'callgraph') {
109
123
  perf?.markInteraction('Callgraph render', callgraphResponse.total);
110
124
  }
125
+
126
+ if (!sourceLoading && sourceResponse?.report.oneofKind === 'source') {
127
+ perf?.markInteraction('Source render', sourceResponse.total);
128
+ }
111
129
  }, [
112
130
  flamegraphLoading,
113
131
  flamegraphResponse,
114
132
  callgraphResponse,
115
133
  callgraphLoading,
116
- topTableLoading,
117
- topTableResponse,
134
+ tableLoading,
135
+ tableResponse,
136
+ sourceLoading,
137
+ sourceResponse,
118
138
  perf,
119
139
  ]);
120
140
 
@@ -143,12 +163,15 @@ export const ProfileViewWithData = ({
143
163
  if (flamegraphResponse !== null) {
144
164
  total = BigInt(flamegraphResponse.total);
145
165
  filtered = BigInt(flamegraphResponse.filtered);
146
- } else if (topTableResponse !== null) {
147
- total = BigInt(topTableResponse.total);
148
- filtered = BigInt(topTableResponse.filtered);
166
+ } else if (tableResponse !== null) {
167
+ total = BigInt(tableResponse.total);
168
+ filtered = BigInt(tableResponse.filtered);
149
169
  } else if (callgraphResponse !== null) {
150
170
  total = BigInt(callgraphResponse.total);
151
171
  filtered = BigInt(callgraphResponse.filtered);
172
+ } else if (sourceResponse !== null) {
173
+ total = BigInt(sourceResponse.total);
174
+ filtered = BigInt(sourceResponse.filtered);
152
175
  }
153
176
 
154
177
  return (
@@ -170,10 +193,12 @@ export const ProfileViewWithData = ({
170
193
  error: flamegraphError,
171
194
  }}
172
195
  topTableData={{
173
- loading: topTableLoading,
174
- data:
175
- topTableResponse?.report.oneofKind === 'top' ? topTableResponse.report.top : undefined,
176
- error: topTableError,
196
+ loading: tableLoading,
197
+ arrow:
198
+ tableResponse?.report.oneofKind === 'tableArrow'
199
+ ? tableResponse.report.tableArrow
200
+ : undefined,
201
+ error: tableError,
177
202
  }}
178
203
  callgraphData={{
179
204
  loading: callgraphLoading,
@@ -183,6 +208,14 @@ export const ProfileViewWithData = ({
183
208
  : undefined,
184
209
  error: callgraphError,
185
210
  }}
211
+ sourceData={{
212
+ loading: sourceLoading,
213
+ data:
214
+ sourceResponse?.report.oneofKind === 'source'
215
+ ? sourceResponse?.report?.source
216
+ : undefined,
217
+ error: sourceError,
218
+ }}
186
219
  sampleUnit={sampleUnit}
187
220
  profileSource={profileSource}
188
221
  queryClient={queryClient}