blob: b1514817fc50d7139bf6510729f99d8c5abc8d1e [file] [log] [blame]
package tracestore
import (
"context"
"strings"
"time"
"go.skia.org/infra/go/paramtools"
"go.skia.org/infra/go/query"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/go/tiling"
"go.skia.org/infra/golden/go/types"
)
// Entry is one digests and related params to be added to the TraceStore.
type Entry struct {
// Params describe the configuration that produced the digest/image.
// These params will be used to form the trace id.
Params map[string]string
// Options give extra details about this trace. These options will be stored
// tile by tile, with the most recent commit's options overwriting any
// previous options. These details do not affect the trace id and are
// largely considered to be FYI.
Options map[string]string
// Digest references the image that was generated by the test.
// If two different Puts set a digest for a given commit + Params, the one
// with the later timestamp will be kept.
Digest types.Digest
}
// TraceStore is the interface to store trace data.
type TraceStore interface {
// Put writes the given entries to the TraceStore at the given commit hash. The timestamp is
// assumed to be the time when the entries were generated and will be used for the digests.
// It is undefined behavior to have multiple entries with the exact same Params.
Put(ctx context.Context, commitHash string, entries []*Entry, ts time.Time) error
// GetTile reads the last n commits and returns them as a tile.
// The second return value is all commits that are in the tile.
GetTile(ctx context.Context, nCommits int) (*tiling.Tile, []*tiling.Commit, error)
// GetDenseTile constructs a tile containing only commits that have data for at least one trace.
// The returned tile will always have length exactly nCommits unless there are fewer than
// nCommits commits with data. The second return value contains all commits starting with the
// first commit of the tile and ending with the most recent commit, in order; i.e. it includes
// all commits in the tile as well as the omitted commits.
GetDenseTile(ctx context.Context, nCommits int) (*tiling.Tile, []*tiling.Commit, error)
}
// TraceIDFromParams deterministically returns a TraceID that uniquely encodes
// the given params. It follows the same convention as perf's trace ids, that
// is something like ",key1=value1,key2=value2,...," where the keys
// are in alphabetical order.
func TraceIDFromParams(params paramtools.Params) tiling.TraceID {
// Clean up any params with , or =
params = forceValid(params)
s, err := query.MakeKeyFast(params)
if err != nil {
sklog.Warningf("Invalid params passed in for trace id %#v: %s", params, err)
}
return tiling.TraceID(s)
}
// clean replaces any special runes (',', '=') in a string such that
// they can be turned into a trace id, which uses those special runes
// as dividers.
func clean(s string) string {
// In most cases, traces will be valid, so check that first.
// Allocating the string buffer and copying the runes can be expensive
// when done for no reason.
bad := false
for _, c := range s {
if c == ',' || c == '=' {
bad = true
break
}
}
if !bad {
return s
}
sb := strings.Builder{}
sb.Grow(len(s))
// Regexp doesn't handle being run from a large number of go routines
// very well. See https://github.com/golang/go/issues/8232.
for _, c := range s {
if c == ',' || c == '=' {
sb.WriteRune('_')
} else {
sb.WriteRune(c)
}
}
return sb.String()
}
// forceValid ensures that the resulting map will make a valid structured key.
func forceValid(m map[string]string) map[string]string {
ret := make(map[string]string, len(m))
for key, value := range m {
ret[clean(key)] = clean(value)
}
return ret
}