X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/4f4f6f5fe9367d5cd6c57070fd8a223efc87cb21..7466d0c1af1f7d9a0c1b4ae54e9f1bbc951f2711:/services/keepstore/config.go diff --git a/services/keepstore/config.go b/services/keepstore/config.go index 83dd84ecc0..17d6acdb68 100644 --- a/services/keepstore/config.go +++ b/services/keepstore/config.go @@ -1,3 +1,7 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + package main import ( @@ -5,11 +9,17 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/http" + "strconv" "strings" "time" "git.curoverse.com/arvados.git/sdk/go/arvados" - log "github.com/Sirupsen/logrus" + "git.curoverse.com/arvados.git/sdk/go/stats" + "github.com/Sirupsen/logrus" + "github.com/golang/protobuf/jsonpb" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" ) type Config struct { @@ -36,9 +46,25 @@ type Config struct { blobSigningKey []byte systemAuthToken string debugLogf func(string, ...interface{}) + + ManagementToken string + + metrics } -var theConfig = DefaultConfig() +var ( + theConfig = DefaultConfig() + formatter = map[string]logrus.Formatter{ + "text": &logrus.TextFormatter{ + FullTimestamp: true, + TimestampFormat: rfc3339NanoFixed, + }, + "json": &logrus.JSONFormatter{ + TimestampFormat: rfc3339NanoFixed, + }, + } + log = logrus.StandardLogger() +) const rfc3339NanoFixed = "2006-01-02T15:04:05.000000000Z07:00" @@ -60,26 +86,19 @@ func DefaultConfig() *Config { // fields, and before using the config. func (cfg *Config) Start() error { if cfg.Debug { - log.SetLevel(log.DebugLevel) + log.Level = logrus.DebugLevel cfg.debugLogf = log.Printf cfg.debugLogf("debugging enabled") } else { + log.Level = logrus.InfoLevel cfg.debugLogf = func(string, ...interface{}) {} } - switch strings.ToLower(cfg.LogFormat) { - case "text": - log.SetFormatter(&log.TextFormatter{ - FullTimestamp: true, - TimestampFormat: rfc3339NanoFixed, - }) - case "json": - log.SetFormatter(&log.JSONFormatter{ - TimestampFormat: rfc3339NanoFixed, - }) - default: + f := formatter[strings.ToLower(cfg.LogFormat)] + if f == nil { return fmt.Errorf(`unsupported log format %q (try "text" or "json")`, cfg.LogFormat) } + log.Formatter = f if cfg.MaxBuffers < 0 { return fmt.Errorf("MaxBuffers must be greater than zero") @@ -136,15 +155,71 @@ func (cfg *Config) Start() error { return nil } +type metrics struct { + registry *prometheus.Registry + reqDuration *prometheus.SummaryVec + timeToStatus *prometheus.SummaryVec + exportProm http.Handler +} + +func (*metrics) Levels() []logrus.Level { + return logrus.AllLevels +} + +func (m *metrics) Fire(ent *logrus.Entry) error { + if tts, ok := ent.Data["timeToStatus"].(stats.Duration); !ok { + } else if method, ok := ent.Data["reqMethod"].(string); !ok { + } else if code, ok := ent.Data["respStatusCode"].(int); !ok { + } else { + m.timeToStatus.WithLabelValues(strconv.Itoa(code), strings.ToLower(method)).Observe(time.Duration(tts).Seconds()) + } + return nil +} + +func (m *metrics) setup() { + m.registry = prometheus.NewRegistry() + m.timeToStatus = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "time_to_status_seconds", + Help: "Summary of request TTFB.", + }, []string{"code", "method"}) + m.reqDuration = prometheus.NewSummaryVec(prometheus.SummaryOpts{ + Name: "request_duration_seconds", + Help: "Summary of request duration.", + }, []string{"code", "method"}) + m.registry.MustRegister(m.timeToStatus) + m.registry.MustRegister(m.reqDuration) + m.exportProm = promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{ + ErrorLog: log, + }) + log.AddHook(m) +} + +func (m *metrics) exportJSON(w http.ResponseWriter, req *http.Request) { + jm := jsonpb.Marshaler{Indent: " "} + mfs, _ := m.registry.Gather() + w.Write([]byte{'['}) + for i, mf := range mfs { + if i > 0 { + w.Write([]byte{','}) + } + jm.Marshal(w, mf) + } + w.Write([]byte{']'}) +} + +func (m *metrics) Instrument(next http.Handler) http.Handler { + return promhttp.InstrumentHandlerDuration(m.reqDuration, next) +} + // VolumeTypes is built up by init() funcs in the source files that // define the volume types. var VolumeTypes = []func() VolumeWithExamples{} type VolumeList []Volume -// UnmarshalJSON, given an array of objects, deserializes each object -// as the volume type indicated by the object's Type field. -func (vols *VolumeList) UnmarshalJSON(data []byte) error { +// UnmarshalJSON -- given an array of objects -- deserializes each +// object as the volume type indicated by the object's Type field. +func (vl *VolumeList) UnmarshalJSON(data []byte) error { typeMap := map[string]func() VolumeWithExamples{} for _, factory := range VolumeTypes { t := factory().Type() @@ -177,7 +252,7 @@ func (vols *VolumeList) UnmarshalJSON(data []byte) error { if err != nil { return err } - *vols = append(*vols, vol) + *vl = append(*vl, vol) } return nil }