blob: d8d35f255521e11477b4eef028628ed6edfed97c [file] [log] [blame]
package main
import (
"bytes"
"context"
"flag"
"math/rand"
"net/url"
"os"
"reflect"
"time"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/common"
"go.skia.org/infra/go/ds"
"go.skia.org/infra/go/eventbus"
"go.skia.org/infra/go/firestore"
"go.skia.org/infra/go/git/gitinfo"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/tiling"
"go.skia.org/infra/go/timer"
tracedb "go.skia.org/infra/go/trace/db"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/expstorage/fs_expstore"
"go.skia.org/infra/golden/go/ignore"
"go.skia.org/infra/golden/go/serialize"
"go.skia.org/infra/golden/go/storage"
"go.skia.org/infra/golden/go/types"
)
var (
fsNamespace = flag.String("fs_namespace", "", "Typically the instance id. e.g. 'flutter', 'skia', etc")
fsProjectID = flag.String("fs_project_id", "skia-firestore", "The project with the firestore instance. Datastore and Firestore can't be in the same project.")
gitRepoDir = flag.String("git_repo_dir", "../../../skia", "Directory location for the Skia repo.")
gitRepoURL = flag.String("git_repo_url", "https://skia.googlesource.com/skia", "The URL to pass to git clone for the source repository.")
nCommits = flag.Int("n_commits", 50, "Number of recent commits to include in the analysis.")
nTests = flag.Int("n_tests", 0, "Set number of tests to pick randomly.")
outputFile = flag.String("output_file", "sample.tile", "Path to the output file for the sample.")
query = flag.String("query", "", "Query to filter which traces are considered.")
sampleSize = flag.Int("sample_size", 0, "Number of random traces to pick. 0 returns the entire tile.")
traceservice = flag.String("trace_service", "localhost:9001", "The address of the traceservice endpoint.")
)
func main() {
flag.Parse()
// Load the data that make up the state of the system.
tile, expectations, ignoreStore := load()
tile = sampleTile(tile, *sampleSize, *query, *nTests)
writeSample(*outputFile, tile, expectations, ignoreStore)
sklog.Infof("Finished.")
}
func sampleTile(tile *tiling.Tile, sampleSize int, queryStr string, nTests int) *tiling.Tile {
// Filter the traces if there was a query defined.
if queryStr != "" {
query, err := url.ParseQuery(queryStr)
if err != nil {
sklog.Fatalf("Unable to parse querye '%s'. Got error: %s", queryStr, err)
}
newTraces := map[tiling.TraceId]tiling.Trace{}
for traceID, trace := range tile.Traces {
if tiling.Matches(trace, query) {
newTraces[traceID] = trace
}
}
tile.Traces = newTraces
}
// Fixed number of tests selected.
if nTests > 0 {
byTest := map[types.TestName][]tiling.TraceId{}
for traceID, trace := range tile.Traces {
name := types.TestName(trace.Params()[types.PRIMARY_KEY_FIELD])
byTest[name] = append(byTest[name], traceID)
}
newTraces := map[tiling.TraceId]tiling.Trace{}
idx := 0
for testName, traceIDs := range byTest {
for _, traceID := range traceIDs {
newTraces[traceID] = tile.Traces[traceID]
}
sklog.Infof("Included test/traces: %s/%d", testName, len(traceIDs))
idx++
if idx >= nTests {
break
}
}
tile.Traces = newTraces
} else if sampleSize > 0 {
// Sample a given number of traces.
traceIDs := make([]tiling.TraceId, 0, len(tile.Traces))
for id := range tile.Traces {
traceIDs = append(traceIDs, id)
}
permutation := rand.Perm(len(traceIDs))[:util.MinInt(len(traceIDs), sampleSize)]
newTraces := make(map[tiling.TraceId]tiling.Trace, len(traceIDs))
for _, idx := range permutation {
newTraces[traceIDs[idx]] = tile.Traces[traceIDs[idx]]
}
tile.Traces = newTraces
}
return tile
}
// writeSample writes sample to disk.
func writeSample(outputFileName string, tile *tiling.Tile, expectations types.Expectations, ignoreStore ignore.IgnoreStore) {
sample := &serialize.Sample{
Tile: tile,
Expectations: expectations,
}
// Get the ignore rules.
var err error
if sample.IgnoreRules, err = ignoreStore.List(false); err != nil {
sklog.Fatalf("Error retrieving ignore rules: %s", err)
}
// Write the sample to disk.
var buf bytes.Buffer
t := timer.New("Writing sample")
err = sample.Serialize(&buf)
if err != nil {
sklog.Fatalf("Error serializing tile: %s", err)
}
t.Stop()
file, err := os.Create(outputFileName)
if err != nil {
sklog.Fatalf("Unable to create file %s: %s", outputFileName, err)
}
outputBuf := buf.Bytes()
_, err = file.Write(outputBuf)
if err != nil {
sklog.Fatalf("Writing file %s. Got error: %s", outputFileName, err)
}
util.Close(file)
// Read the sample from disk and do a deep compare.
t = timer.New("Reading back tile")
foundSample, err := serialize.DeserializeSample(bytes.NewBuffer(outputBuf))
if err != nil {
sklog.Fatalf("Error deserializing sample: %s", err)
}
t.Stop()
// Compare the traces to make sure.
for id, trace := range sample.Tile.Traces {
foundTrace, ok := foundSample.Tile.Traces[id]
if !ok {
sklog.Fatalf("Could not find trace with id: %s", id)
}
if !reflect.DeepEqual(trace, foundTrace) {
sklog.Fatalf("Traces do not match")
}
}
// Compare the expectations and ignores
if !reflect.DeepEqual(sample.Expectations, foundSample.Expectations) {
sklog.Fatalf("Expectations do not match")
}
if !reflect.DeepEqual(sample.IgnoreRules, foundSample.IgnoreRules) {
sklog.Fatalf("Ignore rules do not match")
}
sklog.Infof("File written successfully !")
}
// load retrieves the last tile, the expectations and the ignore store.
func load() (*tiling.Tile, types.Expectations, ignore.IgnoreStore) {
ctx := context.Background()
// Set up flags
common.Init()
evt := eventbus.New()
ts, err := auth.NewDefaultTokenSource(true)
if err != nil {
sklog.Fatalf("Cannot get token source: %s", err)
}
fsClient, err := firestore.NewClient(ctx, *fsProjectID, "gold", *fsNamespace, ts)
if err != nil {
sklog.Fatalf("Unable to configure Firestore: %s", err)
}
// Set up the cloud expectations store
expStore, err := fs_expstore.New(fsClient, evt, fs_expstore.ReadOnly)
if err != nil {
sklog.Fatalf("Unable to initialize fs_expstore: %s", err)
}
// Check out the repository.
git, err := gitinfo.CloneOrUpdate(ctx, *gitRepoURL, *gitRepoDir, false)
if err != nil {
sklog.Fatal(err)
}
// Open the tracedb and load the latest tile.
// Connect to traceDB and create the builders.
tdb, err := tracedb.NewTraceServiceDBFromAddress(*traceservice, types.GoldenTraceBuilder)
if err != nil {
sklog.Fatalf("Failed to connect to tracedb: %s", err)
}
masterTileBuilder, err := tracedb.NewMasterTileBuilder(ctx, tdb, git, *nCommits, evt, "")
if err != nil {
sklog.Fatalf("Failed to build trace/db.DB: %s", err)
}
storages := &storage.Storage{
ExpectationsStore: expStore,
MasterTileBuilder: masterTileBuilder,
NCommits: *nCommits,
EventBus: evt,
}
storages.IgnoreStore, err = ignore.NewCloudIgnoreStore(ds.DS, expStore, storages.GetTileStreamNow(time.Minute*20, "sampler-ignore-store"))
if err != nil {
sklog.Fatalf("Unable to create cloud ignorestore: %s", err)
}
expectations, err := expStore.Get()
if err != nil {
sklog.Fatalf("Unable to get expectations: %s", err)
}
return masterTileBuilder.GetTile(), expectations, storages.IgnoreStore
}