Help us shape the future of Go! Take the 2026 Go Developer Survey

analysis

package
v1.3.8 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 19, 2026 License: AGPL-3.0 Imports: 9 Imported by: 0

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

View Source
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.

View Source
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.

View Source
const (
	HostPressured    = "pressure"
	HostNotPressured = "not_pressure"
)

What a host pressure finding leads with.

View Source
const (
	EventFailure     = "failure"
	EventOOM         = "oom"
	EventSlowStartup = "slow_startup"
)

The kinds of thing that went wrong on a host.

View Source
const (
	MeasureLoadPerCPU = "load_per_cpu"
	MeasureCPUPercent = "cpu_percent"
	MeasureMemoryUsed = "memory_used"
)

The measures a threshold can be drawn on.

View Source
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.

View Source
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.

View Source
const (
	StageBeforeContainer    = "before_container"
	StageBeforeRegistration = "before_registration"
	StageAfterRegistration  = "after_registration"
)

Where a failed start stopped, in the order a start goes through them.

View Source
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.

View Source
const (
	IncidentRuntime   = "runtime"
	IncidentImagePull = "image_pull"
)

The incidents a host keeps, as store.HostIncidents names them.

View Source
const (
	ScopeHost = "host"
	ScopePool = "pool"
)
View Source
const CompareVersion = "compare-v1"
View Source
const ExperimentFailurePercentagePoints = 5
View Source
const ExperimentMaterialRatio = 1.10
View Source
const ExperimentMeasureVersion = "selected-measures-v1"
View Source
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.

View Source
const HostPressureVersion = "host-pressure-v1"
View Source
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.

View Source
const MaxCheckObservations = 5000
View Source
const MaxExperimentAttempts = 2000
View Source
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.

View Source
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.

View Source
const MaxRequiredChecks = 500
View Source
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.

View Source
const MergeDelayVersion = "merge-delay-v1"
View Source
const MinExperimentSamples = 30
View Source
const QueueWaitVersion = "queue-wait-v1"
View Source
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.

View Source
const RootDependencyRows = 50
View Source
const StartupFailuresVersion = "startup-failures-v1"

Variables

View Source
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

func Words

func Words(d time.Duration) string

Words says a duration the way a person would: "3h 10m", "12m 5s", "45s". Two units are enough to compare causes by, and a negative duration (two clocks disagreeing) reads as none rather than as a minus sign.

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 {
	Cause   WaitCause `json:"cause"`
	Seconds int64     `json:"seconds"`
	// Share is the fraction of the wait, to two places.
	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"`
	PercentilesUnavailable bool     `json:"percentiles_unavailable"`
}

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.

const (
	CoverageComplete     Coverage = "complete"
	CoveragePartial      Coverage = "partial"
	CoverageNotPermitted Coverage = "not_permitted"
	CoverageNotAvailable Coverage = "not_available"
	CoverageHeldBack     Coverage = "held_back"
	CoverageFailed       Coverage = "failed"
)

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 ExperimentCounts struct {
	Selected   int `json:"selected"`
	Completed  int `json:"completed"`
	Succeeded  int `json:"succeeded"`
	Failed     int `json:"failed"`
	Cancelled  int `json:"cancelled"`
	Superseded int `json:"superseded"`
	Unknown    int `json:"unknown"`
}

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

type HostIncident struct {
	HostID, Kind, PoolID string
	Since                time.Time
}

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

type HostInterval struct {
	HostID string
	Interval
}

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

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 Interval

type Interval struct {
	From time.Time `json:"from"`
	To   time.Time `json:"to"`
}

Interval is a half-open span of time, [From, To).

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"`
	EventShare  float64 `json:"event_share"`
	MinuteShare float64 `json:"minute_share"`
}

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

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 QueueWaitSnapshot struct {
	Since, Until time.Time
	Jobs         []WaitJob
	Pools        []PoolConditions
	// JobsTruncated and RunnersTruncated say a read stopped at MaxReadJobs. A
	// cut runner read thins AtCeiling as surely as a cut job read thins Jobs.
	JobsTruncated    bool
	RunnersTruncated bool
}

type RequiredCheck

type RequiredCheck struct {
	ID     string
	Mapped bool
}

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 RootDependencyCounts struct {
	Declarations *int `json:"declarations"`
	CommitPins   *int `json:"commit_pins"`
	DigestPins   *int `json:"digest_pins"`
	Floating     *int `json:"floating"`
	Local        *int `json:"local"`
	Expressions  *int `json:"expressions"`
	Unknown      *int `json:"unknown"`
}

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL