Documentation
¶
Overview ¶
Package measurement provides types and utilities for collecting, comparing, and filtering system measurements from various sources (Kubernetes, GPU, OS, SystemD, NodeTopology, NetworkTopology).
Public API contract ¶
This package is part of aicr's cross-repo public API. External producers (e.g. k8s-launch-kit) import these types directly to emit Measurements that aicr Snapshots can consume. The Go type definitions AND the schema conventions (which Subtype names mean what, which fields belong in Data vs Context, the NetworkTopology layout) are part of the contract — see docs/integrator/measurement-api.md. Breaking changes require a pseudo-version bump that downstream consumers pin against.
Core Types ¶
The package defines a hierarchical structure for measurements:
- Type: Enum identifying the measurement source (K8s, GPU, OS, SystemD, NodeTopology, NetworkTopology)
- Measurement: Contains a Type and a slice of Subtypes
- Subtype: Named collection of key-value data (e.g., "cluster", "node"); may also carry an ordered Items list of structured records
- ItemEntry: One element of a Subtype.Items list. Data holds Reading scalars; Context holds string metadata. Mirrors Subtype's payload contract.
- Reading: Interface for type-safe scalar values (int, float64, string, bool, etc.)
- Path: A parsed constraint measurement path ("K8s.server.version", "NetworkTopology.pfs[rail=3].pciAddress") and its extraction against a set of Measurements
Constraint path catalog ¶
catalog.go enumerates which paths are ADDRESSABLE — which {Type, Subtype, Key} triples a supported producer can emit and a path form can name — and ValidatePath is the check. Recipe loading applies it to every constraint name so an unaddressable path fails at load instead of degrading to ErrCodeNotFound during evaluation, where the resolver would read it as "reading absent from this snapshot" and silently skip the gate (issue #1783).
Addressability is a static contract, not a claim about any snapshot. ValidatePath answering nil does NOT mean Path.Extract will find a value: an addressable path absent from a particular snapshot still returns ErrCodeNotFound at evaluation, and that remains the designed graceful-exclusion signal. The catalog only rules out paths that could never resolve against any snapshot.
A COLLECTOR CHANGE MAY REQUIRE A CATALOG ENTRY: a new subtype does (unless its Type is open-subtype), as does a new key in a CLOSED key space, or a change to how a space is addressed. A new key in an OPEN space does not. Omitting a required entry does not weaken a check; it makes a legitimate constraint path fail at load. When a producer's key space is not provably fixed, declare it open rather than guessing a closed set. Note that Subtype.Context is never addressable — no path form reads it — so its keys are deliberately absent.
Creating Measurements ¶
Use convenience constructors to create readings:
m := &Measurement{
Type: TypeK8s,
Subtypes: []Subtype{
{
Name: "cluster",
Data: map[string]Reading{
"version": Str("1.28.0"),
"nodes": Int(3),
"ready": Bool(true),
},
},
},
}
Or use the builder pattern for cleaner code:
m := NewMeasurement(TypeK8s).
WithSubtype(
NewSubtypeBuilder("cluster").
Set("version", Str("1.28.0")).
Set("nodes", Int(3)).
Build(),
)
Accessing Data ¶
Use type-safe getters to retrieve values:
version, err := m.GetSubtype("cluster").GetString("version")
nodes, err := m.GetSubtype("cluster").GetInt64("nodes")
ready, err := m.GetSubtype("cluster").getBool("ready")
Filtering Data ¶
Filter sensitive or unwanted keys using wildcard patterns:
// Remove all keys containing "password" or starting with "secret"
filtered := FilterOut(readings, []string{"*password*", "secret*"})
// Keep only version and count fields
kept := filterIn(readings, []string{"version", "count"})
Serialization ¶
Measurements support JSON and YAML marshaling/unmarshaling:
data, _ := json.Marshal(m) yaml, _ := yaml.Marshal(m)
The Reading interface is automatically marshaled to its underlying value, avoiding wrapper structures in the output.
Index ¶
- Constants
- Variables
- func FilterOut(readings map[string]Reading, keys []string) map[string]Reading
- func ValidatePath(name string) error
- type AllowedScalar
- type ItemEntry
- type Measurement
- type MeasurementBuilder
- type Path
- type Reading
- type Scalar
- type Subtype
- func (st *Subtype) Get(key string) Reading
- func (st *Subtype) GetInt64(key string) (int64, error)
- func (st *Subtype) GetString(key string) (string, error)
- func (st *Subtype) Has(key string) bool
- func (st *Subtype) UnmarshalJSON(data []byte) error
- func (st *Subtype) UnmarshalYAML(node *yaml.Node) error
- func (st *Subtype) Validate() error
- type SubtypeBuilder
- func (b *SubtypeBuilder) Build() Subtype
- func (b *SubtypeBuilder) Set(key string, value Reading) *SubtypeBuilder
- func (b *SubtypeBuilder) SetBool(key string, value bool) *SubtypeBuilder
- func (b *SubtypeBuilder) SetFloat64(key string, value float64) *SubtypeBuilder
- func (b *SubtypeBuilder) SetInt(key string, value int) *SubtypeBuilder
- func (b *SubtypeBuilder) SetInt64(key string, value int64) *SubtypeBuilder
- func (b *SubtypeBuilder) SetString(key, value string) *SubtypeBuilder
- func (b *SubtypeBuilder) SetUint(key string, value uint) *SubtypeBuilder
- func (b *SubtypeBuilder) SetUint64(key string, value uint64) *SubtypeBuilder
- func (b *SubtypeBuilder) WithContext(key, value string) *SubtypeBuilder
- func (b *SubtypeBuilder) WithContextMap(ctx map[string]string) *SubtypeBuilder
- func (b *SubtypeBuilder) WithItem(item ItemEntry) *SubtypeBuilder
- func (b *SubtypeBuilder) WithItems(items []ItemEntry) *SubtypeBuilder
- type Type
Constants ¶
const ( // Kubernetes measurement keys KeyVersion = "version" // GPU measurement keys KeyGPUDriver = "driver" KeyGPUModel = "model" KeyGPUCount = "gpu-count" // GPU hardware detection keys (NFD-based, no driver required) KeyGPUPresent = "gpu-present" KeyGPUDriverLoaded = "driver-loaded" KeyGPUDetectionSource = "detection-source" )
Measurement keys used by external packages.
const PathGPUNodesLabel = "NodeTopology.gpu-nodes.label"
PathGPUNodesLabel is the node-set constraint form from issue #1755. Unlike a scalar path it does not name a reading any producer emits: the evaluator in pkg/constraints synthesizes the GPU-node set from the snapshot's NodeTopology.label readings and quantifies a label predicate over it.
It lives here rather than in pkg/constraints so the catalog can accept it without an import cycle (pkg/constraints imports pkg/recipe, which imports this package).
Variables ¶
var Types = []Type{ TypeK8s, TypeGPU, TypeOS, TypeSystemD, TypeNodeTopology, TypeNetworkTopology, }
Types is the list of all supported measurement types.
Functions ¶
func FilterOut ¶
FilterOut returns a new map with keys filtered out based on the provided patterns. Supports wildcard patterns:
- "prefix*" matches keys starting with "prefix"
- "*suffix" matches keys ending with "suffix"
- "*contains*" matches keys containing "contains"
- "exact" matches keys exactly
func ValidatePath ¶ added in v0.19.0
ValidatePath reports whether name is addressable: whether a supported snapshot producer can emit it and a path form can name it.
It says nothing about a particular snapshot. A path this accepts may still yield ErrCodeNotFound from Path.Extract when the reading is genuinely absent — the designed graceful-exclusion signal, deliberately left intact.
It is the load-time typo gate for recipe constraint names (#1783): a grammatically valid but non-addressable path such as "K8s.server.versionn" would otherwise parse cleanly, evaluate as ErrCodeNotFound, and be treated by the resolver as "reading absent from this snapshot — exclude gracefully", silently skipping the gate the constraint exists to enforce.
Returns nil when the path is addressable. Returns ErrCodeInvalidRequest for an authoring error, and ErrCodeInternal when a known measurement Type has no catalog entry — a catalog gap is an internal defect and must never read as "path accepted".
Types ¶
type AllowedScalar ¶
type AllowedScalar interface {
~int | ~int8 | ~int16 | ~int32 | ~int64 |
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 |
~float32 | ~float64 |
~bool |
~string
}
AllowedScalar is a constraint (compile-time) for what we allow as readings.
type ItemEntry ¶ added in v0.16.0
type ItemEntry struct {
Context map[string]string `json:"context,omitempty" yaml:"context,omitempty"`
Data map[string]Reading `json:"data,omitempty" yaml:"data,omitempty"`
}
ItemEntry is one element of a Subtype.Items list. It mirrors Subtype's scalar-value contract: Data holds Reading scalars; Context holds string-typed descriptive metadata. ItemEntry intentionally does NOT support nested Items — the scalar-only Reading model is preserved.
func (*ItemEntry) UnmarshalJSON ¶ added in v0.16.0
UnmarshalJSON custom unmarshaler for ItemEntry to handle Reading interface inside Data, mirroring Subtype's behavior.
type Measurement ¶
type Measurement struct {
Type Type `json:"type" yaml:"type"`
Subtypes []Subtype `json:"subtypes,omitempty" yaml:"subtypes,omitempty"`
}
Measurement represents collected data of a specific type with multiple subtypes. Each measurement contains a category (Type) and one or more Subtypes with their associated data.
func (*Measurement) GetSubtype ¶
func (m *Measurement) GetSubtype(name string) *Subtype
GetSubtype retrieves a subtype by name, returning nil if not found.
func (*Measurement) Merge ¶
func (m *Measurement) Merge(other *Measurement) error
Merge combines two measurements by adding or updating subtypes from other into m. If a subtype exists in both measurements, the data is merged (other's values take precedence). Returns an error if the measurements have different types.
func (*Measurement) Validate ¶
func (m *Measurement) Validate() error
Validate checks if the measurement is properly formed.
type MeasurementBuilder ¶
type MeasurementBuilder struct {
// contains filtered or unexported fields
}
MeasurementBuilder provides a fluent API for building Measurement instances.
func NewMeasurement ¶
func NewMeasurement(t Type) *MeasurementBuilder
NewMeasurement creates a new MeasurementBuilder with the given type.
func (*MeasurementBuilder) Build ¶
func (b *MeasurementBuilder) Build() *Measurement
Build constructs and returns the Measurement.
func (*MeasurementBuilder) WithSubtype ¶
func (b *MeasurementBuilder) WithSubtype(st Subtype) *MeasurementBuilder
WithSubtype adds a subtype to the measurement.
func (*MeasurementBuilder) WithSubtypeBuilder ¶
func (b *MeasurementBuilder) WithSubtypeBuilder(builder *SubtypeBuilder) *MeasurementBuilder
WithSubtypeBuilder adds a subtype using a SubtypeBuilder.
type Path ¶ added in v0.19.0
Path represents a parsed fully qualified constraint path.
Without item selector: "{Type}.{Subtype}.{Key}"
Example: "K8s.server.version" -> Type="K8s", Subtype="server", Key="version"
With item selector: "{Type}.{Subtype}[<selector>].{Key}"
Index form: "NetworkTopology.pfs[0].rail" Predicate form: "NetworkTopology.pfs[rail=3].pciAddress"
The selector targets an entry in Subtype.Items; Key is then resolved against that ItemEntry's Data (preferred) or Context. Paths without a selector keep the legacy behavior of looking up Key in Subtype.Data.
The key portion may contain dots (e.g., "/proc/sys/kernel/osrelease").
The selector is deliberately unexported: the selector grammar is an implementation detail of the path syntax, not a public shape.
func (*Path) Extract ¶ added in v0.19.0
func (p *Path) Extract(ms []*Measurement) (string, error)
Extract extracts the value at this path from a set of measurements. Returns the value as a string, or an error if the path doesn't exist.
Callers hold a *snapshotter.Snapshot pass snap.Measurements; the nil-snapshot guard belongs at that call site, not here — a nil measurement slice is a legitimate "nothing collected" input, distinct from "no snapshot supplied".
type Reading ¶
type Reading interface {
Any() any
String() string
json.Marshaler
json.Unmarshaler
yaml.Marshaler
yaml.Unmarshaler
// contains filtered or unexported methods
}
Reading is a *runtime* interface (so it can be stored in a map with mixed types).
type Scalar ¶
type Scalar[T AllowedScalar] struct { V T }
Scalar wraps an allowed scalar type. This is how we keep compile-time constraints while still using a runtime interface.
func (Scalar[T]) MarshalJSON ¶
MarshalJSON makes the JSON value be the underlying scalar (not an object wrapper).
func (Scalar[T]) MarshalYAML ¶
MarshalYAML makes the YAML value be the underlying scalar (not an object wrapper).
func (*Scalar[T]) UnmarshalJSON ¶
UnmarshalJSON unmarshals a JSON value into the underlying scalar.
type Subtype ¶
type Subtype struct {
Name string `json:"subtype,omitempty" yaml:"subtype,omitempty"`
Data map[string]Reading `json:"data,omitempty" yaml:"data,omitempty"`
Context map[string]string `json:"context,omitempty" yaml:"context,omitempty"`
Items []ItemEntry `json:"items,omitempty" yaml:"items,omitempty"`
}
Subtype represents a specific subcategory of measurement with associated data. Data contains the actual measurements as key-value pairs. Context provides additional metadata about the measurement environment. Items holds an ordered list of structured records (used when a subtype carries a homogeneous array such as a list of PFs); each entry follows the same scalar-only Reading discipline as Data. Data and Items are independent and may both be populated.
func (*Subtype) GetInt64 ¶
GetInt64 attempts to retrieve an int64 value, returning an error if not found or wrong type. Accepts int, int64, and float64 (JSON decoders deliver integers as float64); a float64 must be representable as an int64 without truncation.
func (*Subtype) GetString ¶
GetString attempts to retrieve a string value, returning an error if not found or wrong type.
func (*Subtype) UnmarshalJSON ¶
UnmarshalJSON custom unmarshaler for Subtype to handle Reading interface
func (*Subtype) UnmarshalYAML ¶
UnmarshalYAML custom unmarshaler for Subtype to handle Reading interface
func (*Subtype) Validate ¶
Validate checks if the subtype is properly formed. A Subtype must have a non-empty Name and carry at least one Data entry or one Items entry; Items alone is sufficient for subtypes whose payload is a list of structured records (e.g. a `pfs` subtype holding per-PF entries). The non-empty-Name invariant honors the OpenAPI `required: [subtype]` schema — the `omitempty` JSON/YAML tag elides the field on the wire only when it is intentionally being left out, never on a Validate'd value.
type SubtypeBuilder ¶
type SubtypeBuilder struct {
// contains filtered or unexported fields
}
SubtypeBuilder provides a fluent API for building Subtype instances.
func NewSubtypeBuilder ¶
func NewSubtypeBuilder(name string) *SubtypeBuilder
NewSubtypeBuilder creates a new SubtypeBuilder with the given name.
func (*SubtypeBuilder) Build ¶
func (b *SubtypeBuilder) Build() Subtype
Build constructs and returns the Subtype.
func (*SubtypeBuilder) Set ¶
func (b *SubtypeBuilder) Set(key string, value Reading) *SubtypeBuilder
Set adds or updates a key-value pair in the subtype data.
func (*SubtypeBuilder) SetBool ¶
func (b *SubtypeBuilder) SetBool(key string, value bool) *SubtypeBuilder
SetBool is a convenience method for adding bool values.
func (*SubtypeBuilder) SetFloat64 ¶
func (b *SubtypeBuilder) SetFloat64(key string, value float64) *SubtypeBuilder
SetFloat64 is a convenience method for adding float64 values.
func (*SubtypeBuilder) SetInt ¶
func (b *SubtypeBuilder) SetInt(key string, value int) *SubtypeBuilder
SetInt is a convenience method for adding int values.
func (*SubtypeBuilder) SetInt64 ¶
func (b *SubtypeBuilder) SetInt64(key string, value int64) *SubtypeBuilder
SetInt64 is a convenience method for adding int64 values.
func (*SubtypeBuilder) SetString ¶
func (b *SubtypeBuilder) SetString(key, value string) *SubtypeBuilder
SetString is a convenience method for adding string values.
func (*SubtypeBuilder) SetUint ¶
func (b *SubtypeBuilder) SetUint(key string, value uint) *SubtypeBuilder
SetUint is a convenience method for adding uint values.
func (*SubtypeBuilder) SetUint64 ¶
func (b *SubtypeBuilder) SetUint64(key string, value uint64) *SubtypeBuilder
SetUint64 is a convenience method for adding uint64 values.
func (*SubtypeBuilder) WithContext ¶ added in v0.16.0
func (b *SubtypeBuilder) WithContext(key, value string) *SubtypeBuilder
WithContext sets a single key/value entry in the subtype Context.
func (*SubtypeBuilder) WithContextMap ¶ added in v0.16.0
func (b *SubtypeBuilder) WithContextMap(ctx map[string]string) *SubtypeBuilder
WithContextMap merges the provided map into the subtype Context.
func (*SubtypeBuilder) WithItem ¶ added in v0.16.0
func (b *SubtypeBuilder) WithItem(item ItemEntry) *SubtypeBuilder
WithItem appends a single ItemEntry to the subtype Items list.
func (*SubtypeBuilder) WithItems ¶ added in v0.16.0
func (b *SubtypeBuilder) WithItems(items []ItemEntry) *SubtypeBuilder
WithItems appends a slice of ItemEntry to the subtype Items list.
type Type ¶
type Type string
Type represents the category of a measurement (e.g., Kubernetes, GPU, OS, SystemD).
Cardinality: most Types appear at most once per snapshot — one K8s measurement, one GPU measurement, etc. TypeNetworkTopology is the planned multi-instance exception (one Measurement per discovered hardware group); today we emit at most one to keep the existing find-first-by-Type consumers (constraints, recipe validation, diff indexing, fingerprint) working unchanged. Lifting that limit is a deliberate future step.