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  }