feat(api): add histogram_format=native to query endpoints

Add a new optional histogram_format query parameter to /api/v1/query
and /api/v1/query_range. When set to native, native histograms in the
JSON response are emitted in a schema-aware shape.

For exponential schemas:

  {
    "count":          "<count>",
    "sum":            "<sum>",
    "schema":         <int>,
    "zero_threshold": "<threshold>",
    "zero_count":     "<count>",                       // when non-zero
    "negative_buckets": [ [ <index>, "<count>" ], ... ], // when non-empty
    "buckets":          [ [ <index>, "<count>" ], ... ]  // when non-empty
  }

zero_threshold is always emitted because, together with schema, it
fully describes the bucket layout. The negative/positive bucket
arrays carry the schema-defined bucket index and count, so empty
buckets that fall between non-empty ones are not transmitted.

For custom-bucket histograms (schema -53):

  {
    "count":      "<count>",
    "sum":        "<sum>",
    "schema":     -53,
    "boundaries": [ "<upper_bound>", ... ],
    "buckets":    [ [ <index>, "<count>" ], ... ]
  }

boundaries lists the bucket upper bounds; each bucket entry's index
is the 0-based index into boundaries.

This lets clients that already understand the schema (e.g. Grafana
rendering NHCB heatmaps) reconstruct every bucket including empty
custom-bucket slots, and is also substantially more compact than the
boundary-based representation for histograms with many buckets.

The existing boundary-based representation is unchanged when
histogram_format is absent or set to any other value.

Signed-off-by: György Krajcsovits <gyorgy.krajcsovits@grafana.com>
This commit is contained in:
György Krajcsovits 2026-05-22 17:43:45 +02:00
parent 891e698992
commit db46de3dcb
No known key found for this signature in database
GPG key ID: 47A8F9CE80FD7C7F
10 changed files with 606 additions and 17 deletions

View file

