diff --git a/internal/tiger/api/types.go b/internal/tiger/api/types.go index 9f649be6..d0acd0a2 100644 --- a/internal/tiger/api/types.go +++ b/internal/tiger/api/types.go @@ -43,6 +43,21 @@ const ( PITR ForkStrategy = "PITR" ) +// Defines values for MetricsSeriesRequestFn. +const ( + AVG MetricsSeriesRequestFn = "AVG" + COUNT MetricsSeriesRequestFn = "COUNT" + INCREASE MetricsSeriesRequestFn = "INCREASE" + LAST MetricsSeriesRequestFn = "LAST" + MAX MetricsSeriesRequestFn = "MAX" + MIN MetricsSeriesRequestFn = "MIN" + P50 MetricsSeriesRequestFn = "P50" + P90 MetricsSeriesRequestFn = "P90" + P99 MetricsSeriesRequestFn = "P99" + RATE MetricsSeriesRequestFn = "RATE" + SUM MetricsSeriesRequestFn = "SUM" +) + // Defines values for ReadReplicaSetStatus. const ( ReadReplicaSetStatusActive ReadReplicaSetStatus = "active" @@ -259,6 +274,13 @@ type MetricsSeriesRequest struct { // response are lowercased. Filters *[]MetricLabelFilter `json:"filters,omitempty"` + // Fn Aggregation function applied per bucket. Only applies to + // metrics-store-backed series (e.g. `pg_*`, `pgex_*`, `pgbouncer_*`, + // `timescaledb_*`); setting it on a legacy `timescale_cloud_*` + // series is rejected with INVALID_REQUEST. When omitted, the server + // picks a sensible default for the metric (typically LAST). + Fn *MetricsSeriesRequestFn `json:"fn,omitempty"` + // From Start of the time window (RFC3339; nanosecond precision accepted). From time.Time `json:"from"` @@ -269,6 +291,13 @@ type MetricsSeriesRequest struct { To time.Time `json:"to"` } +// MetricsSeriesRequestFn Aggregation function applied per bucket. Only applies to +// metrics-store-backed series (e.g. `pg_*`, `pgex_*`, `pgbouncer_*`, +// `timescaledb_*`); setting it on a legacy `timescale_cloud_*` +// series is rejected with INVALID_REQUEST. When omitted, the server +// picks a sensible default for the metric (typically LAST). +type MetricsSeriesRequestFn string + // Peering defines model for Peering. type Peering struct { ErrorMessage *string `json:"error_message,omitempty"` diff --git a/internal/tiger/cmd/service.go b/internal/tiger/cmd/service.go index 622c0f1c..bbcbb371 100644 --- a/internal/tiger/cmd/service.go +++ b/internal/tiger/cmd/service.go @@ -1822,6 +1822,7 @@ func buildServiceMetricsSeriesCmd() *cobra.Command { var role string var filters []string var bucketSeconds int + var fn string var output string cmd := &cobra.Command{ @@ -1890,6 +1891,10 @@ Examples: bs := bucketSeconds body.BucketSeconds = &bs } + if fn != "" { + f := api.MetricsSeriesRequestFn(strings.ToUpper(fn)) + body.Fn = &f + } if len(labelFilters) > 0 { body.Filters = &labelFilters } @@ -1920,6 +1925,7 @@ Examples: cmd.Flags().StringVar(&role, "role", "", "Filter to a specific instance role (PRIMARY or REPLICA)") cmd.Flags().StringSliceVar(&filters, "filter", nil, "Arbitrary label filter as name=value (repeatable)") cmd.Flags().IntVar(&bucketSeconds, "bucket-seconds", 0, "Aggregation bucket size in seconds (optional; server auto-selects based on the time window when omitted, minimum 60s)") + cmd.Flags().StringVar(&fn, "fn", "", "Aggregation function applied per bucket. One of: RATE, INCREASE, SUM, AVG, MIN, MAX, COUNT, P50, P90, P99, LAST. Rejected on the timescale_cloud_* resource/qps/connections/jobs metrics; omit to let the server pick the default") cmd.Flags().VarP((*outputFlag)(&output), "output", "o", "Output format (json, yaml, table)") cmd.MarkFlagRequired("metric") diff --git a/internal/tiger/mcp/service_tools.go b/internal/tiger/mcp/service_tools.go index b7526e0d..48ed9d93 100644 --- a/internal/tiger/mcp/service_tools.go +++ b/internal/tiger/mcp/service_tools.go @@ -455,6 +455,7 @@ type ServiceMetricsSeriesInput struct { Role string `json:"role,omitempty"` Filters []MetricLabelFilterInput `json:"filters,omitempty"` BucketSeconds int `json:"bucket_seconds,omitempty"` + Fn string `json:"fn,omitempty"` } func (ServiceMetricsSeriesInput) Schema() *jsonschema.Schema { @@ -484,6 +485,10 @@ func (ServiceMetricsSeriesInput) Schema() *jsonschema.Schema { schema.Properties["bucket_seconds"].Minimum = util.Ptr(60.0) schema.Properties["bucket_seconds"].Examples = []any{60, 300, 3600} + schema.Properties["fn"].Description = "Aggregation function applied per bucket. Not accepted on these metrics (returns INVALID_REQUEST): timescale_cloud_system_cpu_total_millicores, timescale_cloud_system_cpu_usage_millicores, timescale_cloud_system_disk_io_read_bytes, timescale_cloud_system_disk_io_read_ops, timescale_cloud_system_disk_io_total_bytes, timescale_cloud_system_disk_io_total_ops, timescale_cloud_system_disk_io_write_bytes, timescale_cloud_system_disk_io_write_ops, timescale_cloud_system_disk_usage_bytes, timescale_cloud_system_memory_total_bytes, timescale_cloud_system_memory_usage_bytes, timescale_cloud_database_qps, timescale_cloud_database_num_connections, timescale_cloud_database_job_duration_usecs, timescale_cloud_database_job_success. When omitted, the server picks a sensible default for the metric (typically LAST)." + schema.Properties["fn"].Enum = []any{"RATE", "INCREASE", "SUM", "AVG", "MIN", "MAX", "COUNT", "P50", "P90", "P99", "LAST"} + schema.Properties["fn"].Examples = []any{"RATE"} + return schema } @@ -1420,6 +1425,10 @@ func (s *Server) handleServiceMetricsSeries(ctx context.Context, req *mcp.CallTo bs := input.BucketSeconds body.BucketSeconds = &bs } + if input.Fn != "" { + fn := api.MetricsSeriesRequestFn(strings.ToUpper(input.Fn)) + body.Fn = &fn + } if len(filters) > 0 { body.Filters = &filters } diff --git a/openapi.yaml b/openapi.yaml index f2bd4e85..16a245aa 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1555,6 +1555,42 @@ components: Must be coarse enough for the window's tier; requests that ask for a finer bucket than the tier can produce are rejected. example: 3600 + fn: + type: string + enum: + - RATE + - INCREASE + - SUM + - AVG + - MIN + - MAX + - COUNT + - P50 + - P90 + - P99 + - LAST + description: | + Aggregation function applied per bucket. Optional: when omitted, + the server picks the default for the metric (currently `LAST`). + + Not accepted on the following metrics — requests are rejected + with `INVALID_REQUEST`: + - `timescale_cloud_system_cpu_total_millicores` + - `timescale_cloud_system_cpu_usage_millicores` + - `timescale_cloud_system_disk_io_read_bytes` + - `timescale_cloud_system_disk_io_read_ops` + - `timescale_cloud_system_disk_io_total_bytes` + - `timescale_cloud_system_disk_io_total_ops` + - `timescale_cloud_system_disk_io_write_bytes` + - `timescale_cloud_system_disk_io_write_ops` + - `timescale_cloud_system_disk_usage_bytes` + - `timescale_cloud_system_memory_total_bytes` + - `timescale_cloud_system_memory_usage_bytes` + - `timescale_cloud_database_qps` + - `timescale_cloud_database_num_connections` + - `timescale_cloud_database_job_duration_usecs` + - `timescale_cloud_database_job_success` + example: "RATE" filters: type: array description: |