@parca/profile 0.16.238 → 0.16.240

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,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.240](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.239...@parca/profile@0.16.240) (2023-08-31)
7
+
8
+ **Note:** Version bump only for package @parca/profile
9
+
10
+ ## [0.16.239](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.238...@parca/profile@0.16.239) (2023-08-30)
11
+
12
+ **Note:** Version bump only for package @parca/profile
13
+
6
14
  ## [0.16.238](https://github.com/parca-dev/parca/compare/@parca/profile@0.16.237...@parca/profile@0.16.238) (2023-08-30)
7
15
 
8
16
  **Note:** Version bump only for package @parca/profile
@@ -1,4 +1,4 @@
1
- import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  // Copyright 2022 The Parca Authors
3
3
  // Licensed under the Apache License, Version 2.0 (the "License");
4
4
  // you may not use this file except in compliance with the License.
@@ -93,7 +93,7 @@ const Callgraph = ({ data, svgString, sampleUnit, width }) => {
93
93
  // eslint-disable-next-line react-hooks/exhaustive-deps
94
94
  }, [svgWrapper.current, svgWrapperLoaded]);
95
95
  if (data.nodes.length < 1)
96
- return _jsx(_Fragment, { children: "Profile has no samples" });
96
+ return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
97
97
  const resetView = () => setView(originalView);
98
98
  const isResetViewButtonEnabled = view.scale !== originalView.scale ||
99
99
  view.translation.x !== originalView.translation.x ||
@@ -19,7 +19,7 @@ import { QueryRequest_ReportType } from '@parca/client';
19
19
  import { Button, useParcaContext, useURLState } from '@parca/components';
20
20
  import { divide, getLastItem, valueFormatter } from '@parca/utilities';
21
21
  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';
22
- import { nodeLabel } from '../ProfileIcicleGraph/IcicleGraphArrow/utils';
22
+ import { arrowToString, nodeLabel } from '../ProfileIcicleGraph/IcicleGraphArrow/utils';
23
23
  import { useProfileViewContext } from '../ProfileView/ProfileViewContext';
24
24
  import { useQuery } from '../useQuery';
25
25
  import { hexifyAddress, truncateString, truncateStringReverse } from '../utils';
@@ -63,11 +63,11 @@ const TooltipMetaInfo = ({ table,
63
63
  // total,
64
64
  // totalUnfiltered,
65
65
  onCopy, row, navigateTo, }) => {
66
- const mappingFile = table.getChild(FIELD_MAPPING_FILE)?.get(row) ?? '';
67
- const mappingBuildID = table.getChild(FIELD_MAPPING_BUILD_ID)?.get(row) ?? '';
66
+ const mappingFile = arrowToString(table.getChild(FIELD_MAPPING_FILE)?.get(row)) ?? '';
67
+ const mappingBuildID = arrowToString(table.getChild(FIELD_MAPPING_BUILD_ID)?.get(row)) ?? '';
68
68
  const locationAddress = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row) ?? 0n;
69
69
  const locationLine = table.getChild(FIELD_LOCATION_LINE)?.get(row) ?? 0n;
70
- const functionFilename = table.getChild(FIELD_FUNCTION_FILE_NAME)?.get(row) ?? '';
70
+ const functionFilename = arrowToString(table.getChild(FIELD_FUNCTION_FILE_NAME)?.get(row)) ?? '';
71
71
  const functionStartLine = table.getChild(FIELD_FUNCTION_START_LINE)?.get(row) ?? 0n;
72
72
  const lineNumber = locationLine !== 0n ? locationLine : functionStartLine !== 0n ? functionStartLine : undefined;
73
73
  const pprofLabelPrefix = 'pprof_labels.';
@@ -10,6 +10,6 @@ const ProfileExplorerCompare = ({ queryClient, queryA, queryB, profileA, profile
10
10
  const closeProfileB = () => {
11
11
  closeProfile('B');
12
12
  };
13
- return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex justify-between gap-2", children: [_jsx(Card, { className: "p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryA, profileSelection: profileA, selectProfile: selectProfileA, selectQuery: selectQueryA, closeProfile: closeProfileA, enforcedProfileName: '', comparing: true, onCompareProfile: () => { } }) }), _jsx(Card, { className: "p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryB, profileSelection: profileB, selectProfile: selectProfileB, selectQuery: selectQueryB, closeProfile: closeProfileB, enforcedProfileName: Query.parse(queryA.expression).profileName(), comparing: true, onCompareProfile: () => { } }) })] }), _jsx("div", { className: "grid grid-cols-1", children: profileA != null && profileB != null ? (_jsx("div", { className: "mt-2", children: _jsx(Card, { className: "px-6 py-4", children: _jsx(ProfileViewWithData, { navigateTo: navigateTo, queryClient: queryClient, profileSource: new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource()) }) }) })) : (_jsx("div", { children: _jsx("div", { className: "my-20 text-center", children: _jsx("p", { children: "Select a profile on both sides." }) }) })) })] }));
13
+ return (_jsxs(_Fragment, { children: [_jsxs("div", { className: "flex justify-between gap-2", children: [_jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryA, profileSelection: profileA, selectProfile: selectProfileA, selectQuery: selectQueryA, closeProfile: closeProfileA, enforcedProfileName: '', comparing: true, onCompareProfile: () => { } }) }), _jsx(Card, { className: "mt-2 p-2", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: queryB, profileSelection: profileB, selectProfile: selectProfileB, selectQuery: selectQueryB, closeProfile: closeProfileB, enforcedProfileName: Query.parse(queryA.expression).profileName(), comparing: true, onCompareProfile: () => { } }) })] }), _jsx("div", { className: "grid grid-cols-1", children: profileA != null && profileB != null ? (_jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { navigateTo: navigateTo, queryClient: queryClient, profileSource: new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource()) }) })) : (_jsx("div", { children: _jsx("div", { className: "my-20 text-center", children: _jsx("p", { children: "Select a profile on both sides." }) }) })) })] }));
14
14
  };
