@parca/profile 0.16.227 → 0.16.229

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/GraphTooltipArrow/Content.d.ts +3 -1
  3. package/dist/GraphTooltipArrow/Content.js +44 -6
  4. package/dist/GraphTooltipArrow/ExpandOnHoverValue.js +1 -1
  5. package/dist/MetricsGraph/useMetricsGraphDimensions.js +9 -0
  6. package/dist/ProfileIcicleGraph/IcicleGraphArrow/index.js +1 -1
  7. package/dist/ProfileView/ProfileViewContext.d.ts +14 -0
  8. package/dist/ProfileView/ProfileViewContext.js +30 -0
  9. package/dist/ProfileView/ViewSelector.js +1 -0
  10. package/dist/ProfileView/index.d.ts +11 -4
  11. package/dist/ProfileView/index.js +21 -12
  12. package/dist/ProfileViewWithData.js +35 -11
  13. package/dist/SourceView/Highlighter.d.ts +16 -0
  14. package/dist/SourceView/Highlighter.js +80 -0
  15. package/dist/SourceView/LineNo.d.ts +6 -0
  16. package/dist/SourceView/LineNo.js +23 -0
  17. package/dist/SourceView/index.d.ts +11 -0
  18. package/dist/SourceView/index.js +37 -0
  19. package/dist/Table/index.d.ts +14 -0
  20. package/dist/Table/index.js +184 -0
  21. package/dist/styles.css +1 -1
  22. package/dist/useQuery.d.ts +3 -0
  23. package/dist/useQuery.js +17 -1
  24. package/package.json +10 -8
  25. package/src/GraphTooltipArrow/Content.tsx +88 -9
  26. package/src/GraphTooltipArrow/ExpandOnHoverValue.tsx +1 -1
  27. package/src/MetricsGraph/useMetricsGraphDimensions.ts +9 -0
  28. package/src/ProfileIcicleGraph/IcicleGraphArrow/index.tsx +1 -0
  29. package/src/ProfileView/ProfileViewContext.tsx +52 -0
  30. package/src/ProfileView/ViewSelector.tsx +1 -0
  31. package/src/ProfileView/index.tsx +136 -99
  32. package/src/ProfileViewWithData.tsx +48 -15
  33. package/src/SourceView/Highlighter.tsx +196 -0
  34. package/src/SourceView/LineNo.tsx +31 -0
  35. package/src/SourceView/index.tsx +74 -0
  36. package/src/Table/index.tsx +285 -0
  37. package/src/useQuery.tsx +20 -1
