go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/tsmon/state.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package tsmon 16 17 import ( 18 "context" 19 "fmt" 20 "sync" 21 "sync/atomic" 22 23 "go.chromium.org/luci/common/errors" 24 "go.chromium.org/luci/common/sync/parallel" 25 26 "go.chromium.org/luci/common/tsmon/monitor" 27 "go.chromium.org/luci/common/tsmon/registry" 28 "go.chromium.org/luci/common/tsmon/store" 29 "go.chromium.org/luci/common/tsmon/types" 30 ) 31 32 // State holds the configuration of the tsmon library. There is one global 33 // instance of State, but it can be overridden in a Context by tests. 34 type State struct { 35 mu sync.RWMutex 36 store store.Store 37 monitor monitor.Monitor 38 flusher *autoFlusher 39 callbacks []Callback 40 globalCallbacks []GlobalCallback 41 invokeGlobalCallbacksOnFlush int32 42 } 43 44 // NewState returns a new State instance, configured with a nil store and nil 45 // monitor. By default, global callbacks that are registered will be invoked 46 // when flushing registered metrics. 47 func NewState() *State { 48 return &State{ 49 store: store.NewNilStore(), 50 monitor: monitor.NewNilMonitor(), 51 invokeGlobalCallbacksOnFlush: 1, 52 } 53 } 54 55 var globalState = NewState() 56 57 // GetState returns the State instance held in the context (if set) or else 58 // returns the global State. 59 func GetState(ctx context.Context) *State { 60 return stateFromContext(ctx) 61 } 62 63 // Callbacks returns all registered Callbacks. 64 func (s *State) Callbacks() []Callback { 65 s.mu.RLock() 66 defer s.mu.RUnlock() 67 68 return append([]Callback{}, s.callbacks...) 69 } 70 71 // GlobalCallbacks returns all registered GlobalCallbacks. 72 func (s *State) GlobalCallbacks() []GlobalCallback { 73 s.mu.RLock() 74 defer s.mu.RUnlock() 75 76 return append([]GlobalCallback{}, s.globalCallbacks...) 77 } 78 79 // InhibitGlobalCallbacksOnFlush signals that the registered global callbacks 80 // are not to be executed upon flushing registered metrics. 81 func (s *State) InhibitGlobalCallbacksOnFlush() { 82 atomic.StoreInt32(&s.invokeGlobalCallbacksOnFlush, 0) 83 } 84 85 // InvokeGlobalCallbacksOnFlush signals that the registered global callbacks 86 // are to be be executed upon flushing registered metrics. 87 func (s *State) InvokeGlobalCallbacksOnFlush() { 88 atomic.StoreInt32(&s.invokeGlobalCallbacksOnFlush, 1) 89 } 90 91 // Monitor returns the State's monitor. 92 func (s *State) Monitor() monitor.Monitor { 93 s.mu.RLock() 94 defer s.mu.RUnlock() 95 96 return s.monitor 97 } 98 99 // RegisterCallbacks registers the given Callback(s) with State. 100 func (s *State) RegisterCallbacks(f ...Callback) { 101 s.mu.Lock() 102 defer s.mu.Unlock() 103 104 s.callbacks = append(s.callbacks, f...) 105 } 106 107 // RegisterGlobalCallbacks registers the given GlobalCallback(s) with State. 108 func (s *State) RegisterGlobalCallbacks(f ...GlobalCallback) { 109 s.mu.Lock() 110 defer s.mu.Unlock() 111 112 s.globalCallbacks = append(s.globalCallbacks, f...) 113 } 114 115 // Store returns the State's store. 116 func (s *State) Store() store.Store { 117 s.mu.RLock() 118 defer s.mu.RUnlock() 119 120 return s.store 121 } 122 123 // SetMonitor sets the Store's monitor. 124 func (s *State) SetMonitor(m monitor.Monitor) { 125 s.mu.Lock() 126 defer s.mu.Unlock() 127 128 s.monitor = m 129 } 130 131 // SetStore changes the metric store. All metrics that were registered with 132 // the old store will be re-registered on the new store. 133 func (s *State) SetStore(st store.Store) { 134 s.mu.Lock() 135 defer s.mu.Unlock() 136 137 s.store = st 138 } 139 140 // ResetCumulativeMetrics resets only cumulative metrics. 141 func (s *State) ResetCumulativeMetrics(ctx context.Context) { 142 store := s.Store() 143 144 registry.Iter(func(m types.Metric) { 145 if m.Info().ValueType.IsCumulative() { 146 store.Reset(ctx, m) 147 } 148 }) 149 } 150 151 // RunGlobalCallbacks runs all registered global callbacks that produce global 152 // metrics. 153 // 154 // See RegisterGlobalCallback for more info. 155 func (s *State) RunGlobalCallbacks(ctx context.Context) { 156 for _, cb := range s.GlobalCallbacks() { 157 cb.Callback(ctx) 158 } 159 } 160 161 // Flush sends all the metrics that are registered in the application. 162 // 163 // Uses given monitor if not nil, otherwise the State's current monitor. 164 func (s *State) Flush(ctx context.Context, mon monitor.Monitor) error { 165 return s.ParallelFlush(ctx, mon, 1) 166 } 167 168 // ParallelFlush is similar to Flush, but sends multiple requests in parallel. 169 // 170 // This can be useful to optimize the total duration of flushing for an excessive 171 // number of cells. 172 // 173 // Panics if workers < 1. 174 func (s *State) ParallelFlush(ctx context.Context, mon monitor.Monitor, workers int) error { 175 if mon == nil { 176 mon = s.Monitor() 177 } 178 if mon == nil { 179 return errors.New("no tsmon Monitor is configured") 180 } 181 if workers < 1 { 182 panic(fmt.Errorf("tsmon.ParallelFlush: invalid # of workers(%d)", workers)) 183 } 184 185 // Run any callbacks that have been registered to populate values in callback 186 // metrics. 187 s.runCallbacks(ctx) 188 if atomic.LoadInt32(&s.invokeGlobalCallbacksOnFlush) != 0 { 189 s.RunGlobalCallbacks(ctx) 190 } 191 192 cells := s.Store().GetAll(ctx) 193 if len(cells) == 0 { 194 return nil 195 } 196 // Split up the payload into chunks if there are too many cells. 197 chunkSize := mon.ChunkSize() 198 if chunkSize == 0 { 199 chunkSize = len(cells) 200 } 201 err := parallel.WorkPool(workers, func(taskC chan<- func() error) { 202 for s := 0; s < len(cells); s += chunkSize { 203 start, end := s, s+chunkSize 204 if end > len(cells) { 205 end = len(cells) 206 } 207 taskC <- func() error { 208 return mon.Send(ctx, cells[start:end]) 209 } 210 } 211 }) 212 s.resetGlobalCallbackMetrics(ctx) 213 return err 214 } 215 216 // resetGlobalCallbackMetrics resets metrics produced by global callbacks. 217 // 218 // See RegisterGlobalCallback for more info. 219 func (s *State) resetGlobalCallbackMetrics(ctx context.Context) { 220 store := s.Store() 221 222 for _, cb := range s.GlobalCallbacks() { 223 for _, m := range cb.metrics { 224 store.Reset(ctx, m) 225 } 226 } 227 } 228 229 // runCallbacks runs any callbacks that have been registered to populate values 230 // in callback metrics. 231 func (s *State) runCallbacks(ctx context.Context) { 232 for _, cb := range s.Callbacks() { 233 cb(ctx) 234 } 235 }