blob: db48faad7b374c944281b55c092296d727e3f199 [file] [log] [blame]
package sqltracestore
import (
"context"
"fmt"
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"go.skia.org/infra/go/paramtools"
"go.skia.org/infra/go/query"
"go.skia.org/infra/go/testutils/unittest"
"go.skia.org/infra/go/vec32"
"go.skia.org/infra/perf/go/config"
"go.skia.org/infra/perf/go/sql/sqltest"
"go.skia.org/infra/perf/go/types"
)
const (
// e is a shorter more readable stand-in for the wordy vec32.MISSING_DATA_SENTINEL.
e = vec32.MissingDataSentinel
// testTileSize is the size of tiles we use for tests.
testTileSize = int32(8)
)
var cfg = config.DataStoreConfig{
TileSize: testTileSize,
Project: "test",
Instance: "test",
Table: "test",
Shards: 8,
}
func commonTestSetup(t *testing.T, populateTraces bool) (context.Context, *SQLTraceStore, sqltest.Cleanup) {
unittest.LargeTest(t)
ctx := context.Background()
db, cleanup := sqltest.NewCockroachDBForTests(t, fmt.Sprintf("tracestore%d", rand.Int63()))
store, err := New(db, cfg)
require.NoError(t, err)
if populateTraces {
populatedTestDB(t, store)
}
return ctx, store, cleanup
}
func TestUpdateSourceFile(t *testing.T) {
_, s, cleanup := commonTestSetup(t, false)
defer cleanup()
// Do each update twice to ensure the IDs don't change.
id, err := s.updateSourceFile("foo.txt")
assert.NoError(t, err)
id2, err := s.updateSourceFile("foo.txt")
assert.NoError(t, err)
assert.Equal(t, id, id2)
id, err = s.updateSourceFile("bar.txt")
assert.NoError(t, err)
id2, err = s.updateSourceFile("bar.txt")
assert.NoError(t, err)
assert.Equal(t, id, id2)
}
func TestReadTraces(t *testing.T) {
_, s, cleanup := commonTestSetup(t, true)
defer cleanup()
keys := []string{
",arch=x86,config=8888,",
",arch=x86,config=565,",
}
ts, err := s.ReadTraces(0, keys)
require.NoError(t, err)
assert.Equal(t, types.TraceSet{
",arch=x86,config=565,": {e, 2.3, 3.3, e, e, e, e, e},
",arch=x86,config=8888,": {e, 1.5, 2.5, e, e, e, e, e},
}, ts)
ts, err = s.ReadTraces(1, keys)
require.NoError(t, err)
assert.Equal(t, types.TraceSet{
",arch=x86,config=565,": {4.3, e, e, e, e, e, e, e},
",arch=x86,config=8888,": {3.5, e, e, e, e, e, e, e},
}, ts)
}
func TestReadTraces_InvalidKey(t *testing.T) {
_, s, cleanup := commonTestSetup(t, true)
defer cleanup()
keys := []string{
",arch=x86,config='); DROP TABLE TraceValues,",
",arch=x86,config=565,",
}
_, err := s.ReadTraces(0, keys)
require.Error(t, err)
}
func TestReadTraces_NoResults(t *testing.T) {
_, s, cleanup := commonTestSetup(t, true)
defer cleanup()
keys := []string{
",arch=unknown,",
}
ts, err := s.ReadTraces(0, keys)
require.NoError(t, err)
assert.Equal(t, ts, types.TraceSet{
",arch=unknown,": {e, e, e, e, e, e, e, e},
})
}
func TestReadTraces_EmptyTileReturnsNoData(t *testing.T) {
_, s, cleanup := commonTestSetup(t, true)
defer cleanup()
keys := []string{
",arch=x86,config=8888,",
",arch=x86,config=565,",
}
// Reading from a tile we haven't written to should succeed and return no data.
ts, err := s.ReadTraces(2, keys)
assert.NoError(t, err)
assert.Equal(t, ts, types.TraceSet{
",arch=x86,config=565,": {e, e, e, e, e, e, e, e},
",arch=x86,config=8888,": {e, e, e, e, e, e, e, e},
})
}
func TestQueryTracesIDOnlyByIndex_EmptyQueryReturnsError(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that matches one trace.
q, err := query.NewFromString("")
assert.NoError(t, err)
const emptyTileNumber = types.TileNumber(5)
_, err = s.QueryTracesIDOnlyByIndex(ctx, emptyTileNumber, q)
assert.Error(t, err)
}
// paramSetFromParamsChan is a utility func that reads all the Params from the
// channel and returns them in a ParamSet.
func paramSetFromParamsChan(ch <-chan paramtools.Params) paramtools.ParamSet {
ret := paramtools.NewParamSet()
for p := range ch {
ret.AddParams(p)
}
ret.Normalize()
return ret
}
func TestQueryTracesIDOnlyByIndex_EmptyTileReturnsEmptyParamset(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that matches one trace.
q, err := query.NewFromString("config=565")
assert.NoError(t, err)
ch, err := s.QueryTracesIDOnlyByIndex(ctx, 5, q)
require.NoError(t, err)
assert.Empty(t, paramSetFromParamsChan(ch))
}
func TestQueryTracesIDOnlyByIndex_MatchesOneTrace(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that matches one trace.
q, err := query.NewFromString("config=565")
assert.NoError(t, err)
ch, err := s.QueryTracesIDOnlyByIndex(ctx, 0, q)
require.NoError(t, err)
expected := paramtools.ParamSet{
"arch": []string{"x86"},
"config": []string{"565"},
}
assert.Equal(t, expected, paramSetFromParamsChan(ch))
}
func TestQueryTracesIDOnlyByIndex_MatchesTwoTraces(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that matches two traces.
q, err := query.NewFromString("arch=x86")
assert.NoError(t, err)
ch, err := s.QueryTracesIDOnlyByIndex(ctx, 0, q)
require.NoError(t, err)
expected := paramtools.ParamSet{
"arch": []string{"x86"},
"config": []string{"565", "8888"},
}
assert.Equal(t, expected, paramSetFromParamsChan(ch))
}
func TestQueryTracesByIndex_MatchesOneTrace(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that matches one trace.
q, err := query.NewFromString("config=565")
assert.NoError(t, err)
ts, err := s.QueryTracesByIndex(ctx, 0, q)
assert.NoError(t, err)
assert.Equal(t, ts, types.TraceSet{
",arch=x86,config=565,": {e, 2.3, 3.3, e, e, e, e, e},
})
}
func TestQueryTracesByIndex_MatchesOneTraceInTheSecondTile(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that matches one trace second tile.
q, err := query.NewFromString("config=565")
assert.NoError(t, err)
ts, err := s.QueryTracesByIndex(ctx, 1, q)
assert.NoError(t, err)
assert.Equal(t, ts, types.TraceSet{
",arch=x86,config=565,": {4.3, e, e, e, e, e, e, e},
})
}
func TestQueryTracesByIndex_MatchesTwoTraces(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that matches two traces.
q, err := query.NewFromString("arch=x86")
assert.NoError(t, err)
ts, err := s.QueryTracesByIndex(ctx, 0, q)
assert.NoError(t, err)
assert.Equal(t, ts, types.TraceSet{
",arch=x86,config=565,": {e, 2.3, 3.3, e, e, e, e, e},
",arch=x86,config=8888,": {e, 1.5, 2.5, e, e, e, e, e},
})
}
func TestQueryTracesByIndex_QueryHasUnknownParamReturnsNoError(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Query that has no matching params in the given tile.
q, err := query.NewFromString("arch=unknown")
assert.NoError(t, err)
ts, err := s.QueryTracesByIndex(ctx, 0, q)
assert.NoError(t, err)
assert.Nil(t, ts)
}
func TestQueryTracesByIndex_QueryAgainstTileWithNoDataReturnsNoError(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, false)
defer cleanup()
// Query that has no Postings for the given tile.
q, err := query.NewFromString("arch=unknown")
assert.NoError(t, err)
ts, err := s.QueryTracesByIndex(ctx, 2, q)
assert.NoError(t, err)
assert.Nil(t, ts)
}
func TestTraceCount(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
count, err := s.TraceCount(ctx, 0)
assert.NoError(t, err)
assert.Equal(t, int64(2), count)
count, err = s.TraceCount(ctx, 1)
assert.NoError(t, err)
assert.Equal(t, int64(2), count)
count, err = s.TraceCount(ctx, 2)
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
}
func TestParamSetForTile(t *testing.T) {
_, s, cleanup := commonTestSetup(t, true)
defer cleanup()
ps, err := s.paramSetForTile(1)
assert.NoError(t, err)
expected := paramtools.ParamSet{
"arch": []string{"x86"},
"config": []string{"565", "8888"},
}
assert.Equal(t, expected, ps)
}
func TestParamSetForTile_Empty(t *testing.T) {
_, s, cleanup := commonTestSetup(t, false)
defer cleanup()
// Test the empty case where there is no data in the store.
ps, err := s.paramSetForTile(1)
assert.NoError(t, err)
assert.Equal(t, paramtools.ParamSet{}, ps)
}
func TestGetLatestTile(t *testing.T) {
_, s, cleanup := commonTestSetup(t, true)
defer cleanup()
tileNumber, err := s.GetLatestTile()
assert.NoError(t, err)
assert.Equal(t, types.TileNumber(1), tileNumber)
}
func TestGetLatestTile_Empty(t *testing.T) {
_, s, cleanup := commonTestSetup(t, false)
defer cleanup()
// Test the empty case where there is no data in datastore.
tileNumber, err := s.GetLatestTile()
assert.Error(t, err)
assert.Equal(t, types.BadTileNumber, tileNumber)
}
func TestGetOrderedParamSet(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
tileNumber := types.TileNumber(1)
assert.False(t, s.orderedParamSetCache.Contains(tileNumber))
ops, err := s.GetOrderedParamSet(ctx, tileNumber, time.Now())
assert.NoError(t, err)
expected := paramtools.ParamSet{
"arch": []string{"x86"},
"config": []string{"565", "8888"},
}
assert.Equal(t, expected, ops.ParamSet)
assert.Equal(t, []string{"arch", "config"}, ops.KeyOrder)
assert.True(t, s.orderedParamSetCache.Contains(tileNumber))
}
func TestGetOrderedParamSet_ParamSetCacheIsClearedAfterTTL(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
tileNumber := types.TileNumber(0)
assert.False(t, s.orderedParamSetCache.Contains(tileNumber))
ops, err := s.GetOrderedParamSet(ctx, tileNumber, time.Now())
assert.NoError(t, err)
expected := paramtools.ParamSet{
"arch": []string{"x86"},
"config": []string{"565", "8888"},
}
assert.Equal(t, expected, ops.ParamSet)
assert.Equal(t, []string{"arch", "config"}, ops.KeyOrder)
assert.True(t, s.orderedParamSetCache.Contains(tileNumber))
// Add new points that will expand the ParamSet.
traceNames := []paramtools.Params{
{"config": "8888", "arch": "risc-v"},
{"config": "565", "arch": "risc-v"},
}
err = s.WriteTraces(types.CommitNumber(1), traceNames,
[]float32{1.5, 2.3},
paramtools.ParamSet{}, // ParamSet is empty because WriteTraces doesn't use it in this impl.
"gs://perf-bucket/2020/02/08/11/testdata.json",
time.Time{}) // time is unused in this impl of TraceStore.
// The cached version should be returned.
ops, err = s.GetOrderedParamSet(ctx, tileNumber, time.Now())
assert.NoError(t, err)
assert.Equal(t, expected, ops.ParamSet)
// But if we query past the TTL we should get an updated OPS.
updatedExpected := paramtools.ParamSet{
"arch": []string{"risc-v", "x86"},
"config": []string{"565", "8888"},
}
ops, err = s.GetOrderedParamSet(ctx, tileNumber, time.Now().Add(orderedParamSetCacheTTL*2))
assert.NoError(t, err)
assert.Equal(t, updatedExpected, ops.ParamSet)
}
func TestGetOrderedParamSet_Empty(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, false)
defer cleanup()
// Test the empty case where there is no data in datastore.
ops, err := s.GetOrderedParamSet(ctx, 1, time.Now())
assert.NoError(t, err)
assert.Equal(t, paramtools.ParamSet{}, ops.ParamSet)
}
func TestCountIndices(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, false)
defer cleanup()
count, err := s.CountIndices(ctx, 1)
assert.NoError(t, err)
// The always returns 0.
assert.Equal(t, int64(0), count)
}
func TestCountIndices_Empty(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, false)
defer cleanup()
// Test the empty case where there is no data in datastore.
count, err := s.CountIndices(ctx, 1)
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
}
func TestGetSource(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
filename, err := s.GetSource(ctx, types.CommitNumber(2), ",arch=x86,config=8888,")
require.NoError(t, err)
assert.Equal(t, "gs://perf-bucket/2020/02/08/12/testdata.json", filename)
}
func TestGetSource_Empty(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, true)
defer cleanup()
// Confirm the call works with an empty tracestore.
filename, err := s.GetSource(ctx, types.CommitNumber(5), ",arch=x86,config=8888,")
assert.Error(t, err)
assert.Equal(t, "", filename)
}
func TestSQLTraceStore_TileNumber(t *testing.T) {
_, s, cleanup := commonTestSetup(t, false)
defer cleanup()
assert.Equal(t, types.TileNumber(0), s.TileNumber(types.CommitNumber(1)))
assert.Equal(t, types.TileNumber(1), s.TileNumber(types.CommitNumber(9)))
}
func TestSQLTraceStore_TileSize(t *testing.T) {
_, s, cleanup := commonTestSetup(t, false)
defer cleanup()
assert.Equal(t, testTileSize, s.TileSize())
}
func TestSQLTraceStore_WriteIndices_Success(t *testing.T) {
ctx, s, cleanup := commonTestSetup(t, false)
defer cleanup()
// WriteIndices is a no-op in the SQL backed trace store.
assert.NoError(t, s.WriteIndices(ctx, types.TileNumber(1)))
}
func TestCommitNumberOfTileStart(t *testing.T) {
unittest.SmallTest(t)
s := &SQLTraceStore{
tileSize: 8,
}
assert.Equal(t, types.CommitNumber(0), s.CommitNumberOfTileStart(0))
assert.Equal(t, types.CommitNumber(0), s.CommitNumberOfTileStart(1))
assert.Equal(t, types.CommitNumber(0), s.CommitNumberOfTileStart(7))
assert.Equal(t, types.CommitNumber(8), s.CommitNumberOfTileStart(8))
assert.Equal(t, types.CommitNumber(8), s.CommitNumberOfTileStart(9))
}
func populatedTestDB(t *testing.T, store *SQLTraceStore) {
traceNames := []paramtools.Params{
{"config": "8888", "arch": "x86"},
{"config": "565", "arch": "x86"},
}
err := store.WriteTraces(types.CommitNumber(1), traceNames,
[]float32{1.5, 2.3},
paramtools.ParamSet{}, // ParamSet is empty because WriteTraces doesn't use it in this impl.
"gs://perf-bucket/2020/02/08/11/testdata.json",
time.Time{}) // time is unused in this impl of TraceStore.
require.NoError(t, err)
err = store.WriteTraces(types.CommitNumber(2), traceNames,
[]float32{2.5, 3.3},
paramtools.ParamSet{}, // ParamSet is empty because WriteTraces doesn't use it in this impl.
"gs://perf-bucket/2020/02/08/12/testdata.json",
time.Time{}) // time is unused in this impl of TraceStore.
require.NoError(t, err)
err = store.WriteTraces(types.CommitNumber(8), traceNames,
[]float32{3.5, 4.3},
paramtools.ParamSet{}, // ParamSet is empty because WriteTraces doesn't use it in this impl.
"gs://perf-bucket/2020/02/08/13/testdata.json",
time.Time{}) // time is unused in this impl of TraceStore.
require.NoError(t, err)
}
func Test_traceIDForSQLFromTraceName_Success(t *testing.T) {
unittest.SmallTest(t)
/*
$ python3
Python 3.7.3 (default, Dec 20 2019, 18:57:59)
[GCC 8.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import hashlib
>>> hashlib.md5(b",arch=x86,config=8888,").hexdigest()
'fe385b159ff55dca481069805e5ff050'
*/
assert.Equal(t, traceIDForSQL(`\xfe385b159ff55dca481069805e5ff050`), traceIDForSQLFromTraceName(",arch=x86,config=8888,"))
}