| // tally returns counts of digests for various views on a Tile. |
| package tally |
| |
| import ( |
| "net/url" |
| |
| "go.skia.org/infra/go/tiling" |
| "go.skia.org/infra/go/timer" |
| "go.skia.org/infra/go/util" |
| "go.skia.org/infra/golden/go/types" |
| ) |
| |
| // Tally maps a digest to a count. |
| type Tally map[string]int |
| |
| // Tallies allows querying for digest counts in different ways. |
| // It is not thread safe. The client of this package needs to make sure there |
| // are no conflicts. |
| type Tallies struct { |
| traceTally map[string]Tally |
| testTally map[string]Tally |
| maxCountsByTest map[string]util.StringSet |
| } |
| |
| // New creates a new Tallies object. |
| func New() *Tallies { |
| return &Tallies{} |
| } |
| |
| // Calculate sets the tallies for the given tile. |
| func (t *Tallies) Calculate(tile *tiling.Tile) { |
| trace, test, maxCountsByTest := tallyTile(tile) |
| t.traceTally = trace |
| t.testTally = test |
| t.maxCountsByTest = maxCountsByTest |
| } |
| |
| // ByTest returns Tally's indexed by test name. |
| func (t *Tallies) ByTest() map[string]Tally { |
| return t.testTally |
| } |
| |
| func (t *Tallies) MaxDigestsByTest() map[string]util.StringSet { |
| return t.maxCountsByTest |
| } |
| |
| // ByTrace returns Tally's index by trace id. |
| func (t *Tallies) ByTrace() map[string]Tally { |
| return t.traceTally |
| } |
| |
| // ByQuery returns a Tally of all the digests that match the given query in |
| // the provided tile. |
| func (t *Tallies) ByQuery(tile *tiling.Tile, query url.Values) Tally { |
| return tallyBy(tile, t.traceTally, query) |
| } |
| |
| // tallyBy does the actual work of ByQuery. |
| func tallyBy(tile *tiling.Tile, traceTally map[string]Tally, query url.Values) Tally { |
| ret := Tally{} |
| for k, tr := range tile.Traces { |
| if tiling.Matches(tr, query) { |
| if _, ok := traceTally[k]; !ok { |
| continue |
| } |
| for digest, n := range traceTally[k] { |
| if _, ok := ret[digest]; ok { |
| ret[digest] += n |
| } else { |
| ret[digest] = n |
| } |
| } |
| } |
| } |
| return ret |
| } |
| |
| // tallyTile computes a map[tracename]Tally and map[testname]Tally from the given Tile. |
| func tallyTile(tile *tiling.Tile) (map[string]Tally, map[string]Tally, map[string]util.StringSet) { |
| defer timer.New("tally").Stop() |
| traceTally := map[string]Tally{} |
| testTally := map[string]Tally{} |
| for k, tr := range tile.Traces { |
| gtr := tr.(*types.GoldenTrace) |
| tally := Tally{} |
| for _, s := range gtr.Values { |
| if s == types.MISSING_DIGEST { |
| continue |
| } |
| if n, ok := tally[s]; ok { |
| tally[s] = n + 1 |
| } else { |
| tally[s] = 1 |
| } |
| } |
| traceTally[k] = tally |
| testName := tr.Params()[types.PRIMARY_KEY_FIELD] |
| if t, ok := testTally[testName]; ok { |
| for digest, n := range tally { |
| if _, ok := t[digest]; ok { |
| t[digest] += n |
| } else { |
| t[digest] = n |
| } |
| } |
| } else { |
| cp := Tally{} |
| for k, v := range tally { |
| cp[k] = v |
| } |
| testTally[testName] = cp |
| } |
| } |
| |
| maxCountsByTest := make(map[string]util.StringSet, len(testTally)) |
| for testName, tally := range testTally { |
| maxCount := 0 |
| for _, count := range tally { |
| if count > maxCount { |
| maxCount = count |
| } |
| } |
| maxCountsByTest[testName] = util.StringSet{} |
| for digest, count := range tally { |
| if count == maxCount { |
| maxCountsByTest[testName][digest] = true |
| } |
| } |
| } |
| |
| return traceTally, testTally, maxCountsByTest |
| } |