mirror of
https://github.com/prometheus/prometheus.git
synced 2026-02-10 18:31:12 +01:00
1287 lines
37 KiB
Go
1287 lines
37 KiB
Go
// Copyright The Prometheus Authors
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package tsdb
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/prometheus/prometheus/model/exemplar"
|
|
"github.com/prometheus/prometheus/model/labels"
|
|
"github.com/prometheus/prometheus/storage"
|
|
)
|
|
|
|
var eMetrics = NewExemplarMetrics(prometheus.DefaultRegisterer)
|
|
|
|
// Tests the same exemplar cases as AddExemplar, but specifically the ValidateExemplar function so it can be relied on externally.
|
|
func TestValidateExemplar(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(2, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
l := labels.FromStrings("service", "asdf")
|
|
e := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "qwerty"),
|
|
Value: 0.1,
|
|
Ts: 1,
|
|
}
|
|
|
|
require.NoError(t, es.ValidateExemplar(l, e))
|
|
require.NoError(t, es.AddExemplar(l, e))
|
|
|
|
e2 := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "zxcvb"),
|
|
Value: 0.1,
|
|
Ts: 2,
|
|
}
|
|
|
|
require.NoError(t, es.ValidateExemplar(l, e2))
|
|
require.NoError(t, es.AddExemplar(l, e2))
|
|
|
|
require.Equal(t, es.ValidateExemplar(l, e2), storage.ErrDuplicateExemplar, "error is expected attempting to validate duplicate exemplar")
|
|
|
|
e3 := e2
|
|
e3.Ts = 3
|
|
require.Equal(t, es.ValidateExemplar(l, e3), storage.ErrDuplicateExemplar, "error is expected when attempting to add duplicate exemplar, even with different timestamp")
|
|
|
|
e3.Ts = 1
|
|
e3.Value = 0.3
|
|
require.Equal(t, es.ValidateExemplar(l, e3), storage.ErrOutOfOrderExemplar)
|
|
|
|
e4 := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("a", strings.Repeat("b", exemplar.ExemplarMaxLabelSetLength)),
|
|
Value: 0.1,
|
|
Ts: 2,
|
|
}
|
|
require.Equal(t, storage.ErrExemplarLabelLength, es.ValidateExemplar(l, e4))
|
|
}
|
|
|
|
func TestCircularExemplarStorage_AddExemplar(t *testing.T) {
|
|
series1 := labels.FromStrings("trace_id", "foo")
|
|
series2 := labels.FromStrings("trace_id", "bar")
|
|
|
|
series1Matcher := []*labels.Matcher{{
|
|
Type: labels.MatchEqual,
|
|
Name: "trace_id",
|
|
Value: series1.Get("trace_id"),
|
|
}}
|
|
|
|
series2Matcher := []*labels.Matcher{{
|
|
Type: labels.MatchEqual,
|
|
Name: "trace_id",
|
|
Value: series2.Get("trace_id"),
|
|
}}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
size int64
|
|
exemplars []exemplar.Exemplar
|
|
wantExemplars []exemplar.Exemplar
|
|
matcher []*labels.Matcher
|
|
wantError error
|
|
}{
|
|
{
|
|
name: "insert after newest",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "insert before oldest",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 2},
|
|
{Labels: series1, Value: 0.2, Ts: 1},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 1},
|
|
{Labels: series1, Value: 0.1, Ts: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "insert in between",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
{Labels: series1, Value: 0.3, Ts: 2},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.3, Ts: 2},
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
},
|
|
},
|
|
{
|
|
name: "insert after newest with overflow",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
},
|
|
{
|
|
name: "insert before oldest with overflow",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 0},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.4, Ts: 0},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
},
|
|
{
|
|
name: "insert between with overflow",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
{Labels: series1, Value: 0.3, Ts: 4},
|
|
{Labels: series1, Value: 0.4, Ts: 2},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.4, Ts: 2},
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
{Labels: series1, Value: 0.3, Ts: 4},
|
|
},
|
|
},
|
|
{
|
|
name: "out-of-order insert where evicted entry is insertion point",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2}, // pos 0, linked list middle
|
|
{Labels: series1, Value: 0.1, Ts: 1}, // pos 1, linked list oldest
|
|
{Labels: series1, Value: 0.5, Ts: 5}, // pos 2, linked list newest
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
},
|
|
},
|
|
{
|
|
name: "insert out of the OOO window",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 200},
|
|
{Labels: series1, Value: 0.2, Ts: 1},
|
|
},
|
|
wantError: storage.ErrOutOfOrderExemplar,
|
|
},
|
|
{
|
|
name: "insert multiple series",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
{Labels: series2, Value: 0.3, Ts: 4},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
},
|
|
},
|
|
{
|
|
name: "insert multiple series with overflow",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series2, Value: 0.1, Ts: 1},
|
|
{Labels: series2, Value: 0.2, Ts: 2},
|
|
{Labels: series2, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
matcher: series2Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series2, Value: 0.2, Ts: 2},
|
|
{Labels: series2, Value: 0.3, Ts: 3},
|
|
},
|
|
},
|
|
{
|
|
name: "series1 overflows series2 out-of-order",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series2, Value: 0.1, Ts: 3},
|
|
{Labels: series2, Value: 0.2, Ts: 2},
|
|
{Labels: series2, Value: 0.3, Ts: 4},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
{Labels: series1, Value: 0.5, Ts: 1},
|
|
},
|
|
matcher: series2Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series2, Value: 0.3, Ts: 4},
|
|
},
|
|
},
|
|
{
|
|
name: "ignore duplicate exemplars",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 3},
|
|
{Labels: series1, Value: 0.1, Ts: 3},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 3},
|
|
},
|
|
},
|
|
{
|
|
name: "ignore duplicate exemplars when buffer is full",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 3},
|
|
{Labels: series1, Value: 0.2, Ts: 4},
|
|
{Labels: series1, Value: 0.3, Ts: 5},
|
|
{Labels: series1, Value: 0.3, Ts: 5},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 3},
|
|
{Labels: series1, Value: 0.2, Ts: 4},
|
|
{Labels: series1, Value: 0.3, Ts: 5},
|
|
},
|
|
},
|
|
{
|
|
name: "empty timestamps are valid",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 0},
|
|
{Labels: series1, Value: 0.2, Ts: 0},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 0},
|
|
{Labels: series1, Value: 0.2, Ts: 0},
|
|
},
|
|
},
|
|
{
|
|
name: "exemplar label length exceeds maximum",
|
|
size: 3,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: labels.FromStrings("a", strings.Repeat("b", exemplar.ExemplarMaxLabelSetLength)), Value: 0.1, Ts: 2},
|
|
},
|
|
wantError: storage.ErrExemplarLabelLength,
|
|
},
|
|
{
|
|
name: "native histograms",
|
|
size: 6,
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
},
|
|
{
|
|
name: "evict only exemplar for series then re-add",
|
|
size: 2,
|
|
exemplars: []exemplar.Exemplar{
|
|
// series1 at index 0, series2 at index 1, then series1 evicts its own only exemplar
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series2, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
matcher: series1Matcher,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
},
|
|
}
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(tc.size, eMetrics, 100)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
// Add exemplars and compare tc.wantErr against the first exemplar failing.
|
|
var addError error
|
|
for i, ex := range tc.exemplars {
|
|
addError = es.AddExemplar(ex.Labels, ex)
|
|
if addError != nil {
|
|
break
|
|
}
|
|
if testing.Verbose() {
|
|
t.Logf("Buffer[%d]:\n%s", i, debugCircularBuffer(es))
|
|
}
|
|
}
|
|
if tc.wantError == nil {
|
|
require.NoError(t, addError)
|
|
} else {
|
|
require.ErrorIs(t, addError, tc.wantError)
|
|
}
|
|
if addError != nil {
|
|
return
|
|
}
|
|
|
|
// Ensure exemplars are returned correctly and in-order.
|
|
gotExemplars, err := es.Select(0, 1000, tc.matcher)
|
|
require.NoError(t, err)
|
|
if len(tc.wantExemplars) == 0 {
|
|
require.Empty(t, gotExemplars)
|
|
} else {
|
|
require.Len(t, gotExemplars, 1)
|
|
require.Equal(t, tc.wantExemplars, gotExemplars[0].Exemplars)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCircularExemplarStorage_Resize(t *testing.T) {
|
|
series1 := labels.FromStrings("trace_id", "foo")
|
|
series2 := labels.FromStrings("trace_id", "bar")
|
|
matcher1 := []*labels.Matcher{
|
|
labels.MustNewMatcher(labels.MatchRegexp, "trace_id", "(foo|bar)"),
|
|
}
|
|
|
|
testCases := []struct {
|
|
name string
|
|
exemplars []exemplar.Exemplar
|
|
resize int64
|
|
wantExemplars []exemplar.Exemplar
|
|
wantNextIndex int
|
|
wantError error
|
|
}{
|
|
{
|
|
name: "in-order, grow",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
resize: 10,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
wantNextIndex: 3,
|
|
},
|
|
{
|
|
name: "in-order, shrink",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
resize: 2,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
wantNextIndex: 0,
|
|
},
|
|
{
|
|
name: "out-of-order, shrink",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
},
|
|
resize: 2,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
wantNextIndex: 0,
|
|
},
|
|
{
|
|
name: "out-of-order, grow",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
resize: 5,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
wantNextIndex: 3,
|
|
},
|
|
{
|
|
name: "duplicate timestamps",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 1},
|
|
{Labels: series1, Value: 0.3, Ts: 2},
|
|
},
|
|
resize: 3,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 1},
|
|
{Labels: series1, Value: 0.3, Ts: 2},
|
|
},
|
|
},
|
|
{
|
|
name: "empty input, grow",
|
|
exemplars: []exemplar.Exemplar{},
|
|
resize: 10,
|
|
wantExemplars: []exemplar.Exemplar{},
|
|
wantNextIndex: 3,
|
|
},
|
|
{
|
|
name: "empty input, shrink",
|
|
exemplars: []exemplar.Exemplar{},
|
|
resize: 1,
|
|
wantExemplars: []exemplar.Exemplar{},
|
|
wantNextIndex: 0,
|
|
},
|
|
{
|
|
name: "shrink to zero",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
resize: 0,
|
|
wantExemplars: []exemplar.Exemplar{},
|
|
wantNextIndex: 0,
|
|
},
|
|
{
|
|
name: "multiple series, shrink",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series2, Value: 1.1, Ts: 2},
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
{Labels: series2, Value: 1.2, Ts: 4},
|
|
},
|
|
resize: 2,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 3},
|
|
{Labels: series2, Value: 1.2, Ts: 4},
|
|
},
|
|
wantNextIndex: 0,
|
|
},
|
|
{
|
|
name: "shrink to one",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
resize: 1,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
wantNextIndex: 0,
|
|
},
|
|
{
|
|
name: "shrink to two",
|
|
exemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
},
|
|
resize: 2,
|
|
wantExemplars: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
},
|
|
wantNextIndex: 0,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(3, eMetrics, 100)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
for _, ex := range tc.exemplars {
|
|
require.NoError(t, es.AddExemplar(ex.Labels, ex))
|
|
}
|
|
|
|
// Resize the circular buffer.
|
|
if testing.Verbose() {
|
|
t.Logf("Buffer[before-resize]:\n%s", debugCircularBuffer(es))
|
|
}
|
|
es.Resize(tc.resize)
|
|
if testing.Verbose() {
|
|
t.Logf("Buffer[after-resize]:\n%s", debugCircularBuffer(es))
|
|
}
|
|
|
|
// Ensure exemplars are returned correctly and in-order.
|
|
gotExemplars, err := es.Select(0, 1000, matcher1)
|
|
require.NoError(t, err)
|
|
flat := make([]exemplar.Exemplar, 0)
|
|
for _, group := range gotExemplars {
|
|
flat = append(flat, group.Exemplars...)
|
|
}
|
|
sort.Slice(flat, func(i, j int) bool {
|
|
return flat[i].Ts < flat[j].Ts
|
|
})
|
|
require.Equal(t, tc.wantExemplars, flat, "exemplar mismatch")
|
|
require.Equal(t, tc.wantNextIndex, es.nextIndex, "next index mismatch")
|
|
})
|
|
}
|
|
|
|
resizeTwiceCases := []struct {
|
|
name string
|
|
addExemplars1 []exemplar.Exemplar
|
|
resize1 int64
|
|
wantExemplars1 []exemplar.Exemplar
|
|
resize2 int64
|
|
addExemplars2 []exemplar.Exemplar
|
|
wantExemplars2 []exemplar.Exemplar
|
|
}{
|
|
{
|
|
name: "shrink then grow ordered",
|
|
addExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
resize1: 2,
|
|
wantExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
resize2: 5,
|
|
addExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
{Labels: series1, Value: 0.7, Ts: 7},
|
|
},
|
|
wantExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
{Labels: series1, Value: 0.7, Ts: 7},
|
|
},
|
|
},
|
|
{
|
|
name: "shrink then grow out-of-order",
|
|
addExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
resize1: 2,
|
|
wantExemplars1: []exemplar.Exemplar{
|
|
// We delete in the order of ingestion, not temporally.
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
resize2: 5,
|
|
addExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.7, Ts: 7},
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
},
|
|
wantExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
{Labels: series1, Value: 0.7, Ts: 7},
|
|
},
|
|
},
|
|
{
|
|
name: "grow then shrink ordered",
|
|
addExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
resize1: 5,
|
|
wantExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
resize2: 2,
|
|
addExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
{Labels: series1, Value: 0.7, Ts: 7},
|
|
},
|
|
wantExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
{Labels: series1, Value: 0.7, Ts: 7},
|
|
},
|
|
},
|
|
{
|
|
name: "grow then shrink out-of-order",
|
|
addExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
},
|
|
resize1: 5,
|
|
wantExemplars1: []exemplar.Exemplar{
|
|
// We delete in the order of ingestion, not temporally.
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
resize2: 2,
|
|
addExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.7, Ts: 7},
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
},
|
|
wantExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.5, Ts: 5},
|
|
{Labels: series1, Value: 0.6, Ts: 6},
|
|
},
|
|
},
|
|
{
|
|
name: "grow non-full buffer then add entries",
|
|
addExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
resize1: 10,
|
|
wantExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
resize2: 10,
|
|
addExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
wantExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
{Labels: series1, Value: 0.3, Ts: 3},
|
|
{Labels: series1, Value: 0.4, Ts: 4},
|
|
},
|
|
},
|
|
{
|
|
name: "shrink non-full buffer then add entries",
|
|
addExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
},
|
|
resize1: 2,
|
|
wantExemplars1: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
},
|
|
resize2: 2,
|
|
addExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
wantExemplars2: []exemplar.Exemplar{
|
|
{Labels: series1, Value: 0.1, Ts: 1},
|
|
{Labels: series1, Value: 0.2, Ts: 2},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tc := range resizeTwiceCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(3, eMetrics, 100)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
for _, ex := range tc.addExemplars1 {
|
|
require.NoError(t, es.AddExemplar(ex.Labels, ex))
|
|
}
|
|
es.Resize(tc.resize1)
|
|
gotExemplars, err := es.Select(0, 1000, matcher1)
|
|
require.NoError(t, err)
|
|
require.Len(t, gotExemplars, 1)
|
|
require.Equal(t, tc.wantExemplars1, gotExemplars[0].Exemplars)
|
|
es.Resize(tc.resize2)
|
|
for _, ex := range tc.addExemplars2 {
|
|
require.NoError(t, es.AddExemplar(ex.Labels, ex))
|
|
}
|
|
if testing.Verbose() {
|
|
t.Logf("Buffer[after-resize2]:\n%s", debugCircularBuffer(es))
|
|
}
|
|
gotExemplars, err = es.Select(0, 1000, matcher1)
|
|
require.NoError(t, err)
|
|
require.Len(t, gotExemplars, 1)
|
|
require.Equal(t, tc.wantExemplars2, gotExemplars[0].Exemplars)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestStorageOverflow(t *testing.T) {
|
|
// Test that circular buffer index and assignment
|
|
// works properly, adding more exemplars than can
|
|
// be stored and then querying for them.
|
|
exs, err := NewCircularExemplarStorage(5, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
lName, lValue := "service", "asdf"
|
|
l := labels.FromStrings(lName, lValue)
|
|
|
|
var eList []exemplar.Exemplar
|
|
for i := 0; i < len(es.exemplars)+1; i++ {
|
|
e := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "a"),
|
|
Value: float64(i+1) / 10,
|
|
Ts: int64(101 + i),
|
|
}
|
|
es.AddExemplar(l, e)
|
|
eList = append(eList, e)
|
|
}
|
|
require.True(t, (es.exemplars[0].exemplar.Ts == 106), "exemplar was not stored correctly")
|
|
|
|
m, err := labels.NewMatcher(labels.MatchEqual, lName, lValue)
|
|
require.NoError(t, err, "error creating label matcher for exemplar query")
|
|
ret, err := es.Select(100, 110, []*labels.Matcher{m})
|
|
require.NoError(t, err)
|
|
require.Len(t, ret, 1, "select should have returned samples for a single series only")
|
|
|
|
require.True(t, reflect.DeepEqual(eList[1:], ret[0].Exemplars), "select did not return expected exemplars\n\texpected: %+v\n\tactual: %+v\n", eList[1:], ret[0].Exemplars)
|
|
}
|
|
|
|
func TestSelectExemplar(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(5, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
lName, lValue := "service", "asdf"
|
|
l := labels.FromStrings(lName, lValue)
|
|
e := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "querty"),
|
|
Value: 0.1,
|
|
Ts: 12,
|
|
}
|
|
|
|
err = es.AddExemplar(l, e)
|
|
require.NoError(t, err, "adding exemplar failed")
|
|
require.True(t, reflect.DeepEqual(es.exemplars[0].exemplar, e), "exemplar was not stored correctly")
|
|
|
|
m, err := labels.NewMatcher(labels.MatchEqual, lName, lValue)
|
|
require.NoError(t, err, "error creating label matcher for exemplar query")
|
|
ret, err := es.Select(0, 100, []*labels.Matcher{m})
|
|
require.NoError(t, err)
|
|
require.Len(t, ret, 1, "select should have returned samples for a single series only")
|
|
|
|
expectedResult := []exemplar.Exemplar{e}
|
|
require.True(t, reflect.DeepEqual(expectedResult, ret[0].Exemplars), "select did not return expected exemplars\n\texpected: %+v\n\tactual: %+v\n", expectedResult, ret[0].Exemplars)
|
|
}
|
|
|
|
func TestSelectExemplar_MultiSeries(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(5, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
l1Name := "test_metric"
|
|
l1 := labels.FromStrings(labels.MetricName, l1Name, "service", "asdf")
|
|
l2Name := "test_metric2"
|
|
l2 := labels.FromStrings(labels.MetricName, l2Name, "service", "qwer")
|
|
|
|
for i := 0; i < len(es.exemplars); i++ {
|
|
e1 := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "a"),
|
|
Value: float64(i+1) / 10,
|
|
Ts: int64(101 + i),
|
|
}
|
|
err = es.AddExemplar(l1, e1)
|
|
require.NoError(t, err)
|
|
|
|
e2 := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "b"),
|
|
Value: float64(i+1) / 10,
|
|
Ts: int64(101 + i),
|
|
}
|
|
err = es.AddExemplar(l2, e2)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
m, err := labels.NewMatcher(labels.MatchEqual, labels.MetricName, l2Name)
|
|
require.NoError(t, err, "error creating label matcher for exemplar query")
|
|
ret, err := es.Select(100, 200, []*labels.Matcher{m})
|
|
require.NoError(t, err)
|
|
require.Len(t, ret, 1, "select should have returned samples for a single series only")
|
|
require.Len(t, ret[0].Exemplars, 3, "didn't get expected 8 exemplars, got %d", len(ret[0].Exemplars))
|
|
|
|
m, err = labels.NewMatcher(labels.MatchEqual, labels.MetricName, l1Name)
|
|
require.NoError(t, err, "error creating label matcher for exemplar query")
|
|
ret, err = es.Select(100, 200, []*labels.Matcher{m})
|
|
require.NoError(t, err)
|
|
require.Len(t, ret, 1, "select should have returned samples for a single series only")
|
|
require.Len(t, ret[0].Exemplars, 2, "didn't get expected 8 exemplars, got %d", len(ret[0].Exemplars))
|
|
}
|
|
|
|
func TestSelectExemplar_TimeRange(t *testing.T) {
|
|
var lenEs int64 = 5
|
|
exs, err := NewCircularExemplarStorage(lenEs, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
lName, lValue := "service", "asdf"
|
|
l := labels.FromStrings(lName, lValue)
|
|
|
|
for i := 0; int64(i) < lenEs; i++ {
|
|
err := es.AddExemplar(l, exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", strconv.Itoa(i)),
|
|
Value: 0.1,
|
|
Ts: int64(101 + i),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Equal(t, es.index[string(l.Bytes(nil))].newest, i, "exemplar was not stored correctly")
|
|
}
|
|
|
|
m, err := labels.NewMatcher(labels.MatchEqual, lName, lValue)
|
|
require.NoError(t, err, "error creating label matcher for exemplar query")
|
|
ret, err := es.Select(102, 104, []*labels.Matcher{m})
|
|
require.NoError(t, err)
|
|
require.Len(t, ret, 1, "select should have returned samples for a single series only")
|
|
require.Len(t, ret[0].Exemplars, 3, "didn't get expected two exemplars %d, %+v", len(ret[0].Exemplars), ret)
|
|
}
|
|
|
|
// Test to ensure that even though a series matches more than one matcher from the
|
|
// query that it's exemplars are only included in the result a single time.
|
|
func TestSelectExemplar_DuplicateSeries(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(4, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
e := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "qwerty"),
|
|
Value: 0.1,
|
|
Ts: 12,
|
|
}
|
|
|
|
lName0, lValue0 := "service", "asdf"
|
|
lName1, lValue1 := "cluster", "us-central1"
|
|
l := labels.FromStrings(lName0, lValue0, lName1, lValue1)
|
|
|
|
// Lets just assume somehow the PromQL expression generated two separate lists of matchers,
|
|
// both of which can select this particular series.
|
|
m := [][]*labels.Matcher{
|
|
{
|
|
labels.MustNewMatcher(labels.MatchEqual, lName0, lValue0),
|
|
},
|
|
{
|
|
labels.MustNewMatcher(labels.MatchEqual, lName1, lValue1),
|
|
},
|
|
}
|
|
|
|
err = es.AddExemplar(l, e)
|
|
require.NoError(t, err, "adding exemplar failed")
|
|
require.True(t, reflect.DeepEqual(es.exemplars[0].exemplar, e), "exemplar was not stored correctly")
|
|
|
|
ret, err := es.Select(0, 100, m...)
|
|
require.NoError(t, err)
|
|
require.Len(t, ret, 1, "select should have returned samples for a single series only")
|
|
}
|
|
|
|
func TestIndexOverwrite(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(2, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
l1 := labels.FromStrings("service", "asdf")
|
|
l2 := labels.FromStrings("service", "qwer")
|
|
|
|
err = es.AddExemplar(l1, exemplar.Exemplar{Value: 1, Ts: 1})
|
|
require.NoError(t, err)
|
|
err = es.AddExemplar(l2, exemplar.Exemplar{Value: 2, Ts: 2})
|
|
require.NoError(t, err)
|
|
err = es.AddExemplar(l2, exemplar.Exemplar{Value: 3, Ts: 3})
|
|
require.NoError(t, err)
|
|
|
|
// Ensure index GC'ing is taking place, there should no longer be any
|
|
// index entry for series l1 since we just wrote two exemplars for series l2.
|
|
_, ok := es.index[string(l1.Bytes(nil))]
|
|
require.False(t, ok)
|
|
require.Equal(t, &indexEntry{1, 0, l2}, es.index[string(l2.Bytes(nil))])
|
|
|
|
err = es.AddExemplar(l1, exemplar.Exemplar{Value: 4, Ts: 4})
|
|
require.NoError(t, err)
|
|
|
|
i := es.index[string(l2.Bytes(nil))]
|
|
require.Equal(t, &indexEntry{0, 0, l2}, i)
|
|
}
|
|
|
|
func TestResize(t *testing.T) {
|
|
testCases := []struct {
|
|
name string
|
|
startSize int64
|
|
newCount int64
|
|
expectedSeries []int
|
|
notExpectedSeries []int
|
|
expectedMigrated int
|
|
}{
|
|
{
|
|
name: "Grow",
|
|
startSize: 100,
|
|
newCount: 200,
|
|
expectedSeries: []int{99, 98, 1, 0},
|
|
notExpectedSeries: []int{100},
|
|
expectedMigrated: 100,
|
|
},
|
|
{
|
|
name: "Shrink",
|
|
startSize: 100,
|
|
newCount: 50,
|
|
expectedSeries: []int{99, 98, 50},
|
|
notExpectedSeries: []int{49, 1, 0},
|
|
expectedMigrated: 50,
|
|
},
|
|
{
|
|
name: "ShrinkToZero",
|
|
startSize: 100,
|
|
newCount: 0,
|
|
expectedSeries: []int{},
|
|
notExpectedSeries: []int{},
|
|
expectedMigrated: 0,
|
|
},
|
|
{
|
|
name: "Negative",
|
|
startSize: 100,
|
|
newCount: -1,
|
|
expectedSeries: []int{},
|
|
notExpectedSeries: []int{},
|
|
expectedMigrated: 0,
|
|
},
|
|
{
|
|
name: "NegativeToNegative",
|
|
startSize: -1,
|
|
newCount: -2,
|
|
expectedSeries: []int{},
|
|
notExpectedSeries: []int{},
|
|
expectedMigrated: 0,
|
|
},
|
|
{
|
|
name: "GrowFromZero",
|
|
startSize: 0,
|
|
newCount: 10,
|
|
expectedSeries: []int{},
|
|
notExpectedSeries: []int{},
|
|
expectedMigrated: 0,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(tc.startSize, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
for i := 0; int64(i) < tc.startSize; i++ {
|
|
err = es.AddExemplar(labels.FromStrings("service", strconv.Itoa(i)), exemplar.Exemplar{
|
|
Value: float64(i),
|
|
Ts: int64(i),
|
|
})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
if testing.Verbose() {
|
|
t.Logf("Buffer[before-resize]:\n%s", debugCircularBuffer(es))
|
|
}
|
|
resized := es.Resize(tc.newCount)
|
|
if testing.Verbose() {
|
|
t.Logf("Buffer[after-resize]:\n%s", debugCircularBuffer(es))
|
|
}
|
|
|
|
require.Equal(t, tc.expectedMigrated, resized)
|
|
|
|
q, err := es.Querier(context.TODO())
|
|
require.NoError(t, err)
|
|
|
|
matchers := []*labels.Matcher{labels.MustNewMatcher(labels.MatchEqual, "service", "")}
|
|
|
|
for _, expected := range tc.expectedSeries {
|
|
matchers[0].Value = strconv.Itoa(expected)
|
|
ex, err := q.Select(0, math.MaxInt64, matchers)
|
|
require.NoError(t, err)
|
|
require.NotEmpty(t, ex)
|
|
}
|
|
|
|
for _, notExpected := range tc.notExpectedSeries {
|
|
matchers[0].Value = strconv.Itoa(notExpected)
|
|
ex, err := q.Select(0, math.MaxInt64, matchers)
|
|
require.NoError(t, err)
|
|
require.Empty(t, ex)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func BenchmarkAddExemplar(b *testing.B) {
|
|
// We need to include these labels since we do length calculation
|
|
// before adding.
|
|
exLabels := labels.FromStrings("trace_id", "89620921")
|
|
|
|
for _, capacity := range []int{1000, 10000, 100000} {
|
|
for _, n := range []int{10000, 100000, 1000000} {
|
|
b.Run(fmt.Sprintf("%d/%d", n, capacity), func(b *testing.B) {
|
|
for b.Loop() {
|
|
b.StopTimer()
|
|
exs, err := NewCircularExemplarStorage(int64(capacity), eMetrics, 0)
|
|
require.NoError(b, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
var l labels.Labels
|
|
b.StartTimer()
|
|
|
|
for i := range n {
|
|
if i%100 == 0 {
|
|
l = labels.FromStrings("service", strconv.Itoa(i))
|
|
}
|
|
err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: exLabels})
|
|
if err != nil {
|
|
require.NoError(b, err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkAddExemplar_OutOfOrder(b *testing.B) {
|
|
// We need to include these labels since we do length calculation
|
|
// before adding.
|
|
exLabels := labels.FromStrings("trace_id", "89620921")
|
|
|
|
const (
|
|
capacity = 5000
|
|
)
|
|
|
|
fillOneSeries := func(es *CircularExemplarStorage) {
|
|
for i := range capacity {
|
|
e := exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: exLabels}
|
|
if err := es.AddExemplar(exLabels, e); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
fillMultipleSeries := func(es *CircularExemplarStorage) {
|
|
for i := range capacity {
|
|
l := labels.FromStrings("service", strconv.Itoa(i))
|
|
e := exemplar.Exemplar{Value: float64(i), Ts: int64(i), Labels: l}
|
|
if err := es.AddExemplar(l, e); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
|
|
outOfOrder := func(ts *int64, _ *labels.Labels) {
|
|
switch *ts % 3 {
|
|
case 0:
|
|
return
|
|
case 1:
|
|
*ts = capacity - *ts
|
|
case 2:
|
|
*ts = (capacity - *ts) + 100
|
|
}
|
|
}
|
|
|
|
reverseOrder := func(ts *int64, _ *labels.Labels) {
|
|
*ts = capacity - *ts
|
|
}
|
|
|
|
multipleSeries := func(f func(*int64, *labels.Labels)) func(*int64, *labels.Labels) {
|
|
return func(ts *int64, l *labels.Labels) {
|
|
f(ts, l)
|
|
*l = labels.FromStrings("service", strconv.Itoa(int(*ts)))
|
|
}
|
|
}
|
|
|
|
for fillName, setup := range map[string]func(es *CircularExemplarStorage){
|
|
"empty": func(*CircularExemplarStorage) {},
|
|
"full-one": fillOneSeries,
|
|
"full-multiple": fillMultipleSeries,
|
|
} {
|
|
for orderName, forEach := range map[string]func(ts *int64, l *labels.Labels){
|
|
"in-order": func(*int64, *labels.Labels) {},
|
|
"reverse": reverseOrder,
|
|
"out-of-order": outOfOrder,
|
|
"multi-in-order": multipleSeries(func(*int64, *labels.Labels) {}),
|
|
"multi-reverse": multipleSeries(reverseOrder),
|
|
"multi-out-of-order": multipleSeries(outOfOrder),
|
|
} {
|
|
b.Run(fmt.Sprintf("%s/%s", fillName, orderName), func(b *testing.B) {
|
|
exs, err := NewCircularExemplarStorage(int64(capacity), eMetrics, 100000)
|
|
require.NoError(b, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
l := labels.FromStrings("service", "0")
|
|
setup(es)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
for i := range capacity {
|
|
ts := int64(i)
|
|
forEach(&ts, &l)
|
|
err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: ts, Labels: l})
|
|
if err != nil {
|
|
b.Fatalf("Failed to insert item %d %s: %v", i, l, err)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
func BenchmarkResizeExemplars(b *testing.B) {
|
|
testCases := []struct {
|
|
name string
|
|
startSize int64
|
|
endSize int64
|
|
numExemplars int
|
|
}{
|
|
{
|
|
name: "grow",
|
|
startSize: 100000,
|
|
endSize: 200000,
|
|
numExemplars: 150000,
|
|
},
|
|
{
|
|
name: "shrink",
|
|
startSize: 100000,
|
|
endSize: 50000,
|
|
numExemplars: 100000,
|
|
},
|
|
{
|
|
name: "grow",
|
|
startSize: 1000000,
|
|
endSize: 2000000,
|
|
numExemplars: 1500000,
|
|
},
|
|
{
|
|
name: "shrink",
|
|
startSize: 1000000,
|
|
endSize: 500000,
|
|
numExemplars: 1000000,
|
|
},
|
|
}
|
|
|
|
for _, tc := range testCases {
|
|
b.Run(fmt.Sprintf("%s-%d-to-%d", tc.name, tc.startSize, tc.endSize), func(b *testing.B) {
|
|
for b.Loop() {
|
|
b.StopTimer()
|
|
exs, err := NewCircularExemplarStorage(tc.startSize, eMetrics, 0)
|
|
require.NoError(b, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
var l labels.Labels
|
|
for i := 0; i < int(float64(tc.startSize)*float64(1.5)); i++ {
|
|
if i%100 == 0 {
|
|
l = labels.FromStrings("service", strconv.Itoa(i))
|
|
}
|
|
|
|
err = es.AddExemplar(l, exemplar.Exemplar{Value: float64(i), Ts: int64(i)})
|
|
if err != nil {
|
|
require.NoError(b, err)
|
|
}
|
|
}
|
|
b.StartTimer()
|
|
es.Resize(tc.endSize)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestCircularExemplarStorage_Concurrent_AddExemplar_Resize tries to provoke a data race between AddExemplar and Resize.
|
|
// Run with race detection enabled.
|
|
func TestCircularExemplarStorage_Concurrent_AddExemplar_Resize(t *testing.T) {
|
|
exs, err := NewCircularExemplarStorage(0, eMetrics, 0)
|
|
require.NoError(t, err)
|
|
es := exs.(*CircularExemplarStorage)
|
|
|
|
l := labels.FromStrings("service", "asdf")
|
|
e := exemplar.Exemplar{
|
|
Labels: labels.FromStrings("trace_id", "qwerty"),
|
|
Value: 0.1,
|
|
Ts: 1,
|
|
}
|
|
|
|
var wg sync.WaitGroup
|
|
wg.Add(1)
|
|
t.Cleanup(wg.Wait)
|
|
|
|
started := make(chan struct{})
|
|
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
<-started
|
|
for range 100 {
|
|
require.NoError(t, es.AddExemplar(l, e))
|
|
}
|
|
}()
|
|
|
|
for i := range 100 {
|
|
es.Resize(int64(i + 1))
|
|
if i == 0 {
|
|
close(started)
|
|
}
|
|
}
|
|
}
|
|
|
|
// debugCircularBuffer iterates all exemplars in the circular exemplar storage
|
|
// and returns them as a string. The textual representation contains index
|
|
// pointers and helps debugging exemplar storage.
|
|
func debugCircularBuffer(ce *CircularExemplarStorage) string {
|
|
var sb strings.Builder
|
|
for i, e := range ce.exemplars {
|
|
if e.ref == nil {
|
|
continue
|
|
}
|
|
sb.WriteString(fmt.Sprintf(
|
|
"i: %d, ts: %d, next: %d, prev: %d",
|
|
i, e.exemplar.Ts, e.next, e.prev,
|
|
))
|
|
for _, idx := range ce.index {
|
|
if i == idx.newest {
|
|
sb.WriteString(" <- newest " + idx.seriesLabels.String())
|
|
}
|
|
if i == idx.oldest {
|
|
sb.WriteString(" <- oldest " + idx.seriesLabels.String())
|
|
}
|
|
}
|
|
sb.WriteString("\n")
|
|
}
|
|
sb.WriteString(fmt.Sprintf("Next index: %d\n", ce.nextIndex))
|
|
return sb.String()
|
|
}
|