diff --git a/apps/webapp/app/presenters/v3/RunPresenter.server.ts b/apps/webapp/app/presenters/v3/RunPresenter.server.ts index f2a8dece51..b235dacded 100644 --- a/apps/webapp/app/presenters/v3/RunPresenter.server.ts +++ b/apps/webapp/app/presenters/v3/RunPresenter.server.ts @@ -50,6 +50,7 @@ export class RunPresenter { spanId: true, friendlyId: true, status: true, + startedAt: true, completedAt: true, logsDeletedAt: true, rootTaskRun: { @@ -104,6 +105,7 @@ export class RunPresenter { spanId: run.spanId, status: run.status, isFinished: isFinalRunStatus(run.status), + startedAt: run.startedAt, completedAt: run.completedAt, logsDeletedAt: showDeletedLogs ? null : run.logsDeletedAt, rootTaskRun: run.rootTaskRun, @@ -201,6 +203,10 @@ export class RunPresenter { tree?.id === traceSummary.rootSpan.id ? undefined : traceSummary.rootSpan.runId, duration: totalDuration, rootStartedAt: tree?.data.startTime, + startedAt: run.startedAt, + queuedDuration: run.startedAt + ? millisecondsToNanoseconds(run.startedAt.getTime() - run.createdAt.getTime()) + : undefined, }, }; } diff --git a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx index 24ee6e6123..84331e7ae7 100644 --- a/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx +++ b/apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam.env.$envParam.runs.$runParam/route.tsx @@ -295,7 +295,8 @@ function TraceView({ run, trace, maximumLiveReloadingSetting, resizable }: Loade return <>; } - const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt } = trace; + const { events, parentRunFriendlyId, duration, rootSpanStatus, rootStartedAt, queuedDuration } = + trace; const shouldLiveReload = events.length <= maximumLiveReloadingSetting; const changeToSpan = useDebounce((selectedSpan: string) => { @@ -345,6 +346,7 @@ function TraceView({ run, trace, maximumLiveReloadingSetting, resizable }: Loade totalDuration={duration} rootSpanStatus={rootSpanStatus} rootStartedAt={rootStartedAt ? new Date(rootStartedAt) : undefined} + queuedDuration={queuedDuration} environmentType={run.environment.type} shouldLiveReload={shouldLiveReload} maximumLiveReloadingSetting={maximumLiveReloadingSetting} @@ -472,6 +474,7 @@ type TasksTreeViewProps = { totalDuration: number; rootSpanStatus: "executing" | "completed" | "failed"; rootStartedAt: Date | undefined; + queuedDuration: number | undefined; environmentType: RuntimeEnvironmentType; shouldLiveReload: boolean; maximumLiveReloadingSetting: number; @@ -491,6 +494,7 @@ function TasksTreeView({ totalDuration, rootSpanStatus, rootStartedAt, + queuedDuration, environmentType, shouldLiveReload, maximumLiveReloadingSetting, @@ -502,12 +506,14 @@ function TasksTreeView({ const [errorsOnly, setErrorsOnly] = useState(false); const [showDebug, setShowDebug] = useState(false); const [showDurations, setShowDurations] = useState(true); + const [showQueueTime, setShowQueueTime] = useState(false); const [scale, setScale] = useState(0); const parentRef = useRef(null); const treeScrollRef = useRef(null); const timelineScrollRef = useRef(null); const displayEvents = showDebug ? events : events.filter((event) => !event.data.isDebug); + const queuedTime = showQueueTime ? undefined : queuedDuration; const { nodes, @@ -556,6 +562,13 @@ function TasksTreeView({ onCheckedChange={(e) => setShowDebug(e.valueOf())} /> )} + setShowQueueTime(e.valueOf())} + shortcut={{ key: "Q" }} + /> & { scale: number; parentRef: React.RefObject; @@ -785,27 +799,32 @@ function TimelineView({ toggleNodeSelection, showDurations, treeScrollRef, + queuedDuration, }: TimelineViewProps) { - const isAdmin = useHasAdminAccess(); const timelineContainerRef = useRef(null); const initialTimelineDimensions = useInitialDimensions(timelineContainerRef); const minTimelineWidth = initialTimelineDimensions?.width ?? 300; const maxTimelineWidth = minTimelineWidth * 10; //we want to live-update the duration if the root span is still executing - const [duration, setDuration] = useState(totalDuration); + const [duration, setDuration] = useState(queueAdjustedNs(totalDuration, queuedDuration)); useEffect(() => { if (rootSpanStatus !== "executing" || !rootStartedAt) { - setDuration(totalDuration); + setDuration(queueAdjustedNs(totalDuration, queuedDuration)); return; } const interval = setInterval(() => { - setDuration(millisecondsToNanoseconds(Date.now() - rootStartedAt.getTime())); + setDuration( + queueAdjustedNs( + millisecondsToNanoseconds(Date.now() - rootStartedAt.getTime()), + queuedDuration + ) + ); }, 500); return () => clearInterval(interval); - }, [totalDuration, rootSpanStatus]); + }, [totalDuration, rootSpanStatus, queuedDuration, rootStartedAt]); return (
{/* Follows the cursor */} - + {/* The duration labels */} @@ -920,6 +943,8 @@ function TimelineView({ getTreeProps={getTreeProps} parentClassName="h-full scrollbar-hide" renderNode={({ node, state, index, virtualizer, virtualItem }) => { + const isTopSpan = node.id === events[0]?.id; + return ( {(ms) => ( {(ms) => ( ) : ( - + {(ms) => ( {node.data.isPartial && ( @@ -1197,19 +1253,22 @@ function SpanWithDuration({ style={{ backgroundImage: `url(${tileBgPath})`, backgroundSize: "8px 8px" }} /> )} -
-
+ {formatDurationMilliseconds(props.durationMs, { style: "short", maxDecimalPoints: props.durationMs < 1000 ? 0 : 1, })} -
-
+
+
); @@ -1220,9 +1279,11 @@ const edgeBoundary = 0.17; function CurrentTimeIndicator({ totalDuration, rootStartedAt, + queuedDurationNs, }: { totalDuration: number; rootStartedAt: Date | undefined; + queuedDurationNs: number | undefined; }) { return ( @@ -1235,7 +1296,11 @@ function CurrentTimeIndicator({ offset = lerp(0.5, 1, (ratio - (1 - edgeBoundary)) / edgeBoundary); } - const currentTime = rootStartedAt ? new Date(rootStartedAt.getTime() + ms) : undefined; + const currentTime = rootStartedAt + ? new Date( + rootStartedAt.getTime() + ms + nanosecondsToMilliseconds(queuedDurationNs ?? 0) + ) + : undefined; const currentTimeComponent = currentTime ? : <>; return ( @@ -1300,6 +1365,7 @@ function KeyboardShortcuts({ title="Collapse all" /> toggleExpandLevel(number)} /> + {}} /> ); } diff --git a/apps/webapp/app/v3/runEngineHandlers.server.ts b/apps/webapp/app/v3/runEngineHandlers.server.ts index 6f236cf3ed..1a71157289 100644 --- a/apps/webapp/app/v3/runEngineHandlers.server.ts +++ b/apps/webapp/app/v3/runEngineHandlers.server.ts @@ -333,6 +333,7 @@ export function registerRunEngineEventBusHandlers() { } await eventRepository.recordEvent(retryMessage, { + startTime: BigInt(time.getTime() * 1000000), taskSlug: run.taskIdentifier, environment, attributes: { @@ -347,7 +348,6 @@ export function registerRunEngineEventBusHandlers() { queueName: run.queue, }, context: run.traceContext as Record, - spanIdSeed: `retry-${run.attemptNumber + 1}`, endTime: retryAt, }); } catch (error) {