github.com/web-platform-tests/wpt.fyi@v0.0.0-20240530210107-70cf978996f1/api/query/cache/monitor/monitor.go (about) 1 // Copyright 2018 The WPT Dashboard Project. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package monitor 6 7 import ( 8 "errors" 9 "runtime" 10 "sync" 11 "time" 12 13 "github.com/web-platform-tests/wpt.fyi/api/query/cache/index" 14 "github.com/web-platform-tests/wpt.fyi/shared" 15 ) 16 17 var ( 18 errStopped = errors.New("Monitor stopped") 19 errRunning = errors.New("Monitor running") 20 errNegativePercent = errors.New("Invalid percentage (negative)") 21 errPercentTooLarge = errors.New("Invalid percentage (greater than 1.00)") 22 ) 23 24 // Runtime is a wrapper for the go runtime package. It allows tests to mock 25 // runtime characteristics that code under test may monitor. 26 type Runtime interface { 27 // GetHeapBytes reports the current number of heap allocated bytes. 28 GetHeapBytes() uint64 29 } 30 31 // Monitor is an interface responsible for monitoring runtime conditions. 32 type Monitor interface { 33 // Start starts the monitor, blocking until the monitor stops. 34 Start() error 35 // Stop stops the monitor. 36 Stop() error 37 // SetInterval sets the interval at which the monitor polls runtime state. 38 SetInterval(time.Duration) error 39 // SetMaxHeapBytes sets the limit on heap allocated bytes before attempting to 40 // relieve memory pressure. 41 SetMaxHeapBytes(uint64) error 42 // SetEvictionPercent sets the percentage of runs to be evicted when the soft 43 // memory limit (max heap bytes) is reached. 44 SetEvictionPercent(float64) error 45 } 46 47 // ProxyMonitor is a proxy implementation of the Monitor interface. This type is 48 // generally used in type embeddings that wish to override the behaviour of some 49 // (but not all) methods, deferring to the delegate for all other behaviours. 50 type ProxyMonitor struct { 51 delegate Monitor 52 } 53 54 // Start initiates monitoring by deferring to the proxy's delegate. 55 func (m *ProxyMonitor) Start() error { 56 return m.delegate.Start() 57 } 58 59 // Stop halts monitoring by deferring to the proxy's delegate. 60 func (m *ProxyMonitor) Stop() error { 61 return m.delegate.Stop() 62 } 63 64 // SetInterval changes the interval at which monitoring operations are performed 65 // by deferring to the proxy's delegated. 66 func (m *ProxyMonitor) SetInterval(i time.Duration) error { 67 return m.delegate.SetInterval(i) 68 } 69 70 // SetMaxHeapBytes sets the soft limit on heap memory usage by deferring to the 71 // proxy's delegated. 72 func (m *ProxyMonitor) SetMaxHeapBytes(b uint64) error { 73 return m.delegate.SetMaxHeapBytes(b) 74 } 75 76 // SetEvictionPercent sets the percentage of runs to be evicted when the soft 77 // memory limit (max heap bytes) is reached by deferring to the proxy's 78 // delegate. 79 func (m *ProxyMonitor) SetEvictionPercent(percent float64) error { 80 return m.delegate.SetEvictionPercent(percent) 81 } 82 83 // NewProxyMonitor instantiates a new proxy monitor bound to the given delegate. 84 func NewProxyMonitor(m Monitor) ProxyMonitor { 85 return ProxyMonitor{m} 86 } 87 88 // GoRuntime is the live go runtime implementation of Runtime. 89 type GoRuntime struct{} 90 91 // GetHeapBytes reports the current number of heap allocated bytes. 92 func (r GoRuntime) GetHeapBytes() uint64 { 93 var stats runtime.MemStats 94 runtime.ReadMemStats(&stats) 95 96 return stats.HeapAlloc 97 } 98 99 type indexMonitor struct { 100 logger shared.Logger 101 rt Runtime 102 interval time.Duration 103 maxIngestedRuns uint 104 maxHeapBytes uint64 105 percent float64 106 107 isRunning bool 108 mutex *sync.Mutex 109 runIngested chan bool 110 111 idx index.Index 112 } 113 114 func (m *indexMonitor) Start() error { 115 err := m.start() 116 if err != nil { 117 return err 118 } 119 120 m.idx.SetIngestChan(m.runIngested) 121 122 reqs := uint(0) 123 for { 124 if !m.isRunning { 125 return errStopped 126 } 127 128 timer := make(chan bool, 1) 129 go func() { 130 time.Sleep(m.interval) 131 timer <- true 132 }() 133 134 for { 135 select { 136 case <-timer: 137 reqs = 0 138 case <-m.runIngested: 139 reqs++ 140 if reqs == m.maxIngestedRuns { 141 reqs = 0 142 } 143 } 144 if reqs == 0 { 145 break 146 } 147 } 148 149 m.check() 150 } 151 } 152 153 func (m *indexMonitor) Stop() error { 154 m.mutex.Lock() 155 defer m.mutex.Unlock() 156 if !m.isRunning { 157 return errStopped 158 } 159 m.isRunning = false 160 m.runIngested = make(chan bool) 161 162 return nil 163 } 164 165 func (m *indexMonitor) SetInterval(interval time.Duration) error { 166 m.mutex.Lock() 167 defer m.mutex.Unlock() 168 m.interval = interval 169 170 return nil 171 } 172 173 func (m *indexMonitor) SetMaxHeapBytes(maxHeapBytes uint64) error { 174 m.mutex.Lock() 175 defer m.mutex.Unlock() 176 m.maxHeapBytes = maxHeapBytes 177 178 return nil 179 } 180 181 func (m *indexMonitor) SetEvictionPercent(percent float64) error { 182 if percent < 0 { 183 return errNegativePercent 184 } else if percent > 1.0 { 185 return errPercentTooLarge 186 } 187 188 m.percent = percent 189 190 return nil 191 } 192 193 func (m *indexMonitor) start() error { 194 m.mutex.Lock() 195 defer m.mutex.Unlock() 196 if m.isRunning { 197 return errRunning 198 } 199 m.isRunning = true 200 201 return nil 202 } 203 204 func (m *indexMonitor) check() { 205 heapBytes := m.rt.GetHeapBytes() 206 if heapBytes > m.maxHeapBytes { 207 m.logger.Warningf("Monitor %d bytes allocated, exceeding threshold of %d bytes", heapBytes, m.maxHeapBytes) 208 if _, err := m.idx.EvictRuns(m.percent); err != nil { 209 m.logger.Warningf("Error occurred while evicting %f%% of current runs: %w", m.percent, err) 210 } 211 } else { 212 m.logger.Debugf("Monitor: %d heap-allocated bytes OK", heapBytes) 213 } 214 } 215 216 // NewIndexMonitor instantiates a new index.Index monitor. 217 // nolint:ireturn // TODO: Fix ireturn lint error 218 func NewIndexMonitor( 219 logger shared.Logger, 220 rt Runtime, interval time.Duration, 221 maxIngestedRuns uint, 222 maxHeapBytes uint64, 223 percent float64, 224 idx index.Index, 225 ) (Monitor, error) { 226 if percent < 0 { 227 return nil, errNegativePercent 228 } else if percent > 1.0 { 229 return nil, errPercentTooLarge 230 } 231 232 return &indexMonitor{ 233 logger, 234 rt, 235 interval, 236 maxIngestedRuns, 237 maxHeapBytes, 238 percent, 239 false, 240 &sync.Mutex{}, 241 make(chan bool), 242 idx, 243 }, nil 244 }