15
15
  export default ProfileExplorerCompare;
@@ -3,6 +3,6 @@ import { Card } from '@parca/components';
3
3
  import { ProfileViewWithData } from '..';
4
4
  import ProfileSelector from '../ProfileSelector';
5
5
  const ProfileExplorerSingle = ({ queryClient, query, selectQuery, selectProfile, profile, compareProfile, navigateTo, }) => {
6
- return (_jsxs(_Fragment, { children: [_jsx(Card, { className: "px-6 py-4", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: query, selectQuery: selectQuery, selectProfile: selectProfile, closeProfile: () => { }, profileSelection: profile, comparing: false, onCompareProfile: compareProfile, enforcedProfileName: '' }) }), profile != null ? (_jsx("div", { className: "mt-2", children: _jsx(Card, { className: "px-6 py-4", children: _jsx(ProfileViewWithData, { queryClient: queryClient, profileSource: profile.ProfileSource(), navigateTo: navigateTo }) }) })) : (_jsx(_Fragment, {}))] }));
6
+ return (_jsxs(_Fragment, { children: [_jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileSelector, { queryClient: queryClient, querySelection: query, selectQuery: selectQuery, selectProfile: selectProfile, closeProfile: () => { }, profileSelection: profile, comparing: false, onCompareProfile: compareProfile, enforcedProfileName: '' }) }), profile != null ? (_jsx(Card, { className: "mt-2 px-6 py-4", children: _jsx(ProfileViewWithData, { queryClient: queryClient, profileSource: profile.ProfileSource(), navigateTo: navigateTo }) })) : (_jsx(_Fragment, {}))] }));
7
7
  };
8
8
  export default ProfileExplorerSingle;
@@ -18,7 +18,7 @@ import { selectBinaries, useAppSelector } from '@parca/store';
18
18
  import { isSearchMatch, scaleLinear } from '@parca/utilities';
19
19
  import { FIELD_CHILDREN, FIELD_CUMULATIVE, FIELD_DIFF, FIELD_FUNCTION_NAME, FIELD_MAPPING_FILE, } from './index';
20
20
  import useNodeColor from './useNodeColor';
21
- import { nodeLabel } from './utils';
21
+ import { arrowToString, nodeLabel } from './utils';
22
22
  export const RowHeight = 26;
