Skip to content

Commit d89c8f7

Browse files
author
Spike Test
committed
Add wallclock/duration delta to per-PR journey-delta comment
Extend JourneyDelta with DurationMSDelta, computed in ComputeJourneyDeltas from the existing journeymetrics.Record.DurationMS field, and render it as a new Duration Δ (ms) column in RenderJourneyDeltaComment.
1 parent 6b8109d commit d89c8f7

2 files changed

Lines changed: 66 additions & 13 deletions

File tree

internal/release/journeydelta.go

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,11 +62,12 @@ type JourneyDelta struct {
6262
ScenarioID string
6363
Runtime string
6464
Model string
65-
HasBaseline bool
66-
BaselineRunURL string
67-
TurnsDelta int
68-
TokensDelta journeymetrics.TokenTotals
69-
CostDeltaUSD float64
65+
HasBaseline bool
66+
BaselineRunURL string
67+
TurnsDelta int
68+
DurationMSDelta int64
69+
TokensDelta journeymetrics.TokenTotals
70+
CostDeltaUSD float64
7071
}
7172

7273
// ComputeJourneyDeltas computes, for every scenario/runtime/model the current
@@ -94,6 +95,7 @@ func ComputeJourneyDeltas(baseline journeymetrics.Ledger, current []journeymetri
9495
d.BaselineRunURL = base.RunURL
9596
}
9697
d.TurnsDelta = cur.Turns - base.Turns
98+
d.DurationMSDelta = cur.DurationMS - base.DurationMS
9799
d.TokensDelta = journeymetrics.TokenTotals{
98100
Input: cur.Tokens.Input - base.Tokens.Input,
99101
Output: cur.Tokens.Output - base.Tokens.Output,
@@ -138,20 +140,20 @@ func RenderJourneyDeltaComment(deltas []JourneyDelta) string {
138140
b.WriteString("No journey metrics observations were produced by this run.\n")
139141
return b.String()
140142
}
141-
b.WriteString("| Scenario | Runtime | Model | Turns Δ | Cache Read Δ | Cache Creation Δ | Tokens Δ (total) | Cost Δ (USD) | Baseline |\n")
142-
b.WriteString("| --- | --- | --- | --- | --- | --- | --- | --- | --- |\n")
143+
b.WriteString("| Scenario | Runtime | Model | Turns Δ | Cache Read Δ | Cache Creation Δ | Tokens Δ (total) | Cost Δ (USD) | Duration Δ (ms) | Baseline |\n")
144+
b.WriteString("| --- | --- | --- | --- | --- | --- | --- | --- | --- | --- |\n")
143145
for _, d := range deltas {
144146
if !d.HasBaseline {
145-
fmt.Fprintf(&b, "| %s | %s | %s | n/a (new) | n/a (new) | n/a (new) | n/a (new) | n/a (new) | none (new observation) |\n",
147+
fmt.Fprintf(&b, "| %s | %s | %s | n/a (new) | n/a (new) | n/a (new) | n/a (new) | n/a (new) | n/a (new) | none (new observation) |\n",
146148
d.ScenarioID, d.Runtime, d.Model)
147149
continue
148150
}
149151
baseline := "latest published"
150152
if d.BaselineRunURL != "" {
151153
baseline = fmt.Sprintf("[latest published](%s)", d.BaselineRunURL)
152154
}
153-
fmt.Fprintf(&b, "| %s | %s | %s | %+d | %+d | %+d | %+d | %+.4f | %s |\n",
154-
d.ScenarioID, d.Runtime, d.Model, d.TurnsDelta, d.TokensDelta.CacheRead, d.TokensDelta.CacheCreation, d.TokensDelta.Total, d.CostDeltaUSD, baseline)
155+
fmt.Fprintf(&b, "| %s | %s | %s | %+d | %+d | %+d | %+d | %+.4f | %+d | %s |\n",
156+
d.ScenarioID, d.Runtime, d.Model, d.TurnsDelta, d.TokensDelta.CacheRead, d.TokensDelta.CacheCreation, d.TokensDelta.Total, d.CostDeltaUSD, d.DurationMSDelta, baseline)
155157
}
156158
return b.String()
157159
}

internal/release/journeydelta_test.go

Lines changed: 54 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ func TestRenderJourneyDeltaCommentShowsTokenClassBreakdown(t *testing.T) {
200200
t.Fatalf("comment body missing the data row:\n%s", body)
201201
}
202202
cells := strings.Split(row, "|")
203-
// | (empty) | Scenario | Runtime | Model | Turns Δ | Cache Read Δ | Cache Creation Δ | Tokens Δ (total) | Cost Δ (USD) | Baseline | (empty) |
204-
if len(cells) != 11 {
205-
t.Fatalf("data row has %d cells, want 11 (header shape changed?): %q", len(cells), row)
203+
// | (empty) | Scenario | Runtime | Model | Turns Δ | Cache Read Δ | Cache Creation Δ | Tokens Δ (total) | Cost Δ (USD) | Duration Δ (ms) | Baseline | (empty) |
204+
if len(cells) != 12 {
205+
t.Fatalf("data row has %d cells, want 12 (header shape changed?): %q", len(cells), row)
206206
}
207207
cacheReadCell := strings.TrimSpace(cells[5])
208208
cacheCreationCell := strings.TrimSpace(cells[6])
@@ -218,6 +218,57 @@ func TestRenderJourneyDeltaCommentShowsTokenClassBreakdown(t *testing.T) {
218218
}
219219
}
220220

221+
// TestJourneyDeltaCommentShowsDurationDelta is the TDD proof for the
222+
// wallclock/duration delta: computes deltas from a baseline ledger and a
223+
// current PR run with distinct DurationMS values, then asserts both the
224+
// computed struct field and the rendered comment's Duration Δ column show the
225+
// exact millisecond delta (current - baseline), not a total or a placeholder.
226+
func TestJourneyDeltaCommentShowsDurationDelta(t *testing.T) {
227+
baseline := journeymetrics.Ledger{
228+
Scenarios: []journeymetrics.ScenarioLedgerEntry{
229+
{
230+
ScenarioID: "shallow-boot-window",
231+
Observations: []journeymetrics.Record{
232+
{
233+
ScenarioID: "shallow-boot-window", Runtime: "claude", Model: "claude-sonnet-4-6",
234+
DurationMS: 12000, CapturedAt: "2026-06-20T00:00:00Z",
235+
RunURL: "https://github.com/spacedock-dev/spacedock/actions/runs/27931963802",
236+
},
237+
},
238+
},
239+
},
240+
}
241+
current := []journeymetrics.Record{
242+
{
243+
ScenarioID: "shallow-boot-window", Runtime: "claude", Model: "claude-sonnet-4-6",
244+
DurationMS: 15500,
245+
},
246+
}
247+
248+
deltas := ComputeJourneyDeltas(baseline, current)
249+
if len(deltas) != 1 {
250+
t.Fatalf("deltas = %d, want 1", len(deltas))
251+
}
252+
if deltas[0].DurationMSDelta != 3500 {
253+
t.Fatalf("DurationMSDelta = %d, want 3500 (15500-12000)", deltas[0].DurationMSDelta)
254+
}
255+
256+
body := RenderJourneyDeltaComment(deltas)
257+
var row string
258+
for _, line := range strings.Split(body, "\n") {
259+
if strings.HasPrefix(line, "| shallow-boot-window") {
260+
row = line
261+
break
262+
}
263+
}
264+
if row == "" {
265+
t.Fatalf("comment body missing the data row:\n%s", body)
266+
}
267+
if !strings.Contains(row, "+3500") {
268+
t.Fatalf("data row missing Duration Δ +3500(ms): %q", row)
269+
}
270+
}
271+
221272
// TestRenderJourneyDeltaCommentRendersNoBaselineAsNewNotSelfDelta proves a
222273
// scenario/model with no matching baseline observation renders "n/a (new)" in
223274
// its delta cells rather than a self-delta against an implicit zero baseline

0 commit comments

Comments
 (0)