@ -103,6 +103,11 @@ URL query parameters:
- `limit=<number>`: Maximum number of returned series. Doesn't affect scalars or strings but truncates the number of series for matrices and vectors. Optional. 0 means disabled.
- `lookback_delta=<duration | float>`: Override the [lookback period](#staleness) just for this query in `duration` format or float number of seconds. Optional.
- `stats=<string>`: Include query statistics in the response. If set to `all`, includes detailed statistics. Optional.
- `histogram_format=<string>`: Selects an alternative JSON representation for
native histograms in the response. If set to `native`, each histogram is
emitted using the schema-aware shape described in [Native histograms (native
format)](#native-histograms-native-format) instead of the default
boundary-based shape. Optional.
The current server time is used if the `time` parameter is omitted.
@ -177,6 +182,11 @@ URL query parameters:
- `limit=<number>`: Maximum number of returned series. Optional. 0 means disabled.
- `lookback_delta=<duration | float>`: Override the [lookback period](#staleness) just for this query in `duration` format or float number of seconds. Optional.
- `stats=<string>`: Include query statistics in the response. If set to `all`, includes detailed statistics. Optional.
- `histogram_format=<string>`: Selects an alternative JSON representation for
native histograms in the response. If set to `native`, each histogram is
emitted using the schema-aware shape described in [Native histograms (native
format)](#native-histograms-native-format) instead of the default
boundary-based shape. Optional.
You can URL-encode these parameters directly in the request body by using the `POST` method and
`Content-Type: application/x-www-form-urlencoded` header. This is useful when specifying a large
@ -796,6 +806,63 @@ Note that with the currently implemented bucket schemas, positive buckets are
“open left”, negative buckets are “open right”, and the zero bucket (with a
negative left boundary and a positive right boundary) is “closed both”.
### Native histograms (native format)
When the `histogram_format=native` query parameter is set on `/api/v1/query` or
`/api/v1/query_range`, the `<histogram>` placeholder is rendered using a
schema-aware shape that mirrors the native histogram data model more closely.
The caller is expected to know how buckets are organised for the given schema
(and, for custom-bucket histograms, the supplied bucket boundaries). The
shape depends on the schema.
For exponential-schema histograms:
```json
{
"count": "<count_of_observations>",
"sum": "<sum_of_observations>",
"schema": <schema_number>,
"zero_threshold": "<zero_bucket_threshold>",
"zero_count": "<count_in_zero_bucket>",
"negative_buckets": [ [ <index>, "<count_in_bucket>" ], ... ],
"buckets": [ [ <index>, "<count_in_bucket>" ], ... ]
}
```
- `zero_threshold` is the zero-bucket threshold and is always present, because
together with `schema` it fully describes the bucket layout.
- `zero_count` is the observation count in the zero bucket; it is only present
when non-zero.
- `negative_buckets` and `buckets` (the positive buckets) are each optional and
only present when at least one bucket on that side has a non-zero count. Each
entry is a two-element array `[ <index>, <count> ]` where `<index>` is the
schema-defined bucket index.
For custom-bucket histograms (schema `-53`):
```json
{
"count": "<count_of_observations>",
"sum": "<sum_of_observations>",
"schema": -53,
"boundaries": [ "<bucket_upper_bound>", ... ],
"buckets": [ [ <index>, "<count_in_bucket>" ], ... ]
}
```
- `boundaries` lists the bucket upper bounds.
- Each entry in `buckets` is `[ <index>, <count> ]` where `<index>` is the
0-based index into `boundaries` identifying the bucket's upper bound. The
implicit overflow bucket `(boundaries[len-1], +Inf]` has index `len(boundaries)`
and is emitted just like any other bucket; `+Inf` itself never appears in
the response.
For exponential schemas, observations of ±Inf land in a regular bucket whose
schema-defined upper bound is `+Inf`, one index past the last finite-bound
bucket. It is emitted as any other `[ <index>, <count> ]` entry.
Empty buckets are omitted in both representations.
## Scrape pools
The following endpoint returns a list of all configured scrape pools:

View file

@ -135,3 +135,113 @@ func MarshalHistogram(h *histogram.FloatHistogram, stream *jsoniter.Stream) {
}
stream.WriteObjectEnd()
}
// MarshalHistogramNative marshals a histogram in a format that mirrors the
// native histogram data model closely. It is intended for callers that already
// understand how buckets are organised given the schema (and, for custom-bucket
// histograms, the bucket boundaries).
//
// The shape depends on the schema. For exponential schemas it looks like:
//
// {
// "count": "42",
// "sum": "34593.34",
// "schema": 2,
// "zero_threshold": "0.001",
// "zero_count": "12",
// "negative_buckets": [ [ -1, "3" ], [ 0, "2" ] ],
// "buckets": [ [ 0, "5" ], [ 1, "8" ] ]
// }
//
// "zero_threshold" is always emitted for exponential schemas because it is
// part of the schema definition. "zero_count" is only emitted when the zero
// bucket has a non-zero count. "negative_buckets" and "buckets" are each
// optional and only emitted when at least one bucket on that side has a
// non-zero count. The bucket entries are [index, count] pairs where index is
// the schema-defined bucket index.
//
// For custom-bucket histograms (schema -53) it looks like:
//
// {
// "count": "6",
// "sum": "7",
// "schema": -53,
// "boundaries": [ "0.5", "1", "2", "5" ],
// "buckets": [ [ 1, "2" ], [ 2, "3" ], [ 3, "1" ] ]
// }
//
// "boundaries" lists the bucket upper bounds. Each bucket entry is
// [index, count] where index is the 0-based index into "boundaries"
// identifying the bucket's upper bound.
//
// Counts are encoded as strings for NaN/Inf safety. Empty buckets are
// skipped.
func MarshalHistogramNative(h *histogram.FloatHistogram, stream *jsoniter.Stream) {
stream.WriteObjectStart()
stream.WriteObjectField(`count`)
MarshalFloat(h.Count, stream)
stream.WriteMore()
stream.WriteObjectField(`sum`)
MarshalFloat(h.Sum, stream)
stream.WriteMore()
stream.WriteObjectField(`schema`)
stream.WriteInt32(h.Schema)
if histogram.IsCustomBucketsSchema(h.Schema) {
stream.WriteMore()
stream.WriteObjectField(`boundaries`)
stream.WriteArrayStart()
for i, v := range h.CustomValues {
if i != 0 {
stream.WriteMore()
}
MarshalFloat(v, stream)
}
stream.WriteArrayEnd()
writeIndexedBuckets(stream, `buckets`, h.PositiveBucketIterator())
stream.WriteObjectEnd()
return
}
stream.WriteMore()
stream.WriteObjectField(`zero_threshold`)
MarshalFloat(h.ZeroThreshold, stream)
if h.ZeroCount != 0 {
stream.WriteMore()
stream.WriteObjectField(`zero_count`)
MarshalFloat(h.ZeroCount, stream)
}
writeIndexedBuckets(stream, `negative_buckets`, h.NegativeBucketIterator())
writeIndexedBuckets(stream, `buckets`, h.PositiveBucketIterator())
stream.WriteObjectEnd()
}
// writeIndexedBuckets writes a "<field>: [ [index, count], ... ]" entry from
// the iterator, skipping empty buckets. If every bucket is empty, nothing is
// written (so the field is omitted from the parent object).
func writeIndexedBuckets(stream *jsoniter.Stream, field string, it histogram.BucketIterator[float64]) {
started := false
for it.Next() {
b := it.At()
if b.Count == 0 {
continue
}
if !started {
stream.WriteMore()
stream.WriteObjectField(field)
stream.WriteArrayStart()
started = true
} else {
stream.WriteMore()
}
stream.WriteArrayStart()
stream.WriteInt32(b.Index)
stream.WriteMore()
MarshalFloat(b.Count, stream)
stream.WriteArrayEnd()
}
if started {
stream.WriteArrayEnd()
}
}

View file

@ -580,7 +580,7 @@ func (api *API) query(r *http.Request) (result apiFuncResult) {
return apiFuncResult{&QueryData{
ResultType: res.Value.Type(),
Result: res.Value,
Result: applyHistogramFormat(res.Value, r.FormValue("histogram_format")),
Stats: qs,
}, nil, warnings, qry.Close}
}
@ -705,7 +705,7 @@ func (api *API) queryRange(r *http.Request) (result apiFuncResult) {
return apiFuncResult{&QueryData{
ResultType: res.Value.Type(),
Result: res.Value,
Result: applyHistogramFormat(res.Value, r.FormValue("histogram_format")),
Stats: qs,
}, nil, warnings, qry.Close}
}

View file

@ -416,6 +416,92 @@ func TestAPIWithNativeHistograms(t *testing.T) {
RequireJSONArray("$.data").
RequireLenAtLeast("$.data", 1)
})
t.Run("GET /api/v1/query with histogram_format=native returns raw buckets", func(t *testing.T) {
testhelpers.GET(t, api, "/api/v1/query",
"query", "test_histogram",
"histogram_format", "native").
RequireSuccess().
ValidateOpenAPI().
RequireEquals("$.data.resultType", "vector").
RequireLenAtLeast("$.data.result", 1).
RequireSome("$.data.result", func(item any) bool {
sample, ok := item.(map[string]any)
if !ok {
return false
}
h, ok := sample["histogram"].([]any)
if !ok || len(h) != 2 {
return false
}
obj, ok := h[1].(map[string]any)
if !ok {
return false
}
return hasNativeHistogramShape(obj)
})
})
t.Run("GET /api/v1/query_range with histogram_format=native returns raw buckets", func(t *testing.T) {
// Use end=now+60 so the fixture's millisecond-precision timestamps
// (just after the integer-second boundary) reliably land before the
// last evaluation timestamp.
now := time.Now().Unix()
testhelpers.GET(t, api, "/api/v1/query_range",
"query", "test_histogram",
"start", strconv.FormatInt(now-60, 10),
"end", strconv.FormatInt(now+60, 10),
"step", "60",
"histogram_format", "native").
RequireSuccess().
ValidateOpenAPI().
RequireEquals("$.data.resultType", "matrix").
RequireSome("$.data.result", func(item any) bool {
series, ok := item.(map[string]any)
if !ok {
return false
}
hs, ok := series["histograms"].([]any)
if !ok || len(hs) == 0 {
return false
}
pair, ok := hs[0].([]any)
if !ok || len(pair) != 2 {
return false
}
obj, ok := pair[1].(map[string]any)
if !ok {
return false
}
return hasNativeHistogramShape(obj)
})
})
}
// hasNativeHistogramShape asserts that the given histogram object follows
// the schema-aware native format for an exponential-schema histogram: it
// must carry schema, zero_threshold, and at least one of the [index, count]
// bucket arrays expected for the fixture data (negative_buckets, buckets).
func hasNativeHistogramShape(obj map[string]any) bool {
if _, ok := obj["schema"]; !ok {
return false
}
if _, ok := obj["zero_threshold"]; !ok {
return false
}
if buckets, ok := obj["buckets"].([]any); ok && len(buckets) > 0 {
first, ok := buckets[0].([]any)
if !ok || len(first) != 2 {
return false
}
}
if neg, ok := obj["negative_buckets"].([]any); ok && len(neg) > 0 {
first, ok := neg[0].([]any)
if !ok || len(first) != 2 {
return false
}
}
return true
}
// TestAPIWithStats tests the API with the stats query parameter.

View file

@ -0,0 +1,155 @@
// Copyright The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package v1
import (
"unsafe"
jsoniter "github.com/json-iterator/go"
"github.com/prometheus/prometheus/promql"
"github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/util/jsonutil"
)
// nativeHistogramVector wraps a promql.Vector so that histogram samples are
// marshaled using the native histogram JSON shape (see
// jsonutil.MarshalHistogramNative). It is used to opt in to the alternative
// format on a per-request basis without affecting the default encoders.
type nativeHistogramVector promql.Vector
// nativeHistogramMatrix wraps a promql.Matrix for the same reason as
// nativeHistogramVector.
type nativeHistogramMatrix promql.Matrix
// Type satisfies the parser.Value interface so the wrapper can stand in for
// the underlying value type in QueryData.Result.
func (nativeHistogramVector) Type() parser.ValueType { return parser.ValueTypeVector }
// Type satisfies the parser.Value interface so the wrapper can stand in for
// the underlying value type in QueryData.Result.
func (nativeHistogramMatrix) Type() parser.ValueType { return parser.ValueTypeMatrix }
// String delegates to the wrapped promql.Vector so debug output matches.
func (v nativeHistogramVector) String() string { return promql.Vector(v).String() }
// String delegates to the wrapped promql.Matrix so debug output matches.
func (m nativeHistogramMatrix) String() string { return promql.Matrix(m).String() }
// histogramFormatNative is the histogram_format query parameter value that
// selects the native histogram JSON shape.
const histogramFormatNative = "native"
// applyHistogramFormat returns v wrapped so it is encoded according to the
// requested histogram_format value, or v unchanged when the format is empty
// or not recognised. Non-histogram-bearing values are returned unchanged.
func applyHistogramFormat(v parser.Value, format string) parser.Value {
if format != histogramFormatNative {
return v
}
switch x := v.(type) {
case promql.Vector:
return nativeHistogramVector(x)
case promql.Matrix:
return nativeHistogramMatrix(x)
default:
return v
}
}
func init() {
jsoniter.RegisterTypeEncoderFunc("v1.nativeHistogramVector", unsafeMarshalNativeHistogramVectorJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("v1.nativeHistogramMatrix", unsafeMarshalNativeHistogramMatrixJSON, neverEmpty)
}
func unsafeMarshalNativeHistogramVectorJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
v := *((*nativeHistogramVector)(ptr))
stream.WriteArrayStart()
for i, s := range v {
marshalNativeHistogramSampleJSON(s, stream)
if i != len(v)-1 {
stream.WriteMore()
}
}
stream.WriteArrayEnd()
}
func unsafeMarshalNativeHistogramMatrixJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
m := *((*nativeHistogramMatrix)(ptr))
stream.WriteArrayStart()
for i, s := range m {
marshalNativeHistogramSeriesJSON(s, stream)
if i != len(m)-1 {
stream.WriteMore()
}
}
stream.WriteArrayEnd()
}
func marshalNativeHistogramSampleJSON(s promql.Sample, stream *jsoniter.Stream) {
stream.WriteObjectStart()
stream.WriteObjectField(`metric`)
marshalLabelsJSON(s.Metric, stream)
stream.WriteMore()
if s.H == nil {
stream.WriteObjectField(`value`)
stream.WriteArrayStart()
jsonutil.MarshalTimestamp(s.T, stream)
stream.WriteMore()
jsonutil.MarshalFloat(s.F, stream)
stream.WriteArrayEnd()
} else {
stream.WriteObjectField(`histogram`)
stream.WriteArrayStart()
jsonutil.MarshalTimestamp(s.T, stream)
stream.WriteMore()
jsonutil.MarshalHistogramNative(s.H, stream)
stream.WriteArrayEnd()
}
stream.WriteObjectEnd()
}
func marshalNativeHistogramSeriesJSON(s promql.Series, stream *jsoniter.Stream) {
stream.WriteObjectStart()
stream.WriteObjectField(`metric`)
marshalLabelsJSON(s.Metric, stream)
for i, p := range s.Floats {
stream.WriteMore()
if i == 0 {
stream.WriteObjectField(`values`)
stream.WriteArrayStart()
}
marshalFPointJSON(p, stream)
}
if len(s.Floats) > 0 {
stream.WriteArrayEnd()
}
for i, p := range s.Histograms {
stream.WriteMore()
if i == 0 {
stream.WriteObjectField(`histograms`)
stream.WriteArrayStart()
}
stream.WriteArrayStart()
jsonutil.MarshalTimestamp(p.T, stream)
stream.WriteMore()
jsonutil.MarshalHistogramNative(p.H, stream)
stream.WriteArrayEnd()
}
if len(s.Histograms) > 0 {
stream.WriteArrayEnd()
}
stream.WriteObjectEnd()
}

