| 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 |
| } |