blob: b2334cca9fea2b070e4a535948a4335493324dcf [file]
package analysis
import (
"sync"
"go.skia.org/infra/go/util"
"go.skia.org/infra/golden/go/types"
ptypes "go.skia.org/infra/perf/go/types"
)
// GUIBlameLists contains the blame lists for tests and their
// untriaged digests.
type GUIBlameLists struct {
// Commits of the currently labeled tile.
Commits []*ptypes.Commit `json:"commits"`
// Blames are the blame distributions keyed by test name.
Blames map[string][]*BlameDistribution `json:"blames"`
}
// BlameDistribution contains a rough estimation of the probabilities that
// a commit was responsible for the contained digest.
type BlameDistribution struct {
// Digest under consideration.
Digest string `json:"digest"`
// Freq contains likelihood counts that a commit was responsible
// for the observed digest. It starts with the first possible commitId
// that could have caused the digest and spans to the end the commit range.
Freq []int `json:"freq"`
}
type jobResult struct {
testName string
distributions []*BlameDistribution
}
// getBlameLists calculates the blame lists for the given labeled tile in
// parallel.
func getBlameLists(labeledTile *LabeledTile) *GUIBlameLists {
var wg sync.WaitGroup
results := make(chan jobResult, len(labeledTile.Traces))
for testName, traces := range labeledTile.Traces {
wg.Add(1)
go func(testName string, traces []*LabeledTrace) {
blameCommitRanges := map[string][][]int{}
blameStartCommitIds := map[string]int{}
blameEndCommitIds := map[string]int{}
for _, t := range traces {
first := -1
found := map[string]bool{}
for idx, digest := range t.Digests {
if t.Labels[idx] == types.UNTRIAGED {
if _, ok := found[digest]; !ok {
// We have not seen this digest in this trace before.
found[digest] = true
if currentEnd, ok := blameEndCommitIds[digest]; !ok {
blameEndCommitIds[digest] = t.CommitIds[idx]
blameStartCommitIds[digest] = first + 1
blameCommitRanges[digest] = [][]int{[]int{first + 1, t.CommitIds[idx]}}
} else {
blameEndCommitIds[digest] = util.MinInt(currentEnd, t.CommitIds[idx])
blameStartCommitIds[digest] = util.MinInt(blameStartCommitIds[digest], first+1)
blameCommitRanges[digest] = append(blameCommitRanges[digest], []int{first + 1, t.CommitIds[idx]})
}
} else {
first = t.CommitIds[idx]
}
} else {
first = t.CommitIds[idx]
}
}
}
results <- jobResult{testName, calcBlameDistributions(labeledTile.Commits, blameCommitRanges, blameStartCommitIds, blameEndCommitIds)}
wg.Done()
}(testName, traces)
}
wg.Wait()
close(results)
blames := make(map[string][]*BlameDistribution, len(labeledTile.Traces))
for r := range results {
blames[r.testName] = r.distributions
}
return &GUIBlameLists{
Commits: labeledTile.Commits,
Blames: blames,
}
}
// calcBlameDistributions returns the accumulated counts for the commits in a blamelist.
func calcBlameDistributions(commits []*ptypes.Commit, blameCommitRanges map[string][][]int, blameStartCommitIds map[string]int, blameEndCommitIds map[string]int) []*BlameDistribution {
result := make([]*BlameDistribution, 0, len(blameCommitRanges))
for digest, commitRanges := range blameCommitRanges {
startCommitId := blameStartCommitIds[digest]
endCommitId := blameEndCommitIds[digest]
freq := make([]int, len(commits)-startCommitId)
for _, oneCommitRange := range commitRanges {
for i := oneCommitRange[0]; i <= oneCommitRange[1]; i++ {
if i <= endCommitId {
freq[i-startCommitId]++
} else {
break
}
}
}
result = append(result, &BlameDistribution{
Digest: digest,
Freq: freq,
})
}
return result
}