blob: 0adb5f83db46fee206b4667bf4d9ad0c68512e42 [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tsmon
import (
"context"
"fmt"
"net/url"
"strings"
"go.chromium.org/luci/auth"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/common/tsmon/monitor"
"go.chromium.org/luci/common/tsmon/store"
"go.chromium.org/luci/common/tsmon/target"
"go.chromium.org/luci/hardcoded/chromeinfra"
)
// Store returns the global metric store that contains all the metric values for
// this process. Applications shouldn't need to access this directly - instead
// use the metric objects which provide type-safe accessors.
func Store(c context.Context) store.Store {
return GetState(c).Store()
}
// Monitor returns the global monitor that sends metrics to monitoring
// endpoints. Defaults to a nil monitor, but changed by InitializeFromFlags.
func Monitor(c context.Context) monitor.Monitor {
return GetState(c).Monitor()
}
// SetStore changes the global metric store. All metrics that were registered
// with the old store will be re-registered on the new store.
func SetStore(c context.Context, s store.Store) {
GetState(c).SetStore(s)
}
// InitializeFromFlags configures the tsmon library from flag values.
//
// This will set a Target (information about what's reporting metrics) and a
// Monitor (where to send the metrics to).
func InitializeFromFlags(c context.Context, fl *Flags) error {
// Load the config file, and override its values with flags.
cfg, err := loadConfig(fl.ConfigFile)
if err != nil {
return errors.Annotate(err, "failed to load config file at [%s]", fl.ConfigFile).Err()
}
if fl.Endpoint != "" {
cfg.Endpoint = fl.Endpoint
}
if fl.Credentials != "" {
cfg.Credentials = fl.Credentials
}
if fl.ActAs != "" {
cfg.ActAs = fl.ActAs
}
mon, err := initMonitor(c, cfg)
switch {
case err != nil:
return errors.Annotate(err, "failed to initialize monitor").Err()
case mon == nil:
return nil // tsmon is disabled
}
// Monitoring is enabled, so get the expensive default values for hostname,
// etc.
if cfg.AutoGenHostname {
fl.Target.AutoGenHostname = true
}
if cfg.Hostname != "" {
if fl.Target.DeviceHostname == "" {
fl.Target.DeviceHostname = cfg.Hostname
}
if fl.Target.TaskHostname == "" {
fl.Target.TaskHostname = cfg.Hostname
}
}
if cfg.Region != "" {
if fl.Target.DeviceRegion == "" {
fl.Target.DeviceRegion = cfg.Region
}
if fl.Target.TaskRegion == "" {
fl.Target.TaskRegion = cfg.Region
}
}
fl.Target.SetDefaultsFromHostname()
t, err := target.NewFromFlags(&fl.Target)
if err != nil {
return errors.Annotate(err, "failed to configure target from flags").Err()
}
Initialize(c, mon, store.NewInMemory(t))
state := GetState(c)
if state.flusher != nil {
logging.Infof(c, "Canceling previous tsmon auto flush")
state.flusher.stop()
state.flusher = nil
}
if fl.Flush == FlushAuto {
state.flusher = &autoFlusher{}
state.flusher.start(c, fl.FlushInterval)
}
return nil
}
// Initialize configures the tsmon library with the given monitor and store.
func Initialize(c context.Context, m monitor.Monitor, s store.Store) {
state := GetState(c)
state.SetMonitor(m)
state.SetStore(s)
}
// Shutdown gracefully terminates the tsmon by doing the final flush and
// disabling auto flush (if it was enabled).
//
// It resets Monitor and Store.
//
// Logs error to standard logger. Does nothing if tsmon wasn't initialized.
func Shutdown(c context.Context) {
state := GetState(c)
if store.IsNilStore(state.Store()) {
return
}
if state.flusher != nil {
logging.Debugf(c, "Stopping tsmon auto flush")
state.flusher.stop()
state.flusher = nil
}
// Flush logs errors inside.
Flush(c)
// Reset the state as if 'InitializeFromFlags' was never called.
Initialize(c, monitor.NewNilMonitor(), store.NewNilStore())
}
// ResetCumulativeMetrics resets only cumulative metrics.
func ResetCumulativeMetrics(c context.Context) {
GetState(c).ResetCumulativeMetrics(c)
}
// initMonitor examines flags and config and initializes a monitor.
//
// It returns (nil, nil) if tsmon should be disabled.
func initMonitor(c context.Context, cfg config) (monitor.Monitor, error) {
if cfg.Endpoint == "" {
logging.Infof(c, "tsmon is disabled because no endpoint is configured")
return nil, nil
}
if strings.ToLower(cfg.Endpoint) == "none" {
logging.Infof(c, "tsmon is explicitly disabled")
return nil, nil
}
endpointURL, err := url.Parse(cfg.Endpoint)
if err != nil {
return nil, err
}
switch endpointURL.Scheme {
case "file":
return monitor.NewDebugMonitor(endpointURL.Path), nil
case "http", "https":
client, err := newAuthenticator(c, cfg.Credentials, cfg.ActAs, monitor.ProdxmonScopes).Client()
if err != nil {
return nil, err
}
return monitor.NewHTTPMonitor(c, client, endpointURL)
default:
return nil, fmt.Errorf("unknown tsmon endpoint url: %s", cfg.Endpoint)
}
}
// newAuthenticator returns a new authenticator for HTTP requests.
func newAuthenticator(ctx context.Context, credentials, actAs string, scopes []string) *auth.Authenticator {
// TODO(vadimsh): Don't hardcode auth options here, pass them from outside
// somehow.
authOpts := chromeinfra.DefaultAuthOptions()
authOpts.ServiceAccountJSONPath = credentials
authOpts.Scopes = scopes
authOpts.ActAsServiceAccount = actAs
return auth.NewAuthenticator(ctx, auth.SilentLogin, authOpts)
}