hecate: exclude dry-run history from shutdown budget estimation

This commit is contained in:
Brad Stein 2026-04-05 00:21:05 -03:00
parent f020f77d2b
commit a05973bf2b
3 changed files with 43 additions and 1 deletions

View File

@ -98,6 +98,7 @@ func (o *Orchestrator) Startup(ctx context.Context, opts StartupOptions) (err er
ID: fmt.Sprintf("startup-%d", time.Now().UnixNano()), ID: fmt.Sprintf("startup-%d", time.Now().UnixNano()),
Action: "startup", Action: "startup",
Reason: opts.Reason, Reason: opts.Reason,
DryRun: o.runner.DryRun,
StartedAt: time.Now().UTC(), StartedAt: time.Now().UTC(),
} }
defer o.finalizeRecord(&record, &err) defer o.finalizeRecord(&record, &err)
@ -343,6 +344,7 @@ func (o *Orchestrator) Shutdown(ctx context.Context, opts ShutdownOptions) (err
ID: fmt.Sprintf("shutdown-%d", time.Now().UnixNano()), ID: fmt.Sprintf("shutdown-%d", time.Now().UnixNano()),
Action: "shutdown", Action: "shutdown",
Reason: opts.Reason, Reason: opts.Reason,
DryRun: o.runner.DryRun,
StartedAt: time.Now().UTC(), StartedAt: time.Now().UTC(),
} }
defer o.finalizeRecord(&record, &err) defer o.finalizeRecord(&record, &err)

View File

@ -19,6 +19,7 @@ type RunRecord struct {
ID string `json:"id"` ID string `json:"id"`
Action string `json:"action"` Action string `json:"action"`
Reason string `json:"reason,omitempty"` Reason string `json:"reason,omitempty"`
DryRun bool `json:"dry_run,omitempty"`
StartedAt time.Time `json:"started_at"` StartedAt time.Time `json:"started_at"`
EndedAt time.Time `json:"ended_at"` EndedAt time.Time `json:"ended_at"`
DurationSeconds int `json:"duration_seconds"` DurationSeconds int `json:"duration_seconds"`
@ -207,7 +208,7 @@ func (s *Store) shutdownP95(defaultSeconds int, minSamples int, reasonPrefixes [
return false return false
} }
for _, r := range records { for _, r := range records {
if r.Action == "shutdown" && r.Success && r.DurationSeconds > 0 && matchesPrefix(r.Reason) { if r.Action == "shutdown" && !r.DryRun && r.Success && r.DurationSeconds > 0 && matchesPrefix(r.Reason) {
d = append(d, r.DurationSeconds) d = append(d, r.DurationSeconds)
} }
} }

View File

@ -160,3 +160,42 @@ func TestShutdownP95ByReasonPrefixFiltersSamples(t *testing.T) {
t.Fatalf("expected filtered p95 of 220, got %d", got) t.Fatalf("expected filtered p95 of 220, got %d", got)
} }
} }
func TestShutdownP95IgnoresDryRunSamples(t *testing.T) {
p := filepath.Join(t.TempDir(), "runs.json")
now := time.Now().UTC()
records := []RunRecord{
{
ID: "shutdown-dryrun",
Action: "shutdown",
Reason: "ups-threshold target=Pyrphoros",
DryRun: true,
StartedAt: now,
EndedAt: now,
DurationSeconds: 5,
Success: true,
},
{
ID: "shutdown-real",
Action: "shutdown",
Reason: "ups-threshold target=Pyrphoros",
DryRun: false,
StartedAt: now,
EndedAt: now,
DurationSeconds: 210,
Success: true,
},
}
b, err := json.Marshal(records)
if err != nil {
t.Fatalf("marshal records: %v", err)
}
if err := os.WriteFile(p, b, 0o640); err != nil {
t.Fatalf("write records: %v", err)
}
got := New(p).ShutdownP95ByReasonPrefix(420, 1, []string{"ups-"})
if got != 210 {
t.Fatalf("expected dry-run sample to be ignored and real value 210 used, got %d", got)
}
}