@parca/profile 0.12.20 → 0.12.26

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.26](https://github.com/parca-dev/parca/compare/ui-v0.12.25...ui-v0.12.26) (2022-04-28)
7
+
8
+ ## [0.12.24](https://github.com/parca-dev/parca/compare/ui-v0.12.23...ui-v0.12.24) (2022-04-26)
9
+
10
+ **Note:** Version bump only for package @parca/profile
11
+
12
+ ## [0.12.25](https://github.com/parca-dev/parca/compare/ui-v0.12.24...ui-v0.12.25) (2022-04-27)
13
+
14
+ **Note:** Version bump only for package @parca/profile
15
+
16
+ ## [0.12.23](https://github.com/parca-dev/parca/compare/ui-v0.12.22...ui-v0.12.23) (2022-04-25)
17
+
18
+ **Note:** Version bump only for package @parca/profile
19
+
6
20
  ## [0.12.20](https://github.com/parca-dev/parca/compare/ui-v0.12.19...ui-v0.12.20) (2022-04-12)
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.20",
3
+ "version": "0.12.26",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
- "@parca/client": "^0.12.20",
6
+ "@parca/client": "^0.12.26",
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": "72da90d95cc53eda749dbb5dd6b2ea24fe2e6ca2"
22
+ "gitHead": "a5187e802f95bb7ee967121af9c9b74167d95278"
23
23
  }
@@ -5,8 +5,9 @@ import {scaleLinear} from 'd3-scale';
5
5
  import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
6
6
  import {usePopper} from 'react-popper';
7
7
  import {getLastItem, valueFormatter} from '@parca/functions';
8
+ import {useAppSelector, selectDarkMode} from '@parca/store';
8
9
 
9
- const RowHeight = 20;
10
+ const RowHeight = 26;
10
11
 
