Documentation
¶
Overview ¶
Package analysis computes evidence-backed findings from explicit snapshots. It does not fetch evidence or decide whether a caller may read it.
Index ¶
- Constants
- Variables
- func CatalogCodes() []string
- func ValidRootDependencies(e *RootDependencies, digest string) bool
- func Words(d time.Duration) string
- type BadEvent
- type CauseShare
- type Change
- type CheckDelay
- type CheckObservation
- type CheckTiming
- type CompareEvidence
- type CompareFinding
- type CompareSide
- type CompareSnapshot
- type Confidence
- type Coverage
- type ExperimentArm
- type ExperimentAttempt
- type ExperimentCounts
- type ExperimentDistribution
- type ExperimentEligibility
- type ExperimentMeasurement
- type ExperimentMeasures
- type ExperimentSnapshot
- type Finding
- type HostIncident
- type HostInterval
- type HostJob
- type HostMinute
- type HostPressureEvidence
- type HostPressureFinding
- type HostPressureSnapshot
- type Interval
- type JobFigures
- type LongWait
- type MergeDelayResult
- type MergeDelaySnapshot
- type NextStep
- type PoolConditions
- type PoolWait
- type PressureThreshold
- type PressureThresholds
- type QueueWaitEvidence
- type QueueWaitFinding
- type QueueWaitSnapshot
- type RequiredCheck
- type RootDependencies
- type RootDependencyCounts
- type RootDependencyReference
- type Spread
- type StartAttempt
- type StartupEvidence
- type StartupFailuresFinding
- type StartupGroup
- type StartupSnapshot
- type WaitCause
- type WaitJob
Constants ¶
const ( MinCompareJobs = 30 ConfidentCompareJobs = 100 )
MinCompareJobs is the fewest jobs a side needs before the comparison ranks anything: under it, a release that ran three slow jobs would be blamed for them. ConfidentCompareJobs is what a side needs for a change to be called with high confidence.
const ( Regressed = "regressed" Improved = "improved" Unchanged = "unchanged" NotEnough = "not_enough" )
A change's direction. not_enough is a measure the sample cannot judge, which is not the same as one that held still.
const ( HostPressured = "pressure" HostNotPressured = "not_pressure" )
What a host pressure finding leads with.
const ( EventFailure = "failure" EventOOM = "oom" EventSlowStartup = "slow_startup" )
The kinds of thing that went wrong on a host.
const ( MeasureLoadPerCPU = "load_per_cpu" MeasureCPUPercent = "cpu_percent" MeasureMemoryUsed = "memory_used" )
The measures a threshold can be drawn on.
const ( MaxEvidencePools = 50 MaxLongestPerPool = 5 )
MaxEvidencePools and MaxLongestPerPool bound the evidence, so a finding over a large fleet still fits a reader's window; the totals keep the rest.
const ( StartStarted = "started" StartFailed = "failed" // StartUnknown is a start whose end is not on record: a runner still // starting when the period ended, one removed before it registered with // no fault recorded, or one known only from a session that never // registered (a session that did is started). An outcome this package // does not know is counted the same way. StartUnknown = "unknown" )
The outcomes a start can have. Anything else, or nothing, is unknown.
const ( StageBeforeContainer = "before_container" StageBeforeRegistration = "before_registration" StageAfterRegistration = "after_registration" )
Where a failed start stopped, in the order a start goes through them.
const ( ExplainImage = "image" ExplainImagePullIncident = "image_pull_incident" ExplainRuntimeIncident = "runtime_incident" ExplainThrottle = "throttle" )
What a group's failures are put down to, in the order they are tried: a fault the failed runner recorded, then an incident its host still reports, then a throttle, whose only tie to a failure is when it happened.
const ( IncidentRuntime = "runtime" IncidentImagePull = "image_pull" )
The incidents a host keeps, as store.HostIncidents names them.
const ( ScopeHost = "host" ScopePool = "pool" )
const CompareVersion = "compare-v1"
const ExperimentFailurePercentagePoints = 5
const ExperimentMaterialRatio = 1.10
const ExperimentMeasureVersion = "selected-measures-v1"
const ExperimentRuleVersion = "selected-runs-v2"
A version is fixed before looking at a cohort; changing the sample rule after seeing its failures would let a caller choose the answer.
const HostPressureVersion = "host-pressure-v1"
const JobNamesWithheldText = "" /* 178-byte string literal not displayed */
JobNamesWithheldText is the step a finding carries when its events' job names were left out: it says why, and what would show them.
const MaxCheckObservations = 5000
const MaxExperimentAttempts = 2000
const MaxPressureEvents = 20
MaxPressureEvents bounds the events a finding lists, newest first, so a host that failed all day still fits a reader's window; the counts keep the rest.
const MaxReadJobs = 5000
MaxReadJobs is the most jobs or runners the store reads for one finding. A busy period holds more; the finding then says the read was cut rather than ranking a part as if it were the whole.
const MaxRequiredChecks = 500
const MaxStartupGroups = 20
MaxStartupGroups bounds the groups a finding lists, so a fleet whose every host is failing still fits a reader's window; the counts keep the rest.
const MergeDelayVersion = "merge-delay-v1"
const MinExperimentSamples = 30
const QueueWaitVersion = "queue-wait-v1"
const RepeatedFailures = 3
RepeatedFailures is the fewest failures that make a host or a pool one that keeps failing: one or two failed starts are what a busy fleet always has.
const RootDependencyRows = 50
const StartupFailuresVersion = "startup-failures-v1"
Variables ¶
var DefaultPressure = PressureThresholds{LoadPerCPU: 2.0}
DefaultPressure is the throttle's load line (scheduler.LoadPerCPUOverwhelmed, pinned equal by a test, because this package may not import the scheduler). The reserve is the host's, so the caller fills it in.
Functions ¶
func CatalogCodes ¶
func CatalogCodes() []string
CatalogCodes is every code any analysis can name, so one test can prove each has a catalog page. Each analysis adds its own table here.
func ValidRootDependencies ¶
func ValidRootDependencies(e *RootDependencies, digest string) bool
Types ¶
type BadEvent ¶
type BadEvent struct {
JobID string `json:"job_id"`
Repo string `json:"repo"`
Workflow string `json:"workflow"`
JobName string `json:"job_name"`
Kind string `json:"kind"`
At time.Time `json:"at"`
// Measured is false when no sample covers the event's minute; Pressured
// is then false too, which says nothing either way.
Measured bool `json:"measured"`
Pressured bool `json:"pressured"`
}
BadEvent is one thing that went wrong on the host and the minute it came in. Repo, Workflow and JobName are workflow-written and untrusted.
type CauseShare ¶
type CauseShare struct {
Share float64 `json:"share"`
}
type Change ¶
type Change struct {
Measure string `json:"measure"`
// Unit is "ms" for a time, "rate" for a fraction of the side's jobs.
Unit string `json:"unit"`
Direction string `json:"direction"`
From *float64 `json:"from"`
To *float64 `json:"to"`
// Ratio is how far the measure moved against its bar, whichever way: the
// larger time over the smaller, or a rate's difference over five points.
// It is nil when the measure could not be judged, and for a time that
// moved from nothing, which has no finite ratio.
Ratio *float64 `json:"ratio"`
}
type CheckDelay ¶
type CheckDelay struct {
CheckID string `json:"check_id"`
State string `json:"state"`
ObservationID string `json:"observation_id"`
CompletionDelayMS *int64 `json:"completion_delay_ms"`
Timing CheckTiming `json:"timing"`
}
type CheckObservation ¶
type CheckObservation struct {
ID string
CheckID string
RunID string
InstanceID string
Attempt int
HeadSHA string
Conclusion string
QueuedAt time.Time
ProvisionedAt *time.Time
StartedAt *time.Time
CompletedAt *time.Time
// CheckCompletedAt can follow the job's execution completion. Keep it
// separate so check-reporting delay does not become execution time.
CheckCompletedAt *time.Time
}
CheckObservation identifies one logical instance in a workflow run. Matrix members need distinct InstanceIDs; retries retain that identity and raise Attempt.
type CheckTiming ¶
type CheckTiming struct {
PreStartMS *int64 `json:"pre_start_ms"`
QueueMS *int64 `json:"queue_ms"`
StartupMS *int64 `json:"startup_ms"`
ExecutionMS *int64 `json:"execution_ms"`
}
Timing partitions a job's observable lifetime. Unknown phases remain nil. Startup is carved out of pre-start wait, never added on top of queue wait.
type CompareEvidence ¶
type CompareEvidence struct {
By string `json:"by"`
A CompareSide `json:"a"`
B CompareSide `json:"b"`
// Changes holds one row for every measure, in a fixed order, so a
// reader finds a measure by name whether or not it moved.
Changes []Change `json:"changes"`
Excluded []string `json:"excluded"`
}
type CompareFinding ¶
type CompareFinding = Finding[CompareEvidence]
func Compare ¶
func Compare(s CompareSnapshot) (CompareFinding, error)
Compare says what got worse and what got better between two releases or two periods, and refuses to rank either when a side is too small to say. A is the earlier, B the one being judged against it.
type CompareSide ¶
type CompareSide struct {
// Label is a release such as v1.3.3, or a period such as "the 24 hours
// before". A period's label reads after "in", so it is a noun phrase.
Label string `json:"label"`
Since time.Time `json:"since"`
Until time.Time `json:"until"`
// Figures on a CountsOnly side hold Jobs and FleetFailed and nothing
// else: the roll-up it was read from keeps no outcomes, no OOM kills and
// no times, and a zero there is not a measurement.
Figures JobFigures `json:"figures"`
CountsOnly bool `json:"counts_only"`
// JobsGoneBefore is set on a release side reaching past retention.jobs:
// no roll-up records which release ran a job, so the jobs before it are
// simply gone. A period side never has it: past retention.jobs it is
// read from the roll-up instead (CountsOnly), so Compare refuses it.
JobsGoneBefore *time.Time `json:"jobs_gone_before"`
// MeanSlots is the fleet's slots on offer, averaged over the side's
// sampled minutes; nil when no sample covers the side.
MeanSlots *float64 `json:"mean_slots"`
// NoRecordBefore is set on a CountsOnly side whose counts begin later
// than the period asked for: the fleet keeps no counts before it (its
// first record, or where the roll-up began). With Since not before Until
// the side could not be read at all, which is not the same as no jobs
// having run: where the oldest rows were pruned before the roll-up first
// ran, jobs did run, and nobody can now say how many.
NoRecordBefore *time.Time `json:"no_record_before"`
// AskedUntil is where the period asked for ends. A CountsOnly side can
// end before it, because the roll-up reads whole UTC days: the hours
// from Until to AskedUntil are then the other side's or neither side's,
// and only this says so once Until has moved. Zero when not known.
AskedUntil time.Time `json:"asked_until"`
}
type CompareSnapshot ¶
type CompareSnapshot struct {
// By is "version" or "period".
By string
A, B CompareSide
// Excluded names the conclusions the read left out (cancelled and
// skipped). The analysis cannot see what it was not given, so it says
// what its caller says it left out, and no more.
Excluded []string
}
type Confidence ¶
type Confidence string
Confidence says how far a reader may lean on a finding. Anything short of high carries its reason, so a reader is never left to guess what was thin.
const ( High Confidence = "high" Medium Confidence = "medium" Low Confidence = "low" )
type Coverage ¶
type Coverage string
Coverage describes the reader's knowledge, not whether checks passed.
type ExperimentArm ¶
type ExperimentArm struct {
SourceRevision string `json:"source_revision"`
WorkflowRevision string `json:"workflow_revision"`
InputsID string `json:"inputs_id"`
TestScopeID string `json:"test_scope_id"`
ConfigurationID string `json:"configuration_id"`
ImageID string `json:"image_id"`
ResourceID string `json:"resource_id"`
// The controller must verify this against its retained selection, including
// failed attempts. The pure calculation cannot discover omitted runs.
SelectionComplete bool `json:"selection_complete"`
Attempts []ExperimentAttempt `json:"attempts"`
}
type ExperimentAttempt ¶
type ExperimentAttempt struct {
SourceRevision string `json:"source_revision"`
WorkflowID int64 `json:"workflow_id"`
// Nil is missing evidence; zero is an observed zero. Consumption stays
// attached to failed and excluded attempts rather than disappearing.
LatencyMS *int64 `json:"latency_ms"`
ExecutionMS *int64 `json:"execution_ms"`
AllocatedIdleMS *int64 `json:"allocated_idle_ms"`
RunID int64 `json:"run_id"`
Attempt int `json:"attempt"`
ObservedAt time.Time `json:"observed_at"`
Outcome string `json:"outcome"`
Cache string `json:"cache"`
HostCondition string `json:"host_condition"`
}
type ExperimentCounts ¶
type ExperimentDistribution ¶
type ExperimentDistribution struct {
Samples int `json:"samples"`
Missing int `json:"missing"`
MinMS *int64 `json:"min_ms"`
P50MS *int64 `json:"p50_ms"`
P95MS *int64 `json:"p95_ms"`
MaxMS *int64 `json:"max_ms"`
// A partial sum would look like a smaller bill. Report no total until every
// relevant attempt is measured, even when the measured subset is zero.
TotalMS *int64 `json:"total_ms"`
}
type ExperimentEligibility ¶
type ExperimentEligibility struct {
RuleVersion string `json:"rule_version"`
State string `json:"state"`
Reasons []string `json:"reasons"`
MinimumSamples int `json:"minimum_samples"`
Baseline ExperimentCounts `json:"baseline"`
Candidate ExperimentCounts `json:"candidate"`
}
func AssessExperiment ¶
func AssessExperiment(s ExperimentSnapshot) (ExperimentEligibility, error)
AssessExperiment classifies supplied historical evidence. Eligibility is permission to compare figures, not proof of causation, improvement or safety. The caller retains the snapshot beside this result for reproducibility.
type ExperimentMeasurement ¶
type ExperimentMeasurement struct {
Version string `json:"version"`
Eligibility ExperimentEligibility `json:"eligibility"`
Baseline ExperimentMeasures `json:"baseline"`
Candidate ExperimentMeasures `json:"candidate"`
LatencyChange string `json:"latency_change"`
// Consumption ranks the mean per selected attempt, not totals from cohorts
// of different sizes. Total consumption and sample counts remain visible.
ExecutionChange string `json:"execution_mean_change"`
AllocatedIdleChange string `json:"allocated_idle_mean_change"`
FailureChange string `json:"failure_rate_change"`
}
func MeasureExperiment ¶
func MeasureExperiment(s ExperimentSnapshot) (ExperimentMeasurement, error)
MeasureExperiment reports descriptive figures from supplied evidence. It supplies no confidence interval, causal inference or cost attribution.
type ExperimentMeasures ¶
type ExperimentMeasures struct {
Latency ExperimentDistribution `json:"successful_latency"`
Execution ExperimentDistribution `json:"all_attempt_execution"`
AllocatedIdle ExperimentDistribution `json:"all_attempt_allocated_idle"`
FailureRate *float64 `json:"failure_rate"`
}
type ExperimentSnapshot ¶
type ExperimentSnapshot struct {
RuleVersion string `json:"rule_version"`
Kind string `json:"kind"`
Repository string `json:"repository"`
InitiatingUser string `json:"initiating_user"`
Hypothesis string `json:"hypothesis"`
IntendedDelta string `json:"intended_delta"`
EvidenceID string `json:"evidence_id"`
RevisionDeltaID string `json:"revision_delta_id"`
Since time.Time `json:"since"`
Until time.Time `json:"until"`
Baseline ExperimentArm `json:"baseline"`
Candidate ExperimentArm `json:"candidate"`
}
type Finding ¶
type Finding[E any] struct { // Analysis is queue_wait, compare, host_pressure or startup_failures. Analysis string `json:"analysis"` // Version is queue-wait-v1, compare-v1, host-pressure-v1 or // startup-failures-v1; a reader refuses a version it does not know rather // than misreading its evidence. Version string `json:"version"` Since time.Time `json:"since"` Until time.Time `json:"until"` Summary string `json:"summary"` // Dominant is what the summary leads with; "" when nothing does. Dominant string `json:"dominant"` Confidence Confidence `json:"confidence"` // ConfidenceReason is set exactly when Confidence is not High. ConfidenceReason string `json:"confidence_reason,omitempty"` // NextSteps and Codes are never nil, so they marshal as [] and a reader // need not tell "none" from "missing". NextSteps []NextStep `json:"next_steps"` Codes []string `json:"codes"` Evidence E `json:"evidence"` }
Finding is the one shape every analysis answers in, so the route, the MCP tool, Eli, the CLI and the UI card render the same answer and cannot drift on a field name.
type HostIncident ¶
HostIncident is a runtime or image-pull incident a host still reports. A host keeps them only while they are current, so one that has cleared is not known here; PoolID is the pool whose image would not pull.
type HostInterval ¶
HostInterval is a stretch a host was throttled. A zero To is a throttle still on at Until.
type HostJob ¶
type HostJob struct {
ID, Repo, Workflow, JobName string
StartedAt, CompletedAt *time.Time
Conclusion, FaultKind string
OOMKilled bool
// StartupSeconds is the runner's request to its registration, the
// startup the queue-wait breakdown carves out of a wait.
StartupSeconds *int64
}
HostJob is a job that ran on the host. Repo, Workflow and JobName are written by a workflow, so whoever shows them to a model treats them as untrusted.
type HostMinute ¶
type HostMinute struct {
At time.Time
CPUPercent *float64
// LoadPerCPU is the one-minute load average over the host's cores.
LoadPerCPU *float64
// MemoryAvailableMB is judged against the reserve, as the scheduler
// judges it; MemoryMB is the machine's size at that minute (a host can
// be resized), from which the share in use is worked out.
MemoryAvailableMB *int64
MemoryMB int64
}
HostMinute is one sample of a host. Any figure the agent did not report is nil; a sample with none at all is what an agent too old to measure itself leaves, and counts as no sample.
type HostPressureEvidence ¶
type HostPressureEvidence struct {
HostID string `json:"host_id"`
HostName string `json:"host_name"`
// Pressure is what a minute was judged pressured by.
Pressure PressureThresholds `json:"pressure"`
// Minutes counts the sampled minutes with any measure; ExpectedMinutes
// is how many minutes the period holds.
Minutes int `json:"minutes"`
PressuredMinutes int `json:"pressured_minutes"`
ExpectedMinutes int `json:"expected_minutes"`
// Events is the newest MaxPressureEvents, newest first; EventsCut says
// how many older ones were left out. The counts take in every event.
Events []BadEvent `json:"events"`
EventsCut int `json:"events_cut"`
MeasuredEvents int `json:"measured_events"`
PressuredEvents int `json:"pressured_events"`
Unmeasured int `json:"unmeasured"`
JobsTruncated bool `json:"jobs_truncated"`
// JobNamesWithheld says the events' repository, workflow and job name
// were left out, because the reader may read hosts and not jobs.
JobNamesWithheld bool `json:"job_names_withheld"`
// Threshold is set only when the events cluster and some measure's line
// separates them; a line drawn where nothing clusters would be read as
// the cause.
Threshold *PressureThreshold `json:"threshold"`
}
type HostPressureFinding ¶
type HostPressureFinding = Finding[HostPressureEvidence]
func HostPressure ¶
func HostPressure(s HostPressureSnapshot) (HostPressureFinding, error)
HostPressure says whether what went wrong on a host (failures, OOM kills, slow startups) came in the minutes it was under pressure, with the line on one measure that separates them; or that it did not.
func WithholdJobNames ¶
func WithholdJobNames(f HostPressureFinding) HostPressureFinding
WithholdJobNames is the finding for a reader who may read hosts and not jobs. A host's view names no job, so the events lose their repository, workflow and job name, which jobs.read guards; every count, time, verdict and job id stays, so the answer to the question is the same answer. A finding with no events has nothing to withhold and is returned as it is.
type HostPressureSnapshot ¶
type HostPressureSnapshot struct {
Since, Until time.Time
HostID, HostName string
Minutes []HostMinute
// Jobs are those that started or completed on the host in the period.
Jobs []HostJob
// JobsTruncated says the job read stopped at MaxReadJobs, keeping the
// newest: a busy host over a long period runs more than that, and the
// failures among the oldest are then not there to place.
JobsTruncated bool
Thresholds PressureThresholds
}
type JobFigures ¶
type JobFigures struct {
Jobs int `json:"jobs"`
Succeeded int `json:"succeeded"`
Failed int `json:"failed"`
FleetFailed int `json:"fleet_failed"`
OOMKilled int `json:"oom_killed"`
Duration Spread `json:"duration"`
QueueWait Spread `json:"queue_wait"`
Startup Spread `json:"startup"`
}
JobFigures is what one side's jobs did. The caller leaves out the conclusions it names in CompareSnapshot.Excluded before counting, which is why there is no cancelled count: it would always read 0, and a 0 here would look like a measurement of none cancelled.
type LongWait ¶
type LongWait struct {
JobID string `json:"job_id"`
Repo string `json:"repo"`
Workflow string `json:"workflow"`
JobName string `json:"job_name"`
WaitSeconds int64 `json:"wait_seconds"`
Cause WaitCause `json:"cause"`
}
LongWait names one job. Repo, Workflow and JobName are workflow-written and untrusted.
type MergeDelayResult ¶
type MergeDelayResult struct {
Version string `json:"version"`
HeadSHA string `json:"head_sha"`
TargetBranch string `json:"target_branch"`
ObservedAt time.Time `json:"observed_at"`
WindowStart time.Time `json:"window_start"`
ProtectionCoverage Coverage `json:"protection_coverage"`
ObservationCoverage Coverage `json:"observation_coverage"`
State string `json:"state"`
Checks []CheckDelay `json:"checks"`
// This is an observed completion order, not a dependency critical path or
// a verdict on reviews, conflicts, merge queues or full merge readiness.
LastFinishingCheckID string `json:"last_finishing_check_id"`
IgnoredOtherHeads int `json:"ignored_other_heads"`
}
func MergeDelay ¶
func MergeDelay(s MergeDelaySnapshot) (MergeDelayResult, error)
MergeDelay never guesses required checks when protection is unavailable. Its caller supplies bounded, authorised evidence and a clock snapshot.
type MergeDelaySnapshot ¶
type MergeDelaySnapshot struct {
HeadSHA string
CurrentHeadSHA string
TargetBranch string
ObservedAt time.Time
// WindowStart is the start of this head's observation, not a job's start.
WindowStart time.Time
ProtectionCoverage Coverage
ObservationCoverage Coverage
Required []RequiredCheck
Observations []CheckObservation
}
type NextStep ¶
type NextStep struct {
Text string `json:"text"`
// Setting is a settings key, or a pool or host field as "pool.max_runners".
Setting string `json:"setting,omitempty"`
// Code is a catalog code.
Code string `json:"code,omitempty"`
// Link is the code's catalog page. The controller fills it after the
// analysis returns: where the docs are published is not the analysis's
// to know, and the UI has no other way to turn a code into an address.
Link string `json:"link,omitempty"`
}
NextStep is never a command, and names a setting or a catalog code where one applies: the catalog's own rule, because a pasted command skips the reading that says whether it applies. Some steps rightly name neither, such as GitHub's own delay in handing a job over, or the step that says job names were withheld, and are said in their text alone.
type PoolConditions ¶
type PoolConditions struct {
PoolID, PoolName string
Deleted bool
// AtCeiling is when the pool's live runners were at or above its current
// max_runners.
AtCeiling []Interval
// NoRoom holds the minutes recorded blocked at capacity, each covering
// [m, m+1m).
NoRoom []time.Time
// Throttled is when a host that fits the pool was on a throttle rung. A
// zero To is an episode still open at Until.
Throttled []Interval
}
PoolConditions is what held a pool back over the period.
type PoolWait ¶
type PoolWait struct {
PoolID string `json:"pool_id"`
PoolName string `json:"pool_name"`
Deleted bool `json:"deleted"`
Jobs int `json:"jobs"`
WaitSeconds int64 `json:"wait_seconds"`
Causes []CauseShare `json:"causes"`
// Dominant is "" for a pool whose jobs did not wait at all.
Dominant WaitCause `json:"dominant"`
Longest []LongWait `json:"longest"`
}
type PressureThreshold ¶
type PressureThreshold struct {
Measure string `json:"measure"`
// Value is a load per core, a CPU percentage, or a fraction of memory
// in use; it is rounded down, so the sentence and the shares are of the
// same line.
Value float64 `json:"value"`
}
PressureThreshold is the line on one measure that separates the minutes things went wrong in from the rest: EventShare of the events with that measure came at or above Value, which held for MinuteShare of the minutes it was measured.
type PressureThresholds ¶
type PressureThresholds struct {
LoadPerCPU float64 `json:"load_per_cpu"`
// MemoryReserveMB is the host's own reserve (store.Host.MemoryReserve):
// a minute whose available memory is at or below it is pressured.
MemoryReserveMB int64 `json:"memory_reserve_mb"`
}
PressureThresholds is the scheduler's own test for an overwhelmed host, as far as a minute's sample can show it: the load per core at which internal/scheduler's throttle climbs a rung, and the host's memory reserve. CPU occupancy is deliberately absent. A host whose runners are all inside their CPU quotas sits near 100% all day and is healthy, so a CPU line would make every busy host's failures "cluster in its busy minutes"; the throttle counts CPU only while an unlimited runner is present, which no sample records.
type QueueWaitEvidence ¶
type QueueWaitEvidence struct {
Fleet []CauseShare `json:"fleet"`
// Pools is ordered by WaitSeconds, longest first, and cut to
// MaxEvidencePools; PoolsCut says how many were left out. Fleet, Jobs
// and WaitSeconds still count every pool.
Pools []PoolWait `json:"pools"`
// Jobs counts every job read; Waited only those whose wait was more
// than nothing. Confidence and the sentences go by Waited, because on a
// healthy fleet most jobs start at once and say nothing about why the
// others waited.
Jobs int `json:"jobs"`
Waited int `json:"waited"`
WaitSeconds int64 `json:"wait_seconds"`
// NoRunnerRecord counts jobs that started after waiting past
// eligibility with no runner request on record (its runner pruned), so
// that wait could not be split into startup and GitHub's delay.
NoRunnerRecord int `json:"no_runner_record"`
// RunnerKept counts jobs that started after waiting past eligibility on
// a runner their pool keeps between jobs, which cannot be split either.
RunnerKept int `json:"runner_kept"`
JobsTruncated bool `json:"jobs_truncated"`
RunnersTruncated bool `json:"runners_truncated"`
PoolsCut int `json:"pools_cut"`
}
type QueueWaitFinding ¶
type QueueWaitFinding = Finding[QueueWaitEvidence]
func QueueWait ¶
func QueueWait(s QueueWaitSnapshot) (QueueWaitFinding, error)
QueueWait puts every second of every job's wait in the period down to one cause. It follows ZF-242's interval rule: the phases are disjoint, startup is carved out of the wait rather than added to it, and time no record explains stays unattributed.
type QueueWaitSnapshot ¶
type RequiredCheck ¶
RequiredCheck uses opaque identities. The reader must resolve producer and target-branch rules before mapping observations; job names alone do not suffice.
type RootDependencies ¶
type RootDependencies struct {
Version string `json:"version"`
Basis string `json:"basis"`
SourceSHA256 string `json:"source_sha256"`
Coverage string `json:"coverage"`
Counts *RootDependencyCounts `json:"counts"`
RowsOmitted *bool `json:"rows_omitted"`
References []RootDependencyReference `json:"references"`
}
RootDependencies is a reviewed public whitelist independent of private parser and store types. Null counts mean unsupported inspection, never known zero.
type RootDependencyCounts ¶
type RootDependencyReference ¶
type RootDependencyReference struct {
JobIndex *int `json:"job_index"`
StepIndex *int `json:"step_index"`
Line *int `json:"line"`
Declaration string `json:"declaration"`
Kind string `json:"kind"`
Pin string `json:"pin"`
ReferenceSHA256 string `json:"reference_sha256"`
Revision *string `json:"revision"`
}
type Spread ¶
type Spread struct {
Samples int `json:"samples"`
P50MS *int64 `json:"p50_ms"`
P95MS *int64 `json:"p95_ms"`
}
Spread is a measure's percentiles over a side's jobs. It mirrors store.JobPercentiles, field for field and name for name, so a figure in a finding and on the Usage page's table read the same.
type StartAttempt ¶
type StartAttempt struct {
RunnerID, PoolID, PoolName, HostID, HostName string
RequestedAt time.Time
ContainerStartedAt, RegisteredAt, EndedAt *time.Time
// Outcome is StartStarted, StartFailed or StartUnknown.
Outcome string
FaultKind string
// Message is the failure as the host reported it: written by a host, so
// whoever shows it to a model treats it as untrusted.
Message string
}
StartAttempt is one runner the fleet asked a host to start.
type StartupEvidence ¶
type StartupEvidence struct {
// Groups are the hosts and pools that keep failing, most failures
// first, cut to MaxStartupGroups; GroupsCut says how many were left out.
// When none does but one failed three times or more, it holds that one,
// the group the summary quotes.
Groups []StartupGroup `json:"groups"`
GroupsCut int `json:"groups_cut"`
// Starts counts the starts in the period with an outcome, Failures those
// that failed, and Unknown those with none.
Starts int `json:"starts"`
Failures int `json:"failures"`
Unknown int `json:"unknown"`
Truncated bool `json:"truncated"`
}
type StartupFailuresFinding ¶
type StartupFailuresFinding = Finding[StartupEvidence]
func StartupFailures ¶
func StartupFailures(s StartupSnapshot) (StartupFailuresFinding, error)
StartupFailures names the host or pool that keeps failing to start runners, since when, the stage it fails at, and whether a fault, an incident or a throttle explains it; or says that none does.
type StartupGroup ¶
type StartupGroup struct {
Scope string `json:"scope"`
ID string `json:"id"`
Name string `json:"name"`
// PoolID is set on a host's group that counts one pool's starts alone:
// the host a pool's failures all fell on while the pool started on
// others, which is that host's trouble and not the pool's.
PoolID string `json:"pool_id"`
Starts int `json:"starts"`
Failures int `json:"failures"`
// Unknown counts its starts with no recorded outcome, in neither Starts
// nor Failures.
Unknown int `json:"unknown"`
// FailingSince is the first failure after the last start that
// registered before its latest failure, or its first failure when no
// start registered before that.
FailingSince time.Time `json:"failing_since"`
// RecoveredAt is the first start that registered after its latest
// failure; nil while it is still failing.
RecoveredAt *time.Time `json:"recovered_at"`
LastAt time.Time `json:"last_at"`
// Stage and FaultKind are the most common among its failures.
Stage string `json:"stage"`
FaultKind string `json:"fault_kind"`
// Explanation is image, image_pull_incident, runtime_incident, throttle
// or "", and Explained how many of its failures it covers.
Explanation string `json:"explanation"`
Explained int `json:"explained"`
LastRunnerID string `json:"last_runner_id"`
// LastMessage is written by a host, not the fleet, and is untrusted.
LastMessage string `json:"last_message"`
}
StartupGroup is one host or one pool and how its starts went. Every time in it is when something happened: a failure when it failed (its request when that is not on record), a start when it registered.
type StartupSnapshot ¶
type StartupSnapshot struct {
Since, Until time.Time
// Attempts are the starts asked for in the period; one asked for outside
// it is left out, so a start belongs to one period only.
Attempts []StartAttempt
Throttles []HostInterval
Incidents []HostIncident
// Truncated says the read stopped at MaxReadJobs runners.
Truncated bool
// HeartbeatInterval is the agents' agent.heartbeat_interval. A runtime
// incident is first heard of on the beat after the start that found the
// runtime down, so a failure up to one interval before its since may be
// what raised it, and one further back may not. Zero reaches back not
// at all.
HeartbeatInterval time.Duration
}
type WaitCause ¶
type WaitCause string
WaitCause is what a stretch of a job's wait is put down to. Each second of a wait has exactly one, so the causes add up to the wait.
const ( CauseHeldForReview WaitCause = "held_for_review" CauseUnmatched WaitCause = "unmatched" CausePoolCeiling WaitCause = "pool_ceiling" CauseThrottled WaitCause = "throttled" CauseNoRoom WaitCause = "no_room" CauseRunnerStartup WaitCause = "runner_startup" CauseGitHubDelay WaitCause = "github_delay" // CauseUnattributed is time the fleet keeps no record to explain, such // as a pool with no eligible host. Naming a cause for it would be a guess. CauseUnattributed WaitCause = "unattributed" )
type WaitJob ¶
type WaitJob struct {
ID, PoolID, HostID, Repo, Workflow, JobName string
QueuedAt time.Time
HeldAt, ApprovedAt, EligibleAt *time.Time
// FirstStartFailureAt is the first runner for this job that failed to
// start; the wait after it is that failure's.
FirstStartFailureAt *time.Time
RunnerRequestedAt, RunnerRegisteredAt *time.Time
// StartedAt is nil while the job is still waiting at Until.
StartedAt *time.Time
// RunnerKept is a job taken by a runner its pool keeps between jobs, one
// asked for before the job was queued, whose stamps the caller leaves
// out: that runner's startup is not this job's, and whether it was busy
// or idle when the job became eligible is not recorded. The wait after
// eligibility then goes to the pool's conditions, as for a job whose
// runner is gone, but its record is not missing and is not counted so.
RunnerKept bool
}
WaitJob is one job's timeline. Repo, Workflow and JobName are written by a workflow, so whoever shows them to a model treats them as untrusted.