Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 2 additions & 18 deletions ui/packages/shared/profile/src/ProfileIcicleGraph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,18 @@

import React, {useEffect, useMemo, useState} from 'react';

import {Table, tableFromIPC} from 'apache-arrow';
import {AnimatePresence, motion} from 'framer-motion';

import {Flamegraph, FlamegraphArrow} from '@parca/client';
import {IcicleGraphSkeleton, useParcaContext, useURLState} from '@parca/components';
import {ProfileType} from '@parca/parser';
import {capitalizeOnlyFirstLetter, divide, selectQueryParam} from '@parca/utilities';
import {capitalizeOnlyFirstLetter, divide} from '@parca/utilities';

import {useProfileViewContext} from '../ProfileView/ProfileViewContext';
import DiffLegend from '../components/DiffLegend';
import {IcicleGraph} from './IcicleGraph';
import {FIELD_FUNCTION_NAME, IcicleGraphArrow} from './IcicleGraphArrow';
import ColorStackLegend from './IcicleGraphArrow/ColorStackLegend';
import useMappingList, {useFilenamesList} from './IcicleGraphArrow/useMappingList';
import useMappingList from './IcicleGraphArrow/useMappingList';

const numberFormatter = new Intl.NumberFormat('en-US');

Expand Down Expand Up @@ -70,14 +68,8 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
const {onError, authenticationErrorMessage, isDarkMode} = useParcaContext();
const {compareMode} = useProfileViewContext();
const [isLoading, setIsLoading] = useState<boolean>(true);
const isColorStackLegendEnabled = selectQueryParam('color_stack_legend') === 'true';

const table: Table<any> | null = useMemo(() => {
return arrow !== undefined ? tableFromIPC(arrow.record) : null;
}, [arrow]);

const mappingsList = useMappingList(metadataMappingFiles);
const filenamesList = useFilenamesList(table);

const [storeSortBy = FIELD_FUNCTION_NAME] = useURLState('sort_by');
const [colorBy, setColorBy] = useURLState('color_by');
Expand All @@ -89,7 +81,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
const [compareAbsolute = compareAbsoluteDefault] = useURLState('compare_absolute');
const isCompareAbsolute = compareAbsolute === 'true';

