LongTask UI Improvements

1. Script name instead of  repeating “Long Animation Frame” text. Showing script 2 by default, and + more if there are more scripts

2. Task timeline will use Green, Yellow and Red (<100ms, >100ms, >200ms) respectively.

3. Improved formatting, fonts, etc.
This commit is contained in:
Sudheer Salavadi 2025-04-03 16:05:22 -04:00
parent 6d2fd12b44
commit 5ad32757c1
3 changed files with 71 additions and 43 deletions

View file

@ -1,13 +1,14 @@
import React from 'react';
import { observer } from 'mobx-react-lite';
import { useTranslation } from 'react-i18next';
import { Input } from 'antd';
import { Input, Tag } from 'antd';
import { VList, VListHandle } from 'virtua';
import { PlayerContext } from 'App/components/Session/playerContext';
import JumpButton from '../JumpButton';
import { useRegExListFilterMemo } from '../useListFilter';
import BottomBlock from '../BottomBlock';
import { NoContent, Icon } from 'UI';
import { NoContent, Icon, TagBadge } from 'UI';
import { Hourglass } from 'lucide-react';
import { InfoCircleOutlined } from '@ant-design/icons';
import { mockData } from './__mock';
import { Segmented } from 'antd';
@ -140,22 +141,24 @@ function LongTaskRow({
className={'mt-1'}
/>
<div className="flex flex-col">
<div className="flex flex-col w-full">
<div className="flex flex-col">
<TaskTitle entry={task} />
</div>
{expanded ? (
<>
<TaskTimeline task={task} />
<div className={'flex items-center gap-1'}>
<div className={'text-gray-dark'}>First UI event timestamp:</div>
<div>{task.firstUIEventTimestamp.toFixed(2)} ms</div>
<div className={'flex items-center gap-1 mb-2'}>
<div className={'text-neutral-900 font-medium'}>First UI event timestamp:</div>
<div className='text-neutral-600 font-mono block'>{task.firstUIEventTimestamp.toFixed(2)} ms</div>
</div>
<div className={'text-gray-dark'}>Scripts:</div>
<div className="flex flex-col gap-1 pl-2">
{task.scripts.map((script, index) => (
<Script script={script} key={index} />
))}
<div className='flex gap-1'>
<div className={'text-neutral-900 font-medium'}>Scripts:</div>
<div className="flex flex-col gap-1">
{task.scripts.map((script, index) => (
<Script script={script} key={index} />
))}
</div>
</div>
</>
) : null}
@ -165,6 +168,8 @@ function LongTaskRow({
);
}
function TaskTitle({
entry,
}: {
@ -172,20 +177,42 @@ function TaskTitle({
name: string;
duration: number;
blockingDuration?: number;
scripts: LongAnimationTask['scripts'];
};
}) {
const isBlocking =
entry.blockingDuration !== undefined && entry.blockingDuration > 0;
const scriptTitles = entry.scripts.map(script =>
script.invokerType ? script.invokerType : script.name
);
let scriptsDisplay;
if (scriptTitles.length === 0) {
scriptsDisplay = <span className="font-mono font-bold">No Scripts</span>;
} else if (scriptTitles.length === 1) {
scriptsDisplay = <span className="font-mono font-bold">{scriptTitles[0]}</span>;
} else if (scriptTitles.length === 2) {
scriptsDisplay = <span className="font-mono font-bold">{scriptTitles[0]}, {scriptTitles[1]}</span>;
} else {
scriptsDisplay = (
<span className="font-mono font-bold">
{scriptTitles[0]}, {scriptTitles[1]} {' '}
<Tag color="default" bordered={false}>+{scriptTitles.length - 2} More</Tag>
</span>
);
}
return (
<div className={'flex items-center gap-1'}>
<span>Long Animation Frame</span>
<span className={'text-disabled-text'}>
({entry.duration.toFixed(2)} ms)
<div className={'flex items-center gap-1 text-sm'}>
{scriptsDisplay}
<span className={'text-neutral-600 font-mono'}>
{Math.round(entry.duration)} ms
</span>
{isBlocking ? (
<span className={'text-red'}>
{entry.blockingDuration!.toFixed(2)} ms blocking
</span>
<Tag bordered={false} color="red" className="font-mono rounded-lg text-xs flex gap-1 items-center text-red-600">
<Hourglass size={11} /> {Math.round(entry.blockingDuration!)} ms blocking
</Tag>
) : null}
</div>
);

View file

@ -1,5 +1,7 @@
import React from 'react'
import { LongAnimationTask } from './type'
import { Tag } from 'antd';
import { Code } from 'lucide-react';
function getAddress(script: LongAnimationTask['scripts'][number]) {
return `${script.sourceURL}${script.sourceFunctionName ? ':' + script.sourceFunctionName : ''}${script.sourceCharPosition && script.sourceCharPosition >= 0 ? ':' + script.sourceCharPosition : ''}`;
@ -10,7 +12,7 @@ function ScriptTitle({
script: LongAnimationTask['scripts'][number]
}) {
return script.invokerType ? (
<span>{script.invokerType}</span>
<span className=''>{script.invokerType}</span>
) : (
<span>{script.name}</span>
)
@ -23,13 +25,13 @@ function ScriptInfo({
}) {
const hasInvoker = script.invoker !== script.sourceURL;
return (
<div className={'border-l border-l-gray-light pl-1'}>
<div className={''}>
{hasInvoker ? (
<InfoEntry title={'invoker:'} value={script.invoker} />
) : null}
<InfoEntry title={'address:'} value={getAddress(script)} />
<InfoEntry title={'script execution:'} value={`${script.duration} ms`} />
<InfoEntry title={'pause duration:'} value={`${script.pauseDuration} ms`} />
<InfoEntry title={'script execution:'} value={`${Math.round(script.duration)} ms`} />
<InfoEntry title={'pause duration:'} value={`${Math.round(script.pauseDuration)} ms`} />
</div>
);
}
@ -42,17 +44,17 @@ function InfoEntry({
value: string | number;
}) {
return (
<div className={'flex items-center gap-1'}>
<div className={'text-disabled-text'}>{title}</div>
<div>{value}</div>
<div className={'flex items-center gap-1 text-sm'}>
<div className={''}>{title}</div>
<div className='font-mono text-neutral-600'>{value}</div>
</div>
);
}
function Script({ script }: { script: LongAnimationTask['scripts'][number] }) {
return (
<div className="flex flex-col">
<ScriptTitle script={script} />
<div className="flex flex-col mb-4">
<Tag className='w-fit font-mono text-sm font-bold flex gap-1 items-center rounded-lg'><Code size={12} /> <ScriptTitle script={script} /></Tag>
<ScriptInfo script={script} />
</div>
)

View file

@ -3,6 +3,12 @@ import { Tooltip } from 'antd'
import { LongAnimationTask } from "./type";
import cn from "classnames";
export const getSeverityBgClass = (duration: number) => {
if (duration > 200) return 'bg-[#CC0000]';
if (duration > 100) return 'bg-[#EFB100]';
return 'bg-[#66a299]';
};
function TaskTimeline({ task }: { task: LongAnimationTask }) {
const totalDuration = task.duration;
const scriptDuration = task.scripts.reduce((sum, script) => sum + script.duration, 0);
@ -16,41 +22,34 @@ function TaskTimeline({ task }: { task: LongAnimationTask }) {
const layoutWidth = (layoutDuration / totalDuration) * 100;
const idleWidth = (idleDuration / totalDuration) * 100;
const getSeverityClass = (duration) => {
if (duration > 200) return 'bg-[#e7000b]';
if (duration > 100) return 'bg-[#efb100]';
return 'bg-[#51a2ff]';
};
return (
<div className="w-full mb-2">
<div className="text-gray-dark mb-1">Timeline:</div>
<div className="flex h-4 w-full rounded-sm overflow-hidden">
<div className="w-full mb-2 mt-1">
<div className="flex h-2 w-full rounded overflow-hidden">
{scriptDuration > 0 && (
<TimelineSegment
classes={`${getSeverityClass(scriptDuration)} h-full`}
name={`Script: ${scriptDuration.toFixed(2)}ms`}
classes={`${getSeverityBgClass(scriptDuration)} h-full`}
name={`Script: ${Math.round(scriptDuration)}ms`}
width={scriptWidth}
/>
)}
{idleDuration > 0 && (
<TimelineSegment
classes="bg-gray-light h-full"
classes="bg-gray-light h-full bg-[repeating-linear-gradient(45deg,#ccc_0px,#ccc_5px,#f2f2f2_5px,#f2f2f2_10px)]"
width={idleWidth}
name={`Idle: ${idleDuration.toFixed(2)}ms`}
name={`Idle: ${Math.round(idleDuration)}ms`}
/>
)}
{layoutDuration > 0 && (
<TimelineSegment
classes="bg-[#8200db] h-full"
width={layoutWidth}
name={`Layout & Style: ${layoutDuration.toFixed(2)}ms`}
name={`Layout & Style: ${Math.round(layoutDuration)}ms`}
/>
)}
</div>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>start: {task.startTime.toFixed(2)}ms</span>
<span>finish: {(task.startTime + task.duration).toFixed(2)}ms</span>
<span>Start: {Math.round(task.startTime)} ms</span>
<span>Finish: {Math.round(task.startTime + task.duration)} ms</span>
</div>
</div>
);