blob: 721d7d96b9e0686a1a87f9c4719431e25f321ae2 [file] [log] [blame]
package common
import (
"flag"
"fmt"
"net/http"
"os"
"runtime"
"sort"
"github.com/skia-dev/glog"
"go.skia.org/infra/go/auth"
"go.skia.org/infra/go/httputils"
"go.skia.org/infra/go/influxdb_init"
"go.skia.org/infra/go/metrics2"
"go.skia.org/infra/go/sklog"
)
// Opt represents the initialization parameters for a single init service, where
// services are InfluxDB, Prometheus, etc.
//
// Initializing flags, metrics, and logging, with two options for metrics, and
// another option for logging is complicated by the fact that some
// initializations are order dependent, and each app may want a different
// subset of options. The solution is to encapsulate each optional piece,
// prom, influx, etc, into its own Opt, and then initialize each Opt in the
// right order.
//
// Not only are the Opts order dependent but initialization needs to be broken
// into two phases, preinit() and init().
//
// The desired order for all Opts is:
// 0 - base
// 1 - cloudlogging
// 2 - influx
// 3 - prometheus
//
// Construct the Opts that are desired and pass them to common.InitWith(), i.e.:
//
// common.InitWith(
// "skiaperf",
// common.InfluxOpt(influxHost, influxUser, influxPassword, influxDatabase, local),
// common.PrometheusOpt(promPort),
// common.CloudLoggingOpt(),
// )
//
type Opt interface {
// order is the sort order that Opts are executed in.
order() int
preinit(appName string) error
init(appName string) error
}
// optSlice is a utility type for sorting Opts by order().
type optSlice []Opt
func (p optSlice) Len() int { return len(p) }
func (p optSlice) Less(i, j int) bool { return p[i].order() < p[j].order() }
func (p optSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
// baseInitOpt is an Opt that is always constructed internally, added to any
// Opts passed into InitWith() and always runs first.
//
// Implements Opt.
type baseInitOpt struct{}
func (b *baseInitOpt) preinit(appName string) error {
flag.Parse()
glog.Info("base preinit")
return nil
}
func (b *baseInitOpt) init(appName string) error {
glog.Info("base init")
flag.VisitAll(func(f *flag.Flag) {
sklog.Infof("Flags: --%s=%v", f.Name, f.Value)
})
// See skbug.com/4386 for details on why the below section exists.
sklog.Info("Initializing logging for log level INFO.")
sklog.Warning("Initializing logging for log level WARNING.")
sklog.Error("Initializing logging for log level ERROR.")
// Use all cores.
runtime.GOMAXPROCS(runtime.NumCPU())
return nil
}
func (b *baseInitOpt) order() int {
return 0
}
// cloudLoggingInitOpt implements Opt for cloud logging.
type cloudLoggingInitOpt struct{}
// CloudLoggingOpt creates an Opt to initialize cloud logging when passed to InitWith().
func CloudLoggingOpt() Opt {
return &cloudLoggingInitOpt{}
}
func (o *cloudLoggingInitOpt) preinit(appName string) error {
glog.Info("cloudlogging preinit")
hostname, err := os.Hostname()
if err != nil {
return fmt.Errorf("Could not get hostname: %s", err)
}
return sklog.PreInitCloudLogging(hostname, appName)
}
func (o *cloudLoggingInitOpt) init(appName string) error {
glog.Info("cloudlogging init")
transport := &http.Transport{
Dial: httputils.FastDialTimeout,
}
c, err := auth.NewJWTServiceAccountClient("", "", transport, sklog.CLOUD_LOGGING_WRITE_SCOPE)
if err != nil {
return fmt.Errorf("Problem getting authenticated client: %s", err)
}
metricLookup := map[string]metrics2.Counter{}
for _, sev := range sklog.AllSeverities {
metricLookup[sev] = metrics2.GetCounter("num_log_lines", map[string]string{"level": sev, "log_source": appName})
}
metricsCallback := func(severity string) {
metricLookup[severity].Inc(1)
}
return sklog.PostInitCloudLogging(c, metricsCallback)
}
func (o *cloudLoggingInitOpt) order() int {
return 1
}
// influxInitOpt implements Opt for InfluxDB.
type influxInitOpt struct {
influxHost *string
influxUser *string
influxPassword *string
influxDatabase *string
skipMetadata *bool
}
// InfluxOpt creates an Opt to initialize Influx metrics when passed to InitWith().
func InfluxOpt(influxHost, influxUser, influxPassword, influxDatabase *string, skipMetadata *bool) Opt {
return &influxInitOpt{
influxHost: influxHost,
influxUser: influxUser,
influxPassword: influxPassword,
influxDatabase: influxDatabase,
skipMetadata: skipMetadata,
}
}
func (o *influxInitOpt) preinit(appName string) error {
glog.Info("influx preinit")
influxClient, err := influxdb_init.NewClientFromParamsAndMetadata(*o.influxHost, *o.influxUser, *o.influxPassword, *o.influxDatabase, *o.skipMetadata)
if err != nil {
return fmt.Errorf("Failed to create influx client: %s", err)
}
if err := metrics2.Init(appName, influxClient); err != nil {
return fmt.Errorf("Failed to init metrics2 for influx: %s", err)
}
metrics2.RuntimeMetrics()
return nil
}
func (o *influxInitOpt) init(appName string) error {
glog.Info("influx init")
return nil
}
func (o *influxInitOpt) order() int {
return 2
}
// promInitOpt implments Opt for Prometheus.
type promInitOpt struct {
port *string
}
// PrometheusOpt creates an Opt to initialize Prometheus metrics when passed to InitWith().
func PrometheusOpt(port *string) Opt {
return &promInitOpt{
port: port,
}
}
func (o *promInitOpt) preinit(appName string) error {
glog.Info("prom preinit")
return metrics2.InitPromMaybeInflux(*o.port)
}
func (o *promInitOpt) init(appName string) error {
glog.Info("prom init")
return nil
}
func (o *promInitOpt) order() int {
return 3
}
// InitWith takes Opt's and initializes each service, where services are InfluxDB, Prometheus, etc.
func InitWith(appName string, opts ...Opt) error {
// Add baseInitOpt.
opts = append(opts, &baseInitOpt{})
// Sort by order().
sort.Sort(optSlice(opts))
// Check for duplicate Opts.
for i := 0; i < len(opts)-1; i++ {
if opts[i].order() == opts[i+1].order() {
return fmt.Errorf("Only one of each type of Opt can be used.")
}
}
// Run all preinit's.
for _, o := range opts {
if err := o.preinit(appName); err != nil {
return err
}
}
// Run all init's.
for _, o := range opts {
if err := o.init(appName); err != nil {
return err
}
}
sklog.Flush()
return nil
}
// InitWithMust calls InitWith and fails fatally if an error is encountered.
func InitWithMust(appName string, opts ...Opt) {
if err := InitWith(appName, opts...); err != nil {
sklog.Fatalf("Failed to initialize: %s", err)
}
}