23
23
  export const IcicleGraphNodes = React.memo(function IcicleGraphNodesNoMemo({ table, childRows, mappingColors, x, y, xScale, total, totalWidth, level, path, setCurPath, setHoveringRow, setHoveringLevel, curPath, sortBy, searchString, darkMode, compareMode, }) {
24
24
  const cumulatives = table.getChild(FIELD_CUMULATIVE);
@@ -56,8 +56,8 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, map
56
56
  const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
57
57
  const diffColumn = table.getChild(FIELD_DIFF);
58
58
  // get the actual values from the columns
59
- const mappingFile = mappingColumn?.get(row);
60
- const functionName = functionNameColumn?.get(row);
59
+ const mappingFile = arrowToString(mappingColumn?.get(row));
60
+ const functionName = arrowToString(functionNameColumn?.get(row));
61
61
  const cumulative = cumulativeColumn?.get(row);
62
62
  const diff = diffColumn?.get(row);
63
63
  const childRows = Array.from(table.getChild(FIELD_CHILDREN)?.get(row) ?? []);
@@ -66,8 +66,8 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({ table, row, map
66
66
  case FIELD_FUNCTION_NAME:
67
67
  childRows.sort((a, b) => {
68
68
  // TODO: Support fallthrough to comparing addresses or something
69
- const afn = functionNameColumn?.get(a);
70
- const bfn = functionNameColumn?.get(b);
69
+ const afn = arrowToString(functionNameColumn?.get(a));
70
+ const bfn = arrowToString(functionNameColumn?.get(b));
71
71
  if (afn !== null && bfn !== null) {
72
72
  return afn.localeCompare(bfn);
73
73
  }
@@ -19,7 +19,7 @@ import GraphTooltipArrow from '../../GraphTooltipArrow';
19
19
  import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
20
20
  import ColorStackLegend from './ColorStackLegend';
21
21
  import { IcicleNode, RowHeight } from './IcicleGraphNodes';
22
- import { extractFeature } from './utils';
22
+ import { arrowToString, extractFeature } from './utils';
23
23
  export const FIELD_LABELS_ONLY = 'labels_only';
24
24
  export const FIELD_MAPPING_FILE = 'mapping_file';
25
25
  export const FIELD_MAPPING_BUILD_ID = 'mapping_build_id';
@@ -56,7 +56,8 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ table, total, f
56
56
  const len = mapping.dictionary.length;
57
57
  const entries = [];
58
58
  for (let i = 0; i < len; i++) {
59
- entries.push(getLastItem(mapping.dictionary.get(i)) ?? '');
59
+ const fn = arrowToString(mapping.dictionary.get(i));
60
+ entries.push(getLastItem(fn) ?? '');
60
61
  }
61
62
  return entries;
62
63
  })
@@ -73,7 +74,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({ table, total, f
73
74
  }
74
75
  const len = fn.dictionary.length;
75
76
  for (let i = 0; i < len; i++) {
76
- const fn = functionNamesDict?.get(i);
77
+ const fn = arrowToString(functionNamesDict?.get(i));
77
78
  if (fn?.startsWith('runtime') === true) {
78
79
  mappings.push('runtime');
79
80
  break;
@@ -2,3 +2,4 @@ import { Table } from 'apache-arrow';
2
2
  import { type Feature } from '@parca/store';
3
3
  export declare function nodeLabel(table: Table<any>, row: number, level: number, showBinaryName: boolean): string;
4
4
  export declare const extractFeature: (mapping: string) => Feature;
5
+ export declare const arrowToString: (buffer: any) => string | null;
@@ -15,7 +15,7 @@ import { getLastItem } from '@parca/utilities';
15
15
  import { hexifyAddress } from '../../utils';
16
16
  import { FIELD_FUNCTION_NAME, FIELD_LABELS_ONLY, FIELD_LOCATION_ADDRESS, FIELD_MAPPING_FILE, } from './index';
17
17
  export function nodeLabel(table, row, level, showBinaryName) {
18
- const functionName = table.getChild(FIELD_FUNCTION_NAME)?.get(row);
18
+ const functionName = arrowToString(table.getChild(FIELD_FUNCTION_NAME)?.get(row));
19
19
  const labelsOnly = table.getChild(FIELD_LABELS_ONLY)?.get(row);
20
20
  const pprofLabelPrefix = 'pprof_labels.';
21
21
  const labelColumnNames = table.schema.fields.filter(field => field.name.startsWith(pprofLabelPrefix));
@@ -53,3 +53,12 @@ export const extractFeature = (mapping) => {
53
53
  }
54
54
  return { name: EVERYTHING_ELSE, type: FEATURE_TYPES.Misc };
55
55
  };
56
+ export const arrowToString = (buffer) => {
57
+ if (buffer == null || typeof buffer === 'string') {
58
+ return buffer;
59
+ }
60
+ if (ArrayBuffer.isView(buffer)) {
61
+ return String.fromCharCode.apply(null, buffer);
62
+ }
63
+ return '';
64
+ };
@@ -14,7 +14,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
14
14
  import { Fragment, useCallback, useEffect, useMemo } from 'react';
15
15
  import { Menu, Transition } from '@headlessui/react';
16
16
  import { Icon } from '@iconify/react';
17
- import { Button, Select, useURLState } from '@parca/components';
17
+ import { Button, Select, useParcaContext, useURLState } from '@parca/components';
18
18
  import { useContainerDimensions } from '@parca/hooks';
19
19
  import { divide, selectQueryParam } from '@parca/utilities';
20
20
  import DiffLegend from '../components/DiffLegend';
@@ -51,6 +51,7 @@ const GroupAndSortActionButtons = ({ navigateTo }) => {
51
51
  return (_jsxs(_Fragment, { children: [_jsx(GroupByDropdown, { groupBy: groupBy, toggleGroupBy: toggleGroupBy }), _jsx(SortBySelect, { compareMode: compareMode, sortBy: storeSortBy, setSortBy: setStoreSortBy })] }));
52
52
  };
53
53
  const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, table, total, filtered, curPath, setNewCurPath, sampleUnit, navigateTo, loading, setActionButtons, error, }) {
54
+ const { loader } = useParcaContext();
54
55
  const compareMode = selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
55
56
  const { ref, dimensions } = useContainerDimensions();
56
57
  const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState({
@@ -82,14 +83,17 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({ graph, table, to
82
83
  }
83
84
  setActionButtons(_jsx("div", { className: "flex w-full justify-end gap-2 pb-2", children: _jsxs("div", { className: "ml-2 flex w-full items-end justify-between gap-2", children: [table !== undefined && _jsx(GroupAndSortActionButtons, { navigateTo: navigateTo }), _jsx("div", { children: _jsx(Button, { color: "neutral", onClick: () => setNewCurPath([]), disabled: curPath.length === 0, variant: "neutral", children: "Reset View" }) })] }) }));
84
85
  }, [navigateTo, table, curPath, setNewCurPath, setActionButtons]);
86
+ if (loading) {
87
+ return _jsx("div", { className: "h-96", children: loader });
88
+ }
85
89
  if (error != null) {
86
90
  console.error('Error: ', error);
87
91
  return _jsxs("div", { className: "flex justify-center p-10", children: ["An error occurred: ", error.message] });
88
92
  }
89
93
  if (graph === undefined && table === undefined)
90
- return _jsx("div", { children: "no data..." });
94
+ return _jsx("div", { className: "mx-auto text-center", children: "no data..." });
91
95
  if (total === 0n && !loading)
92
- return _jsx(_Fragment, { children: "Profile has no samples" });
96
+ return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
93
97
  if (isTrimmed) {
94
98
  console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
95
99
  }
@@ -173,7 +173,9 @@ export const ProfileView = ({ total, filtered, flamegraphData, topTableData, cal
173
173
  e.preventDefault();
174
174
  onDownloadPProf();
175
175
  }, disabled: pprofDownloading, children: [pprofDownloading != null && pprofDownloading ? 'Downloading...' : 'Download pprof', _jsx(Icon, { icon: "material-symbols:download", width: 20 })] }), _jsx(ViewSelector, { defaultValue: "", navigateTo: navigateTo, position: -1, placeholderText: "Add panel", icon: _jsx(Icon, { icon: "material-symbols:add", width: 20 }), 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: cx('grid w-full gap-2', isMultiPanelView ? 'grid-cols-2' : 'grid-cols-1'), ...provided.droppableProps, children: dashboardItems.map((dashboardItem, index) => {
176
- return (_jsx(Draggable, { draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView, children: (provided, snapshot) => (_createElement("div", { ref: provided.innerRef, ...provided.draggableProps, key: dashboardItem, className: cx('w-full rounded p-2 shadow dark:border-gray-500 dark:bg-gray-700', snapshot.isDragging ? 'bg-gray-200' : 'bg-white') },
176
+ return (_jsx(Draggable, { draggableId: dashboardItem, index: index, isDragDisabled: !isMultiPanelView, children: (provided, snapshot) => (_createElement("div", { ref: provided.innerRef, ...provided.draggableProps, key: dashboardItem, className: cx('min-h-[200px] w-full rounded p-2 shadow dark:border dark:border-gray-700 dark:bg-gray-700', snapshot.isDragging
177
+ ? 'bg-gray-200 dark:bg-gray-500'
178
+ : 'bg-white dark:bg-gray-700') },
177
179
  _jsx(VisualizationPanel, { handleClosePanel: handleClosePanel, isMultiPanelView: isMultiPanelView, dashboardItem: dashboardItem, getDashboardItemByType: getDashboardItemByType, dragHandleProps: provided.dragHandleProps, navigateTo: navigateTo, index: index }))) }, dashboardItem));
178
180
  }) })) }) })) })] }) }));
