Merge pull request #9481 from prometheus/beorn7/cleanup

Style cleanup of all the changes in sparsehistogram so far
pull/9490/head
Björn Rabenstein 2021-10-11 14:01:36 +02:00 committed by GitHub
commit 12486b1250
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1926 additions and 1854 deletions

View File

@ -58,9 +58,9 @@ import (
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/discovery"
_ "github.com/prometheus/prometheus/discovery/install" // Register service discovery implementations.
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/notifier"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/logging"
"github.com/prometheus/prometheus/pkg/relabel"
@ -1190,7 +1190,7 @@ func (n notReadyAppender) AppendExemplar(ref uint64, l labels.Labels, e exemplar
return 0, tsdb.ErrNotReady
}
func (n notReadyAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, sh histogram.SparseHistogram) (uint64, error) {
func (n notReadyAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, sh histogram.Histogram) (uint64, error) {
return 0, tsdb.ErrNotReady
}

View File

@ -405,7 +405,6 @@ func readQueryLog(t *testing.T, path string) []queryLogLine {
}
func TestQueryLog(t *testing.T) {
t.Skip()
if testing.Short() {
t.Skip("skipping test in short mode.")
}

2
go.mod
View File

@ -62,7 +62,7 @@ require (
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34
golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac
golang.org/x/tools v0.1.5
golang.org/x/tools v0.1.7
google.golang.org/api v0.56.0
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83
google.golang.org/protobuf v1.27.1

6
go.sum
View File

@ -1308,6 +1308,7 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.0/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs=
github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA=
github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg=
@ -1497,6 +1498,7 @@ golang.org/x/net v0.0.0-20210520170846-37e1c6afe023/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f h1:w6wWR0H+nyVpbSAQbzVEIACVyr/h8l/BEkY6Sokc7Eg=
golang.org/x/net v0.0.0-20210903162142-ad29c8ab022f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
@ -1634,6 +1636,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34 h1:GkvMjFtXUmahfDtashnc1mnrCtuBVcwse5QV2lUk/tI=
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -1739,8 +1742,9 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -17,55 +17,90 @@ import (
"math"
)
// SparseHistogram encodes a sparse histogram
// full details: https://docs.google.com/document/d/1cLNv3aufPZb3fNfaJgdaRBZsInZKKIHo9E6HinJVbpM/edit#
// the most tricky bit is how bucket indices represent real bucket boundaries
// Histogram encodes a sparse, high-resolution histogram. See the design
// document for full details:
// https://docs.google.com/document/d/1cLNv3aufPZb3fNfaJgdaRBZsInZKKIHo9E6HinJVbpM/edit#
//
// an example for schema 0 (which doubles the size of consecutive buckets):
// The most tricky bit is how bucket indices represent real bucket boundaries.
// An example for schema 0 (by which each bucket is twice as wide as the
// previous bucket):
//
// buckets syntax (LE,GE) (-2,-1) (-1,-0.5) (-0.5,-0.25) ... (-0.001-0.001) ... (0.25-0.5)(0.5-1) (1-2) ....
// ^
// zero bucket (here width a width of 0.001) ZB
// pos bucket idx ... -1 0 1 2 3
// neg bucket idx 3 2 1 0 -1 ...
// actively used bucket indices themselves are represented by the spans
type SparseHistogram struct {
Schema int32
ZeroThreshold float64
ZeroCount, Count uint64
Sum float64
PositiveSpans, NegativeSpans []Span
// Bucket boundaries → [-2,-1) [-1,-0.5) [-0.5,-0.25) ... [-0.001,0.001] ... (0.25,0.5] (0.5,1] (1,2] ....
// ↑ ↑ ↑ ↑ ↑ ↑ ↑
// Zero bucket (width e.g. 0.001) → | | | ZB | | |
// Positive bucket indices → | | | ... -1 0 1 2 3
// Negative bucket indices → 3 2 1 0 -1 ...
//
// Wich bucket indices are actually used is determined by the spans.
type Histogram struct {
// Currently valid schema numbers are -4 <= n <= 8. They are all for
// base-2 bucket schemas, where 1 is a bucket boundary in each case, and
// then each power of two is divided into 2^n logarithmic buckets. Or
// in other words, each bucket boundary is the previous boundary times
// 2^(2^-n).
Schema int32
// Width of the zero bucket.
ZeroThreshold float64
// Observations falling into the zero bucket.
ZeroCount uint64
// Total number of observations.
Count uint64
// Sum of observations.
Sum float64
// Spans for positive and negative buckets (see Span below).
PositiveSpans, NegativeSpans []Span
// Observation counts in buckets. The first element is an absolute
// count. All following ones are deltas relative to the previous
// element.
PositiveBuckets, NegativeBuckets []int64
}
// A Span defines a continuous sequence of buckets.
type Span struct {
// Gap to previous span (always positive), or starting index for the 1st
// span (which can be negative).
Offset int32
// Length of the span.
Length uint32
}
func (s SparseHistogram) Copy() SparseHistogram {
c := s
// Copy returns a deep copy of the Histogram.
func (h Histogram) Copy() Histogram {
c := h
if s.PositiveSpans != nil {
c.PositiveSpans = make([]Span, len(s.PositiveSpans))
copy(c.PositiveSpans, s.PositiveSpans)
if h.PositiveSpans != nil {
c.PositiveSpans = make([]Span, len(h.PositiveSpans))
copy(c.PositiveSpans, h.PositiveSpans)
}
if s.NegativeSpans != nil {
c.NegativeSpans = make([]Span, len(s.NegativeSpans))
copy(c.NegativeSpans, s.NegativeSpans)
if h.NegativeSpans != nil {
c.NegativeSpans = make([]Span, len(h.NegativeSpans))
copy(c.NegativeSpans, h.NegativeSpans)
}
if s.PositiveBuckets != nil {
c.PositiveBuckets = make([]int64, len(s.PositiveBuckets))
copy(c.PositiveBuckets, s.PositiveBuckets)
if h.PositiveBuckets != nil {
c.PositiveBuckets = make([]int64, len(h.PositiveBuckets))
copy(c.PositiveBuckets, h.PositiveBuckets)
}
if s.NegativeBuckets != nil {
c.NegativeBuckets = make([]int64, len(s.NegativeBuckets))
copy(c.NegativeBuckets, s.NegativeBuckets)
if h.NegativeBuckets != nil {
c.NegativeBuckets = make([]int64, len(h.NegativeBuckets))
copy(c.NegativeBuckets, h.NegativeBuckets)
}
return c
}
// CumulativeBucketIterator returns a BucketIterator to iterate over a
// cumulative view of the buckets. This method currently only supports
// Histograms without negative buckets and panics if the Histogram has negative
// buckets. It is currently only used for testing.
func (h Histogram) CumulativeBucketIterator() BucketIterator {
if len(h.NegativeBuckets) > 0 {
panic("CumulativeIterator called on Histogram with negative buckets")
}
return &cumulativeBucketIterator{h: h, posSpansIdx: -1}
}
// BucketIterator iterates over the buckets of a Histogram, returning decoded
// buckets.
type BucketIterator interface {
// Next advances the iterator by one.
Next() bool
@ -76,28 +111,23 @@ type BucketIterator interface {
Err() error
}
// Bucket represents a bucket (currently only a cumulative one with an upper
// inclusive bound and a cumulative count).
type Bucket struct {
Le float64
Upper float64
Count uint64
}
// CumulativeExpandSparseHistogram expands the given histogram to produce cumulative buckets.
// It assumes that the total length of spans matches the number of buckets for pos and neg respectively.
// TODO: supports only positive buckets, also do for negative.
func CumulativeExpandSparseHistogram(h SparseHistogram) BucketIterator {
return &cumulativeBucketIterator{h: h, posSpansIdx: -1}
}
type cumulativeBucketIterator struct {
h SparseHistogram
h Histogram
posSpansIdx int // Index in h.PositiveSpans we are in. -1 means 0 bucket.
posBucketsIdx int // Index in h.PositiveBuckets
posBucketsIdx int // Index in h.PositiveBuckets.
idxInSpan uint32 // Index in the current span. 0 <= idxInSpan < span.Length.
initialised bool
currIdx int32 // The actual bucket index after decoding from spans.
currLe float64 // The upper boundary of the current bucket.
currUpper float64 // The upper boundary of the current bucket.
currCount int64 // Current non-cumulative count for the current bucket. Does not apply for empty bucket.
currCumulativeCount uint64 // Current "cumulative" count for the current bucket.
@ -116,7 +146,7 @@ func (c *cumulativeBucketIterator) Next() bool {
return c.Next()
}
c.currLe = c.h.ZeroThreshold
c.currUpper = c.h.ZeroThreshold
c.currCount = int64(c.h.ZeroCount)
c.currCumulativeCount = uint64(c.currCount)
return true
@ -128,7 +158,7 @@ func (c *cumulativeBucketIterator) Next() bool {
if c.emptyBucketCount > 0 {
// We are traversing through empty buckets at the moment.
c.currLe = getLe(c.currIdx, c.h.Schema)
c.currUpper = getUpper(c.currIdx, c.h.Schema)
c.currIdx++
c.emptyBucketCount--
return true
@ -145,7 +175,7 @@ func (c *cumulativeBucketIterator) Next() bool {
c.currCount += c.h.PositiveBuckets[c.posBucketsIdx]
c.currCumulativeCount += uint64(c.currCount)
c.currLe = getLe(c.currIdx, c.h.Schema)
c.currUpper = getUpper(c.currIdx, c.h.Schema)
c.posBucketsIdx++
c.idxInSpan++
@ -163,24 +193,26 @@ func (c *cumulativeBucketIterator) Next() bool {
}
func (c *cumulativeBucketIterator) At() Bucket {
return Bucket{
Le: c.currLe,
Upper: c.currUpper,
Count: c.currCumulativeCount,
}
}
func (c *cumulativeBucketIterator) Err() error { return nil }
func getLe(idx, schema int32) float64 {
func getUpper(idx, schema int32) float64 {
if schema < 0 {
return math.Ldexp(1, int(idx)<<(-schema))
}
fracIdx := idx & ((1 << schema) - 1)
frac := sparseBounds[schema][fracIdx]
frac := exponentialBounds[schema][fracIdx]
exp := (int(idx) >> schema) + 1
return math.Ldexp(frac, exp)
}
var sparseBounds = [][]float64{
// exponentialBounds is a precalculated table of bucket bounds in the interval
// [0.5,1) in schema 0 to 8.
var exponentialBounds = [][]float64{
// Schema "0":
{0.5},
// Schema 1:

View File

@ -0,0 +1,165 @@
// Copyright 2021 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"
"testing"
"github.com/stretchr/testify/require"
)
func TestCumulativeBucketIterator(t *testing.T) {
cases := []struct {
histogram Histogram
expectedCumulativeBuckets []Bucket
}{
{
histogram: Histogram{
Schema: 0,
PositiveSpans: []Span{
{Offset: 0, Length: 2},
{Offset: 1, Length: 2},
},
PositiveBuckets: []int64{1, 1, -1, 0},
},
expectedCumulativeBuckets: []Bucket{
{Upper: 1, Count: 1},
{Upper: 2, Count: 3},
{Upper: 4, Count: 3},
{Upper: 8, Count: 4},
{Upper: 16, Count: 5},
},
},
{
histogram: Histogram{
Schema: 0,
PositiveSpans: []Span{
{Offset: 0, Length: 5},
{Offset: 1, Length: 1},
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0},
},
expectedCumulativeBuckets: []Bucket{
{Upper: 1, Count: 1},
{Upper: 2, Count: 4},
{Upper: 4, Count: 5},
{Upper: 8, Count: 7},
{Upper: 16, Count: 8},
{Upper: 32, Count: 8},
{Upper: 64, Count: 9},
},
},
{
histogram: Histogram{
Schema: 0,
PositiveSpans: []Span{
{Offset: 0, Length: 7},
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0},
},
expectedCumulativeBuckets: []Bucket{
{Upper: 1, Count: 1},
{Upper: 2, Count: 4},
{Upper: 4, Count: 5},
{Upper: 8, Count: 7},
{Upper: 16, Count: 8},
{Upper: 32, Count: 9},
{Upper: 64, Count: 10},
},
},
{
histogram: Histogram{
Schema: 3,
PositiveSpans: []Span{
{Offset: -5, Length: 2}, // -5 -4
{Offset: 2, Length: 3}, // -1 0 1
{Offset: 2, Length: 2}, // 4 5
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 3},
},
expectedCumulativeBuckets: []Bucket{
{Upper: 0.6484197773255048, Count: 1}, // -5
{Upper: 0.7071067811865475, Count: 4}, // -4
{Upper: 0.7711054127039704, Count: 4}, // -3
{Upper: 0.8408964152537144, Count: 4}, // -2
{Upper: 0.9170040432046711, Count: 5}, // -1
{Upper: 1, Count: 7}, // 1
{Upper: 1.0905077326652577, Count: 8}, // 0
{Upper: 1.189207115002721, Count: 8}, // 1
{Upper: 1.2968395546510096, Count: 8}, // 2
{Upper: 1.414213562373095, Count: 9}, // 3
{Upper: 1.5422108254079407, Count: 13}, // 4
},
},
{
histogram: Histogram{
Schema: -2,
PositiveSpans: []Span{
{Offset: -2, Length: 4}, // -2 -1 0 1
{Offset: 2, Length: 2}, // 4 5
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0},
},
expectedCumulativeBuckets: []Bucket{
{Upper: 0.00390625, Count: 1}, // -2
{Upper: 0.0625, Count: 4}, // -1
{Upper: 1, Count: 5}, // 0
{Upper: 16, Count: 7}, // 1
{Upper: 256, Count: 7}, // 2
{Upper: 4096, Count: 7}, // 3
{Upper: 65536, Count: 8}, // 4
{Upper: 1048576, Count: 9}, // 5
},
},
{
histogram: Histogram{
Schema: -1,
PositiveSpans: []Span{
{Offset: -2, Length: 5}, // -2 -1 0 1 2
},
PositiveBuckets: []int64{1, 2, -2, 1, -1},
},
expectedCumulativeBuckets: []Bucket{
{Upper: 0.0625, Count: 1}, // -2
{Upper: 0.25, Count: 4}, // -1
{Upper: 1, Count: 5}, // 0
{Upper: 4, Count: 7}, // 1
{Upper: 16, Count: 8}, // 2
},
},
}
for i, c := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
it := c.histogram.CumulativeBucketIterator()
actualBuckets := make([]Bucket, 0, len(c.expectedCumulativeBuckets))
for it.Next() {
actualBuckets = append(actualBuckets, it.At())
}
require.NoError(t, it.Err())
require.Equal(t, c.expectedCumulativeBuckets, actualBuckets)
})
}
}

View File

@ -1,165 +0,0 @@
// Copyright 2021 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"
"testing"
"github.com/stretchr/testify/require"
)
func TestCumulativeExpandSparseHistogram(t *testing.T) {
cases := []struct {
hist SparseHistogram
expBuckets []Bucket
}{
{
hist: SparseHistogram{
Schema: 0,
PositiveSpans: []Span{
{Offset: 0, Length: 2},
{Offset: 1, Length: 2},
},
PositiveBuckets: []int64{1, 1, -1, 0},
},
expBuckets: []Bucket{
{Le: 1, Count: 1},
{Le: 2, Count: 3},
{Le: 4, Count: 3},
{Le: 8, Count: 4},
{Le: 16, Count: 5},
},
},
{
hist: SparseHistogram{
Schema: 0,
PositiveSpans: []Span{
{Offset: 0, Length: 5},
{Offset: 1, Length: 1},
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0},
},
expBuckets: []Bucket{
{Le: 1, Count: 1},
{Le: 2, Count: 4},
{Le: 4, Count: 5},
{Le: 8, Count: 7},
{Le: 16, Count: 8},
{Le: 32, Count: 8},
{Le: 64, Count: 9},
},
},
{
hist: SparseHistogram{
Schema: 0,
PositiveSpans: []Span{
{Offset: 0, Length: 7},
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 0},
},
expBuckets: []Bucket{
{Le: 1, Count: 1},
{Le: 2, Count: 4},
{Le: 4, Count: 5},
{Le: 8, Count: 7},
{Le: 16, Count: 8},
{Le: 32, Count: 9},
{Le: 64, Count: 10},
},
},
{
hist: SparseHistogram{
Schema: 3,
PositiveSpans: []Span{
{Offset: -5, Length: 2}, // -5 -4
{Offset: 2, Length: 3}, // -1 0 1
{Offset: 2, Length: 2}, // 4 5
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0, 3},
},
expBuckets: []Bucket{
{Le: 0.6484197773255048, Count: 1}, // -5
{Le: 0.7071067811865475, Count: 4}, // -4
{Le: 0.7711054127039704, Count: 4}, // -3
{Le: 0.8408964152537144, Count: 4}, // -2
{Le: 0.9170040432046711, Count: 5}, // -1
{Le: 1, Count: 7}, // 1
{Le: 1.0905077326652577, Count: 8}, // 0
{Le: 1.189207115002721, Count: 8}, // 1
{Le: 1.2968395546510096, Count: 8}, // 2
{Le: 1.414213562373095, Count: 9}, // 3
{Le: 1.5422108254079407, Count: 13}, // 4
},
},
{
hist: SparseHistogram{
Schema: -2,
PositiveSpans: []Span{
{Offset: -2, Length: 4}, // -2 -1 0 1
{Offset: 2, Length: 2}, // 4 5
},
PositiveBuckets: []int64{1, 2, -2, 1, -1, 0},
},
expBuckets: []Bucket{
{Le: 0.00390625, Count: 1}, // -2
{Le: 0.0625, Count: 4}, // -1
{Le: 1, Count: 5}, // 0
{Le: 16, Count: 7}, // 1
{Le: 256, Count: 7}, // 2
{Le: 4096, Count: 7}, // 3
{Le: 65536, Count: 8}, // 4
{Le: 1048576, Count: 9}, // 5
},
},
{
hist: SparseHistogram{
Schema: -1,
PositiveSpans: []Span{
{Offset: -2, Length: 5}, // -2 -1 0 1 2
},
PositiveBuckets: []int64{1, 2, -2, 1, -1},
},
expBuckets: []Bucket{
{Le: 0.0625, Count: 1}, // -2
{Le: 0.25, Count: 4}, // -1
{Le: 1, Count: 5}, // 0
{Le: 4, Count: 7}, // 1
{Le: 16, Count: 8}, // 2
},
},
}
for i, c := range cases {
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
it := CumulativeExpandSparseHistogram(c.hist)
actBuckets := make([]Bucket, 0, len(c.expBuckets))
for it.Next() {
actBuckets = append(actBuckets, it.At())
}
require.NoError(t, it.Err())
require.Equal(t, c.expBuckets, actBuckets)
})
}
}

View File

@ -16,8 +16,8 @@ package textparse
import (
"mime"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
)
@ -29,9 +29,8 @@ type Parser interface {
Series() ([]byte, *int64, float64)
// Histogram returns the bytes of a series with a sparse histogram as a
// value, the timestamp if set, and the sparse histogram in the current
// sample.
Histogram() ([]byte, *int64, histogram.SparseHistogram)
// value, the timestamp if set, and the histogram in the current sample.
Histogram() ([]byte, *int64, histogram.Histogram)
// Help returns the metric name and help text in the current entry.
// Must only be called after Next returned a help entry.

View File

@ -27,8 +27,8 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/value"
)
@ -114,10 +114,10 @@ func (p *OpenMetricsParser) Series() ([]byte, *int64, float64) {
return p.series, nil, p.val
}
// Histogram always returns (nil, nil, SparseHistogram{}) because OpenMetrics
// does not support sparse histograms.
func (p *OpenMetricsParser) Histogram() ([]byte, *int64, histogram.SparseHistogram) {
return nil, nil, histogram.SparseHistogram{}
// Histogram always returns (nil, nil, histogram.Histogram{}) because
// OpenMetrics does not support sparse histograms.
func (p *OpenMetricsParser) Histogram() ([]byte, *int64, histogram.Histogram) {
return nil, nil, histogram.Histogram{}
}
// Help returns the metric name and help text in the current entry.

View File

@ -28,8 +28,8 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/value"
)
@ -169,10 +169,10 @@ func (p *PromParser) Series() ([]byte, *int64, float64) {
return p.series, nil, p.val
}
// Histogram always returns (nil, nil, SparseHistogram{}) because the Prometheus
// text format does not support sparse histograms.
func (p *PromParser) Histogram() ([]byte, *int64, histogram.SparseHistogram) {
return nil, nil, histogram.SparseHistogram{}
// Histogram always returns (nil, nil, histogram.Histogram{}) because the
// Prometheus text format does not support sparse histograms.
func (p *PromParser) Histogram() ([]byte, *int64, histogram.Histogram) {
return nil, nil, histogram.Histogram{}
}
// Help returns the metric name and help text in the current entry.

View File

@ -27,8 +27,8 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
dto "github.com/prometheus/prometheus/prompb/io/prometheus/client"
@ -65,6 +65,7 @@ type ProtobufParser struct {
metricBytes *bytes.Buffer // A somewhat fluid representation of the current metric.
}
// NewProtobufParser returns a parser for the payload in the byte slice.
func NewProtobufParser(b []byte) Parser {
return &ProtobufParser{
in: b,
@ -134,13 +135,13 @@ func (p *ProtobufParser) Series() ([]byte, *int64, float64) {
// Histogram returns the bytes of a series with a sparse histogram as a
// value, the timestamp if set, and the sparse histogram in the current
// sample.
func (p *ProtobufParser) Histogram() ([]byte, *int64, histogram.SparseHistogram) {
func (p *ProtobufParser) Histogram() ([]byte, *int64, histogram.Histogram) {
var (
m = p.mf.GetMetric()[p.metricPos]
ts = m.GetTimestampMs()
h = m.GetHistogram()
)
sh := histogram.SparseHistogram{
sh := histogram.Histogram{
Count: h.GetSampleCount(),
Sum: h.GetSampleSum(),
ZeroThreshold: h.GetSbZeroThreshold(),

View File

@ -22,8 +22,8 @@ import (
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
dto "github.com/prometheus/prometheus/prompb/io/prometheus/client"
@ -266,7 +266,7 @@ metric: <
help string
unit string
comment string
shs histogram.SparseHistogram
shs histogram.Histogram
e []exemplar.Exemplar
}{
{
@ -332,7 +332,7 @@ metric: <
{
m: "test_histogram",
t: 1234568,
shs: histogram.SparseHistogram{
shs: histogram.Histogram{
Count: 175,
ZeroCount: 2,
Sum: 0.0008280461746287094,

View File

@ -21,7 +21,7 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/storage"
@ -296,8 +296,11 @@ func (ssi *storageSeriesIterator) At() (t int64, v float64) {
return p.T, p.V
}
func (ssi *storageSeriesIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return 0, histogram.SparseHistogram{}
// AtHistogram always returns (0, histogram.Histogram{}) because there is no
// support for histogram values yet.
// TODO(beorn7): Fix that for histogram support in PromQL.
func (ssi *storageSeriesIterator) AtHistogram() (int64, histogram.Histogram) {
return 0, histogram.Histogram{}
}
func (ssi *storageSeriesIterator) ChunkEncoding() chunkenc.Encoding {

View File

@ -17,8 +17,8 @@ import (
"context"
"math/rand"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/storage"
)
@ -35,7 +35,7 @@ func (a nopAppender) Append(uint64, labels.Labels, int64, float64) (uint64, erro
func (a nopAppender) AppendExemplar(uint64, labels.Labels, exemplar.Exemplar) (uint64, error) {
return 0, nil
}
func (a nopAppender) AppendHistogram(uint64, labels.Labels, int64, histogram.SparseHistogram) (uint64, error) {
func (a nopAppender) AppendHistogram(uint64, labels.Labels, int64, histogram.Histogram) (uint64, error) {
return 0, nil
}
func (a nopAppender) Commit() error { return nil }
@ -47,9 +47,9 @@ type sample struct {
v float64
}
type hist struct {
h histogram.SparseHistogram
type histogramSample struct {
t int64
h histogram.Histogram
}
// collectResultAppender records all samples that were added through the appender.
@ -61,9 +61,9 @@ type collectResultAppender struct {
rolledbackResult []sample
pendingExemplars []exemplar.Exemplar
resultExemplars []exemplar.Exemplar
resultHistograms []hist
pendingHistograms []hist
rolledbackHistograms []hist
resultHistograms []histogramSample
pendingHistograms []histogramSample
rolledbackHistograms []histogramSample
}
func (a *collectResultAppender) Append(ref uint64, lset labels.Labels, t int64, v float64) (uint64, error) {
@ -96,13 +96,13 @@ func (a *collectResultAppender) AppendExemplar(ref uint64, l labels.Labels, e ex
return a.next.AppendExemplar(ref, l, e)
}
func (a *collectResultAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, sh histogram.SparseHistogram) (uint64, error) {
a.pendingHistograms = append(a.pendingHistograms, hist{h: sh, t: t})
func (a *collectResultAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, h histogram.Histogram) (uint64, error) {
a.pendingHistograms = append(a.pendingHistograms, histogramSample{h: h, t: t})
if a.next == nil {
return 0, nil
}
return a.next.AppendHistogram(ref, l, t, sh)
return a.next.AppendHistogram(ref, l, t, h)
}
func (a *collectResultAppender) Commit() error {

View File

@ -39,8 +39,8 @@ import (
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/discovery/targetgroup"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/pool"
"github.com/prometheus/prometheus/pkg/relabel"
@ -1411,7 +1411,7 @@ loop:
met []byte
parsedTimestamp *int64
val float64
his histogram.SparseHistogram
h histogram.Histogram
)
if et, err = p.Next(); err != nil {
if err == io.EOF {
@ -1439,7 +1439,7 @@ loop:
t := defTime
if isHistogram {
met, parsedTimestamp, his = p.Histogram()
met, parsedTimestamp, h = p.Histogram()
} else {
met, parsedTimestamp, val = p.Series()
}
@ -1491,7 +1491,7 @@ loop:
}
if isHistogram {
ref, err = app.AppendHistogram(ref, lset, t, his)
ref, err = app.AppendHistogram(ref, lset, t, h)
} else {
ref, err = app.Append(ref, lset, t, val)
}
@ -1554,7 +1554,6 @@ loop:
if err == nil {
sl.cache.forEachStale(func(lset labels.Labels) bool {
// Series no longer exposed, mark it stale.
// TODO(beorn7): Appending staleness markers breaks horribly for histograms.
_, err = app.Append(0, lset, defTime, math.Float64frombits(value.StaleNaN))
switch errors.Cause(err) {
case storage.ErrOutOfOrderSample, storage.ErrDuplicateSampleForTimestamp:

View File

@ -16,7 +16,7 @@ package storage
import (
"math"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/tsdb/chunkenc"
)
@ -198,8 +198,11 @@ func (it *sampleRingIterator) At() (int64, float64) {
return it.r.at(it.i)
}
func (it *sampleRingIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return 0, histogram.SparseHistogram{}
// AtHistogram always returns (0, histogram.Histogram{}) because there is no
// support for histogram values yet.
// TODO(beorn7): Fix that for histogram support in PromQL.
func (it *sampleRingIterator) AtHistogram() (int64, histogram.Histogram) {
return 0, histogram.Histogram{}
}
func (it *sampleRingIterator) ChunkEncoding() chunkenc.Encoding {

View File

@ -17,7 +17,7 @@ import (
"math/rand"
"testing"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/stretchr/testify/require"
)
@ -196,8 +196,8 @@ type mockSeriesIterator struct {
func (m *mockSeriesIterator) Seek(t int64) bool { return m.seek(t) }
func (m *mockSeriesIterator) At() (int64, float64) { return m.at() }
func (m *mockSeriesIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return 0, histogram.SparseHistogram{}
func (m *mockSeriesIterator) AtHistogram() (int64, histogram.Histogram) {
return 0, histogram.Histogram{}
}
func (m *mockSeriesIterator) ChunkEncoding() chunkenc.Encoding {
return chunkenc.EncXOR
@ -219,8 +219,8 @@ func (it *fakeSeriesIterator) At() (int64, float64) {
return it.idx * it.step, 123 // value doesn't matter
}
func (it *fakeSeriesIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return it.idx * it.step, histogram.SparseHistogram{} // value doesn't matter
func (it *fakeSeriesIterator) AtHistogram() (int64, histogram.Histogram) {
return it.idx * it.step, histogram.Histogram{} // value doesn't matter
}
func (it *fakeSeriesIterator) ChunkEncoding() chunkenc.Encoding {

View File

@ -20,8 +20,8 @@ import (
"github.com/go-kit/log/level"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
)
@ -173,14 +173,14 @@ func (f *fanoutAppender) AppendExemplar(ref uint64, l labels.Labels, e exemplar.
return ref, nil
}
func (f *fanoutAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, sh histogram.SparseHistogram) (uint64, error) {
ref, err := f.primary.AppendHistogram(ref, l, t, sh)
func (f *fanoutAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, h histogram.Histogram) (uint64, error) {
ref, err := f.primary.AppendHistogram(ref, l, t, h)
if err != nil {
return ref, err
}
for _, appender := range f.secondaries {
if _, err := appender.AppendHistogram(ref, l, t, sh); err != nil {
if _, err := appender.AppendHistogram(ref, l, t, h); err != nil {
return 0, err
}
}

View File

@ -18,8 +18,8 @@ import (
"errors"
"fmt"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/chunks"
@ -213,17 +213,16 @@ type ExemplarAppender interface {
AppendExemplar(ref uint64, l labels.Labels, e exemplar.Exemplar) (uint64, error)
}
// HistogramAppender provides an interface for adding sparse histogram to the Prometheus.
// HistogramAppender provides an interface for appending histograms to the storage.
type HistogramAppender interface {
// AppendHistogram adds a sparse histogram for the given series labels.
// An optional reference number can be provided to accelerate calls.
// A reference number is returned which can be used to add further
// histograms in the same or later transactions.
// Returned reference numbers are ephemeral and may be rejected in calls
// to Append() at any point. Adding the sample via Append() returns a new
// reference number.
// AppendHistogram adds a histogram for the given series labels. An
// optional reference number can be provided to accelerate calls. A
// reference number is returned which can be used to add further
// histograms in the same or later transactions. Returned reference
// numbers are ephemeral and may be rejected in calls to Append() at any
// point. Adding the sample via Append() returns a new reference number.
// If the reference is 0 it must not be used for caching.
AppendHistogram(ref uint64, l labels.Labels, t int64, sh histogram.SparseHistogram) (uint64, error)
AppendHistogram(ref uint64, l labels.Labels, t int64, h histogram.Histogram) (uint64, error)
}
// SeriesSet contains a set of series.

View File

@ -22,7 +22,7 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/chunks"
@ -465,7 +465,7 @@ func (c *chainSampleIterator) Seek(t int64) bool {
}
if len(c.h) > 0 {
c.curr = heap.Pop(&c.h).(chunkenc.Iterator)
if c.curr.ChunkEncoding() == chunkenc.EncSHS {
if c.curr.ChunkEncoding() == chunkenc.EncHistogram {
c.lastt, _ = c.curr.AtHistogram()
} else {
c.lastt, _ = c.curr.At()
@ -483,7 +483,7 @@ func (c *chainSampleIterator) At() (t int64, v float64) {
return c.curr.At()
}
func (c *chainSampleIterator) AtHistogram() (int64, histogram.SparseHistogram) {
func (c *chainSampleIterator) AtHistogram() (int64, histogram.Histogram) {
if c.curr == nil {
panic("chainSampleIterator.AtHistogram() called before first .Next() or after .Next() returned false.")
}
@ -517,7 +517,7 @@ func (c *chainSampleIterator) Next() bool {
var currt int64
for {
if c.curr.Next() {
if c.curr.ChunkEncoding() == chunkenc.EncSHS {
if c.curr.ChunkEncoding() == chunkenc.EncHistogram {
currt, _ = c.curr.AtHistogram()
} else {
currt, _ = c.curr.At()
@ -534,7 +534,7 @@ func (c *chainSampleIterator) Next() bool {
// Check current iterator with the top of the heap.
var nextt int64
if c.h[0].ChunkEncoding() == chunkenc.EncSHS {
if c.h[0].ChunkEncoding() == chunkenc.EncHistogram {
nextt, _ = c.h[0].AtHistogram()
} else {
nextt, _ = c.h[0].At()
@ -552,7 +552,7 @@ func (c *chainSampleIterator) Next() bool {
}
c.curr = heap.Pop(&c.h).(chunkenc.Iterator)
if c.curr.ChunkEncoding() == chunkenc.EncSHS {
if c.curr.ChunkEncoding() == chunkenc.EncHistogram {
currt, _ = c.curr.AtHistogram()
} else {
currt, _ = c.curr.At()
@ -581,12 +581,12 @@ func (h samplesIteratorHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h samplesIteratorHeap) Less(i, j int) bool {
var at, bt int64
if h[i].ChunkEncoding() == chunkenc.EncSHS {
if h[i].ChunkEncoding() == chunkenc.EncHistogram {
at, _ = h[i].AtHistogram()
} else {
at, _ = h[i].At()
}
if h[j].ChunkEncoding() == chunkenc.EncSHS {
if h[j].ChunkEncoding() == chunkenc.EncHistogram {
bt, _ = h[j].AtHistogram()
} else {
bt, _ = h[j].At()

View File

@ -26,8 +26,8 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/textparse"
"github.com/prometheus/prometheus/prompb"
@ -370,8 +370,11 @@ func (c *concreteSeriesIterator) At() (t int64, v float64) {
return s.Timestamp, s.Value
}
func (c *concreteSeriesIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return 0, histogram.SparseHistogram{}
// AtHistogram always returns (0, histogram.Histogram{}) because there is no
// support for histogram values yet.
// TODO(beorn7): Fix that for histogram support in remote storage.
func (c *concreteSeriesIterator) AtHistogram() (int64, histogram.Histogram) {
return 0, histogram.Histogram{}
}
func (c *concreteSeriesIterator) ChunkEncoding() chunkenc.Encoding {

View File

@ -24,8 +24,8 @@ import (
"github.com/prometheus/client_golang/prometheus/promauto"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/wal"
@ -241,7 +241,7 @@ func (t *timestampTracker) AppendExemplar(_ uint64, _ labels.Labels, _ exemplar.
return 0, nil
}
func (t *timestampTracker) AppendHistogram(_ uint64, _ labels.Labels, ts int64, _ histogram.SparseHistogram) (uint64, error) {
func (t *timestampTracker) AppendHistogram(_ uint64, _ labels.Labels, ts int64, _ histogram.Histogram) (uint64, error) {
t.histograms++
if ts > t.highestTimestamp {
t.highestTimestamp = ts

View File

@ -25,8 +25,8 @@ import (
"github.com/go-kit/log"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/prompb"
"github.com/prometheus/prometheus/storage"
@ -188,7 +188,7 @@ func (m *mockAppendable) AppendExemplar(_ uint64, l labels.Labels, e exemplar.Ex
return 0, nil
}
func (*mockAppendable) AppendHistogram(ref uint64, l labels.Labels, t int64, sh histogram.SparseHistogram) (uint64, error) {
// noop until we implement sparse histograms over remote write
func (*mockAppendable) AppendHistogram(ref uint64, l labels.Labels, t int64, h histogram.Histogram) (uint64, error) {
// TODO(beorn7): Noop until we implement sparse histograms over remote write.
return 0, nil
}

View File

@ -17,7 +17,7 @@ import (
"math"
"sort"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/chunks"
@ -91,8 +91,10 @@ func (it *listSeriesIterator) At() (int64, float64) {
return s.T(), s.V()
}
func (it *listSeriesIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return 0, histogram.SparseHistogram{}
// AtHistogram always returns (0, histogram.Histogram{}) because there is no
// support for histogram values yet.
func (it *listSeriesIterator) AtHistogram() (int64, histogram.Histogram) {
return 0, histogram.Histogram{}
}
func (it *listSeriesIterator) ChunkEncoding() chunkenc.Encoding {

View File

@ -18,7 +18,7 @@ import (
"sync"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
)
// Encoding is the identifier for a chunk encoding.
@ -30,8 +30,8 @@ func (e Encoding) String() string {
return "none"
case EncXOR:
return "XOR"
case EncSHS:
return "SHS"
case EncHistogram:
return "histogram"
}
return "<unknown>"
}
@ -39,7 +39,7 @@ func (e Encoding) String() string {
// IsValidEncoding returns true for supported encodings.
func IsValidEncoding(e Encoding) bool {
switch e {
case EncXOR, EncSHS:
case EncXOR, EncHistogram:
return true
}
return false
@ -49,7 +49,7 @@ func IsValidEncoding(e Encoding) bool {
const (
EncNone Encoding = iota
EncXOR
EncSHS
EncHistogram
)
// Chunk holds a sequence of sample pairs that can be iterated over and appended to.
@ -82,7 +82,7 @@ type Chunk interface {
// Appender adds sample pairs to a chunk.
type Appender interface {
Append(int64, float64)
AppendHistogram(t int64, h histogram.SparseHistogram)
AppendHistogram(t int64, h histogram.Histogram)
}
// Iterator is a simple iterator that can only get the next value.
@ -100,7 +100,7 @@ type Iterator interface {
At() (int64, float64)
// AtHistogram returns the current timestamp/histogram pair.
// Before the iterator has advanced AtHistogram behaviour is unspecified.
AtHistogram() (int64, histogram.SparseHistogram)
AtHistogram() (int64, histogram.Histogram)
// Err returns the current error. It should be used only after iterator is
// exhausted, that is `Next` or `Seek` returns false.
Err() error
@ -117,8 +117,8 @@ type nopIterator struct{}
func (nopIterator) Seek(int64) bool { return false }
func (nopIterator) At() (int64, float64) { return math.MinInt64, 0 }
func (nopIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return math.MinInt64, histogram.SparseHistogram{}
func (nopIterator) AtHistogram() (int64, histogram.Histogram) {
return math.MinInt64, histogram.Histogram{}
}
func (nopIterator) Next() bool { return false }
func (nopIterator) Err() error { return nil }
@ -132,8 +132,8 @@ type Pool interface {
// pool is a memory pool of chunk objects.
type pool struct {
xor sync.Pool
shs sync.Pool
xor sync.Pool
histogram sync.Pool
}
// NewPool returns a new pool.
@ -144,9 +144,9 @@ func NewPool() Pool {
return &XORChunk{b: bstream{}}
},
},
shs: sync.Pool{
histogram: sync.Pool{
New: func() interface{} {
return &HistoChunk{b: bstream{}}
return &HistogramChunk{b: bstream{}}
},
},
}
@ -159,9 +159,9 @@ func (p *pool) Get(e Encoding, b []byte) (Chunk, error) {
c.b.stream = b
c.b.count = 0
return c, nil
case EncSHS:
case EncHistogram:
// TODO: update metadata
c := p.shs.Get().(*HistoChunk)
c := p.histogram.Get().(*HistogramChunk)
c.b.stream = b
c.b.count = 0
return c, nil
@ -182,9 +182,9 @@ func (p *pool) Put(c Chunk) error {
xc.b.stream = nil
xc.b.count = 0
p.xor.Put(c)
case EncSHS:
case EncHistogram:
// TODO: update metadata
sh, ok := c.(*HistoChunk)
sh, ok := c.(*HistogramChunk)
// This may happen often with wrapped chunks. Nothing we can really do about
// it but returning an error would cause a lot of allocations again. Thus,
// we just skip it.
@ -193,7 +193,7 @@ func (p *pool) Put(c Chunk) error {
}
sh.b.stream = nil
sh.b.count = 0
p.shs.Put(c)
p.histogram.Put(c)
default:
return errors.Errorf("invalid chunk encoding %q", c.Encoding())
}
@ -207,9 +207,9 @@ func FromData(e Encoding, d []byte) (Chunk, error) {
switch e {
case EncXOR:
return &XORChunk{b: bstream{count: 0, stream: d}}, nil
case EncSHS:
case EncHistogram:
// TODO: update metadata
return &HistoChunk{b: bstream{count: 0, stream: d}}, nil
return &HistogramChunk{b: bstream{count: 0, stream: d}}, nil
}
return nil, errors.Errorf("invalid chunk encoding %q", e)
}
@ -219,8 +219,8 @@ func NewEmptyChunk(e Encoding) (Chunk, error) {
switch e {
case EncXOR:
return NewXORChunk(), nil
case EncSHS:
return NewHistoChunk(), nil
case EncHistogram:
return NewHistogramChunk(), nil
}
return nil, errors.Errorf("invalid chunk encoding %q", e)
}

View File

@ -1,943 +0,0 @@
// Copyright 2021 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.
// The code in this file was largely written by Damian Gryski as part of
// https://github.com/dgryski/go-tsz and published under the license below.
// It was modified to accommodate reading from byte slices without modifying
// the underlying bytes, which would panic when reading from mmap'd
// read-only byte slices.
// Copyright (c) 2015,2016 Damian Gryski <damian@gryski.com>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package chunkenc
import (
"encoding/binary"
"math"
"math/bits"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/value"
)
const ()
// HistoChunk holds sparse histogram encoded sample data.
// Appends a histogram sample
// * schema defines the resolution (number of buckets per power of 2)
// Currently, valid numbers are -4 <= n <= 8.
// They are all for base-2 bucket schemas, where 1 is a bucket boundary in each case, and
// then each power of two is divided into 2^n logarithmic buckets.
// Or in other words, each bucket boundary is the previous boundary times 2^(2^-n).
// In the future, more bucket schemas may be added using numbers < -4 or > 8.
// The bucket with upper boundary of 1 is always bucket 0.
// Then negative numbers for smaller boundaries and positive for uppers.
//
// fields are stored like so:
// field ts count zeroCount sum []posbuckets negbuckets
// observation 1 raw raw raw raw []raw []raw
// observation 2 delta delta delta xor []delta []delta
// observation >2 dod dod dod xor []dod []dod
// TODO zerothreshold
type HistoChunk struct {
b bstream
}
// NewHistoChunk returns a new chunk with Histo encoding of the given size.
func NewHistoChunk() *HistoChunk {
b := make([]byte, 3, 128)
return &HistoChunk{b: bstream{stream: b, count: 0}}
}
// Encoding returns the encoding type.
func (c *HistoChunk) Encoding() Encoding {
return EncSHS
}
// Bytes returns the underlying byte slice of the chunk.
func (c *HistoChunk) Bytes() []byte {
return c.b.bytes()
}
// NumSamples returns the number of samples in the chunk.
func (c *HistoChunk) NumSamples() int {
return int(binary.BigEndian.Uint16(c.Bytes()))
}
// Meta returns the histogram metadata.
// callers may only call this on chunks that have at least one sample
func (c *HistoChunk) Meta() (int32, float64, []histogram.Span, []histogram.Span, error) {
if c.NumSamples() == 0 {
panic("HistoChunk.Meta() called on an empty chunk")
}
b := newBReader(c.Bytes()[2:])
return readHistoChunkMeta(&b)
}
// CounterResetHeader defines the first 2 bits of the chunk header.
type CounterResetHeader byte
const (
CounterReset CounterResetHeader = 0b10000000
NotCounterReset CounterResetHeader = 0b01000000
GaugeType CounterResetHeader = 0b11000000
UnknownCounterReset CounterResetHeader = 0b00000000
)
// SetCounterResetHeader sets the counter reset header.
func (c *HistoChunk) SetCounterResetHeader(h CounterResetHeader) {
switch h {
case CounterReset, NotCounterReset, GaugeType, UnknownCounterReset:
bytes := c.Bytes()
bytes[2] = (bytes[2] & 0b00111111) | byte(h)
default:
panic("invalid CounterResetHeader type")
}
}
// GetCounterResetHeader returns the info about the first 2 bits of the chunk header.
func (c *HistoChunk) GetCounterResetHeader() CounterResetHeader {
return CounterResetHeader(c.Bytes()[2] & 0b11000000)
}
// Compact implements the Chunk interface.
func (c *HistoChunk) Compact() {
if l := len(c.b.stream); cap(c.b.stream) > l+chunkCompactCapacityThreshold {
buf := make([]byte, l)
copy(buf, c.b.stream)
c.b.stream = buf
}
}
// Appender implements the Chunk interface.
func (c *HistoChunk) Appender() (Appender, error) {
it := c.iterator(nil)
// To get an appender we must know the state it would have if we had
// appended all existing data from scratch.
// We iterate through the end and populate via the iterator's state.
for it.Next() {
}
if err := it.Err(); err != nil {
return nil, err
}
a := &HistoAppender{
b: &c.b,
schema: it.schema,
zeroThreshold: it.zeroThreshold,
posSpans: it.posSpans,
negSpans: it.negSpans,
t: it.t,
cnt: it.cnt,
zcnt: it.zcnt,
tDelta: it.tDelta,
cntDelta: it.cntDelta,
zcntDelta: it.zcntDelta,
posbuckets: it.posbuckets,
negbuckets: it.negbuckets,
posbucketsDelta: it.posbucketsDelta,
negbucketsDelta: it.negbucketsDelta,
sum: it.sum,
leading: it.leading,
trailing: it.trailing,
buf64: make([]byte, binary.MaxVarintLen64),
}
if binary.BigEndian.Uint16(a.b.bytes()) == 0 {
a.leading = 0xff
}
return a, nil
}
func countSpans(spans []histogram.Span) int {
var cnt int
for _, s := range spans {
cnt += int(s.Length)
}
return cnt
}
func newHistoIterator(b []byte) *histoIterator {
it := &histoIterator{
br: newBReader(b),
numTotal: binary.BigEndian.Uint16(b),
t: math.MinInt64,
}
// The first 2 bytes contain chunk headers.
// We skip that for actual samples.
_, _ = it.br.readBits(16)
return it
}
func (c *HistoChunk) iterator(it Iterator) *histoIterator {
// TODO fix this. this is taken from xor.go // dieter not sure what the purpose of this is
// Should iterators guarantee to act on a copy of the data so it doesn't lock append?
// When using striped locks to guard access to chunks, probably yes.
// Could only copy data if the chunk is not completed yet.
//if histoIter, ok := it.(*histoIterator); ok {
// histoIter.Reset(c.b.bytes())
// return histoIter
//}
return newHistoIterator(c.b.bytes())
}
// Iterator implements the Chunk interface.
func (c *HistoChunk) Iterator(it Iterator) Iterator {
return c.iterator(it)
}
// HistoAppender is an Appender implementation for sparse histograms.
type HistoAppender struct {
b *bstream
// Metadata:
schema int32
zeroThreshold float64
posSpans, negSpans []histogram.Span
// For the fields that are tracked as dod's. Note that we expect to
// handle negative deltas (e.g. resets) by creating new chunks, we still
// want to support it in general hence signed integer types.
t int64
cnt, zcnt uint64
tDelta, cntDelta, zcntDelta int64
posbuckets, negbuckets []int64
posbucketsDelta, negbucketsDelta []int64
// The sum is Gorilla xor encoded.
sum float64
leading uint8
trailing uint8
buf64 []byte // For working on varint64's.
}
func putVarint(b *bstream, buf []byte, x int64) {
for _, byt := range buf[:binary.PutVarint(buf, x)] {
b.writeByte(byt)
}
}
func putUvarint(b *bstream, buf []byte, x uint64) {
for _, byt := range buf[:binary.PutUvarint(buf, x)] {
b.writeByte(byt)
}
}
// Append implements Appender. This implementation does nothing for now.
// TODO(beorn7): Implement in a meaningful way, i.e. we need to support
// appending of stale markers, but this should never be used for "real"
// samples.
func (a *HistoAppender) Append(int64, float64) {}
// Appendable returns whether the chunk can be appended to, and if so
// whether any recoding needs to happen using the provided interjections
// (in case of any new buckets, positive or negative range, respectively)
// The chunk is not appendable if:
// * the schema has changed
// * the zerobucket threshold has changed
// * any buckets disappeared
// * there was a counter reset in the count of observations or in any bucket, including the zero bucket
// * the last sample in the chunk was stale while the current sample is not stale
// It returns an additional boolean set to true if it is not appendable because of a counter reset.
// If the given sample is stale, it will always return true.
// If counterReset is true, okToAppend MUST be false.
func (a *HistoAppender) Appendable(h histogram.SparseHistogram) (posInterjections []Interjection, negInterjections []Interjection, okToAppend bool, counterReset bool) {
if value.IsStaleNaN(h.Sum) {
// This is a stale sample whose buckets and spans don't matter.
okToAppend = true
return
}
if value.IsStaleNaN(a.sum) {
// If the last sample was stale, then we can only accept stale samples in this chunk.
return
}
if h.Count < a.cnt {
// There has been a counter reset.
counterReset = true
return
}
if h.Schema != a.schema || h.ZeroThreshold != a.zeroThreshold {
return
}
if h.ZeroCount < a.zcnt {
// There has been a counter reset since ZeroThreshold didn't change.
counterReset = true
return
}
var ok bool
posInterjections, ok = compareSpans(a.posSpans, h.PositiveSpans)
if !ok {
counterReset = true
return
}
negInterjections, ok = compareSpans(a.negSpans, h.NegativeSpans)
if !ok {
counterReset = true
return
}
if counterResetInAnyBucket(a.posbuckets, h.PositiveBuckets, a.posSpans, h.PositiveSpans) ||
counterResetInAnyBucket(a.negbuckets, h.NegativeBuckets, a.negSpans, h.NegativeSpans) {
counterReset, posInterjections, negInterjections = true, nil, nil
return
}
okToAppend = true
return
}
// counterResetInAnyBucket returns true if there was a counter reset for any bucket.
// This should be called only when buckets are same or new buckets were added,
// and does not handle the case of buckets missing.
func counterResetInAnyBucket(oldBuckets, newBuckets []int64, oldSpans, newSpans []histogram.Span) bool {
if len(oldSpans) == 0 || len(oldBuckets) == 0 {
return false
}
oldSpanSliceIdx, newSpanSliceIdx := 0, 0 // Index for the span slices.
oldInsideSpanIdx, newInsideSpanIdx := uint32(0), uint32(0) // Index inside a span.
oldIdx, newIdx := oldSpans[0].Offset, newSpans[0].Offset
oldBucketSliceIdx, newBucketSliceIdx := 0, 0 // Index inside bucket slice.
oldVal, newVal := oldBuckets[0], newBuckets[0]
// Since we assume that new spans won't have missing buckets, there will never be a case
// where the old index will not find a matching new index.
for {
if oldIdx == newIdx {
if newVal < oldVal {
return true
}
}
if oldIdx <= newIdx {
// Moving ahead old bucket and span by 1 index.
if oldInsideSpanIdx == oldSpans[oldSpanSliceIdx].Length-1 {
// Current span is over.
oldSpanSliceIdx++
oldInsideSpanIdx = 0
if oldSpanSliceIdx >= len(oldSpans) {
// All old spans are over.
break
}
oldIdx += 1 + oldSpans[oldSpanSliceIdx].Offset
} else {
oldInsideSpanIdx++
oldIdx++
}
oldBucketSliceIdx++
oldVal += oldBuckets[oldBucketSliceIdx]
}
if oldIdx > newIdx {
// Moving ahead new bucket and span by 1 index.
if newInsideSpanIdx == newSpans[newSpanSliceIdx].Length-1 {
// Current span is over.
newSpanSliceIdx++
newInsideSpanIdx = 0
if newSpanSliceIdx >= len(newSpans) {
// All new spans are over.
// This should not happen, old spans above should catch this first.
panic("new spans over before old spans in counterReset")
}
newIdx += 1 + newSpans[newSpanSliceIdx].Offset
} else {
newInsideSpanIdx++
newIdx++
}
newBucketSliceIdx++
newVal += newBuckets[newBucketSliceIdx]
}
}
return false
}
// AppendHistogram appends a SparseHistogram to the chunk. We assume the
// histogram is properly structured. E.g. that the number of pos/neg buckets
// used corresponds to the number conveyed by the pos/neg span structures.
// callers must call Appendable() first and act accordingly!
func (a *HistoAppender) AppendHistogram(t int64, h histogram.SparseHistogram) {
var tDelta, cntDelta, zcntDelta int64
num := binary.BigEndian.Uint16(a.b.bytes())
if value.IsStaleNaN(h.Sum) {
// Emptying out other fields to write no buckets, and an empty meta in case of
// first histogram in the chunk.
h = histogram.SparseHistogram{Sum: h.Sum}
}
switch num {
case 0:
// the first append gets the privilege to dictate the metadata
// but it's also responsible for encoding it into the chunk!
writeHistoChunkMeta(a.b, h.Schema, h.ZeroThreshold, h.PositiveSpans, h.NegativeSpans)
a.schema = h.Schema
a.zeroThreshold = h.ZeroThreshold
a.posSpans, a.negSpans = h.PositiveSpans, h.NegativeSpans
numPosBuckets, numNegBuckets := countSpans(h.PositiveSpans), countSpans(h.NegativeSpans)
a.posbuckets = make([]int64, numPosBuckets)
a.negbuckets = make([]int64, numNegBuckets)
a.posbucketsDelta = make([]int64, numPosBuckets)
a.negbucketsDelta = make([]int64, numNegBuckets)
// now store actual data
putVarint(a.b, a.buf64, t)
putUvarint(a.b, a.buf64, h.Count)
putUvarint(a.b, a.buf64, h.ZeroCount)
a.b.writeBits(math.Float64bits(h.Sum), 64)
for _, buck := range h.PositiveBuckets {
putVarint(a.b, a.buf64, buck)
}
for _, buck := range h.NegativeBuckets {
putVarint(a.b, a.buf64, buck)
}
case 1:
tDelta = t - a.t
cntDelta = int64(h.Count) - int64(a.cnt)
zcntDelta = int64(h.ZeroCount) - int64(a.zcnt)
if value.IsStaleNaN(h.Sum) {
cntDelta, zcntDelta = 0, 0
}
putVarint(a.b, a.buf64, tDelta)
putVarint(a.b, a.buf64, cntDelta)
putVarint(a.b, a.buf64, zcntDelta)
a.writeSumDelta(h.Sum)
for i, buck := range h.PositiveBuckets {
delta := buck - a.posbuckets[i]
putVarint(a.b, a.buf64, delta)
a.posbucketsDelta[i] = delta
}
for i, buck := range h.NegativeBuckets {
delta := buck - a.negbuckets[i]
putVarint(a.b, a.buf64, delta)
a.negbucketsDelta[i] = delta
}
default:
tDelta = t - a.t
cntDelta = int64(h.Count) - int64(a.cnt)
zcntDelta = int64(h.ZeroCount) - int64(a.zcnt)
tDod := tDelta - a.tDelta
cntDod := cntDelta - a.cntDelta
zcntDod := zcntDelta - a.zcntDelta
if value.IsStaleNaN(h.Sum) {
cntDod, zcntDod = 0, 0
}
putInt64VBBucket(a.b, tDod)
putInt64VBBucket(a.b, cntDod)
putInt64VBBucket(a.b, zcntDod)
a.writeSumDelta(h.Sum)
for i, buck := range h.PositiveBuckets {
delta := buck - a.posbuckets[i]
dod := delta - a.posbucketsDelta[i]
putInt64VBBucket(a.b, dod)
a.posbucketsDelta[i] = delta
}
for i, buck := range h.NegativeBuckets {
delta := buck - a.negbuckets[i]
dod := delta - a.negbucketsDelta[i]
putInt64VBBucket(a.b, dod)
a.negbucketsDelta[i] = delta
}
}
binary.BigEndian.PutUint16(a.b.bytes(), num+1)
a.t = t
a.cnt = h.Count
a.zcnt = h.ZeroCount
a.tDelta = tDelta
a.cntDelta = cntDelta
a.zcntDelta = zcntDelta
a.posbuckets, a.negbuckets = h.PositiveBuckets, h.NegativeBuckets
// note that the bucket deltas were already updated above
a.sum = h.Sum
}
// Recode converts the current chunk to accommodate an expansion of the set of
// (positive and/or negative) buckets used, according to the provided
// interjections, resulting in the honoring of the provided new posSpans and
// negSpans.
func (a *HistoAppender) Recode(posInterjections, negInterjections []Interjection, posSpans, negSpans []histogram.Span) (Chunk, Appender) {
// TODO(beorn7): This currently just decodes everything and then encodes
// it again with the new span layout. This can probably be done in-place
// by editing the chunk. But let's first see how expensive it is in the
// big picture.
byts := a.b.bytes()
it := newHistoIterator(byts)
hc := NewHistoChunk()
app, err := hc.Appender()
if err != nil {
panic(err)
}
numPosBuckets, numNegBuckets := countSpans(posSpans), countSpans(negSpans)
for it.Next() {
tOld, hOld := it.AtHistogram()
// We have to newly allocate slices for the modified buckets
// here because they are kept by the appender until the next
// append.
// TODO(beorn7): We might be able to optimize this.
posBuckets := make([]int64, numPosBuckets)
negBuckets := make([]int64, numNegBuckets)
// Save the modified histogram to the new chunk.
hOld.PositiveSpans, hOld.NegativeSpans = posSpans, negSpans
if len(posInterjections) > 0 {
hOld.PositiveBuckets = interject(hOld.PositiveBuckets, posBuckets, posInterjections)
}
if len(negInterjections) > 0 {
hOld.NegativeBuckets = interject(hOld.NegativeBuckets, negBuckets, negInterjections)
}
app.AppendHistogram(tOld, hOld)
}
// Set the flags.
hc.SetCounterResetHeader(CounterResetHeader(byts[2] & 0b11000000))
return hc, app
}
func (a *HistoAppender) writeSumDelta(v float64) {
vDelta := math.Float64bits(v) ^ math.Float64bits(a.sum)
if vDelta == 0 {
a.b.writeBit(zero)
return
}
a.b.writeBit(one)
leading := uint8(bits.LeadingZeros64(vDelta))
trailing := uint8(bits.TrailingZeros64(vDelta))
// Clamp number of leading zeros to avoid overflow when encoding.
if leading >= 32 {
leading = 31
}
if a.leading != 0xff && leading >= a.leading && trailing >= a.trailing {
a.b.writeBit(zero)
a.b.writeBits(vDelta>>a.trailing, 64-int(a.leading)-int(a.trailing))
} else {
a.leading, a.trailing = leading, trailing
a.b.writeBit(one)
a.b.writeBits(uint64(leading), 5)
// Note that if leading == trailing == 0, then sigbits == 64. But that value doesn't actually fit into the 6 bits we have.
// Luckily, we never need to encode 0 significant bits, since that would put us in the other case (vdelta == 0).
// So instead we write out a 0 and adjust it back to 64 on unpacking.
sigbits := 64 - leading - trailing
a.b.writeBits(uint64(sigbits), 6)
a.b.writeBits(vDelta>>trailing, int(sigbits))
}
}
type histoIterator struct {
br bstreamReader
numTotal uint16
numRead uint16
// Metadata:
schema int32
zeroThreshold float64
posSpans, negSpans []histogram.Span
// For the fields that are tracked as dod's.
t int64
cnt, zcnt uint64
tDelta, cntDelta, zcntDelta int64
posbuckets, negbuckets []int64
posbucketsDelta, negbucketsDelta []int64
// The sum is Gorilla xor encoded.
sum float64
leading uint8
trailing uint8
err error
}
func (it *histoIterator) Seek(t int64) bool {
if it.err != nil {
return false
}
for t > it.t || it.numRead == 0 {
if !it.Next() {
return false
}
}
return true
}
func (it *histoIterator) At() (int64, float64) {
panic("cannot call histoIterator.At().")
}
func (it *histoIterator) ChunkEncoding() Encoding {
return EncSHS
}
func (it *histoIterator) AtHistogram() (int64, histogram.SparseHistogram) {
if value.IsStaleNaN(it.sum) {
return it.t, histogram.SparseHistogram{Sum: it.sum}
}
return it.t, histogram.SparseHistogram{
Count: it.cnt,
ZeroCount: it.zcnt,
Sum: it.sum,
ZeroThreshold: it.zeroThreshold,
Schema: it.schema,
PositiveSpans: it.posSpans,
NegativeSpans: it.negSpans,
PositiveBuckets: it.posbuckets,
NegativeBuckets: it.negbuckets,
}
}
func (it *histoIterator) Err() error {
return it.err
}
func (it *histoIterator) Reset(b []byte) {
// The first 2 bytes contain chunk headers.
// We skip that for actual samples.
it.br = newBReader(b[2:])
it.numTotal = binary.BigEndian.Uint16(b)
it.numRead = 0
it.t, it.cnt, it.zcnt = 0, 0, 0
it.tDelta, it.cntDelta, it.zcntDelta = 0, 0, 0
for i := range it.posbuckets {
it.posbuckets[i] = 0
it.posbucketsDelta[i] = 0
}
for i := range it.negbuckets {
it.negbuckets[i] = 0
it.negbucketsDelta[i] = 0
}
it.sum = 0
it.leading = 0
it.trailing = 0
it.err = nil
}
func (it *histoIterator) Next() bool {
if it.err != nil || it.numRead == it.numTotal {
return false
}
if it.numRead == 0 {
// first read is responsible for reading chunk metadata and initializing fields that depend on it
// We give counter reset info at chunk level, hence we discard it here.
schema, zeroThreshold, posSpans, negSpans, err := readHistoChunkMeta(&it.br)
if err != nil {
it.err = err
return false
}
it.schema = schema
it.zeroThreshold = zeroThreshold
it.posSpans, it.negSpans = posSpans, negSpans
numPosBuckets, numNegBuckets := countSpans(posSpans), countSpans(negSpans)
if numPosBuckets > 0 {
it.posbuckets = make([]int64, numPosBuckets)
it.posbucketsDelta = make([]int64, numPosBuckets)
}
if numNegBuckets > 0 {
it.negbuckets = make([]int64, numNegBuckets)
it.negbucketsDelta = make([]int64, numNegBuckets)
}
// now read actual data
t, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.t = t
cnt, err := binary.ReadUvarint(&it.br)
if err != nil {
it.err = err
return false
}
it.cnt = cnt
zcnt, err := binary.ReadUvarint(&it.br)
if err != nil {
it.err = err
return false
}
it.zcnt = zcnt
sum, err := it.br.readBits(64)
if err != nil {
it.err = err
return false
}
it.sum = math.Float64frombits(sum)
for i := range it.posbuckets {
v, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.posbuckets[i] = v
}
for i := range it.negbuckets {
v, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.negbuckets[i] = v
}
it.numRead++
return true
}
if it.numRead == 1 {
tDelta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.tDelta = tDelta
it.t += int64(it.tDelta)
cntDelta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.cntDelta = cntDelta
it.cnt = uint64(int64(it.cnt) + it.cntDelta)
zcntDelta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.zcntDelta = zcntDelta
it.zcnt = uint64(int64(it.zcnt) + it.zcntDelta)
ok := it.readSum()
if !ok {
return false
}
if value.IsStaleNaN(it.sum) {
it.numRead++
return true
}
for i := range it.posbuckets {
delta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.posbucketsDelta[i] = delta
it.posbuckets[i] = it.posbuckets[i] + delta
}
for i := range it.negbuckets {
delta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.negbucketsDelta[i] = delta
it.negbuckets[i] = it.negbuckets[i] + delta
}
it.numRead++
return true
}
tDod, err := readInt64VBBucket(&it.br)
if err != nil {
it.err = err
return false
}
it.tDelta = it.tDelta + tDod
it.t += it.tDelta
cntDod, err := readInt64VBBucket(&it.br)
if err != nil {
it.err = err
return false
}
it.cntDelta = it.cntDelta + cntDod
it.cnt = uint64(int64(it.cnt) + it.cntDelta)
zcntDod, err := readInt64VBBucket(&it.br)
if err != nil {
it.err = err
return false
}
it.zcntDelta = it.zcntDelta + zcntDod
it.zcnt = uint64(int64(it.zcnt) + it.zcntDelta)
ok := it.readSum()
if !ok {
return false
}
if value.IsStaleNaN(it.sum) {
it.numRead++
return true
}
for i := range it.posbuckets {
dod, err := readInt64VBBucket(&it.br)
if err != nil {
it.err = err
return false
}
it.posbucketsDelta[i] = it.posbucketsDelta[i] + dod
it.posbuckets[i] = it.posbuckets[i] + it.posbucketsDelta[i]
}
for i := range it.negbuckets {
dod, err := readInt64VBBucket(&it.br)
if err != nil {
it.err = err
return false
}
it.negbucketsDelta[i] = it.negbucketsDelta[i] + dod
it.negbuckets[i] = it.negbuckets[i] + it.negbucketsDelta[i]
}
it.numRead++
return true
}
func (it *histoIterator) readSum() bool {
bit, err := it.br.readBitFast()
if err != nil {
bit, err = it.br.readBit()
}
if err != nil {
it.err = err
return false
}
if bit == zero {
// it.sum = it.sum
} else {
bit, err := it.br.readBitFast()
if err != nil {
bit, err = it.br.readBit()
}
if err != nil {
it.err = err
return false
}
if bit == zero {
// reuse leading/trailing zero bits
// it.leading, it.trailing = it.leading, it.trailing
} else {
bits, err := it.br.readBitsFast(5)
if err != nil {
bits, err = it.br.readBits(5)
}
if err != nil {
it.err = err
return false
}
it.leading = uint8(bits)
bits, err = it.br.readBitsFast(6)
if err != nil {
bits, err = it.br.readBits(6)
}
if err != nil {
it.err = err
return false
}
mbits := uint8(bits)
// 0 significant bits here means we overflowed and we actually need 64; see comment in encoder
if mbits == 0 {
mbits = 64
}
it.trailing = 64 - it.leading - mbits
}
mbits := 64 - it.leading - it.trailing
bits, err := it.br.readBitsFast(mbits)
if err != nil {
bits, err = it.br.readBits(mbits)
}
if err != nil {
it.err = err
return false
}
vbits := math.Float64bits(it.sum)
vbits ^= bits << it.trailing
it.sum = math.Float64frombits(vbits)
}
return true
}

View File

@ -1,261 +0,0 @@
// Copyright 2021 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 chunkenc
import (
"github.com/prometheus/prometheus/pkg/histogram"
)
func writeHistoChunkMeta(b *bstream, schema int32, zeroThreshold float64, posSpans, negSpans []histogram.Span) {
putInt64VBBucket(b, int64(schema))
putFloat64VBBucket(b, zeroThreshold)
putHistoChunkMetaSpans(b, posSpans)
putHistoChunkMetaSpans(b, negSpans)
}
func putHistoChunkMetaSpans(b *bstream, spans []histogram.Span) {
putInt64VBBucket(b, int64(len(spans)))
for _, s := range spans {
putInt64VBBucket(b, int64(s.Length))
putInt64VBBucket(b, int64(s.Offset))
}
}
func readHistoChunkMeta(b *bstreamReader) (schema int32, zeroThreshold float64, posSpans []histogram.Span, negSpans []histogram.Span, err error) {
_, err = b.ReadByte() // The header.
if err != nil {
return
}
v, err := readInt64VBBucket(b)
if err != nil {
return
}
schema = int32(v)
zeroThreshold, err = readFloat64VBBucket(b)
if err != nil {
return
}
posSpans, err = readHistoChunkMetaSpans(b)
if err != nil {
return
}
negSpans, err = readHistoChunkMetaSpans(b)
if err != nil {
return
}
return
}
func readHistoChunkMetaSpans(b *bstreamReader) ([]histogram.Span, error) {
var spans []histogram.Span
num, err := readInt64VBBucket(b)
if err != nil {
return nil, err
}
for i := 0; i < int(num); i++ {
length, err := readInt64VBBucket(b)
if err != nil {
return nil, err
}
offset, err := readInt64VBBucket(b)
if err != nil {
return nil, err
}
spans = append(spans, histogram.Span{
Length: uint32(length),
Offset: int32(offset),
})
}
return spans, nil
}
type bucketIterator struct {
spans []histogram.Span
span int // span position of last yielded bucket
bucket int // bucket position within span of last yielded bucket
idx int // bucket index (globally across all spans) of last yielded bucket
}
func newBucketIterator(spans []histogram.Span) *bucketIterator {
b := bucketIterator{
spans: spans,
span: 0,
bucket: -1,
idx: -1,
}
if len(spans) > 0 {
b.idx += int(spans[0].Offset)
}
return &b
}
func (b *bucketIterator) Next() (int, bool) {
// we're already out of bounds
if b.span >= len(b.spans) {
return 0, false
}
try:
if b.bucket < int(b.spans[b.span].Length-1) { // try to move within same span.
b.bucket++
b.idx++
return b.idx, true
} else if b.span < len(b.spans)-1 { // try to move from one span to the next
b.span++
b.idx += int(b.spans[b.span].Offset + 1)
b.bucket = 0
if b.spans[b.span].Length == 0 {
// pathological case that should never happen. We can't use this span, let's try again.
goto try
}
return b.idx, true
}
// we're out of options
return 0, false
}
// Interjection describes that num new buckets are introduced before processing the pos'th delta from the original slice
type Interjection struct {
pos int
num int
}
// compareSpans returns the interjections to convert a slice of deltas to a new slice representing an expanded set of buckets, or false if incompatible (e.g. if buckets were removed)
// For example:
// Let's say the old buckets look like this:
// span syntax: [offset, length]
// spans : [ 0 , 2 ] [2,1] [ 3 , 2 ] [3,1] [1,1]
// bucket idx : [0] [1] 2 3 [4] 5 6 7 [8] [9] 10 11 12 [13] 14 [15]
// raw values 6 3 3 2 4 5 1
// deltas 6 -3 0 -1 2 1 -4
// But now we introduce a new bucket layout. (carefully chosen example where we have a span appended, one unchanged[*], one prepended, and two merge - in that order)
// [*] unchanged in terms of which bucket indices they represent. but to achieve that, their offset needs to change if "disrupted" by spans changing ahead of them
// \/ this one is "unchanged"
// spans : [ 0 , 3 ] [1,1] [ 1 , 4 ] [ 3 , 3 ]
// bucket idx : [0] [1] [2] 3 [4] 5 [6] [7] [8] [9] 10 11 12 [13] [14] [15]
// raw values 6 3 0 3 0 0 2 4 5 0 1
// deltas 6 -3 -3 3 -3 0 2 2 1 -5 1
// delta mods: / \ / \ / \
// note that whenever any new buckets are introduced, the subsequent "old" bucket needs to readjust its delta to the new base of 0
// thus, for the caller, who wants to transform the set of original deltas to a new set of deltas to match a new span layout that adds buckets, we simply
// need to generate a list of interjections
// note: within compareSpans we don't have to worry about the changes to the spans themselves,
// thanks to the iterators, we get to work with the more useful bucket indices (which of course directly correspond to the buckets we have to adjust)
func compareSpans(a, b []histogram.Span) ([]Interjection, bool) {
ai := newBucketIterator(a)
bi := newBucketIterator(b)
var interjections []Interjection
// when inter.num becomes > 0, this becomes a valid interjection that should be yielded when we finish a streak of new buckets
var inter Interjection
av, aok := ai.Next()
bv, bok := bi.Next()
loop:
for {
switch {
case aok && bok:
switch {
case av == bv: // Both have an identical value. move on!
// Finish WIP interjection and reset.
if inter.num > 0 {
interjections = append(interjections, inter)
}
inter.num = 0
av, aok = ai.Next()
bv, bok = bi.Next()
inter.pos++
case av < bv: // b misses a value that is in a.
return interjections, false
case av > bv: // a misses a value that is in b. Forward b and recompare.
inter.num++
bv, bok = bi.Next()
}
case aok && !bok: // b misses a value that is in a.
return interjections, false
case !aok && bok: // a misses a value that is in b. Forward b and recompare.
inter.num++
bv, bok = bi.Next()
default: // Both iterators ran out. We're done.
if inter.num > 0 {
interjections = append(interjections, inter)
}
break loop
}
}
return interjections, true
}
// interject merges 'in' with the provided interjections and writes them into
// 'out', which must already have the appropriate length.
func interject(in, out []int64, interjections []Interjection) []int64 {
var j int // Position in out.
var v int64 // The last value seen.
var interj int // The next interjection to process.
for i, d := range in {
if interj < len(interjections) && i == interjections[interj].pos {
// We have an interjection!
// Add interjection.num new delta values such that their
// bucket values equate 0.
out[j] = int64(-v)
j++
for x := 1; x < interjections[interj].num; x++ {
out[j] = 0
j++
}
interj++
// Now save the value from the input. The delta value we
// should save is the original delta value + the last
// value of the point before the interjection (to undo
// the delta that was introduced by the interjection).
out[j] = d + v
j++
v = d + v
continue
}
// If there was no interjection, the original delta is still
// valid.
out[j] = d
j++
v += d
}
switch interj {
case len(interjections):
// All interjections processed. Nothing more to do.
case len(interjections) - 1:
// One more interjection to process at the end.
out[j] = int64(-v)
j++
for x := 1; x < interjections[interj].num; x++ {
out[j] = 0
j++
}
default:
panic("unprocessed interjections left")
}
return out
}

934
tsdb/chunkenc/histogram.go Normal file
View File

@ -0,0 +1,934 @@
// Copyright 2021 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 chunkenc
import (
"encoding/binary"
"math"
"math/bits"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/value"
)
const ()
// HistogramChunk holds encoded sample data for a sparse, high-resolution
// histogram.
//
// TODO(beorn7): Document the layout of chunk metadata.
//
// Each sample has multiple "fields", stored in the following way (raw = store
// number directly, delta = store delta to the previous number, dod = store
// delta of the delta to the previous number, xor = what we do for regular
// sample values):
//
// field → ts count zeroCount sum []posbuckets []negbuckets
// sample 1 raw raw raw raw []raw []raw
// sample 2 delta delta delta xor []delta []delta
// sample >2 dod dod dod xor []dod []dod
type HistogramChunk struct {
b bstream
}
// NewHistogramChunk returns a new chunk with histogram encoding of the given
// size.
func NewHistogramChunk() *HistogramChunk {
b := make([]byte, 3, 128)
return &HistogramChunk{b: bstream{stream: b, count: 0}}
}
// Encoding returns the encoding type.
func (c *HistogramChunk) Encoding() Encoding {
return EncHistogram
}
// Bytes returns the underlying byte slice of the chunk.
func (c *HistogramChunk) Bytes() []byte {
return c.b.bytes()
}
// NumSamples returns the number of samples in the chunk.
func (c *HistogramChunk) NumSamples() int {
return int(binary.BigEndian.Uint16(c.Bytes()))
}
// Meta returns the histogram metadata. Only call this on chunks that have at
// least one sample.
func (c *HistogramChunk) Meta() (
schema int32, zeroThreshold float64,
negativeSpans, positiveSpans []histogram.Span,
err error,
) {
if c.NumSamples() == 0 {
panic("HistoChunk.Meta() called on an empty chunk")
}
b := newBReader(c.Bytes()[2:])
return readHistogramChunkMeta(&b)
}
// CounterResetHeader defines the first 2 bits of the chunk header.
type CounterResetHeader byte
const (
CounterReset CounterResetHeader = 0b10000000
NotCounterReset CounterResetHeader = 0b01000000
GaugeType CounterResetHeader = 0b11000000
UnknownCounterReset CounterResetHeader = 0b00000000
)
// SetCounterResetHeader sets the counter reset header.
func (c *HistogramChunk) SetCounterResetHeader(h CounterResetHeader) {
switch h {
case CounterReset, NotCounterReset, GaugeType, UnknownCounterReset:
bytes := c.Bytes()
bytes[2] = (bytes[2] & 0b00111111) | byte(h)
default:
panic("invalid CounterResetHeader type")
}
}
// GetCounterResetHeader returns the info about the first 2 bits of the chunk
// header.
func (c *HistogramChunk) GetCounterResetHeader() CounterResetHeader {
return CounterResetHeader(c.Bytes()[2] & 0b11000000)
}
// Compact implements the Chunk interface.
func (c *HistogramChunk) Compact() {
if l := len(c.b.stream); cap(c.b.stream) > l+chunkCompactCapacityThreshold {
buf := make([]byte, l)
copy(buf, c.b.stream)
c.b.stream = buf
}
}
// Appender implements the Chunk interface.
func (c *HistogramChunk) Appender() (Appender, error) {
it := c.iterator(nil)
// To get an appender, we must know the state it would have if we had
// appended all existing data from scratch. We iterate through the end
// and populate via the iterator's state.
for it.Next() {
}
if err := it.Err(); err != nil {
return nil, err
}
a := &HistogramAppender{
b: &c.b,
schema: it.schema,
zThreshold: it.zThreshold,
pSpans: it.pSpans,
nSpans: it.nSpans,
t: it.t,
cnt: it.cnt,
zCnt: it.zCnt,
tDelta: it.tDelta,
cntDelta: it.cntDelta,
zCntDelta: it.zCntDelta,
pBuckets: it.pBuckets,
nBuckets: it.nBuckets,
pBucketsDelta: it.pBucketsDelta,
nBucketsDelta: it.nBucketsDelta,
sum: it.sum,
leading: it.leading,
trailing: it.trailing,
buf64: make([]byte, binary.MaxVarintLen64),
}
if binary.BigEndian.Uint16(a.b.bytes()) == 0 {
a.leading = 0xff
}
return a, nil
}
func countSpans(spans []histogram.Span) int {
var cnt int
for _, s := range spans {
cnt += int(s.Length)
}
return cnt
}
func newHistogramIterator(b []byte) *histogramIterator {
it := &histogramIterator{
br: newBReader(b),
numTotal: binary.BigEndian.Uint16(b),
t: math.MinInt64,
}
// The first 2 bytes contain chunk headers.
// We skip that for actual samples.
_, _ = it.br.readBits(16)
return it
}
func (c *HistogramChunk) iterator(it Iterator) *histogramIterator {
// This commet is copied from XORChunk.iterator:
// Should iterators guarantee to act on a copy of the data so it doesn't lock append?
// When using striped locks to guard access to chunks, probably yes.
// Could only copy data if the chunk is not completed yet.
if histogramIter, ok := it.(*histogramIterator); ok {
histogramIter.Reset(c.b.bytes())
return histogramIter
}
return newHistogramIterator(c.b.bytes())
}
// Iterator implements the Chunk interface.
func (c *HistogramChunk) Iterator(it Iterator) Iterator {
return c.iterator(it)
}
// HistogramAppender is an Appender implementation for sparse histograms.
type HistogramAppender struct {
b *bstream
// Metadata:
schema int32
zThreshold float64
pSpans, nSpans []histogram.Span
// Although we intend to start new chunks on counter resets, we still
// have to handle negative deltas for gauge histograms. Therefore, even
// deltas are signed types here (even for tDelta to not treat that one
// specially).
t int64
cnt, zCnt uint64
tDelta, cntDelta, zCntDelta int64
pBuckets, nBuckets []int64
pBucketsDelta, nBucketsDelta []int64
// The sum is Gorilla xor encoded.
sum float64
leading uint8
trailing uint8
buf64 []byte // For working on varint64's.
}
func putVarint(b *bstream, buf []byte, x int64) {
for _, byt := range buf[:binary.PutVarint(buf, x)] {
b.writeByte(byt)
}
}
func putUvarint(b *bstream, buf []byte, x uint64) {
for _, byt := range buf[:binary.PutUvarint(buf, x)] {
b.writeByte(byt)
}
}
// Append implements Appender. This implementation panics because normal float
// samples must never be appended to a histogram chunk.
func (a *HistogramAppender) Append(int64, float64) {
panic("appended a float sample to a histogram chunk")
}
// Appendable returns whether the chunk can be appended to, and if so
// whether any recoding needs to happen using the provided interjections
// (in case of any new buckets, positive or negative range, respectively).
//
// The chunk is not appendable in the following cases:
//
// • The schema has changed.
//
// • The threshold for the zero bucket has changed.
//
// • Any buckets have disappeared.
//
// • There was a counter reset in the count of observations or in any bucket,
// including the zero bucket.
//
// • The last sample in the chunk was stale while the current sample is not stale.
//
// The method returns an additional boolean set to true if it is not appendable
// because of a counter reset. If the given sample is stale, it is always ok to
// append. If counterReset is true, okToAppend is always false.
func (a *HistogramAppender) Appendable(h histogram.Histogram) (
positiveInterjections, negativeInterjections []Interjection,
okToAppend bool, counterReset bool,
) {
if value.IsStaleNaN(h.Sum) {
// This is a stale sample whose buckets and spans don't matter.
okToAppend = true
return
}
if value.IsStaleNaN(a.sum) {
// If the last sample was stale, then we can only accept stale
// samples in this chunk.
return
}
if h.Count < a.cnt {
// There has been a counter reset.
counterReset = true
return
}
if h.Schema != a.schema || h.ZeroThreshold != a.zThreshold {
return
}
if h.ZeroCount < a.zCnt {
// There has been a counter reset since ZeroThreshold didn't change.
counterReset = true
return
}
var ok bool
positiveInterjections, ok = compareSpans(a.pSpans, h.PositiveSpans)
if !ok {
counterReset = true
return
}
negativeInterjections, ok = compareSpans(a.nSpans, h.NegativeSpans)
if !ok {
counterReset = true
return
}
if counterResetInAnyBucket(a.pBuckets, h.PositiveBuckets, a.pSpans, h.PositiveSpans) ||
counterResetInAnyBucket(a.nBuckets, h.NegativeBuckets, a.nSpans, h.NegativeSpans) {
counterReset, positiveInterjections, negativeInterjections = true, nil, nil
return
}
okToAppend = true
return
}
// counterResetInAnyBucket returns true if there was a counter reset for any
// bucket. This should be called only when the bucket layout is the same or new
// buckets were added. It does not handle the case of buckets missing.
func counterResetInAnyBucket(oldBuckets, newBuckets []int64, oldSpans, newSpans []histogram.Span) bool {
if len(oldSpans) == 0 || len(oldBuckets) == 0 {
return false
}
oldSpanSliceIdx, newSpanSliceIdx := 0, 0 // Index for the span slices.
oldInsideSpanIdx, newInsideSpanIdx := uint32(0), uint32(0) // Index inside a span.
oldIdx, newIdx := oldSpans[0].Offset, newSpans[0].Offset
oldBucketSliceIdx, newBucketSliceIdx := 0, 0 // Index inside bucket slice.
oldVal, newVal := oldBuckets[0], newBuckets[0]
// Since we assume that new spans won't have missing buckets, there will never be a case
// where the old index will not find a matching new index.
for {
if oldIdx == newIdx {
if newVal < oldVal {
return true
}
}
if oldIdx <= newIdx {
// Moving ahead old bucket and span by 1 index.
if oldInsideSpanIdx == oldSpans[oldSpanSliceIdx].Length-1 {
// Current span is over.
oldSpanSliceIdx++
oldInsideSpanIdx = 0
if oldSpanSliceIdx >= len(oldSpans) {
// All old spans are over.
break
}
oldIdx += 1 + oldSpans[oldSpanSliceIdx].Offset
} else {
oldInsideSpanIdx++
oldIdx++
}
oldBucketSliceIdx++
oldVal += oldBuckets[oldBucketSliceIdx]
}
if oldIdx > newIdx {
// Moving ahead new bucket and span by 1 index.
if newInsideSpanIdx == newSpans[newSpanSliceIdx].Length-1 {
// Current span is over.
newSpanSliceIdx++
newInsideSpanIdx = 0
if newSpanSliceIdx >= len(newSpans) {
// All new spans are over.
// This should not happen, old spans above should catch this first.
panic("new spans over before old spans in counterReset")
}
newIdx += 1 + newSpans[newSpanSliceIdx].Offset
} else {
newInsideSpanIdx++
newIdx++
}
newBucketSliceIdx++
newVal += newBuckets[newBucketSliceIdx]
}
}
return false
}
// AppendHistogram appends a histogram to the chunk. The caller must ensure that
// the histogram is properly structured, e.g. the number of buckets used
// corresponds to the number conveyed by the span structures. First call
// Appendable() and act accordingly!
func (a *HistogramAppender) AppendHistogram(t int64, h histogram.Histogram) {
var tDelta, cntDelta, zCntDelta int64
num := binary.BigEndian.Uint16(a.b.bytes())
if value.IsStaleNaN(h.Sum) {
// Emptying out other fields to write no buckets, and an empty
// meta in case of first histogram in the chunk.
h = histogram.Histogram{Sum: h.Sum}
}
switch num {
case 0:
// The first append gets the privilege to dictate the metadata
// but it's also responsible for encoding it into the chunk!
writeHistogramChunkMeta(a.b, h.Schema, h.ZeroThreshold, h.PositiveSpans, h.NegativeSpans)
a.schema = h.Schema
a.zThreshold = h.ZeroThreshold
a.pSpans, a.nSpans = h.PositiveSpans, h.NegativeSpans
numPBuckets, numNBuckets := countSpans(h.PositiveSpans), countSpans(h.NegativeSpans)
a.pBuckets = make([]int64, numPBuckets)
a.nBuckets = make([]int64, numNBuckets)
a.pBucketsDelta = make([]int64, numPBuckets)
a.nBucketsDelta = make([]int64, numNBuckets)
// Now store the actual data.
putVarint(a.b, a.buf64, t)
putUvarint(a.b, a.buf64, h.Count) // TODO(beorn7): Use putVarbitInt?
putUvarint(a.b, a.buf64, h.ZeroCount) // TODO(beorn7): Use putVarbitInt?
a.b.writeBits(math.Float64bits(h.Sum), 64)
for _, buck := range h.PositiveBuckets {
putVarint(a.b, a.buf64, buck) // TODO(beorn7): Use putVarbitInt?
}
for _, buck := range h.NegativeBuckets {
putVarint(a.b, a.buf64, buck) // TODO(beorn7): Use putVarbitInt?
}
case 1:
tDelta = t - a.t
cntDelta = int64(h.Count) - int64(a.cnt)
zCntDelta = int64(h.ZeroCount) - int64(a.zCnt)
if value.IsStaleNaN(h.Sum) {
cntDelta, zCntDelta = 0, 0
}
putVarint(a.b, a.buf64, tDelta) // TODO(beorn7): This should probably be putUvarint.
putVarint(a.b, a.buf64, cntDelta) // TODO(beorn7): Use putVarbitInt?
putVarint(a.b, a.buf64, zCntDelta) // TODO(beorn7): Use putVarbitInt?
a.writeSumDelta(h.Sum)
for i, buck := range h.PositiveBuckets {
delta := buck - a.pBuckets[i]
putVarint(a.b, a.buf64, delta) // TODO(beorn7): Use putVarbitInt?
a.pBucketsDelta[i] = delta
}
for i, buck := range h.NegativeBuckets {
delta := buck - a.nBuckets[i]
putVarint(a.b, a.buf64, delta) // TODO(beorn7): Use putVarbitInt?
a.nBucketsDelta[i] = delta
}
default:
tDelta = t - a.t
cntDelta = int64(h.Count) - int64(a.cnt)
zCntDelta = int64(h.ZeroCount) - int64(a.zCnt)
tDod := tDelta - a.tDelta
cntDod := cntDelta - a.cntDelta
zCntDod := zCntDelta - a.zCntDelta
if value.IsStaleNaN(h.Sum) {
cntDod, zCntDod = 0, 0
}
putVarbitInt(a.b, tDod)
putVarbitInt(a.b, cntDod)
putVarbitInt(a.b, zCntDod)
a.writeSumDelta(h.Sum)
for i, buck := range h.PositiveBuckets {
delta := buck - a.pBuckets[i]
dod := delta - a.pBucketsDelta[i]
putVarbitInt(a.b, dod)
a.pBucketsDelta[i] = delta
}
for i, buck := range h.NegativeBuckets {
delta := buck - a.nBuckets[i]
dod := delta - a.nBucketsDelta[i]
putVarbitInt(a.b, dod)
a.nBucketsDelta[i] = delta
}
}
binary.BigEndian.PutUint16(a.b.bytes(), num+1)
a.t = t
a.cnt = h.Count
a.zCnt = h.ZeroCount
a.tDelta = tDelta
a.cntDelta = cntDelta
a.zCntDelta = zCntDelta
a.pBuckets, a.nBuckets = h.PositiveBuckets, h.NegativeBuckets
// Note that the bucket deltas were already updated above.
a.sum = h.Sum
}
// Recode converts the current chunk to accommodate an expansion of the set of
// (positive and/or negative) buckets used, according to the provided
// interjections, resulting in the honoring of the provided new positive and
// negative spans.
func (a *HistogramAppender) Recode(
positiveInterjections, negativeInterjections []Interjection,
positiveSpans, negativeSpans []histogram.Span,
) (Chunk, Appender) {
// TODO(beorn7): This currently just decodes everything and then encodes
// it again with the new span layout. This can probably be done in-place
// by editing the chunk. But let's first see how expensive it is in the
// big picture.
byts := a.b.bytes()
it := newHistogramIterator(byts)
hc := NewHistogramChunk()
app, err := hc.Appender()
if err != nil {
panic(err)
}
numPositiveBuckets, numNegativeBuckets := countSpans(positiveSpans), countSpans(negativeSpans)
for it.Next() {
tOld, hOld := it.AtHistogram()
// We have to newly allocate slices for the modified buckets
// here because they are kept by the appender until the next
// append.
// TODO(beorn7): We might be able to optimize this.
positiveBuckets := make([]int64, numPositiveBuckets)
negativeBuckets := make([]int64, numNegativeBuckets)
// Save the modified histogram to the new chunk.
hOld.PositiveSpans, hOld.NegativeSpans = positiveSpans, negativeSpans
if len(positiveInterjections) > 0 {
hOld.PositiveBuckets = interject(hOld.PositiveBuckets, positiveBuckets, positiveInterjections)
}
if len(negativeInterjections) > 0 {
hOld.NegativeBuckets = interject(hOld.NegativeBuckets, negativeBuckets, negativeInterjections)
}
app.AppendHistogram(tOld, hOld)
}
hc.SetCounterResetHeader(CounterResetHeader(byts[2] & 0b11000000))
return hc, app
}
func (a *HistogramAppender) writeSumDelta(v float64) {
vDelta := math.Float64bits(v) ^ math.Float64bits(a.sum)
if vDelta == 0 {
a.b.writeBit(zero)
return
}
a.b.writeBit(one)
leading := uint8(bits.LeadingZeros64(vDelta))
trailing := uint8(bits.TrailingZeros64(vDelta))
// Clamp number of leading zeros to avoid overflow when encoding.
if leading >= 32 {
leading = 31
}
if a.leading != 0xff && leading >= a.leading && trailing >= a.trailing {
a.b.writeBit(zero)
a.b.writeBits(vDelta>>a.trailing, 64-int(a.leading)-int(a.trailing))
} else {
a.leading, a.trailing = leading, trailing
a.b.writeBit(one)
a.b.writeBits(uint64(leading), 5)
// Note that if leading == trailing == 0, then sigbits == 64.
// But that value doesn't actually fit into the 6 bits we have.
// Luckily, we never need to encode 0 significant bits, since
// that would put us in the other case (vdelta == 0). So
// instead we write out a 0 and adjust it back to 64 on
// unpacking.
sigbits := 64 - leading - trailing
a.b.writeBits(uint64(sigbits), 6)
a.b.writeBits(vDelta>>trailing, int(sigbits))
}
}
type histogramIterator struct {
br bstreamReader
numTotal uint16
numRead uint16
// Metadata:
schema int32
zThreshold float64
pSpans, nSpans []histogram.Span
// For the fields that are tracked as deltas and ultimately dod's.
t int64
cnt, zCnt uint64
tDelta, cntDelta, zCntDelta int64
pBuckets, nBuckets []int64
pBucketsDelta, nBucketsDelta []int64
// The sum is Gorilla xor encoded.
sum float64
leading uint8
trailing uint8
err error
}
func (it *histogramIterator) Seek(t int64) bool {
if it.err != nil {
return false
}
for t > it.t || it.numRead == 0 {
if !it.Next() {
return false
}
}
return true
}
func (it *histogramIterator) At() (int64, float64) {
panic("cannot call histogramIterator.At")
}
func (it *histogramIterator) ChunkEncoding() Encoding {
return EncHistogram
}
func (it *histogramIterator) AtHistogram() (int64, histogram.Histogram) {
if value.IsStaleNaN(it.sum) {
return it.t, histogram.Histogram{Sum: it.sum}
}
return it.t, histogram.Histogram{
Count: it.cnt,
ZeroCount: it.zCnt,
Sum: it.sum,
ZeroThreshold: it.zThreshold,
Schema: it.schema,
PositiveSpans: it.pSpans,
NegativeSpans: it.nSpans,
PositiveBuckets: it.pBuckets,
NegativeBuckets: it.nBuckets,
}
}
func (it *histogramIterator) Err() error {
return it.err
}
func (it *histogramIterator) Reset(b []byte) {
// The first 2 bytes contain chunk headers.
// We skip that for actual samples.
it.br = newBReader(b[2:])
it.numTotal = binary.BigEndian.Uint16(b)
it.numRead = 0
it.t, it.cnt, it.zCnt = 0, 0, 0
it.tDelta, it.cntDelta, it.zCntDelta = 0, 0, 0
// TODO(beorn7): Those will be recreated anyway.
// Either delete them here entirely or recycle them
// below if big enough.
for i := range it.pBuckets {
it.pBuckets[i] = 0
it.pBucketsDelta[i] = 0
}
for i := range it.nBuckets {
it.nBuckets[i] = 0
it.nBucketsDelta[i] = 0
}
it.sum = 0
it.leading = 0
it.trailing = 0
it.err = nil
}
func (it *histogramIterator) Next() bool {
if it.err != nil || it.numRead == it.numTotal {
return false
}
if it.numRead == 0 {
// The first read is responsible for reading the chunk metadata
// and for initializing fields that depend on it. We give
// counter reset info at chunk level, hence we discard it here.
schema, zeroThreshold, posSpans, negSpans, err := readHistogramChunkMeta(&it.br)
if err != nil {
it.err = err
return false
}
it.schema = schema
it.zThreshold = zeroThreshold
it.pSpans, it.nSpans = posSpans, negSpans
numPosBuckets, numNegBuckets := countSpans(posSpans), countSpans(negSpans)
if numPosBuckets > 0 {
it.pBuckets = make([]int64, numPosBuckets)
it.pBucketsDelta = make([]int64, numPosBuckets)
}
if numNegBuckets > 0 {
it.nBuckets = make([]int64, numNegBuckets)
it.nBucketsDelta = make([]int64, numNegBuckets)
}
// Now read the actual data.
t, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.t = t
cnt, err := binary.ReadUvarint(&it.br)
if err != nil {
it.err = err
return false
}
it.cnt = cnt
zcnt, err := binary.ReadUvarint(&it.br)
if err != nil {
it.err = err
return false
}
it.zCnt = zcnt
sum, err := it.br.readBits(64)
if err != nil {
it.err = err
return false
}
it.sum = math.Float64frombits(sum)
for i := range it.pBuckets {
v, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.pBuckets[i] = v
}
for i := range it.nBuckets {
v, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.nBuckets[i] = v
}
it.numRead++
return true
}
if it.numRead == 1 {
tDelta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.tDelta = tDelta
it.t += int64(it.tDelta)
cntDelta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.cntDelta = cntDelta
it.cnt = uint64(int64(it.cnt) + it.cntDelta)
zcntDelta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.zCntDelta = zcntDelta
it.zCnt = uint64(int64(it.zCnt) + it.zCntDelta)
ok := it.readSum()
if !ok {
return false
}
if value.IsStaleNaN(it.sum) {
it.numRead++
return true
}
for i := range it.pBuckets {
delta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.pBucketsDelta[i] = delta
it.pBuckets[i] = it.pBuckets[i] + delta
}
for i := range it.nBuckets {
delta, err := binary.ReadVarint(&it.br)
if err != nil {
it.err = err
return false
}
it.nBucketsDelta[i] = delta
it.nBuckets[i] = it.nBuckets[i] + delta
}
it.numRead++
return true
}
tDod, err := readVarbitInt(&it.br)
if err != nil {
it.err = err
return false
}
it.tDelta = it.tDelta + tDod
it.t += it.tDelta
cntDod, err := readVarbitInt(&it.br)
if err != nil {
it.err = err
return false
}
it.cntDelta = it.cntDelta + cntDod
it.cnt = uint64(int64(it.cnt) + it.cntDelta)
zcntDod, err := readVarbitInt(&it.br)
if err != nil {
it.err = err
return false
}
it.zCntDelta = it.zCntDelta + zcntDod
it.zCnt = uint64(int64(it.zCnt) + it.zCntDelta)
ok := it.readSum()
if !ok {
return false
}
if value.IsStaleNaN(it.sum) {
it.numRead++
return true
}
for i := range it.pBuckets {
dod, err := readVarbitInt(&it.br)
if err != nil {
it.err = err
return false
}
it.pBucketsDelta[i] = it.pBucketsDelta[i] + dod
it.pBuckets[i] = it.pBuckets[i] + it.pBucketsDelta[i]
}
for i := range it.nBuckets {
dod, err := readVarbitInt(&it.br)
if err != nil {
it.err = err
return false
}
it.nBucketsDelta[i] = it.nBucketsDelta[i] + dod
it.nBuckets[i] = it.nBuckets[i] + it.nBucketsDelta[i]
}
it.numRead++
return true
}
func (it *histogramIterator) readSum() bool {
bit, err := it.br.readBitFast()
if err != nil {
bit, err = it.br.readBit()
}
if err != nil {
it.err = err
return false
}
if bit == zero {
return true // it.sum = it.sum
}
bit, err = it.br.readBitFast()
if err != nil {
bit, err = it.br.readBit()
}
if err != nil {
it.err = err
return false
}
if bit == zero {
// Reuse leading/trailing zero bits.
// it.leading, it.trailing = it.leading, it.trailing
} else {
bits, err := it.br.readBitsFast(5)
if err != nil {
bits, err = it.br.readBits(5)
}
if err != nil {
it.err = err
return false
}
it.leading = uint8(bits)
bits, err = it.br.readBitsFast(6)
if err != nil {
bits, err = it.br.readBits(6)
}
if err != nil {
it.err = err
return false
}
mbits := uint8(bits)
// 0 significant bits here means we overflowed and we actually
// need 64; see comment in encoder.
if mbits == 0 {
mbits = 64
}
it.trailing = 64 - it.leading - mbits
}
mbits := 64 - it.leading - it.trailing
bits, err := it.br.readBitsFast(mbits)
if err != nil {
bits, err = it.br.readBits(mbits)
}
if err != nil {
it.err = err
return false
}
vbits := math.Float64bits(it.sum)
vbits ^= bits << it.trailing
it.sum = math.Float64frombits(vbits)
return true
}

View File

@ -0,0 +1,286 @@
// Copyright 2021 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 chunkenc
import (
"github.com/prometheus/prometheus/model/histogram"
)
func writeHistogramChunkMeta(b *bstream, schema int32, zeroThreshold float64, positiveSpans, negativeSpans []histogram.Span) {
putVarbitInt(b, int64(schema))
putVarbitFloat(b, zeroThreshold)
putHistogramChunkMetaSpans(b, positiveSpans)
putHistogramChunkMetaSpans(b, negativeSpans)
}
func putHistogramChunkMetaSpans(b *bstream, spans []histogram.Span) {
putVarbitInt(b, int64(len(spans)))
for _, s := range spans {
putVarbitInt(b, int64(s.Length))
putVarbitInt(b, int64(s.Offset))
}
}
func readHistogramChunkMeta(b *bstreamReader) (
schema int32, zeroThreshold float64,
positiveSpans, negativeSpans []histogram.Span,
err error,
) {
_, err = b.ReadByte() // The header.
if err != nil {
return
}
v, err := readVarbitInt(b)
if err != nil {
return
}
schema = int32(v)
zeroThreshold, err = readVarbitFloat(b)
if err != nil {
return
}
positiveSpans, err = readHistogramChunkMetaSpans(b)
if err != nil {
return
}
negativeSpans, err = readHistogramChunkMetaSpans(b)
if err != nil {
return
}
return
}
func readHistogramChunkMetaSpans(b *bstreamReader) ([]histogram.Span, error) {
var spans []histogram.Span
num, err := readVarbitInt(b)
if err != nil {
return nil, err
}
for i := 0; i < int(num); i++ {
length, err := readVarbitInt(b)
if err != nil {
return nil, err
}
offset, err := readVarbitInt(b)
if err != nil {
return nil, err
}
spans = append(spans, histogram.Span{
Length: uint32(length),
Offset: int32(offset),
})
}
return spans, nil
}
type bucketIterator struct {
spans []histogram.Span
span int // Span position of last yielded bucket.
bucket int // Bucket position within span of last yielded bucket.
idx int // Bucket index (globally across all spans) of last yielded bucket.
}
func newBucketIterator(spans []histogram.Span) *bucketIterator {
b := bucketIterator{
spans: spans,
span: 0,
bucket: -1,
idx: -1,
}
if len(spans) > 0 {
b.idx += int(spans[0].Offset)
}
return &b
}
func (b *bucketIterator) Next() (int, bool) {
// We're already out of bounds.
if b.span >= len(b.spans) {
return 0, false
}
try:
if b.bucket < int(b.spans[b.span].Length-1) { // Try to move within same span.
b.bucket++
b.idx++
return b.idx, true
} else if b.span < len(b.spans)-1 { // Try to move from one span to the next.
b.span++
b.idx += int(b.spans[b.span].Offset + 1)
b.bucket = 0
if b.spans[b.span].Length == 0 {
// Pathological case that should never happen. We can't use this span, let's try again.
goto try
}
return b.idx, true
}
// We're out of options.
return 0, false
}
// An Interjection describes how many new buckets have to be introduced before
// processing the pos'th delta from the original slice.
type Interjection struct {
pos int
num int
}
// compareSpans returns the interjections to convert a slice of deltas to a new
// slice representing an expanded set of buckets, or false if incompatible
// (e.g. if buckets were removed).
//
// Example:
//
// Let's say the old buckets look like this:
//
// span syntax: [offset, length]
// spans : [ 0 , 2 ] [2,1] [ 3 , 2 ] [3,1] [1,1]
// bucket idx : [0] [1] 2 3 [4] 5 6 7 [8] [9] 10 11 12 [13] 14 [15]
// raw values 6 3 3 2 4 5 1
// deltas 6 -3 0 -1 2 1 -4
//
// But now we introduce a new bucket layout. (Carefully chosen example where we
// have a span appended, one unchanged[*], one prepended, and two merge - in
// that order.)
//
// [*] unchanged in terms of which bucket indices they represent. but to achieve
// that, their offset needs to change if "disrupted" by spans changing ahead of
// them
//
// \/ this one is "unchanged"
// spans : [ 0 , 3 ] [1,1] [ 1 , 4 ] [ 3 , 3 ]
// bucket idx : [0] [1] [2] 3 [4] 5 [6] [7] [8] [9] 10 11 12 [13] [14] [15]
// raw values 6 3 0 3 0 0 2 4 5 0 1
// deltas 6 -3 -3 3 -3 0 2 2 1 -5 1
// delta mods: / \ / \ / \
//
// Note that whenever any new buckets are introduced, the subsequent "old"
// bucket needs to readjust its delta to the new base of 0. Thus, for the caller
// who wants to transform the set of original deltas to a new set of deltas to
// match a new span layout that adds buckets, we simply need to generate a list
// of interjections.
//
// Note: Within compareSpans we don't have to worry about the changes to the
// spans themselves, thanks to the iterators we get to work with the more useful
// bucket indices (which of course directly correspond to the buckets we have to
// adjust).
func compareSpans(a, b []histogram.Span) ([]Interjection, bool) {
ai := newBucketIterator(a)
bi := newBucketIterator(b)
var interjections []Interjection
// When inter.num becomes > 0, this becomes a valid interjection that
// should be yielded when we finish a streak of new buckets.
var inter Interjection
av, aOK := ai.Next()
bv, bOK := bi.Next()
loop:
for {
switch {
case aOK && bOK:
switch {
case av == bv: // Both have an identical value. move on!
// Finish WIP interjection and reset.
if inter.num > 0 {
interjections = append(interjections, inter)
}
inter.num = 0
av, aOK = ai.Next()
bv, bOK = bi.Next()
inter.pos++
case av < bv: // b misses a value that is in a.
return interjections, false
case av > bv: // a misses a value that is in b. Forward b and recompare.
inter.num++
bv, bOK = bi.Next()
}
case aOK && !bOK: // b misses a value that is in a.
return interjections, false
case !aOK && bOK: // a misses a value that is in b. Forward b and recompare.
inter.num++
bv, bOK = bi.Next()
default: // Both iterators ran out. We're done.
if inter.num > 0 {
interjections = append(interjections, inter)
}
break loop
}
}
return interjections, true
}
// interject merges 'in' with the provided interjections and writes them into
// 'out', which must already have the appropriate length.
func interject(in, out []int64, interjections []Interjection) []int64 {
var (
j int // Position in out.
v int64 // The last value seen.
interj int // The next interjection to process.
)
for i, d := range in {
if interj < len(interjections) && i == interjections[interj].pos {
// We have an interjection!
// Add interjection.num new delta values such that their
// bucket values equate 0.
out[j] = int64(-v)
j++
for x := 1; x < interjections[interj].num; x++ {
out[j] = 0
j++
}
interj++
// Now save the value from the input. The delta value we
// should save is the original delta value + the last
// value of the point before the interjection (to undo
// the delta that was introduced by the interjection).
out[j] = d + v
j++
v = d + v
continue
}
// If there was no interjection, the original delta is still
// valid.
out[j] = d
j++
v += d
}
switch interj {
case len(interjections):
// All interjections processed. Nothing more to do.
case len(interjections) - 1:
// One more interjection to process at the end.
out[j] = int64(-v)
j++
for x := 1; x < interjections[interj].num; x++ {
out[j] = 0
j++
}
default:
panic("unprocessed interjections left")
}
return out
}

View File

@ -21,13 +21,15 @@ package chunkenc
import (
"testing"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/stretchr/testify/require"
)
// example of a span layout and resulting bucket indices (_idx_ is used in this histogram, others are shown just for context)
// spans : [offset: 0, length: 2] [offset 1, length 1]
// bucket idx : _0_ _1_ 2 [3] 4 ...
// Example of a span layout and resulting bucket indices (_idx_ is used in this
// histogram, others are shown just for context):
//
// spans : [offset: 0, length: 2] [offset 1, length 1]
// bucket idx : _0_ _1_ 2 [3] 4 ...
func TestBucketIterator(t *testing.T) {
type test struct {
@ -74,7 +76,7 @@ func TestBucketIterator(t *testing.T) {
},
idxs: []int{100, 101, 102, 103, 112, 113, 114, 115, 116, 117, 118, 119},
},
// the below 2 sets ore the ones described in compareSpans's comments
// The below 2 sets ore the ones described in compareSpans's comments.
{
spans: []histogram.Span{
{Offset: 0, Length: 2},

View File

@ -16,21 +16,21 @@ package chunkenc
import (
"testing"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/stretchr/testify/require"
)
func TestHistoChunkSameBuckets(t *testing.T) {
c := NewHistoChunk()
func TestHistogramChunkSameBuckets(t *testing.T) {
c := NewHistogramChunk()
var exp []res
// Create fresh appender and add the first histogram
// Create fresh appender and add the first histogram.
app, err := c.Appender()
require.NoError(t, err)
require.Equal(t, 0, c.NumSamples())
ts := int64(1234567890)
h := histogram.SparseHistogram{
h := histogram.Histogram{
Count: 5,
ZeroCount: 2,
Sum: 18.4,
@ -56,7 +56,7 @@ func TestHistoChunkSameBuckets(t *testing.T) {
exp = append(exp, res{t: ts, h: h})
require.Equal(t, 2, c.NumSamples())
// Add update with new appender
// Add update with new appender.
app, err = c.Appender()
require.NoError(t, err)
@ -113,12 +113,12 @@ func TestHistoChunkSameBuckets(t *testing.T) {
type res struct {
t int64
h histogram.SparseHistogram
h histogram.Histogram
}
// mimics the scenario described for compareSpans()
func TestHistoChunkBucketChanges(t *testing.T) {
c := Chunk(NewHistoChunk())
// Mimics the scenario described for compareSpans().
func TestHistogramChunkBucketChanges(t *testing.T) {
c := Chunk(NewHistogramChunk())
// Create fresh appender and add the first histogram.
app, err := c.Appender()
@ -126,7 +126,7 @@ func TestHistoChunkBucketChanges(t *testing.T) {
require.Equal(t, 0, c.NumSamples())
ts1 := int64(1234567890)
h1 := histogram.SparseHistogram{
h1 := histogram.Histogram{
Count: 5,
ZeroCount: 2,
Sum: 18.4,
@ -157,24 +157,26 @@ func TestHistoChunkBucketChanges(t *testing.T) {
h2.Count += 9
h2.ZeroCount++
h2.Sum = 30
// Existing histogram should get values converted from the above to: 6 3 0 3 0 0 2 4 5 0 1 (previous values with some new empty buckets in between)
// Existing histogram should get values converted from the above to:
// 6 3 0 3 0 0 2 4 5 0 1 (previous values with some new empty buckets in between)
// so the new histogram should have new counts >= these per-bucket counts, e.g.:
h2.PositiveBuckets = []int64{7, -2, -4, 2, -2, -1, 2, 3, 0, -5, 1} // 7 5 1 3 1 0 2 5 5 0 1 (total 30)
// This is how span changes will be handled.
histoApp, _ := app.(*HistoAppender)
posInterjections, negInterjections, ok, cr := histoApp.Appendable(h2)
hApp, _ := app.(*HistogramAppender)
posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
require.Greater(t, len(posInterjections), 0)
require.Equal(t, 0, len(negInterjections))
require.True(t, ok) // Only new buckets came in.
require.False(t, cr)
c, app = histoApp.Recode(posInterjections, negInterjections, h2.PositiveSpans, h2.NegativeSpans)
c, app = hApp.Recode(posInterjections, negInterjections, h2.PositiveSpans, h2.NegativeSpans)
app.AppendHistogram(ts2, h2)
require.Equal(t, 2, c.NumSamples())
// Because the 2nd histogram has expanded buckets, we should expect all histograms (in particular the first)
// to come back using the new spans metadata as well as the expanded buckets.
// Because the 2nd histogram has expanded buckets, we should expect all
// histograms (in particular the first) to come back using the new spans
// metadata as well as the expanded buckets.
h1.PositiveSpans = h2.PositiveSpans
h1.PositiveBuckets = []int64{6, -3, -3, 3, -3, 0, 2, 2, 1, -5, 1}
exp := []res{
@ -192,7 +194,7 @@ func TestHistoChunkBucketChanges(t *testing.T) {
}
func TestHistoChunkAppendable(t *testing.T) {
c := Chunk(NewHistoChunk())
c := Chunk(NewHistogramChunk())
// Create fresh appender and add the first histogram.
app, err := c.Appender()
@ -200,7 +202,7 @@ func TestHistoChunkAppendable(t *testing.T) {
require.Equal(t, 0, c.NumSamples())
ts := int64(1234567890)
h1 := histogram.SparseHistogram{
h1 := histogram.Histogram{
Count: 5,
ZeroCount: 2,
Sum: 18.4,
@ -230,12 +232,13 @@ func TestHistoChunkAppendable(t *testing.T) {
h2.Count += 9
h2.ZeroCount++
h2.Sum = 30
// Existing histogram should get values converted from the above to: 6 3 0 3 0 0 2 4 5 0 1 (previous values with some new empty buckets in between)
// Existing histogram should get values converted from the above to:
// 6 3 0 3 0 0 2 4 5 0 1 (previous values with some new empty buckets in between)
// so the new histogram should have new counts >= these per-bucket counts, e.g.:
h2.PositiveBuckets = []int64{7, -2, -4, 2, -2, -1, 2, 3, 0, -5, 1} // 7 5 1 3 1 0 2 5 5 0 1 (total 30)
histoApp, _ := app.(*HistoAppender)
posInterjections, negInterjections, ok, cr := histoApp.Appendable(h2)
hApp, _ := app.(*HistogramAppender)
posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
require.Greater(t, len(posInterjections), 0)
require.Equal(t, 0, len(negInterjections))
require.True(t, ok) // Only new buckets came in.
@ -253,8 +256,8 @@ func TestHistoChunkAppendable(t *testing.T) {
h2.Sum = 21
h2.PositiveBuckets = []int64{6, -3, -1, 2, 1, -4} // counts: 6, 3, 2, 4, 5, 1 (total 21)
histoApp, _ := app.(*HistoAppender)
posInterjections, negInterjections, ok, cr := histoApp.Appendable(h2)
hApp, _ := app.(*HistogramAppender)
posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
require.Equal(t, 0, len(posInterjections))
require.Equal(t, 0, len(negInterjections))
require.False(t, ok) // Need to cut a new chunk.
@ -266,8 +269,8 @@ func TestHistoChunkAppendable(t *testing.T) {
h2.Sum = 23
h2.PositiveBuckets = []int64{6, -4, 1, -1, 2, 1, -4} // counts: 6, 2, 3, 2, 4, 5, 1 (total 23)
histoApp, _ := app.(*HistoAppender)
posInterjections, negInterjections, ok, cr := histoApp.Appendable(h2)
hApp, _ := app.(*HistogramAppender)
posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
require.Equal(t, 0, len(posInterjections))
require.Equal(t, 0, len(negInterjections))
require.False(t, ok) // Need to cut a new chunk.
@ -283,20 +286,24 @@ func TestHistoChunkAppendable(t *testing.T) {
{Offset: 3, Length: 3},
}
h2.Sum = 29
// Existing histogram should get values converted from the above to: 6 3 0 3 0 0 2 4 5 0 1 (previous values with some new empty buckets in between)
// Existing histogram should get values converted from the above to:
// 6 3 0 3 0 0 2 4 5 0 1 (previous values with some new empty buckets in between)
// so the new histogram should have new counts >= these per-bucket counts, e.g.:
h2.PositiveBuckets = []int64{7, -2, -4, 2, -2, -1, 2, 3, 0, -5, 0} // 7 5 1 3 1 0 2 5 5 0 0 (total 29)
histoApp, _ := app.(*HistoAppender)
posInterjections, negInterjections, ok, cr := histoApp.Appendable(h2)
hApp, _ := app.(*HistogramAppender)
posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
require.Equal(t, 0, len(posInterjections))
require.Equal(t, 0, len(negInterjections))
require.False(t, ok) // Need to cut a new chunk.
require.True(t, cr)
}
{ // New histogram that has a counter reset while new buckets were added before the first bucket and reset on first bucket.
// (to catch the edge case where the new bucket should be forwarded ahead until first old bucket at start)
{
// New histogram that has a counter reset while new buckets were
// added before the first bucket and reset on first bucket. (to
// catch the edge case where the new bucket should be forwarded
// ahead until first old bucket at start)
h2 := h1
h2.PositiveSpans = []histogram.Span{
{Offset: -3, Length: 2},
@ -307,12 +314,13 @@ func TestHistoChunkAppendable(t *testing.T) {
{Offset: 1, Length: 1},
}
h2.Sum = 26
// Existing histogram should get values converted from the above to: 0, 0, 6, 3, 3, 2, 4, 5, 1
// Existing histogram should get values converted from the above to:
// 0, 0, 6, 3, 3, 2, 4, 5, 1
// so the new histogram should have new counts >= these per-bucket counts, e.g.:
h2.PositiveBuckets = []int64{1, 1, 3, -2, 0, -1, 2, 1, -4} // counts: 1, 2, 5, 3, 3, 2, 4, 5, 1 (total 26)
histoApp, _ := app.(*HistoAppender)
posInterjections, negInterjections, ok, cr := histoApp.Appendable(h2)
hApp, _ := app.(*HistogramAppender)
posInterjections, negInterjections, ok, cr := hApp.Appendable(h2)
require.Equal(t, 0, len(posInterjections))
require.Equal(t, 0, len(negInterjections))
require.False(t, ok) // Need to cut a new chunk.

143
tsdb/chunkenc/varbit.go Normal file
View File

@ -0,0 +1,143 @@
// Copyright 2021 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 chunkenc
import (
"math"
)
// putVarbitFloat writes a float64 using varbit encoding. It does so by
// converting the underlying bits into an int64.
func putVarbitFloat(b *bstream, val float64) {
// TODO(beorn7): The resulting int64 here will almost never be a small
// integer. Thus, the varbit encoding doesn't really make sense
// here. This function is only used to encode the zero threshold in
// histograms. Based on that, here is an idea to improve the encoding:
//
// It is recommended to use (usually negative) powers of two as
// threshoulds. The default value for the zero threshald is in fact
// 2^-128, or 0.5*2^-127, as it is represented by IEEE 754. It is
// therefore worth a try to test if the threshold is a power of 2 and
// then just store the exponent. 0 is also a commen threshold for those
// use cases where only observations of precisely zero should go to the
// zero bucket. This results in the following proposal:
// - First we store 1 byte.
// - Iff that byte is 255 (all bits set), it is followed by a direct
// 8byte representation of the float.
// - If the byte is 0, the threshold is 0.
// - In all other cases, take the number represented by the byte,
// subtract 246, and that's the exponent (i.e. between -245 and
// +8, covering thresholds that are powers of 2 between 2^-246
// to 128).
putVarbitInt(b, int64(math.Float64bits(val)))
}
// readVarbitFloat reads a float64 encoded with putVarbitFloat
func readVarbitFloat(b *bstreamReader) (float64, error) {
val, err := readVarbitInt(b)
if err != nil {
return 0, err
}
return math.Float64frombits(uint64(val)), nil
}
// putVarbitInt writes an int64 using varbit encoding with a bit bucketing
// optimized for the dod's observed in histogram buckets.
//
// TODO(Dieterbe): We could improve this further: Each branch doesn't need to
// support any values of any of the prior branches. So we can expand the range
// of each branch. Do more with fewer bits. It comes at the price of more
// expensive encoding and decoding (cutting out and later adding back that
// center-piece we skip).
func putVarbitInt(b *bstream, val int64) {
switch {
case val == 0:
b.writeBit(zero)
case bitRange(val, 3): // -3 <= val <= 4
b.writeBits(0b10, 2)
b.writeBits(uint64(val), 3)
case bitRange(val, 6): // -31 <= val <= 32
b.writeBits(0b110, 3)
b.writeBits(uint64(val), 6)
case bitRange(val, 9): // -255 <= val <= 256
b.writeBits(0b1110, 4)
b.writeBits(uint64(val), 9)
case bitRange(val, 12): // -2047 <= val <= 2048
b.writeBits(0b11110, 5)
b.writeBits(uint64(val), 12)
default:
b.writeBits(0b11111, 5)
b.writeBits(uint64(val), 64)
}
}
// readVarbitInt reads an int64 encoced with putVarbitInt.
func readVarbitInt(b *bstreamReader) (int64, error) {
var d byte
for i := 0; i < 5; i++ {
d <<= 1
bit, err := b.readBitFast()
if err != nil {
bit, err = b.readBit()
}
if err != nil {
return 0, err
}
if bit == zero {
break
}
d |= 1
}
var val int64
var sz uint8
switch d {
case 0b0:
// val == 0
case 0b10:
sz = 3
case 0b110:
sz = 6
case 0b1110:
sz = 9
case 0b11110:
sz = 12
case 0b11111:
// Do not use fast because it's very unlikely it will succeed.
bits, err := b.readBits(64)
if err != nil {
return 0, err
}
val = int64(bits)
}
if sz != 0 {
bits, err := b.readBitsFast(sz)
if err != nil {
bits, err = b.readBits(sz)
}
if err != nil {
return 0, err
}
if bits > (1 << (sz - 1)) {
// Or something.
bits = bits - (1 << sz)
}
val = int64(bits)
}
return val, nil
}

View File

@ -1,155 +0,0 @@
// Copyright 2021 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.
// The code in this file was largely written by Damian Gryski as part of
// https://github.com/dgryski/go-tsz and published under the license below.
// It was modified to accommodate reading from byte slices without modifying
// the underlying bytes, which would panic when reading from mmap'd
// read-only byte slices.
// Copyright (c) 2015,2016 Damian Gryski <damian@gryski.com>
// All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
// * Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// * Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
package chunkenc
import (
"math"
)
// putFloat64VBBucket writes a float64 using varbit optimized for SHS buckets.
// It does so by converting the underlying bits into an int64.
func putFloat64VBBucket(b *bstream, val float64) {
// TODO: Since this is used for the zero threshold, this almost always goes into the default
// bit range (i.e. using 5+64 bits). So we can consider skipping `putInt64VBBucket` and directly
// write the float and save 5 bits here.
putInt64VBBucket(b, int64(math.Float64bits(val)))
}
// readFloat64VBBucket reads a float64 using varbit optimized for SHS buckets
func readFloat64VBBucket(b *bstreamReader) (float64, error) {
val, err := readInt64VBBucket(b)
if err != nil {
return 0, err
}
return math.Float64frombits(uint64(val)), nil
}
// putInt64VBBucket writes an int64 using varbit optimized for SHS buckets.
//
// TODO(Dieterbe): We could improve this further: Each branch doesn't need to
// support any values of any of the prior branches. So we can expand the range
// of each branch. Do more with fewer bits. It comes at the price of more
// expensive encoding and decoding (cutting out and later adding back that
// center-piece we skip).
func putInt64VBBucket(b *bstream, val int64) {
switch {
case val == 0:
b.writeBit(zero)
case bitRange(val, 3): // -3 <= val <= 4
b.writeBits(0b10, 2)
b.writeBits(uint64(val), 3)
case bitRange(val, 6): // -31 <= val <= 32
b.writeBits(0b110, 3)
b.writeBits(uint64(val), 6)
case bitRange(val, 9): // -255 <= val <= 256
b.writeBits(0b1110, 4)
b.writeBits(uint64(val), 9)
case bitRange(val, 12): // -2047 <= val <= 2048
b.writeBits(0b11110, 5)
b.writeBits(uint64(val), 12)
default:
b.writeBits(0b11111, 5)
b.writeBits(uint64(val), 64)
}
}
// readInt64VBBucket reads an int64 using varbit optimized for SHS buckets
func readInt64VBBucket(b *bstreamReader) (int64, error) {
var d byte
for i := 0; i < 5; i++ {
d <<= 1
bit, err := b.readBitFast()
if err != nil {
bit, err = b.readBit()
}
if err != nil {
return 0, err
}
if bit == zero {
break
}
d |= 1
}
var val int64
var sz uint8
switch d {
case 0b0:
// val == 0
case 0b10:
sz = 3
case 0b110:
sz = 6
case 0b1110:
sz = 9
case 0b11110:
sz = 12
case 0b11111:
// Do not use fast because it's very unlikely it will succeed.
bits, err := b.readBits(64)
if err != nil {
return 0, err
}
val = int64(bits)
}
if sz != 0 {
bits, err := b.readBitsFast(sz)
if err != nil {
bits, err = b.readBits(sz)
}
if err != nil {
return 0, err
}
if bits > (1 << (sz - 1)) {
// or something
bits = bits - (1 << sz)
}
val = int64(bits)
}
return val, nil
}

View File

@ -48,7 +48,7 @@ import (
"math"
"math/bits"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
)
const (
@ -150,8 +150,8 @@ type xorAppender struct {
trailing uint8
}
func (a *xorAppender) AppendHistogram(t int64, h histogram.SparseHistogram) {
//panic("cannot call xorAppender.AppendHistogram().")
func (a *xorAppender) AppendHistogram(t int64, h histogram.Histogram) {
panic("appended a histogram to an xor chunk")
}
func (a *xorAppender) Append(t int64, v float64) {
@ -181,6 +181,12 @@ func (a *xorAppender) Append(t int64, v float64) {
// Gorilla has a max resolution of seconds, Prometheus milliseconds.
// Thus we use higher value range steps with larger bit size.
//
// TODO(beorn7): This seems to needlessly jump to large bit
// sizes even for very small deviations from zero. Timestamp
// compression can probably benefit from some smaller bit
// buckets. See also what was done for histogram encoding in
// varbit.go.
switch {
case dod == 0:
a.b.writeBit(zero)
@ -278,7 +284,7 @@ func (it *xorIterator) At() (int64, float64) {
return it.t, it.val
}
func (it *xorIterator) AtHistogram() (int64, histogram.SparseHistogram) {
func (it *xorIterator) AtHistogram() (int64, histogram.Histogram) {
panic("cannot call xorIterator.AtHistogram().")
}

View File

@ -32,7 +32,7 @@ import (
prom_testutil "github.com/prometheus/client_golang/prometheus/testutil"
"github.com/stretchr/testify/require"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/chunkenc"
@ -1328,7 +1328,7 @@ func TestHeadCompactionWithHistograms(t *testing.T) {
type timedHist struct {
t int64
h histogram.SparseHistogram
h histogram.Histogram
}
// Ingest samples.
@ -1384,7 +1384,7 @@ func TestHeadCompactionWithHistograms(t *testing.T) {
// Depending on numSeriesPerSchema, it can take few gigs of memory;
// the test adds all samples to appender before committing instead of
// buffering the writes to make it run faster.
func TestSparseHistoSpaceSavings(t *testing.T) {
func TestSparseHistogramSpaceSavings(t *testing.T) {
t.Skip()
cases := []struct {
@ -1455,7 +1455,7 @@ func TestSparseHistoSpaceSavings(t *testing.T) {
var allSparseSeries []struct {
baseLabels labels.Labels
hists []histogram.SparseHistogram
hists []histogram.Histogram
}
for sid, schema := range allSchemas {
@ -1467,7 +1467,7 @@ func TestSparseHistoSpaceSavings(t *testing.T) {
}
allSparseSeries = append(allSparseSeries, struct {
baseLabels labels.Labels
hists []histogram.SparseHistogram
hists []histogram.Histogram
}{baseLabels: lbls, hists: generateCustomHistograms(numHistograms, c.numBuckets, c.numSpans, c.gapBetweenSpans, schema)})
}
}
@ -1522,13 +1522,13 @@ func TestSparseHistoSpaceSavings(t *testing.T) {
h := ah.hists[i]
numOldSeriesPerHistogram = 0
it := histogram.CumulativeExpandSparseHistogram(h)
it := h.CumulativeBucketIterator()
itIdx := 0
var err error
for it.Next() {
numOldSeriesPerHistogram++
b := it.At()
lbls := append(ah.baseLabels, labels.Label{Name: "le", Value: fmt.Sprintf("%.16f", b.Le)})
lbls := append(ah.baseLabels, labels.Label{Name: "le", Value: fmt.Sprintf("%.16f", b.Upper)})
refs[itIdx], err = oldApp.Append(refs[itIdx], lbls, ts, float64(b.Count))
require.NoError(t, err)
itIdx++
@ -1614,9 +1614,9 @@ Savings: Index=%.2f%%, Chunks=%.2f%%, Total=%.2f%%
}
}
func generateCustomHistograms(numHists, numBuckets, numSpans, gapBetweenSpans, schema int) (r []histogram.SparseHistogram) {
func generateCustomHistograms(numHists, numBuckets, numSpans, gapBetweenSpans, schema int) (r []histogram.Histogram) {
// First histogram with all the settings.
h := histogram.SparseHistogram{
h := histogram.Histogram{
Sum: 1000 * rand.Float64(),
Schema: int32(schema),
}
@ -1711,7 +1711,7 @@ func TestSparseHistogramCompactionAndQuery(t *testing.T) {
type timedHist struct {
t int64
h histogram.SparseHistogram
h histogram.Histogram
}
expHists := make(map[string][]timedHist)

View File

@ -34,8 +34,14 @@ in-file offset (lower 4 bytes) and segment sequence number (upper 4 bytes).
└───────────────┴───────────────────┴──────────────┴────────────────┘
```
## XOR chunk
TODO(beorn7): Add.
## Histogram chunk
TODO(beorn7): This is out of date. Update once settled on the (more or less) final format.
```
┌──────────────┬─────────────────┬──────────────────────────┬──────────────────────────┬──────────────┐
│ len <uint16> │ schema <varint> │ pos-spans <span-section> │ neg-spans <span-section> │ data <bytes>

View File

@ -29,8 +29,8 @@ import (
"go.uber.org/atomic"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/chunkenc"
@ -614,7 +614,7 @@ func (h *Head) Init(minValidTime int64) error {
sparseHistogramSeries := 0
for _, m := range h.series.series {
for _, ms := range m {
if ms.sparseHistogramSeries {
if ms.histogramSeries {
sparseHistogramSeries++
}
}
@ -1397,7 +1397,7 @@ func (s *stripeSeries) gc(mint int64) (map[uint64]struct{}, int, int64, int) {
s.locks[j].Lock()
}
if series.sparseHistogramSeries {
if series.histogramSeries {
sparseHistogramSeriesDeleted++
}
deleted[series.ref] = struct{}{}
@ -1488,9 +1488,9 @@ func (s *stripeSeries) getOrSet(hash uint64, lset labels.Labels, createSeries fu
return series, true, nil
}
type hist struct {
type histogramSample struct {
t int64
h histogram.SparseHistogram
h histogram.Histogram
}
type sample struct {
@ -1517,7 +1517,7 @@ type memSeries struct {
nextAt int64 // Timestamp at which to cut the next chunk.
sampleBuf [4]sample
histBuf [4]hist
histogramBuf [4]histogramSample
pendingCommit bool // Whether there are samples waiting to be committed to this series.
app chunkenc.Appender // Current appender for the chunk.
@ -1527,7 +1527,7 @@ type memSeries struct {
txs *txRing
// Temporary variable for sparsehistogram experiment.
sparseHistogramSeries bool
histogramSeries bool
}
func newMemSeries(lset labels.Labels, id uint64, chunkRange int64, memChunkPool *sync.Pool) *memSeries {

View File

@ -21,8 +21,8 @@ import (
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/value"
"github.com/prometheus/prometheus/storage"
@ -67,14 +67,14 @@ func (a *initAppender) AppendExemplar(ref uint64, l labels.Labels, e exemplar.Ex
return a.app.AppendExemplar(ref, l, e)
}
func (a *initAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, sh histogram.SparseHistogram) (uint64, error) {
func (a *initAppender) AppendHistogram(ref uint64, l labels.Labels, t int64, h histogram.Histogram) (uint64, error) {
if a.app != nil {
return a.app.AppendHistogram(ref, l, t, sh)
return a.app.AppendHistogram(ref, l, t, h)
}
a.head.initTime(t)
a.app = a.head.appender()
return a.app.AppendHistogram(ref, l, t, sh)
return a.app.AppendHistogram(ref, l, t, h)
}
// initTime initializes a head with the first timestamp. This only needs to be called
@ -269,8 +269,8 @@ func (a *headAppender) Append(ref uint64, lset labels.Labels, t int64, v float64
}
}
if value.IsStaleNaN(v) && s.sparseHistogramSeries {
return a.AppendHistogram(ref, lset, t, histogram.SparseHistogram{Sum: v})
if value.IsStaleNaN(v) && s.histogramSeries {
return a.AppendHistogram(ref, lset, t, histogram.Histogram{Sum: v})
}
s.Lock()
@ -322,7 +322,7 @@ func (s *memSeries) appendable(t int64, v float64) error {
}
// appendableHistogram checks whether the given sample is valid for appending to the series.
func (s *memSeries) appendableHistogram(t int64, sh histogram.SparseHistogram) error {
func (s *memSeries) appendableHistogram(t int64, sh histogram.Histogram) error {
c := s.head()
if c == nil {
return nil
@ -372,7 +372,7 @@ func (a *headAppender) AppendExemplar(ref uint64, _ labels.Labels, e exemplar.Ex
return s.ref, nil
}
func (a *headAppender) AppendHistogram(ref uint64, lset labels.Labels, t int64, sh histogram.SparseHistogram) (uint64, error) {
func (a *headAppender) AppendHistogram(ref uint64, lset labels.Labels, t int64, h histogram.Histogram) (uint64, error) {
if t < a.minValidTime {
a.head.metrics.outOfBoundSamples.Inc()
return 0, storage.ErrOutOfBounds
@ -396,7 +396,7 @@ func (a *headAppender) AppendHistogram(ref uint64, lset labels.Labels, t int64,
if err != nil {
return 0, err
}
s.sparseHistogramSeries = true
s.histogramSeries = true
if created {
a.head.metrics.sparseHistogramSeries.Inc()
a.series = append(a.series, record.RefSeries{
@ -407,7 +407,7 @@ func (a *headAppender) AppendHistogram(ref uint64, lset labels.Labels, t int64,
}
s.Lock()
if err := s.appendableHistogram(t, sh); err != nil {
if err := s.appendableHistogram(t, h); err != nil {
s.Unlock()
if err == storage.ErrOutOfOrderSample {
a.head.metrics.outOfOrderSamples.Inc()
@ -427,7 +427,7 @@ func (a *headAppender) AppendHistogram(ref uint64, lset labels.Labels, t int64,
a.histograms = append(a.histograms, record.RefHistogram{
Ref: s.ref,
T: t,
H: sh,
H: h,
})
a.histogramSeries = append(a.histogramSeries, s)
return s.ref, nil
@ -604,37 +604,39 @@ func (s *memSeries) append(t int64, v float64, appendID uint64, chunkDiskMapper
return true, chunkCreated
}
// appendHistogram adds the sparse histogram.
// appendHistogram adds the histogram.
// It is unsafe to call this concurrently with s.iterator(...) without holding the series lock.
func (s *memSeries) appendHistogram(t int64, sh histogram.SparseHistogram, appendID uint64, chunkDiskMapper *chunks.ChunkDiskMapper) (sampleInOrder, chunkCreated bool) {
func (s *memSeries) appendHistogram(t int64, sh histogram.Histogram, appendID uint64, chunkDiskMapper *chunks.ChunkDiskMapper) (sampleInOrder, chunkCreated bool) {
// Head controls the execution of recoding, so that we own the proper chunk reference afterwards.
// We check for Appendable before appendPreprocessor because in case it ends up creating a new chunk,
// we need to know if there was also a counter reset or not to set the meta properly.
app, _ := s.app.(*chunkenc.HistoAppender)
app, _ := s.app.(*chunkenc.HistogramAppender)
var (
posInterjections, negInterjections []chunkenc.Interjection
okToAppend, counterReset bool
positiveInterjections, negativeInterjections []chunkenc.Interjection
okToAppend, counterReset bool
)
if app != nil {
posInterjections, negInterjections, okToAppend, counterReset = app.Appendable(sh)
positiveInterjections, negativeInterjections, okToAppend, counterReset = app.Appendable(sh)
}
c, sampleInOrder, chunkCreated := s.appendPreprocessor(t, chunkenc.EncSHS, chunkDiskMapper)
c, sampleInOrder, chunkCreated := s.appendPreprocessor(t, chunkenc.EncHistogram, chunkDiskMapper)
if !sampleInOrder {
return sampleInOrder, chunkCreated
}
if !chunkCreated {
// We have 3 cases here
// !okToAppend -> we need to cut a new chunk
// okToAppend but we have interjections -> existing chunk needs recoding before we can append our histogram
// okToAppend and no interjections -> chunk is ready to support our histogram
// - !okToAppend -> We need to cut a new chunk.
// - okToAppend but we have interjections -> Existing chunk needs recoding before we can append our histogram.
// - okToAppend and no interjections -> Chunk is ready to support our histogram.
if !okToAppend || counterReset {
c = s.cutNewHeadChunk(t, chunkenc.EncSHS, chunkDiskMapper)
c = s.cutNewHeadChunk(t, chunkenc.EncHistogram, chunkDiskMapper)
chunkCreated = true
} else if len(posInterjections) > 0 || len(negInterjections) > 0 {
// new buckets have appeared. we need to recode all prior histograms within the chunk before we can process this one.
chunk, app := app.Recode(posInterjections, negInterjections, sh.PositiveSpans, sh.NegativeSpans)
} else if len(positiveInterjections) > 0 || len(negativeInterjections) > 0 {
// New buckets have appeared. We need to recode all
// prior histogram samples within the chunk before we
// can process this one.
chunk, app := app.Recode(positiveInterjections, negativeInterjections, sh.PositiveSpans, sh.NegativeSpans)
s.headChunk = &memChunk{
minTime: s.headChunk.minTime,
maxTime: s.headChunk.maxTime,
@ -645,7 +647,7 @@ func (s *memSeries) appendHistogram(t int64, sh histogram.SparseHistogram, appen
}
if chunkCreated {
hc := s.headChunk.chunk.(*chunkenc.HistoChunk)
hc := s.headChunk.chunk.(*chunkenc.HistogramChunk)
header := chunkenc.UnknownCounterReset
if counterReset {
header = chunkenc.CounterReset
@ -656,14 +658,14 @@ func (s *memSeries) appendHistogram(t int64, sh histogram.SparseHistogram, appen
}
s.app.AppendHistogram(t, sh)
s.sparseHistogramSeries = true
s.histogramSeries = true
c.maxTime = t
s.histBuf[0] = s.histBuf[1]
s.histBuf[1] = s.histBuf[2]
s.histBuf[2] = s.histBuf[3]
s.histBuf[3] = hist{t: t, h: sh}
s.histogramBuf[0] = s.histogramBuf[1]
s.histogramBuf[1] = s.histogramBuf[2]
s.histogramBuf[2] = s.histogramBuf[3]
s.histogramBuf[3] = histogramSample{t: t, h: sh}
if appendID > 0 {
s.txs.add(appendID)

View File

@ -21,7 +21,7 @@ import (
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/chunkenc"
@ -443,7 +443,7 @@ func (s *memSeries) iterator(id int, isoState *isolationState, chunkDiskMapper *
msIter.total = numSamples
msIter.stopAfter = stopAfter
msIter.buf = s.sampleBuf
msIter.histBuf = s.histBuf
msIter.histogramBuf = s.histogramBuf
return msIter
}
return &memSafeIterator{
@ -452,18 +452,18 @@ func (s *memSeries) iterator(id int, isoState *isolationState, chunkDiskMapper *
i: -1,
stopAfter: stopAfter,
},
total: numSamples,
buf: s.sampleBuf,
histBuf: s.histBuf,
total: numSamples,
buf: s.sampleBuf,
histogramBuf: s.histogramBuf,
}
}
type memSafeIterator struct {
stopIterator
total int
buf [4]sample
histBuf [4]hist
total int
buf [4]sample
histogramBuf [4]histogramSample
}
func (it *memSafeIterator) Seek(t int64) bool {
@ -502,11 +502,11 @@ func (it *memSafeIterator) At() (int64, float64) {
return s.t, s.v
}
func (it *memSafeIterator) AtHistogram() (int64, histogram.SparseHistogram) {
func (it *memSafeIterator) AtHistogram() (int64, histogram.Histogram) {
if it.total-it.i > 4 {
return it.Iterator.AtHistogram()
}
s := it.histBuf[4-(it.total-it.i)]
s := it.histogramBuf[4-(it.total-it.i)]
return s.t, s.h
}

View File

@ -37,8 +37,8 @@ import (
"go.uber.org/atomic"
"github.com/prometheus/prometheus/config"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/exemplar"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/pkg/value"
"github.com/prometheus/prometheus/storage"
@ -2539,15 +2539,15 @@ func TestAppendHistogram(t *testing.T) {
require.NoError(t, head.Init(0))
app := head.Appender(context.Background())
type timedHist struct {
type timedHistogram struct {
t int64
h histogram.SparseHistogram
h histogram.Histogram
}
expHists := make([]timedHist, 0, numHistograms)
expHistograms := make([]timedHistogram, 0, numHistograms)
for i, h := range generateHistograms(numHistograms) {
_, err := app.AppendHistogram(0, l, int64(i), h)
require.NoError(t, err)
expHists = append(expHists, timedHist{int64(i), h})
expHistograms = append(expHistograms, timedHistogram{int64(i), h})
}
require.NoError(t, app.Commit())
@ -2564,13 +2564,13 @@ func TestAppendHistogram(t *testing.T) {
require.False(t, ss.Next())
it := s.Iterator()
actHists := make([]timedHist, 0, len(expHists))
actHistograms := make([]timedHistogram, 0, len(expHistograms))
for it.Next() {
t, h := it.AtHistogram()
actHists = append(actHists, timedHist{t, h.Copy()})
actHistograms = append(actHistograms, timedHistogram{t, h.Copy()})
}
require.Equal(t, expHists, actHists)
require.Equal(t, expHistograms, actHistograms)
})
}
}
@ -2586,17 +2586,17 @@ func TestHistogramInWAL(t *testing.T) {
require.NoError(t, head.Init(0))
app := head.Appender(context.Background())
type timedHist struct {
type timedHistogram struct {
t int64
h histogram.SparseHistogram
h histogram.Histogram
}
expHists := make([]timedHist, 0, numHistograms)
expHistograms := make([]timedHistogram, 0, numHistograms)
for i, h := range generateHistograms(numHistograms) {
h.NegativeSpans = h.PositiveSpans
h.NegativeBuckets = h.PositiveBuckets
_, err := app.AppendHistogram(0, l, int64(i), h)
require.NoError(t, err)
expHists = append(expHists, timedHist{int64(i), h})
expHistograms = append(expHistograms, timedHistogram{int64(i), h})
}
require.NoError(t, app.Commit())
@ -2621,18 +2621,18 @@ func TestHistogramInWAL(t *testing.T) {
require.False(t, ss.Next())
it := s.Iterator()
actHists := make([]timedHist, 0, len(expHists))
actHistograms := make([]timedHistogram, 0, len(expHistograms))
for it.Next() {
t, h := it.AtHistogram()
actHists = append(actHists, timedHist{t, h.Copy()})
actHistograms = append(actHistograms, timedHistogram{t, h.Copy()})
}
require.Equal(t, expHists, actHists)
require.Equal(t, expHistograms, actHistograms)
}
func generateHistograms(n int) (r []histogram.SparseHistogram) {
func generateHistograms(n int) (r []histogram.Histogram) {
for i := 0; i < n; i++ {
r = append(r, histogram.SparseHistogram{
r = append(r, histogram.Histogram{
Count: 5 + uint64(i*4),
ZeroCount: 2 + uint64(i),
ZeroThreshold: 0.001,
@ -2957,22 +2957,22 @@ func TestSparseHistogramMetrics(t *testing.T) {
})
require.NoError(t, head.Init(0))
expHistSeries, expHistSamples := 0, 0
expHSeries, expHSamples := 0, 0
for x := 0; x < 5; x++ {
expHistSeries++
expHSeries++
l := labels.Labels{{Name: "a", Value: fmt.Sprintf("b%d", x)}}
for i, h := range generateHistograms(10) {
app := head.Appender(context.Background())
_, err := app.AppendHistogram(0, l, int64(i), h)
require.NoError(t, err)
require.NoError(t, app.Commit())
expHistSamples++
expHSamples++
}
}
require.Equal(t, float64(expHistSeries), prom_testutil.ToFloat64(head.metrics.sparseHistogramSeries))
require.Equal(t, float64(expHistSamples), prom_testutil.ToFloat64(head.metrics.sparseHistogramSamplesTotal))
require.Equal(t, float64(expHSeries), prom_testutil.ToFloat64(head.metrics.sparseHistogramSeries))
require.Equal(t, float64(expHSamples), prom_testutil.ToFloat64(head.metrics.sparseHistogramSamplesTotal))
require.NoError(t, head.Close())
w, err := wal.NewSize(nil, nil, head.wal.Dir(), 32768, false)
@ -2981,7 +2981,7 @@ func TestSparseHistogramMetrics(t *testing.T) {
require.NoError(t, err)
require.NoError(t, head.Init(0))
require.Equal(t, float64(expHistSeries), prom_testutil.ToFloat64(head.metrics.sparseHistogramSeries))
require.Equal(t, float64(expHSeries), prom_testutil.ToFloat64(head.metrics.sparseHistogramSeries))
require.Equal(t, float64(0), prom_testutil.ToFloat64(head.metrics.sparseHistogramSamplesTotal)) // Counter reset.
}
@ -2994,11 +2994,11 @@ func TestSparseHistogramStaleSample(t *testing.T) {
})
require.NoError(t, head.Init(0))
type timedHist struct {
type timedHistogram struct {
t int64
h histogram.SparseHistogram
h histogram.Histogram
}
expHists := make([]timedHist, 0, numHistograms)
expHistograms := make([]timedHistogram, 0, numHistograms)
testQuery := func(numStale int) {
q, err := NewBlockQuerier(head, head.MinTime(), head.MaxTime())
@ -3014,17 +3014,17 @@ func TestSparseHistogramStaleSample(t *testing.T) {
require.False(t, ss.Next())
it := s.Iterator()
actHists := make([]timedHist, 0, len(expHists))
actHistograms := make([]timedHistogram, 0, len(expHistograms))
for it.Next() {
t, h := it.AtHistogram()
actHists = append(actHists, timedHist{t, h.Copy()})
actHistograms = append(actHistograms, timedHistogram{t, h.Copy()})
}
// We cannot compare StaleNAN with require.Equal, hence checking each histogram manually.
require.Equal(t, len(expHists), len(actHists))
require.Equal(t, len(expHistograms), len(actHistograms))
actNumStale := 0
for i, eh := range expHists {
ah := actHists[i]
for i, eh := range expHistograms {
ah := actHistograms[i]
if value.IsStaleNaN(eh.h.Sum) {
actNumStale++
require.True(t, value.IsStaleNaN(ah.h.Sum))
@ -3040,14 +3040,14 @@ func TestSparseHistogramStaleSample(t *testing.T) {
// Adding stale in the same appender.
app := head.Appender(context.Background())
for _, h := range generateHistograms(numHistograms) {
_, err := app.AppendHistogram(0, l, 100*int64(len(expHists)), h)
_, err := app.AppendHistogram(0, l, 100*int64(len(expHistograms)), h)
require.NoError(t, err)
expHists = append(expHists, timedHist{100 * int64(len(expHists)), h})
expHistograms = append(expHistograms, timedHistogram{100 * int64(len(expHistograms)), h})
}
// +1 so that delta-of-delta is not 0.
_, err := app.Append(0, l, 100*int64(len(expHists))+1, math.Float64frombits(value.StaleNaN))
_, err := app.Append(0, l, 100*int64(len(expHistograms))+1, math.Float64frombits(value.StaleNaN))
require.NoError(t, err)
expHists = append(expHists, timedHist{100*int64(len(expHists)) + 1, histogram.SparseHistogram{Sum: math.Float64frombits(value.StaleNaN)}})
expHistograms = append(expHistograms, timedHistogram{100*int64(len(expHistograms)) + 1, histogram.Histogram{Sum: math.Float64frombits(value.StaleNaN)}})
require.NoError(t, app.Commit())
// Only 1 chunk in the memory, no m-mapped chunk.
@ -3059,17 +3059,17 @@ func TestSparseHistogramStaleSample(t *testing.T) {
// Adding stale in different appender and continuing series after a stale sample.
app = head.Appender(context.Background())
for _, h := range generateHistograms(2 * numHistograms)[numHistograms:] {
_, err := app.AppendHistogram(0, l, 100*int64(len(expHists)), h)
_, err := app.AppendHistogram(0, l, 100*int64(len(expHistograms)), h)
require.NoError(t, err)
expHists = append(expHists, timedHist{100 * int64(len(expHists)), h})
expHistograms = append(expHistograms, timedHistogram{100 * int64(len(expHistograms)), h})
}
require.NoError(t, app.Commit())
app = head.Appender(context.Background())
// +1 so that delta-of-delta is not 0.
_, err = app.Append(0, l, 100*int64(len(expHists))+1, math.Float64frombits(value.StaleNaN))
_, err = app.Append(0, l, 100*int64(len(expHistograms))+1, math.Float64frombits(value.StaleNaN))
require.NoError(t, err)
expHists = append(expHists, timedHist{100*int64(len(expHists)) + 1, histogram.SparseHistogram{Sum: math.Float64frombits(value.StaleNaN)}})
expHistograms = append(expHistograms, timedHistogram{100*int64(len(expHistograms)) + 1, histogram.Histogram{Sum: math.Float64frombits(value.StaleNaN)}})
require.NoError(t, app.Commit())
// Total 2 chunks, 1 m-mapped.

View File

@ -15,11 +15,6 @@ package tsdb
import (
"fmt"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/encoding"
tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
"github.com/prometheus/prometheus/tsdb/fileutil"
"io/ioutil"
"math"
"os"
@ -30,6 +25,12 @@ import (
"sync"
"time"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/prometheus/prometheus/tsdb/encoding"
tsdb_errors "github.com/prometheus/prometheus/tsdb/errors"
"github.com/prometheus/prometheus/tsdb/fileutil"
"github.com/go-kit/log/level"
"github.com/pkg/errors"
"go.uber.org/atomic"
@ -159,7 +160,7 @@ func (h *Head) loadWAL(r *wal.Reader, multiRef map[uint64]uint64, mmappedChunks
if ms.head() == nil {
// First histogram for the series. Count this in metrics.
ms.sparseHistogramSeries = true
ms.histogramSeries = true
}
if rh.T < h.minValidTime.Load() {

View File

@ -21,7 +21,7 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/tsdb/chunkenc"
@ -649,7 +649,7 @@ func (p *populateWithDelSeriesIterator) Seek(t int64) bool {
}
func (p *populateWithDelSeriesIterator) At() (int64, float64) { return p.curr.At() }
func (p *populateWithDelSeriesIterator) AtHistogram() (int64, histogram.SparseHistogram) {
func (p *populateWithDelSeriesIterator) AtHistogram() (int64, histogram.Histogram) {
return p.curr.AtHistogram()
}
func (p *populateWithDelSeriesIterator) ChunkEncoding() chunkenc.Encoding {
@ -688,8 +688,8 @@ func (p *populateWithDelChunkSeriesIterator) Next() bool {
app chunkenc.Appender
err error
)
if p.currDelIter.ChunkEncoding() == chunkenc.EncSHS {
newChunk = chunkenc.NewHistoChunk()
if p.currDelIter.ChunkEncoding() == chunkenc.EncHistogram {
newChunk = chunkenc.NewHistogramChunk()
app, err = newChunk.Appender()
} else {
newChunk = chunkenc.NewXORChunk()
@ -714,11 +714,11 @@ func (p *populateWithDelChunkSeriesIterator) Next() bool {
var (
t int64
v float64
h histogram.SparseHistogram
h histogram.Histogram
)
if p.currDelIter.ChunkEncoding() == chunkenc.EncSHS {
if hc, ok := p.currChkMeta.Chunk.(*chunkenc.HistoChunk); ok {
newChunk.(*chunkenc.HistoChunk).SetCounterResetHeader(hc.GetCounterResetHeader())
if p.currDelIter.ChunkEncoding() == chunkenc.EncHistogram {
if hc, ok := p.currChkMeta.Chunk.(*chunkenc.HistogramChunk); ok {
newChunk.(*chunkenc.HistogramChunk).SetCounterResetHeader(hc.GetCounterResetHeader())
}
t, h = p.currDelIter.AtHistogram()
p.curr.MinTime = t
@ -870,7 +870,7 @@ func (it *DeletedIterator) At() (int64, float64) {
return it.Iter.At()
}
func (it *DeletedIterator) AtHistogram() (int64, histogram.SparseHistogram) {
func (it *DeletedIterator) AtHistogram() (int64, histogram.Histogram) {
t, h := it.Iter.AtHistogram()
return t, h
}
@ -889,7 +889,7 @@ func (it *DeletedIterator) Seek(t int64) bool {
// Now double check if the entry falls into a deleted interval.
var ts int64
if it.ChunkEncoding() == chunkenc.EncSHS {
if it.ChunkEncoding() == chunkenc.EncHistogram {
ts, _ = it.AtHistogram()
} else {
ts, _ = it.At()
@ -916,7 +916,7 @@ func (it *DeletedIterator) Next() bool {
Outer:
for it.Iter.Next() {
var ts int64
if it.ChunkEncoding() == chunkenc.EncSHS {
if it.ChunkEncoding() == chunkenc.EncHistogram {
ts, _ = it.AtHistogram()
} else {
ts, _ = it.At()

View File

@ -20,7 +20,7 @@ import (
"github.com/pkg/errors"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/pkg/labels"
"github.com/prometheus/prometheus/tsdb/encoding"
"github.com/prometheus/prometheus/tsdb/tombstones"
@ -74,7 +74,7 @@ type RefExemplar struct {
type RefHistogram struct {
Ref uint64
T int64
H histogram.SparseHistogram
H histogram.Histogram
}
// Decoder decodes series, sample, and tombstone records.
@ -233,14 +233,14 @@ func (d *Decoder) ExemplarsFromBuffer(dec *encoding.Decbuf, exemplars []RefExemp
return exemplars, nil
}
func (d *Decoder) Histograms(rec []byte, hists []RefHistogram) ([]RefHistogram, error) {
func (d *Decoder) Histograms(rec []byte, histograms []RefHistogram) ([]RefHistogram, error) {
dec := encoding.Decbuf{B: rec}
t := Type(dec.Byte())
if t != Histograms {
return nil, errors.New("invalid record type")
}
if dec.Len() == 0 {
return hists, nil
return histograms, nil
}
var (
baseRef = dec.Be64()
@ -253,7 +253,7 @@ func (d *Decoder) Histograms(rec []byte, hists []RefHistogram) ([]RefHistogram,
rh := RefHistogram{
Ref: baseRef + uint64(dref),
T: baseTime + dtime,
H: histogram.SparseHistogram{
H: histogram.Histogram{
Schema: 0,
ZeroThreshold: 0,
ZeroCount: 0,
@ -303,16 +303,16 @@ func (d *Decoder) Histograms(rec []byte, hists []RefHistogram) ([]RefHistogram,
rh.H.NegativeBuckets[i] = dec.Varint64()
}
hists = append(hists, rh)
histograms = append(histograms, rh)
}
if dec.Err() != nil {
return nil, errors.Wrapf(dec.Err(), "decode error after %d histograms", len(hists))
return nil, errors.Wrapf(dec.Err(), "decode error after %d histograms", len(histograms))
}
if len(dec.B) > 0 {
return nil, errors.Errorf("unexpected %d bytes left in entry", len(dec.B))
}
return hists, nil
return histograms, nil
}
// Encoder encodes series, sample, and tombstones records.
@ -410,21 +410,21 @@ func (e *Encoder) EncodeExemplarsIntoBuffer(exemplars []RefExemplar, buf *encodi
}
}
func (e *Encoder) Histograms(hists []RefHistogram, b []byte) []byte {
func (e *Encoder) Histograms(histograms []RefHistogram, b []byte) []byte {
buf := encoding.Encbuf{B: b}
buf.PutByte(byte(Histograms))
if len(hists) == 0 {
if len(histograms) == 0 {
return buf.Get()
}
// Store base timestamp and base reference number of first histogram.
// All histograms encode their timestamp and ref as delta to those.
first := hists[0]
first := histograms[0]
buf.PutBE64(first.Ref)
buf.PutBE64int64(first.T)
for _, h := range hists {
for _, h := range histograms {
buf.PutVarint64(int64(h.Ref) - int64(first.Ref))
buf.PutVarint64(h.T - first.T)

View File

@ -16,7 +16,7 @@ package tsdbutil
import (
"math"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/tsdb/chunkenc"
)
@ -160,8 +160,8 @@ func (it *sampleRingIterator) At() (int64, float64) {
return it.r.at(it.i)
}
func (it *sampleRingIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return 0, histogram.SparseHistogram{}
func (it *sampleRingIterator) AtHistogram() (int64, histogram.Histogram) {
return 0, histogram.Histogram{}
}
func (it *sampleRingIterator) ChunkEncoding() chunkenc.Encoding {

View File

@ -18,7 +18,7 @@ import (
"sort"
"testing"
"github.com/prometheus/prometheus/pkg/histogram"
"github.com/prometheus/prometheus/model/histogram"
"github.com/prometheus/prometheus/tsdb/chunkenc"
"github.com/stretchr/testify/require"
)
@ -152,8 +152,8 @@ func (it *listSeriesIterator) At() (int64, float64) {
return s.t, s.v
}
func (it *listSeriesIterator) AtHistogram() (int64, histogram.SparseHistogram) {
return 0, histogram.SparseHistogram{}
func (it *listSeriesIterator) AtHistogram() (int64, histogram.Histogram) {
return 0, histogram.Histogram{}
}
func (it *listSeriesIterator) ChunkEncoding() chunkenc.Encoding {

View File

@ -296,7 +296,6 @@ var sampleFlagMap = map[string]string{
}
func TestEndpoints(t *testing.T) {
t.Skip()
suite, err := promql.NewTest(t, `
load 1m
test_metric1{foo="bar"} 0+100x100