From 8a4f4335fdd494b6b9951519f7de156b822a51cf Mon Sep 17 00:00:00 2001 From: Thanatat Tamtan Date: Thu, 11 Jun 2026 16:48:18 +0700 Subject: [PATCH] Add waf.limitMetrics read API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirrors waf.metrics at the (limit, result) grain over ratelimit_usages, so the console can chart the limited share — limited / (allowed + limited) — which is how a shadow-mode limit is sized before enforcing. Co-Authored-By: Claude Opus 4.8 (1M context) --- client/waf.go | 9 +++++++++ waf.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/client/waf.go b/client/waf.go index 3804d65..1293dfb 100644 --- a/client/waf.go +++ b/client/waf.go @@ -49,3 +49,12 @@ func (c wafClient) Metrics(ctx context.Context, m *api.WAFMetrics) (*api.WAFMetr } return &res, nil } + +func (c wafClient) LimitMetrics(ctx context.Context, m *api.WAFLimitMetrics) (*api.WAFLimitMetricsResult, error) { + var res api.WAFLimitMetricsResult + err := c.inv.invoke(ctx, "waf.limitMetrics", m, &res) + if err != nil { + return nil, err + } + return &res, nil +} diff --git a/waf.go b/waf.go index 8bd8179..9db031e 100644 --- a/waf.go +++ b/waf.go @@ -28,6 +28,7 @@ type WAF interface { Set(ctx context.Context, m *WAFSet) (*Empty, error) Delete(ctx context.Context, m *WAFDelete) (*Empty, error) Metrics(ctx context.Context, m *WAFMetrics) (*WAFMetricsResult, error) + LimitMetrics(ctx context.Context, m *WAFLimitMetrics) (*WAFLimitMetricsResult, error) } // WAFRule mirrors parapet's waf.Rule. Expression is a CEL expression returning @@ -361,3 +362,39 @@ type WAFMetricsSeries struct { Total float64 `json:"total" yaml:"total"` // this series' sum over the range Points [][2]float64 `json:"points" yaml:"points"` // [unixSeconds, count], time-ordered } + +// WAFLimitMetrics reads a zone's rate-limit decision counts +// (parapet_ratelimit_total, collected per minute into the apiserver) over a +// time range. Series come per (limit, result) so the caller can chart the +// limited share — limited / (allowed + limited) — which is how a shadow-mode +// limit is sized before it is enforced. +type WAFLimitMetrics struct { + Project string `json:"project" yaml:"project"` + Location string `json:"location" yaml:"location"` + TimeRange WAFMetricsTimeRange `json:"timeRange" yaml:"timeRange"` +} + +func (m *WAFLimitMetrics) Valid() error { + v := validator.New() + + v.Must(m.Project != "", "project required") + v.Must(m.Location != "", "location required") + v.Must(validWAFMetricsTimeRange[m.TimeRange], "timeRange invalid") + + return WrapValidate(v) +} + +// WAFLimitMetricsResult mirrors WAFMetricsResult at the (limit, result) grain. +// LimitID is the short, project-local id, matching WAF.Get so the caller can +// join a series to its limit. +type WAFLimitMetricsResult struct { + Series []*WAFLimitMetricsSeries `json:"series" yaml:"series"` + Total float64 `json:"total" yaml:"total"` +} + +type WAFLimitMetricsSeries struct { + LimitID string `json:"limitId" yaml:"limitId"` + Result string `json:"result" yaml:"result"` // allowed|limited + Total float64 `json:"total" yaml:"total"` // this series' sum over the range + Points [][2]float64 `json:"points" yaml:"points"` // [unixSeconds, count], time-ordered +}