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 }