179
181
  };
@@ -129,16 +129,16 @@ export const Table = React.memo(function Table({ data, sampleUnit: unit, navigat
129
129
  };
130
130
  }, [compareMode]);
131
131
  if (loading)
132
- return _jsx(_Fragment, { children: "Loading..." });
132
+ return _jsx("div", { className: "mx-auto text-center", children: "Loading..." });
133
133
  if (data === undefined)
134
- return _jsx(_Fragment, { children: "Profile has no samples" });
134
+ return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
135
135
  const table = tableFromIPC(data);
136
136
  const flatColumn = table.getChild('flat');
137
137
  const flatDiffColumn = table.getChild('flat_diff');
138
138
  const cumulativeColumn = table.getChild('cumulative');
139
139
  const cumulativeDiffColumn = table.getChild('cumulative_diff');
140
140
  if (table.numRows === 0)
141
- return _jsx(_Fragment, { children: "Profile has no samples" });
141
+ return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
142
142
  const rows = [];
143
143
  // TODO: Figure out how to only read the data of the columns we need for the virtualized table
144
144
  for (let i = 0; i < table.numRows; i++) {
@@ -142,7 +142,7 @@ export const TopTable = React.memo(function TopTable({ data: top, sampleUnit: un
142
142
  }, [compareMode]);
143
143
  const total = top != null ? top.list.length : 0;
144
144
  if (total === 0 && !loading)
145
- return _jsx(_Fragment, { children: "Profile has no samples" });
145
+ return _jsx("div", { className: "mx-auto text-center", children: "Profile has no samples" });
146
146
  return (_jsx("div", { className: "relative", children: _jsx("div", { className: "font-robotoMono h-[80vh] w-full overflow-scroll", children: _jsx(Table, { data: top?.list ?? [], columns: columns, initialSorting: initialSorting, onRowClick: onRowClick, enableHighlighting: enableHighlighting, shouldHighlightRow: shouldHighlightRow, usePointerCursor: dashboardItems.length > 1 }) }) }));
147
147
  });
148
148
  export default TopTable;
package/dist/styles.css CHANGED
@@ -1 +1 @@
1
- /*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.static{position:static}.absolute{position:absolute}.relative{position:relative}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mr-6{margin-right:1.5rem}.mr-1{margin-right:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-6{height:1.5rem}.h-4{height:1rem}.h-full{height:100%}.h-1{height:.25rem}.h-96{height:24rem}.h-\[80vh\]{height:80vh}.max-h-\[400px\]{max-height:400px}.min-h-52{min-height:13rem}.min-h-48{min-height:12rem}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-4{width:1rem}.w-40{width:10rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-8{width:2rem}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.max-w-\[500px\]{max-width:500px}.max-w-\[300px\]{max-width:300px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-3{gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0{padding-bottom:0;padding-top:0}.px-1{padding-left:.25rem;padding-right:.25rem}.pr-0{padding-right:0}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pl-1{padding-left:.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-90{opacity:.9}.opacity-50{opacity:.5}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
1
+ /*! tailwindcss v3.2.4 | MIT License | https://tailwindcss.com*/*,:after,:before{border:0 solid #e5e7eb;box-sizing:border-box}:after,:before{--tw-content:""}html{-webkit-text-size-adjust:100%;font-feature-settings:normal;font-family:ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4}body{line-height:inherit;margin:0}hr{border-top-width:1px;color:inherit;height:0}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{border-collapse:collapse;border-color:inherit;text-indent:0}button,input,optgroup,select,textarea{color:inherit;font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{color:#9ca3af;opacity:1}input::placeholder,textarea::placeholder{color:#9ca3af;opacity:1}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{height:auto;max-width:100%}[hidden]{display:none}*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width:640px){.container{max-width:640px}}@media (min-width:768px){.container{max-width:768px}}@media (min-width:1024px){.container{max-width:1024px}}@media (min-width:1280px){.container{max-width:1280px}}@media (min-width:1536px){.container{max-width:1536px}}.pointer-events-none{pointer-events:none}.static{position:static}.absolute{position:absolute}.relative{position:relative}.-inset-2{bottom:-.5rem;left:-.5rem;right:-.5rem;top:-.5rem}.inset-y-0{bottom:0;top:0}.left-\[25px\]{left:25px}.left-0{left:0}.top-\[-46px\]{top:-46px}.right-0{right:0}.z-50{z-index:50}.z-10{z-index:10}.m-auto{margin:auto}.m-2{margin:.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mx-2{margin-left:.5rem;margin-right:.5rem}.my-2{margin-bottom:.5rem;margin-top:.5rem}.my-20{margin-bottom:5rem;margin-top:5rem}.my-6{margin-bottom:1.5rem;margin-top:1.5rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mr-6{margin-right:1.5rem}.mr-1{margin-right:.25rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-fit{height:-moz-fit-content;height:fit-content}.h-10{height:2.5rem}.h-96{height:24rem}.h-6{height:1.5rem}.h-4{height:1rem}.h-full{height:100%}.h-1{height:.25rem}.h-\[80vh\]{height:80vh}.max-h-\[400px\]{max-height:400px}.min-h-52{min-height:13rem}.min-h-48{min-height:12rem}.min-h-\[200px\]{min-height:200px}.w-full{width:100%}.w-auto{width:auto}.w-1\/4{width:25%}.w-3\/4{width:75%}.w-\[500px\]{width:500px}.w-4{width:1rem}.w-40{width:10rem}.w-3{width:.75rem}.w-5{width:1.25rem}.w-7{width:1.75rem}.w-9{width:2.25rem}.w-11{width:2.75rem}.w-\[52px\]{width:52px}.w-\[68px\]{width:68px}.w-\[76px\]{width:76px}.w-\[84px\]{width:84px}.w-\[92px\]{width:92px}.w-\[100px\]{width:100px}.w-\[108px\]{width:108px}.w-\[116px\]{width:116px}.w-8{width:2rem}.w-16{width:4rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-\[420px\]{width:420px}.min-w-\[300px\]{min-width:300px}.min-w-\[400px\]{min-width:400px}.max-w-\[500px\]{max-width:500px}.max-w-\[300px\]{max-width:300px}.max-w-md{max-width:28rem}.flex-1{flex:1 1 0%}.shrink-0{flex-shrink:0}.flex-grow-0{flex-grow:0}.flex-grow{flex-grow:1}.table-auto{table-layout:auto}.table-fixed{table-layout:fixed}.translate-y-1{--tw-translate-y:0.25rem}.translate-y-0,.translate-y-1{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y:0px}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skewX(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-pointer{cursor:pointer}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-row{flex-direction:row}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.content-start{align-content:flex-start}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-4{gap:1rem}.gap-2{gap:.5rem}.gap-1{gap:.25rem}.gap-3{gap:.75rem}.gap-x-2{-moz-column-gap:.5rem;column-gap:.5rem}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-bottom:calc(1.25rem*var(--tw-space-y-reverse));margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-scroll{overflow:scroll}.overflow-x-hidden{overflow-x:hidden}.text-ellipsis{text-overflow:ellipsis}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-none{border-radius:0}.rounded-l{border-bottom-left-radius:.25rem;border-top-left-radius:.25rem}.rounded-r{border-bottom-right-radius:.25rem;border-top-right-radius:.25rem}.border{border-width:1px}.border-r{border-right-width:1px}.border-l{border-left-width:1px}.border-r-0{border-right-width:0}.border-l-0{border-left-width:0}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity))}.border-red-400{--tw-border-opacity:1;border-color:rgb(248 113 113/var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity))}.border-r-gray-200{--tw-border-opacity:1;border-right-color:rgb(229 231 235/var(--tw-border-opacity))}.border-l-amber-900{--tw-border-opacity:1;border-left-color:rgb(120 53 15/var(--tw-border-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-red-100{--tw-bg-opacity:1;background-color:rgb(254 226 226/var(--tw-bg-opacity))}.bg-black{--tw-bg-opacity:1;background-color:rgb(0 0 0/var(--tw-bg-opacity))}.bg-yellow-200{--tw-bg-opacity:1;background-color:rgb(254 240 138/var(--tw-bg-opacity))}.bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-inherit{background-color:inherit}.fill-transparent{fill:transparent}.fill-current{fill:currentColor}.stroke-gray-300{stroke:#d1d5db}.stroke-white{stroke:#fff}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-10{padding:2.5rem}.p-4{padding:1rem}.p-1{padding:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.py-1{padding-bottom:.25rem;padding-top:.25rem}.py-2{padding-bottom:.5rem;padding-top:.5rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-4{padding-bottom:1rem;padding-top:1rem}.px-4{padding-left:1rem;padding-right:1rem}.py-3{padding-bottom:.75rem;padding-top:.75rem}.px-3{padding-left:.75rem;padding-right:.75rem}.py-0{padding-bottom:0;padding-top:0}.px-1{padding-left:.25rem;padding-right:.25rem}.pr-0{padding-right:0}.pl-3{padding-left:.75rem}.pr-9{padding-right:2.25rem}.pt-2{padding-top:.5rem}.pb-4{padding-bottom:1rem}.pb-2{padding-bottom:.5rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pl-1{padding-left:.25rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.text-base{font-size:1rem;line-height:1.5rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.font-semibold{font-weight:600}.font-bold{font-weight:700}.font-medium{font-weight:500}.capitalize{text-transform:capitalize}.leading-6{line-height:1.5rem}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.\!text-indigo-600{--tw-text-opacity:1!important;color:rgb(79 70 229/var(--tw-text-opacity))!important}.opacity-100{opacity:1}.opacity-0{opacity:0}.opacity-90{opacity:.9}.opacity-50{opacity:.5}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\]{--tw-shadow:0 0 10px 2px rgba(0,0,0,.3);--tw-shadow-colored:0 0 10px 2px var(--tw-shadow-color)}.shadow-\[0_0_10px_2px_rgba\(0\2c 0\2c 0\2c 0\.3\)\],.shadow-lg{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow:0 10px 15px -3px rgba(0,0,0,.1),0 4px 6px -4px rgba(0,0,0,.1);--tw-shadow-colored:0 10px 15px -3px var(--tw-shadow-color),0 4px 6px -4px var(--tw-shadow-color)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.ring-1{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity:0.05}.blur{--tw-blur:blur(8px)}.blur,.invert{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.invert{--tw-invert:invert(100%)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-duration:.15s;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1)}.duration-100{transition-duration:.1s}.duration-200{transition-duration:.2s}.duration-150{transition-duration:.15s}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-1:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity))}.focus\:ring-indigo-600:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(79 70 229/var(--tw-ring-opacity))}.group:hover .group-hover\:flex{display:flex}[class~=theme-dark] .dark\:border{border-width:1px}[class~=theme-dark] .dark\:border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-600{--tw-border-opacity:1;border-color:rgb(75 85 99/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-gray-700{--tw-border-opacity:1;border-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:border-r-gray-700{--tw-border-opacity:1;border-right-color:rgb(55 65 81/var(--tw-border-opacity))}[class~=theme-dark] .dark\:bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-700{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-gray-500{--tw-bg-opacity:1;background-color:rgb(107 114 128/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:bg-yellow-700{--tw-bg-opacity:1;background-color:rgb(161 98 7/var(--tw-bg-opacity))}[class~=theme-dark] .dark\:stroke-gray-500{stroke:#6b7280}[class~=theme-dark] .dark\:stroke-gray-700{stroke:#374151}[class~=theme-dark] .dark\:text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}[class~=theme-dark] .dark\:text-gray-50{--tw-text-opacity:1;color:rgb(249 250 251/var(--tw-text-opacity))}[class~=theme-dark] .dark\:\!text-indigo-400{--tw-text-opacity:1!important;color:rgb(129 140 248/var(--tw-text-opacity))!important}[class~=theme-dark] .dark\:ring-white{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}[class~=theme-dark] .dark\:ring-opacity-20{--tw-ring-opacity:0.2}@media (min-width:640px){.sm\:inline{display:inline}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@parca/profile",
3
- "version": "0.16.238",
3
+ "version": "0.16.240",
4
4
  "description": "Profile viewing libraries",
5
5
  "dependencies": {
6
6
  "@parca/client": "^0.16.86",
7
- "@parca/components": "^0.16.185",
7
+ "@parca/components": "^0.16.187",
8
8
  "@parca/dynamicsize": "^0.16.54",
9
9
  "@parca/hooks": "^0.0.21",
10
10
  "@parca/parser": "^0.16.55",
11
- "@parca/store": "^0.16.99",
12
- "@parca/utilities": "^0.0.28",
11
+ "@parca/store": "^0.16.100",
12
+ "@parca/utilities": "^0.0.29",
13
13
  "@tanstack/react-query": "^4.0.5",
14
14
  "@types/react-beautiful-dnd": "^13.1.3",
15
15
  "apache-arrow": "^12.0.0",
@@ -49,5 +49,5 @@
49
49
  "access": "public",
50
50
  "registry": "https://registry.npmjs.org/"
51
51
  },
52
- "gitHead": "5372bb39e5aabffc1884b6141803f173e1a2a490"
52
+ "gitHead": "86e4f37f0fdffafdc5bbe71e8efe04550af1ae59"
53
53
  }
