@parca/profile 0.12.23 → 0.12.27

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,20 @@
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.12.27](https://github.com/parca-dev/parca/compare/ui-v0.12.26...ui-v0.12.27) (2022-04-29)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.12.26](https://github.com/parca-dev/parca/compare/ui-v0.12.25...ui-v0.12.26) (2022-04-28)
11
+
12
+ ## [0.12.24](https://github.com/parca-dev/parca/compare/ui-v0.12.23...ui-v0.12.24) (2022-04-26)
13
+
14
+ **Note:** Version bump only for package @parca/profile
15
+
16
+ ## [0.12.25](https://github.com/parca-dev/parca/compare/ui-v0.12.24...ui-v0.12.25) (2022-04-27)
17
+
18
+ **Note:** Version bump only for package @parca/profile
19
+
6
20
  ## [0.12.23](https://github.com/parca-dev/parca/compare/ui-v0.12.22...ui-v0.12.23) (2022-04-25)
7
21
 
8
22
  **Note:** Version bump only for package @parca/profile
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.12.23",
3
+ "version": "0.12.27",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
- "@parca/client": "^0.12.20",
6
+ "@parca/client": "^0.12.27",
7
7
  "@parca/dynamicsize": "^0.12.0",
8
- "@parca/parser": "^0.12.3",
8
+ "@parca/parser": "^0.12.26",
9
9
  "d3-scale": "^4.0.2"
10
10
  },
11
11
  "main": "src/index.tsx",
@@ -19,5 +19,5 @@
19
19
  "access": "public",
20
20
  "registry": "https://registry.npmjs.org/"
21
21
  },
22
- "gitHead": "adfe17cad5627ceb7e3ba6ffa0a3806bf8b7b8b4"
22
+ "gitHead": "5468f69e0d44285ea304bd870ce9dccd25a3e019"
23
23
  }
