diff options
Diffstat (limited to 'plugins/metrics/plugin.go')
-rw-r--r-- | plugins/metrics/plugin.go | 193 |
1 files changed, 193 insertions, 0 deletions
diff --git a/plugins/metrics/plugin.go b/plugins/metrics/plugin.go new file mode 100644 index 00000000..b9b79d95 --- /dev/null +++ b/plugins/metrics/plugin.go @@ -0,0 +1,193 @@ +package metrics + +// todo: declare metric at runtime + +import ( + "context" + "crypto/tls" + "net/http" + "sync" + "time" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + "github.com/spiral/errors" + "github.com/spiral/roadrunner/v2/log" + "github.com/spiral/roadrunner/v2/metrics" + "golang.org/x/sys/cpu" +) + +const ( + // ID declares public service name. + ID = "metrics" + // maxHeaderSize declares max header size for prometheus server + maxHeaderSize = 1024 * 1024 * 100 // 104MB +) + +// Plugin to manage application metrics using Prometheus. +type Plugin struct { + cfg Config + log log.Logger + mu sync.Mutex // all receivers are pointers + http *http.Server + collectors []prometheus.Collector //sync.Map // all receivers are pointers + registry *prometheus.Registry +} + +// Init service. +func (m *Plugin) Init(cfg Config, log log.Logger) (bool, error) { + m.cfg = cfg + m.log = log + m.registry = prometheus.NewRegistry() + + m.registry.MustRegister(prometheus.NewProcessCollector(prometheus.ProcessCollectorOpts{})) + m.registry.MustRegister(prometheus.NewGoCollector()) + + //if r != nil { + // if err := r.Register(ID, &rpcServer{s}); err != nil { + // return false, err + // } + //} + + return true, nil +} + +// Enabled indicates that server is able to collect metrics. +//func (m *Plugin) Enabled() bool { +// return m.cfg != nil +//} +// +//// Register new prometheus collector. +//func (m *Plugin) Register(c prometheus.Collector) error { +// return m.registry.Register(c) +//} + +// MustRegister registers new collector or fails with panic. +func (m *Plugin) MustRegister(c prometheus.Collector) { + m.registry.MustRegister(c) +} + +// Serve prometheus metrics service. +func (m *Plugin) Serve() error { + // register application specific metrics + collectors, err := m.cfg.getCollectors() + if err != nil { + return err + } + + for name, collector := range collectors { + if err := m.registry.Register(collector); err != nil { + return err + } + + m.collectors.Store(name, collector) + } + + m.mu.Lock() + + var topCipherSuites []uint16 + var defaultCipherSuitesTLS13 []uint16 + + hasGCMAsmAMD64 := cpu.X86.HasAES && cpu.X86.HasPCLMULQDQ + hasGCMAsmARM64 := cpu.ARM64.HasAES && cpu.ARM64.HasPMULL + // Keep in sync with crypto/aes/cipher_s390x.go. + hasGCMAsmS390X := cpu.S390X.HasAES && cpu.S390X.HasAESCBC && cpu.S390X.HasAESCTR && (cpu.S390X.HasGHASH || cpu.S390X.HasAESGCM) + + hasGCMAsm := hasGCMAsmAMD64 || hasGCMAsmARM64 || hasGCMAsmS390X + + if hasGCMAsm { + // If AES-GCM hardware is provided then prioritise AES-GCM + // cipher suites. + topCipherSuites = []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + } + defaultCipherSuitesTLS13 = []uint16{ + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_AES_256_GCM_SHA384, + } + } else { + // Without AES-GCM hardware, we put the ChaCha20-Poly1305 + // cipher suites first. + topCipherSuites = []uint16{ + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, + } + defaultCipherSuitesTLS13 = []uint16{ + tls.TLS_CHACHA20_POLY1305_SHA256, + tls.TLS_AES_128_GCM_SHA256, + tls.TLS_AES_256_GCM_SHA384, + } + } + + DefaultCipherSuites := make([]uint16, 0, 22) + DefaultCipherSuites = append(DefaultCipherSuites, topCipherSuites...) + DefaultCipherSuites = append(DefaultCipherSuites, defaultCipherSuitesTLS13...) + + m.http = &http.Server{ + Addr: m.cfg.Address, + Handler: promhttp.HandlerFor(m.registry, promhttp.HandlerOpts{}), + IdleTimeout: time.Hour * 24, + ReadTimeout: time.Minute * 60, + MaxHeaderBytes: maxHeaderSize, + ReadHeaderTimeout: time.Minute * 60, + WriteTimeout: time.Minute * 60, + TLSConfig: &tls.Config{ + CurvePreferences: []tls.CurveID{ + tls.CurveP256, + tls.CurveP384, + tls.CurveP521, + tls.X25519, + }, + CipherSuites: DefaultCipherSuites, + MinVersion: tls.VersionTLS12, + PreferServerCipherSuites: true, + }, + } + m.mu.Unlock() + + err = m.http.ListenAndServe() + if err != nil && err != http.ErrServerClosed { + return err + } + + return nil +} + +// Stop prometheus metrics service. +func (m *Plugin) Stop() { + m.mu.Lock() + defer m.mu.Unlock() + + if m.http != nil { + // gracefully stop server + go func() { + err := m.http.Shutdown(context.Background()) + if err != nil { + // Function should be Stop() error + m.log.Error("stop error", "error", errors.Errorf("error shutting down the metrics server: error %v", err)) + } + }() + } +} + +func (m *Plugin) Collects() []interface{} { + return []interface{}{ + m.Register, + } +} + +// Collector returns application specific collector by name or nil if collector not found. +func (m *Plugin) Register(stat metrics.StatProvider) error { + m.collectors = append(m.collectors, stat.MetricsCollector()) + return nil +} |