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 <noreply@anthropic.com>
This commit is contained in:
mixeme
2026-06-24 08:13:02 +03:00
parent 2b0cdb57f4
commit 7dafa82b21
3 changed files with 27 additions and 4 deletions
+4 -4
View File
@@ -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
+15
View File
@@ -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 {
+8
View File
@@ -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