Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ updates:
- /
- /exp
- /tutorial/whatsup
- /api/prometheus/v1/paritytest
schedule:
interval: "monthly"
groups:
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## Unreleased

* [FEATURE] testutil: Add GatherAndFormat to encode a subset of metrics from a Gatherer. #2091
* [BUGFIX] **breaking** api/prometheus/v1: Fix `TSDBBlocks` so it can decode real server responses; fixes issues with data enveloping and aligns the TSDB Blocks struct types to match the upstream Prometheus counterpart. #1928

## 1.24.1 / 2026-07-23

Expand Down
8 changes: 8 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@ test-exp:
test-exp-short:
cd exp && $(GOTEST) -short $(GOOPTS) $(pkgs)

.PHONY: parity-deps
parity-deps:
cd api/prometheus/v1/paritytest && $(GO) mod download

.PHONY: test-parity
test-parity: parity-deps
cd api/prometheus/v1/paritytest && $(GOTEST) $(test-flags) $(GOOPTS) $(pkgs)

.PHONY: check-crlf
check-crlf:
@echo ">> checking for CRLF line endings"
Expand Down
84 changes: 53 additions & 31 deletions api/prometheus/v1/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,38 +696,60 @@ type TSDBHeadStats struct {
MaxTime int `json:"maxTime"`
}

// TSDBBlocksResult contains the results from querying the tsdb blocks endpoint.
// TSDBBlocksResult contains the result from querying the tsdb blocks endpoint.
//
// Note: TSDB block types are defined locally rather than importing from
// prometheus/prometheus to avoid circular dependencies and minimize the
// dependency footprint. These types match the HTTP API wire format and use
// string for ULID fields (since JSON serializes ULIDs as strings). See the
// individual type comments for links to upstream Prometheus definitions.
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"`
Blocks []TSDBBlockMeta `json:"blocks"`
}

// TSDBBlockMeta contains the metadata for a single TSDB block.
// Counterpart to prometheus/prometheus tsdb.BlockMeta:
// https://github.com/prometheus/prometheus/blob/v0.313.2/tsdb/block.go#L164
type TSDBBlockMeta struct {
ULID string `json:"ulid"`
MinTime int64 `json:"minTime"`
MaxTime int64 `json:"maxTime"`
Stats TSDBBlockStats `json:"stats,omitempty"`
Compaction TSDBBlockMetaCompaction `json:"compaction"`
Version int `json:"version"`
}

// TSDBBlockStats contains block stats for a single TSDB block.
// Counterpart to prometheus/prometheus tsdb.BlockStats:
// https://github.com/prometheus/prometheus/blob/v0.313.2/tsdb/block.go#L184
type TSDBBlockStats struct {
NumSamples uint64 `json:"numSamples,omitempty"`
NumFloatSamples uint64 `json:"numFloatSamples,omitempty"`
NumHistogramSamples uint64 `json:"numHistogramSamples,omitempty"`
NumSeries uint64 `json:"numSeries,omitempty"`
NumChunks uint64 `json:"numChunks,omitempty"`
NumTombstones uint64 `json:"numTombstones,omitempty"`
}

// TSDBBlockDesc describes a TSDB block reference, used in compaction parent tracking.
// Counterpart to prometheus/prometheus tsdb.BlockDesc:
// https://github.com/prometheus/prometheus/blob/v0.313.2/tsdb/block.go#L194
type TSDBBlockDesc struct {
ULID string `json:"ulid"`
MinTime int64 `json:"minTime"`
MaxTime int64 `json:"maxTime"`
}

// TSDBBlockMetaCompaction contains block compaction details for a single TSDB block.
// Counterpart to prometheus/prometheus tsdb.BlockMetaCompaction:
// https://github.com/prometheus/prometheus/blob/v0.313.2/tsdb/block.go#L201
type TSDBBlockMetaCompaction struct {
Level int `json:"level"`
Sources []string `json:"sources,omitempty"`
Deletable bool `json:"deletable,omitempty"`
Parents []TSDBBlockDesc `json:"parents,omitempty"`
Failed bool `json:"failed,omitempty"`
Hints []string `json:"hints,omitempty"`
}

