From 7dafa82b2108ea481b2ae2b462ea840bb49b071b Mon Sep 17 00:00:00 2001 From: mixeme Date: Wed, 24 Jun 2026 08:13:02 +0300 Subject: [PATCH] domain/runtime, app/run: add execution-time statistics aggregate (T2.4) Add RunCount, FailCount, LastDurationMS, AvgDurationMS, MaxDurationMS to JobRuntime and fold each completed RunRecord into them via updateStats called from executeRun. Co-Authored-By: Claude Sonnet 4.6 --- docs/RELEASE-0.10-TASKS.md | 8 ++++---- src/app/run.go | 15 +++++++++++++++ src/domain/runtime.go | 8 ++++++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/docs/RELEASE-0.10-TASKS.md b/docs/RELEASE-0.10-TASKS.md index 778a99b..e66f5a2 100644 --- a/docs/RELEASE-0.10-TASKS.md +++ b/docs/RELEASE-0.10-TASKS.md @@ -88,10 +88,10 @@ Done first because both share a compact, single-line record formatter. - [x] T1.4 — formatter tests ### Phase 2 — Execution-time statistics -- [ ] T2.1 — `DurationMS` on `RunRecord` -- [ ] T2.2 — measure duration in runner -- [ ] T2.3 — `duration` log header -- [ ] T2.4 — runtime aggregate + `executeRun` update +- [x] T2.1 — `DurationMS` on `RunRecord` +- [x] T2.2 — measure duration in runner +- [x] T2.3 — `duration` log header +- [x] T2.4 — runtime aggregate + `executeRun` update - [ ] T2.5 — seed stats from log files - [ ] T2.6 — `DisplayStats` + Statistics row - [ ] T2.7 — stats tests diff --git a/src/app/run.go b/src/app/run.go index 54514dc..2a9a91e 100644 --- a/src/app/run.go +++ b/src/app/run.go @@ -140,6 +140,7 @@ func (s *Service) executeRun(ctx context.Context, jobCopy domain.Job, trigger st runtime.LastState = record.State runtime.Output = record.Output prependLog(runtime, record) + updateStats(runtime, record) rerun := runtime.Pending && current.Enabled && !s.paused runtime.Pending = false if rerun { @@ -190,6 +191,20 @@ func (s *Service) advanceNextDueLocked(job *domain.Job, runtime *domain.JobRunti runtime.NextDue = sched.Next(from) } +// updateStats folds one completed RunRecord into the runtime's aggregate +// execution-time statistics. Called under mu inside executeRun. +func updateStats(rt *domain.JobRuntime, r domain.RunRecord) { + rt.RunCount++ + if r.State == "Failed" { + rt.FailCount++ + } + rt.LastDurationMS = r.DurationMS + if r.DurationMS > rt.MaxDurationMS { + rt.MaxDurationMS = r.DurationMS + } + rt.AvgDurationMS = (rt.AvgDurationMS*int64(rt.RunCount-1) + r.DurationMS) / int64(rt.RunCount) +} + // runningOutput is the placeholder output shown while a job is running, before // the real command output replaces it. func runningOutput(job domain.Job, trigger string, started time.Time) string { diff --git a/src/domain/runtime.go b/src/domain/runtime.go index e2ffd3b..df5508b 100644 --- a/src/domain/runtime.go +++ b/src/domain/runtime.go @@ -22,6 +22,14 @@ type JobRuntime struct { // Pending is set when a run was skipped due to the overlap policy being // "queue". The scheduler will start this job as soon as the current run ends. Pending bool + + // Execution-time statistics accumulated since the last process start. + // Seeded from log files on startup by T2.5; zero until then. + RunCount int + FailCount int + LastDurationMS int64 + AvgDurationMS int64 + MaxDurationMS int64 } // NewRuntime builds the initial runtime state for a freshly loaded or created