From 561508cae8c74ab7ee7819e8e327fe4c4750b711 Mon Sep 17 00:00:00 2001 From: Irine Sistiana <49315432+IrineSistiana@users.noreply.github.com> Date: Fri, 24 Jun 2022 21:35:22 +0800 Subject: [PATCH] coremain: add metrics support --- coremain/mosdns.go | 20 ++++++ coremain/register.go | 12 +++- go.mod | 1 + go.sum | 2 + pkg/metrics/metrics.go | 152 +++++++++++++++++++++++++++++++++++++++++ 5 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 pkg/metrics/metrics.go diff --git a/coremain/mosdns.go b/coremain/mosdns.go index bc02e0832..4155edba4 100644 --- a/coremain/mosdns.go +++ b/coremain/mosdns.go @@ -25,7 +25,9 @@ import ( "github.com/IrineSistiana/mosdns/v4/mlog" "github.com/IrineSistiana/mosdns/v4/pkg/data_provider" "github.com/IrineSistiana/mosdns/v4/pkg/executable_seq" + "github.com/IrineSistiana/mosdns/v4/pkg/metrics" "github.com/IrineSistiana/mosdns/v4/pkg/notifier" + "go.uber.org/zap" "net/http" "runtime" @@ -46,6 +48,10 @@ type Mosdns struct { httpAPIMux *http.ServeMux httpAPIServer *http.Server + rootMetricsReg *metrics.Registry + pluginsMetricsReg *metrics.Registry + serversMetricsReg *metrics.Registry + sc *notifier.SafeClose } @@ -63,6 +69,12 @@ func RunMosdns(cfg *Config) error { httpAPIMux: http.NewServeMux(), sc: notifier.NewSafeClose(), } + m.rootMetricsReg = metrics.NewRegistry() + m.pluginsMetricsReg = metrics.NewRegistry() + m.serversMetricsReg = metrics.NewRegistry() + m.rootMetricsReg.Set("plugins", m.pluginsMetricsReg) + m.rootMetricsReg.Set("servers", m.serversMetricsReg) + m.httpAPIMux.HandleFunc("/metrics/", metrics.HandleFunc(m.rootMetricsReg, m.logger)) // Init data manager dupTag := make(map[string]struct{}) @@ -178,6 +190,14 @@ func (m *Mosdns) GetExecutables() map[string]executable_seq.Executable { return m.execs } +func (m *Mosdns) GetPluginMetricsReg() *metrics.Registry { + return m.pluginsMetricsReg +} + +func (m *Mosdns) GetServerMetricsReg() *metrics.Registry { + return m.serversMetricsReg +} + func (m *Mosdns) GetMatchers() map[string]executable_seq.Matcher { return m.matchers } diff --git a/coremain/register.go b/coremain/register.go index 2148c28f0..fe2f0b2e6 100644 --- a/coremain/register.go +++ b/coremain/register.go @@ -21,6 +21,7 @@ package coremain import ( "fmt" + "github.com/IrineSistiana/mosdns/v4/pkg/metrics" "github.com/IrineSistiana/mosdns/v4/pkg/utils" "go.uber.org/zap" "sync" @@ -149,12 +150,15 @@ func LoadNewPersetPluginFuncs() map[string]NewPersetPluginFunc { // BP represents a basic plugin, which implements Plugin. // It also has an internal logger, for convenience. type BP struct { + Metrics metrics.Registry + tag, typ string l *zap.Logger s *zap.SugaredLogger - m *Mosdns + m *Mosdns + metricsReg *metrics.Registry } // NewBP creates a new BP and initials its logger. @@ -185,6 +189,12 @@ func (p *BP) M() *Mosdns { return p.m } +func (p *BP) GetMetricsReg() *metrics.Registry { + return p.m.pluginsMetricsReg.GetOrSet(p.tag, func() metrics.Var { + return metrics.NewRegistry() + }).(*metrics.Registry) +} + func (p *BP) Close() error { return nil } diff --git a/go.mod b/go.mod index 962d6d64e..75536196b 100644 --- a/go.mod +++ b/go.mod @@ -12,6 +12,7 @@ require ( github.com/miekg/dns v1.1.49 github.com/mitchellh/mapstructure v1.5.0 github.com/nadoo/ipset v0.5.0 + github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 github.com/spf13/cobra v1.4.0 github.com/spf13/viper v1.12.0 go.uber.org/zap v1.21.0 diff --git a/go.sum b/go.sum index 90ede186c..f41f5870f 100644 --- a/go.sum +++ b/go.sum @@ -287,6 +287,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 h1:N/ElC8H3+5XpJzTSTfLsJV/mx9Q9g7kxmchpfZyxgzM= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= diff --git a/pkg/metrics/metrics.go b/pkg/metrics/metrics.go new file mode 100644 index 000000000..d57c58df9 --- /dev/null +++ b/pkg/metrics/metrics.go @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2020-2022, IrineSistiana + * + * This file is part of mosdns. + * + * mosdns is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * mosdns is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package metrics + +import ( + "encoding/json" + "github.com/rcrowley/go-metrics" + "go.uber.org/zap" + "net/http" + "sync" +) + +type Var interface { + // Publish should return a build-in type or a map[string]interface{} + // of build-in types. + Publish() interface{} +} + +type Registry struct { + l sync.RWMutex + m map[string]Var +} + +func NewRegistry() *Registry { + return &Registry{ + m: make(map[string]Var), + } +} + +func (r *Registry) Get(name string) Var { + r.l.RLock() + defer r.l.RUnlock() + return r.m[name] +} + +func (r *Registry) GetOrSet(name string, f func() Var) Var { + r.l.Lock() + defer r.l.Unlock() + v, ok := r.m[name] + if !ok { + v = f() + r.m[name] = v + } + return v +} + +func (r *Registry) Set(name string, v Var) { + r.l.Lock() + defer r.l.Unlock() + r.m[name] = v +} + +func (r *Registry) Publish() interface{} { + m := make(map[string]interface{}) + r.l.RLock() + defer r.l.RUnlock() + for name, v := range r.m { + m[name] = v.Publish() + } + return m +} + +type Histogram struct { + metrics.Histogram +} + +func NewHistogram(reservoirSize int) *Histogram { + return &Histogram{Histogram: metrics.NewHistogram(metrics.NewUniformSample(reservoirSize))} +} + +func (h *Histogram) Publish() interface{} { + m := make(map[string]interface{}) + ps := h.Percentiles([]float64{0, 0.25, 0.5, 0.75, 1}) + m["min"] = ps[0] + m["p25"] = ps[1] + m["p50"] = ps[2] + m["p75"] = ps[3] + m["max"] = ps[4] + m["avg"] = int64(h.Mean()) + return m +} + +type Counter struct { + metrics.Counter +} + +func NewCounter() *Counter { + return &Counter{Counter: metrics.NewCounter()} +} + +func (c *Counter) Publish() interface{} { + return c.Count() +} + +type Gauge struct { + metrics.Gauge +} + +func NewGauge() *Gauge { + return &Gauge{Gauge: metrics.NewGauge()} +} + +type GaugeFunc struct { + l sync.Mutex + f func() int64 +} + +func NewGaugeFunc(f func() int64) *GaugeFunc { + return &GaugeFunc{f: f} +} + +func (gf *GaugeFunc) Publish() interface{} { + gf.l.Lock() + defer gf.l.Unlock() + return gf.f() +} + +func (g *Gauge) Publish() interface{} { + return g.Value() +} + +func HandleFunc(r Var, lg *zap.Logger) http.HandlerFunc { + return func(w http.ResponseWriter, req *http.Request) { + b, err := json.MarshalIndent(r.Publish(), "", " ") + if err != nil { + w.WriteHeader(http.StatusInternalServerError) + w.Write([]byte(err.Error())) + lg.Error("failed to encode json", zap.Error(err)) + return + } + if _, err := w.Write(b); err != nil { + lg.Error("http write err", zap.Error(err)) + } + } +}