Skip to content

Commit 91919db

Browse files
authored
add run duration (#181)
Signed-off-by: kerthcet <kerthcet@gmail.com>
1 parent 3ae29dc commit 91919db

4 files changed

Lines changed: 61 additions & 6 deletions

File tree

alphatrion/run/run.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import asyncio
22
import uuid
3+
from datetime import UTC, datetime
34

45
from alphatrion.runtime.contextvars import current_run_id
56
from alphatrion.runtime.runtime import global_runtime
@@ -50,19 +51,28 @@ def done(self):
5051
if self.cancelled():
5152
return
5253

54+
run = self._runtime._metadb.get_run(run_id=self.id)
55+
duration = (
56+
datetime.now(UTC) - run.created_at.replace(tzinfo=UTC)
57+
).total_seconds()
58+
5359
self._runtime.metadb.update_run(
54-
run_id=self._id,
55-
status=Status.COMPLETED,
60+
run_id=self._id, status=Status.COMPLETED, duration=duration
5661
)
5762
self._result = self._task.result()
5863

5964
def cancel(self):
6065
# TODO: we should wait for the task to be actually cancelled
6166
# and catch the CancelledError exception in the task function.
6267
self._task.cancel()
68+
69+
run = self._runtime._metadb.get_run(run_id=self.id)
70+
duration = (
71+
datetime.now(UTC) - run.created_at.replace(tzinfo=UTC)
72+
).total_seconds()
73+
6374
self._runtime.metadb.update_run(
64-
run_id=self._id,
65-
status=Status.CANCELLED,
75+
run_id=self._id, status=Status.CANCELLED, duration=duration
6676
)
6777

6878
def cancelled(self) -> bool:

alphatrion/storage/sql_models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,7 @@ class Run(Base):
177177
nullable=True,
178178
comment="Additional metadata for the run",
179179
)
180+
duration = Column(Float, default=0.0, comment="Duration of the run in seconds")
180181
status = Column(
181182
Integer,
182183
default=Status.PENDING,
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""add duration for run
2+
3+
Revision ID: 0f417c7cf4d3
4+
Revises: 766c8b7fe6c5
5+
Create Date: 2026-03-02 20:20:56.486059
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = '0f417c7cf4d3'
16+
down_revision: Union[str, Sequence[str], None] = '766c8b7fe6c5'
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column('runs', sa.Column('duration', sa.Float(), nullable=True, comment='Duration of the run in seconds'))
25+
# ### end Alembic commands ###
26+
27+
28+
def downgrade() -> None:
29+
"""Downgrade schema."""
30+
# ### commands auto generated by Alembic - please adjust! ###
31+
op.drop_column('runs', 'duration')
32+
# ### end Alembic commands ###

tests/unit/experiment/test_experimant.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,16 +220,24 @@ async def fake_work(exp_id: uuid.UUID):
220220
async with CraftExperiment.start(name="first-experiment") as exp:
221221
start_time = datetime.now()
222222

223-
exp.run(lambda: fake_work(exp.id))
223+
run1 = exp.run(lambda: fake_work(exp.id))
224224
assert len(exp._runs) == 1
225225

226-
exp.run(lambda: fake_work(exp.id))
226+
run2 = exp.run(lambda: fake_work(exp.id))
227227
assert len(exp._runs) == 2
228228

229229
await exp.wait()
230230
assert datetime.now() - start_time >= timedelta(seconds=3)
231231
assert len(exp._runs) == 0
232232

233+
run1_obj = run1._get_obj()
234+
assert run1_obj.status == Status.COMPLETED
235+
assert run1_obj.duration >= 3.0
236+
237+
run2_obj = run2._get_obj()
238+
assert run2_obj.status == Status.COMPLETED
239+
assert run2_obj.duration >= 3.0
240+
233241

234242
@pytest.mark.asyncio
235243
async def test_create_experiment_with_run_cancelled():
@@ -256,12 +264,16 @@ async def fake_work(timeout: int):
256264

257265
run_0_obj = run_0._get_obj()
258266
assert run_0_obj.status == Status.COMPLETED
267+
assert run_0_obj.duration >= 1.0
259268
run_1_obj = run_1._get_obj()
260269
assert run_1_obj.status == Status.CANCELLED
270+
assert run_1_obj.duration >= 2.0
261271
run_2_obj = run_2._get_obj()
262272
assert run_2_obj.status == Status.CANCELLED
273+
assert run_2_obj.duration >= 2.0
263274
run_3_obj = run_3._get_obj()
264275
assert run_3_obj.status == Status.CANCELLED
276+
assert run_3_obj.duration >= 2.0
265277

266278

267279
@pytest.mark.asyncio

0 commit comments

Comments
 (0)