// WalReplayStatus represents the wal replay status.
Expand Down
148 changes: 121 additions & 27 deletions api/prometheus/v1/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1202,52 +1202,146 @@ func TestAPIs(t *testing.T) {
err: errors.New("some error"),
},

// Empty blocks list.
{
do: doTSDBBlocks(),
reqMethod: "GET",
reqPath: "/api/v1/status/tsdb/blocks",
inRes: map[string]any{
"status": "success",
"data": map[string]any{
"blocks": []any{
map[string]any{
"ulid": "01JZ8JKZY6XSK3PTDP9ZKRWT60",
"minTime": 1750860620060,
"maxTime": 1750867200000,
"stats": map[string]any{
"numSamples": 13701,
"numSeries": 716,
"numChunks": 716,
"blocks": []any{},
},
res: TSDBBlocksResult{
Blocks: []TSDBBlockMeta{},
},
},

// A single success case covers the decode quirks in one round
// trip: a fully populated block exercising every optional
// stats and compaction field (including the compaction hint
// Prometheus's OOO feature emits, CompactionHintFromOutOfOrder
// upstream), plus a minimal uncompacted block with no stats
// field at all.
// The fully populated block's stats deliberately exceed 2^32:
// narrowing any of the uint64 stats fields (which real blocks
// do overflow past 32 bits) makes the expected literal below a
// compile error and the decode a truncation failure. The
// unknown fields must be ignored by the decoder: upstream grows
// the block metadata over time (that history is why this
// endpoint's types needed fixing), so older clients must
// tolerate fields they don't know yet.
{
do: doTSDBBlocks(),
Comment thread
tjhop marked this conversation as resolved.
reqMethod: "GET",
reqPath: "/api/v1/status/tsdb/blocks",
inRes: map[string]any{
"blocks": []any{
map[string]any{
"ulid": "01AAAAAAAAAAAAAAAAAAAAAA00",
"minTime": 1750860000000,
"maxTime": 1750867200000,
"stats": map[string]any{
"numSamples": 9000000000,
"numFloatSamples": 5000000000,
"numHistogramSamples": 4000000000,
"numSeries": 4400000000,
"numChunks": 4500000000,
"numTombstones": 4300000000,
"someFutureStatUnknownToThisClient": 42,
},
"compaction": map[string]any{
"level": 3,
"sources": []any{
"01AAAAAAAAAAAAAAAAAAAAAA01",
"01AAAAAAAAAAAAAAAAAAAAAA02",
"01AAAAAAAAAAAAAAAAAAAAAA03",
},
"compaction": map[string]any{
"level": 1,
"sources": []any{
"01JZ8JKZY6XSK3PTDP9ZKRWT60",
"deletable": true,
"parents": []any{
map[string]any{
"ulid": "01AAAAAAAAAAAAAAAAAAAAAA01",
"minTime": 1750853000000,
"maxTime": 1750860000000,
},
map[string]any{
"ulid": "01AAAAAAAAAAAAAAAAAAAAAA02",
"minTime": 1750860000000,
"maxTime": 1750867200000,
},
},
"failed": true,
"hints": []any{
"from-out-of-order",
"deduplication",
"downsampling",
},
"version": 1,
},
"version": 1,
"someFutureFieldUnknownToThisClient": "ignored",
},
map[string]any{
"ulid": "01JZ8JKZY6XSK3PTDP9ZKRWT60",
"minTime": 1750860620060,
"maxTime": 1750867200000,
"compaction": map[string]any{
"level": 1,
"sources": []any{
"01JZ8JKZY6XSK3PTDP9ZKRWT60",
},
},
"version": 1,
},
},
},
res: TSDBBlocksResult{
Status: "success",
Data: TSDBBlocksData{
Blocks: []TSDBBlocksBlockMetadata{{
Ulid: "01JZ8JKZY6XSK3PTDP9ZKRWT60",
MinTime: 1750860620060,
Blocks: []TSDBBlockMeta{
{
ULID: "01AAAAAAAAAAAAAAAAAAAAAA00",
MinTime: 1750860000000,
MaxTime: 1750867200000,
Version: 1,
Stats: TSDBBlocksStats{
NumSamples: 13701,
NumSeries: 716,
NumChunks: 716,
Stats: TSDBBlockStats{
NumSamples: 9000000000,
NumFloatSamples: 5000000000,
NumHistogramSamples: 4000000000,
NumSeries: 4400000000,
NumChunks: 4500000000,
NumTombstones: 4300000000,
},
Compaction: TSDBBlockMetaCompaction{
Level: 3,
Sources: []string{
"01AAAAAAAAAAAAAAAAAAAAAA01",
"01AAAAAAAAAAAAAAAAAAAAAA02",
"01AAAAAAAAAAAAAAAAAAAAAA03",
},
Deletable: true,
Parents: []TSDBBlockDesc{
{
ULID: "01AAAAAAAAAAAAAAAAAAAAAA01",
MinTime: 1750853000000,
MaxTime: 1750860000000,
},
{
ULID: "01AAAAAAAAAAAAAAAAAAAAAA02",
MinTime: 1750860000000,
MaxTime: 1750867200000,
},
},
Failed: true,
Hints: []string{"from-out-of-order", "deduplication", "downsampling"},
},
Compaction: TSDBBlocksCompaction{
},
{
ULID: "01JZ8JKZY6XSK3PTDP9ZKRWT60",
MinTime: 1750860620060,
MaxTime: 1750867200000,
Version: 1,
Stats: TSDBBlockStats{}, // zero values
Compaction: TSDBBlockMetaCompaction{
Level: 1,
Sources: []string{"01JZ8JKZY6XSK3PTDP9ZKRWT60"},
},
}},
},
},
},
},
Expand Down
95 changes: 95 additions & 0 deletions api/prometheus/v1/paritytest/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
module github.com/prometheus/client_golang/api/prometheus/v1/paritytest

go 1.25.0

replace github.com/prometheus/client_golang => ../../../..

require (
github.com/oklog/ulid/v2 v2.1.2
github.com/prometheus/client_golang v1.23.2
github.com/prometheus/prometheus v0.313.2
github.com/stretchr/testify v1.11.1
)

require (
cloud.google.com/go/auth v0.20.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.22.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.14.0 // indirect
github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect
github.com/AzureAD/microsoft-authentication-library-for-go v1.7.2 // indirect
github.com/alecthomas/units v0.0.0-20240927000941-0f3dac36c52b // indirect
github.com/aws/aws-sdk-go-v2 v1.42.0 // indirect
github.com/aws/aws-sdk-go-v2/config v1.32.25 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.24 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.29 // indirect
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.30 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.12 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.29 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.2.0 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.31.3 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.36.6 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.43.3 // indirect
github.com/aws/smithy-go v1.27.2 // indirect
github.com/bboreham/go-loser v0.0.0-20230920113527-fcc2c21820a3 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
github.com/golang/snappy v1.0.0 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.15 // indirect
github.com/googleapis/gax-go/v2 v2.22.0 // indirect
github.com/grafana/regexp v0.0.0-20250905093917-f7b3be9d1853 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.19.1 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang/exp v0.0.0-20260602051030-3537b20ac86b // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/prometheus/common v0.70.1 // indirect
github.com/prometheus/otlptranslator v1.0.0 // indirect
github.com/prometheus/procfs v0.21.1 // indirect
github.com/prometheus/sigv4 v0.4.1 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0 // indirect
go.opentelemetry.io/otel v1.44.0 // indirect
go.opentelemetry.io/otel/metric v1.44.0 // indirect
go.opentelemetry.io/otel/trace v1.44.0 // indirect
go.uber.org/atomic v1.11.0 // indirect
go.uber.org/goleak v1.3.0 // indirect
go.yaml.in/yaml/v2 v2.4.4 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/exp v0.0.0-20260218203240-3dfff04db8fa // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sync v0.22.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
golang.org/x/time v0.15.0 // indirect
google.golang.org/api v0.278.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad // indirect
google.golang.org/grpc v1.82.1 // indirect
google.golang.org/protobuf v1.36.11 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apimachinery v0.35.3 // indirect
k8s.io/client-go v0.35.3 // indirect
k8s.io/klog/v2 v2.140.0 // indirect
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
)
Loading
Loading