Documentation
¶
Index ¶
- Constants
- Variables
- func AlignDown(t time.Time, step time.Duration) time.Time
- func DefaultStep(since, until time.Time, src Source) time.Duration
- func IncludeTokens(k SnapshotKind) []string
- func NewSeriesProvider(opts SeriesOptions) serverapiserver.StorageProvider
- func NewSnapshotObject(kind SnapshotKind) runtime.Object
- func NewSnapshotProvider(opts SnapshotOptions) serverapiserver.StorageProvider
- func ParseDuration(s string) (time.Duration, error)
- func ParseInstant(s string, now time.Time) (time.Time, error)
- func ParseStep(raw string, src Source) (time.Duration, error)
- func RoundLatencyMillis(ms float64) float64
- func SeriesCacheKey(req *SeriesRequest) string
- func SnapshotCacheKey(req *SnapshotRequest) string
- func SourceNames() []string
- func StatusCode(err error) int
- func ToStatusError(verb string, err error) error
- func UnitsFor(recipes ...Recipe) map[string]string
- type Cache
- type Error
- func BadRequest(format string, a ...any) *Error
- func Internal(cause error) *Error
- func NotFound(gr schema.GroupResource, name string) *Error
- func NotImplemented(flags ...string) *Error
- func TooManyRequests() *Error
- func Unavailable(cause error) *Error
- func Unprocessable(format string, a ...any) *Error
- type ErrorKind
- type Measures
- type MetricsMap
- type OwnerChecker
- type Recipe
- type RecipeResolver
- type Scope
- type ScopeKind
- type SeriesConnecter
- func (c *SeriesConnecter) Connect(_ context.Context, name string, _ runtime.Object, ...) (http.Handler, error)
- func (c *SeriesConnecter) ConnectMethods() []string
- func (c *SeriesConnecter) Destroy()
- func (c *SeriesConnecter) GetSingularName() string
- func (c *SeriesConnecter) NamespaceScoped() bool
- func (c *SeriesConnecter) New() runtime.Object
- func (c *SeriesConnecter) NewConnectOptions() (runtime.Object, bool, string)
- type SeriesOptions
- type SeriesQuery
- type SeriesReader
- type SeriesRequest
- type SnapshotConnecter
- func (c *SnapshotConnecter) Connect(_ context.Context, name string, _ runtime.Object, ...) (http.Handler, error)
- func (c *SnapshotConnecter) ConnectMethods() []string
- func (c *SnapshotConnecter) Destroy()
- func (c *SnapshotConnecter) GetSingularName() string
- func (c *SnapshotConnecter) GroupVersionKind(schema.GroupVersion) schema.GroupVersionKind
- func (c *SnapshotConnecter) NamespaceScoped() bool
- func (c *SnapshotConnecter) New() runtime.Object
- func (c *SnapshotConnecter) NewConnectOptions() (runtime.Object, bool, string)
- type SnapshotKind
- type SnapshotOptions
- type SnapshotQuery
- type SnapshotReader
- type SnapshotRequest
- type Source
- type Window
Constants ¶
const ( // DefaultWindow is the window both surfaces read when the caller gives // none, so a KPI tile and the chart under it cover the same range. DefaultWindow = time.Hour // MaxBuckets bounds the time axis of one series read. MaxBuckets = 1500 // MaxSeries bounds the cardinality axis. Rows past it set truncated. MaxSeries = 50 // MaxPoints bounds buckets x series x measures for one response. MaxPoints = 20000 // MaxLookback bounds how far back a raw-log read may start. MaxLookback = 31 * 24 * time.Hour // MaxRawGroupByWindow bounds a grouped read over raw logs, which scans // rows rather than buckets. MaxRawGroupByWindow = 24 * time.Hour )
Caps shared by the snapshot connecters and metrics/<name>/series. A request over a cap is a 400 that names the factors, so the caller can lower top, widen step, or ask for fewer measures.
const ( // DefaultCacheTTL is how long a metrics response stays cached. It matches // the granularity of the live rollup, so a poll that arrives before the // next bucket closes cannot see anything new anyway. DefaultCacheTTL = time.Minute )
Variables ¶
var ( // SourceOTELLogs is the raw access-log table. SourceOTELLogs = Source{Name: "otel_logs", Granularity: time.Second, MaxWindow: MaxLookback, Raw: true} // SourceHTTP1m is the one-minute rollup. SourceHTTP1m = Source{Name: "http_1m", Granularity: time.Minute, MaxWindow: 7 * 24 * time.Hour} // SourceHTTP1h is the one-hour rollup. SourceHTTP1h = Source{Name: "http_1h", Granularity: time.Hour, MaxWindow: 400 * 24 * time.Hour} // SourceEnvoy1m is the one-minute Envoy stats rollup. SourceEnvoy1m = Source{Name: "envoy_1m", Granularity: time.Minute, MaxWindow: 30 * 24 * time.Hour} // SourceRelay1m is the one-minute tunnel datapath rollup, written by the // relay. SourceRelay1m = Source{Name: "relay_1m", Granularity: time.Minute, MaxWindow: 30 * 24 * time.Hour} )
The Phase 1 sources. A recipe has exactly one source, resolved once on write, and is never routed per request.
var ValidScopeKinds = []ScopeKind{ ScopeProject, ScopeGateway, ScopeHTTPRoute, ScopeProxy, ScopeService, ScopeVPCNetwork, ScopeTunnel, }
ValidScopeKinds is the list a 400 message prints and MetricSource publishes in status.scopes, so a client does not discover it by trial.
Functions ¶
func AlignDown ¶
AlignDown truncates t down to a whole multiple of step, in UTC. Zero or a negative step leaves t alone.
func DefaultStep ¶
DefaultStep is the source granularity, widened in whole granularity steps until the window fits under the bucket cap. A caller that gives no step gets the finest chart the caps allow rather than a 400.
func IncludeTokens ¶
func IncludeTokens(k SnapshotKind) []string
IncludeTokens lists the legal include tokens for a snapshot kind.
func NewSeriesProvider ¶
func NewSeriesProvider(opts SeriesOptions) serverapiserver.StorageProvider
NewSeriesProvider adapts the series connecter to the apiserver builder.
func NewSnapshotObject ¶
func NewSnapshotObject(kind SnapshotKind) runtime.Object
NewSnapshotObject returns an empty object of the given snapshot kind.
func NewSnapshotProvider ¶
func NewSnapshotProvider(opts SnapshotOptions) serverapiserver.StorageProvider
NewSnapshotProvider adapts a snapshot connecter to the apiserver builder.
func ParseDuration ¶
ParseDuration parses a Go duration, also accepting a whole-day suffix.
func ParseInstant ¶
ParseInstant parses an RFC3339 instant or a relative duration such as -6h. A relative value is resolved against now.
func ParseStep ¶
ParseStep parses an explicit step and rounds it up to the source granularity. An empty step returns zero, which means the caller must pick DefaultStep.
func RoundLatencyMillis ¶
RoundLatencyMillis is the one copy of the latency rounding rule. A percentile is interpolated from a histogram, so its fractional part is noise; both typed surfaces report it as whole milliseconds, and they must agree. A value that is not finite collapses to zero, which is what an empty bucket means.
func SeriesCacheKey ¶
func SeriesCacheKey(req *SeriesRequest) string
SeriesCacheKey builds the cache key for a parsed series read. The window is aligned to the applied step, and the step itself is part of the key, so two reads at different steps never share an entry.
func SnapshotCacheKey ¶
func SnapshotCacheKey(req *SnapshotRequest) string
SnapshotCacheKey builds the cache key for a parsed snapshot read. A snapshot has no step, so the window aligns to the source granularity.
func SourceNames ¶
func SourceNames() []string
SourceNames lists the known sources, for an error message.
func StatusCode ¶
StatusCode reports the status code err maps to. It is what a handler logs next to the request id, and what a test asserts on.
func ToStatusError ¶
ToStatusError maps err to the status the client sees. An unclassified error becomes a generic internal error, so no backend text can leak by accident. A cancelled or timed-out read is a 503, not a 500: the backend did not answer.
Types ¶
type Cache ¶
type Cache struct {
// contains filtered or unexported fields
}
Cache is the in-memory response cache the connecters share. The key aligns the window to the step before it is built, so two polls a few seconds apart produce the same key and the second one is served without a second scan.
Entries expire a TTL after they are written and the cache is bounded by entry count.
func NewCache ¶
NewCache returns a cache with the given TTL. A TTL of zero or less takes DefaultCacheTTL.
func (*Cache) Get ¶
Get returns a deep copy of the cached response, so a caller that mutates the result cannot corrupt the entry.
type Error ¶
type Error struct {
// Kind selects the status code.
Kind ErrorKind
// Msg is the client-visible message.
Msg string
// Resource and Name name the missing object on a KindNotFound.
Resource schema.GroupResource
Name string
// contains filtered or unexported fields
}
Error is a classified metrics read failure. Msg is safe to show a client; the wrapped error is not, and is only for the server log.
func BadRequest ¶
BadRequest returns a 400 whose message must list the valid values.
func NotFound ¶
func NotFound(gr schema.GroupResource, name string) *Error
NotFound returns a 404 for a missing owner object or recipe. A series read over an owner that does not exist is a 404, not a 200 full of zeros.
func NotImplemented ¶
NotImplemented returns a 501 naming the flags the build is missing, so the failure reads as a configuration message and not as a missing path.
func TooManyRequests ¶
func TooManyRequests() *Error
TooManyRequests returns a 429 for the concurrency cap.
func Unavailable ¶
Unavailable returns a 503. The cause is logged, never returned: a backend error message states the table layout and the tenancy predicate.
func Unprocessable ¶
Unprocessable returns a 422 with the mapped compiler message.
type ErrorKind ¶
type ErrorKind int
ErrorKind classifies a metrics read failure. It is the whole client-visible error vocabulary: raw ClickHouse text and compiled SQL never reach a client, they go to the server log with the request id.
const ( // KindBadRequest is a guardrail violation, an unknown groupBy, an unknown // include token, or an unknown scopeKind. The message lists valid values. KindBadRequest ErrorKind = iota // KindNotFound is a missing owner object or a missing recipe. KindNotFound // KindUnprocessable is a recipe or fragment that does not compile. KindUnprocessable // KindTooManyRequests is the per-apiserver concurrency cap. KindTooManyRequests KindUnavailable // KindNotImplemented is a build with no metrics backend configured. KindNotImplemented // KindInternal is a server bug. Its detail never reaches the client. KindInternal )
type Measures ¶
type Measures = metricsv1alpha1.Measures
Measures is the wire map every measure set uses, aliased so a read model does not import the API group for one type.
type MetricsMap ¶
type MetricsMap = metricsv1alpha1.MetricsMap
MetricsMap is the per-recipe measure map, keyed by Metric name.
type OwnerChecker ¶
type OwnerChecker interface {
// OwnerExists reports whether the object the scope names is in the store.
OwnerExists(ctx context.Context, scope Scope) (bool, error)
}
OwnerChecker reports whether the scoped owner object exists. A read over an owner that does not exist is a 404 before any backend read, rather than a 200 full of zeros.
type Recipe ¶
type Recipe struct {
// Name is the recipe name.
Name string
// Source is the resolved source.
Source Source
// Keys are the valid groupBy values, from MetricSource.
Keys []string
// Measures are the recipe's output columns.
Measures []string
// DefaultColumns are the measures a client shows first. The first entry is
// the default orderBy.
DefaultColumns []string
// Units maps a measure name to its display unit, echoed in a response.
Units map[string]string
// Compiled reports whether the recipe still compiles. A recipe that does
// not is a 422 from series.
Compiled bool
// CompileMessage is the sanitized reason a recipe does not compile.
CompileMessage string
}
Recipe is the compiled fact set the validator needs about a Metric: the resolved source, the groupable keys, the measure names, and the columns a client shows first. The deployment that owns the catalog fills it in.
type RecipeResolver ¶
type RecipeResolver interface {
// Recipe returns the compiled recipe named name.
Recipe(ctx context.Context, name string) (Recipe, error)
}
RecipeResolver resolves a Metric name to the compiled facts the validator needs. An unknown name must return NotFound.
type Scope ¶
type Scope struct {
// Kind is the owner kind. Project means the whole project.
Kind ScopeKind
// Name is the owner name. It is empty for a Project scope.
Name string
// Listener narrows a Gateway scope to one listener.
Listener string
}
Scope is the server-enforced read scope. Every field comes from the request path or from the scope parameters, never from a client-supplied predicate.
type ScopeKind ¶
type ScopeKind string
ScopeKind is an owner kind a scope parameter accepts. It takes an object kind, not a resource name.
const ( // ScopeProject is the whole project. It takes no scope name. ScopeProject ScopeKind = "Project" // ScopeGateway scopes to one Gateway. ScopeGateway ScopeKind = "Gateway" // ScopeHTTPRoute scopes to one HTTPRoute. ScopeHTTPRoute ScopeKind = "HTTPRoute" // ScopeProxy scopes to one Proxy. ScopeProxy ScopeKind = "Proxy" // ScopeService scopes to one compute Service. ScopeService ScopeKind = "Service" // ScopeVPCNetwork scopes to one VPCNetwork. ScopeVPCNetwork ScopeKind = "VPCNetwork" // ScopeTunnel scopes to one Tunnel, which is one agent connection to a // relay. ScopeTunnel ScopeKind = "Tunnel" )
type SeriesConnecter ¶
type SeriesConnecter struct {
// contains filtered or unexported fields
}
SeriesConnecter serves metrics/<name>/series. The path element is the recipe name; the scope is a parameter, which is how a chart is built.
func NewSeriesConnecter ¶
func NewSeriesConnecter(opts SeriesOptions) *SeriesConnecter
NewSeriesConnecter returns the series connect storage.
func (*SeriesConnecter) Connect ¶
func (c *SeriesConnecter) Connect(_ context.Context, name string, _ runtime.Object, responder registryrest.Responder) (http.Handler, error)
Connect captures the recipe name from the path and returns the handler. The query string is parsed in ServeHTTP, where the request is available.
func (*SeriesConnecter) ConnectMethods ¶
func (c *SeriesConnecter) ConnectMethods() []string
func (*SeriesConnecter) Destroy ¶
func (c *SeriesConnecter) Destroy()
func (*SeriesConnecter) GetSingularName ¶
func (c *SeriesConnecter) GetSingularName() string
func (*SeriesConnecter) NamespaceScoped ¶
func (c *SeriesConnecter) NamespaceScoped() bool
func (*SeriesConnecter) New ¶
func (c *SeriesConnecter) New() runtime.Object
New returns the response kind, so the apiserver encodes the body to the right group version under content negotiation.
func (*SeriesConnecter) NewConnectOptions ¶
func (c *SeriesConnecter) NewConnectOptions() (runtime.Object, bool, string)
NewConnectOptions returns no typed options. The builder installs connect handlers with the metav1-only ParameterCodec, so a registered options object cannot decode; the handler reads the query string itself.
type SeriesOptions ¶
type SeriesOptions struct {
// Reader runs the query. A nil Reader makes every read a 501 that names
// the missing configuration, so the path stays in discovery.
Reader SeriesReader
// Recipes resolves the recipe named by the path element.
Recipes RecipeResolver
// Owners is optional. When it is set, a scoped read over a missing owner
// is a 404 before the backend is touched.
Owners OwnerChecker
// Cache is optional. When it is set, responses are cached under a
// step-aligned key and the response advertises its max-age.
Cache *Cache
// GroupResource names the resource in a NotFound status.
GroupResource schema.GroupResource
// MissingFlags are named in the 501 a build with no backend returns.
MissingFlags []string
// Now anchors the default window. It defaults to time.Now.
Now func() time.Time
// Log receives the unsanitized failure next to the request id. It
// defaults to the process default logger.
Log *slog.Logger
}
SeriesOptions configures the metrics/<name>/series connecter.
type SeriesQuery ¶
type SeriesQuery struct {
ScopeKind string `json:"scopeKind,omitempty"`
ScopeName string `json:"scopeName,omitempty"`
ScopeListener string `json:"scopeListener,omitempty"`
GroupBy string `json:"groupBy,omitempty"`
OrderBy string `json:"orderBy,omitempty"`
Top int `json:"top,omitempty"`
Since string `json:"since,omitempty"`
Until string `json:"until,omitempty"`
Window string `json:"window,omitempty"`
Step string `json:"step,omitempty"`
}
SeriesQuery is the optional JSON request body. Every field mirrors a query parameter; the query string wins when both carry the same field.
type SeriesReader ¶
type SeriesReader interface {
// ReadSeries returns the series set for req. A backend that cannot be
// reached must return Unavailable, so the client sees 503 and not 500.
ReadSeries(ctx context.Context, req *SeriesRequest) (*metricsv1alpha1.MetricSeriesSet, error)
}
SeriesReader runs a validated series query against the metrics backend. It is the only seam between the shared handler and the deployment that owns the ClickHouse read model.
type SeriesRequest ¶
type SeriesRequest struct {
// Metric is the recipe name.
Metric string
// Recipe is the compiled recipe the request resolved to.
Recipe Recipe
// Scope is the server-enforced read scope.
Scope Scope
// GroupBy is one role: key field of the source, or empty.
GroupBy string
// OrderBy is the measure the top-N ranks by.
OrderBy string
// Top bounds the series count.
Top int
// Since and Until are the half-open [since, until) window bounds.
Since time.Time
Until time.Time
// Step is the applied bucket width, rounded up to the source granularity.
Step time.Duration
// Buckets is how many buckets the window yields at Step.
Buckets int
// DataUpTo is the end of the last complete bucket.
DataUpTo time.Time
}
SeriesRequest is a parsed, validated series query. The read model builds its statement from this and from nothing else in the request.
func ParseSeriesRequest ¶
ParseSeriesRequest validates a series query against the recipe it names. The recipe is resolved by the caller, because the catalog lives in the deployment that owns the read model.
type SnapshotConnecter ¶
type SnapshotConnecter struct {
// contains filtered or unexported fields
}
SnapshotConnecter serves one <owner>/<name>/metrics path. One type serves every owner kind; Kind selects the response kind and the include tokens.
func NewSnapshotConnecter ¶
func NewSnapshotConnecter(opts SnapshotOptions) *SnapshotConnecter
NewSnapshotConnecter returns the snapshot connect storage for one owner kind.
func (*SnapshotConnecter) Connect ¶
func (c *SnapshotConnecter) Connect(_ context.Context, name string, _ runtime.Object, responder registryrest.Responder) (http.Handler, error)
Connect captures the owner name from the path and returns the handler.
func (*SnapshotConnecter) ConnectMethods ¶
func (c *SnapshotConnecter) ConnectMethods() []string
func (*SnapshotConnecter) Destroy ¶
func (c *SnapshotConnecter) Destroy()
func (*SnapshotConnecter) GetSingularName ¶
func (c *SnapshotConnecter) GetSingularName() string
func (*SnapshotConnecter) GroupVersionKind ¶
func (c *SnapshotConnecter) GroupVersionKind(schema.GroupVersion) schema.GroupVersionKind
GroupVersionKind states the kind the connecter answers with. A snapshot mounts on its owner kind, which lives in another group, so the apiserver cannot read the response kind off the mount point: without this the mount fails to install, and with it the response encodes in the metrics group.
func (*SnapshotConnecter) NamespaceScoped ¶
func (c *SnapshotConnecter) NamespaceScoped() bool
func (*SnapshotConnecter) New ¶
func (c *SnapshotConnecter) New() runtime.Object
func (*SnapshotConnecter) NewConnectOptions ¶
func (c *SnapshotConnecter) NewConnectOptions() (runtime.Object, bool, string)
NewConnectOptions returns no typed options; see SeriesConnecter.
type SnapshotKind ¶
type SnapshotKind string
SnapshotKind is one of the per-owner snapshot kinds.
const ( // SnapshotGateway is returned by gateways/<name>/metrics. SnapshotGateway SnapshotKind = "GatewayMetrics" // SnapshotHTTPRoute is returned by httproutes/<name>/metrics. SnapshotHTTPRoute SnapshotKind = "HTTPRouteMetrics" // SnapshotProxy is returned by proxies/<name>/metrics. SnapshotProxy SnapshotKind = "ProxyMetrics" // SnapshotService is returned by services/<name>/metrics. SnapshotService SnapshotKind = "ServiceMetrics" // SnapshotVPCNetwork is returned by vpcnetworks/<name>/metrics. SnapshotVPCNetwork SnapshotKind = "VPCNetworkMetrics" // SnapshotTunnel is returned by tunnels/<name>/metrics. SnapshotTunnel SnapshotKind = "TunnelMetrics" )
type SnapshotOptions ¶
type SnapshotOptions struct {
// Kind selects the response kind and the legal include tokens.
Kind SnapshotKind
// Source is the table the snapshot reads, which sets the window cap and
// the cache alignment.
Source Source
// Reader builds the snapshot. A nil Reader makes every read a 501.
Reader SnapshotReader
// Decorate is optional. When it is set, it runs on a freshly read snapshot
// before the snapshot is cached, so a decoration that comes from outside
// the read model — such as the replica gauges off the owner status — is
// carried by the cached object too and is not recomputed per cache hit. An
// error from it fails the read through the same mapper as a backend error.
Decorate func(ctx context.Context, req *SnapshotRequest, obj runtime.Object) error
// Owners is optional. When it is set, a read over a missing owner is a 404
// before the backend is touched.
Owners OwnerChecker
// Cache is optional.
Cache *Cache
// GroupResource names the resource in a NotFound status.
GroupResource schema.GroupResource
// MissingFlags are named in the 501 a build with no backend returns.
MissingFlags []string
// Now anchors the default window. It defaults to time.Now.
Now func() time.Time
// Log receives the unsanitized failure next to the request id.
Log *slog.Logger
}
SnapshotOptions configures one <owner>/<name>/metrics connecter.
type SnapshotQuery ¶
type SnapshotQuery struct {
ScopeListener string `json:"scopeListener,omitempty"`
Include []string `json:"include,omitempty"`
OrderBy string `json:"orderBy,omitempty"`
Top int `json:"top,omitempty"`
Since string `json:"since,omitempty"`
Until string `json:"until,omitempty"`
Window string `json:"window,omitempty"`
Metrics []string `json:"metric,omitempty"`
}
SnapshotQuery is the optional JSON request body of a snapshot read.
type SnapshotReader ¶
type SnapshotReader interface {
// ReadSnapshot returns the snapshot for req.
ReadSnapshot(ctx context.Context, req *SnapshotRequest) (runtime.Object, error)
}
SnapshotReader builds one owner snapshot. The returned object must be the kind the connecter was built for.
type SnapshotRequest ¶
type SnapshotRequest struct {
// Kind is the snapshot kind the connecter serves.
Kind SnapshotKind
// Name is the owner name from the request path.
Name string
// Scope is the owner scope, derived from the path.
Scope Scope
// Since and Until are the half-open [since, until) window bounds.
Since time.Time
Until time.Time
// Window is Until minus Since.
Window time.Duration
// DataUpTo is the end of the last complete bucket.
DataUpTo time.Time
// Granularity is the source bucket width. A snapshot has no step of its
// own, so this is the unit the response cache aligns the window to and the
// max-age the response advertises.
Granularity time.Duration
// Include are the resolved nesting tokens, already expanded from all.
Include []string
// Top bounds each cut leaf list.
Top int
// OrderBy is the measure the leaf list ranks by.
OrderBy string
// Metrics restricts the recipes evaluated. An empty list means every
// managed recipe.
Metrics []string
}
SnapshotRequest is a parsed, validated snapshot read.
func ParseSnapshotRequest ¶
func ParseSnapshotRequest(q url.Values, kind SnapshotKind, name string, src Source, now time.Time) (*SnapshotRequest, error)
ParseSnapshotRequest validates a snapshot read. The owner scope comes from the request path, so no scope parameter is accepted.
func (*SnapshotRequest) Includes ¶
func (r *SnapshotRequest) Includes(token string) bool
Includes reports whether token was requested.
type Source ¶
type Source struct {
// Name is the source name a recipe writes in spec.source.
Name string
// Granularity is the bucket width. It is one second for raw logs, which
// have no bucket of their own.
Granularity time.Duration
// MaxWindow is the widest window this source serves.
MaxWindow time.Duration
// Raw is true for a source of individual log rows, which is bounded by the
// concurrency cap and by MaxRawGroupByWindow rather than by bucket count.
Raw bool
}
Source is the table a recipe reads. Granularity is both the bucket width and the minimum step: a smaller step is rounded up rather than rejected.
func LookupSource ¶
LookupSource resolves a source name.
type Window ¶
Window is the resolved read window plus the end of the last complete bucket.
func ParseWindow ¶
ParseWindow resolves since, until, and window against the source granularity. window is sugar for since=-<d> and is rejected together with since. until defaults to the end of the last complete bucket, which is what lets a client tell a partial trailing bucket from a drop in traffic.