View file

@ -102,6 +102,53 @@ func TestJsonCodec_Encode(t *testing.T) {
},
expected: `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"__name__":"foo"},"histograms":[[1,{"count":"10","sum":"20","buckets":[[1,"-1.6817928305074288","-1.414213562373095","1"],[1,"-1.414213562373095","-1.189207115002721","2"],[3,"-0.001","0.001","12"],[0,"1.414213562373095","1.6817928305074288","1"],[0,"1.6817928305074288","2","2"],[0,"2.378414230005442","2.82842712474619","2"],[0,"2.82842712474619","3.3635856610148576","1"],[0,"3.3635856610148576","4","1"]]}]]}]}}`,
},
{
response: &QueryData{
ResultType: parser.ValueTypeMatrix,
Result: nativeHistogramMatrix{
promql.Series{
Histograms: []promql.HPoint{{H: &histogram.FloatHistogram{
Schema: 2,
ZeroThreshold: 0.001,
ZeroCount: 12,
Count: 10,
Sum: 20,
PositiveSpans: []histogram.Span{
{Offset: 3, Length: 2},
{Offset: 1, Length: 3},
},
NegativeSpans: []histogram.Span{
{Offset: 2, Length: 2},
},
PositiveBuckets: []float64{1, 2, 2, 1, 1},
NegativeBuckets: []float64{2, 1},
}, T: 1000}},
Metric: labels.FromStrings("__name__", "foo"),
},
},
},
expected: `{"status":"success","data":{"resultType":"matrix","result":[{"metric":{"__name__":"foo"},"histograms":[[1,{"count":"10","sum":"20","schema":2,"zero_threshold":"0.001","zero_count":"12","negative_buckets":[[2,"2"],[3,"1"]],"buckets":[[3,"1"],[4,"2"],[6,"2"],[7,"1"],[8,"1"]]}]]}]}}`,
},
{
response: &QueryData{
ResultType: parser.ValueTypeVector,
Result: nativeHistogramVector{
promql.Sample{
Metric: labels.FromStrings("__name__", "nhcb"),
T: 1000,
H: &histogram.FloatHistogram{
Schema: histogram.CustomBucketsSchema,
Count: 6,
Sum: 7,
CustomValues: []float64{0.5, 1, 2, 5},
PositiveSpans: []histogram.Span{{Offset: 1, Length: 3}},
PositiveBuckets: []float64{2, 3, 1},
},
},
},
},
expected: `{"status":"success","data":{"resultType":"vector","result":[{"metric":{"__name__":"nhcb"},"histogram":[1,{"count":"6","sum":"7","schema":-53,"boundaries":["0.5","1","2","5"],"buckets":[[1,"2"],[2,"3"],[3,"1"]]}]}]}}`,
},
{
response: promql.FPoint{F: 0, T: 0},
expected: `{"status":"success","data":[0,"0"]}`,

View file

@ -33,6 +33,7 @@ func (*OpenAPIBuilder) queryPath() *v3.PathItem {
queryParamWithExample("timeout", "Evaluation timeout. Optional. Defaults to and is capped by the value of the -query.timeout flag.", false, durationSchema(), []example{{"duration", "1m30s"}, {"number", "90"}}),
queryParamWithExample("lookback_delta", "Override the lookback period for this query. Optional.", false, durationSchema(), []example{{"duration", "5m"}, {"number", "300"}}),
queryParamWithExample("stats", "When provided, include query statistics in the response. The special value 'all' enables more comprehensive statistics.", false, stringSchema(), []example{{"example", "all"}}),
queryParamWithExample("histogram_format", "Request an alternative JSON representation for native histograms in the response. The only supported value is 'native', which emits raw bucket indices instead of bucket boundaries. Optional.", false, stringSchema(), []example{{"example", "native"}}),
}
return &v3.PathItem{
Get: &v3.Operation{
@ -62,6 +63,7 @@ func (*OpenAPIBuilder) queryRangePath() *v3.PathItem {
queryParamWithExample("timeout", "Evaluation timeout. Optional. Defaults to and is capped by the value of the -query.timeout flag.", false, durationSchema(), []example{{"duration", "1m30s"}, {"number", "90"}}),
queryParamWithExample("lookback_delta", "Override the lookback period for this query. Optional.", false, durationSchema(), []example{{"duration", "5m"}, {"number", "300"}}),
queryParamWithExample("stats", "When provided, include query statistics in the response. The special value 'all' enables more comprehensive statistics.", false, stringSchema(), []example{{"example", "all"}}),
queryParamWithExample("histogram_format", "Request an alternative JSON representation for native histograms in the response. The only supported value is 'native', which emits raw bucket indices instead of bucket boundaries. Optional.", false, stringSchema(), []example{{"example", "native"}}),
}
return &v3.PathItem{
Get: &v3.Operation{

View file

@ -332,26 +332,44 @@ func (*OpenAPIBuilder) floatSampleSchema() *base.SchemaProxy {
}
func (*OpenAPIBuilder) histogramValueSchema() *base.SchemaProxy {
indexCountArray := base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: base.CreateSchemaProxy(&base.Schema{
OneOf: []*base.SchemaProxy{
base.CreateSchemaProxy(&base.Schema{Type: []string{"number"}}),
stringSchema(),
},
})},
})
props := orderedmap.New[string, *base.SchemaProxy]()
props.Set("count", stringSchemaWithDescription("Total count of observations."))
props.Set("sum", stringSchemaWithDescription("Sum of all observed values."))
props.Set("buckets", base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Description: "Histogram buckets as [boundary_rule, lower, upper, count].",
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: base.CreateSchemaProxy(&base.Schema{
OneOf: []*base.SchemaProxy{
base.CreateSchemaProxy(&base.Schema{Type: []string{"number"}}),
stringSchema(),
},
})},
})},
Description: "Histogram buckets. The default representation uses [boundary_rule, lower, upper, count] entries; when histogram_format=native was requested each entry is [index, count] for the positive buckets only.",
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: indexCountArray},
}))
props.Set("schema", base.CreateSchemaProxy(&base.Schema{
Type: []string{"integer"},
Description: "Bucket schema number. Only present when histogram_format=native was requested.",
}))
props.Set("zero_threshold", stringSchemaWithDescription("Zero-bucket threshold for exponential-schema histograms. Always present when histogram_format=native was requested for an exponential-schema histogram, because it is part of the schema definition."))
props.Set("zero_count", stringSchemaWithDescription("Observation count in the zero bucket for exponential-schema histograms. Only present when histogram_format=native was requested and the zero-bucket count is non-zero."))
props.Set("negative_buckets", base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Description: "Negative buckets for exponential-schema histograms as [index, count] pairs. Only present when histogram_format=native was requested.",
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: indexCountArray},
}))
props.Set("boundaries", base.CreateSchemaProxy(&base.Schema{
Type: []string{"array"},
Description: "Bucket upper bounds for custom-bucket histograms (schema -53). Only present when histogram_format=native was requested.",
Items: &base.DynamicValue[*base.SchemaProxy, bool]{A: stringSchema()},
}))
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"object"},
Description: "Native histogram value representation.",
Description: "Native histogram value representation. The default response uses [boundary_rule, lower, upper, count] bucket entries. With histogram_format=native exponential histograms expose zero_threshold, optional zero_count, and optional negative_buckets/buckets ([index, count] pairs); custom-bucket histograms (schema -53) expose boundaries plus buckets.",
Required: []string{"count", "sum"},
AdditionalProperties: &base.DynamicValue[*base.SchemaProxy, bool]{N: 1, B: false},
Properties: props,
@ -620,6 +638,7 @@ func (*OpenAPIBuilder) queryPostInputBodySchema() *base.SchemaProxy {
props.Set("timeout", stringSchemaWithDescriptionAndExample("Form field: Evaluation timeout (optional, defaults to and is capped by the value of the -query.timeout flag).", "30s"))
props.Set("lookback_delta", stringSchemaWithDescriptionAndExample("Form field: Override the lookback period for this query (optional).", "5m"))
props.Set("stats", stringSchemaWithDescriptionAndExample("Form field: When provided, include query statistics in the response (the special value 'all' enables more comprehensive statistics).", "all"))
props.Set("histogram_format", stringSchemaWithDescriptionAndExample("Form field: Alternative JSON representation for native histograms. Set to 'native' to emit raw bucket indices instead of bucket boundaries (optional).", "native"))
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"object"},
@ -640,6 +659,7 @@ func (*OpenAPIBuilder) queryRangePostInputBodySchema() *base.SchemaProxy {
props.Set("timeout", stringSchemaWithDescriptionAndExample("Form field: Evaluation timeout (optional, defaults to and is capped by the value of the -query.timeout flag).", "30s"))
props.Set("lookback_delta", stringSchemaWithDescriptionAndExample("Form field: Override the lookback period for this query (optional).", "5m"))
props.Set("stats", stringSchemaWithDescriptionAndExample("Form field: When provided, include query statistics in the response (the special value 'all' enables more comprehensive statistics).", "all"))
props.Set("histogram_format", stringSchemaWithDescriptionAndExample("Form field: Alternative JSON representation for native histograms. Set to 'native' to emit raw bucket indices instead of bucket boundaries (optional).", "native"))
return base.CreateSchemaProxy(&base.Schema{
Type: []string{"object"},

View file

@ -104,6 +104,16 @@ paths:
examples:
example:
value: all
- name: histogram_format
in: query
description: Request an alternative JSON representation for native histograms in the response. The only supported value is 'native', which emits raw bucket indices instead of bucket boundaries. Optional.
required: false
explode: false
schema:
type: string
examples:
example:
value: native
responses:
"200":
description: Query executed successfully.
@ -337,6 +347,16 @@ paths:
examples:
example:
value: all
- name: histogram_format
in: query
description: Request an alternative JSON representation for native histograms in the response. The only supported value is 'native', which emits raw bucket indices instead of bucket boundaries. Optional.
required: false
explode: false
schema:
type: string
examples:
example:
value: native
responses:
"200":
description: Range query executed successfully.
@ -3469,6 +3489,10 @@ components:
type: string
description: 'Form field: When provided, include query statistics in the response (the special value ''all'' enables more comprehensive statistics).'
example: all
histogram_format:
type: string
description: 'Form field: Alternative JSON representation for native histograms. Set to ''native'' to emit raw bucket indices instead of bucket boundaries (optional).'
example: native
required:
- query
additionalProperties: false
@ -3509,6 +3533,10 @@ components:
type: string
description: 'Form field: When provided, include query statistics in the response (the special value ''all'' enables more comprehensive statistics).'
example: all
histogram_format:
type: string
description: 'Form field: Alternative JSON representation for native histograms. Set to ''native'' to emit raw bucket indices instead of bucket boundaries (optional).'
example: native
required:
- query
- start
@ -3883,12 +3911,35 @@ components:
oneOf:
- type: number
- type: string
description: Histogram buckets as [boundary_rule, lower, upper, count].
description: Histogram buckets. The default representation uses [boundary_rule, lower, upper, count] entries; when histogram_format=native was requested each entry is [index, count] for the positive buckets only.
schema:
type: integer
description: Bucket schema number. Only present when histogram_format=native was requested.
zero_threshold:
type: string
description: Zero-bucket threshold for exponential-schema histograms. Always present when histogram_format=native was requested for an exponential-schema histogram, because it is part of the schema definition.
zero_count:
type: string
description: Observation count in the zero bucket for exponential-schema histograms. Only present when histogram_format=native was requested and the zero-bucket count is non-zero.
negative_buckets:
type: array
items:
type: array
items:
oneOf:
- type: number
- type: string
description: Negative buckets for exponential-schema histograms as [index, count] pairs. Only present when histogram_format=native was requested.
boundaries:
type: array
items:
type: string
description: Bucket upper bounds for custom-bucket histograms (schema -53). Only present when histogram_format=native was requested.
required:
- count
- sum
additionalProperties: false
description: Native histogram value representation.
description: Native histogram value representation. The default response uses [boundary_rule, lower, upper, count] bucket entries. With histogram_format=native exponential histograms expose zero_threshold, optional zero_count, and optional negative_buckets/buckets ([index, count] pairs); custom-bucket histograms (schema -53) expose boundaries plus buckets.
LabelsOutputBody:
type: object
properties:

View file

@ -104,6 +104,16 @@ paths:
examples:
example:
value: all
- name: histogram_format
in: query
description: Request an alternative JSON representation for native histograms in the response. The only supported value is 'native', which emits raw bucket indices instead of bucket boundaries. Optional.
required: false
explode: false
schema:
type: string
examples:
example:
value: native
responses:
"200":
description: Query executed successfully.
@ -337,6 +347,16 @@ paths:
examples:
example:
value: all
- name: histogram_format
in: query
description: Request an alternative JSON representation for native histograms in the response. The only supported value is 'native', which emits raw bucket indices instead of bucket boundaries. Optional.
required: false
explode: false
schema:
type: string
examples:
example:
value: native
responses:
"200":
description: Range query executed successfully.
@ -3507,6 +3527,10 @@ components:
type: string
description: 'Form field: When provided, include query statistics in the response (the special value ''all'' enables more comprehensive statistics).'
example: all
histogram_format:
type: string
description: 'Form field: Alternative JSON representation for native histograms. Set to ''native'' to emit raw bucket indices instead of bucket boundaries (optional).'
example: native
required:
- query
additionalProperties: false
@ -3547,6 +3571,10 @@ components:
type: string
description: 'Form field: When provided, include query statistics in the response (the special value ''all'' enables more comprehensive statistics).'
example: all
histogram_format:
type: string
description: 'Form field: Alternative JSON representation for native histograms. Set to ''native'' to emit raw bucket indices instead of bucket boundaries (optional).'
example: native
required:
- query
- start
@ -3921,12 +3949,35 @@ components:
oneOf:
- type: number
- type: string
description: Histogram buckets as [boundary_rule, lower, upper, count].
description: Histogram buckets. The default representation uses [boundary_rule, lower, upper, count] entries; when histogram_format=native was requested each entry is [index, count] for the positive buckets only.
schema:
type: integer
description: Bucket schema number. Only present when histogram_format=native was requested.
zero_threshold:
type: string
description: Zero-bucket threshold for exponential-schema histograms. Always present when histogram_format=native was requested for an exponential-schema histogram, because it is part of the schema definition.
zero_count:
type: string
description: Observation count in the zero bucket for exponential-schema histograms. Only present when histogram_format=native was requested and the zero-bucket count is non-zero.
negative_buckets:
type: array
items:
type: array
items:
oneOf:
- type: number
- type: string
description: Negative buckets for exponential-schema histograms as [index, count] pairs. Only present when histogram_format=native was requested.
boundaries:
type: array
items:
type: string
description: Bucket upper bounds for custom-bucket histograms (schema -53). Only present when histogram_format=native was requested.
required:
- count
- sum
additionalProperties: false
description: Native histogram value representation.
description: Native histogram value representation. The default response uses [boundary_rule, lower, upper, count] bucket entries. With histogram_format=native exponential histograms expose zero_threshold, optional zero_count, and optional negative_buckets/buckets ([index, count] pairs); custom-bucket histograms (schema -53) expose boundaries plus buckets.
LabelsOutputBody:
type: object
properties: