| OLD | NEW |
| 1 package analysis | 1 package analysis |
| 2 | 2 |
| 3 import ( | 3 import ( |
| 4 "sync" | 4 "sync" |
| 5 "time" | 5 "time" |
| 6 | 6 |
| 7 "github.com/golang/glog" | 7 "github.com/golang/glog" |
| 8 "github.com/rcrowley/go-metrics" | 8 "github.com/rcrowley/go-metrics" |
| 9 | 9 |
| 10 "skia.googlesource.com/buildbot.git/go/util" | 10 "skia.googlesource.com/buildbot.git/go/util" |
| 11 "skia.googlesource.com/buildbot.git/golden/go/diff" | 11 "skia.googlesource.com/buildbot.git/golden/go/diff" |
| 12 "skia.googlesource.com/buildbot.git/golden/go/expstorage" | 12 "skia.googlesource.com/buildbot.git/golden/go/expstorage" |
| 13 "skia.googlesource.com/buildbot.git/golden/go/types" | 13 "skia.googlesource.com/buildbot.git/golden/go/types" |
| 14 ptypes "skia.googlesource.com/buildbot.git/perf/go/types" | 14 ptypes "skia.googlesource.com/buildbot.git/perf/go/types" |
| 15 ) | 15 ) |
| 16 | 16 |
| 17 // Stores a Trace with labels and digests in memory. CommitIds, Digests and | 17 // LabeledTrace stores a Trace with labels and digests. CommitIds, Digests and |
| 18 // Labels are of the same length, identical indices refer to the same digest. | 18 // Labels are of the same length, identical indices refer to the same digest. |
| 19 type LabeledTrace struct { | 19 type LabeledTrace struct { |
| 20 » Params map[string]string `json:"params"` | 20 » Params map[string]string |
| 21 » CommitIds []int `json:"commitIds"` | 21 » CommitIds []int |
| 22 » Digests []string `json:"digests"` | 22 » Digests []string |
| 23 » Labels []types.Label `json:"labels` | 23 » Labels []types.Label |
| 24 } | 24 } |
| 25 | 25 |
| 26 func NewLabeledTrace(params map[string]string, capacity int) *LabeledTrace { | 26 func NewLabeledTrace(params map[string]string, capacity int) *LabeledTrace { |
| 27 return &LabeledTrace{ | 27 return &LabeledTrace{ |
| 28 Params: params, | 28 Params: params, |
| 29 CommitIds: make([]int, 0, capacity), | 29 CommitIds: make([]int, 0, capacity), |
| 30 Digests: make([]string, 0, capacity), | 30 Digests: make([]string, 0, capacity), |
| 31 Labels: make([]types.Label, 0, capacity), | 31 Labels: make([]types.Label, 0, capacity), |
| 32 } | 32 } |
| 33 } | 33 } |
| 34 | 34 |
| 35 // Add the given tripples of commitIds, digests and labels to this LabeledTrace. | 35 // addLabledDigests adds the given tripples of commitIds, digests and labels to
this LabeledTrace. |
| 36 func (lt *LabeledTrace) addLabeledDigests(commitIds []int, digests []string, lab
els []types.Label) { | 36 func (lt *LabeledTrace) addLabeledDigests(commitIds []int, digests []string, lab
els []types.Label) { |
| 37 lt.CommitIds = append(lt.CommitIds, commitIds...) | 37 lt.CommitIds = append(lt.CommitIds, commitIds...) |
| 38 lt.Digests = append(lt.Digests, digests...) | 38 lt.Digests = append(lt.Digests, digests...) |
| 39 lt.Labels = append(lt.Labels, labels...) | 39 lt.Labels = append(lt.Labels, labels...) |
| 40 } | 40 } |
| 41 | 41 |
| 42 // Aggregates the Traces in tile and provides the commits that the | 42 // LabeledTile aggregates the traces of a tile and provides a slice of commits |
| 43 // CommitIds in LabeledTrace refer to. | 43 // that the commitIds in LabeledTrace refer to. |
| 44 // LabeledTile and LabeledTrace store the cannonical information |
| 45 // extracted from the unterlying tile store. The (redundant) output data is |
| 46 // derived from these. |
| 44 type LabeledTile struct { | 47 type LabeledTile struct { |
| 45 » Commits []*ptypes.Commit `json:"commits"` | 48 » Commits []*ptypes.Commit |
| 46 | 49 |
| 47 // Traces are indexed by the primary key (test name). This is somewhat | 50 // Traces are indexed by the primary key (test name). This is somewhat |
| 48 // redundant, but this also output format. | 51 // redundant, but this also output format. |
| 49 » Traces map[string][]*LabeledTrace `json:"traces"` | 52 » Traces map[string][]*LabeledTrace |
| 50 } | 53 } |
| 51 | 54 |
| 52 func NewLabeledTile() *LabeledTile { | 55 func NewLabeledTile() *LabeledTile { |
| 53 return &LabeledTile{ | 56 return &LabeledTile{ |
| 54 Commits: []*ptypes.Commit{}, | 57 Commits: []*ptypes.Commit{}, |
| 55 Traces: map[string][]*LabeledTrace{}, | 58 Traces: map[string][]*LabeledTrace{}, |
| 56 } | 59 } |
| 57 } | 60 } |
| 58 | 61 |
| 59 // Utility function that returns the testName and a labeled trace for the given | 62 // getLabeledTrace is a utility function that returns the testName and a labeled |
| 60 // Trace (read from a TileStore). If the LabeledTrace does not exist it will be | 63 // trace for the given trace (read from a TileStore). If the LabeledTrace does |
| 61 // added. | 64 // not exist it will be added. |
| 62 func (t *LabeledTile) getLabeledTrace(trace ptypes.Trace) (string, *LabeledTrace
) { | 65 func (t *LabeledTile) getLabeledTrace(trace ptypes.Trace) (string, *LabeledTrace
) { |
| 63 params := trace.Params() | 66 params := trace.Params() |
| 64 pKey := params[types.PRIMARY_KEY_FIELD] | 67 pKey := params[types.PRIMARY_KEY_FIELD] |
| 65 if _, ok := t.Traces[pKey]; !ok { | 68 if _, ok := t.Traces[pKey]; !ok { |
| 66 // Add the primary key with a single labled trace. | 69 // Add the primary key with a single labled trace. |
| 67 t.Traces[pKey] = []*LabeledTrace{} | 70 t.Traces[pKey] = []*LabeledTrace{} |
| 68 } | 71 } |
| 69 | 72 |
| 70 // Search through the traces associated witht this test. | 73 // Search through the traces associated witht this test. |
| 71 for _, v := range t.Traces[pKey] { | 74 for _, v := range t.Traces[pKey] { |
| 72 if util.MapsEqual(v.Params, params) { | 75 if util.MapsEqual(v.Params, params) { |
| 73 return pKey, v | 76 return pKey, v |
| 74 } | 77 } |
| 75 } | 78 } |
| 76 | 79 |
| 77 // If we cannot find the trace in our set of tests we are adding a new | 80 // If we cannot find the trace in our set of tests we are adding a new |
| 78 // labeled trace. | 81 // labeled trace. |
| 79 newLT := NewLabeledTrace(params, trace.Len()) | 82 newLT := NewLabeledTrace(params, trace.Len()) |
| 80 t.Traces[pKey] = append(t.Traces[pKey], newLT) | 83 t.Traces[pKey] = append(t.Traces[pKey], newLT) |
| 81 return pKey, newLT | 84 return pKey, newLT |
| 82 } | 85 } |
| 83 | 86 |
| 84 // Analyzer continuously manages the tasks, like pollint for new traces | 87 // LabelCounts is an output type to hold counts for classification labels. |
| 85 // on disk, etc. | 88 type LabelCounts struct { |
| 89 » Unt int `json:"unt"` // Untriaged |
| 90 » Pos int `json:"pos"` // Positive |
| 91 » Neg int `json:"neg"` // Negative |
| 92 } |
| 93 |
| 94 // GUITileCounts is an output type for the aggregated label counts. |
| 95 type GUITileCounts struct { |
| 96 » Commits []*ptypes.Commit `json:"commits"` |
| 97 » Counts map[string][]LabelCounts `json:"counts"` |
| 98 } |
| 99 |
| 100 // GUITestCounts is an output type for a single test that contains the |
| 101 // aggregated counts over all traces and also the individual traces |
| 102 // and their labels. |
| 103 type GUITestCounts struct { |
| 104 » Commits []*ptypes.Commit `json:"commits"` |
| 105 » Aggregated []LabelCounts `json:"aggregated"` |
| 106 » Traces []*GUILabeledTrace `json:"traces"` |
| 107 } |
| 108 |
| 109 // GUILabeledTrace is an output type for the labels of a trace. |
| 110 type GUILabeledTrace struct { |
| 111 » Params map[string]string `json:"params"` |
| 112 |
| 113 » // List of commitId and Label pairs. |
| 114 » Labels []IdLabel `json:"labels"` |
| 115 } |
| 116 |
| 117 // IdLabel stores the commitId and the label for one entry in a trace. |
| 118 type IdLabel struct { |
| 119 » Id int `json:"id"` |
| 120 » Label int `json:"label"` |
| 121 } |
| 122 |
| 123 // Analyzer continuously manages tasks like polling for new traces |
| 124 // on disk and generating diffs between images. It is the primary interface |
| 125 // to be called by the HTTP frontend. |
| 86 type Analyzer struct { | 126 type Analyzer struct { |
| 87 expStore expstorage.ExpectationsStore | 127 expStore expstorage.ExpectationsStore |
| 88 diffStore diff.DiffStore | 128 diffStore diff.DiffStore |
| 89 tileStore ptypes.TileStore | 129 tileStore ptypes.TileStore |
| 90 | 130 |
| 131 // Canonical data structure to hold our information about commits, diges
ts |
| 132 // and labels. |
| 91 currentTile *LabeledTile | 133 currentTile *LabeledTile |
| 92 | 134 |
| 93 » // Lock to protect the expectations and the current labeled tile. | 135 » // Output data structures that are derived from currentTile. |
| 136 » currentTileCounts *GUITileCounts |
| 137 » currentTestCounts map[string]*GUITestCounts |
| 138 |
| 139 » // Lock to protect the expectations and current* variables. |
| 94 mutex sync.Mutex | 140 mutex sync.Mutex |
| 95 } | 141 } |
| 96 | 142 |
| 97 func NewAnalyzer(expStore expstorage.ExpectationsStore, tileStore ptypes.TileSto
re, diffStore diff.DiffStore, timeBetweenPolls time.Duration) *Analyzer { | 143 func NewAnalyzer(expStore expstorage.ExpectationsStore, tileStore ptypes.TileSto
re, diffStore diff.DiffStore, timeBetweenPolls time.Duration) *Analyzer { |
| 98 result := &Analyzer{ | 144 result := &Analyzer{ |
| 99 expStore: expStore, | 145 expStore: expStore, |
| 100 diffStore: diffStore, | 146 diffStore: diffStore, |
| 101 tileStore: tileStore, | 147 tileStore: tileStore, |
| 102 | 148 |
| 103 currentTile: NewLabeledTile(), | 149 currentTile: NewLabeledTile(), |
| 104 } | 150 } |
| 105 | 151 |
| 106 go result.loop(timeBetweenPolls) | 152 go result.loop(timeBetweenPolls) |
| 107 return result | 153 return result |
| 108 } | 154 } |
| 109 | 155 |
| 110 // Returns an entire Tile which is a collection of 'traces' over a series of | 156 // GetTileCounts returns an entire Tile which is a collection of 'traces' over |
| 111 // of commits. Each trace contains the digests and their labels based on | 157 // a series of commits. Each trace contains the digests and their labels |
| 112 // out knowledge base about digests (expectations). | 158 // based on our knowledge about digests (expectations). |
| 113 func (a *Analyzer) GetLabeledTile() *LabeledTile { | 159 func (a *Analyzer) GetTileCounts() (*GUITileCounts, error) { |
| 114 a.mutex.Lock() | 160 a.mutex.Lock() |
| 115 defer a.mutex.Unlock() | 161 defer a.mutex.Unlock() |
| 116 | 162 |
| 117 » return a.currentTile | 163 » return a.currentTileCounts, nil |
| 118 } | 164 } |
| 119 | 165 |
| 120 func (a *Analyzer) GetLabeledTraces(testName string) []*LabeledTrace { | 166 // GetTestCounts returns the classification counts for a specific tests. |
| 167 func (a *Analyzer) GetTestCounts(testName string) (*GUITestCounts, error) { |
| 121 a.mutex.Lock() | 168 a.mutex.Lock() |
| 122 defer a.mutex.Unlock() | 169 defer a.mutex.Unlock() |
| 123 | 170 |
| 124 » return a.currentTile.Traces[testName] | 171 » // TODO (stephana): This should return any error that occurs during read
ing |
| 172 » // of the tiles. We would rather get an error on the front-end than |
| 173 » // look at outdated data. |
| 174 » return a.currentTestCounts[testName], nil |
| 125 } | 175 } |
| 126 | 176 |
| 177 // SetDigestLabels sets the labels for the given digest and records the user |
| 178 // that made the classification. |
| 127 func (a *Analyzer) SetDigestLabels(labeledTestDigests map[string]types.TestClass
ification, userId string) (map[string][]*LabeledTrace, error) { | 179 func (a *Analyzer) SetDigestLabels(labeledTestDigests map[string]types.TestClass
ification, userId string) (map[string][]*LabeledTrace, error) { |
| 128 a.mutex.Lock() | 180 a.mutex.Lock() |
| 129 defer a.mutex.Unlock() | 181 defer a.mutex.Unlock() |
| 130 | 182 |
| 131 expectations, err := a.expStore.Get(true) | 183 expectations, err := a.expStore.Get(true) |
| 132 if err != nil { | 184 if err != nil { |
| 133 return nil, err | 185 return nil, err |
| 134 } | 186 } |
| 135 expectations.AddDigests(labeledTestDigests) | 187 expectations.AddDigests(labeledTestDigests) |
| 136 if err = a.expStore.Put(expectations, userId); err != nil { | 188 if err = a.expStore.Put(expectations, userId); err != nil { |
| 137 return nil, err | 189 return nil, err |
| 138 } | 190 } |
| 139 | 191 |
| 140 // Let's update our knowledge of the labels. | 192 // Let's update our knowledge of the labels. |
| 141 updatedTraces := a.relabelTraces(labeledTestDigests) | 193 updatedTraces := a.relabelTraces(labeledTestDigests) |
| 194 |
| 142 return updatedTraces, nil | 195 return updatedTraces, nil |
| 143 } | 196 } |
| 144 | 197 |
| 145 // Main loop. | 198 // loop is the main event loop. |
| 146 func (a *Analyzer) loop(timeBetweenPolls time.Duration) { | 199 func (a *Analyzer) loop(timeBetweenPolls time.Duration) { |
| 147 // The number of times we've successfully loaded and processed a tile. | 200 // The number of times we've successfully loaded and processed a tile. |
| 148 runsCounter := metrics.NewRegisteredCounter("analysis.runs", metrics.Def
aultRegistry) | 201 runsCounter := metrics.NewRegisteredCounter("analysis.runs", metrics.Def
aultRegistry) |
| 149 | 202 |
| 150 // The number of times an error has ocurred when trying to load a tile. | 203 // The number of times an error has ocurred when trying to load a tile. |
| 151 errorTileLoadingCounter := metrics.NewRegisteredCounter("analysis.errors
", metrics.DefaultRegistry) | 204 errorTileLoadingCounter := metrics.NewRegisteredCounter("analysis.errors
", metrics.DefaultRegistry) |
| 152 | 205 |
| 153 » for { | 206 » for _ = range time.Tick(timeBetweenPolls) { |
| 154 glog.Info("Reading tiles ... ") | 207 glog.Info("Reading tiles ... ") |
| 155 | 208 |
| 156 // Load the tile and process it. | 209 // Load the tile and process it. |
| 157 tile, err := a.tileStore.Get(0, -1) | 210 tile, err := a.tileStore.Get(0, -1) |
| 158 if err != nil { | 211 if err != nil { |
| 159 glog.Errorf("Error reading tile store: %s\n", err.Error(
)) | 212 glog.Errorf("Error reading tile store: %s\n", err.Error(
)) |
| 160 errorTileLoadingCounter.Inc(1) | 213 errorTileLoadingCounter.Inc(1) |
| 161 } else { | 214 } else { |
| 162 newLabeledTile := a.processTile(tile) | 215 newLabeledTile := a.processTile(tile) |
| 216 newTileCounts, newTestCounts := a.getOutputCounts(newLab
eledTile) |
| 217 |
| 163 a.mutex.Lock() | 218 a.mutex.Lock() |
| 164 a.currentTile = newLabeledTile | 219 a.currentTile = newLabeledTile |
| 220 a.currentTileCounts = newTileCounts |
| 221 a.currentTestCounts = newTestCounts |
| 165 a.mutex.Unlock() | 222 a.mutex.Unlock() |
| 166 } | 223 } |
| 224 glog.Info("Done processing tiles.") |
| 167 runsCounter.Inc(1) | 225 runsCounter.Inc(1) |
| 168 | |
| 169 // Sleep for a while until the next poll. | |
| 170 time.Sleep(timeBetweenPolls) | |
| 171 } | 226 } |
| 172 } | 227 } |
| 173 | 228 |
| 174 // Process a tile segment and add it to the currentTile. | 229 // processTile processes the last two tiles and updates the cannonical and |
| 230 // output data structures. |
| 175 func (a *Analyzer) processTile(tile *ptypes.Tile) *LabeledTile { | 231 func (a *Analyzer) processTile(tile *ptypes.Tile) *LabeledTile { |
| 176 result := NewLabeledTile() | 232 result := NewLabeledTile() |
| 177 | 233 |
| 178 tileLen := tile.LastCommitIndex() + 1 | 234 tileLen := tile.LastCommitIndex() + 1 |
| 179 result.Commits = tile.Commits[:tileLen] | 235 result.Commits = tile.Commits[:tileLen] |
| 180 | 236 |
| 181 // Note: We are assumming that the number and order of traces will chang
e | 237 // Note: We are assumming that the number and order of traces will chang
e |
| 182 // over time. | 238 // over time. |
| 183 for _, v := range tile.Traces { | 239 for _, v := range tile.Traces { |
| 184 tempCommitIds := make([]int, 0, tileLen) | 240 tempCommitIds := make([]int, 0, tileLen) |
| (...skipping 16 matching lines...) Expand all Loading... |
| 201 if err := a.labelDigests(testName, tempDigests, tempLabels); err
!= nil { | 257 if err := a.labelDigests(testName, tempDigests, tempLabels); err
!= nil { |
| 202 glog.Errorf("Error labeling digests: %s\n", err.Error()) | 258 glog.Errorf("Error labeling digests: %s\n", err.Error()) |
| 203 continue | 259 continue |
| 204 } | 260 } |
| 205 targetLabeledTrace.addLabeledDigests(tempCommitIds, tempDigests,
tempLabels) | 261 targetLabeledTrace.addLabeledDigests(tempCommitIds, tempDigests,
tempLabels) |
| 206 } | 262 } |
| 207 | 263 |
| 208 return result | 264 return result |
| 209 } | 265 } |
| 210 | 266 |
| 211 // Run over the traces in of the tiles that have changed and label them | 267 // relabelTraces iterates over the traces in of the tiles that have changed and |
| 212 // according to our current expecatations. | 268 // labels them according to our current expecatations. |
| 213 func (a *Analyzer) relabelTraces(labeledTestDigests map[string]types.TestClassif
ication) map[string][]*LabeledTrace { | 269 func (a *Analyzer) relabelTraces(labeledTestDigests map[string]types.TestClassif
ication) map[string][]*LabeledTrace { |
| 214 result := map[string][]*LabeledTrace{} | 270 result := map[string][]*LabeledTrace{} |
| 215 | 271 |
| 216 for testName := range labeledTestDigests { | 272 for testName := range labeledTestDigests { |
| 217 if traces, ok := a.currentTile.Traces[testName]; ok { | 273 if traces, ok := a.currentTile.Traces[testName]; ok { |
| 218 for _, trace := range traces { | 274 for _, trace := range traces { |
| 219 // Note: This is potentially slower than using l
abels in | 275 // Note: This is potentially slower than using l
abels in |
| 220 // labeledTestDigests directly, but it keeps the
code simpler. | 276 // labeledTestDigests directly, but it keeps the
code simpler. |
| 221 a.labelDigests(testName, trace.Digests, trace.La
bels) | 277 a.labelDigests(testName, trace.Digests, trace.La
bels) |
| 222 } | 278 } |
| (...skipping 18 matching lines...) Expand all Loading... |
| 241 for idx, digest := range digests { | 297 for idx, digest := range digests { |
| 242 if test, ok := expectations.Tests[testName]; ok { | 298 if test, ok := expectations.Tests[testName]; ok { |
| 243 if foundLabel, ok := test[digest]; ok { | 299 if foundLabel, ok := test[digest]; ok { |
| 244 targetLabels[idx] = foundLabel | 300 targetLabels[idx] = foundLabel |
| 245 } | 301 } |
| 246 } | 302 } |
| 247 } | 303 } |
| 248 | 304 |
| 249 return nil | 305 return nil |
| 250 } | 306 } |
| 307 |
| 308 // getOutputCounts derives the output counts from the given labeled tile. |
| 309 func (a *Analyzer) getOutputCounts(labeledTile *LabeledTile) (*GUITileCounts, ma
p[string]*GUITestCounts) { |
| 310 // Stores the aggregated counts of a tile for each test. |
| 311 tileCountsMap := make(map[string][]LabelCounts, len(labeledTile.Traces)) |
| 312 |
| 313 // Stores the aggregated counts for each test and individual trace infor
mation. |
| 314 testCountsMap := make(map[string]*GUITestCounts, len(labeledTile.Traces)
) |
| 315 |
| 316 for testName, testTraces := range labeledTile.Traces { |
| 317 acc := make([]LabelCounts, len(labeledTile.Commits)) |
| 318 tempTraces := make([]*GUILabeledTrace, 0, len(testTraces)) |
| 319 |
| 320 for _, oneTrace := range testTraces { |
| 321 tempTrace := &GUILabeledTrace{ |
| 322 Params: oneTrace.Params, |
| 323 Labels: make([]IdLabel, len(oneTrace.CommitIds))
, |
| 324 } |
| 325 |
| 326 for i, ci := range oneTrace.CommitIds { |
| 327 switch oneTrace.Labels[i] { |
| 328 case types.UNTRIAGED: |
| 329 acc[ci].Unt++ |
| 330 case types.POSITIVE: |
| 331 acc[ci].Pos++ |
| 332 case types.NEGATIVE: |
| 333 acc[ci].Neg++ |
| 334 } |
| 335 tempTrace.Labels[i].Id = ci |
| 336 tempTrace.Labels[i].Label = int(oneTrace.Labels[
i]) |
| 337 } |
| 338 |
| 339 tempTraces = append(tempTraces, tempTrace) |
| 340 } |
| 341 |
| 342 tileCountsMap[testName] = acc |
| 343 testCountsMap[testName] = &GUITestCounts{ |
| 344 Commits: labeledTile.Commits, |
| 345 Aggregated: acc, |
| 346 Traces: tempTraces, |
| 347 } |
| 348 } |
| 349 |
| 350 tileCounts := &GUITileCounts{ |
| 351 Commits: labeledTile.Commits, |
| 352 Counts: tileCountsMap, |
| 353 } |
| 354 |
| 355 return tileCounts, testCountsMap |
| 356 } |
| OLD | NEW |