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  }