Skip to content

Commit

Permalink
ui: MetricsGraphStrips and TimelineGuide component improvements (#5362)
Browse files Browse the repository at this point in the history
* Extracting TimelineGuide from MetricGraphStrips and making it reusable

* Added ticks count to the props

* Removed the unwanted styles

* MetricsGraphStrips props renamed

* More refactoring and state integration

* Storybook sotry fix

* Type fix

* Storybook mock data fix
  • Loading branch information
manojVivek authored Dec 5, 2024
1 parent 6fd206f commit 7e2f970
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 42 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,21 +46,21 @@ export default meta;

export const ThreeCPUStrips = {
args: {
cpus: Array.from(mockData, (_, i) => `CPU ${i + 1}`),
cpus: Array.from(mockData, (_, i) => ({labels: [{name: 'cpuid', value: i + 1}]})),
data: mockData,
selectedTimeline: {index: 1, bounds: [mockData[0][25].timestamp, mockData[0][100].timestamp]},
onSelectedTimeline: (index: number, bounds: NumberDuo): void => {
console.log('onSelectedTimeline', index, bounds);
selectedTimeframe: {index: 1, bounds: [mockData[0][25].timestamp, mockData[0][100].timestamp]},
onSelectedTimeframe: (index: number, bounds: NumberDuo): void => {
console.log('onSelectedTimeframe', index, bounds);
},
},
render: function Component(args: any): JSX.Element {
const [, setArgs] = useArgs();

const onSelectedTimeline = (index: number, bounds: NumberDuo): void => {
args.onSelectedTimeline(index, bounds);
setArgs({...args, selectedTimeline: {index, bounds}});
const onSelectedTimeframe = (index: number, bounds: NumberDuo): void => {
args.onSelectedTimeframe(index, bounds);
setArgs({...args, selectedTimeframe: {index, bounds}});
};

return <MetricsGraphStrips {...args} onSelectedTimeline={onSelectedTimeline} />;
return <MetricsGraphStrips {...args} onSelectedTimeframe={onSelectedTimeframe} />;
},
};
48 changes: 36 additions & 12 deletions ui/packages/shared/profile/src/MetricsGraphStrips/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,30 +16,53 @@ import {useMemo, useState} from 'react';
import {Icon} from '@iconify/react';
import * as d3 from 'd3';

import {LabelSet} from '@parca/client';

import {TimelineGuide} from '../TimelineGuide';
import {NumberDuo} from '../utils';
import {AreaGraph, DataPoint} from './AreaGraph';

interface Props {
cpus: string[];
cpus: LabelSet[];
data: DataPoint[][];
selectedTimeline?: {
index: number;
selectedTimeframe?: {
labels: LabelSet;
bounds: NumberDuo;
};
onSelectedTimeline: (index: number, bounds: NumberDuo | undefined) => void;
onSelectedTimeframe: (labels: LabelSet, bounds: NumberDuo | undefined) => void;
width?: number;
}

const getTimelineGuideHeight = (cpus: string[], collapsedIndices: number[]): number => {
const labelSetToString = (labelSet?: LabelSet): string => {
if (labelSet === undefined) {
return '{}';
}

let str = '{';

let isFirst = true;
for (const label of labelSet.labels) {
if (!isFirst) {
str += ', ';
isFirst = false;
}
str += `${label.name}: ${label.value}`;
}

str += '}';

return str;
};

const getTimelineGuideHeight = (cpus: LabelSet[], collapsedIndices: number[]): number => {
return 56 * (cpus.length - collapsedIndices.length) + 20 * collapsedIndices.length + 24;
};

export const MetricsGraphStrips = ({
cpus,
data,
selectedTimeline,
onSelectedTimeline,
selectedTimeframe,
onSelectedTimeframe,
width,
}: Props): JSX.Element => {
const [collapsedIndices, setCollapsedIndices] = useState<number[]>([]);
Expand Down Expand Up @@ -68,8 +91,9 @@ export const MetricsGraphStrips = ({
/>
{cpus.map((cpu, i) => {
const isCollapsed = collapsedIndices.includes(i);
const labelStr = labelSetToString(cpu);
return (
<div className="relative min-h-5" style={{width: width ?? 1468}} key={cpu}>
<div className="relative min-h-5" style={{width: width ?? 1468}} key={labelStr}>
<div
className="text-xs absolute top-0 left-0 flex gap-[2px] items-center bg-white/50 px-1 rounded-sm cursor-pointer z-30"
onClick={() => {
Expand All @@ -83,19 +107,19 @@ export const MetricsGraphStrips = ({
}}
>
<Icon icon={isCollapsed ? 'bxs:right-arrow' : 'bxs:down-arrow'} />
{cpu}
{labelStr}
</div>
{!isCollapsed ? (
<AreaGraph
data={data[i]}
height={56}
width={width ?? 1468}
fill={color(i.toString()) as string}
fill={color(labelStr) as string}
selectionBounds={
selectedTimeline?.index === i ? selectedTimeline.bounds : undefined
cpu === selectedTimeframe?.labels ? selectedTimeframe.bounds : undefined
}
setSelectionBounds={bounds => {
onSelectedTimeline(i, bounds);
onSelectedTimeframe(cpu, bounds);
}}
/>
) : null}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export const FIELD_MAPPING_BUILD_ID = 'mapping_build_id';
export const FIELD_LOCATION_ADDRESS = 'location_address';
export const FIELD_LOCATION_LINE = 'location_line';
export const FIELD_INLINED = 'inlined';
export const FIELD_TIMESTAMP = 'timestamp';
export const FIELD_FUNCTION_NAME = 'function_name';
export const FIELD_FUNCTION_SYSTEM_NAME = 'function_system_name';
export const FIELD_FUNCTION_FILE_NAME = 'function_file_name';
Expand Down
10 changes: 4 additions & 6 deletions ui/packages/shared/profile/src/ProfileIcicleGraph/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ interface ProfileIcicleGraphProps {
isHalfScreen: boolean;
metadataMappingFiles?: string[];
metadataLoading?: boolean;
showTimelineGuide?: boolean;
}

const ErrorContent = ({errorMessage}: {errorMessage: string}): JSX.Element => {
Expand All @@ -66,10 +65,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
width,
isHalfScreen,
metadataMappingFiles,
showTimelineGuide = false,
}: ProfileIcicleGraphProps): JSX.Element {
const {onError, authenticationErrorMessage, isDarkMode} = useParcaContext();
const {compareMode} = useProfileViewContext();
const {compareMode, timelineGuide} = useProfileViewContext();
const [isLoading, setIsLoading] = useState<boolean>(true);

const mappingsList = useMappingList(metadataMappingFiles);
Expand Down Expand Up @@ -169,9 +167,9 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
if (arrow !== undefined)
return (
<div className="relative">
{showTimelineGuide && (
{timelineGuide?.show === true && (
<TimelineGuide
bounds={[0, 60000]}
bounds={timelineGuide.props.bounds}
width={width}
height={1000}
margin={0}
Expand Down Expand Up @@ -210,7 +208,7 @@ const ProfileIcicleGraph = function ProfileIcicleGraphNonMemo({
isDarkMode,
mappingsList,
isCompareAbsolute,
showTimelineGuide,
timelineGuide,
]);

if (error != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ interface GetDashboardItemProps {
perf?: {
onRender?: ProfilerOnRenderCallback;
};
showTimelineGuide?: boolean;
}

export const getDashboardItem = ({
Expand All @@ -67,7 +66,6 @@ export const getDashboardItem = ({
setSearchString,
callgraphSVG,
perf,
showTimelineGuide,
}: GetDashboardItemProps): JSX.Element => {
switch (type) {
case 'icicle':
Expand Down Expand Up @@ -100,7 +98,6 @@ export const getDashboardItem = ({
}
metadataMappingFiles={flamegraphData.metadataMappingFiles}
metadataLoading={flamegraphData.metadataLoading}
showTimelineGuide={showTimelineGuide}
/>
</ConditionalWrapper>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,27 @@
import {ReactNode, createContext, useContext} from 'react';

import {ProfileSource} from '../../ProfileSource';
import {NumberDuo} from '../../utils';

export type TimelineGuideData =
| {show: false}
| {
show: true;
props: {
bounds: NumberDuo;
};
};

interface Props {
profileSource?: ProfileSource;
compareMode: boolean;
timelineGuide?: TimelineGuideData;
}

export const defaultValue: Props = {
profileSource: undefined,
compareMode: false,
timelineGuide: {show: false},
};

const ProfileViewContext = createContext<Props>(defaultValue);
Expand Down
5 changes: 2 additions & 3 deletions ui/packages/shared/profile/src/ProfileView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export const ProfileView = ({
pprofDownloading,
compare,
showVisualizationSelector,
showTimelineGuide,
timelineGuide,
}: ProfileViewProps): JSX.Element => {
const {
timezone,
Expand Down Expand Up @@ -110,7 +110,6 @@ export const ProfileView = ({
setSearchString,
callgraphSVG,
perf,
showTimelineGuide,
});
};

Expand All @@ -131,7 +130,7 @@ export const ProfileView = ({

return (
<KeyDownProvider>
<ProfileViewContextProvider value={{profileSource, compareMode}}>
<ProfileViewContextProvider value={{profileSource, compareMode, timelineGuide}}>
<DashboardProvider>
<ProfileHeader
profileSourceString={profileSource?.toString(timezone)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
} from '@parca/client';

import {ProfileSource} from '../../ProfileSource';
import {TimelineGuideData} from '../context/ProfileViewContext';

export interface FlamegraphData {
loading: boolean;
Expand Down Expand Up @@ -73,4 +74,5 @@ export interface ProfileViewProps {
pprofDownloading?: boolean;
showVisualizationSelector?: boolean;
showTimelineGuide?: boolean;
timelineGuide?: TimelineGuideData;
}
16 changes: 11 additions & 5 deletions ui/packages/shared/profile/src/ProfileViewWithData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,10 @@ import {QueryRequest_ReportType, QueryServiceClient} from '@parca/client';
import {useGrpcMetadata, useParcaContext, useURLState} from '@parca/components';
import {saveAsBlob} from '@parca/utilities';

import {FIELD_FUNCTION_NAME} from './ProfileIcicleGraph/IcicleGraphArrow';
import {FIELD_FUNCTION_NAME, FIELD_TIMESTAMP} from './ProfileIcicleGraph/IcicleGraphArrow';
import {ProfileSource} from './ProfileSource';
import {ProfileView} from './ProfileView';
import {TimelineGuideData} from './ProfileView/context/ProfileViewContext';
import {useQuery} from './useQuery';
import {downloadPprof} from './utils';

Expand All @@ -28,14 +29,16 @@ interface ProfileViewWithDataProps {
profileSource: ProfileSource;
compare?: boolean;
showVisualizationSelector?: boolean;
showTimelineGuide?: boolean;
isGroupByTimestamp?: boolean;
timelineGuide?: TimelineGuideData;
}

export const ProfileViewWithData = ({
queryClient,
profileSource,
showVisualizationSelector,
showTimelineGuide,
isGroupByTimestamp,
timelineGuide,
}: ProfileViewWithDataProps): JSX.Element => {
const metadata = useGrpcMetadata();
const [dashboardItems] = useURLState<string[]>('dashboard_items', {
Expand All @@ -44,7 +47,10 @@ export const ProfileViewWithData = ({
const [sourceBuildID] = useURLState<string>('source_buildid');
const [sourceFilename] = useURLState<string>('source_filename');
const [groupBy] = useURLState<string[]>('group_by', {
defaultValue: [FIELD_FUNCTION_NAME],
defaultValue: [
isGroupByTimestamp === true ? FIELD_TIMESTAMP : (null as unknown as string),
FIELD_FUNCTION_NAME,
].filter(Boolean),
alwaysReturnArray: true,
});

Expand Down Expand Up @@ -244,7 +250,7 @@ export const ProfileViewWithData = ({
onDownloadPProf={() => void downloadPProfClick()}
pprofDownloading={pprofDownloading}
showVisualizationSelector={showVisualizationSelector}
showTimelineGuide={showTimelineGuide}
timelineGuide={timelineGuide}
/>
);
};
Expand Down
5 changes: 0 additions & 5 deletions ui/packages/shared/profile/src/TimelineGuide/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,6 @@ export const TimelineGuide = ({bounds, width, height, margin, ticks}: Props): JS
y1={-height + 20}
y2={-height + 20}
/>
{/* <g transform={`translate(${(width - 2.5 * margin) / 2}, ${margin / 2})`}>
<text fill="currentColor" dy=".71em" y={5} className="text-sm">
Time
</text>
</g> */}
</g>
</svg>
</div>
Expand Down
1 change: 1 addition & 0 deletions ui/packages/shared/profile/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export * from './ProfileViewWithData';
export * from './utils';
export * from './ProfileTypeSelector';
export * from './SourceView';
export * from './ProfileMetricsGraph';
export {default as Callgraph} from './Callgraph';

export const DEFAULT_PROFILE_EXPLORER_PARAM_VALUES = {
Expand Down

0 comments on commit 7e2f970

Please sign in to comment.