const colorByValue = colorBy === undefined || colorBy === '' ? 'binary' : (colorBy as string);
const mappingsListCount = useMemo(
() => mappingsList.filter(m => m !== '').length,
[mappingsList]
Expand Down Expand Up @@ -231,13 +222,6 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
transition={{duration: 0.5}}
>
{compareMode ? <DiffLegend /> : null}
{isColorStackLegendEnabled && (
<ColorStackLegend
compareMode={compareMode}
mappings={colorByValue === 'binary' ? mappingsList : filenamesList}
loading={isLoading}
/>
)}
<div className="min-h-48" id="h-icicle-graph">
<>{icicleGraph}</>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ import {useURLState} from '@parca/components';
import {USER_PREFERENCES, useCurrentColorProfile, useUserPreference} from '@parca/hooks';
import {EVERYTHING_ELSE, selectDarkMode, useAppSelector} from '@parca/store';

import {getMappingColors} from '.';
import useMappingList from './useMappingList';
import {getMappingColors} from '../ProfileIcicleGraph/IcicleGraphArrow/';
import useMappingList from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';

interface Props {
mappings?: string[];
Expand Down
23 changes: 13 additions & 10 deletions ui/packages/shared/profile/src/ProfileView/VisualizationPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import React, {useState} from 'react';
import React from 'react';

import {Icon} from '@iconify/react';
import cx from 'classnames';
Expand All @@ -26,11 +26,11 @@ interface Props {
isMultiPanelView: boolean;
handleClosePanel: (dashboardItem: string) => void;
dragHandleProps: DraggableProvidedDragHandleProps | null | undefined;
getDashboardItemByType: (props: {
type: string;
isHalfScreen: boolean;
setActionButtons: (actionButtons: JSX.Element) => void;
}) => JSX.Element;
getDashboardItemByType: (props: {type: string; isHalfScreen: boolean}) => JSX.Element;
actionButtons: {
icicle: JSX.Element;
table: JSX.Element;
};
}

export const VisualizationPanel = React.memo(function VisualizationPanel({
Expand All @@ -39,8 +39,8 @@ export const VisualizationPanel = React.memo(function VisualizationPanel({
handleClosePanel,
dragHandleProps,
getDashboardItemByType,
actionButtons,
}: Props): JSX.Element {
const [actionButtons, setActionButtons] = useState<JSX.Element>(<></>);
const {flamegraphHint} = useParcaContext();

return (
Expand All @@ -52,14 +52,18 @@ export const VisualizationPanel = React.memo(function VisualizationPanel({
isMultiPanelView && dashboardItem === 'icicle' ? 'items-end gap-x-2' : 'items-end'
)}
>
<div className="flex items-center">
<div className="flex items-center gap-2">
<div
className={cx(isMultiPanelView ? '' : 'hidden', 'flex items-center')}
{...dragHandleProps}
>
<Icon className="text-xl" icon="material-symbols:drag-indicator" />
</div>
<div className="flex gap-2">{actionButtons}</div>
{isMultiPanelView ? (
<div className="flex gap-2">
{actionButtons[dashboardItem as keyof typeof actionButtons]}
</div>
) : null}
</div>
<div
className={cx(
Expand All @@ -83,7 +87,6 @@ export const VisualizationPanel = React.memo(function VisualizationPanel({
{getDashboardItemByType({
type: dashboardItem,
isHalfScreen: isMultiPanelView,
setActionButtons,
})}
</>
);
Expand Down
68 changes: 59 additions & 9 deletions ui/packages/shared/profile/src/ProfileView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {Profiler, ProfilerProps, useCallback, useEffect, useState} from 'react';
import {Profiler, ProfilerProps, useCallback, useEffect, useMemo, useState} from 'react';

import {Table as ArrowTable, tableFromIPC} from 'apache-arrow';
import cx from 'classnames';
import {scaleLinear} from 'd3';
import graphviz from 'graphviz-wasm';
Expand Down Expand Up @@ -42,10 +43,17 @@ import {Callgraph} from '../';
import {jsonToDot} from '../Callgraph/utils';
import ProfileIcicleGraph from '../ProfileIcicleGraph';
import {FIELD_FUNCTION_NAME} from '../ProfileIcicleGraph/IcicleGraphArrow';
import useMappingList, {
useFilenamesList,
} from '../ProfileIcicleGraph/IcicleGraphArrow/useMappingList';
import {ProfileSource} from '../ProfileSource';
import {SourceView} from '../SourceView';
import {Table} from '../Table';
import VisualisationToolbar from '../components/VisualisationToolbar';
import VisualisationToolbar, {
IcicleGraphToolbar,
TableToolbar,
} from '../components/VisualisationToolbar';
import ColorStackLegend from './ColorStackLegend';
import {ProfileViewContextProvider} from './ProfileViewContext';
import {VisualizationPanel} from './VisualizationPanel';

Expand Down Expand Up @@ -130,10 +138,22 @@ export const ProfileView = ({
const [graphvizLoaded, setGraphvizLoaded] = useState(false);
const [callgraphSVG, setCallgraphSVG] = useState<string | undefined>(undefined);
const [currentSearchString, setSearchString] = useURLState<string | undefined>('search_string');
const [colorStackLegend] = useURLState<string | undefined>('color_stack_legend');
const [colorBy] = useURLState('color_by');

const isColorStackLegendEnabled = colorStackLegend === 'true';
const colorByValue = colorBy === undefined || colorBy === '' ? 'binary' : (colorBy as string);

const isDarkMode = useAppSelector(selectDarkMode);
const isMultiPanelView = dashboardItems.length > 1;

const table: ArrowTable<any> | null = useMemo(() => {
return flamegraphData.arrow !== undefined ? tableFromIPC(flamegraphData.arrow.record) : null;
}, [flamegraphData.arrow]);

const mappingsList = useMappingList(flamegraphData.metadataMappingFiles);
const filenamesList = useFilenamesList(table);

const {perf, profileViewExternalMainActions} = useParcaContext();

useEffect(() => {
Expand Down Expand Up @@ -195,11 +215,9 @@ export const ProfileView = ({
const getDashboardItemByType = ({
type,
isHalfScreen,
setActionButtons,
}: {
type: string;
isHalfScreen: boolean;
setActionButtons: (actionButtons: JSX.Element) => void;
}): JSX.Element => {
switch (type) {
case 'icicle': {
Expand All @@ -221,13 +239,12 @@ export const ProfileView = ({
filtered={filtered}
profileType={profileSource?.ProfileType()}
loading={flamegraphData.loading}
setActionButtons={setActionButtons}
error={flamegraphData.error}
isHalfScreen={isHalfScreen}
width={
dimensions?.width !== undefined
? isHalfScreen
? (dimensions.width - 40) / 2
? (dimensions.width - 54) / 2
: dimensions.width - 16
: 0
}
Expand Down Expand Up @@ -260,10 +277,10 @@ export const ProfileView = ({
data={topTableData.arrow?.record}
unit={topTableData.unit}
profileType={profileSource?.ProfileType()}
setActionButtons={setActionButtons}
currentSearchString={currentSearchString}
setSearchString={setSearchString}
isHalfScreen={isHalfScreen}
metadataMappingFiles={flamegraphData.metadataMappingFiles}
/>
) : (
<></>
Expand All @@ -276,7 +293,6 @@ export const ProfileView = ({
data={sourceData.data}
total={total}
filtered={filtered}
setActionButtons={setActionButtons}
/>
) : (
<></>
Expand Down Expand Up @@ -342,6 +358,28 @@ export const ProfileView = ({
hasProfileSource &&
(profileViewExternalMainActions === null || profileViewExternalMainActions === undefined);

const clearSelection = useCallback((): void => {
setSearchString?.('');
}, [setSearchString]);

const getActionButtonsWithMultiPanelView = (): {
icicle: JSX.Element;
table: JSX.Element;
} => {
return {
icicle: <IcicleGraphToolbar curPath={curPath} setNewCurPath={setNewCurPath} />,
table: (
<TableToolbar
profileType={profileSource?.ProfileType()}
total={total}
filtered={filtered}
clearSelection={clearSelection}
currentSearchString={currentSearchString}
/>
),
};
};

return (
<KeyDownProvider>
<ProfileViewContextProvider value={{profileSource, compareMode}}>
Expand Down Expand Up @@ -400,6 +438,14 @@ export const ProfileView = ({
groupByLabels={flamegraphData.metadataLabels ?? []}
/>

{isColorStackLegendEnabled && (
<ColorStackLegend
compareMode={compareMode}
mappings={colorByValue === 'binary' ? mappingsList : filenamesList}
loading={flamegraphData.metadataLoading}
/>
)}

<div className="w-full" ref={ref}>
<DragDropContext onDragEnd={onDragEnd}>
<Droppable droppableId="droppable" direction="horizontal">
Expand Down Expand Up @@ -429,7 +475,10 @@ export const ProfileView = ({
'w-full min-h-96',
snapshot.isDragging
? 'bg-gray-200 dark:bg-gray-500'
: 'bg-white dark:bg-gray-900'
: 'bg-white dark:bg-gray-900',
isMultiPanelView
? 'border-2 border-gray-100 dark:border-gray-700 rounded-md p-3'
: ''
)}
>
<VisualizationPanel
Expand All @@ -439,6 +488,7 @@ export const ProfileView = ({
getDashboardItemByType={getDashboardItemByType}
dragHandleProps={provided.dragHandleProps}
index={index}
actionButtons={getActionButtonsWithMultiPanelView()}
/>
</div>
)}
Expand Down
4 changes: 1 addition & 3 deletions ui/packages/shared/profile/src/ProfileViewWithData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,8 @@ export const ProfileViewWithData = ({
profileSource,
QueryRequest_ReportType.PROFILE_METADATA,
{
skip: !dashboardItems.includes('icicle'),
nodeTrimThreshold,
groupBy,
invertCallStack,
binaryFrameFilter: undefined,
}
);

Expand All @@ -97,6 +94,7 @@ export const ProfileViewWithData = ({
error: tableError,
} = useQuery(queryClient, profileSource, QueryRequest_ReportType.TABLE_ARROW, {
skip: !dashboardItems.includes('table'),
binaryFrameFilter,
});

const {
Expand Down
Loading