Report how long a run has been going, not just how long it took

This commit is contained in:
2026-08-25 18:33:28 +02:00
parent c3ff7df192
commit 4cdcf19de1
4 changed files with 67 additions and 3 deletions
+10 -1
View File
@@ -12,12 +12,13 @@ from typing import Any, Literal
from fastapi import APIRouter, Depends, Request
from fastapi.concurrency import run_in_threadpool
from pydantic import BaseModel
from pydantic import BaseModel, model_validator
from sqlalchemy import ColumnElement, Integer, cast, func
from sqlalchemy import select as sa_select
from sqlmodel import col, select
from fluksio.api.deps import FlowControllerDep, SessionDep, get_current_user
from fluksio.api.routes.runs import elapsed_ms
from fluksio.core.config import settings
from fluksio.flow.controller import ADVISORY_ISSUES, NodeStatus
from fluksio.models import EngineEvent, FlowRun, MetricBucket
@@ -81,9 +82,17 @@ class RunRow(BaseModel):
finished_at: datetime | None = None
nodes: int
errors: int
#: How long it took, or — while it is still going — how long it has been
#: going: the collector only writes a duration once a cascade finishes.
duration_ms: float
deliveries: int
@model_validator(mode="after")
def _running_duration(self) -> "RunRow":
if self.status == "running" and not self.duration_ms:
self.duration_ms = elapsed_ms(self.started_at)
return self
class RunPage(BaseModel):
data: list[RunRow]
+16 -1
View File
@@ -5,11 +5,12 @@ in hours, so nothing here waits for one. The way to follow a run is to poll it
or to listen on the flow socket, which carries its start and finish.
"""
from datetime import UTC, datetime
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.concurrency import run_in_threadpool
from pydantic import BaseModel, Field
from pydantic import BaseModel, Field, model_validator
from sqlalchemy import func
from sqlalchemy import select as sa_select
from sqlmodel import Session, col, select
@@ -28,6 +29,12 @@ router = APIRouter(
MAX_SWEEP = 1000
def elapsed_ms(since: datetime) -> float:
"""Milliseconds since an instant the columns stored as UTC."""
start = since if since.tzinfo else since.replace(tzinfo=UTC)
return round((datetime.now(UTC) - start).total_seconds() * 1000, 2)
class RunCreate(BaseModel):
params: dict[str, Any] = Field(default_factory=dict)
seed: int | None = None
@@ -95,9 +102,17 @@ class RunRow(BaseModel):
created_at: Any
started_at: Any = None
finished_at: Any = None
#: How long it took, or — while it is still going — how long it has been
#: going: a duration of its own is only written once a run finishes.
duration_ms: float
actor: str
@model_validator(mode="after")
def _running_duration(self) -> "RunRow":
if self.status == "running" and not self.duration_ms and self.started_at:
self.duration_ms = elapsed_ms(self.started_at)
return self
class RunDetail(RunRow):
result: dict[str, Any] = Field(default_factory=dict)