11
12
  const icicleRectStyles = {
12
13
  cursor: 'pointer',
@@ -65,7 +66,7 @@ function IcicleRect({
65
66
  />
66
67
  {width > 5 && (
67
68
  <svg width={width - 5} height={height}>
68
- <text x={5} y={13} style={{fontSize: '12px'}}>
69
+ <text x={5} y={15} style={{fontSize: '12px'}}>
69
70
  {name}
70
71
  </text>
71
72
  </svg>
@@ -75,7 +76,7 @@ function IcicleRect({
75
76
  }
76
77
 
77
78
  interface IcicleGraphNodesProps {
78
- data: FlamegraphNode.AsObject[];
79
+ data: FlamegraphNode[];
79
80
  x: number;
80
81
  y: number;
81
82
  total: number;
@@ -83,38 +84,20 @@ interface IcicleGraphNodesProps {
83
84
  level: number;
84
85
  curPath: string[];
85
86
  setCurPath: (path: string[]) => void;
86
- setHoveringNode: (
87
- node: FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined
88
- ) => void;
87
+ setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
89
88
  path: string[];
90
89
  xScale: (value: number) => number;
91
90
  }
92
91
 
93
- function diffColor(diff: number, cumulative: number): string {
94
- const prevValue = cumulative - diff;
95
- const diffRatio = prevValue > 0 ? (Math.abs(diff) > 0 ? diff / prevValue : 0) : 1.0;
96
-
97
- const diffTransparency =
98
- Math.abs(diff) > 0 ? Math.min((Math.abs(diffRatio) / 2 + 0.5) * 0.8, 0.8) : 0;
99
- const color =
100
- diff === 0
101
- ? '#90c7e0'
102
- : diff > 0
103
- ? `rgba(221, 46, 69, ${diffTransparency})`
104
- : `rgba(59, 165, 93, ${diffTransparency})`;
105
-
106
- return color;
107
- }
108
-
109
- export function nodeLabel(node: FlamegraphNode.AsObject): string {
92
+ export function nodeLabel(node: FlamegraphNode): string {
110
93
  if (node.meta === undefined) return '<unknown>';
111
94
  const mapping = `${
112
95
  node.meta?.mapping?.file !== undefined && node.meta?.mapping?.file !== ''
113
96
  ? '[' + getLastItem(node.meta.mapping.file) + '] '
114
97
  : ''
115
98
  }`;
116
- if (node.meta.pb_function?.name !== undefined && node.meta.pb_function?.name !== '')
117
- 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;
118
101
 
119
102
  const address = `${
120
103
  node.meta.location?.address !== undefined && node.meta.location?.address !== 0
@@ -126,6 +109,26 @@ export function nodeLabel(node: FlamegraphNode.AsObject): string {
126
109
  return fallback === '' ? '<unknown>' : fallback;
127
110
  }
128
111
 
112
+ function diffColor(diff: number, cumulative: number, isDarkMode: boolean): string {
113
+ const prevValue = cumulative - diff;
114
+ const diffRatio = prevValue > 0 ? (Math.abs(diff) > 0 ? diff / prevValue : 0) : 1.0;
115
+
116
+ const diffTransparency =
117
+ Math.abs(diff) > 0 ? Math.min((Math.abs(diffRatio) / 2 + 0.5) * 0.8, 0.8) : 0;
118
+
119
+ const newSpanColor = isDarkMode ? '#B3BAE1' : '#929FEB';
120
+ const increasedSpanColor = isDarkMode
121
+ ? `rgba(255, 177, 204, ${diffTransparency})`
122
+ : `rgba(254, 153, 187, ${diffTransparency})`;
123
+ const reducedSpanColor = isDarkMode
124
+ ? `rgba(103, 158, 92, ${diffTransparency})`
125
+ : `rgba(164, 214, 153, ${diffTransparency})`;
126
+
127
+ const color = diff === 0 ? newSpanColor : diff > 0 ? increasedSpanColor : reducedSpanColor;
128
+
129
+ return color;
130
+ }
131
+
129
132
  export function IcicleGraphNodes({
130
133
  data,
131
134
  x,
@@ -139,6 +142,8 @@ export function IcicleGraphNodes({
139
142
  setCurPath,
140
143
  curPath,
141
144
  }: IcicleGraphNodesProps) {
145
+ const isDarkMode = useAppSelector(selectDarkMode);
146
+
142
147
  const nodes =
143
148
  curPath.length === 0 ? data : data.filter(d => d != null && curPath[0] === nodeLabel(d));
144
149
 
@@ -147,23 +152,25 @@ export function IcicleGraphNodes({
147
152
  return (
148
153
  <g transform={`translate(${x}, ${y})`}>
149
154
  {nodes.map((d, i) => {
150
- 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);
151
158
 
152
159
  const nextCurPath = curPath.length === 0 ? [] : curPath.slice(1);
153
160
  const width =
154
161
  nextCurPath.length > 0 || (nextCurPath.length === 0 && curPath.length === 1)
155
162
  ? totalWidth
156
- : xScale(d.cumulative);
163
+ : xScale(cumulative);
157
164
 
158
165
  if (width <= 1) {
159
166
  return <></>;
160
167
  }
161
168
 
162
- const key = `${level}-${i}`;
163
169
  const name = nodeLabel(d);
170
+ const key = `${level}-${i}`;
164
171
  const nextPath = path.concat([name]);
165
172
 
166
- const color = diffColor(d.diff === undefined ? 0 : d.diff, d.cumulative);
173
+ const color = diffColor(diff, cumulative, isDarkMode);
167
174
 
168
175
  const onClick = () => {
169
176
  setCurPath(nextPath);
@@ -172,7 +179,7 @@ export function IcicleGraphNodes({
172
179
  const xStart = xScale(start);
173
180
  const newXScale =
174
181
  nextCurPath.length === 0 && curPath.length === 1
175
- ? scaleLinear().domain([0, d.cumulative]).range([0, totalWidth])
182
+ ? scaleLinear().domain([0, cumulative]).range([0, totalWidth])
176
183
  : xScale;
177
184
 
178
185
  const onMouseEnter = () => setHoveringNode(d);
@@ -196,7 +203,7 @@ export function IcicleGraphNodes({
196
203
  {data !== undefined && data.length > 0 && (
197
204
  <IcicleGraphNodes
198
205
  key={`node-${key}`}
199
- data={d.childrenList}
206
+ data={d.children}
200
207
  x={xStart}
201
208
  y={RowHeight}
202
209
  xScale={newXScale}
@@ -223,14 +230,14 @@ interface FlamegraphTooltipProps {
223
230
  y: number;
224
231
  unit: string;
225
232
  total: number;
226
- hoveringNode: FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined;
233
+ hoveringNode: FlamegraphNode | FlamegraphRootNode | undefined;
227
234
  contextElement: Element | null;
228
235
  }
229
236
 
230
237
  const FlamegraphNodeTooltipTableRows = ({
231
238
  hoveringNode,
232
239
  }: {
233
- hoveringNode: FlamegraphNode.AsObject;
240
+ hoveringNode: FlamegraphNode;
234
241
  }): JSX.Element => {
235
242
  if (hoveringNode.meta === undefined) return <></>;
236
243
 
@@ -338,20 +345,21 @@ export const FlamegraphTooltip = ({
338
345
 
339
346
  if (hoveringNode === undefined || hoveringNode == null) return <></>;
340
347
 
341
- const diff = hoveringNode.diff === undefined ? 0 : hoveringNode.diff;
342
- 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;
343
351
  const diffRatio = Math.abs(diff) > 0 ? diff / prevValue : 0;
344
352
  const diffSign = diff > 0 ? '+' : '';
345
353
  const diffValueText = diffSign + valueFormatter(diff, unit, 1);
346
354
  const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
347
355
  const diffText = `${diffValueText} (${diffPercentageText})`;
348
356
 
349
- const hoveringFlamegraphNode = hoveringNode as FlamegraphNode.AsObject;
357
+ const hoveringFlamegraphNode = hoveringNode as FlamegraphNode;
350
358
  const metaRows =
351
359
  hoveringFlamegraphNode.meta === undefined ? (
352
360
  <></>
353
361
  ) : (
354
- <FlamegraphNodeTooltipTableRows hoveringNode={hoveringNode as FlamegraphNode.AsObject} />
362
+ <FlamegraphNodeTooltipTableRows hoveringNode={hoveringNode as FlamegraphNode} />
355
363
  );
356
364
 
357
365
  return (
@@ -369,13 +377,13 @@ export const FlamegraphTooltip = ({
369
377
  <p>root</p>
370
378
  ) : (
371
379
  <>
372
- {hoveringFlamegraphNode.meta.pb_function !== undefined &&
373
- hoveringFlamegraphNode.meta.pb_function.name !== '' ? (
374
- <p>{hoveringFlamegraphNode.meta.pb_function.name}</p>
380
+ {hoveringFlamegraphNode.meta.function !== undefined &&
381
+ hoveringFlamegraphNode.meta.function.name !== '' ? (
382
+ <p>{hoveringFlamegraphNode.meta.function.name}</p>
375
383
  ) : (
376
384
  <>
377
385
  {hoveringFlamegraphNode.meta.location !== undefined &&
378
- hoveringFlamegraphNode.meta.location.address !== 0 ? (
386
+ parseInt(hoveringFlamegraphNode.meta.location.address, 10) !== 0 ? (
379
387
  <p>
380
388
  {'0x' + hoveringFlamegraphNode.meta.location.address.toString(16)}
381
389
  </p>
@@ -393,8 +401,8 @@ export const FlamegraphTooltip = ({
393
401
  <tr>
394
402
  <td className="w-1/5">Cumulative</td>
395
403
  <td className="w-4/5">
396
- {valueFormatter(hoveringNode.cumulative, unit, 2)} (
397
- {((hoveringNode.cumulative * 100) / total).toFixed(2)}%)
404
+ {valueFormatter(hoveringNodeCumulative, unit, 2)} (
405
+ {((hoveringNodeCumulative * 100) / total).toFixed(2)}%)
398
406
  </td>
399
407
  </tr>
400
408
  {hoveringNode.diff !== undefined && diff !== 0 && (
@@ -417,15 +425,13 @@ export const FlamegraphTooltip = ({
417
425
  };
418
426
 
419
427
  interface IcicleGraphRootNodeProps {
420
- node: FlamegraphRootNode.AsObject;
428
+ node: FlamegraphRootNode;
421
429
  xScale: (value: number) => number;
422
430
  total: number;
423
431
  totalWidth: number;
424
432
  curPath: string[];
425
433
  setCurPath: (path: string[]) => void;
426
- setHoveringNode: (
427
- node: FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined
428
- ) => void;
434
+ setHoveringNode: (node: FlamegraphNode | FlamegraphRootNode | undefined) => void;
429
435
  }
430
436
 
431
437
  export function IcicleGraphRootNode({
@@ -437,7 +443,11 @@ export function IcicleGraphRootNode({
437
443
  setCurPath,
438
444
  curPath,
439
445
  }: IcicleGraphRootNodeProps) {
440
- const color = diffColor(node.diff === undefined ? 0 : node.diff, node.cumulative);
446
+ const isDarkMode = useAppSelector(selectDarkMode);
447
+
448
+ const cumulative = parseFloat(node.cumulative);
449
+ const diff = parseFloat(node.diff);
450
+ const color = diffColor(diff, cumulative, isDarkMode);
441
451
 
442
452
  const onClick = () => setCurPath([]);
443
453
  const onMouseEnter = () => setHoveringNode(node);
@@ -459,7 +469,7 @@ export function IcicleGraphRootNode({
459
469
  curPath={curPath}
460
470
  />
461
471
  <MemoizedIcicleGraphNodes
462
- data={node.childrenList}
472
+ data={node.children}
463
473
  x={0}
464
474
  y={RowHeight}
465
475
  xScale={xScale}
@@ -478,15 +488,22 @@ export function IcicleGraphRootNode({
478
488
  const MemoizedIcicleGraphRootNode = React.memo(IcicleGraphRootNode);
479
489
 
480
490
  interface IcicleGraphProps {
481
- graph: Flamegraph.AsObject;
491
+ graph: Flamegraph;
492
+ sampleUnit: string;
482
493
  width?: number;
483
494
  curPath: string[];
484
495
  setCurPath: (path: string[]) => void;
485
496
  }
486
497
 
487
- 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) {
488
505
  const [hoveringNode, setHoveringNode] = useState<
489
- FlamegraphNode.AsObject | FlamegraphRootNode.AsObject | undefined
506
+ FlamegraphNode | FlamegraphRootNode | undefined
490
507
  >();
491
508
  const [pos, setPos] = useState([0, 0]);
492
509
  const [height, setHeight] = useState(0);
@@ -509,19 +526,26 @@ export default function IcicleGraph({graph, width, setCurPath, curPath}: IcicleG
509
526
  throttledSetPos([rel[0], rel[1]]);
510
527
  };
511
528
 
512
- 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]);
513
531
 
514
532
  return (
515
533
  <div onMouseLeave={() => setHoveringNode(undefined)}>
516
534
  <FlamegraphTooltip
517
- unit={graph.unit}
518
- total={graph.total}
535
+ unit={sampleUnit}
536
+ total={total}
519
537
  x={pos[0]}
520
538
  y={pos[1]}
521
539
  hoveringNode={hoveringNode}
522
540
  contextElement={svg.current}
523
541
  />
524
- <svg width={width} height={height} onMouseMove={onMouseMove} ref={svg}>
542
+ <svg
543
+ className="font-robotoMono"
544
+ width={width}
545
+ height={height}
546
+ onMouseMove={onMouseMove}
547
+ ref={svg}
548
+ >
525
549
  <g ref={ref}>
526
550
  <MemoizedIcicleGraphRootNode
527
551
  node={graph.root}
@@ -529,7 +553,7 @@ export default function IcicleGraph({graph, width, setCurPath, curPath}: IcicleG
529
553
  curPath={curPath}
530
554
  setCurPath={setCurPath}
531
555
  xScale={xScale}
532
- total={graph.total}
556
+ total={total}
533
557
  totalWidth={width}
534
558
  />
535
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 => {
@@ -167,7 +134,7 @@ export const TopTable = ({queryClient, profileSource}: ProfileViewProps): JSX.El
167
134
 
168
135
  return (
169
136
  <>
170
- <div className="w-full">
137
+ <div className="w-full font-robotoMono">
171
138
  <table className="iciclegraph-table table-fixed text-left w-full divide-y divide-gray-200 dark:divide-gray-700">
172
139
  <thead className="bg-gray-50 dark:bg-gray-800">
173
140
  <tr>
@@ -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
+ };