@parca/profile 0.13.0 → 0.13.4

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.13.4](https://github.com/parca-dev/parca/compare/ui-v0.13.3...ui-v0.13.4) (2022-06-07)
7
+
8
+ ## [0.13.2](https://github.com/parca-dev/parca/compare/ui-v0.13.1...ui-v0.13.2) (2022-05-31)
9
+
10
+ **Note:** Version bump only for package @parca/profile
11
+
12
+ ## [0.13.3](https://github.com/parca-dev/parca/compare/ui-v0.13.2...ui-v0.13.3) (2022-06-07)
13
+
14
+ **Note:** Version bump only for package @parca/profile
15
+
16
+ ## [0.13.1](https://github.com/parca-dev/parca/compare/ui-v0.13.0...ui-v0.13.1) (2022-05-31)
17
+
18
+ **Note:** Version bump only for package @parca/profile
19
+
6
20
  # [0.13.0](https://github.com/parca-dev/parca/compare/ui-v0.12.38...ui-v0.13.0) (2022-05-30)
7
21
 
8
22
  ### Features
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.13.0",
3
+ "version": "0.13.4",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@parca/client": "^0.13.0",
@@ -19,5 +19,5 @@
19
19
  "access": "public",
20
20
  "registry": "https://registry.npmjs.org/"
21
21
  },
22
- "gitHead": "4d00bb1f08c62bc8c33285f6844a0a4386f31442"
22
+ "gitHead": "4ad4fb5220f1cddd26f23bddc3b58e61166aa2d9"
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,8 +325,9 @@ export default function IcicleGraph({
521
325
  />
522
326
  <svg
523
327
  className="font-robotoMono"
328
+ width={width}
329
+ height={height}
524
330
  onMouseMove={onMouseMove}
525
- viewBox={`0 0 ${width} ${height}`}
526
331
  preserveAspectRatio="xMinYMid"
527
332
  ref={svg}
528
333
  >
@@ -1,7 +1,7 @@
1
1
  import React, {useEffect, useState} from 'react';
2
2
  import {parseParams} from '@parca/functions';
3
3
  import {QueryServiceClient, QueryRequest_ReportType} from '@parca/client';
4
- import {Button, Card, useGrpcMetadata, useParcaTheme} from '@parca/components';
4
+ import {Button, Card, Input, SearchNodes, useGrpcMetadata, useParcaTheme} from '@parca/components';
5
5
 
6
6
  import ProfileIcicleGraph from './ProfileIcicleGraph';
7
7
  import {ProfileSource} from './ProfileSource';
@@ -124,6 +124,8 @@ export const ProfileView = ({
124
124
  Download pprof
125
125
  </Button>
126
126
  </div>
127
+
128
+ <SearchNodes />
127
129
  </div>
128
130
 
129
131
  <div className="flex ml-auto">
@@ -140,7 +142,7 @@ export const ProfileView = ({
140
142
 
141
143
  <Button
142
144
  variant={`${currentView === 'table' ? 'primary' : 'neutral'}`}
143
- 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"
144
146
  onClick={() => switchProfileView('table')}
145
147
  >
146
148
  Table
@@ -148,7 +150,7 @@ export const ProfileView = ({
148
150
 
149
151
  <Button
150
152
  variant={`${currentView === 'both' ? 'primary' : 'neutral'}`}
151
- 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"
152
154
  onClick={() => switchProfileView('both')}
153
155
  >
154
156
  Both
@@ -156,7 +158,7 @@ export const ProfileView = ({
156
158
 
157
159
  <Button
158
160
  variant={`${currentView === 'icicle' ? 'primary' : 'neutral'}`}
159
- 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"
160
162
  onClick={() => switchProfileView('icicle')}
161
163
  >
162
164
  Icicle Graph
@@ -199,16 +201,14 @@ export const ProfileView = ({
199
201
  </div>
200
202
 
201
203
  <div className="w-1/2">
202
- <ProfileIcicleGraph
203
- curPath={curPath}
204
- setNewCurPath={setNewCurPath}
205
- graph={
206
- response?.report.oneofKind === 'flamegraph'
207
- ? response.report.flamegraph
208
- : undefined
209
- }
210
- sampleUnit={sampleUnit}
211
- />
204
+ {response !== null && response.report.oneofKind === 'flamegraph' && (
205
+ <ProfileIcicleGraph
206
+ curPath={curPath}
207
+ setNewCurPath={setNewCurPath}
208
+ graph={response.report.flamegraph}
209
+ sampleUnit={sampleUnit}
210
+ />
211
+ )}
212
212
  </div>
213
213
  </>
214
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>