github.com/abayer/test-infra@v0.0.5/mungegithub/mungers/flakesync/cache.go (about) 1 /* 2 Copyright 2016 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package flakesync 18 19 import ( 20 "container/list" 21 "sort" 22 "sync" 23 "time" 24 ) 25 26 // Job is a test job, e.g. "kubernetes-e2e-gce" 27 type Job string 28 29 // Number is a run of a test job. 30 type Number int 31 32 // Test is the name of an individual test that runs in a Job. 33 type Test string 34 35 // ResultStatus can be stable, flaky, or failed 36 type ResultStatus string 37 38 const ( 39 // ResultStable means green, everything passed 40 ResultStable ResultStatus = "stable" 41 42 // ResultFlaky means the job run worked, but some tests failed. 43 ResultFlaky ResultStatus = "flaky" 44 45 // ResultFailed means it failed without generating readable JUnit 46 // files, and introspection is not possible. 47 ResultFailed ResultStatus = "failed" 48 49 // RunBrokenTestName names a "flake" which really represents the fact 50 // that the entire run was broken. 51 RunBrokenTestName Test = "Suite so broken it failed to produce JUnit output" 52 53 // Maximum number of flakes to keep in memory. 54 maxFlakes = 20000 55 ) 56 57 // Result records a test job completion. 58 type Result struct { 59 // The jenkins job 60 Job 61 // The run number 62 Number 63 64 Status ResultStatus 65 66 StartTime time.Time 67 EndTime time.Time 68 69 // test name to reason/desc 70 Flakes map[Test]string 71 } 72 73 // Flake records a single flake occurrence. 74 type Flake struct { 75 Job 76 Number 77 Test 78 Reason string 79 80 // Pointer back to the result this flake came from 81 *Result 82 } 83 84 type flakeKey struct { 85 Job 86 Number 87 Test 88 } 89 90 type flakeMap map[flakeKey]*Flake 91 type key struct { 92 Job 93 Number 94 } 95 type jobMap map[key]*Result 96 97 // Cache caches test result lookups, and aggregates flakes in a single place. 98 // TODO: evict based on time. 99 // TODO: evict when issue filed. 100 // TODO: backfill to given time. 101 type Cache struct { 102 lock sync.Mutex 103 byJob jobMap 104 flakeQueue flakeMap 105 expireList *list.List 106 maxFlakes int // tests can modify this 107 108 // only one expensive lookup at a time. Also, don't lock the cache 109 // while we're doing an expensive update. If you lock both locks, you 110 // must lock this one first. 111 expensiveLookupLock sync.Mutex 112 doExpensiveLookup ResultFunc 113 } 114 115 // ResultFunc should look up the job & number from its source (GCS or 116 // wherever). 117 type ResultFunc func(Job, Number) (*Result, error) 118 119 // NewCache returns a new Cache. 120 func NewCache(getFunc ResultFunc) *Cache { 121 c := &Cache{ 122 byJob: jobMap{}, 123 flakeQueue: flakeMap{}, 124 expireList: list.New(), 125 doExpensiveLookup: getFunc, 126 maxFlakes: maxFlakes, 127 } 128 return c 129 } 130 131 // Get returns an item from the cache, possibly calling the lookup function 132 // passed at construction time. 133 func (c *Cache) Get(j Job, n Number) (*Result, error) { 134 if r, ok := c.lookup(j, n); ok { 135 return r, nil 136 } 137 return c.populate(j, n) 138 } 139 140 func (c *Cache) lookup(j Job, n Number) (*Result, bool) { 141 c.lock.Lock() 142 defer c.lock.Unlock() 143 k := key{j, n} 144 r, ok := c.byJob[k] 145 return r, ok 146 } 147 148 func (c *Cache) populate(j Job, n Number) (*Result, error) { 149 c.expensiveLookupLock.Lock() 150 defer c.expensiveLookupLock.Unlock() 151 if r, ok := c.lookup(j, n); ok { 152 // added to the queue in the time it took us to get the lock. 153 return r, nil 154 } 155 156 r, err := c.doExpensiveLookup(j, n) 157 if err != nil { 158 return nil, err 159 } 160 161 c.lock.Lock() 162 defer c.lock.Unlock() 163 k := key{j, n} 164 c.byJob[k] = r 165 166 // Add any flakes to the queue. 167 for f, reason := range r.Flakes { 168 k := flakeKey{j, n, f} 169 c.flakeQueue[k] = &Flake{ 170 Job: j, 171 Number: n, 172 Test: f, 173 Reason: reason, 174 Result: r, 175 } 176 c.expireList.PushFront(k) 177 // kick out old flakes if we have too many. 178 for len(c.flakeQueue) > c.maxFlakes { 179 e := c.expireList.Back() 180 delete(c.flakeQueue, e.Value.(flakeKey)) 181 c.expireList.Remove(e) 182 } 183 } 184 return r, nil 185 } 186 187 // Flakes is a sortable list of flakes. 188 type Flakes []Flake 189 190 func (f Flakes) Len() int { return len(f) } 191 func (f Flakes) Swap(i, j int) { f[i], f[j] = f[j], f[i] } 192 func (f Flakes) Less(i, j int) bool { 193 if f[i].Test < f[j].Test { 194 return true 195 } 196 if f[i].Test > f[j].Test { 197 return false 198 } 199 if f[i].Job < f[j].Job { 200 return true 201 } 202 if f[i].Job > f[j].Job { 203 return false 204 } 205 if f[i].Number < f[j].Number { 206 return true 207 } 208 if f[i].Number > f[j].Number { 209 return false 210 } 211 return f[i].Reason < f[j].Reason 212 } 213 214 // Flakes lists all the current flakes, sorted. 215 func (c *Cache) Flakes() Flakes { 216 flakes := Flakes{} 217 func() { 218 c.lock.Lock() 219 defer c.lock.Unlock() 220 for _, f := range c.flakeQueue { 221 flakes = append(flakes, *f) 222 } 223 }() 224 sort.Sort(flakes) 225 return flakes 226 }