@parca/profile 0.12.36 → 0.13.3

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,22 @@
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.13.3](https://github.com/parca-dev/parca/compare/ui-v0.13.2...ui-v0.13.3) (2022-06-07)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.13.1](https://github.com/parca-dev/parca/compare/ui-v0.13.0...ui-v0.13.1) (2022-05-31)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
14
+ # [0.13.0](https://github.com/parca-dev/parca/compare/ui-v0.12.38...ui-v0.13.0) (2022-05-30)
15
+
16
+ ### Features
17
+
18
+ - useContainerDimensions hook for smooth chart resize ([3fe5670](https://github.com/parca-dev/parca/commit/3fe5670cb94e838d83e5cb10d453ee620c2dc3c1))
19
+
20
+ ## [0.12.36](https://github.com/parca-dev/parca/compare/ui-v0.12.35...ui-v0.12.36) (2022-05-06)
21
+
6
22
  ## [0.12.36](https://github.com/parca-dev/parca/compare/ui-v0.12.35...ui-v0.12.36) (2022-05-06)
7
23
 
8
24
  **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.36",
3
+ "version": "0.13.3",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
- "@parca/client": "^0.12.35",
7
- "@parca/dynamicsize": "^0.12.35",
8
- "@parca/parser": "^0.12.35",
6
+ "@parca/client": "^0.13.0",
7
+ "@parca/dynamicsize": "^0.13.0",
8
+ "@parca/parser": "^0.13.0",
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": "bf00c1edfc430b108a4457cc6943e1e160dca89c"
22
+ "gitHead": "f5e7eadd0ffae7a9de6bdd7a57534d26f072c1d3"
23
23
  }
@@ -3,9 +3,9 @@ import {throttle} from 'lodash';
3
3
  import {pointer} from 'd3-selection';
4
4
  import {scaleLinear} from 'd3-scale';
5
5
  import {Flamegraph, FlamegraphNode, FlamegraphRootNode} from '@parca/client';
6
- import {usePopper} from 'react-popper';
7
- import {getLastItem, valueFormatter, diffColor} from '@parca/functions';
8
- import {useAppSelector, selectDarkMode} from '@parca/store';
6
+ import {FlamegraphTooltip} from '@parca/components';
7
+ import {getLastItem, diffColor, isSearchMatch, SEARCH_STRING_COLOR} from '@parca/functions';
8
+ import {useAppSelector, selectDarkMode, selectSearchNodeString} from '@parca/store';
9
9
 
10
10
  interface IcicleGraphProps {
11
11
  graph: Flamegraph;
@@ -29,15 +29,6 @@ interface IcicleGraphNodesProps {
29
29
  xScale: (value: number) => number;
30
30
  }
31
31
 
32
- interface FlamegraphTooltipProps {
33
- x: number;
34
- y: number;
35
- unit: string;
36
- total: number;
37
- hoveringNode: FlamegraphNode | FlamegraphRootNode | undefined;
38
- contextElement: Element | null;
39
- }
40
-
41
32
  interface IcicleGraphRootNodeProps {
42
33
  node: FlamegraphRootNode;
43
34
  xScale: (value: number) => number;
@@ -85,6 +76,7 @@ function IcicleRect({
85
76
  onClick,
86
77
  curPath,
87
78
  }: IcicleRectProps) {
79
+ const currentSearchString = useAppSelector(selectSearchNodeString);
88
80
  const isFaded = curPath.length > 0 && name !== curPath[curPath.length - 1];
89
81
  const styles = isFaded ? fadedIcicleRectStyles : icicleRectStyles;
90
82
 
@@ -102,6 +94,8 @@ function IcicleRect({
102
94
  width={width - 1}
103
95
  height={height - 1}
104
96
  style={{
97
+ opacity:
98
+ Boolean(currentSearchString) && !isSearchMatch(currentSearchString, name) ? 0.5 : 1,
105
99
  fill: color,
106
100
  }}
107
101
  />
@@ -232,196 +226,6 @@ export function IcicleGraphNodes({
232
226
 
233
227
  const MemoizedIcicleGraphNodes = React.memo(IcicleGraphNodes);
234
228
 
235
- const FlamegraphNodeTooltipTableRows = ({
236
- hoveringNode,
237
- }: {
238
- hoveringNode: FlamegraphNode;
239
- }): JSX.Element => {
240
- if (hoveringNode.meta === undefined) return <></>;
241
-
242
- return (
243
- <>
244
- {hoveringNode.meta.pb_function?.filename !== undefined &&
245
- hoveringNode.meta.pb_function?.filename !== '' && (
246
- <tr>
247
- <td className="w-1/5">File</td>
248
- <td className="w-4/5">
249
- {hoveringNode.meta.pb_function.filename}
250
- {hoveringNode.meta.line?.line !== undefined && hoveringNode.meta.line?.line !== 0
251
- ? ` +${hoveringNode.meta.line.line.toString()}`
252
- : `${
253
- hoveringNode.meta.pb_function?.startLine !== undefined &&
254
- hoveringNode.meta.pb_function?.startLine !== 0
255
- ? ` +${hoveringNode.meta.pb_function.startLine.toString()}`
256
- : ''
257
- }`}
258
- </td>
259
- </tr>
260
- )}
261
- {hoveringNode.meta.location?.address !== undefined &&
262
- hoveringNode.meta.location?.address !== 0 && (
263
- <tr>
264
- <td className="w-1/5">Address</td>
265
- <td className="w-4/5">{' 0x' + hoveringNode.meta.location.address.toString(16)}</td>
266
- </tr>
267
- )}
268
- {hoveringNode.meta.mapping !== undefined && hoveringNode.meta.mapping.file !== '' && (
269
- <tr>
270
- <td className="w-1/5">Binary</td>
271
- <td className="w-4/5">{getLastItem(hoveringNode.meta.mapping.file)}</td>
272
- </tr>
273
- )}
274
- </>
275
- );
276
- };
277
-
278
- function generateGetBoundingClientRect(contextElement: Element, x = 0, y = 0) {
279
- const domRect = contextElement.getBoundingClientRect();
280
- return () =>
281
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
282
- ({
283
- width: 0,
284
- height: 0,
285
- top: domRect.y + y,
286
- left: domRect.x + x,
287
- right: domRect.x + x,
288
- bottom: domRect.y + y,
289
- } as ClientRect);
290
- }
291
-
292
- const virtualElement = {
293
- getBoundingClientRect: () =>
294
- // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
295
- ({
296
- width: 0,
297
- height: 0,
298
- top: 0,
299
- left: 0,
300
- right: 0,
301
- bottom: 0,
302
- } as ClientRect),
303
- };
304
-
305
- export const FlamegraphTooltip = ({
306
- x,
307
- y,
308
- unit,
309
- total,
310
- hoveringNode,
311
- contextElement,
312
- }: FlamegraphTooltipProps): JSX.Element => {
313
- const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
314
-
315
- const {styles, attributes, ...popperProps} = usePopper(virtualElement, popperElement, {
316
- placement: 'auto-start',
317
- strategy: 'absolute',
318
- modifiers: [
319
- {
320
- name: 'preventOverflow',
321
- options: {
322
- tether: false,
323
- altAxis: true,
324
- },
325
- },
326
- {
327
- name: 'offset',
328
- options: {
329
- offset: [30, 30],
330
- },
331
- },
332
- ],
333
- });
334
-
335
- const update = popperProps.update;
336
-
337
- useEffect(() => {
338
- if (contextElement != null) {
339
- virtualElement.getBoundingClientRect = generateGetBoundingClientRect(contextElement, x, y);
340
- update?.();
341
- }
342
- }, [x, y, contextElement, update]);
343
-
344
- if (hoveringNode === undefined || hoveringNode == null) return <></>;
345
-
346
- const hoveringNodeCumulative = parseFloat(hoveringNode.cumulative);
347
- const diff = hoveringNode.diff === undefined ? 0 : parseFloat(hoveringNode.diff);
348
- const prevValue = hoveringNodeCumulative - diff;
349
- const diffRatio = Math.abs(diff) > 0 ? diff / prevValue : 0;
350
- const diffSign = diff > 0 ? '+' : '';
351
- const diffValueText = diffSign + valueFormatter(diff, unit, 1);
352
- const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
353
- const diffText = `${diffValueText} (${diffPercentageText})`;
354
-
355
- const hoveringFlamegraphNode = hoveringNode as FlamegraphNode;
356
- const metaRows =
357
- hoveringFlamegraphNode.meta === undefined ? (
358
- <></>
359
- ) : (
360
- <FlamegraphNodeTooltipTableRows hoveringNode={hoveringNode as FlamegraphNode} />
361
- );
362
-
363
- return (
364
- <div ref={setPopperElement} style={styles.popper} {...attributes.popper}>
365
- <div className="flex">
366
- <div className="m-auto">
367
- <div
368
- className="border-gray-300 dark:border-gray-500 bg-gray-50 dark:bg-gray-900 rounded-lg p-3 shadow-lg opacity-90"
369
- style={{borderWidth: 1}}
370
- >
371
- <div className="flex flex-row">
372
- <div className="ml-2 mr-6">
373
- <span className="font-semibold">
374
- {hoveringFlamegraphNode.meta === undefined ? (
375
- <p>root</p>
376
- ) : (
377
- <>
378
- {hoveringFlamegraphNode.meta.function !== undefined &&
379
- hoveringFlamegraphNode.meta.function.name !== '' ? (
380
- <p>{hoveringFlamegraphNode.meta.function.name}</p>
381
- ) : (
382
- <>
383
- {hoveringFlamegraphNode.meta.location !== undefined &&
384
- parseInt(hoveringFlamegraphNode.meta.location.address, 10) !== 0 ? (
385
- <p>
386
- {'0x' + hoveringFlamegraphNode.meta.location.address.toString(16)}
387
- </p>
388
- ) : (
389
- <p>unknown</p>
390
- )}
391
- </>
392
- )}
393
- </>
394
- )}
395
- </span>
396
- <span className="text-gray-700 dark:text-gray-300 my-2">
397
- <table className="table-fixed">
398
- <tbody>
399
- <tr>
400
- <td className="w-1/5">Cumulative</td>
401
- <td className="w-4/5">
402
- {valueFormatter(hoveringNodeCumulative, unit, 2)} (
403
- {((hoveringNodeCumulative * 100) / total).toFixed(2)}%)
404
- </td>
405
- </tr>
406
- {hoveringNode.diff !== undefined && diff !== 0 && (
407
- <tr>
408
- <td className="w-1/5">Diff</td>
409
- <td className="w-4/5">{diffText}</td>
410
- </tr>
411
- )}
412
- {metaRows}
413
- </tbody>
414
- </table>
415
- </span>
416
- </div>
417
- </div>
418
- </div>
419
- </div>
420
- </div>
421
- </div>
422
- );
423
- };
424
-
425
229
  export function IcicleGraphRootNode({
426
230
  node,
427
231
  xScale,
@@ -521,9 +325,9 @@ export default function IcicleGraph({
521
325
  />
522
326
  <svg
523
327
  className="font-robotoMono"
524
- width={width}
525
- height={height}
526
328
  onMouseMove={onMouseMove}
329
+ viewBox={`0 0 ${width} ${height}`}
330
+ preserveAspectRatio="xMinYMid"
527
331
  ref={svg}
528
332
  >
529
333
  <g ref={ref}>
@@ -1,5 +1,6 @@
1
1
  import {Flamegraph} from '@parca/client';
2
2
  import {useAppSelector, selectCompareMode} from '@parca/store';
3
+ import {useContainerDimensions} from '@parca/dynamicsize';
3
4
 
4
5
  import DiffLegend from './components/DiffLegend';
5
6
  import IcicleGraph from './IcicleGraph';
@@ -13,7 +14,6 @@ interface ProfileIcicleGraphProps {
13
14
  }
14
15
 
15
16
  const ProfileIcicleGraph = ({
16
- width,
17
17
  graph,
18
18
  curPath,
19
19
  setNewCurPath,
@@ -25,16 +25,20 @@ const ProfileIcicleGraph = ({
25
25
  const total = graph.total;
26
26
  if (parseFloat(total) === 0) return <>Profile has no samples</>;
27
27
 
28
+ const {ref, dimensions} = useContainerDimensions();
29
+
28
30
  return (
29
31
  <>
30
32
  {compareMode && <DiffLegend />}
31
- <IcicleGraph
32
- width={width}
33
- graph={graph}
34
- curPath={curPath}
35
- setCurPath={setNewCurPath}
36
- sampleUnit={sampleUnit}
37
- />
33
+ <div ref={ref}>
34
+ <IcicleGraph
35
+ width={dimensions?.width}
36
+ graph={graph}
37
+ curPath={curPath}
38
+ setCurPath={setNewCurPath}
39
+ sampleUnit={sampleUnit}
40
+ />
41
+ </div>
38
42
  </>
39
43
  );
40
44
  };
@@ -1,10 +1,7 @@
1
1
  import React, {useEffect, useState} from 'react';
2
- import {CalcWidth} from '@parca/dynamicsize';
3
2
  import {parseParams} from '@parca/functions';
4
- import {QueryServiceClient, QueryResponse, QueryRequest_ReportType} from '@parca/client';
5
- import {RpcError} from '@protobuf-ts/runtime-rpc';
6
- import {Button, Card, Spinner, useGrpcMetadata, useParcaTheme} from '@parca/components';
7
- import * as parca_query_v1alpha1_query_pb from '@parca/client/src/parca/query/v1alpha1/query_pb';
3
+ import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
4
+ import {Button, Card, Input, SearchNodes, useGrpcMetadata, useParcaTheme} from '@parca/components';
8
5
 
9
6
  import ProfileIcicleGraph from './ProfileIcicleGraph';
10
7
  import {ProfileSource} from './ProfileSource';
@@ -127,6 +124,8 @@ export const ProfileView = ({
127
124
  Download pprof
128
125
  </Button>
129
126
  </div>
127
+
128
+ <SearchNodes />
130
129
  </div>
131
130
 
132
131
  <div className="flex ml-auto">
@@ -143,7 +142,7 @@ export const ProfileView = ({
143
142
 
144
143
  <Button
145
144
  variant={`${currentView === 'table' ? 'primary' : 'neutral'}`}
146
- className="rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
145
+ className="items-center rounded-tr-none rounded-br-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
147
146
  onClick={() => switchProfileView('table')}
148
147
  >
149
148
  Table
@@ -151,7 +150,7 @@ export const ProfileView = ({
151
150
 
152
151
  <Button
153
152
  variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
154
- 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"
153
+ className="items-center 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"
155
154
  onClick={() => switchProfileView('both')}
156
155
  >
157
156
  Both
@@ -159,7 +158,7 @@ export const ProfileView = ({
159
158
 
160
159
  <Button
161
160
  variant={`${currentView === 'icicle' ? 'primary' : 'neutral'}`}
162
- className="rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
161
+ className="items-center rounded-tl-none rounded-bl-none w-auto px-8 whitespace-nowrap text-ellipsis no-outline-on-buttons"
163
162
  onClick={() => switchProfileView('icicle')}
164
163
  >
165
164
  Icicle Graph
@@ -172,14 +171,12 @@ export const ProfileView = ({
172
171
  response !== null &&
173
172
  response.report.oneofKind === 'flamegraph' && (
174
173
  <div className="w-full">
175
- <CalcWidth throttle={300} delay={2000}>
176
- <ProfileIcicleGraph
177
- curPath={curPath}
178
- setNewCurPath={setNewCurPath}
179
- graph={response.report.flamegraph}
180
- sampleUnit={sampleUnit}
181
- />
182
- </CalcWidth>
174
+ <ProfileIcicleGraph
175
+ curPath={curPath}
176
+ setNewCurPath={setNewCurPath}
177
+ graph={response.report.flamegraph}
178
+ sampleUnit={sampleUnit}
179
+ />
183
180
  </div>
184
181
  )}
185
182
 
@@ -204,18 +201,14 @@ export const ProfileView = ({
204
201
  </div>
205
202
 
206
203
  <div className="w-1/2">
207
- <CalcWidth throttle={300} delay={2000}>
204
+ {response !== null && response.report.oneofKind === 'flamegraph' && (
208
205
  <ProfileIcicleGraph
209
206
  curPath={curPath}
210
207
  setNewCurPath={setNewCurPath}
211
- graph={
212
- response?.report.oneofKind === 'flamegraph'
213
- ? response.report.flamegraph
214
- : undefined
215
- }
208
+ graph={response.report.flamegraph}
216
209
  sampleUnit={sampleUnit}
217
210
  />
218
- </CalcWidth>
211
+ )}
219
212
  </div>
220
213
  </>
221
214
  )}
package/src/TopTable.tsx CHANGED
@@ -1,7 +1,6 @@
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';
1
+ import React from 'react';
2
+ import {getLastItem, valueFormatter, isSearchMatch, SEARCH_STRING_COLOR} from '@parca/functions';
3
+ import {useAppSelector, selectCompareMode, selectSearchNodeString} from '@parca/store';
5
4
  import {
6
5
  QueryResponse,
7
6
  QueryServiceClient,
@@ -107,6 +106,7 @@ export const TopTable = ({
107
106
  }: ProfileViewProps): JSX.Element => {
108
107
  const {response, error} = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP);
109
108
  const {items, requestSort, sortConfig} = useSortableData(response);
109
+ const currentSearchString = useAppSelector(selectSearchNodeString);
110
110
 
111
111
  const compareMode = useAppSelector(selectCompareMode);
112
112
 
@@ -184,22 +184,36 @@ export const TopTable = ({
184
184
  </tr>
185
185
  </thead>
186
186
  <tbody className="bg-white divide-y divide-gray-200 dark:bg-gray-900 dark:divide-gray-700">
187
- {items?.map((report, index) => (
188
- <tr key={index} className="hover:bg-[#62626212] dark:hover:bg-[#ffffff12]">
189
- <td className="text-xs py-1.5 pl-2">{RowLabel(report.meta)}</td>
190
- <td className="text-xs py-1.5 text-right">
191
- {valueFormatter(report.flat, unit, 2)}
192
- </td>
193
- <td className="text-xs py-1.5 text-right pr-2">
194
- {valueFormatter(report.cumulative, unit, 2)}
195
- </td>
196
- {compareMode && (
187
+ {items?.map((report, index) => {
188
+ const name = RowLabel(report.meta);
189
+ return (
190
+ <tr
191
+ key={index}
192
+ className="hover:bg-[#62626212] dark:hover:bg-[#ffffff12]"
193
+ style={{
194
+ opacity:
195
+ currentSearchString !== undefined &&
196
+ currentSearchString !== '' &&
197
+ !isSearchMatch(currentSearchString, name)
198
+ ? 0.5
199
+ : 1,
200
+ }}
201
+ >
202
+ <td className="text-xs py-1.5 pl-2">{name}</td>
203
+ <td className="text-xs py-1.5 text-right">
204
+ {valueFormatter(report.flat, unit, 2)}
205
+ </td>
197
206
  <td className="text-xs py-1.5 text-right pr-2">
198
- {addPlusSign(valueFormatter(report.diff, unit, 2))}
207
+ {valueFormatter(report.cumulative, unit, 2)}
199
208
  </td>
200
- )}
201
- </tr>
202
- ))}
209
+ {compareMode && (
210
+ <td className="text-xs py-1.5 text-right pr-2">
211
+ {addPlusSign(valueFormatter(report.diff, unit, 2))}
212
+ </td>
213
+ )}
214
+ </tr>
215
+ );
216
+ })}
203
217
  </tbody>
204
218
  </table>
205
219
  </div>