go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/tsmon/iface.go (about)

     1  // Copyright 2015 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  	"net/url"
    21  	"strings"
    22  
    23  	"go.chromium.org/luci/auth"
    24  	"go.chromium.org/luci/common/errors"
    25  	"go.chromium.org/luci/common/logging"
    26  	"go.chromium.org/luci/common/tsmon/monitor"
    27  	"go.chromium.org/luci/common/tsmon/store"
    28  	"go.chromium.org/luci/common/tsmon/target"
    29  
    30  	"go.chromium.org/luci/hardcoded/chromeinfra"
    31  )
    32  
    33  // Store returns the global metric store that contains all the metric values for
    34  // this process.  Applications shouldn't need to access this directly - instead
    35  // use the metric objects which provide type-safe accessors.
    36  func Store(ctx context.Context) store.Store {
    37  	return GetState(ctx).Store()
    38  }
    39  
    40  // Monitor returns the global monitor that sends metrics to monitoring
    41  // endpoints.  Defaults to a nil monitor, but changed by InitializeFromFlags.
    42  func Monitor(ctx context.Context) monitor.Monitor {
    43  	return GetState(ctx).Monitor()
    44  }
    45  
    46  // SetStore changes the global metric store.  All metrics that were registered
    47  // with the old store will be re-registered on the new store.
    48  func SetStore(ctx context.Context, s store.Store) {
    49  	GetState(ctx).SetStore(s)
    50  }
    51  
    52  // InitializeFromFlags configures the tsmon library from flag values.
    53  //
    54  // This will set a Target (information about what's reporting metrics) and a
    55  // Monitor (where to send the metrics to).
    56  func InitializeFromFlags(ctx context.Context, fl *Flags) error {
    57  	// Load the config file, and override its values with flags.
    58  	cfg, err := loadConfig(fl.ConfigFile)
    59  	if err != nil {
    60  		return errors.Annotate(err, "failed to load config file at [%s]", fl.ConfigFile).Err()
    61  	}
    62  
    63  	if fl.Endpoint != "" {
    64  		cfg.Endpoint = fl.Endpoint
    65  	}
    66  	if fl.Credentials != "" {
    67  		cfg.Credentials = fl.Credentials
    68  	}
    69  	if fl.ActAs != "" {
    70  		cfg.ActAs = fl.ActAs
    71  	}
    72  
    73  	mon, err := initMonitor(ctx, cfg)
    74  	switch {
    75  	case err != nil:
    76  		return errors.Annotate(err, "failed to initialize monitor").Err()
    77  	case mon == nil:
    78  		return nil // tsmon is disabled
    79  	}
    80  
    81  	// Monitoring is enabled, so get the expensive default values for hostname,
    82  	// etc.
    83  	if cfg.AutoGenHostname {
    84  		fl.Target.AutoGenHostname = true
    85  	}
    86  	if cfg.Hostname != "" {
    87  		if fl.Target.DeviceHostname == "" {
    88  			fl.Target.DeviceHostname = cfg.Hostname
    89  		}
    90  		if fl.Target.TaskHostname == "" {
    91  			fl.Target.TaskHostname = cfg.Hostname
    92  		}
    93  	}
    94  	if cfg.Region != "" {
    95  		if fl.Target.DeviceRegion == "" {
    96  			fl.Target.DeviceRegion = cfg.Region
    97  		}
    98  		if fl.Target.TaskRegion == "" {
    99  			fl.Target.TaskRegion = cfg.Region
   100  		}
   101  	}
   102  	fl.Target.SetDefaultsFromHostname()
   103  	t, err := target.NewFromFlags(&fl.Target)
   104  	if err != nil {
   105  		return errors.Annotate(err, "failed to configure target from flags").Err()
   106  	}
   107  
   108  	Initialize(ctx, mon, store.NewInMemory(t))
   109  
   110  	state := GetState(ctx)
   111  	if state.flusher != nil {
   112  		logging.Infof(ctx, "Canceling previous tsmon auto flush")
   113  		state.flusher.stop()
   114  		state.flusher = nil
   115  	}
   116  
   117  	if fl.Flush == FlushAuto {
   118  		state.flusher = &autoFlusher{}
   119  		state.flusher.start(ctx, fl.FlushInterval)
   120  	}
   121  
   122  	return nil
   123  }
   124  
   125  // Initialize configures the tsmon library with the given monitor and store.
   126  func Initialize(ctx context.Context, m monitor.Monitor, s store.Store) {
   127  	state := GetState(ctx)
   128  	state.SetMonitor(m)
   129  	state.SetStore(s)
   130  }
   131  
   132  // Shutdown gracefully terminates the tsmon by doing the final flush and
   133  // disabling auto flush (if it was enabled).
   134  //
   135  // It resets Monitor and Store.
   136  //
   137  // Logs error to standard logger. Does nothing if tsmon wasn't initialized.
   138  func Shutdown(ctx context.Context) {
   139  	state := GetState(ctx)
   140  	if store.IsNilStore(state.Store()) {
   141  		return
   142  	}
   143  
   144  	if state.flusher != nil {
   145  		logging.Debugf(ctx, "Stopping tsmon auto flush")
   146  		state.flusher.stop()
   147  		state.flusher = nil
   148  	}
   149  
   150  	// Flush logs errors inside.
   151  	Flush(ctx)
   152  
   153  	// Reset the state as if 'InitializeFromFlags' was never called.
   154  	Initialize(ctx, monitor.NewNilMonitor(), store.NewNilStore())
   155  }
   156  
   157  // ResetCumulativeMetrics resets only cumulative metrics.
   158  func ResetCumulativeMetrics(ctx context.Context) {
   159  	GetState(ctx).ResetCumulativeMetrics(ctx)
   160  }
   161  
   162  // initMonitor examines flags and config and initializes a monitor.
   163  //
   164  // It returns (nil, nil) if tsmon should be disabled.
   165  func initMonitor(ctx context.Context, cfg config) (monitor.Monitor, error) {
   166  	if cfg.Endpoint == "" {
   167  		logging.Infof(ctx, "tsmon is disabled because no endpoint is configured")
   168  		return nil, nil
   169  	}
   170  	if strings.ToLower(cfg.Endpoint) == "none" {
   171  		logging.Infof(ctx, "tsmon is explicitly disabled")
   172  		return nil, nil
   173  	}
   174  
   175  	endpointURL, err := url.Parse(cfg.Endpoint)
   176  	if err != nil {
   177  		return nil, err
   178  	}
   179  
   180  	switch endpointURL.Scheme {
   181  	case "file":
   182  		return monitor.NewDebugMonitor(endpointURL.Path), nil
   183  	case "http", "https":
   184  		client, err := newAuthenticator(ctx, cfg.Credentials, cfg.ActAs, monitor.ProdxmonScopes).Client()
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  
   189  		return monitor.NewHTTPMonitor(ctx, client, endpointURL)
   190  	default:
   191  		return nil, fmt.Errorf("unknown tsmon endpoint url: %s", cfg.Endpoint)
   192  	}
   193  }
   194  
   195  // newAuthenticator returns a new authenticator for HTTP requests.
   196  func newAuthenticator(ctx context.Context, credentials, actAs string, scopes []string) *auth.Authenticator {
   197  	// TODO(vadimsh): Don't hardcode auth options here, pass them from outside
   198  	// somehow.
   199  	authOpts := chromeinfra.DefaultAuthOptions()
   200  	authOpts.ServiceAccountJSONPath = credentials
   201  	authOpts.Scopes = scopes
   202  	authOpts.ActAsServiceAccount = actAs
   203  	return auth.NewAuthenticator(ctx, auth.SilentLogin, authOpts)
   204  }