@@ -295,7 +295,8 @@ function TraceView({ run, trace, maximumLiveReloadingSetting, resizable }: Loade
295
295
return < > </ > ;
296
296
}
297
297
298
- const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace ;
298
+ const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt, queuedDuration } =
299
+ trace ;
299
300
const shouldLiveReload = events . length <= maximumLiveReloadingSetting ;
300
301
301
302
const changeToSpan = useDebounce ( ( selectedSpan : string ) => {
@@ -345,6 +346,7 @@ function TraceView({ run, trace, maximumLiveReloadingSetting, resizable }: Loade
345
346
totalDuration = { duration }
346
347
rootSpanStatus = { rootSpanStatus }
347
348
rootStartedAt = { rootStartedAt ? new Date ( rootStartedAt ) : undefined }
349
+ queuedDuration = { queuedDuration }
348
350
environmentType = { run . environment . type }
349
351
shouldLiveReload = { shouldLiveReload }
350
352
maximumLiveReloadingSetting = { maximumLiveReloadingSetting }
@@ -472,6 +474,7 @@ type TasksTreeViewProps = {
472
474
totalDuration : number ;
473
475
rootSpanStatus : "executing" | "completed" | "failed" ;
474
476
rootStartedAt : Date | undefined ;
477
+ queuedDuration : number | undefined ;
475
478
environmentType : RuntimeEnvironmentType ;
476
479
shouldLiveReload : boolean ;
477
480
maximumLiveReloadingSetting : number ;
@@ -491,6 +494,7 @@ function TasksTreeView({
491
494
totalDuration,
492
495
rootSpanStatus,
493
496
rootStartedAt,
497
+ queuedDuration,
494
498
environmentType,
495
499
shouldLiveReload,
496
500
maximumLiveReloadingSetting,
@@ -502,12 +506,14 @@ function TasksTreeView({
502
506
const [ errorsOnly , setErrorsOnly ] = useState ( false ) ;
503
507
const [ showDebug , setShowDebug ] = useState ( false ) ;
504
508
const [ showDurations , setShowDurations ] = useState ( true ) ;
509
+ const [ showQueueTime , setShowQueueTime ] = useState ( false ) ;
505
510
const [ scale , setScale ] = useState ( 0 ) ;
506
511
const parentRef = useRef < HTMLDivElement > ( null ) ;
507
512
const treeScrollRef = useRef < HTMLDivElement > ( null ) ;
508
513
const timelineScrollRef = useRef < HTMLDivElement > ( null ) ;
509
514
510
515
const displayEvents = showDebug ? events : events . filter ( ( event ) => ! event . data . isDebug ) ;
516
+ const queuedTime = showQueueTime ? undefined : queuedDuration ;
511
517
512
518
const {
513
519
nodes,
@@ -556,6 +562,13 @@ function TasksTreeView({
556
562
onCheckedChange = { ( e ) => setShowDebug ( e . valueOf ( ) ) }
557
563
/>
558
564
) }
565
+ < Switch
566
+ variant = "small"
567
+ label = "Queue time"
568
+ checked = { showQueueTime }
569
+ onCheckedChange = { ( e ) => setShowQueueTime ( e . valueOf ( ) ) }
570
+ shortcut = { { key : "Q" } }
571
+ />
559
572
< Switch
560
573
variant = "small"
561
574
label = "Errors only"
@@ -692,6 +705,7 @@ function TasksTreeView({
692
705
events = { events }
693
706
rootSpanStatus = { rootSpanStatus }
694
707
rootStartedAt = { rootStartedAt }
708
+ queuedDuration = { queuedTime }
695
709
parentRef = { parentRef }
696
710
timelineScrollRef = { timelineScrollRef }
697
711
nodes = { nodes }
@@ -754,7 +768,7 @@ function TasksTreeView({
754
768
755
769
type TimelineViewProps = Pick <
756
770
TasksTreeViewProps ,
757
- "totalDuration" | "rootSpanStatus" | "events" | "rootStartedAt"
771
+ "totalDuration" | "rootSpanStatus" | "events" | "rootStartedAt" | "queuedDuration"
758
772
> & {
759
773
scale : number ;
760
774
parentRef : React . RefObject < HTMLDivElement > ;
@@ -785,27 +799,32 @@ function TimelineView({
785
799
toggleNodeSelection,
786
800
showDurations,
787
801
treeScrollRef,
802
+ queuedDuration,
788
803
} : TimelineViewProps ) {
789
- const isAdmin = useHasAdminAccess ( ) ;
790
804
const timelineContainerRef = useRef < HTMLDivElement > ( null ) ;
791
805
const initialTimelineDimensions = useInitialDimensions ( timelineContainerRef ) ;
792
806
const minTimelineWidth = initialTimelineDimensions ?. width ?? 300 ;
793
807
const maxTimelineWidth = minTimelineWidth * 10 ;
794
808
795
809
//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 ) ) ;
797
811
useEffect ( ( ) => {
798
812
if ( rootSpanStatus !== "executing" || ! rootStartedAt ) {
799
- setDuration ( totalDuration ) ;
813
+ setDuration ( queueAdjustedNs ( totalDuration , queuedDuration ) ) ;
800
814
return ;
801
815
}
802
816
803
817
const interval = setInterval ( ( ) => {
804
- setDuration ( millisecondsToNanoseconds ( Date . now ( ) - rootStartedAt . getTime ( ) ) ) ;
818
+ setDuration (
819
+ queueAdjustedNs (
820
+ millisecondsToNanoseconds ( Date . now ( ) - rootStartedAt . getTime ( ) ) ,
821
+ queuedDuration
822
+ )
823
+ ) ;
805
824
} , 500 ) ;
806
825
807
826
return ( ) => clearInterval ( interval ) ;
808
- } , [ totalDuration , rootSpanStatus ] ) ;
827
+ } , [ totalDuration , rootSpanStatus , queuedDuration , rootStartedAt ] ) ;
809
828
810
829
return (
811
830
< div
@@ -820,7 +839,11 @@ function TimelineView({
820
839
maxWidth = { maxTimelineWidth }
821
840
>
822
841
{ /* Follows the cursor */ }
823
- < CurrentTimeIndicator totalDuration = { duration } rootStartedAt = { rootStartedAt } />
842
+ < CurrentTimeIndicator
843
+ totalDuration = { duration }
844
+ rootStartedAt = { rootStartedAt }
845
+ queuedDurationNs = { queuedDuration }
846
+ />
824
847
825
848
< Timeline . Row className = "grid h-full grid-rows-[2rem_1fr]" >
826
849
{ /* The duration labels */ }
@@ -920,6 +943,8 @@ function TimelineView({
920
943
getTreeProps = { getTreeProps }
921
944
parentClassName = "h-full scrollbar-hide"
922
945
renderNode = { ( { node, state, index, virtualizer, virtualItem } ) => {
946
+ const isTopSpan = node . id === events [ 0 ] ?. id ;
947
+
923
948
return (
924
949
< Timeline . Row
925
950
key = { index }
@@ -941,7 +966,9 @@ function TimelineView({
941
966
eventIndex === 0 ? (
942
967
< Timeline . Point
943
968
key = { eventIndex }
944
- ms = { nanosecondsToMilliseconds ( event . offset ) }
969
+ ms = { nanosecondsToMilliseconds (
970
+ queueAdjustedNs ( event . offset , queuedDuration )
971
+ ) }
945
972
>
946
973
{ ( ms ) => (
947
974
< motion . div
@@ -956,13 +983,15 @@ function TimelineView({
956
983
) : (
957
984
< Timeline . Point
958
985
key = { eventIndex }
959
- ms = { nanosecondsToMilliseconds ( event . offset ) }
986
+ ms = { nanosecondsToMilliseconds (
987
+ queueAdjustedNs ( event . offset , queuedDuration )
988
+ ) }
960
989
className = "z-10"
961
990
>
962
991
{ ( ms ) => (
963
992
< motion . div
964
993
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" ,
966
995
eventBorderClassName ( node . data )
967
996
) }
968
997
layoutId = { `${ node . id } -${ event . name } ` }
@@ -975,7 +1004,9 @@ function TimelineView({
975
1004
node . data . timelineEvents [ 0 ] &&
976
1005
node . data . timelineEvents [ 0 ] . offset < node . data . offset ? (
977
1006
< Timeline . Span
978
- startMs = { nanosecondsToMilliseconds ( node . data . timelineEvents [ 0 ] . offset ) }
1007
+ startMs = { nanosecondsToMilliseconds (
1008
+ queueAdjustedNs ( node . data . timelineEvents [ 0 ] . offset , queuedDuration )
1009
+ ) }
979
1010
durationMs = { nanosecondsToMilliseconds (
980
1011
node . data . offset - node . data . timelineEvents [ 0 ] . offset
981
1012
) }
@@ -988,21 +1019,35 @@ function TimelineView({
988
1019
) : null }
989
1020
< SpanWithDuration
990
1021
showDuration = { state . selected ? true : showDurations }
991
- startMs = { nanosecondsToMilliseconds ( node . data . offset ) }
1022
+ startMs = { nanosecondsToMilliseconds (
1023
+ Math . max ( queueAdjustedNs ( node . data . offset , queuedDuration ) , 0 )
1024
+ ) }
992
1025
durationMs = {
993
1026
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
+ )
996
1036
}
997
1037
node = { node }
1038
+ fadeLeft = { isTopSpan && queuedDuration !== undefined }
998
1039
/>
999
1040
</ >
1000
1041
) : (
1001
- < Timeline . Point ms = { nanosecondsToMilliseconds ( node . data . offset ) } >
1042
+ < Timeline . Point
1043
+ ms = { nanosecondsToMilliseconds (
1044
+ queueAdjustedNs ( node . data . offset , queuedDuration )
1045
+ ) }
1046
+ >
1002
1047
{ ( ms ) => (
1003
1048
< motion . div
1004
1049
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" ,
1006
1051
eventBackgroundClassName ( node . data )
1007
1052
) }
1008
1053
layoutId = { node . id }
@@ -1027,6 +1072,14 @@ function TimelineView({
1027
1072
) ;
1028
1073
}
1029
1074
1075
+ function queueAdjustedNs ( timeNs : number , queuedDurationNs : number | undefined ) {
1076
+ if ( queuedDurationNs ) {
1077
+ return timeNs - queuedDurationNs ;
1078
+ }
1079
+
1080
+ return timeNs ;
1081
+ }
1082
+
1030
1083
function NodeText ( { node } : { node : TraceEvent } ) {
1031
1084
const className = "truncate" ;
1032
1085
return (
@@ -1180,15 +1233,18 @@ function PulsingDot() {
1180
1233
function SpanWithDuration ( {
1181
1234
showDuration,
1182
1235
node,
1236
+ fadeLeft,
1183
1237
...props
1184
- } : Timeline . SpanProps & { node : TraceEvent ; showDuration : boolean } ) {
1238
+ } : Timeline . SpanProps & { node : TraceEvent ; showDuration : boolean ; fadeLeft : boolean } ) {
1185
1239
return (
1186
1240
< Timeline . Span { ...props } >
1187
1241
< motion . div
1188
1242
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"
1191
1246
) }
1247
+ style = { { backgroundSize : "20px 100%" , backgroundRepeat : "no-repeat" } }
1192
1248
layoutId = { node . id }
1193
1249
>
1194
1250
{ node . data . isPartial && (
@@ -1197,19 +1253,22 @@ function SpanWithDuration({
1197
1253
style = { { backgroundImage : `url(${ tileBgPath } )` , backgroundSize : "8px 8px" } }
1198
1254
/>
1199
1255
) }
1200
- < div
1256
+ < motion . div
1201
1257
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" ,
1203
1259
! showDuration && "opacity-0"
1204
1260
) }
1205
1261
>
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
+ >
1207
1266
{ formatDurationMilliseconds ( props . durationMs , {
1208
1267
style : "short" ,
1209
1268
maxDecimalPoints : props . durationMs < 1000 ? 0 : 1 ,
1210
1269
} ) }
1211
- </ div >
1212
- </ div >
1270
+ </ motion . div >
1271
+ </ motion . div >
1213
1272
</ motion . div >
1214
1273
</ Timeline . Span >
1215
1274
) ;
@@ -1220,9 +1279,11 @@ const edgeBoundary = 0.17;
1220
1279
function CurrentTimeIndicator ( {
1221
1280
totalDuration,
1222
1281
rootStartedAt,
1282
+ queuedDurationNs,
1223
1283
} : {
1224
1284
totalDuration : number ;
1225
1285
rootStartedAt : Date | undefined ;
1286
+ queuedDurationNs : number | undefined ;
1226
1287
} ) {
1227
1288
return (
1228
1289
< Timeline . FollowCursor >
@@ -1235,7 +1296,11 @@ function CurrentTimeIndicator({
1235
1296
offset = lerp ( 0.5 , 1 , ( ratio - ( 1 - edgeBoundary ) ) / edgeBoundary ) ;
1236
1297
}
1237
1298
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 ;
1239
1304
const currentTimeComponent = currentTime ? < DateTimeShort date = { currentTime } /> : < > </ > ;
1240
1305
1241
1306
return (
@@ -1300,6 +1365,7 @@ function KeyboardShortcuts({
1300
1365
title = "Collapse all"
1301
1366
/>
1302
1367
< NumberShortcuts toggleLevel = { ( number ) => toggleExpandLevel ( number ) } />
1368
+ < ShortcutWithAction shortcut = { { key : "Q" } } title = "Queue time" action = { ( ) => { } } />
1303
1369
</ >
1304
1370
) ;
1305
1371
}
0 commit comments