@@ -76,7 +76,7 @@ function IcicleRect({
76
76
  }
77
77
 
78
78
  interface IcicleGraphNodesProps {
79
- data: FlamegraphNode.AsObject[];
79
+ data: FlamegraphNode[];
80
80
  x: number;
81
81
  y: number;
82
82
  total: number;
@@ -84,22 +84,20 @@ interface IcicleGraphNodesProps {
84
84
  level: number;
85
85
  curPath: string[];
86
86
  setCurPath: (path: string[]) => void;
87
- setHoveringNode: (
88
- node: FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined
89
- ) => void;
87
+ setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
90
88
  path: string[];
91
89
  xScale: (value: number) => number;
92
90
  }
93
91
 
94
- export function nodeLabel(node: FlamegraphNode.AsObject): string {
92
+ export function nodeLabel(node: FlamegraphNode): string {
95
93
  if (node.meta === undefined) return '<unknown>';
96
94
  const mapping = `${
97
95
  node.meta?.mapping?.file !== undefined && node.meta?.mapping?.file !== ''
98
96
  ? '[' + getLastItem(node.meta.mapping.file) + '] '
99
97
  : ''
100
98
  }`;
101
- if (node.meta.pb_function?.name !== undefined && node.meta.pb_function?.name !== '')
102
- return mapping + node.meta.pb_function.name;
99
+ if (node.meta.function?.name !== undefined && node.meta.function?.name !== '')
100
+ return mapping + node.meta.function.name;
103
101
 
104
102
  const address = `${
105
103
  node.meta.location?.address !== undefined && node.meta.location?.address !== 0
@@ -154,23 +152,25 @@ export function IcicleGraphNodes({
154
152
  return (
155
153
  <g transform={`translate(${x}, ${y})`}>
156
154
  {nodes.map((d, i) => {
157
- const start = nodes.slice(0, i).reduce((sum, d) => sum + d.cumulative, 0);
155
+ const cumulative = parseFloat(d.cumulative);
156
+ const diff = parseFloat(d.diff);
157
+ const start = nodes.slice(0, i).reduce((sum, d) => sum + parseFloat(d.cumulative), 0);
158
158
 
159
159
  const nextCurPath = curPath.length === 0 ? [] : curPath.slice(1);
160
160
  const width =
161
161
  nextCurPath.length > 0 || (nextCurPath.length === 0 && curPath.length === 1)
162
162
  ? totalWidth
163
- : xScale(d.cumulative);
163
+ : xScale(cumulative);
164
164
 
165
165
  if (width <= 1) {
166
166
  return <></>;
167
167
  }
168
168
 
169
- const key = `${level}-${i}`;
170
169
  const name = nodeLabel(d);
170
+ const key = `${level}-${i}`;
171
171
  const nextPath = path.concat([name]);
172
172
 
173
- const color = diffColor(d.diff === undefined ? 0 : d.diff, d.cumulative, isDarkMode);
173
+ const color = diffColor(diff, cumulative, isDarkMode);
174
174
 
175
175
  const onClick = () => {
176
176
  setCurPath(nextPath);
@@ -179,7 +179,7 @@ export function IcicleGraphNodes({
179
179
  const xStart = xScale(start);
180
180
  const newXScale =
181
181
  nextCurPath.length === 0 && curPath.length === 1
182
- ? scaleLinear().domain([0, d.cumulative]).range([0, totalWidth])
182
+ ? scaleLinear().domain([0, cumulative]).range([0, totalWidth])
183
183
  : xScale;
184
184
 
185
185
  const onMouseEnter = () => setHoveringNode(d);
@@ -203,7 +203,7 @@ export function IcicleGraphNodes({
203
203
  {data !== undefined && data.length > 0 && (
204
204
  <IcicleGraphNodes
205
205
  key={`node-${key}`}
206
- data={d.childrenList}
206
+ data={d.children}
207
207
  x={xStart}
208
208
  y={RowHeight}
209
209
  xScale={newXScale}
@@ -230,14 +230,14 @@ interface FlamegraphTooltipProps {
230
230
  y: number;
231
231
  unit: string;
232
232
  total: number;
233
- hoveringNode: FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined;
233
+ hoveringNode: FlamegraphNode | FlamegraphRootNode | undefined;
234
234
  contextElement: Element | null;
235
235
  }
236
236
 
237
237
  const FlamegraphNodeTooltipTableRows = ({
238
238
  hoveringNode,
239
239
  }: {
240
- hoveringNode: FlamegraphNode.AsObject;
240
+ hoveringNode: FlamegraphNode;
241
241
  }): JSX.Element => {
242
242
  if (hoveringNode.meta === undefined) return <></>;
243
243
 
@@ -345,20 +345,21 @@ export const FlamegraphTooltip = ({
345
345
 
346
346
  if (hoveringNode === undefined || hoveringNode == null) return <></>;
347
347
 
348
- const diff = hoveringNode.diff === undefined ? 0 : hoveringNode.diff;
349
- const prevValue = hoveringNode.cumulative - diff;
348
+ const hoveringNodeCumulative = parseFloat(hoveringNode.cumulative);
349
+ const diff = hoveringNode.diff === undefined ? 0 : parseFloat(hoveringNode.diff);
350
+ const prevValue = hoveringNodeCumulative - diff;
350
351
  const diffRatio = Math.abs(diff) > 0 ? diff / prevValue : 0;
351
352
  const diffSign = diff > 0 ? '+' : '';
352
353
  const diffValueText = diffSign + valueFormatter(diff, unit, 1);
353
354
  const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
354
355
  const diffText = `${diffValueText} (${diffPercentageText})`;
355
356
 
356
- const hoveringFlamegraphNode = hoveringNode as FlamegraphNode.AsObject;
357
+ const hoveringFlamegraphNode = hoveringNode as FlamegraphNode;
357
358
  const metaRows =
358
359
  hoveringFlamegraphNode.meta === undefined ? (
359
360
  <></>
360
361
  ) : (
361
- <FlamegraphNodeTooltipTableRows hoveringNode={hoveringNode as FlamegraphNode.AsObject} />
362
+ <FlamegraphNodeTooltipTableRows hoveringNode={hoveringNode as FlamegraphNode} />
362
363
  );
363
364
 
364
365
  return (
@@ -376,13 +377,13 @@ export const FlamegraphTooltip = ({
376
377
  <p>root</p>
377
378
  ) : (
378
379
  <>
379
- {hoveringFlamegraphNode.meta.pb_function !== undefined &&
380
- hoveringFlamegraphNode.meta.pb_function.name !== '' ? (
381
- <p>{hoveringFlamegraphNode.meta.pb_function.name}</p>
380
+ {hoveringFlamegraphNode.meta.function !== undefined &&
381
+ hoveringFlamegraphNode.meta.function.name !== '' ? (
382
+ <p>{hoveringFlamegraphNode.meta.function.name}</p>
382
383
  ) : (
383
384
  <>
384
385
  {hoveringFlamegraphNode.meta.location !== undefined &&
385
- hoveringFlamegraphNode.meta.location.address !== 0 ? (
386
+ parseInt(hoveringFlamegraphNode.meta.location.address, 10) !== 0 ? (
386
387
  <p>
387
388
  {'0x' + hoveringFlamegraphNode.meta.location.address.toString(16)}
388
389
  </p>
@@ -400,8 +401,8 @@ export const FlamegraphTooltip = ({
400
401
  <tr>
401
402
  <td className="w-1/5">Cumulative</td>
402
403
  <td className="w-4/5">
403
- {valueFormatter(hoveringNode.cumulative, unit, 2)} (
404
- {((hoveringNode.cumulative * 100) / total).toFixed(2)}%)
404
+ {valueFormatter(hoveringNodeCumulative, unit, 2)} (
405
+ {((hoveringNodeCumulative * 100) / total).toFixed(2)}%)
405
406
  </td>
406
407
  </tr>
407
408
  {hoveringNode.diff !== undefined && diff !== 0 && (
@@ -424,15 +425,13 @@ export const FlamegraphTooltip = ({
424
425
  };
425
426
 
426
427
  interface IcicleGraphRootNodeProps {
427
- node: FlamegraphRootNode.AsObject;
428
+ node: FlamegraphRootNode;
428
429
  xScale: (value: number) => number;
429
430
  total: number;
430
431
  totalWidth: number;
431
432
  curPath: string[];
432
433
  setCurPath: (path: string[]) => void;
433
- setHoveringNode: (
434
- node: FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined
435
- ) => void;
434
+ setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
436
435
  }
437
436
 
438
437
  export function IcicleGraphRootNode({
@@ -446,7 +445,9 @@ export function IcicleGraphRootNode({
446
445
  }: IcicleGraphRootNodeProps) {
447
446
  const isDarkMode = useAppSelector(selectDarkMode);
448
447
 
449
- const color = diffColor(node.diff === undefined ? 0 : node.diff, node.cumulative, isDarkMode);
448
+ const cumulative = parseFloat(node.cumulative);
449
+ const diff = parseFloat(node.diff);
450
+ const color = diffColor(diff, cumulative, isDarkMode);
450
451
 
451
452
  const onClick = () => setCurPath([]);
452
453
  const onMouseEnter = () => setHoveringNode(node);
@@ -468,7 +469,7 @@ export function IcicleGraphRootNode({
468
469
  curPath={curPath}
469
470
  />
470
471
  <MemoizedIcicleGraphNodes
471
- data={node.childrenList}
472
+ data={node.children}
472
473
  x={0}
473
474
  y={RowHeight}
474
475
  xScale={xScale}
@@ -487,15 +488,22 @@ export function IcicleGraphRootNode({
487
488
  const MemoizedIcicleGraphRootNode = React.memo(IcicleGraphRootNode);
488
489
 
489
490
  interface IcicleGraphProps {
490
- graph: Flamegraph.AsObject;
491
+ graph: Flamegraph;
492
+ sampleUnit: string;
491
493
  width?: number;
492
494
  curPath: string[];
493
495
  setCurPath: (path: string[]) => void;
494
496
  }
495
497
 
496
- export default function IcicleGraph({graph, width, setCurPath, curPath}: IcicleGraphProps) {
498
+ export default function IcicleGraph({
499
+ graph,
500
+ width,
501
+ setCurPath,
502
+ curPath,
503
+ sampleUnit,
504
+ }: IcicleGraphProps) {
497
505
  const [hoveringNode, setHoveringNode] = useState<
498
- FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined
506
+ FlamegraphNode | FlamegraphRootNode | undefined
499
507
  >();
500
508
  const [pos, setPos] = useState([0, 0]);
501
509
  const [height, setHeight] = useState(0);
@@ -518,13 +526,14 @@ export default function IcicleGraph({graph, width, setCurPath, curPath}: IcicleG
518
526
  throttledSetPos([rel[0], rel[1]]);
519
527
  };
520
528
 
521
- const xScale = scaleLinear().domain([0, graph.total]).range([0, width]);
529
+ const total = parseFloat(graph.total);
530
+ const xScale = scaleLinear().domain([0, total]).range([0, width]);
522
531
 
523
532
  return (
524
533
  <div onMouseLeave={() => setHoveringNode(undefined)}>
525
534
  <FlamegraphTooltip
526
- unit={graph.unit}
527
- total={graph.total}
535
+ unit={sampleUnit}
536
+ total={total}
528
537
  x={pos[0]}
529
538
  y={pos[1]}
530
539
  hoveringNode={hoveringNode}
@@ -544,7 +553,7 @@ export default function IcicleGraph({graph, width, setCurPath, curPath}: IcicleG
544
553
  curPath={curPath}
545
554
  setCurPath={setCurPath}
546
555
  xScale={xScale}
547
- total={graph.total}
556
+ total={total}
548
557
  totalWidth={width}
549
558
  />
550
559
  </g>
@@ -3,17 +3,32 @@ import {Flamegraph} from '@parca/client';
3
3
 
4
4
  interface ProfileIcicleGraphProps {
5
5
  width?: number;
6
- graph: Flamegraph.AsObject | undefined;
6
+ graph: Flamegraph | undefined;
7
+ sampleUnit: string;
7
8
  curPath: string[] | [];
8
9
  setNewCurPath: (path: string[]) => void;
9
10
  }
10
11
 
11
- const ProfileIcicleGraph = ({width, graph, curPath, setNewCurPath}: ProfileIcicleGraphProps) => {
12
+ const ProfileIcicleGraph = ({
13
+ width,
14
+ graph,
15
+ curPath,
16
+ setNewCurPath,
17
+ sampleUnit,
18
+ }: ProfileIcicleGraphProps) => {
12
19
  if (graph === undefined) return <div>no data...</div>;
13
20
  const total = graph.total;
14
- if (total === 0) return <>Profile has no samples</>;
21
+ if (parseFloat(total) === 0) return <>Profile has no samples</>;
15
22
 
16
- return <IcicleGraph width={width} graph={graph} curPath={curPath} setCurPath={setNewCurPath} />;
23
+ return (
24
+ <IcicleGraph
25
+ width={width}
26
+ graph={graph}
27
+ curPath={curPath}
28
+ setCurPath={setNewCurPath}
29
+ sampleUnit={sampleUnit}
30
+ />
31
+ );
17
32
  };
18
33
 
19
34
  export default ProfileIcicleGraph;
@@ -1,18 +1,19 @@
1
1
  import React from 'react';
2
2
  import {formatDate} from '@parca/functions';
3
- import {Query} from '@parca/parser';
3
+ import {Query, ProfileType} from '@parca/parser';
4
4
  import {
5
5
  Label,
6
6
  QueryRequest,
7
+ QueryRequest_Mode,
8
+ QueryRequest_ReportType,
7
9
  ProfileDiffSelection,
8
- SingleProfile,
9
- MergeProfile,
10
- DiffProfile,
10
+ ProfileDiffSelection_Mode,
11
+ Timestamp,
11
12
  } from '@parca/client';
12
- import {Timestamp} from 'google-protobuf/google/protobuf/timestamp_pb';
13
13
 
14
14
  export interface ProfileSource {
15
15
  QueryRequest: () => QueryRequest;
16
+ ProfileType: () => ProfileType;
16
17
  DiffSelection: () => ProfileDiffSelection;
17
18
  Describe: () => JSX.Element;
18
19
  toString: () => string;
@@ -42,8 +43,8 @@ export function SuffixParams(params: {[key: string]: any}, suffix: string): {[ke
42
43
  );
43
44
  }
44
45
 
45
- export function ParseLabels(labels: string[]): Label.AsObject[] {
46
- return labels.map(function (labelString): Label.AsObject {
46
+ export function ParseLabels(labels: string[]): Label[] {
47
+ return labels.map(function (labelString): Label {
47
48
  const parts = labelString.split('=', 2);
48
49
  return {name: parts[0], value: parts[1]};
49
50
  });
@@ -55,6 +56,7 @@ export function ProfileSelectionFromParams(
55
56
  to: string | undefined,
56
57
  merge: string | undefined,
57
58
  labels: string[] | undefined,
59
+ profileName: string | undefined,
58
60
  time: string | undefined
59
61
  ): ProfileSelection | null {
60
62
  if (
@@ -66,28 +68,30 @@ export function ProfileSelectionFromParams(
66
68
  ) {
67
69
  return new MergedProfileSelection(parseInt(from), parseInt(to), expression);
68
70
  }
69
- if (labels !== undefined && time !== undefined) {
70
- return new SingleProfileSelection(ParseLabels(labels), parseInt(time));
71
+ if (labels !== undefined && time !== undefined && profileName !== undefined) {
72
+ return new SingleProfileSelection(profileName, ParseLabels(labels), parseInt(time));
71
73
  }
72
74
  return null;
73
75
  }
74
76
 
75
77
  export class SingleProfileSelection implements ProfileSelection {
76
- labels: Label.AsObject[];
78
+ profileName: string;
79
+ labels: Label[];
77
80
  time: number;
78
81
 
79
- constructor(labels: Label.AsObject[], time: number) {
82
+ constructor(profileName: string, labels: Label[], time: number) {
83
+ this.profileName = profileName;
80
84
  this.labels = labels;
81
85
  this.time = time;
82
86
  }
83
87
 
84
88
  ProfileName(): string {
85
- const label = this.labels.find(e => e.name === '__name__');
86
- return label !== undefined ? label.value : '';
89
+ return this.profileName;
87
90
  }
88
91
 
89
92
  HistoryParams(): {[key: string]: any} {
90
93
  return {
94
+ profile_name: this.profileName,
91
95
  labels: this.labels.map(label => `${label.name}=${label.value}`),
92
96
  time: this.time,
93
97
  };
@@ -98,7 +102,7 @@ export class SingleProfileSelection implements ProfileSelection {
98
102
  }
99
103
 
100
104
  ProfileSource(): ProfileSource {
101
- return new SingleProfileSource(this.labels, this.time);
105
+ return new SingleProfileSource(this.profileName, this.labels, this.time);
102
106
  }
103
107
  }
104
108
 
@@ -136,50 +140,58 @@ export class MergedProfileSelection implements ProfileSelection {
136
140
  }
137
141
 
138
142
  export class SingleProfileSource implements ProfileSource {
139
- labels: Label.AsObject[];
143
+ profName: string;
144
+ labels: Label[];
140
145
  time: number;
141
146
 
142
- constructor(labels: Label.AsObject[], time: number) {
147
+ constructor(profileName: string, labels: Label[], time: number) {
148
+ this.profName = profileName;
143
149
  this.labels = labels;
144
150
  this.time = time;
145
151
  }
146
152
 
147
153
  query(): string {
148
- const seriesQuery = this.labels.reduce(function (agg: string, label: Label.AsObject) {
149
- return agg + `${label.name}="${label.value}",`;
150
- }, '{');
154
+ const seriesQuery =
155
+ this.profName +
156
+ this.labels.reduce(function (agg: string, label: Label) {
157
+ return agg + `${label.name}="${label.value}",`;
158
+ }, '{');
151
159
  return seriesQuery + '}';
152
160
  }
153
161
 
154
162
  DiffSelection(): ProfileDiffSelection {
155
- const sel = new ProfileDiffSelection();
156
- sel.setMode(ProfileDiffSelection.Mode.MODE_SINGLE_UNSPECIFIED);
157
-
158
- const singleProfile = new SingleProfile();
159
- const ts = new Timestamp();
160
- ts.fromDate(new Date(this.time));
161
- singleProfile.setTime(ts);
162
- singleProfile.setQuery(this.query());
163
- sel.setSingle(singleProfile);
164
-
165
- return sel;
163
+ return {
164
+ options: {
165
+ oneofKind: 'single',
166
+ single: {
167
+ time: Timestamp.fromDate(new Date(this.time)),
168
+ query: this.query(),
169
+ },
170
+ },
171
+ mode: ProfileDiffSelection_Mode.SINGLE_UNSPECIFIED,
172
+ };
166
173
  }
167
174
 
168
175
  QueryRequest(): QueryRequest {
169
- const req = new QueryRequest();
170
- req.setMode(QueryRequest.Mode.MODE_SINGLE_UNSPECIFIED);
171
- const singleQueryRequest = new SingleProfile();
172
- const ts = new Timestamp();
173
- ts.fromDate(new Date(this.time));
174
- singleQueryRequest.setTime(ts);
175
- singleQueryRequest.setQuery(this.query());
176
- req.setSingle(singleQueryRequest);
177
- return req;
176
+ return {
177
+ options: {
178
+ oneofKind: 'single',
179
+ single: {
180
+ time: Timestamp.fromDate(new Date(this.time)),
181
+ query: this.query(),
182
+ },
183
+ },
184
+ reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
185
+ mode: QueryRequest_Mode.SINGLE_UNSPECIFIED,
186
+ };
187
+ }
188
+
189
+ ProfileType(): ProfileType {
190
+ return ProfileType.fromString(this.profName);
178
191
  }
179
192
 
180
193
  profileName(): string {
181
- const label = this.labels.find(e => e.name === '__name__');
182
- return label !== undefined ? label.value : '';
194
+ return this.profName;
183
195
  }
184
196
 
185
197
  Describe(): JSX.Element {
@@ -208,8 +220,8 @@ export class SingleProfileSource implements ProfileSource {
208
220
 
209
221
  stringLabels(): string[] {
210
222
  return this.labels
211
- .filter((label: Label.AsObject) => label.name !== '__name__')
212
- .map((label: Label.AsObject) => `${label.name}=${label.value}`);
223
+ .filter((label: Label) => label.name !== '__name__')
224
+ .map((label: Label) => `${label.name}=${label.value}`);
213
225
  }
214
226
 
215
227
  toString(): string {
@@ -229,19 +241,25 @@ export class ProfileDiffSource implements ProfileSource {
229
241
  }
230
242
 
231
243
  DiffSelection(): ProfileDiffSelection {
232
- return new ProfileDiffSelection();
244
+ throw new Error('Method not implemented.');
233
245
  }
234
246
 
235
247
  QueryRequest(): QueryRequest {
236
- const req = new QueryRequest();
237
- req.setMode(QueryRequest.Mode.MODE_DIFF);
238
- const diffQueryRequest = new DiffProfile();
239
-
240
- diffQueryRequest.setA(this.a.DiffSelection());
241
- diffQueryRequest.setB(this.b.DiffSelection());
248
+ return {
249
+ options: {
250
+ oneofKind: 'diff',
251
+ diff: {
252
+ a: this.a.DiffSelection(),
253
+ b: this.b.DiffSelection(),
254
+ },
255
+ },
256
+ reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
257
+ mode: QueryRequest_Mode.DIFF,
258
+ };
259
+ }
242
260
 
243
- req.setDiff(diffQueryRequest);
244
- return req;
261
+ ProfileType(): ProfileType {
262
+ return this.a.ProfileType();
245
263
  }
246
264
 
247
265
  Describe(): JSX.Element {
@@ -269,45 +287,36 @@ export class MergedProfileSource implements ProfileSource {
269
287
  }
270
288
 
271
289
  DiffSelection(): ProfileDiffSelection {
272
- const sel = new ProfileDiffSelection();
273
- sel.setMode(ProfileDiffSelection.Mode.MODE_MERGE);
274
-
275
- const mergeProfile = new MergeProfile();
276
-
277
- const startTs = new Timestamp();
278
- startTs.fromDate(new Date(this.from));
279
- mergeProfile.setStart(startTs);
280
-
281
- const endTs = new Timestamp();
282
- endTs.fromDate(new Date(this.to));
283
- mergeProfile.setEnd(endTs);
284
-
285
- mergeProfile.setQuery(this.query);
286
-
287
- sel.setMerge(mergeProfile);
288
-
289
- return sel;
290
+ return {
291
+ options: {
292
+ oneofKind: 'merge',
293
+ merge: {
294
+ start: Timestamp.fromDate(new Date(this.from)),
295
+ end: Timestamp.fromDate(new Date(this.to)),
296
+ query: this.query,
297
+ },
298
+ },
299
+ mode: ProfileDiffSelection_Mode.MERGE,
300
+ };
290
301
  }
291
302
 
292
303
  QueryRequest(): QueryRequest {
293
- const req = new QueryRequest();
294
- req.setMode(QueryRequest.Mode.MODE_MERGE);
295
-
296
- const mergeQueryRequest = new MergeProfile();
297
-
298
- const startTs = new Timestamp();
299
- startTs.fromDate(new Date(this.from));
300
- mergeQueryRequest.setStart(startTs);
301
-
302
- const endTs = new Timestamp();
303
- endTs.fromDate(new Date(this.to));
304
- mergeQueryRequest.setEnd(endTs);
305
-
306
- mergeQueryRequest.setQuery(this.query);
307
-
308
- req.setMerge(mergeQueryRequest);
304
+ return {
305
+ options: {
306
+ oneofKind: 'merge',
307
+ merge: {
308
+ start: Timestamp.fromDate(new Date(this.from)),
309
+ end: Timestamp.fromDate(new Date(this.to)),
310
+ query: this.query,
311
+ },
312
+ },
313
+ reportType: QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED,
314
+ mode: QueryRequest_Mode.MERGE,
315
+ };
316
+ }
309
317
 
310
- return req;
318
+ ProfileType(): ProfileType {
319
+ return ProfileType.fromString(Query.parse(this.query).profileName());
311
320
  }
312
321
 
313
322
  Describe(): JSX.Element {
@@ -1,12 +1,14 @@
1
1
  import React, {useEffect, useState} from 'react';
2
2
  import {CalcWidth} from '@parca/dynamicsize';
3
3
  import {parseParams} from '@parca/functions';
4
- import {QueryRequest, QueryResponse, QueryServiceClient, ServiceError} from '@parca/client';
4
+ import {QueryServiceClient, QueryResponse, QueryRequest_ReportType} from '@parca/client';
5
+ import {RpcError} from '@protobuf-ts/runtime-rpc';
5
6
  import {Button, Card, useGrpcMetadata} from '@parca/components';
6
7
  import * as parca_query_v1alpha1_query_pb from '@parca/client/src/parca/query/v1alpha1/query_pb';
7
8
 
8
9
  import ProfileIcicleGraph from './ProfileIcicleGraph';
9
10
  import {ProfileSource} from './ProfileSource';
11
+ import {useQuery} from './useQuery';
10
12
  import TopTable from './TopTable';
11
13
 
12
14
  import './ProfileView.styles.css';
@@ -20,12 +22,6 @@ interface ProfileViewProps {
20
22
  compare?: boolean;
21
23
  }
22
24
 
23
- export interface IQueryResult {
24
- isLoading: boolean;
25
- response: QueryResponse | null;
26
- error: ServiceError | null;
27
- }
28
-
29
25
  function arrayEquals(a, b): boolean {
30
26
  return (
31
27
  Array.isArray(a) &&
@@ -35,41 +31,6 @@ function arrayEquals(a, b): boolean {
35
31
  );
36
32
  }
37
33
 
38
- export const useQuery = (
39
- client: QueryServiceClient,
40
- profileSource: ProfileSource
41
- ): IQueryResult => {
42
- const [result, setResult] = useState<IQueryResult>({
43
- isLoading: false,
44
- response: null,
45
- error: null,
46
- });
47
- const metadata = useGrpcMetadata();
48
-
49
- useEffect(() => {
50
- setResult({
51
- ...result,
52
- isLoading: true,
53
- });
54
- const req = profileSource.QueryRequest();
55
- req.setReportType(QueryRequest.ReportType.REPORT_TYPE_FLAMEGRAPH_UNSPECIFIED);
56
-
57
- client.query(
58
- req,
59
- metadata,
60
- (error: ServiceError | null, responseMessage: QueryResponse | null) => {
61
- setResult({
62
- isLoading: false,
63
- response: responseMessage,
64
- error: error,
65
- });
66
- }
67
- );
68
- }, [client, profileSource]);
69
-
70
- return result;
71
- };
72
-
73
34
  export const ProfileView = ({
74
35
  queryClient,
75
36
  profileSource,
@@ -79,7 +40,11 @@ export const ProfileView = ({
79
40
  const currentViewFromURL = router.currentProfileView as string;
80
41
  const [curPath, setCurPath] = useState<string[]>([]);
81
42
  const [isLoaderVisible, setIsLoaderVisible] = useState<boolean>(false);
82
- const {isLoading, response, error} = useQuery(queryClient, profileSource);
43
+ const {isLoading, response, error} = useQuery(
44
+ queryClient,
45
+ profileSource,
46
+ QueryRequest_ReportType.FLAMEGRAPH_UNSPECIFIED
47
+ );
83
48
  const [currentView, setCurrentView] = useState<string | undefined>(currentViewFromURL);
84
49
  const grpcMetadata = useGrpcMetadata();
85
50
 
@@ -94,7 +59,7 @@ export const ProfileView = ({
94
59
  setIsLoaderVisible(false);
95
60
  }
96
61
  return () => clearTimeout(showLoaderTimeout);
97
- }, [isLoading]);
62
+ }, [isLoading, isLoaderVisible]);
98
63
 
99
64
  if (isLoaderVisible) {
100
65
  return (
@@ -132,40 +97,35 @@ export const ProfileView = ({
132
97
  );
133
98
  }
134
99
 
135
- if (error) {
100
+ if (error !== null) {
136
101
  return <div className="p-10 flex justify-center">An error occurred: {error.message}</div>;
137
102
  }
138
103
 
139
104
  const downloadPProf = (e: React.MouseEvent<HTMLElement>) => {
140
105
  e.preventDefault();
141
106
 
142
- const req = profileSource.QueryRequest();
143
- req.setReportType(QueryRequest.ReportType.REPORT_TYPE_PPROF);
107
+ const req = {
108
+ ...profileSource.QueryRequest(),
109
+ reportType: QueryRequest_ReportType.PPROF,
110
+ };
144
111
 
145
- queryClient.query(
146
- req,
147
- grpcMetadata,
148
- (
149
- error: ServiceError | null,
150
- responseMessage: parca_query_v1alpha1_query_pb.QueryResponse | null
151
- ) => {
152
- if (error != null) {
153
- console.error('Error while querying', error);
112
+ queryClient
113
+ .query(req, grpcMetadata)
114
+ .response.then(response => {
115
+ if (response.report.oneofKind !== 'pprof') {
116
+ console.log('Expected pprof report, got:', response.report.oneofKind);
154
117
  return;
155
118
  }
156
- if (responseMessage !== null) {
157
- const bytes = responseMessage.getPprof();
158
- const blob = new Blob([bytes], {type: 'application/octet-stream'});
159
-
160
- const link = document.createElement('a');
161
- link.href = window.URL.createObjectURL(blob);
162
- link.download = 'profile.pb.gz';
163
- link.click();
164
- } else {
165
- console.error(error);
166
- }
167
- }
168
- );
119
+ const blob = new Blob([response.report.pprof], {type: 'application/octet-stream'});
120
+
121
+ const link = document.createElement('a');
122
+ link.href = window.URL.createObjectURL(blob);
123
+ link.download = 'profile.pb.gz';
124
+ link.click();
125
+ })
126
+ .catch(error => {
127
+ console.error('Error while querying', error);
128
+ });
169
129
  };
170
130
 
171
131
  const resetIcicleGraph = () => setCurPath([]);
@@ -177,13 +137,15 @@ export const ProfileView = ({
177
137
  };
178
138
 
179
139
  const switchProfileView = (view: string) => {
180
- if (!navigateTo) return;
140
+ if (navigateTo === undefined) return;
181
141
 
182
142
  setCurrentView(view);
183
143
 
184
144
  navigateTo('/', {...router, ...{currentProfileView: view}});
185
145
  };
186
146
 
147
+ const sampleUnit = profileSource.ProfileType().sampleUnit;
148
+
187
149
  return (
188
150
  <>
189
151
  <div className="py-3">
@@ -204,7 +166,7 @@ export const ProfileView = ({
204
166
  color="neutral"
205
167
  onClick={resetIcicleGraph}
206
168
  disabled={curPath.length === 0}
207
- additionalClasses="whitespace-nowrap text-ellipsis"
169
+ className="whitespace-nowrap text-ellipsis"
208
170
  >
209
171
  Reset View
210
172
  </Button>
@@ -212,7 +174,7 @@ export const ProfileView = ({
212
174
 
213
175
  <Button
214
176
  color={`${currentView === 'table' ? 'primary' : 'neutral'}`}
215
- additionalClasses={`rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons`}
177
+ className="rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
216
178
  onClick={() => switchProfileView('table')}
217
179
  >
218
180
  Table
@@ -220,7 +182,7 @@ export const ProfileView = ({
220
182
 
221
183
  <Button
222
184
  color={`${currentView === 'both' ? 'primary' : 'neutral'}`}
223
- additionalClasses={`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`}
185
+ className="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"
224
186
  onClick={() => switchProfileView('both')}
225
187
  >
226
188
  Both
@@ -228,7 +190,7 @@ export const ProfileView = ({
228
190
 
229
191
  <Button
230
192
  color={`${currentView === 'icicle' ? 'primary' : 'neutral'}`}
231
- additionalClasses={`rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons`}
193
+ className="rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
232
194
  onClick={() => switchProfileView('icicle')}
233
195
  >
234
196
  Icicle Graph
@@ -237,28 +199,39 @@ export const ProfileView = ({
237
199
  </div>
238
200
 
239
201
  <div className="flex space-x-4 justify-between">
240
- {currentView === 'icicle' && (
241
- <div className="w-full">
242
- <CalcWidth throttle={300} delay={2000}>
243
- <ProfileIcicleGraph
244
- curPath={curPath}
245
- setNewCurPath={setNewCurPath}
246
- graph={response?.getFlamegraph()?.toObject()}
247
- />
248
- </CalcWidth>
249
- </div>
250
- )}
202
+ {currentView === 'icicle' &&
203
+ response !== null &&
204
+ response.report.oneofKind === 'flamegraph' && (
205
+ <div className="w-full">
206
+ <CalcWidth throttle={300} delay={2000}>
207
+ <ProfileIcicleGraph
208
+ curPath={curPath}
209
+ setNewCurPath={setNewCurPath}
210
+ graph={response.report.flamegraph}
211
+ sampleUnit={sampleUnit}
212
+ />
213
+ </CalcWidth>
214
+ </div>
215
+ )}
251
216
 
252
217
  {currentView === 'table' && (
253
218
  <div className="w-full">
254
- <TopTable queryClient={queryClient} profileSource={profileSource} />
219
+ <TopTable
220
+ queryClient={queryClient}
221
+ profileSource={profileSource}
222
+ sampleUnit={sampleUnit}
223
+ />
255
224
  </div>
256
225
  )}
257
226
 
258
227
  {currentView === 'both' && (
259
228
  <>
260
229
  <div className="w-1/2">
261
- <TopTable queryClient={queryClient} profileSource={profileSource} />
230
+ <TopTable
231
+ queryClient={queryClient}
232
+ profileSource={profileSource}
233
+ sampleUnit={sampleUnit}
234
+ />
262
235
  </div>
263
236
 
264
237
  <div className="w-1/2">
@@ -266,7 +239,12 @@ export const ProfileView = ({
266
239
  <ProfileIcicleGraph
267
240
  curPath={curPath}
268
241
  setNewCurPath={setNewCurPath}
269
- graph={response?.getFlamegraph()?.toObject()}
242
+ graph={
243
+ response?.report.oneofKind === 'flamegraph'
244
+ ? response.report.flamegraph
245
+ : undefined
246
+ }
247
+ sampleUnit={sampleUnit}
270
248
  />
271
249
  </CalcWidth>
272
250
  </div>
package/src/TopTable.tsx CHANGED
@@ -1,27 +1,21 @@
1
1
  import React, {useEffect, useState} from 'react';
2
+ import * as parca_query_v1alpha1_query_pb from '@parca/client/src/parca/query/v1alpha1/query_pb';
3
+ import {getLastItem, valueFormatter} from '@parca/functions';
4
+ import {useAppSelector, selectCompareMode} from '@parca/store';
2
5
  import {
3
- QueryRequest,
4
6
  QueryResponse,
5
7
  QueryServiceClient,
6
- ServiceError,
8
+ QueryRequest_ReportType,
7
9
  TopNodeMeta,
8
10
  } from '@parca/client';
9
- import * as parca_query_v1alpha1_query_pb from '@parca/client/src/parca/query/v1alpha1/query_pb';
10
- import {getLastItem, valueFormatter} from '@parca/functions';
11
- import {useGrpcMetadata} from '@parca/components';
12
- import {useAppSelector, selectCompareMode} from '@parca/store';
13
-
14
11
  import {ProfileSource} from './ProfileSource';
12
+ import {useQuery} from './useQuery';
15
13
  import './TopTable.styles.css';
16
14
 
17
15
  interface ProfileViewProps {
18
16
  queryClient: QueryServiceClient;
19
17
  profileSource: ProfileSource;
20
- }
21
-
22
- export interface IQueryResult {
23
- response: QueryResponse | null;
24
- error: ServiceError | null;
18
+ sampleUnit: string;
25
19
  }
26
20
 
27
21
  const Arrow = ({direction}: {direction: string | undefined}) => {
@@ -47,11 +41,12 @@ const useSortableData = (
47
41
  config
48
42
  );
49
43
 
50
- const rawTableReport = response?.toObject().top?.listList;
44
+ const rawTableReport =
45
+ response !== null && response.report.oneofKind === 'top' ? response.report.top.list : [];
51
46
 
52
- const items = rawTableReport?.map(node => ({
47
+ const items = rawTableReport.map(node => ({
53
48
  ...node,
54
- name: node.meta?.pb_function?.name,
49
+ name: node.meta?.function?.name,
55
50
  }));
56
51
 
57
52
  const sortedItems = React.useMemo(() => {
@@ -83,71 +78,43 @@ const useSortableData = (
83
78
  return {items: sortedItems, requestSort, sortConfig};
84
79
  };
85
80
 
86
- export const useQuery = (
87
- client: QueryServiceClient,
88
- profileSource: ProfileSource
89
- ): IQueryResult => {
90
- const [result, setResult] = useState<IQueryResult>({
91
- response: null,
92
- error: null,
93
- });
94
- const metadata = useGrpcMetadata();
95
-
96
- useEffect(() => {
97
- const req = profileSource.QueryRequest();
98
- req.setReportType(QueryRequest.ReportType.REPORT_TYPE_TOP);
99
-
100
- client.query(
101
- req,
102
- metadata,
103
- (
104
- error: ServiceError | null,
105
- responseMessage: parca_query_v1alpha1_query_pb.QueryResponse | null
106
- ) => {
107
- setResult({
108
- response: responseMessage,
109
- error: error,
110
- });
111
- }
112
- );
113
- }, [client, profileSource]);
114
-
115
- return result;
116
- };
117
-
118
- export const RowLabel = (meta: TopNodeMeta.AsObject | undefined): string => {
81
+ export const RowLabel = (meta: TopNodeMeta | undefined): string => {
119
82
  if (meta === undefined) return '<unknown>';
120
83
  const mapping = `${
121
84
  meta?.mapping?.file !== undefined && meta?.mapping?.file !== ''
122
85
  ? `[${getLastItem(meta.mapping.file)}]`
123
86
  : ''
124
87
  }`;
125
- if (meta.pb_function?.name !== undefined && meta.pb_function?.name !== '')
126
- return `${mapping} ${meta.pb_function.name}`;
88
+ if (meta.function?.name !== undefined && meta.function?.name !== '')
89
+ return `${mapping} ${meta.function.name}`;
127
90
 
91
+ const addr = parseInt(meta.location?.address ?? '0', 10);
128
92
  const address = `${
129
- meta.location?.address !== undefined && meta.location?.address !== 0
130
- ? `0x${meta.location.address.toString(16)}`
131
- : ''
93
+ meta.location?.address !== undefined && addr !== 0 ? `0x${addr.toString(16)}` : ''
132
94
  }`;
133
95
  const fallback = `${mapping} ${address}`;
134
96
 
135
97
  return fallback === '' ? '<unknown>' : fallback;
136
98
  };
137
99
 
138
- export const TopTable = ({queryClient, profileSource}: ProfileViewProps): JSX.Element => {
139
- const {response, error} = useQuery(queryClient, profileSource);
100
+ export const TopTable = ({
101
+ queryClient,
102
+ profileSource,
103
+ sampleUnit,
104
+ }: ProfileViewProps): JSX.Element => {
105
+ const {response, error} = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP);
140
106
  const {items, requestSort, sortConfig} = useSortableData(response);
141
107
 
142
108
  const compareMode = useAppSelector(selectCompareMode);
143
109
 
144
- const unit = response?.toObject().top?.unit as string;
110
+ const unit = sampleUnit;
145
111
 
146
112
  if (error != null) {
147
113
  return <div className="p-10 flex justify-center">An error occurred: {error.message}</div>;
148
114
  }
149
115
 
150
- const total = response?.toObject().top?.listList.length;
116
+ const total =
117
+ response !== null && response.report.oneofKind === 'top' ? response.report.top.list.length : 0;
151
118
  if (total === 0) return <>Profile has no samples</>;
152
119
 
153
120
  const getClassNamesFor = name => {
@@ -0,0 +1,42 @@
1
+ import React, {useEffect, useState} from 'react';
2
+ import {QueryServiceClient, QueryResponse, QueryRequest_ReportType} from '@parca/client';
3
+ import {RpcError} from '@protobuf-ts/runtime-rpc';
4
+ import {useGrpcMetadata} from '@parca/components';
5
+ import {ProfileSource} from './ProfileSource';
6
+
7
+ export interface IQueryResult {
8
+ response: QueryResponse | null;
9
+ error: RpcError | null;
10
+ isLoading: boolean;
11
+ }
12
+
13
+ export const useQuery = (
14
+ client: QueryServiceClient,
15
+ profileSource: ProfileSource,
16
+ reportType: QueryRequest_ReportType
17
+ ): IQueryResult => {
18
+ const [result, setResult] = useState<IQueryResult>({
19
+ response: null,
20
+ error: null,
21
+ isLoading: false,
22
+ });
23
+ const metadata = useGrpcMetadata();
24
+
25
+ useEffect(() => {
26
+ setResult({
27
+ response: null,
28
+ error: null,
29
+ isLoading: true,
30
+ });
31
+ const req = profileSource.QueryRequest();
32
+ req.reportType = reportType;
33
+
34
+ const call = client.query(req, metadata);
35
+
36
+ call.response
37
+ .then(response => setResult({response: response, error: null, isLoading: false}))
38
+ .catch(error => setResult({error: error, response: null, isLoading: false}));
39
+ }, [client, profileSource, metadata, reportType]);
40
+
41
+ return result;
42
+ };