Documentation
¶
Overview ¶
Package model holds the plain-data types shared by the gather, detect and tool layers.
Nothing here may reference a Kubernetes client, a context, or a live API object: Snapshot must round-trip through YAML so that `argus capture` writes fixtures, tests read them back, and production runs the identical detect path. That constraint is load-bearing — see the plan.
Index ¶
- func ParseCPU(q string) int64
- func ParseMem(q string) int64
- func Wrap(s string, indent int) string
- type ChainPod
- type ConditionView
- type ContainerSpecView
- type ContainerStateView
- type ContainerView
- type EventGroup
- type Evidence
- type Finding
- type HPAView
- type Hop
- type IngressRoute
- type LogBundle
- type LogGroup
- type NodeCapacity
- type NodeFit
- type NodeView
- type PDBView
- type PendingReport
- type PendingSpec
- type PodView
- type ProbeView
- type ReplicaSetView
- type ServiceChain
- type ServicePortSpec
- type ServiceView
- type Severity
- type Snapshot
- type Taint
- type Toleration
- type ToolHint
- type TraceReport
- type TriageGroup
- type TriageResult
- type WorkloadView
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ParseCPU ¶
ParseCPU returns a CPU quantity in millicores, or 0 if unset/unparseable. Detectors do arithmetic on these, so a bad value must degrade to "unknown", never panic.
func Wrap ¶ added in v0.1.9
Wrap reflows text to ~92 columns with the given indent.
Lives here because both the diagnosis renderer and the log renderer need it, and a copy in each is how two renderers start disagreeing about their own output. Long unwrapped lines are not cosmetic: an evidence line ran to 181 characters, which wraps unreadably in a terminal and forced horizontal scrolling everywhere else it was shown.
Types ¶
type ChainPod ¶ added in v0.1.19
type ChainPod struct {
Name string `json:"name"`
Ready bool `json:"ready"`
Phase string `json:"phase,omitempty"`
// PortNames and PortNumbers are what the containers DECLARE. Kubernetes neither
// requires a listening port to be declared nor verifies that a declared one is
// listening, which is why a numeric mismatch is a hint and a named one is proof.
PortNames []string `json:"port_names,omitempty"`
PortNumbers []int32 `json:"port_numbers,omitempty"`
}
ChainPod is a pod the selector matched, with the ports its containers declare.
type ConditionView ¶
type ConditionView struct {
Type string `json:"type"`
Status string `json:"status"`
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
LastChangeSecsAgo int64 `json:"last_change_seconds_ago,omitempty"`
}
ConditionView is a k8s condition reduced to what a detector reads.
type ContainerSpecView ¶
type ContainerSpecView struct {
Name string `json:"name"`
Image string `json:"image"`
RequestCPU string `json:"request_cpu,omitempty"`
RequestMem string `json:"request_mem,omitempty"`
LimitCPU string `json:"limit_cpu,omitempty"`
LimitMem string `json:"limit_mem,omitempty"`
// Command is the entrypoint override. It is what exit 126/127 is usually about, and citing it
// turns "exit 127" into "your command is X and the image does not contain it".
Command []string `json:"command,omitempty"`
Args []string `json:"args,omitempty"`
EnvKeys []string `json:"env_keys,omitempty"` // keys only; values are redacted at the projection boundary
EnvFrom []string `json:"env_from,omitempty"`
Mounts []string `json:"mounts,omitempty"`
Readiness *ProbeView `json:"readiness,omitempty"`
Liveness *ProbeView `json:"liveness,omitempty"`
Startup *ProbeView `json:"startup,omitempty"`
}
ContainerSpecView is the desired shape of a container — the half of ContainerView that also appears in a ReplicaSet template, which is why it is factored out and diffable on its own.
type ContainerStateView ¶
type ContainerStateView struct {
Status string `json:"status"` // running | waiting | terminated
Reason string `json:"reason,omitempty"`
Message string `json:"message,omitempty"`
ExitCode int32 `json:"exit_code,omitempty"`
Signal int32 `json:"signal,omitempty"`
SecondsAgo int64 `json:"seconds_ago,omitempty"` // since started or finished
}
ContainerStateView flattens running/waiting/terminated into one shape.
type ContainerView ¶
type ContainerView struct {
ContainerSpecView `json:",inline"`
Ready bool `json:"ready"`
Started bool `json:"started"`
RestartCount int32 `json:"restart_count"`
State ContainerStateView `json:"state"`
LastState *ContainerStateView `json:"last_state,omitempty"`
// UsageCPU/UsageMem come from metrics.k8s.io; empty when the metrics API is unavailable.
UsageCPU string `json:"usage_cpu,omitempty"`
UsageMem string `json:"usage_mem,omitempty"`
}
ContainerView merges the container spec, its live status, and its current metrics — the three things every detector needs to correlate and which kubectl makes you fetch separately.
type EventGroup ¶
type EventGroup struct {
Type string `json:"type"` // Normal | Warning
Reason string `json:"reason"`
Message string `json:"message"` // normalized: UIDs, IPs, digests stripped
Count int32 `json:"count"` // total occurrences, respecting the apiserver's own aggregation
ObjectKind string `json:"object_kind,omitempty"`
ObjectName string `json:"object_name,omitempty"` // one example object, not the only one
// ObjectCount is how many distinct objects reported this. Forty pods reporting the same
// BackOff is one group with ObjectCount 40, which is the fact an SRE actually wants.
ObjectCount int `json:"object_count,omitempty"`
FirstSeenSecondsAgo int64 `json:"first_seen_seconds_ago"`
LastSeenSecondsAgo int64 `json:"last_seen_seconds_ago"`
}
EventGroup is a deduplicated bucket of events. A hundred BackOff events collapse to one line.
type Evidence ¶
type Evidence struct {
Source string `json:"source"` // "pod.lastState", "event", "replicaset.diff", "metrics"
Ref string `json:"ref"` // "pod/checkout-api-7d9f", "rs/checkout-api-7d9f"
Excerpt string `json:"excerpt"` // the actual value that triggered the detector
}
Evidence is a citation. Every Finding must carry at least one — a high-confidence diagnosis with nothing to check is worse than no diagnosis during an incident.
type Finding ¶
type Finding struct {
ID string `json:"id"` // "oomkill.limit-too-low"
Severity Severity `json:"severity"` // critical | warning | info
// Confidence is how sure we are the finding is real, independent of severity. Be honest: a
// detector working from partial data (see Snapshot.Degraded) must dock its own confidence.
Confidence float64 `json:"confidence"` // 0.0 - 1.0
Scope string `json:"scope"` // "workload/checkout-api", "node/gke-abc"
Title string `json:"title"` // one line
Detail string `json:"detail"` // 2-4 sentences: cause, not symptom
Evidence []Evidence `json:"evidence"` // never empty; enforced by test
NextTool *ToolHint `json:"next_tool,omitempty"`
// Suppresses lists finding IDs this one subsumes. The node detector uses it to widen scope:
// three Deployments failing on one node is one node finding, not three workload diagnoses.
Suppresses []string `json:"-"`
}
Finding is one ranked diagnosis. Flat and JSON-friendly, matching the gospect-mcp finding model.
type HPAView ¶
type HPAView struct {
Name string `json:"name"`
MinReplicas int32 `json:"min_replicas"`
MaxReplicas int32 `json:"max_replicas"`
Current int32 `json:"current_replicas"`
Desired int32 `json:"desired_replicas"`
Conditions []ConditionView `json:"conditions,omitempty"`
}
HPAView explains scale decisions that look like workload failures.
type Hop ¶ added in v0.1.19
type Hop struct {
Step string `json:"step"`
// Status is ok | broken | warn | skipped. "skipped" covers both a hop that does not
// apply and one downstream of a break, where the data says nothing about health.
Status string `json:"status"`
Detail string `json:"detail"`
// Remedy is set only on the hop where the chain breaks.
Remedy string `json:"remedy,omitempty"`
}
Hop is one link in the chain, in the order traffic traverses it.
type IngressRoute ¶ added in v0.1.19
type IngressRoute struct {
Ingress string `json:"ingress"`
Class string `json:"class,omitempty"`
Host string `json:"host,omitempty"`
Path string `json:"path,omitempty"`
// BackendPort as written on the rule: a number, or a Service port NAME.
BackendPort string `json:"backend_port"`
BackendIsName bool `json:"backend_is_name,omitempty"`
}
IngressRoute is one Ingress rule pointing at the Service being traced.
type LogBundle ¶ added in v0.1.2
type LogBundle struct {
Pod string `json:"pod"`
Container string `json:"container"`
// Previous reports whether these are the *previous* container instance's logs. On a
// crashlooping pod that is almost always what you want: the current instance is in backoff
// and has produced nothing.
Previous bool `json:"previous"`
// Reason explains why this pod, container and instance were chosen, so the selection is
// auditable rather than magic.
Reason string `json:"reason"`
Groups []LogGroup `json:"groups"`
// DroppedGroups counts distinct lines elided by the token budget. Never silent: a truncated
// log that does not say it was truncated reads as a complete one.
DroppedGroups int `json:"dropped_groups,omitempty"`
Note string `json:"note,omitempty"`
}
LogBundle is projected container output: redacted, grouped and budgeted.
Raw logs are the least structured and most dangerous thing argus emits — unbounded in size, and the one place an application can print a credential. Everything here exists to bound one of those two risks.
type LogGroup ¶ added in v0.1.2
type LogGroup struct {
Text string `json:"text"`
Count int `json:"count"`
// FirstSecondsAgo/LastSecondsAgo are zero when the log carried no usable timestamps.
FirstSecondsAgo int64 `json:"first_seconds_ago,omitempty"`
LastSecondsAgo int64 `json:"last_seconds_ago,omitempty"`
}
LogGroup is a set of log lines identical after normalization. Ten thousand identical panics collapse to one entry with a count, which is the difference between a readable diagnosis and a context window full of the same stack trace.
type NodeCapacity ¶ added in v0.1.16
type NodeCapacity struct {
Name string `json:"name"`
Labels map[string]string `json:"labels,omitempty"`
Taints []Taint `json:"taints,omitempty"`
Unschedulable bool `json:"unschedulable,omitempty"`
Ready bool `json:"ready"`
AllocCPUMilli int64 `json:"alloc_cpu_milli"`
AllocMemBytes int64 `json:"alloc_mem_bytes"`
// Used is the sum of REQUESTS of pods already assigned here, which is what the
// scheduler reserves against — not current usage, which is a different number and
// the one people reach for by mistake.
UsedCPUMilli int64 `json:"used_cpu_milli"`
UsedMemBytes int64 `json:"used_mem_bytes"`
}
NodeCapacity is one node's schedulable state: what it has, what is already committed on it, and what would keep a pod off it.
type NodeFit ¶ added in v0.1.16
type NodeFit struct {
Node string `json:"node"`
Fits bool `json:"fits"`
// Reasons is empty when the node fits. Each entry names the check and its numbers,
// because "insufficient memory" without the figures is not an explanation.
Reasons []string `json:"reasons,omitempty"`
FreeCPUMilli int64 `json:"free_cpu_milli"`
FreeMemBytes int64 `json:"free_mem_bytes"`
}
NodeFit is the verdict for one node, with the numbers that produced it.
type NodeView ¶
type NodeView struct {
Name string `json:"name"`
Ready bool `json:"ready"`
Unschedulable bool `json:"unschedulable,omitempty"`
Conditions []ConditionView `json:"conditions,omitempty"`
Taints []string `json:"taints,omitempty"`
AllocCPU string `json:"alloc_cpu,omitempty"`
AllocMem string `json:"alloc_mem,omitempty"`
}
NodeView carries the conditions that explain workload failures the workload did not cause.
type PDBView ¶
type PDBView struct {
Name string `json:"name"`
DesiredHealthy int32 `json:"desired_healthy"`
CurrentHealthy int32 `json:"current_healthy"`
DisruptionsAllowed int32 `json:"disruptions_allowed"`
}
PDBView explains rollouts that are stuck rather than broken.
type PendingReport ¶ added in v0.1.16
type PendingReport struct {
Pod string `json:"pod"`
Namespace string `json:"namespace"`
PendingSeconds int64 `json:"pending_seconds"`
// Reason/Message are the scheduler's own words from the PodScheduled condition,
// reported alongside our arithmetic rather than instead of it.
Reason string `json:"scheduler_reason,omitempty"`
Message string `json:"scheduler_message,omitempty"`
Spec PendingSpec `json:"asked_for"`
Nodes []NodeFit `json:"nodes"`
Feasible int `json:"feasible_nodes"`
Summary []string `json:"summary"`
// NotChecked states the limits of the analysis. A scheduling explanation that
// implies completeness it does not have is worse than one that names its gaps.
NotChecked []string `json:"not_checked,omitempty"`
}
PendingReport explains one pod's unschedulability.
type PendingSpec ¶ added in v0.1.16
type PendingSpec struct {
NeedCPUMilli int64 `json:"need_cpu_milli"`
NeedMemBytes int64 `json:"need_mem_bytes"`
NodeSelector map[string]string `json:"node_selector,omitempty"`
Tolerations []Toleration `json:"tolerations,omitempty"`
HasAffinity bool `json:"has_node_affinity,omitempty"`
}
PendingSpec is what the unschedulable pod is asking for.
type PodView ¶
type PodView struct {
Name string `json:"name"`
Node string `json:"node,omitempty"`
Phase string `json:"phase"`
Ready bool `json:"ready"`
Labels map[string]string `json:"labels,omitempty"`
OwnerKind string `json:"owner_kind,omitempty"`
OwnerName string `json:"owner_name,omitempty"`
CreatedSecondsAgo int64 `json:"created_seconds_ago"`
// SchedulingReason/Message come from the PodScheduled condition when Phase is Pending.
SchedulingReason string `json:"scheduling_reason,omitempty"`
SchedulingMessage string `json:"scheduling_message,omitempty"`
Containers []ContainerView `json:"containers,omitempty"`
}
PodView is the projected pod. Budget: under 400 tokens serialized — enforced by test.
type ProbeView ¶
type ProbeView struct {
Kind string `json:"kind"` // http | tcp | exec | grpc
InitialDelay int32 `json:"initial_delay"`
Period int32 `json:"period"`
Timeout int32 `json:"timeout"`
FailureThreshold int32 `json:"failure_threshold"`
SuccessThreshold int32 `json:"success_threshold,omitempty"`
}
ProbeView is the probe timing a detector reasons about; the handler details do not matter to it.
func (*ProbeView) Deadline ¶
Deadline is how long the probe tolerates a slow start before the kubelet acts, in seconds. This is the number the readiness detector compares against observed startup time — computing it by hand from four separate fields is exactly the arithmetic humans get wrong at 3am.
type ReplicaSetView ¶
type ReplicaSetView struct {
Name string `json:"name"`
Revision string `json:"revision,omitempty"`
Desired int32 `json:"desired"`
Ready int32 `json:"ready"`
Available int32 `json:"available"`
Current bool `json:"current"` // matches the workload's current pod-template hash
CreatedSecondsAgo int64 `json:"created_seconds_ago"`
// Template is the projected pod template, which is what the rollout detector diffs.
Template []ContainerSpecView `json:"template,omitempty"`
}
ReplicaSetView carries enough of a ReplicaSet to diff two generations and tell which one is the current rollout target.
type ServiceChain ¶ added in v0.1.19
type ServiceChain struct {
Service string `json:"service"`
Namespace string `json:"namespace"`
Type string `json:"type,omitempty"`
Selector map[string]string `json:"selector,omitempty"`
Ports []ServicePortSpec `json:"ports,omitempty"`
Routes []IngressRoute `json:"routes,omitempty"`
Matched []ChainPod `json:"matched_pods,omitempty"`
// NearMiss names pods whose label value is a plausible misspelling of what the selector
// wants. That is the shape a mislabelled workload has, and it is the whole difference
// between "nothing is deployed here" and "your selector has a typo".
//
// Plausible is load-bearing. Matching on a shared label KEY looks equivalent and
// over-matches badly: every pod in a namespace carries `app`, so on a live cluster it
// named six unrelated workloads and pointed the reader at the wrong one.
NearMiss []string `json:"near_miss_pods,omitempty"`
// NearMissTotal is how many pods qualified, which is not len(NearMiss) — that list is
// truncated for readability, and reporting its length as the count states a wrong number.
NearMissTotal int `json:"near_miss_total,omitempty"`
EndpointsReady int `json:"endpoints_ready"`
EndpointsNotReady int `json:"endpoints_not_ready"`
// EndpointPorts is what the EndpointSlices actually carry. Empty while pods matched is
// the dataplane confirming a targetPort that resolved to nothing.
EndpointPorts []int32 `json:"endpoint_ports,omitempty"`
ExternalPolicyLocal bool `json:"external_traffic_policy_local,omitempty"`
// PodsTruncated records that the namespace holds more pods than were listed. It matters
// because it turns "the selector matches nothing" from an incomplete answer into a
// possibly wrong one, and that hop has to hedge rather than stay definitive.
PodsTruncated bool `json:"pods_truncated,omitempty"`
}
ServiceChain is the input to a trace. Plain data with no client and no live objects, so Trace stays a pure function and is testable without a cluster, like every detector.
type ServicePortSpec ¶ added in v0.1.19
type ServicePortSpec struct {
Name string `json:"name,omitempty"`
Port int32 `json:"port"`
Protocol string `json:"protocol,omitempty"`
// TargetPort as written. A NAME here is the case worth tracing: it has to match a
// containerPort name, and when it does not, the Service produces no endpoint port at
// all while both the Service and the pods keep reporting healthy.
TargetPort string `json:"target_port"`
TargetIsName bool `json:"target_is_name,omitempty"`
}
ServicePortSpec is one Service port and the target it resolves to.
type ServiceView ¶
type ServiceView struct {
Name string `json:"name"`
Selector map[string]string `json:"selector,omitempty"`
Ports []string `json:"ports,omitempty"`
ReadyCount int `json:"ready_count"`
NotReadyCount int `json:"not_ready_count"`
// MatchedPods is how many pods in the namespace the selector matches at all, ready or not.
// Zero here means a label mismatch; non-zero with ReadyCount 0 means a readiness failure.
MatchedPods int `json:"matched_pods"`
}
ServiceView plus its endpoint readiness — the pair that exposes the classic silent failure where a Service selector matches nothing and `kubectl get` looks entirely healthy.
type Severity ¶
type Severity string
Severity is how bad the finding is if it is real. Ranked critical > warning > info.
type Snapshot ¶
type Snapshot struct {
Scope string `json:"scope"` // "workload/prod/checkout-api" or "cluster"
Namespace string `json:"namespace"` // empty for cluster scope
Workload *WorkloadView `json:"workload,omitempty"`
ReplicaSets []ReplicaSetView `json:"replicasets,omitempty"`
Pods []PodView `json:"pods,omitempty"`
Events []EventGroup `json:"events,omitempty"`
Services []ServiceView `json:"services,omitempty"`
Nodes []NodeView `json:"nodes,omitempty"`
HPA *HPAView `json:"hpa,omitempty"`
PDB *PDBView `json:"pdb,omitempty"`
// Degraded lists gather steps that failed or timed out. Detectors working from partial data
// must dock their confidence — see Snapshot.Missing.
Degraded []string `json:"degraded,omitempty"`
// Notes records deliberate elisions — data we chose not to keep, not data we failed to get.
// Kept separate from Degraded so that trimming rollout history never makes a detector think
// the apiserver was unreachable. Silent truncation reads as "we looked at everything".
Notes []string `json:"notes,omitempty"`
}
Snapshot is everything argus gathered about one question, projected down to an explicit allowlist of fields. Detectors are pure functions over this type.
Time is stored as *seconds ago* rather than as absolute timestamps. A fixture with absolute times silently rots: a detector asking "was this OOMKill recent?" stops firing the day after the fixture is captured, and the test still passes because no detector fires and none was expected. Relative time keeps committed fixtures meaningful forever.
type Taint ¶ added in v0.1.16
type Taint struct {
Key string `json:"key"`
Value string `json:"value,omitempty"`
Effect string `json:"effect"`
}
Taint is a node taint reduced to what a toleration check needs.
type Toleration ¶ added in v0.1.16
type Toleration struct {
Key string `json:"key,omitempty"`
Operator string `json:"operator,omitempty"` // Equal (default) | Exists
Value string `json:"value,omitempty"`
Effect string `json:"effect,omitempty"` // empty matches every effect
}
Toleration is a pod toleration, matching Kubernetes' semantics for the fields that actually decide whether a taint is tolerated.
type ToolHint ¶
type ToolHint struct {
Tool string `json:"tool"`
Args map[string]string `json:"args,omitempty"`
Reason string `json:"reason,omitempty"`
}
ToolHint points the model at the next useful call rather than making it guess.
type TraceReport ¶ added in v0.1.19
type TraceReport struct {
Service string `json:"service"`
Namespace string `json:"namespace"`
Hops []Hop `json:"hops"`
// BrokenAt names the first failing hop, empty when the declared chain is intact.
BrokenAt string `json:"broken_at,omitempty"`
// NotChecked is load-bearing for this tool in a way it is not for the others. The
// declared chain being intact is a common and genuinely useful result, and it means the
// cause is in here — so an empty or vague list would send the reader back to what they
// already ruled out.
NotChecked []string `json:"not_checked"`
}
TraceReport is the answer: the chain, and where it gives out.
type TriageGroup ¶ added in v0.1.10
type TriageGroup struct {
Kind string `json:"kind"`
Name string `json:"name"`
Namespace string `json:"namespace"`
Ready int32 `json:"ready"`
Desired int32 `json:"desired"`
Pods int `json:"pods"`
Findings []Finding `json:"findings"`
}
TriageGroup is one controller's findings. Triage reports controllers, never pods: forty crashlooping pods of one Deployment is one entry with a count, not forty entries.
type TriageResult ¶ added in v0.1.10
type TriageResult struct {
Scope string `json:"scope"`
Scanned int `json:"workloads_scanned"`
Unhealthy int `json:"workloads_with_findings"`
// Cluster carries findings that are about shared infrastructure rather than any one workload —
// an unhealthy node above all. Reported once here instead of repeated on every workload it
// happens to host, which is the same collapsing principle applied one level up.
Cluster []Finding `json:"cluster,omitempty"`
Groups []TriageGroup `json:"groups"`
Omitted int `json:"omitted_groups,omitempty"`
Degraded []string `json:"degraded,omitempty"`
Notes []string `json:"notes,omitempty"`
}
TriageResult is the whole-cluster answer to "what is broken right now".
type WorkloadView ¶
type WorkloadView struct {
Kind string `json:"kind"` // Deployment | StatefulSet | DaemonSet | Rollout
Name string `json:"name"`
Namespace string `json:"namespace"`
Labels map[string]string `json:"labels,omitempty"`
Selector map[string]string `json:"selector,omitempty"`
Desired int32 `json:"desired"`
Ready int32 `json:"ready"`
Updated int32 `json:"updated"`
Available int32 `json:"available"`
Generation int64 `json:"generation"`
ObservedGeneration int64 `json:"observed_generation"`
CreatedSecondsAgo int64 `json:"created_seconds_ago"`
Conditions []ConditionView `json:"conditions,omitempty"`
}
WorkloadView is the controller under diagnosis.