@@ -295,7 +295,8 @@ function TraceView({ run, trace, maximumLiveReloadingSetting, resizable }: Loade
295295 return < > </ > ;
296296 }
297297
298- const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace ;
298+ const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt, queuedDuration } =
299+ trace ;
299300 const shouldLiveReload = events . length <= maximumLiveReloadingSetting ;
300301
301302 const changeToSpan = useDebounce ( ( selectedSpan : string ) => {
@@ -345,6 +346,7 @@ function TraceView({ run, trace, maximumLiveReloadingSetting, resizable }: Loade
345346 totalDuration = { duration }
346347 rootSpanStatus = { rootSpanStatus }
347348 rootStartedAt = { rootStartedAt ? new Date ( rootStartedAt ) : undefined }
349+ queuedDuration = { queuedDuration }
348350 environmentType = { run . environment . type }
349351 shouldLiveReload = { shouldLiveReload }
350352 maximumLiveReloadingSetting = { maximumLiveReloadingSetting }
@@ -472,6 +474,7 @@ type TasksTreeViewProps = {
472474 totalDuration : number ;
473475 rootSpanStatus : "executing" | "completed" | "failed" ;
474476 rootStartedAt : Date | undefined ;
477+ queuedDuration : number | undefined ;
475478 environmentType : RuntimeEnvironmentType ;
476479 shouldLiveReload : boolean ;
477480 maximumLiveReloadingSetting : number ;
@@ -491,6 +494,7 @@ function TasksTreeView({
491494 totalDuration,
492495 rootSpanStatus,
493496 rootStartedAt,
497+ queuedDuration,
494498 environmentType,
495499 shouldLiveReload,
496500 maximumLiveReloadingSetting,
@@ -502,12 +506,14 @@ function TasksTreeView({
502506 const [ errorsOnly , setErrorsOnly ] = useState ( false ) ;
503507 const [ showDebug , setShowDebug ] = useState ( false ) ;
504508 const [ showDurations , setShowDurations ] = useState ( true ) ;
509+ const [ showQueueTime , setShowQueueTime ] = useState ( false ) ;
505510 const [ scale , setScale ] = useState ( 0 ) ;
506511 const parentRef = useRef < HTMLDivElement > ( null ) ;
507512 const treeScrollRef = useRef < HTMLDivElement > ( null ) ;
508513 const timelineScrollRef = useRef < HTMLDivElement > ( null ) ;
509514
510515 const displayEvents = showDebug ? events : events . filter ( ( event ) => ! event . data . isDebug ) ;
516+ const queuedTime = showQueueTime ? undefined : queuedDuration ;
511517
512518 const {
513519 nodes,
@@ -556,6 +562,13 @@ function TasksTreeView({
556562 onCheckedChange = { ( e ) => setShowDebug ( e . valueOf ( ) ) }
557563 />
558564 ) }
565+ < Switch
566+ variant = "small"
567+ label = "Queue time"
568+ checked = { showQueueTime }
569+ onCheckedChange = { ( e ) => setShowQueueTime ( e . valueOf ( ) ) }
570+ shortcut = { { key : "Q" } }
571+ />
559572 < Switch
560573 variant = "small"
561574 label = "Errors only"
@@ -692,6 +705,7 @@ function TasksTreeView({
692705 events = { events }
693706 rootSpanStatus = { rootSpanStatus }
694707 rootStartedAt = { rootStartedAt }
708+ queuedDuration = { queuedTime }
695709 parentRef = { parentRef }
696710 timelineScrollRef = { timelineScrollRef }
697711 nodes = { nodes }
@@ -754,7 +768,7 @@ function TasksTreeView({
754768
755769type TimelineViewProps = Pick <
756770 TasksTreeViewProps ,
757- "totalDuration" | "rootSpanStatus" | "events" | "rootStartedAt"
771+ "totalDuration" | "rootSpanStatus" | "events" | "rootStartedAt" | "queuedDuration"
758772> & {
759773 scale : number ;
760774 parentRef : React . RefObject < HTMLDivElement > ;
@@ -785,27 +799,32 @@ function TimelineView({
785799 toggleNodeSelection,
786800 showDurations,
787801 treeScrollRef,
802+ queuedDuration,
788803} : TimelineViewProps ) {
789- const isAdmin = useHasAdminAccess ( ) ;
790804 const timelineContainerRef = useRef < HTMLDivElement > ( null ) ;
791805 const initialTimelineDimensions = useInitialDimensions ( timelineContainerRef ) ;
792806 const minTimelineWidth = initialTimelineDimensions ?. width ?? 300 ;
793807 const maxTimelineWidth = minTimelineWidth * 10 ;
794808
795809 //we want to live-update the duration if the root span is still executing
796- const [ duration , setDuration ] = useState ( totalDuration ) ;
810+ const [ duration , setDuration ] = useState ( queueAdjustedNs ( totalDuration , queuedDuration ) ) ;
797811 useEffect ( ( ) => {
798812 if ( rootSpanStatus !== "executing" || ! rootStartedAt ) {
799- setDuration ( totalDuration ) ;
813+ setDuration ( queueAdjustedNs ( totalDuration , queuedDuration ) ) ;
800814 return ;
801815 }
802816
803817 const interval = setInterval ( ( ) => {
804- setDuration ( millisecondsToNanoseconds ( Date . now ( ) - rootStartedAt . getTime ( ) ) ) ;
818+ setDuration (
819+ queueAdjustedNs (
820+ millisecondsToNanoseconds ( Date . now ( ) - rootStartedAt . getTime ( ) ) ,
821+ queuedDuration
822+ )
823+ ) ;
805824 } , 500 ) ;
806825
807826 return ( ) => clearInterval ( interval ) ;
808- } , [ totalDuration , rootSpanStatus ] ) ;
827+ } , [ totalDuration , rootSpanStatus , queuedDuration , rootStartedAt ] ) ;
809828
810829 return (
811830 < div
@@ -820,7 +839,11 @@ function TimelineView({
820839 maxWidth = { maxTimelineWidth }
821840 >
822841 { /* Follows the cursor */ }
823- < CurrentTimeIndicator totalDuration = { duration } rootStartedAt = { rootStartedAt } />
842+ < CurrentTimeIndicator
843+ totalDuration = { duration }
844+ rootStartedAt = { rootStartedAt }
845+ queuedDurationNs = { queuedDuration }
846+ />
824847
825848 < Timeline . Row className = "grid h-full grid-rows-[2rem_1fr]" >
826849 { /* The duration labels */ }
@@ -920,6 +943,8 @@ function TimelineView({
920943 getTreeProps = { getTreeProps }
921944 parentClassName = "h-full scrollbar-hide"
922945 renderNode = { ( { node, state, index, virtualizer, virtualItem } ) => {
946+ const isTopSpan = node . id === events [ 0 ] ?. id ;
947+
923948 return (
924949 < Timeline . Row
925950 key = { index }
@@ -941,7 +966,9 @@ function TimelineView({
941966 eventIndex === 0 ? (
942967 < Timeline . Point
943968 key = { eventIndex }
944- ms = { nanosecondsToMilliseconds ( event . offset ) }
969+ ms = { nanosecondsToMilliseconds (
970+ queueAdjustedNs ( event . offset , queuedDuration )
971+ ) }
945972 >
946973 { ( ms ) => (
947974 < motion . div
@@ -956,13 +983,15 @@ function TimelineView({
956983 ) : (
957984 < Timeline . Point
958985 key = { eventIndex }
959- ms = { nanosecondsToMilliseconds ( event . offset ) }
986+ ms = { nanosecondsToMilliseconds (
987+ queueAdjustedNs ( event . offset , queuedDuration )
988+ ) }
960989 className = "z-10"
961990 >
962991 { ( ms ) => (
963992 < motion . div
964993 className = { cn (
965- "-ml-1 size-[0.3125rem] rounded-full border bg-background-bright" ,
994+ "-ml-[0.1562rem] size-[0.3125rem] rounded-full border bg-background-bright" ,
966995 eventBorderClassName ( node . data )
967996 ) }
968997 layoutId = { `${ node . id } -${ event . name } ` }
@@ -975,7 +1004,9 @@ function TimelineView({
9751004 node . data . timelineEvents [ 0 ] &&
9761005 node . data . timelineEvents [ 0 ] . offset < node . data . offset ? (
9771006 < Timeline . Span
978- startMs = { nanosecondsToMilliseconds ( node . data . timelineEvents [ 0 ] . offset ) }
1007+ startMs = { nanosecondsToMilliseconds (
1008+ queueAdjustedNs ( node . data . timelineEvents [ 0 ] . offset , queuedDuration )
1009+ ) }
9791010 durationMs = { nanosecondsToMilliseconds (
9801011 node . data . offset - node . data . timelineEvents [ 0 ] . offset
9811012 ) }
@@ -988,21 +1019,35 @@ function TimelineView({
9881019 ) : null }
9891020 < SpanWithDuration
9901021 showDuration = { state . selected ? true : showDurations }
991- startMs = { nanosecondsToMilliseconds ( node . data . offset ) }
1022+ startMs = { nanosecondsToMilliseconds (
1023+ Math . max ( queueAdjustedNs ( node . data . offset , queuedDuration ) , 0 )
1024+ ) }
9921025 durationMs = {
9931026 node . data . duration
994- ? nanosecondsToMilliseconds ( node . data . duration )
995- : nanosecondsToMilliseconds ( duration - node . data . offset )
1027+ ? //completed
1028+ nanosecondsToMilliseconds ( Math . min ( node . data . duration , duration ) )
1029+ : //in progress
1030+ nanosecondsToMilliseconds (
1031+ Math . min (
1032+ duration + ( queuedDuration ?? 0 ) - node . data . offset ,
1033+ duration
1034+ )
1035+ )
9961036 }
9971037 node = { node }
1038+ fadeLeft = { isTopSpan && queuedDuration !== undefined }
9981039 />
9991040 </ >
10001041 ) : (
1001- < Timeline . Point ms = { nanosecondsToMilliseconds ( node . data . offset ) } >
1042+ < Timeline . Point
1043+ ms = { nanosecondsToMilliseconds (
1044+ queueAdjustedNs ( node . data . offset , queuedDuration )
1045+ ) }
1046+ >
10021047 { ( ms ) => (
10031048 < motion . div
10041049 className = { cn (
1005- "-ml-1 size-3 rounded-full border-2 border-background-bright" ,
1050+ "-ml-0.5 size-3 rounded-full border-2 border-background-bright" ,
10061051 eventBackgroundClassName ( node . data )
10071052 ) }
10081053 layoutId = { node . id }
@@ -1027,6 +1072,14 @@ function TimelineView({
10271072 ) ;
10281073}
10291074
1075+ function queueAdjustedNs ( timeNs : number , queuedDurationNs : number | undefined ) {
1076+ if ( queuedDurationNs ) {
1077+ return timeNs - queuedDurationNs ;
1078+ }
1079+
1080+ return timeNs ;
1081+ }
1082+
10301083function NodeText ( { node } : { node : TraceEvent } ) {
10311084 const className = "truncate" ;
10321085 return (
@@ -1180,15 +1233,18 @@ function PulsingDot() {
11801233function SpanWithDuration ( {
11811234 showDuration,
11821235 node,
1236+ fadeLeft,
11831237 ...props
1184- } : Timeline . SpanProps & { node : TraceEvent ; showDuration : boolean } ) {
1238+ } : Timeline . SpanProps & { node : TraceEvent ; showDuration : boolean ; fadeLeft : boolean } ) {
11851239 return (
11861240 < Timeline . Span { ...props } >
11871241 < motion . div
11881242 className = { cn (
1189- "relative flex h-4 w-full min-w-0.5 items-center rounded-sm" ,
1190- eventBackgroundClassName ( node . data )
1243+ "relative flex h-4 w-full min-w-0.5 items-center" ,
1244+ eventBackgroundClassName ( node . data ) ,
1245+ fadeLeft ? "rounded-r-sm bg-gradient-to-r from-black/50 to-transparent" : "rounded-sm"
11911246 ) }
1247+ style = { { backgroundSize : "20px 100%" , backgroundRepeat : "no-repeat" } }
11921248 layoutId = { node . id }
11931249 >
11941250 { node . data . isPartial && (
@@ -1197,19 +1253,22 @@ function SpanWithDuration({
11971253 style = { { backgroundImage : `url(${ tileBgPath } )` , backgroundSize : "8px 8px" } }
11981254 />
11991255 ) }
1200- < div
1256+ < motion . div
12011257 className = { cn (
1202- "sticky left-0 z-10 transition group-hover:opacity-100" ,
1258+ "sticky left-0 z-10 transition-opacity group-hover:opacity-100" ,
12031259 ! showDuration && "opacity-0"
12041260 ) }
12051261 >
1206- < div className = "whitespace-nowrap rounded-sm px-1 py-0.5 text-xxs text-text-bright text-shadow-custom" >
1262+ < motion . div
1263+ className = "whitespace-nowrap rounded-sm px-1 py-0.5 text-xxs text-text-bright text-shadow-custom"
1264+ layout = "position"
1265+ >
12071266 { formatDurationMilliseconds ( props . durationMs , {
12081267 style : "short" ,
12091268 maxDecimalPoints : props . durationMs < 1000 ? 0 : 1 ,
12101269 } ) }
1211- </ div >
1212- </ div >
1270+ </ motion . div >
1271+ </ motion . div >
12131272 </ motion . div >
12141273 </ Timeline . Span >
12151274 ) ;
@@ -1220,9 +1279,11 @@ const edgeBoundary = 0.17;
12201279function CurrentTimeIndicator ( {
12211280 totalDuration,
12221281 rootStartedAt,
1282+ queuedDurationNs,
12231283} : {
12241284 totalDuration : number ;
12251285 rootStartedAt : Date | undefined ;
1286+ queuedDurationNs : number | undefined ;
12261287} ) {
12271288 return (
12281289 < Timeline . FollowCursor >
@@ -1235,7 +1296,11 @@ function CurrentTimeIndicator({
12351296 offset = lerp ( 0.5 , 1 , ( ratio - ( 1 - edgeBoundary ) ) / edgeBoundary ) ;
12361297 }
12371298
1238- const currentTime = rootStartedAt ? new Date ( rootStartedAt . getTime ( ) + ms ) : undefined ;
1299+ const currentTime = rootStartedAt
1300+ ? new Date (
1301+ rootStartedAt . getTime ( ) + ms + nanosecondsToMilliseconds ( queuedDurationNs ?? 0 )
1302+ )
1303+ : undefined ;
12391304 const currentTimeComponent = currentTime ? < DateTimeShort date = { currentTime } /> : < > </ > ;
12401305
12411306 return (
@@ -1300,6 +1365,7 @@ function KeyboardShortcuts({
13001365 title = "Collapse all"
13011366 />
13021367 < NumberShortcuts toggleLevel = { ( number ) => toggleExpandLevel ( number ) } />
1368+ < ShortcutWithAction shortcut = { { key : "Q" } } title = "Queue time" action = { ( ) => { } } />
13031369 </ >
13041370 ) ;
13051371}
0 commit comments