Like Prometheus, but for logs.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
loki/pkg/util/time_test.go

286 lines
6.1 KiB

package util
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestTimeFromMillis(t *testing.T) {
var testExpr = []struct {
input int64
expected time.Time
}{
{input: 1000, expected: time.Unix(1, 0)},
{input: 1500, expected: time.Unix(1, 500*nanosecondsInMillisecond)},
}
for i, c := range testExpr {
t.Run(fmt.Sprint(i), func(t *testing.T) {
res := TimeFromMillis(c.input)
require.Equal(t, c.expected, res)
})
}
}
func TestDurationWithJitter(t *testing.T) {
const numRuns = 1000
for i := 0; i < numRuns; i++ {
actual := DurationWithJitter(time.Minute, 0.5)
assert.GreaterOrEqual(t, int64(actual), int64(30*time.Second))
assert.LessOrEqual(t, int64(actual), int64(90*time.Second))
}
}
func TestDurationWithJitter_ZeroInputDuration(t *testing.T) {
assert.Equal(t, time.Duration(0), DurationWithJitter(time.Duration(0), 0.5))
}
func TestDurationWithPositiveJitter(t *testing.T) {
const numRuns = 1000
for i := 0; i < numRuns; i++ {
actual := DurationWithPositiveJitter(time.Minute, 0.5)
assert.GreaterOrEqual(t, int64(actual), int64(60*time.Second))
assert.LessOrEqual(t, int64(actual), int64(90*time.Second))
}
}
func TestDurationWithPositiveJitter_ZeroInputDuration(t *testing.T) {
assert.Equal(t, time.Duration(0), DurationWithPositiveJitter(time.Duration(0), 0.5))
}
func TestParseTime(t *testing.T) {
var tests = []struct {
input string
fail bool
result time.Time
}{
{
input: "",
fail: true,
}, {
input: "abc",
fail: true,
}, {
input: "30s",
fail: true,
}, {
input: "123",
result: time.Unix(123, 0),
}, {
input: "123.123",
result: time.Unix(123, 123000000),
}, {
input: "2015-06-03T13:21:58.555Z",
result: time.Unix(1433337718, 555*time.Millisecond.Nanoseconds()),
}, {
input: "2015-06-03T14:21:58.555+01:00",
result: time.Unix(1433337718, 555*time.Millisecond.Nanoseconds()),
}, {
// Test nanosecond rounding.
input: "2015-06-03T13:21:58.56789Z",
result: time.Unix(1433337718, 567*1e6),
}, {
// Test float rounding.
input: "1543578564.705",
result: time.Unix(1543578564, 705*1e6),
},
}
for _, test := range tests {
ts, err := ParseTime(test.input)
if test.fail {
require.Error(t, err)
continue
}
require.NoError(t, err)
assert.Equal(t, TimeToMillis(test.result), ts)
}
}
func TestNewDisableableTicker_Enabled(t *testing.T) {
stop, ch := NewDisableableTicker(10 * time.Millisecond)
defer stop()
time.Sleep(100 * time.Millisecond)
select {
case <-ch:
break
default:
t.Error("ticker should have ticked when enabled")
}
}
func TestNewDisableableTicker_Disabled(t *testing.T) {
stop, ch := NewDisableableTicker(0)
defer stop()
time.Sleep(100 * time.Millisecond)
select {
case <-ch:
t.Error("ticker should not have ticked when disabled")
default:
break
}
}
avoid using bloomfilters for chunks in stats calls by avoiding duplicates (#7209) **What this PR does / why we need it**: Avoid using bloomfilters for chunks deduplication in tsdb `Stats` calls by avoiding fetching duplicate entries. The idea is to split and align queries by [ObjectStorageIndexRequiredPeriod](https://github.com/grafana/loki/blob/61794710a720da135d8479b7c2f723c740e86404/pkg/storage/config/schema_config.go#L47) and make each split process chunks with a start time >= start time of the table interval. In other terms, table interval that contains start time of the chunk, owns it. For e.g. if the table interval is 10s, and we have chunks 5-7, 8-12, 11-13. Query with range 6-15 would be split into 6-10, 10-15. query1 would process chunks 5-7, 8-12 and query2 would process chunks 11-13. This check is not applied for the first split so that we do not eliminate any chunks that overlaps the original query intervals but starts at the previous table. For e.g. if the table interval is 10s, and we have chunks 5-7, 8-13, 14-13. Query with range 11-12 should process chunk 8-13 even though its start time <= start time of table we will query for index. The caveat here is that we will overestimate the data we will be processing if the index is not compacted yet since it could have duplicate chunks when RF > 1. I think it is okay since the Stats call is just an estimation and need not be accurate. Removing all the extra processing saves us quite a bit of CPU and memory, as seen from the benchmark comparison between the two implementations: ``` name old time/op new time/op delta IndexClient_Stats-10 187µs ± 0% 34µs ± 1% -82.00% (p=0.008 n=5+5) name old alloc/op new alloc/op delta IndexClient_Stats-10 61.5kB ± 4% 12.5kB ± 2% -79.69% (p=0.008 n=5+5) name old allocs/op new allocs/op delta IndexClient_Stats-10 1.46k ± 0% 0.48k ± 0% -67.28% (p=0.008 n=5+5) ``` **Checklist** - [x] Tests updated
3 years ago
type timeInterval struct {
from, through time.Time
}
func TestForInterval(t *testing.T) {
splitInterval := 10 * time.Second
for _, tc := range []struct {
name string
inp timeInterval
expectedIntervals []timeInterval
endTimeInclusive bool
}{
{
name: "range smaller than split interval",
inp: timeInterval{
from: time.Unix(5, 0),
through: time.Unix(8, 0),
},
expectedIntervals: []timeInterval{
{
from: time.Unix(5, 0),
through: time.Unix(8, 0),
},
},
},
{
name: "range exactly equal and aligned to split interval",
inp: timeInterval{
from: time.Unix(10, 0),
through: time.Unix(20, 0),
},
expectedIntervals: []timeInterval{
{
from: time.Unix(10, 0),
through: time.Unix(20, 0),
},
},
},
{
name: "multiple splits with end time not inclusive",
inp: timeInterval{
from: time.Unix(5, 0),
through: time.Unix(28, 0),
},
expectedIntervals: []timeInterval{
{
from: time.Unix(5, 0),
through: time.Unix(10, 0),
},
{
from: time.Unix(10, 0),
through: time.Unix(20, 0),
},
{
from: time.Unix(20, 0),
through: time.Unix(28, 0),
},
},
},
{
name: "multiple splits with end time inclusive",
inp: timeInterval{
from: time.Unix(5, 0),
through: time.Unix(28, 0),
},
endTimeInclusive: true,
expectedIntervals: []timeInterval{
{
from: time.Unix(5, 0),
through: time.Unix(10, 0).Add(-time.Millisecond),
},
{
from: time.Unix(10, 0),
through: time.Unix(20, 0).Add(-time.Millisecond),
},
{
from: time.Unix(20, 0),
through: time.Unix(28, 0),
},
},
},
} {
t.Run(tc.name, func(t *testing.T) {
var actualIntervals []timeInterval
ForInterval(splitInterval, tc.inp.from, tc.inp.through, tc.endTimeInclusive, func(start, end time.Time) {
actualIntervals = append(actualIntervals, timeInterval{
from: start,
through: end,
})
})
require.Equal(t, tc.expectedIntervals, actualIntervals)
})
}
}
func TestGetFactorOfTime(t *testing.T) {
for _, tc := range []struct {
desc string
from, through, extentMin, extentMax int64
exp float64
}{
{
desc: "equal",
from: 10, through: 20,
extentMin: 10, extentMax: 20,
exp: 1,
},
{
desc: "50% overlap on left",
from: 10, through: 20,
extentMin: 5, extentMax: 15,
exp: 0.5,
},
{
desc: "50% overlap on right",
from: 10, through: 20,
extentMin: 15, extentMax: 25,
exp: 0.5,
},
{
desc: "10% overlap on right",
from: 15, through: 16,
extentMin: 15, extentMax: 25,
exp: 0.1,
},
{
desc: "no overlap",
from: 10, through: 20,
extentMin: 25, extentMax: 35,
exp: 0,
},
{
desc: "no overlap, through=extentMin",
from: 10, through: 20,
extentMin: 20, extentMax: 35,
exp: 0,
},
{
desc: "factor would be NaN",
from: 1685655637000000000, through: 1685656237000000000,
extentMin: 1685656107442496000, extentMax: 1685656107442496000,
exp: 1,
},
} {
t.Run(tc.desc, func(t *testing.T) {
factor := GetFactorOfTime(tc.from, tc.through, tc.extentMin, tc.extentMax)
require.Equal(t, tc.exp, factor)
})
}
}