github.com/slackhq/nebula@v1.9.0/stats.go (about) 1 package nebula 2 3 import ( 4 "errors" 5 "fmt" 6 "log" 7 "net" 8 "net/http" 9 "runtime" 10 "strconv" 11 "time" 12 13 graphite "github.com/cyberdelia/go-metrics-graphite" 14 mp "github.com/nbrownus/go-metrics-prometheus" 15 "github.com/prometheus/client_golang/prometheus" 16 "github.com/prometheus/client_golang/prometheus/promhttp" 17 "github.com/rcrowley/go-metrics" 18 "github.com/sirupsen/logrus" 19 "github.com/slackhq/nebula/config" 20 ) 21 22 // startStats initializes stats from config. On success, if any further work 23 // is needed to serve stats, it returns a func to handle that work. If no 24 // work is needed, it'll return nil. On failure, it returns nil, error. 25 func startStats(l *logrus.Logger, c *config.C, buildVersion string, configTest bool) (func(), error) { 26 mType := c.GetString("stats.type", "") 27 if mType == "" || mType == "none" { 28 return nil, nil 29 } 30 31 interval := c.GetDuration("stats.interval", 0) 32 if interval == 0 { 33 return nil, fmt.Errorf("stats.interval was an invalid duration: %s", c.GetString("stats.interval", "")) 34 } 35 36 var startFn func() 37 switch mType { 38 case "graphite": 39 err := startGraphiteStats(l, interval, c, configTest) 40 if err != nil { 41 return nil, err 42 } 43 case "prometheus": 44 var err error 45 startFn, err = startPrometheusStats(l, interval, c, buildVersion, configTest) 46 if err != nil { 47 return nil, err 48 } 49 default: 50 return nil, fmt.Errorf("stats.type was not understood: %s", mType) 51 } 52 53 metrics.RegisterDebugGCStats(metrics.DefaultRegistry) 54 metrics.RegisterRuntimeMemStats(metrics.DefaultRegistry) 55 56 go metrics.CaptureDebugGCStats(metrics.DefaultRegistry, interval) 57 go metrics.CaptureRuntimeMemStats(metrics.DefaultRegistry, interval) 58 59 return startFn, nil 60 } 61 62 func startGraphiteStats(l *logrus.Logger, i time.Duration, c *config.C, configTest bool) error { 63 proto := c.GetString("stats.protocol", "tcp") 64 host := c.GetString("stats.host", "") 65 if host == "" { 66 return errors.New("stats.host can not be empty") 67 } 68 69 prefix := c.GetString("stats.prefix", "nebula") 70 addr, err := net.ResolveTCPAddr(proto, host) 71 if err != nil { 72 return fmt.Errorf("error while setting up graphite sink: %s", err) 73 } 74 75 if !configTest { 76 l.Infof("Starting graphite. Interval: %s, prefix: %s, addr: %s", i, prefix, addr) 77 go graphite.Graphite(metrics.DefaultRegistry, i, prefix, addr) 78 } 79 return nil 80 } 81 82 func startPrometheusStats(l *logrus.Logger, i time.Duration, c *config.C, buildVersion string, configTest bool) (func(), error) { 83 namespace := c.GetString("stats.namespace", "") 84 subsystem := c.GetString("stats.subsystem", "") 85 86 listen := c.GetString("stats.listen", "") 87 if listen == "" { 88 return nil, fmt.Errorf("stats.listen should not be empty") 89 } 90 91 path := c.GetString("stats.path", "") 92 if path == "" { 93 return nil, fmt.Errorf("stats.path should not be empty") 94 } 95 96 pr := prometheus.NewRegistry() 97 pClient := mp.NewPrometheusProvider(metrics.DefaultRegistry, namespace, subsystem, pr, i) 98 if !configTest { 99 go pClient.UpdatePrometheusMetrics() 100 } 101 102 // Export our version information as labels on a static gauge 103 g := prometheus.NewGauge(prometheus.GaugeOpts{ 104 Namespace: namespace, 105 Subsystem: subsystem, 106 Name: "info", 107 Help: "Version information for the Nebula binary", 108 ConstLabels: prometheus.Labels{ 109 "version": buildVersion, 110 "goversion": runtime.Version(), 111 "boringcrypto": strconv.FormatBool(boringEnabled()), 112 }, 113 }) 114 pr.MustRegister(g) 115 g.Set(1) 116 117 var startFn func() 118 if !configTest { 119 startFn = func() { 120 l.Infof("Prometheus stats listening on %s at %s", listen, path) 121 http.Handle(path, promhttp.HandlerFor(pr, promhttp.HandlerOpts{ErrorLog: l})) 122 log.Fatal(http.ListenAndServe(listen, nil)) 123 } 124 } 125 126 return startFn, nil 127 }