-
Notifications
You must be signed in to change notification settings - Fork 12
Track per-phase duration on each instance #223
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
79df5a4
Track per-phase duration for each instance
sjmiller609 86dcffe
gofmt phasetracking
sjmiller609 76be7f1
Assert phase tracking in existing standby/fork integration tests
sjmiller609 7a80521
Use UTC for now in standby/stop transition timestamps
sjmiller609 a6f4d10
Deep-copy phase tracker on metadata clone and use UTC in fork
sjmiller609 c535c06
Mirror public State in phase tracking via lazy boot-marker hydration
sjmiller609 6490aa7
Merge remote-tracking branch 'origin/main' into hypeship/instance-pha…
sjmiller609 a29fd64
phasetracking: drop unused PhasePaused/PhaseShutdown constants
sjmiller609 3a95db6
phasetracking: clamp marker-derived transition time to Phases.Since
sjmiller609 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,114 @@ | ||
| // Package phasetracking accumulates cumulative time-in-phase for instance | ||
| // lifecycle phases. The tracker is embedded in instance metadata and updated | ||
| // at every externally-observable state transition so consumers can use the | ||
| // resulting durations for billing, observability, and analytics. | ||
| // | ||
| // Phases mirror the externally-observable values of instances.State (lowercased | ||
| // so they remain stable in the API surface even if the internal enum is | ||
| // renamed). The Initializing→Running transition is detected lazily when guest | ||
| // boot markers are persisted, so the tracker reflects the same view of guest | ||
| // readiness that the public State machine reports — not the bare moment the | ||
| // VMM process came up. | ||
| // | ||
| // Transient internal substates that no external observer can see — for example | ||
| // the Paused/Shutdown steps inside a single Standby or Stop orchestration — are | ||
| // intentionally not recorded; they would be sub-millisecond blips inside a | ||
| // non-yielding function call that adds noise without truth. | ||
| // | ||
| // Only the transition orchestration sites in lib/instances should call Record. | ||
| // The tracker intentionally does not subscribe to the lifecycle event bus — | ||
| // that bus is best-effort and lossy, which is unsuitable for billing. | ||
| package phasetracking | ||
|
|
||
| import "time" | ||
|
|
||
| // Phase is the canonical lifecycle phase name. Values mirror instances.State | ||
|
sjmiller609 marked this conversation as resolved.
|
||
| // lowercased so they remain stable in the API surface even if the internal | ||
| // State enum is renamed. | ||
| type Phase string | ||
|
|
||
| const ( | ||
| PhaseStopped Phase = "stopped" | ||
| PhaseCreated Phase = "created" | ||
| PhaseInitializing Phase = "initializing" | ||
| PhaseRunning Phase = "running" | ||
| PhaseStandby Phase = "standby" | ||
| ) | ||
|
|
||
| // Tracker accumulates cumulative wall-clock time spent in each phase. | ||
| // | ||
| // Invariants: | ||
| // - Cumulative[phase] is the total ms spent in `phase` across all prior | ||
| // completed visits to that phase. | ||
| // - Time spent in the *current* phase (since `Since`) is NOT yet rolled into | ||
| // Cumulative — callers that want "live" totals should use Snapshot. | ||
| // - Current and Since must be updated atomically with Cumulative; that's | ||
| // the contract of Record. Direct mutation is not supported. | ||
| // | ||
| // The zero value is valid: it represents an instance that has not entered | ||
| // any phase yet. The first Record call sets Current and Since without | ||
| // accruing time (there is no prior phase to accrue from). | ||
| type Tracker struct { | ||
| Current Phase `json:"current,omitempty"` | ||
| Since time.Time `json:"since,omitempty"` | ||
| Cumulative map[Phase]int64 `json:"cumulative,omitempty"` | ||
| } | ||
|
|
||
| // Record transitions into newPhase as of `now`, first accruing time-in-current | ||
| // into Cumulative. Safe to call on a zero-value Tracker (first transition has | ||
| // no prior phase, so no accrual happens). | ||
| // | ||
| // `now` is a parameter rather than time.Now() so tests can pin time and so | ||
| // callers can use the same `now` value they're persisting elsewhere on the | ||
| // metadata (e.g. StartedAt) without drift. | ||
| func (t *Tracker) Record(newPhase Phase, now time.Time) { | ||
| if t.Cumulative == nil { | ||
| t.Cumulative = make(map[Phase]int64) | ||
| } | ||
| if t.Current != "" && !t.Since.IsZero() { | ||
| elapsed := now.Sub(t.Since).Milliseconds() | ||
| if elapsed > 0 { | ||
| t.Cumulative[t.Current] += elapsed | ||
| } | ||
| } | ||
| t.Current = newPhase | ||
| t.Since = now | ||
| } | ||
|
|
||
| // Snapshot returns a copy of Cumulative with the in-flight time-in-current | ||
| // rolled in, without mutating the tracker. Use this when reporting "running | ||
| // time so far" — typically in the API response path. | ||
| func (t Tracker) Snapshot(now time.Time) map[Phase]int64 { | ||
| out := make(map[Phase]int64, len(t.Cumulative)+1) | ||
| for k, v := range t.Cumulative { | ||
| out[k] = v | ||
| } | ||
| if t.Current != "" && !t.Since.IsZero() { | ||
| elapsed := now.Sub(t.Since).Milliseconds() | ||
| if elapsed > 0 { | ||
| out[t.Current] += elapsed | ||
| } | ||
| } | ||
| return out | ||
| } | ||
|
|
||
| // Reset clears all accumulated state. Used when forking — the fork is a new | ||
| // instance and must not inherit the source's phase history. | ||
| func (t *Tracker) Reset() { | ||
| t.Current = "" | ||
| t.Since = time.Time{} | ||
| t.Cumulative = nil | ||
| } | ||
|
|
||
| // Clone returns a deep copy of the tracker. The returned tracker shares no | ||
| // state with the receiver, so independent Record/Reset calls do not interfere. | ||
| func (t Tracker) Clone() Tracker { | ||
| dst := t | ||
| if t.Cumulative != nil { | ||
| dst.Cumulative = make(map[Phase]int64, len(t.Cumulative)) | ||
| for k, v := range t.Cumulative { | ||
| dst.Cumulative[k] = v | ||
| } | ||
| } | ||
| return dst | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.