diff --git a/api/v1/api.go b/api/v1/api.go index abf550ec..f7932b61 100644 --- a/api/v1/api.go +++ b/api/v1/api.go @@ -24,6 +24,7 @@ import ( "github.com/prometheus/common/route" "github.com/prometheus/pushgateway/handler" + "github.com/prometheus/pushgateway/histogram" "github.com/prometheus/pushgateway/storage" ) @@ -230,9 +231,21 @@ func makeEncodableMetrics(metrics []*dto.Metric, metricsType dto.MetricType) []e metric["count"] = fmt.Sprint(m.GetSummary().GetSampleCount()) metric["sum"] = fmt.Sprint(m.GetSummary().GetSampleSum()) case dto.MetricType_HISTOGRAM: - metric["buckets"] = makeBuckets(m) - metric["count"] = fmt.Sprint(m.GetHistogram().GetSampleCount()) metric["sum"] = fmt.Sprint(m.GetHistogram().GetSampleSum()) + if b := makeBuckets(m); len(b) > 0 { + metric["buckets"] = b + metric["count"] = fmt.Sprint(m.GetHistogram().GetSampleCount()) + } else { + h, fh := histogram.NewModelHistogram(m.GetHistogram()) + if h == nil { + // float histogram + metric["count"] = fmt.Sprint(fh.Count) + metric["buckets"] = histogram.BucketsAsJson[float64](histogram.GetAPIFloatBuckets(fh)) + } else { + metric["count"] = fmt.Sprint(h.Count) + metric["buckets"] = histogram.BucketsAsJson[uint64](histogram.GetAPIBuckets(h)) + } + } default: metric["value"] = fmt.Sprint(getValue(m)) } diff --git a/api/v1/api_test.go b/api/v1/api_test.go index c8bd3149..40efacc5 100644 --- a/api/v1/api_test.go +++ b/api/v1/api_test.go @@ -66,6 +66,82 @@ var ( }, }, } + mfh = &dto.MetricFamily{ + Name: proto.String("mfh"), + Type: dto.MetricType_HISTOGRAM.Enum(), + Metric: []*dto.Metric{ + { + Label: []*dto.LabelPair{ + { + Name: proto.String("testing"), + Value: proto.String("int histogram"), + }, + }, + Histogram: &dto.Histogram{ + SampleCount: proto.Uint64(20), + SampleSum: proto.Float64(99.23), + Schema: proto.Int32(1), + NegativeDelta: []int64{0, 2, -2, 0}, + PositiveDelta: []int64{0, 2, -2, 0}, + PositiveSpan: []*dto.BucketSpan{ + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + }, + NegativeSpan: []*dto.BucketSpan{ + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + }, + }, + }, + { + Label: []*dto.LabelPair{ + { + Name: proto.String("testing"), + Value: proto.String("float histogram"), + }, + }, + Histogram: &dto.Histogram{ + SampleCountFloat: proto.Float64(20), + SampleSum: proto.Float64(99.23), + Schema: proto.Int32(1), + NegativeCount: []float64{2, 2, -2, 0}, + PositiveCount: []float64{2, 2, -2, 0}, + PositiveSpan: []*dto.BucketSpan{ + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + }, + NegativeSpan: []*dto.BucketSpan{ + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + { + Offset: proto.Int32(0), + Length: proto.Uint32(2), + }, + }, + }, + }, + }, + } grouping1 = map[string]string{ "job": "Björn", @@ -142,7 +218,7 @@ func TestMetricsAPI(t *testing.T) { dms.SubmitWriteRequest(storage.WriteRequest{ Labels: grouping1, Timestamp: testTime, - MetricFamilies: testutil.MetricFamiliesMap(mf1), + MetricFamilies: testutil.MetricFamiliesMap(mf1, mfh), Done: errCh, }) @@ -181,6 +257,82 @@ func TestMetricsAPI(t *testing.T) { } ] }, + "mfh": { + "time_stamp": "2020-03-10T00:54:08.025744841+05:30", + "type": "HISTOGRAM", + "metrics": [ + { + "buckets": [ + [ + 1, + "-1.414213562373095", + "-1", + "2" + ], + [ + 0, + "1", + "1.414213562373095", + "2" + ] + ], + "count": "20", + "labels": { + "instance": "inst'a\"n\\ce1", + "job": "Björn", + "testing": "int histogram" + }, + "sum": "99.23" + }, + { + "buckets": [ + [ + 1, + "-2", + "-1.414213562373095", + "-2" + ], + [ + 1, + "-1.414213562373095", + "-1", + "2" + ], + [ + 1, + "-1", + "-0.7071067811865475", + "2" + ], + [ + 0, + "0.7071067811865475", + "1", + "2" + ], + [ + 0, + "1", + "1.414213562373095", + "2" + ], + [ + 0, + "1.414213562373095", + "2", + "-2" + ] + ], + "count": "20", + "labels": { + "instance": "inst'a\"n\\ce1", + "job": "Björn", + "testing": "float histogram" + }, + "sum": "99.23" + } + ] + }, "push_failure_time_seconds": { "time_stamp": "2020-03-10T00:54:08.025744841+05:30", "type": "GAUGE", diff --git a/go.mod b/go.mod index 26de2bfa..ce515d30 100644 --- a/go.mod +++ b/go.mod @@ -17,20 +17,25 @@ require ( ) require ( - github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/oauth2 v0.16.0 // indirect +) + +require ( + github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/go-logfmt/logfmt v0.5.1 // indirect + github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/jpillora/backoff v1.0.0 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f // indirect github.com/prometheus/procfs v0.12.0 // indirect - github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect + github.com/prometheus/prometheus v0.49.1 + github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect - golang.org/x/crypto v0.18.0 // indirect - golang.org/x/net v0.20.0 // indirect - golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.5.0 // indirect golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect diff --git a/go.sum b/go.sum index 309b3688..1ae225ab 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,7 @@ github.com/alecthomas/kingpin/v2 v2.4.0 h1:f48lwail6p8zpO1bC4TxtqACaGqHYA22qkHjHpqDjYY= github.com/alecthomas/kingpin/v2 v2.4.0/go.mod h1:0gyi0zQnjuFk8xrkNKamJoyUo382HRL7ATRpFZCw6tE= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 h1:s6gZFSlWYmbqAuRjVTiNNhvNRfY2Wxp9nhfyel4rklc= -github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9 h1:ez/4by2iGztzR4L0zgAOR8lTQK9VlyBVVd7G4omaOQs= +github.com/alecthomas/units v0.0.0-20231202071711-9a357b53e9c9/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= @@ -9,11 +9,11 @@ github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU= github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= -github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA= -github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4= +github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -34,8 +34,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zk github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f h1:KUppIJq7/+SVif2QVs3tOP0zanoHgBEVAwHxUSIzRqU= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/prometheus/client_golang v1.19.0 h1:ygXvpU1AoN1MhdzckN+PyD9QJOSD4x7kmXYlnfbA6JU= github.com/prometheus/client_golang v1.19.0/go.mod h1:ZRM9uEAypZakd+q/x7+gmsvXdURP+DABIEIjnmDdp+k= github.com/prometheus/client_model v0.6.0 h1:k1v3CzpSRUTrKMppY35TLwPvxHqBu0bYgxZzqGIgaos= @@ -46,19 +46,23 @@ github.com/prometheus/exporter-toolkit v0.11.0 h1:yNTsuZ0aNCNFQ3aFTD2uhPOvr4iD7f github.com/prometheus/exporter-toolkit v0.11.0/go.mod h1:BVnENhnNecpwoTLiABx7mrPB/OLRIgN74qlQbV+FK1Q= github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= +github.com/prometheus/prometheus v0.49.1 h1:90mDvjrFnca2m+0qPSIDr3y7iHPTAagOAElz7j+HtGk= +github.com/prometheus/prometheus v0.49.1/go.mod h1:aDogiyqmv3aBIWDb5z5Sdcxuuf2BOfiJwOIm9JGpMnI= github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 h1:bUGsEnyNbVPw06Bs80sCeARAlK8lhwqGyi6UT8ymuGk= -github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c h1:aqg5Vm5dwtvL+YgDpBcK1ITf3o96N/K7/wsRXQnUTEs= +github.com/shurcooL/httpfs v0.0.0-20230704072500-f1e31cf0ba5c/go.mod h1:owqhoLW1qZoYLZzLnBw+QkPP9WZnjlSWihhxAJC1+/M= github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92 h1:OfRzdxCzDhp+rsKWXuOO2I/quKMJ/+TQwVbIP/gltZg= github.com/shurcooL/vfsgen v0.0.0-20230704071429-0000e147ea92/go.mod h1:7/OT02F6S6I7v6WXb+IjhMuZEYfH/RJ5RwEWnEo5BMg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb h1:c0vyKkb6yr3KR7jEfJaOSv4lG7xPkbN6r52aJz1d8a8= +golang.org/x/exp v0.0.0-20231206192017-f3f8817b8deb/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= @@ -75,7 +79,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM= +golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/histogram/prometheus_model.go b/histogram/prometheus_model.go new file mode 100644 index 00000000..e0f0c782 --- /dev/null +++ b/histogram/prometheus_model.go @@ -0,0 +1,153 @@ +// Copyright 2020 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 histogram + +import ( + "fmt" + + dto "github.com/prometheus/client_model/go" + model "github.com/prometheus/prometheus/model/histogram" +) + +type APIBucket[BC model.BucketCount] struct { + Boundaries uint64 + Lower, Upper float64 + Count BC +} + +func NewModelHistogram(ch *dto.Histogram) (*model.Histogram, *model.FloatHistogram) { + if ch.GetSampleCountFloat() > 0 || ch.GetZeroCountFloat() > 0 { + // It is a float histogram. + fh := model.FloatHistogram{ + Count: ch.GetSampleCountFloat(), + Sum: ch.GetSampleSum(), + ZeroThreshold: ch.GetZeroThreshold(), + ZeroCount: ch.GetZeroCountFloat(), + Schema: ch.GetSchema(), + PositiveSpans: make([]model.Span, len(ch.GetPositiveSpan())), + PositiveBuckets: ch.GetPositiveCount(), + NegativeSpans: make([]model.Span, len(ch.GetNegativeSpan())), + NegativeBuckets: ch.GetNegativeCount(), + } + for i, span := range ch.GetPositiveSpan() { + fh.PositiveSpans[i].Offset = span.GetOffset() + fh.PositiveSpans[i].Length = span.GetLength() + } + for i, span := range ch.GetNegativeSpan() { + fh.NegativeSpans[i].Offset = span.GetOffset() + fh.NegativeSpans[i].Length = span.GetLength() + } + return nil, &fh + } + h := model.Histogram{ + Count: ch.GetSampleCount(), + Sum: ch.GetSampleSum(), + ZeroThreshold: ch.GetZeroThreshold(), + ZeroCount: ch.GetZeroCount(), + Schema: ch.GetSchema(), + PositiveSpans: make([]model.Span, len(ch.GetPositiveSpan())), + PositiveBuckets: ch.GetPositiveDelta(), + NegativeSpans: make([]model.Span, len(ch.GetNegativeSpan())), + NegativeBuckets: ch.GetNegativeDelta(), + } + for i, span := range ch.GetPositiveSpan() { + h.PositiveSpans[i].Offset = span.GetOffset() + h.PositiveSpans[i].Length = span.GetLength() + } + for i, span := range ch.GetNegativeSpan() { + h.NegativeSpans[i].Offset = span.GetOffset() + h.NegativeSpans[i].Length = span.GetLength() + } + return &h, nil +} + +func BucketsAsJson[BC model.BucketCount](buckets []APIBucket[BC]) [][]interface{} { + ret := make([][]interface{}, len(buckets)) + for i, b := range buckets { + ret[i] = []interface{}{b.Boundaries, fmt.Sprintf("%v", b.Lower), fmt.Sprintf("%v", b.Upper), fmt.Sprintf("%v", b.Count)} + } + return ret +} + +func GetAPIBuckets(h *model.Histogram) []APIBucket[uint64] { + var apiBuckets []APIBucket[uint64] + var nBuckets []model.Bucket[uint64] + for it := h.NegativeBucketIterator(); it.Next(); { + bucket := it.At() + if bucket.Count != 0 { + nBuckets = append(nBuckets, it.At()) + } + } + for i := len(nBuckets) - 1; i >= 0; i-- { + apiBuckets = append(apiBuckets, makeBucket[uint64](nBuckets[i])) + } + + if h.ZeroCount != 0 { + apiBuckets = append(apiBuckets, makeBucket[uint64](h.ZeroBucket())) + } + + for it := h.PositiveBucketIterator(); it.Next(); { + bucket := it.At() + if bucket.Count != 0 { + apiBuckets = append(apiBuckets, makeBucket[uint64](bucket)) + } + } + return apiBuckets +} + +func GetAPIFloatBuckets(h *model.FloatHistogram) []APIBucket[float64] { + var apiBuckets []APIBucket[float64] + var nBuckets []model.Bucket[float64] + for it := h.NegativeBucketIterator(); it.Next(); { + bucket := it.At() + if bucket.Count != 0 { + nBuckets = append(nBuckets, it.At()) + } + } + for i := len(nBuckets) - 1; i >= 0; i-- { + apiBuckets = append(apiBuckets, makeBucket[float64](nBuckets[i])) + } + + if h.ZeroCount != 0 { + apiBuckets = append(apiBuckets, makeBucket[float64](h.ZeroBucket())) + } + + for it := h.PositiveBucketIterator(); it.Next(); { + bucket := it.At() + if bucket.Count != 0 { + apiBuckets = append(apiBuckets, makeBucket[float64](bucket)) + } + } + return apiBuckets +} + +func makeBucket[BC model.BucketCount](bucket model.Bucket[BC]) APIBucket[BC] { + boundaries := uint64(2) // Exclusive on both sides AKA open interval. + if bucket.LowerInclusive { + if bucket.UpperInclusive { + boundaries = 3 // Inclusive on both sides AKA closed interval. + } else { + boundaries = 1 // Inclusive only on lower end AKA right open. + } + } else { + if bucket.UpperInclusive { + boundaries = 0 // Inclusive only on upper end AKA left open. + } + } + return APIBucket[BC]{ + Boundaries: boundaries, + Lower: bucket.Lower, + Upper: bucket.Upper, + Count: bucket.Count, + } +}