package/CHANGELOG.md CHANGED
@@ -3,6 +3,14 @@
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.16.229](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.228...@parca/profile@0.16.229) (2023-08-23)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.228](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.227...@parca/profile@0.16.228) (2023-08-23)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.227](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.225...@parca/profile@0.16.227) (2023-08-21)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Table } from 'apache-arrow';
3
+ import { type NavigateFunction } from '@parca/utilities';
3
4
  interface GraphTooltipArrowContentProps {
4
5
  table: Table<any>;
5
6
  unit: string;
@@ -8,6 +9,7 @@ interface GraphTooltipArrowContentProps {
8
9
  row: number | null;
9
10
  level: number;
10
11
  isFixed: boolean;
12
+ navigateTo: NavigateFunction;
11
13
  }
12
- declare const GraphTooltipArrowContent: ({ table, unit, total, totalUnfiltered, row, level, isFixed, }: GraphTooltipArrowContentProps) => React.JSX.Element;
14
+ declare const GraphTooltipArrowContent: ({ table, unit, total, totalUnfiltered, row, level, isFixed, navigateTo, }: GraphTooltipArrowContentProps) => React.JSX.Element;
13
15
  export default GraphTooltipArrowContent;
@@ -13,16 +13,21 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
13
13
  // limitations under the License.
14
14
  import { useState } from 'react';
15
15
  import { CopyToClipboard } from 'react-copy-to-clipboard';
16
+ import { Tooltip } from 'react-tooltip';
17
+ import { QueryRequest_ReportType } from '@parca/client';
18
+ import { Button, useParcaContext, useURLState } from '@parca/components';
16
19
  import { divide, getLastItem, valueFormatter } from '@parca/utilities';
17
20
  import { FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_FILE_NAME, FIELD_FUNCTION_START_LINE, FIELD_LOCATION_ADDRESS, FIELD_LOCATION_LINE, FIELD_MAPPING_BUILD_ID, FIELD_MAPPING_FILE, } from '../ProfileIcicleGraph/IcicleGraphArrow';
18
21
  import { nodeLabel } from '../ProfileIcicleGraph/IcicleGraphArrow/utils';
22
+ import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
23
+ import { useQuery } from '../useQuery';
19
24
  import { hexifyAddress, truncateString, truncateStringReverse } from '../utils';
20
25
  import { ExpandOnHover } from './ExpandOnHoverValue';
21
26
  let timeoutHandle = null;
22
27
  const NoData = () => {
23
28
  return _jsx("span", { className: "rounded bg-gray-200 px-2 dark:bg-gray-800", children: "Not available" });
24
29
  };
25
- const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, level, isFixed, }) => {
30
+ const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, level, isFixed, navigateTo, }) => {
26
31
  const [isCopied, setIsCopied] = useState(false);
27
32
  if (row === null) {
28
33
  return _jsx(_Fragment, {});
@@ -44,7 +49,6 @@ const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, le
44
49
  const diffPercentageText = diffSign + (diffRatio * 100).toFixed(2) + '%';
45
50
  const diffText = `${diffValueText} (${diffPercentageText})`;
46
51
  const name = nodeLabel(table, row, level, false);
47
- console.log(level, row, name);
48
52
  const getTextForCumulative = (hoveringNodeCumulative) => {
49
53
  const filtered = totalUnfiltered > total
50
54
  ? ` / ${(100 * divide(hoveringNodeCumulative, total)).toFixed(2)}% of filtered`
@@ -52,12 +56,12 @@ const GraphTooltipArrowContent = ({ table, unit, total, totalUnfiltered, row, le
52
56
  return `${valueFormatter(hoveringNodeCumulative, unit, 2)}
53
57
  (${(100 * divide(hoveringNodeCumulative, totalUnfiltered)).toFixed(2)}%${filtered})`;
54
58
  };
55
- return (_jsx("div", { className: `flex text-sm ${isFixed ? 'w-full' : ''}`, children: _jsx("div", { className: `m-auto w-full ${isFixed ? 'w-full' : ''}`, children: _jsxs("div", { className: "min-h-52 flex w-[500px] flex-col justify-between rounded-lg border border-gray-300 bg-gray-50 p-3 shadow-lg dark:border-gray-500 dark:bg-gray-900", children: [_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "mx-2", children: [_jsx("div", { className: "flex h-10 items-center break-all font-semibold", children: row === 0 ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: name !== '' ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: name, children: _jsx("button", { className: "cursor-pointer text-left", children: name }) })) : (_jsx(_Fragment, { children: locationAddress !== 0n ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer text-left", children: hexifyAddress(locationAddress) }) })) : (_jsx("p", { children: "unknown" })) })) })) }), _jsx("table", { className: "my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Cumulative" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: getTextForCumulative(cumulative), children: _jsx("button", { className: "cursor-pointer", children: getTextForCumulative(cumulative) }) }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: diffText, children: _jsx("button", { className: "cursor-pointer", children: diffText }) }) })] })), _jsx(TooltipMetaInfo, { table: table, row: row, onCopy: onCopy })] }) })] }) }), _jsx("span", { className: "mx-2 block text-xs text-gray-500", children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' })] }) }) }));
59
+ return (_jsx("div", { className: `flex text-sm ${isFixed ? 'w-full' : ''}`, children: _jsx("div", { className: `m-auto w-full ${isFixed ? 'w-full' : ''}`, children: _jsxs("div", { className: "min-h-52 flex w-[500px] flex-col justify-between rounded-lg border border-gray-300 bg-gray-50 p-3 shadow-lg dark:border-gray-500 dark:bg-gray-900", children: [_jsx("div", { className: "flex flex-row", children: _jsxs("div", { className: "mx-2", children: [_jsx("div", { className: "flex h-10 items-center break-all font-semibold", children: row === 0 ? (_jsx("p", { children: "root" })) : (_jsx(_Fragment, { children: name !== '' ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: name, children: _jsx("button", { className: "cursor-pointer text-left", children: name }) })) : (_jsx(_Fragment, { children: locationAddress !== 0n ? (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer text-left", children: hexifyAddress(locationAddress) }) })) : (_jsx("p", { children: "unknown" })) })) })) }), _jsx("table", { className: "my-2 w-full table-fixed pr-0 text-gray-700 dark:text-gray-300", children: _jsxs("tbody", { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Cumulative" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: getTextForCumulative(cumulative), children: _jsx("button", { className: "cursor-pointer", children: getTextForCumulative(cumulative) }) }) })] }), diff !== 0n && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Diff" }), _jsx("td", { className: "w-3/4", children: _jsx(CopyToClipboard, { onCopy: onCopy, text: diffText, children: _jsx("button", { className: "cursor-pointer", children: diffText }) }) })] })), _jsx(TooltipMetaInfo, { table: table, row: row, onCopy: onCopy, navigateTo: navigateTo })] }) })] }) }), _jsx("span", { className: "mx-2 block text-xs text-gray-500", children: isCopied ? 'Copied!' : 'Hold shift and click on a value to copy.' })] }) }) }));
56
60
  };
57
61
  const TooltipMetaInfo = ({ table,
58
62
  // total,
59
63
  // totalUnfiltered,
60
- onCopy, row, }) => {
64
+ onCopy, row, navigateTo, }) => {
61
65
  const mappingFile = table.getChild(FIELD_MAPPING_FILE)?.get(row) ?? '';
62
66
  const mappingBuildID = table.getChild(FIELD_MAPPING_BUILD_ID)?.get(row) ?? '';
63
67
  const locationAddress = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row) ?? 0n;
@@ -66,12 +70,21 @@ onCopy, row, }) => {
66
70
  const functionStartLine = table.getChild(FIELD_FUNCTION_START_LINE)?.get(row) ?? 0n;
67
71
  const pprofLabelPrefix = 'pprof_labels.';
68
72
  const labelColumnNames = table.schema.fields.filter(field => field.name.startsWith(pprofLabelPrefix));
73
+ const { queryServiceClient } = useParcaContext();
74
+ const { profileSource } = useProfileViewContext();
75
+ const { isLoading: sourceLoading, response: sourceResponse } = useQuery(queryServiceClient, profileSource, QueryRequest_ReportType.SOURCE, {
76
+ skip: profileSource === undefined,
77
+ sourceBuildID: mappingBuildID,
78
+ sourceFilename: functionFilename,
79
+ sourceOnly: true,
80
+ });
81
+ const isSourceAvailable = !sourceLoading && sourceResponse != null;
69
82
  const getTextForFile = () => {
70
83
  if (functionFilename === '')
71
84
  return '<unknown>';
72
85
  return `${functionFilename} ${locationLine !== 0n
73
86
  ? ` +${locationLine.toString()}`
74
- : `${functionStartLine !== 0n ? ` +${functionStartLine}` : ''}`}`;
87
+ : `${functionStartLine !== 0n ? `:${functionStartLine}` : ''}`}`;
75
88
  };
76
89
  const file = getTextForFile();
77
90
  const labelPairs = labelColumnNames
@@ -81,6 +94,31 @@ onCopy, row, }) => {
81
94
  ])
82
95
  .filter(value => value[1] !== '');
83
96
  const labels = labelPairs.map((l) => (_jsx("span", { className: "mr-3 inline-block rounded-lg bg-gray-200 px-2 py-1 text-xs font-bold text-gray-700 dark:bg-gray-700 dark:text-gray-400", children: `${l[0]}="${l[1]}"` }, l[0])));
84
- return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: file, children: _jsx("button", { className: "cursor-pointer whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 40) }) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer", children: hexifyAddress(locationAddress) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: mappingFile === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingFile, children: _jsx("button", { className: "cursor-pointer", children: getLastItem(mappingFile) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: mappingBuildID === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingBuildID, children: _jsx("button", { className: "cursor-pointer", children: truncateString(getLastItem(mappingBuildID), 28) }) })) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
97
+ const [dashboardItems, setDashboardItems] = useURLState({
98
+ param: 'dashboard_items',
99
+ navigateTo,
100
+ });
101
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
102
+ const [unusedBuildId, setSourceBuildId] = useURLState({
103
+ param: 'source_buildid',
104
+ navigateTo,
105
+ });
106
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
107
+ const [unusedFilename, setSourceFilename] = useURLState({
108
+ param: 'source_filename',
109
+ navigateTo,
110
+ });
111
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
112
+ const [unusedLine, setSourceLine] = useURLState({
113
+ param: 'source_line',
114
+ navigateTo,
115
+ });
116
+ const openFile = () => {
117
+ setDashboardItems([dashboardItems[0], 'source']);
118
+ setSourceBuildId(mappingBuildID);
119
+ setSourceFilename(functionFilename);
120
+ setSourceLine(locationLine.toString());
121
+ };
122
+ return (_jsxs(_Fragment, { children: [_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "File" }), _jsx("td", { className: "w-3/4 break-all", children: functionFilename === '' ? (_jsx(NoData, {})) : (_jsxs("div", { className: "flex gap-4", children: [_jsx(CopyToClipboard, { onCopy: onCopy, text: file, children: _jsx("button", { className: "cursor-pointer whitespace-nowrap text-left", children: _jsx(ExpandOnHover, { value: file, displayValue: truncateStringReverse(file, 30) }) }) }), _jsxs("div", { className: "flex gap-2", children: [_jsx("div", { "data-tooltip-id": "open-source-button-help", "data-tooltip-content": "There is no source code uploaded for this build", children: _jsx(Button, { variant: 'neutral', onClick: () => openFile(), className: "shrink-0", disabled: !isSourceAvailable, children: "open" }) }), !isSourceAvailable ? _jsx(Tooltip, { id: "open-source-button-help" }) : null] })] })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Address" }), _jsx("td", { className: "w-3/4 break-all", children: locationAddress === 0n ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: hexifyAddress(locationAddress), children: _jsx("button", { className: "cursor-pointer", children: hexifyAddress(locationAddress) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Binary" }), _jsx("td", { className: "w-3/4 break-all", children: mappingFile === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingFile, children: _jsx("button", { className: "cursor-pointer", children: getLastItem(mappingFile) }) })) })] }), _jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Build Id" }), _jsx("td", { className: "w-3/4 break-all", children: mappingBuildID === '' ? (_jsx(NoData, {})) : (_jsx(CopyToClipboard, { onCopy: onCopy, text: mappingBuildID, children: _jsx("button", { className: "cursor-pointer", children: truncateString(getLastItem(mappingBuildID), 28) }) })) })] }), labelPairs.length > 0 && (_jsxs("tr", { children: [_jsx("td", { className: "w-1/4", children: "Labels" }), _jsx("td", { className: "w-3/4 break-all", children: labels })] }))] }));
85
123
  };
86
124
  export default GraphTooltipArrowContent;
@@ -1,4 +1,4 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  export const ExpandOnHover = ({ value, displayValue }) => {
3
- return (_jsxs("div", { className: "group relative w-full", children: [_jsx("div", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap", children: displayValue ?? value }), _jsx("div", { className: "absolute -inset-2 hidden h-fit max-w-[500px] whitespace-normal break-all rounded bg-gray-50 p-2 shadow-[0_0_10px_2px_rgba(0,0,0,0.3)] group-hover:flex dark:bg-gray-900", children: value })] }));
3
+ return (_jsxs("div", { className: "group relative w-full", children: [_jsx("div", { className: "w-full overflow-hidden text-ellipsis whitespace-nowrap", children: displayValue ?? value }), _jsx("div", { className: "absolute -inset-2 hidden h-fit max-w-[500px] whitespace-normal break-all rounded bg-gray-50 p-2 shadow-[0_0_10px_2px_rgba(0,0,0,0.3)] group-hover:flex dark:bg-gray-900 z-10", children: value })] }));
4
4
  };
@@ -17,6 +17,15 @@ const margin = 50;
17
17
  export const useMetricsGraphDimensions = (comparing) => {
18
18
  let { width } = useWindowSize();
19
19
  const { profileExplorer } = useParcaContext();
20
+ if (profileExplorer == null) {
21
+ return {
22
+ width: 0,
23
+ height: 0,
24
+ heightStyle: '0',
25
+ margin: 0,
26
+ marginRight: 0,
27
+ };
28
+ }
20
29
  width = width - profileExplorer.PaddingX;
21
30
  if (comparing) {
22
31
  width = width / 2 - 32;
@@ -128,6 +128,6 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ table, total, f
128
128
  if (table.numRows === 0 || width === undefined) {
129
129
  return _jsx(_Fragment, {});
130
130
  }
131
- return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode }), _jsx(GraphTooltipArrow, { contextElement: svg.current, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, unit: sampleUnit }) }), root] }));
131
+ return (_jsxs("div", { onMouseLeave: () => dispatch(setHoveringNode(undefined)), children: [_jsx(ColorStackLegend, { mappingColors: mappingColors, navigateTo: navigateTo, compareMode: compareMode }), _jsx(GraphTooltipArrow, { contextElement: svg.current, children: _jsx(GraphTooltipArrowContent, { table: table, row: hoveringRow, level: hoveringLevel ?? 0, isFixed: false, total: total, totalUnfiltered: total + filtered, unit: sampleUnit, navigateTo: navigateTo }) }), root] }));
132
132
  });
133
133
  export default IcicleGraphArrow;
@@ -0,0 +1,14 @@
1
+ import { ReactNode } from 'react';
2
+ import { ProfileSource } from '../ProfileSource';
3
+ interface Props {
4
+ profileSource?: ProfileSource;
5
+ sampleUnit: string;
6
+ }
7
+ export declare const defaultValue: Props;
8
+ declare const ProfileViewContext: import("react").Context<Props>;
9
+ export declare const ProfileViewContextProvider: ({ children, value, }: {
10
+ children: ReactNode;
11
+ value?: Props | undefined;
12
+ }) => JSX.Element;
13
+ export declare const useProfileViewContext: () => Props;
14
+ export default ProfileViewContext;
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Copyright 2022 The Parca Authors
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { createContext, useContext } from 'react';
15
+ export const defaultValue = {
16
+ profileSource: undefined,
17
+ sampleUnit: 'bytes',
18
+ };
19
+ const ProfileViewContext = createContext(defaultValue);
20
+ export const ProfileViewContextProvider = ({ children, value, }) => {
21
+ return (_jsx(ProfileViewContext.Provider, { value: { ...defaultValue, ...(value ?? {}) }, children: children }));
22
+ };
23
+ export const useProfileViewContext = () => {
24
+ const context = useContext(ProfileViewContext);
25
+ if (context == null) {
26
+ return defaultValue;
27
+ }
28
+ return context;
29
+ };
30
+ export default ProfileViewContext;
@@ -22,6 +22,7 @@ const ViewSelector = ({ defaultValue, navigateTo, position, placeholderText, pri
22
22
  const allItems = [
23
23
  { key: 'table', canBeSelected: !dashboardItems.includes('table') },
24
24
  { key: 'icicle', canBeSelected: !dashboardItems.includes('icicle') },
25
+ { key: 'source', canBeSelected: false },
25
26
  ];
26
27
  if (callgraphEnabled) {
27
28
  allItems.push({
@@ -1,5 +1,5 @@
1
- import { Table } from 'apache-arrow';
2
- import { Callgraph as CallgraphType, Flamegraph, QueryServiceClient, Top } from '@parca/client';
1
+ import { Table as ArrowTable } from 'apache-arrow';
2
+ import { Callgraph as CallgraphType, Flamegraph, QueryServiceClient, Source, TableArrow, Top } from '@parca/client';
3
3
  import { ProfileSource } from '../ProfileSource';
4
4
  type NavigateFunction = (path: string, queryParams: any, options?: {
5
5
  replace?: boolean;
@@ -7,13 +7,14 @@ type NavigateFunction = (path: string, queryParams: any, options?: {
7
7
  export interface FlamegraphData {
8
8
  loading: boolean;
9
9
  data?: Flamegraph;
10
- table?: Table<any>;
10
+ table?: ArrowTable<any>;
11
11
  total?: bigint;
12
12
  filtered?: bigint;
13
13
  error?: any;
14
14
  }
15
15
  export interface TopTableData {
16
16
  loading: boolean;
17
+ arrow?: TableArrow;
17
18
  data?: Top;
18
19
  total?: bigint;
19
20
  filtered?: bigint;
@@ -26,12 +27,18 @@ interface CallgraphData {
26
27
  filtered?: bigint;
27
28
  error?: any;
28
29
  }
30
+ interface SourceData {
31
+ loading: boolean;
32
+ data?: Source;
33
+ error?: any;
34
+ }
29
35
  export interface ProfileViewProps {
30
36
  total: bigint;
31
37
  filtered: bigint;
32
38
  flamegraphData?: FlamegraphData;
33
39
  topTableData?: TopTableData;
34
40
  callgraphData?: CallgraphData;
41
+ sourceData?: SourceData;
35
42
  sampleUnit: string;
36
43
  profileSource?: ProfileSource;
37
44
  queryClient?: QueryServiceClient;
@@ -40,5 +47,5 @@ export interface ProfileViewProps {
40
47
  onDownloadPProf: () => void;
41
48
  pprofDownloading?: boolean;
42
49
  }
43
- export declare const ProfileView: ({ total, filtered, flamegraphData, topTableData, callgraphData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, }: ProfileViewProps) => JSX.Element;
50
+ export declare const ProfileView: ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, }: ProfileViewProps) => JSX.Element;
44
51
  export {};
@@ -24,10 +24,12 @@ import { getNewSpanColor } from '@parca/utilities';
24
24
  import { Callgraph } from '../';
25
25
  import { jsonToDot } from '../Callgraph/utils';
26
26
  import ProfileIcicleGraph from '../ProfileIcicleGraph';
27
- import { TopTable } from '../TopTable';
27
+ import { SourceView } from '../SourceView';
28
+ import Table from '../Table';
28
29
  import ProfileShareButton from '../components/ProfileShareButton';
29
30
  import useDelayedLoader from '../useDelayedLoader';
30
31
  import FilterByFunctionButton from './FilterByFunctionButton';
32
+ import { ProfileViewContextProvider } from './ProfileViewContext';
31
33
  import ViewSelector from './ViewSelector';
32
34
  import { VisualizationPanel } from './VisualizationPanel';
33
35
  function arrayEquals(a, b) {
@@ -36,7 +38,7 @@ function arrayEquals(a, b) {
36
38
  a.length === b.length &&
37
39
  a.every((val, index) => val === b[index]));
38
40
  }
39
- export const ProfileView = ({ total, filtered, flamegraphData, topTableData, callgraphData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, }) => {
41
+ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, callgraphData, sourceData, sampleUnit, profileSource, queryClient, navigateTo, onDownloadPProf, pprofDownloading, }) => {
40
42
  const { ref, dimensions } = useContainerDimensions();
41
43
  const [curPath, setCurPath] = useState([]);
42
44
  const [rawDashboardItems = ['icicle'], setDashboardItems] = useURLState({
@@ -76,12 +78,16 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
76
78
  if (dashboardItems.includes('table')) {
77
79
  return Boolean(topTableData?.loading);
78
80
  }
81
+ if (dashboardItems.includes('source')) {
82
+ return Boolean(sourceData?.loading);
83
+ }
79
84
  return false;
80
85
  }, [
81
86
  dashboardItems,
82
87
  callgraphData?.loading,
83
88
  flamegraphData?.loading,
84
89
  topTableData?.loading,
90
+ sourceData?.loading,
85
91
  callgraphSVG,
86
92
  ]);
87
93
  const isLoaderVisible = useDelayedLoader(isLoading);
@@ -135,7 +141,10 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
135
141
  dimensions?.width !== undefined ? (_jsx(Callgraph, { data: callgraphData.data, svgString: callgraphSVG, sampleUnit: sampleUnit, width: isHalfScreen ? dimensions?.width / 2 : dimensions?.width })) : (_jsx(_Fragment, {}));
136
142
  }
137
143
  case 'table': {
138
- return topTableData != null ? (_jsx(TopTable, { loading: topTableData.loading, data: topTableData.data, sampleUnit: sampleUnit, navigateTo: navigateTo, setActionButtons: setActionButtons, currentSearchString: currentSearchString })) : (_jsx(_Fragment, {}));
144
+ return topTableData != null ? (_jsx(Table, { loading: topTableData.loading, data: topTableData.arrow?.record, sampleUnit: sampleUnit, navigateTo: navigateTo, setActionButtons: setActionButtons, currentSearchString: currentSearchString })) : (_jsx(_Fragment, {}));
145
+ }
146
+ case 'source': {
147
+ return sourceData != null ? (_jsx(SourceView, { loading: sourceData.loading, data: sourceData.data, total: total, filtered: filtered, setActionButtons: setActionButtons })) : (_jsx(_Fragment, {}));
139
148
  }
140
149
  default: {
141
150
  return _jsx(_Fragment, {});
@@ -157,13 +166,13 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
157
166
  setDashboardItems(newDashboardItems);
158
167
  }
159
168
  };
160
- return (_jsx(KeyDownProvider, { children: _jsx("div", { className: "py-3", children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", { className: "flex w-full py-3", children: [_jsxs("div", { className: "flex space-x-4 lg:w-1/2", children: [_jsxs("div", { className: "flex space-x-1", children: [profileSource !== undefined && queryClient !== undefined ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsx(Button, { color: "neutral", onClick: e => {
161
- e.preventDefault();
162
- onDownloadPProf();
163
- }, disabled: pprofDownloading, children: pprofDownloading != null && pprofDownloading
164
- ? 'Downloading'
165
- : 'Download pprof' })] }), _jsx(FilterByFunctionButton, { navigateTo: navigateTo })] }), _jsx("div", { className: "ml-auto flex gap-2", children: _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel...", primary: true, addView: true, disabled: isMultiPanelView || dashboardItems.length < 1 }) })] }), _jsx("div", { className: "w-full", ref: ref, children: isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsx(DragDropContext, { onDragEnd: onDragEnd, children: _jsx(Droppable, { droppableId: "droppable", direction: "horizontal", children: provided => (_jsx("div", { ref: provided.innerRef, className: "flex w-full justify-between space-x-4", ...provided.droppableProps, children: dashboardItems.map((dashboardItem, index) => {
166
- return (_jsx(Draggable, { draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView, children: (provided, snapshot) => (_createElement("div", { ref: provided.innerRef, ...provided.draggableProps, key: dashboardItem, className: cx('rounded border border-gray-300 p-3 dark:border-gray-500 dark:bg-gray-700', isMultiPanelView ? 'w-1/2' : 'w-full', snapshot.isDragging ? 'bg-gray-200' : 'bg-white') },
167
- _jsx(VisualizationPanel, { handleClosePanel: handleClosePanel, isMultiPanelView: isMultiPanelView, dashboardItem: dashboardItem, getDashboardItemByType: getDashboardItemByType, dragHandleProps: provided.dragHandleProps, navigateTo: navigateTo, index: index }))) }, dashboardItem));
168
- }) })) }) })) })] }) }) }) }));
169
+ return (_jsx(KeyDownProvider, { children: _jsx(ProfileViewContextProvider, { value: { profileSource, sampleUnit }, children: _jsx("div", { className: "py-3", children: _jsx(Card, { children: _jsxs(Card.Body, { children: [_jsxs("div", { className: "flex w-full py-3", children: [_jsxs("div", { className: "flex space-x-4 lg:w-1/2", children: [_jsxs("div", { className: "flex space-x-1", children: [profileSource !== undefined && queryClient !== undefined ? (_jsx(ProfileShareButton, { queryRequest: profileSource.QueryRequest(), queryClient: queryClient })) : null, _jsx(Button, { color: "neutral", onClick: e => {
170
+ e.preventDefault();
171
+ onDownloadPProf();
172
+ }, disabled: pprofDownloading, children: pprofDownloading != null && pprofDownloading
173
+ ? 'Downloading'
174
+ : 'Download pprof' })] }), _jsx(FilterByFunctionButton, { navigateTo: navigateTo })] }), _jsx("div", { className: "ml-auto flex gap-2", children: _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel...", primary: true, addView: true, disabled: isMultiPanelView || dashboardItems.length < 1 }) })] }), _jsx("div", { className: "w-full", ref: ref, children: isLoaderVisible ? (_jsx(_Fragment, { children: loader })) : (_jsx(DragDropContext, { onDragEnd: onDragEnd, children: _jsx(Droppable, { droppableId: "droppable", direction: "horizontal", children: provided => (_jsx("div", { ref: provided.innerRef, className: "flex w-full justify-between space-x-4", ...provided.droppableProps, children: dashboardItems.map((dashboardItem, index) => {
175
+ return (_jsx(Draggable, { draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView, children: (provided, snapshot) => (_createElement("div", { ref: provided.innerRef, ...provided.draggableProps, key: dashboardItem, className: cx('rounded border border-gray-300 p-3 dark:border-gray-500 dark:bg-gray-700', isMultiPanelView ? 'w-1/2' : 'w-full', snapshot.isDragging ? 'bg-gray-200' : 'bg-white') },
176
+ _jsx(VisualizationPanel, { handleClosePanel: handleClosePanel, isMultiPanelView: isMultiPanelView, dashboardItem: dashboardItem, getDashboardItemByType: getDashboardItemByType, dragHandleProps: provided.dragHandleProps, navigateTo: navigateTo, index: index }))) }, dashboardItem));
177
+ }) })) }) })) })] }) }) }) }) }));
169
178
  };
@@ -24,6 +24,8 @@ import { downloadPprof } from './utils';
24
24
  export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, }) => {
25
25
  const metadata = useGrpcMetadata();
26
26
  const [dashboardItems = ['icicle']] = useURLState({ param: 'dashboard_items', navigateTo });
27
+ const [sourceBuildID] = useURLState({ param: 'source_buildid', navigateTo });
28
+ const [sourceFilename] = useURLState({ param: 'source_filename', navigateTo });
27
29
  const [groupBy = [FIELD_FUNCTION_NAME]] = useURLState({ param: 'group_by', navigateTo });
28
30
  const [enableTrimming] = useUserPreference(USER_PREFERENCES.ENABLE_GRAPH_TRIMMING.key);
29
31
  const [arrowFlamegraphEnabled] = useUIFeatureFlag('flamegraph-arrow');
@@ -50,30 +52,40 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
50
52
  groupBy: groupByParam,
51
53
  });
52
54
  const { perf } = useParcaContext();
53
- const { isLoading: topTableLoading, response: topTableResponse, error: topTableError, } = useQuery(queryClient, profileSource, QueryRequest_ReportType.TOP, {
55
+ const { isLoading: tableLoading, response: tableResponse, error: tableError, } = useQuery(queryClient, profileSource, QueryRequest_ReportType.TABLE_ARROW, {
54
56
  skip: !dashboardItems.includes('table'),
55
57
  });
56
58
  const { isLoading: callgraphLoading, response: callgraphResponse, error: callgraphError, } = useQuery(queryClient, profileSource, QueryRequest_ReportType.CALLGRAPH, {
57
59
  skip: !dashboardItems.includes('callgraph'),
58
60
  });
61
+ const { isLoading: sourceLoading, response: sourceResponse, error: sourceError, } = useQuery(queryClient, profileSource, QueryRequest_ReportType.SOURCE, {
62
+ skip: !dashboardItems.includes('source'),
63
+ sourceBuildID,
64
+ sourceFilename,
65
+ });
59
66
  useEffect(() => {
60
67
  if ((!flamegraphLoading && flamegraphResponse?.report.oneofKind === 'flamegraph') ||
61
68
  flamegraphResponse?.report.oneofKind === 'flamegraphArrow') {
62
69
  perf?.markInteraction('Flamegraph render', flamegraphResponse.total);
63
70
  }
64
- if (!topTableLoading && topTableResponse?.report.oneofKind === 'top') {
65
- perf?.markInteraction('Top table render', topTableResponse.total);
71
+ if (!tableLoading && tableResponse?.report.oneofKind === 'tableArrow') {
72
+ perf?.markInteraction('table render', tableResponse.total);
66
73
  }
67
74
  if (!callgraphLoading && callgraphResponse?.report.oneofKind === 'callgraph') {
68
75
  perf?.markInteraction('Callgraph render', callgraphResponse.total);
69
76
  }
77
+ if (!sourceLoading && sourceResponse?.report.oneofKind === 'source') {
78
+ perf?.markInteraction('Source render', sourceResponse.total);
79
+ }
70
80
  }, [
71
81
  flamegraphLoading,
72
82
  flamegraphResponse,
73
83
  callgraphResponse,
74
84
  callgraphLoading,
75
- topTableLoading,
76
- topTableResponse,
85
+ tableLoading,
86
+ tableResponse,
87
+ sourceLoading,
88
+ sourceResponse,
77
89
  perf,
78
90
  ]);
79
91
  const sampleUnit = profileSource.ProfileType().sampleUnit;
@@ -100,14 +112,18 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
100
112
  total = BigInt(flamegraphResponse.total);
101
113
  filtered = BigInt(flamegraphResponse.filtered);
102
114
  }
103
- else if (topTableResponse !== null) {
104
- total = BigInt(topTableResponse.total);
105
- filtered = BigInt(topTableResponse.filtered);
115
+ else if (tableResponse !== null) {
116
+ total = BigInt(tableResponse.total);
117
+ filtered = BigInt(tableResponse.filtered);
106
118
  }
107
119
  else if (callgraphResponse !== null) {
108
120
  total = BigInt(callgraphResponse.total);
109
121
  filtered = BigInt(callgraphResponse.filtered);
110
122
  }
123
+ else if (sourceResponse !== null) {
124
+ total = BigInt(sourceResponse.total);
125
+ filtered = BigInt(sourceResponse.filtered);
126
+ }
111
127
  return (_jsx(ProfileView, { total: total, filtered: filtered, flamegraphData: {
112
128
  loading: flamegraphLoading,
113
129
  data: flamegraphResponse?.report.oneofKind === 'flamegraph'
@@ -120,15 +136,23 @@ export const ProfileViewWithData = ({ queryClient, profileSource, navigateTo, })
120
136
  filtered: BigInt(flamegraphResponse?.filtered ?? '0'),
121
137
  error: flamegraphError,
122
138
  }, topTableData: {
123
- loading: topTableLoading,
124
- data: topTableResponse?.report.oneofKind === 'top' ? topTableResponse.report.top : undefined,
125
- error: topTableError,
139
+ loading: tableLoading,
140
+ arrow: tableResponse?.report.oneofKind === 'tableArrow'
141
+ ? tableResponse.report.tableArrow
142
+ : undefined,
143
+ error: tableError,
126
144
  }, callgraphData: {
127
145
  loading: callgraphLoading,
128
146
  data: callgraphResponse?.report.oneofKind === 'callgraph'
129
147
  ? callgraphResponse?.report?.callgraph
130
148
  : undefined,
131
149
  error: callgraphError,
150
+ }, sourceData: {
151
+ loading: sourceLoading,
152
+ data: sourceResponse?.report.oneofKind === 'source'
153
+ ? sourceResponse?.report?.source
154
+ : undefined,
155
+ error: sourceError,
132
156
  }, sampleUnit: sampleUnit, profileSource: profileSource, queryClient: queryClient, navigateTo: navigateTo, onDownloadPProf: () => void downloadPProfClick(), pprofDownloading: pprofDownloading }));
133
157
  };
134
158
  export default ProfileViewWithData;
@@ -0,0 +1,16 @@
1
+ import { Vector } from 'apache-arrow';
2
+ import { type createElementProps } from 'react-syntax-highlighter';
3
+ interface RendererProps {
4
+ rows: any[];
5
+ stylesheet: createElementProps['stylesheet'];
6
+ useInlineStyles: createElementProps['useInlineStyles'];
7
+ }
8
+ type Renderer = ({ rows, stylesheet, useInlineStyles }: RendererProps) => JSX.Element;
9
+ interface HighlighterProps {
10
+ content: string;
11
+ language?: string;
12
+ renderer?: Renderer;
13
+ }
14
+ export declare const profileAwareRenderer: (cumulative: Vector | null, flat: Vector | null, total: bigint, filtered: bigint) => Renderer;
15
+ export declare const Highlighter: ({ content, language, renderer }: HighlighterProps) => JSX.Element;
16
+ export {};
@@ -0,0 +1,80 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
2
+ // Copyright 2022 The Parca Authors
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { useId } from 'react';
15
+ import cx from 'classnames';
16
+ import { scaleLinear } from 'd3-scale';
17
+ import SyntaxHighlighter, { createElement } from 'react-syntax-highlighter';
18
+ import { atomOneDark, atomOneLight } from 'react-syntax-highlighter/dist/esm/styles/hljs';
19
+ import { Tooltip } from 'react-tooltip';
20
+ import { useURLState } from '@parca/components';
21
+ import { selectDarkMode, useAppSelector } from '@parca/store';
22
+ import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
23
+ import { LineNo } from './LineNo';
24
+ // cannot make this a function on the number as we need the classes to be static for tailwind
25
+ const charsToWidthMap = {
26
+ 1: 'w-3',
27
+ 2: 'w-5',
28
+ 3: 'w-7',
29
+ 4: 'w-9',
30
+ 5: 'w-11',
31
+ 6: 'w-[52px]',
32
+ 7: 'w-[60px]]',
33
+ 8: 'w-[68px]',
34
+ 9: 'w-[76px]',
35
+ 10: 'w-[84px]',
36
+ 11: 'w-[92px]',
37
+ 12: 'w-[100px]',
38
+ 13: 'w-[108px]',
39
+ 14: 'w-[116px]',
40
+ };
41
+ const intensityScale = scaleLinear().domain([0, 99]).range([0.05, 0.75]);
42
+ const LineProfileMetadata = ({ value, total, filtered, }) => {
43
+ const commonClasses = 'w-[52px] shrink-0';
44
+ const id = useId();
45
+ const { sampleUnit } = useProfileViewContext();
46
+ if (value === 0n) {
47
+ return _jsx("div", { className: cx(commonClasses) });
48
+ }
49
+ const unfilteredPercent = (Number(value) / Number(total + filtered)) * 100;
50
+ const filteredPercent = (Number(value) / Number(total)) * 100;
51
+ const valueString = value.toString();
52
+ const valueWithUnit = `${valueString}${sampleUnit}`;
53
+ return (_jsxs(_Fragment, { children: [_jsx("p", { className: cx('w- flex justify-end overflow-hidden text-ellipsis whitespace-nowrap', commonClasses), style: { backgroundColor: `rgba(236, 151, 6, ${intensityScale(unfilteredPercent)})` }, "data-tooltip-id": id, "data-tooltip-content": `${valueWithUnit} (${unfilteredPercent.toFixed(2)}%${filtered > 0n ? ` / ${filteredPercent.toFixed(2)}%` : ''})`, children: valueString }), _jsx(Tooltip, { id: id })] }));
54
+ };
55
+ const charsToWidth = (chars) => {
56
+ return charsToWidthMap[chars];
57
+ };
58
+ export const profileAwareRenderer = (cumulative, flat, total, filtered) => {
59
+ return function ProfileAwareRenderer({ rows, stylesheet, useInlineStyles, }) {
60
+ const lineNumberWidth = charsToWidth(rows.length.toString().length);
61
+ const [sourceLine] = useURLState({ param: 'source_line', navigateTo: () => { } });
62
+ return (_jsx(_Fragment, { children: rows.map((node, i) => {
63
+ const lineNumber = node.children[0].children[0].value;
64
+ const isCurrentLine = sourceLine === lineNumber.toString();
65
+ node.children = node.children.slice(1);
66
+ return (_jsxs("div", { className: "flex gap-1", children: [_jsx("div", { className: cx('shrink-0 border-r border-gray-200 pr-1 text-right dark:border-gray-700', lineNumberWidth), children: _jsx(LineNo, { value: lineNumber, isCurrent: isCurrentLine }) }), _jsx(LineProfileMetadata, { value: cumulative?.get(i) ?? 0n, total: total, filtered: filtered }), _jsx(LineProfileMetadata, { value: flat?.get(i) ?? 0n, total: total, filtered: filtered }), _jsx("div", { className: cx('w-11/12 flex-grow-0 border-l border-gray-200 pl-1 dark:border-gray-700', {
67
+ 'bg-yellow-200 dark:bg-yellow-700': isCurrentLine,
68
+ }), children: createElement({
69
+ key: `source-line-${i}`,
70
+ node,
71
+ stylesheet,
72
+ useInlineStyles,
73
+ }) })] }, `${i}`));
74
+ }) }));
75
+ };
76
+ };
77
+ export const Highlighter = ({ content, language, renderer }) => {
78
+ const isDarkMode = useAppSelector(selectDarkMode);
79
+ return (_jsxs("div", { className: "relative", children: [_jsxs("div", { className: "flex gap-2 text-xs", children: [_jsx("div", { className: cx('text-right', charsToWidth(content.split('\n').length.toString().length)), children: "Line" }), _jsxs("div", { className: "flex gap-3", children: [_jsx("div", { children: "Cumulative" }), _jsx("div", { children: "Flat" }), _jsx("div", { children: "Source" })] })] }), _jsx("div", { className: "h-[80vh] overflow-y-auto text-xs", children: _jsx(SyntaxHighlighter, { language: language, style: isDarkMode ? atomOneDark : atomOneLight, showLineNumbers: true, renderer: renderer, children: content }) })] }));
80
+ };
@@ -0,0 +1,6 @@
1
+ interface Props {
2
+ value: number;
3
+ isCurrent?: boolean;
4
+ }
5
+ export declare const LineNo: ({ value, isCurrent }: Props) => JSX.Element;
6
+ export {};
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ // Copyright 2022 The Parca Authors
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ import { useEffect, useRef } from 'react';
15
+ export const LineNo = ({ value, isCurrent = false }) => {
16
+ const ref = useRef(null);
17
+ useEffect(() => {
18
+ if (isCurrent) {
19
+ ref.current?.scrollIntoView({ behavior: 'smooth', block: 'center' });
20
+ }
21
+ }, [isCurrent]);
22
+ return _jsx("code", { ref: ref, children: value.toString() + '\n' });
23
+ };
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { Source } from '@parca/client';
3
+ interface SourceViewProps {
4
+ loading: boolean;
5
+ data?: Source;
6
+ total: bigint;
7
+ filtered: bigint;
8
+ setActionButtons?: (buttons: JSX.Element) => void;
9
+ }
10
+ export declare const SourceView: React.NamedExoticComponent<SourceViewProps>;
11
+ export default SourceView;