From 2a300693bb95e611c52aeb857d7ef3bad9067cbd Mon Sep 17 00:00:00 2001 From: TJ Hoplock Date: Mon, 20 Oct 2025 11:53:54 -0400 Subject: [PATCH] feat: add support for `/status/tsdb/blocks` endpoint Adds a new method to the API interface to support the new TSDB blocks endpoint that was added in prometheus 3.6.0: https://github.com/prometheus/prometheus/releases/tag/v3.6.0 prometheus/prometheus#16695 Signed-off-by: TJ Hoplock --- api/prometheus/v1/api.go | 55 +++++++++++++++++++++++++++++ api/prometheus/v1/api_test.go | 65 +++++++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) diff --git a/api/prometheus/v1/api.go b/api/prometheus/v1/api.go index 1c71d7274..8e5e6a390 100644 --- a/api/prometheus/v1/api.go +++ b/api/prometheus/v1/api.go @@ -380,6 +380,7 @@ const ( epBuildinfo = apiPrefix + "/status/buildinfo" epRuntimeinfo = apiPrefix + "/status/runtimeinfo" epTSDB = apiPrefix + "/status/tsdb" + epTSDBBlocks = apiPrefix + "/status/tsdb/blocks" epWalReplay = apiPrefix + "/status/walreplay" epFormatQuery = apiPrefix + "/format_query" ) @@ -504,6 +505,8 @@ type API interface { Metadata(ctx context.Context, metric, limit string) (map[string][]Metadata, error) // TSDB returns the cardinality statistics. TSDB(ctx context.Context, opts ...Option) (TSDBResult, error) + // TSDBBlocks returns the list of currently loaded TSDB blocks and their metadata. + TSDBBlocks(ctx context.Context) (TSDBBlocksResult, error) // WalReplay returns the current replay status of the wal. WalReplay(ctx context.Context) (WalReplayStatus, error) // FormatQuery formats a PromQL expression in a prettified way. @@ -693,6 +696,40 @@ type TSDBHeadStats struct { MaxTime int `json:"maxTime"` } +// TSDBBlocksResult contains the results from querying the tsdb blocks endpoint. +type TSDBBlocksResult struct { + Status string `json:"status"` + Data TSDBBlocksData `json:"data"` +} + +// TSDBBlocksData contains the metadata for the tsdb blocks. +type TSDBBlocksData struct { + Blocks []TSDBBlocksBlockMetadata `json:"blocks"` +} + +// TSDBBlocksBlockMetadata contains the metadata for a single tsdb block. +type TSDBBlocksBlockMetadata struct { + Ulid string `json:"ulid"` + MinTime int64 `json:"minTime"` + MaxTime int64 `json:"maxTime"` + Stats TSDBBlocksStats `json:"stats"` + Compaction TSDBBlocksCompaction `json:"compaction"` + Version int `json:"version"` +} + +// TSDBBlocksStats contains block stats for a single tsdb block. +type TSDBBlocksStats struct { + NumSamples int `json:"numSamples"` + NumSeries int `json:"numSeries"` + NumChunks int `json:"numChunks"` +} + +// TSDBBlocksCompaction contains block compaction details for a single block. +type TSDBBlocksCompaction struct { + Level int `json:"level"` + Sources []string `json:"sources"` +} + // WalReplayStatus represents the wal replay status. type WalReplayStatus struct { Min int `json:"min"` @@ -1350,6 +1387,24 @@ func (h *httpAPI) TSDB(ctx context.Context, opts ...Option) (TSDBResult, error) return res, err } +func (h *httpAPI) TSDBBlocks(ctx context.Context) (TSDBBlocksResult, error) { + u := h.client.URL(epTSDBBlocks, nil) + + req, err := http.NewRequest(http.MethodGet, u.String(), nil) + if err != nil { + return TSDBBlocksResult{}, err + } + + _, body, _, err := h.client.Do(ctx, req) + if err != nil { + return TSDBBlocksResult{}, err + } + + var res TSDBBlocksResult + err = json.Unmarshal(body, &res) + return res, err +} + func (h *httpAPI) WalReplay(ctx context.Context) (WalReplayStatus, error) { u := h.client.URL(epWalReplay, nil) diff --git a/api/prometheus/v1/api_test.go b/api/prometheus/v1/api_test.go index 190fb1e22..aa26cba8d 100644 --- a/api/prometheus/v1/api_test.go +++ b/api/prometheus/v1/api_test.go @@ -226,6 +226,13 @@ func TestAPIs(t *testing.T) { } } + doTSDBBlocks := func(opts ...Option) func() (interface{}, Warnings, error) { + return func() (interface{}, Warnings, error) { + v, err := promAPI.TSDBBlocks(context.Background()) + return v, nil, err + } + } + doWalReply := func() func() (interface{}, Warnings, error) { return func() (interface{}, Warnings, error) { v, err := promAPI.WalReplay(context.Background()) @@ -1186,6 +1193,64 @@ func TestAPIs(t *testing.T) { }, }, + { + do: doTSDBBlocks(), + reqMethod: "GET", + reqPath: "/api/v1/status/tsdb/blocks", + inErr: errors.New("some error"), + err: errors.New("some error"), + }, + + { + do: doTSDBBlocks(), + reqMethod: "GET", + reqPath: "/api/v1/status/tsdb/blocks", + inRes: map[string]interface{}{ + "status": "success", + "data": map[string]interface{}{ + "blocks": []interface{}{ + map[string]interface{}{ + "ulid": "01JZ8JKZY6XSK3PTDP9ZKRWT60", + "minTime": 1750860620060, + "maxTime": 1750867200000, + "stats": map[string]interface{}{ + "numSamples": 13701, + "numSeries": 716, + "numChunks": 716, + }, + "compaction": map[string]interface{}{ + "level": 1, + "sources": []interface{}{ + "01JZ8JKZY6XSK3PTDP9ZKRWT60", + }, + }, + "version": 1, + }, + }, + }, + }, + res: TSDBBlocksResult{ + Status: "success", + Data: TSDBBlocksData{ + Blocks: []TSDBBlocksBlockMetadata{{ + Ulid: "01JZ8JKZY6XSK3PTDP9ZKRWT60", + MinTime: 1750860620060, + MaxTime: 1750867200000, + Version: 1, + Stats: TSDBBlocksStats{ + NumSamples: 13701, + NumSeries: 716, + NumChunks: 716, + }, + Compaction: TSDBBlocksCompaction{ + Level: 1, + Sources: []string{"01JZ8JKZY6XSK3PTDP9ZKRWT60"}, + }, + }}, + }, + }, + }, + { do: doWalReply(), reqMethod: "GET",