@@ -115,7 +115,8 @@ const Callgraph = ({data, svgString, sampleUnit, width}: Props): JSX.Element =>
115
115
  // eslint-disable-next-line react-hooks/exhaustive-deps
116
116
  }, [svgWrapper.current, svgWrapperLoaded]);
117
117
 
118
- if (data.nodes.length < 1) return <>Profile has no samples</>;
118
+ if (data.nodes.length < 1)
119
+ return <div className="mx-auto text-center">Profile has no samples</div>;
119
120
 
120
121
  const resetView = (): void => setView(originalView);
121
122
 
@@ -33,7 +33,7 @@ import {
33
33
  FIELD_MAPPING_BUILD_ID,
34
34
  FIELD_MAPPING_FILE,
35
35
  } from '../ProfileIcicleGraph/IcicleGraphArrow';
36
- import {nodeLabel} from '../ProfileIcicleGraph/IcicleGraphArrow/utils';
36
+ import {arrowToString, nodeLabel} from '../ProfileIcicleGraph/IcicleGraphArrow/utils';
37
37
  import {ProfileSource} from '../ProfileSource';
38
38
  import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
39
39
  import {useQuery} from '../useQuery';
@@ -190,11 +190,13 @@ const TooltipMetaInfo = ({
190
190
  onCopy: () => void;
191
191
  navigateTo: NavigateFunction;
192
192
  }): React.JSX.Element => {
193
- const mappingFile: string = table.getChild(FIELD_MAPPING_FILE)?.get(row) ?? '';
194
- const mappingBuildID: string = table.getChild(FIELD_MAPPING_BUILD_ID)?.get(row) ?? '';
193
+ const mappingFile: string = arrowToString(table.getChild(FIELD_MAPPING_FILE)?.get(row)) ?? '';
194
+ const mappingBuildID: string =
195
+ arrowToString(table.getChild(FIELD_MAPPING_BUILD_ID)?.get(row)) ?? '';
195
196
  const locationAddress: bigint = table.getChild(FIELD_LOCATION_ADDRESS)?.get(row) ?? 0n;
196
197
  const locationLine: bigint = table.getChild(FIELD_LOCATION_LINE)?.get(row) ?? 0n;
197
- const functionFilename: string = table.getChild(FIELD_FUNCTION_FILE_NAME)?.get(row) ?? '';
198
+ const functionFilename: string =
199
+ arrowToString(table.getChild(FIELD_FUNCTION_FILE_NAME)?.get(row)) ?? '';
198
200
  const functionStartLine: bigint = table.getChild(FIELD_FUNCTION_START_LINE)?.get(row) ?? 0n;
199
201
  const lineNumber =
200
202
  locationLine !== 0n ? locationLine : functionStartLine !== 0n ? functionStartLine : undefined;
@@ -59,7 +59,7 @@ const ProfileExplorerCompare = ({
59
59
  return (
60
60
  <>
61
61
  <div className="flex justify-between gap-2">
62
- <Card className="p-2">
62
+ <Card className="mt-2 p-2">
63
63
  <ProfileSelector
64
64
  queryClient={queryClient}
65
65
  querySelection={queryA}
@@ -72,7 +72,7 @@ const ProfileExplorerCompare = ({
72
72
  onCompareProfile={() => {}}
73
73
  />
74
74
  </Card>
75
- <Card className="p-2">
75
+ <Card className="mt-2 p-2">
76
76
  <ProfileSelector
77
77
  queryClient={queryClient}
78
78
  querySelection={queryB}
@@ -88,17 +88,15 @@ const ProfileExplorerCompare = ({
88
88
  </div>
89
89
  <div className="grid grid-cols-1">
90
90
  {profileA != null && profileB != null ? (
91
- <div className="mt-2">
92
- <Card className="px-6 py-4">
93
- <ProfileViewWithData
94
- navigateTo={navigateTo}
95
- queryClient={queryClient}
96
- profileSource={
97
- new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource())
98
- }
99
- />
100
- </Card>
101
- </div>
91
+ <Card className="mt-2 px-6 py-4">
92
+ <ProfileViewWithData
93
+ navigateTo={navigateTo}
94
+ queryClient={queryClient}
95
+ profileSource={
96
+ new ProfileDiffSource(profileA.ProfileSource(), profileB.ProfileSource())
97
+ }
98
+ />
99
+ </Card>
102
100
  ) : (
103
101
  <div>
104
102
  <div className="my-20 text-center">
@@ -39,7 +39,7 @@ const ProfileExplorerSingle = ({
39
39
  }: ProfileExplorerSingleProps): JSX.Element => {
40
40
  return (
41
41
  <>
42
- <Card className="px-6 py-4">
42
+ <Card className="mt-2 px-6 py-4">
43
43
  <ProfileSelector
44
44
  queryClient={queryClient}
45
45
  querySelection={query}
@@ -53,15 +53,13 @@ const ProfileExplorerSingle = ({
53
53
  />
54
54
  </Card>
55
55
  {profile != null ? (
56
- <div className="mt-2">
57
- <Card className="px-6 py-4">
58
- <ProfileViewWithData
59
- queryClient={queryClient}
60
- profileSource={profile.ProfileSource()}
61
- navigateTo={navigateTo}
62
- />
63
- </Card>
64
- </div>
56
+ <Card className="mt-2 px-6 py-4">
57
+ <ProfileViewWithData
58
+ queryClient={queryClient}
59
+ profileSource={profile.ProfileSource()}
60
+ navigateTo={navigateTo}
61
+ />
62
+ </Card>
65
63
  ) : (
66
64
  <></>
67
65
  )}
@@ -28,7 +28,7 @@ import {
28
28
  FIELD_MAPPING_FILE,
29
29
  } from './index';
30
30
  import useNodeColor from './useNodeColor';
31
- import {nodeLabel} from './utils';
31
+ import {arrowToString, nodeLabel} from './utils';
32
32
 
33
33
  export const RowHeight = 26;
34
34
 
@@ -188,8 +188,8 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
188
188
  const cumulativeColumn = table.getChild(FIELD_CUMULATIVE);
189
189
  const diffColumn = table.getChild(FIELD_DIFF);
190
190
  // get the actual values from the columns
191
- const mappingFile: string | null = mappingColumn?.get(row);
192
- const functionName: string | null = functionNameColumn?.get(row);
191
+ const mappingFile: string | null = arrowToString(mappingColumn?.get(row));
192
+ const functionName: string | null = arrowToString(functionNameColumn?.get(row));
193
193
  const cumulative: bigint = cumulativeColumn?.get(row);
194
194
  const diff: bigint | null = diffColumn?.get(row);
195
195
  const childRows: number[] = Array.from(table.getChild(FIELD_CHILDREN)?.get(row) ?? []);
@@ -199,8 +199,8 @@ export const IcicleNode = React.memo(function IcicleNodeNoMemo({
199
199
  case FIELD_FUNCTION_NAME:
200
200
  childRows.sort((a, b) => {
201
201
  // TODO: Support fallthrough to comparing addresses or something
202
- const afn: string | null = functionNameColumn?.get(a);
203
- const bfn: string | null = functionNameColumn?.get(b);
202
+ const afn: string | null = arrowToString(functionNameColumn?.get(a));
203
+ const bfn: string | null = arrowToString(functionNameColumn?.get(b));
204
204
  if (afn !== null && bfn !== null) {
205
205
  return afn.localeCompare(bfn);
206
206
  }
@@ -35,7 +35,7 @@ import GraphTooltipArrow from '../../GraphTooltipArrow';
35
35
  import GraphTooltipArrowContent from '../../GraphTooltipArrow/Content';
36
36
  import ColorStackLegend from './ColorStackLegend';
37
37
  import {IcicleNode, RowHeight, mappingColors} from './IcicleGraphNodes';
38
- import {extractFeature} from './utils';
38
+ import {arrowToString, extractFeature} from './utils';
39
39
 
40
40
  export const FIELD_LABELS_ONLY = 'labels_only';
41
41
  export const FIELD_MAPPING_FILE = 'mapping_file';
@@ -103,7 +103,8 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
103
103
  const len = mapping.dictionary.length;
104
104
  const entries: string[] = [];
105
105
  for (let i = 0; i < len; i++) {
106
- entries.push(getLastItem(mapping.dictionary.get(i)) ?? '');
106
+ const fn = arrowToString(mapping.dictionary.get(i));
107
+ entries.push(getLastItem(fn) ?? '');
107
108
  }
108
109
  return entries;
109
110
  })
@@ -122,7 +123,7 @@ export const IcicleGraphArrow = memo(function IcicleGraphArrow({
122
123
  }
123
124
  const len = fn.dictionary.length;
124
125
  for (let i = 0; i < len; i++) {
125
- const fn: string | null = functionNamesDict?.get(i);
126
+ const fn: string | null = arrowToString(functionNamesDict?.get(i));
126
127
  if (fn?.startsWith('runtime') === true) {
127
128
  mappings.push('runtime');
128
129
  break;
@@ -31,7 +31,7 @@ export function nodeLabel(
31
31
  level: number,
32
32
  showBinaryName: boolean
33
33
  ): string {
34
- const functionName: string | null = table.getChild(FIELD_FUNCTION_NAME)?.get(row);
34
+ const functionName: string | null = arrowToString(table.getChild(FIELD_FUNCTION_NAME)?.get(row));
35
35
  const labelsOnly: boolean | null = table.getChild(FIELD_LABELS_ONLY)?.get(row);
36
36
  const pprofLabelPrefix = 'pprof_labels.';
37
37
  const labelColumnNames = table.schema.fields.filter(field =>
@@ -76,3 +76,13 @@ export const extractFeature = (mapping: string): Feature => {
76
76
 
77
77
  return {name: EVERYTHING_ELSE, type: FEATURE_TYPES.Misc};
78
78
  };
79
+
80
+ export const arrowToString = (buffer: any): string | null => {
81
+ if (buffer == null || typeof buffer === 'string') {
82
+ return buffer;
83
+ }
84
+ if (ArrayBuffer.isView(buffer)) {
85
+ return String.fromCharCode.apply(null, buffer as unknown as number[]);
86
+ }
87
+ return '';
88
+ };
@@ -18,7 +18,7 @@ import {Icon} from '@iconify/react';
18
18
  import {Table} from 'apache-arrow';
19
19
 
20
20
  import {Flamegraph} from '@parca/client';
21
- import {Button, Select, useURLState} from '@parca/components';
21
+ import {Button, Select, useParcaContext, useURLState} from '@parca/components';
22
22
  import {useContainerDimensions} from '@parca/hooks';
23
23
  import {divide, selectQueryParam, type NavigateFunction} from '@parca/utilities';
24
24
 
@@ -114,6 +114,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
114
114
  setActionButtons,
115
115
  error,
116
116
  }: ProfileIcicleGraphProps): JSX.Element {
117
+ const {loader} = useParcaContext();
117
118
  const compareMode: boolean =
118
119
  selectQueryParam('compare_a') === 'true' && selectQueryParam('compare_b') === 'true';
119
120
  const {ref, dimensions} = useContainerDimensions();
@@ -177,14 +178,20 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
177
178
  );
178
179
  }, [navigateTo, table, curPath, setNewCurPath, setActionButtons]);
179
180
 
181
+ if (loading) {
182
+ return <div className="h-96">{loader}</div>;
183
+ }
184
+
180
185
  if (error != null) {
181
186
  console.error('Error: ', error);
182
187
  return <div className="flex justify-center p-10">An error occurred: {error.message}</div>;
183
188
  }
184
189
 
185
- if (graph === undefined && table === undefined) return <div>no data...</div>;
190
+ if (graph === undefined && table === undefined)
191
+ return <div className="mx-auto text-center">no data...</div>;
186
192
 
187
- if (total === 0n && !loading) return <>Profile has no samples</>;
193
+ if (total === 0n && !loading)
194
+ return <div className="mx-auto text-center">Profile has no samples</div>;
188
195
 
189
196
  if (isTrimmed) {
190
197
  console.info(`Trimmed ${trimmedFormatted} (${trimmedPercentage}%) too small values.`);
@@ -434,8 +434,10 @@ export const ProfileView = ({
434
434
  {...provided.draggableProps}
435
435
  key={dashboardItem}
436
436
  className={cx(
437
- 'w-full rounded p-2 shadow dark:border-gray-500 dark:bg-gray-700',
438
- snapshot.isDragging ? 'bg-gray-200' : 'bg-white'
437
+ 'min-h-[200px] w-full rounded p-2 shadow dark:border dark:border-gray-700 dark:bg-gray-700',
438
+ snapshot.isDragging
439
+ ? 'bg-gray-200 dark:bg-gray-500'
440
+ : 'bg-white dark:bg-gray-700'
439
441
  )}
440
442
  >
441
443
  <VisualizationPanel
@@ -206,8 +206,8 @@ export const Table = React.memo(function Table({
206
206
  };
207
207
  }, [compareMode]);
208
208
 
209
- if (loading) return <>Loading...</>;
210
- if (data === undefined) return <>Profile has no samples</>;
209
+ if (loading) return <div className="mx-auto text-center">Loading...</div>;
210
+ if (data === undefined) return <div className="mx-auto text-center">Profile has no samples</div>;
211
211
 
212
212
  const table = tableFromIPC(data);
213
213
  const flatColumn = table.getChild('flat');
@@ -215,7 +215,7 @@ export const Table = React.memo(function Table({
215
215
  const cumulativeColumn = table.getChild('cumulative');
216
216
  const cumulativeDiffColumn = table.getChild('cumulative_diff');
217
217
 
218
- if (table.numRows === 0) return <>Profile has no samples</>;
218
+ if (table.numRows === 0) return <div className="mx-auto text-center">Profile has no samples</div>;
219
219
 
220
220
  const rows: row[] = [];
221
221
  // TODO: Figure out how to only read the data of the columns we need for the virtualized table
@@ -222,7 +222,8 @@ export const TopTable = React.memo(function TopTable({
222
222
 
223
223
  const total = top != null ? top.list.length : 0;
224
224
 
225
- if (total === 0 && !loading) return <>Profile has no samples</>;
225
+ if (total === 0 && !loading)
226
+ return <div className="mx-auto text-center">Profile has no samples</div>;
226
227
 
227
228
  return (
228
229
  <div className="relative">