squash merge from dev branch

This commit is contained in:
Steve Brunton
2017-09-04 22:52:14 -04:00
parent 123cd935a3
commit ed916703c6
1058 changed files with 311598 additions and 3 deletions

32
exporter/config.go Normal file
View File

@@ -0,0 +1,32 @@
package exporter
import (
"fmt"
"go.uber.org/zap"
)
type Config struct {
Devices []Device
Logger *zap.SugaredLogger
Metrics PromMetrics
}
func (c *Config) FromFlags(device, address, user, password *string) error {
if *device == "" || *address == "" || *user == "" || *password == "" {
return fmt.Errorf("missing required param for single device configuration")
}
d := &Device{
Address: *address,
Name: *device,
User: *user,
Password: *password,
}
*c = Config{
Devices: []Device{*d},
}
return nil
}

81
exporter/device.go Normal file
View File

@@ -0,0 +1,81 @@
package exporter
import (
"go.uber.org/zap"
"gopkg.in/routeros.v2"
"strconv"
"strings"
)
const (
apiPort = ":8728"
)
type Device struct {
Address string
Name string
User string
Password string
}
func (d *Device) fetchInterfaceMetrics(c *routeros.Client, m PromMetrics, l *zap.SugaredLogger) error {
l.Debugw("fetching interface metrics",
"device", d.Name,
)
reply, err := c.Run("/interface/print", "?disabled=false",
"?running=true", "=.proplist="+strings.Join(InterfaceProps, ","))
if err != nil {
return err
}
for _, re := range reply.Re {
var name string
// name should always be first element on the array
for _, p := range InterfaceProps {
if p == "name" {
name = re.Map[p]
} else {
v, err := strconv.ParseFloat(re.Map[p], 64)
if err != nil {
l.Errorw("error parsing value to float",
"device", d.Name,
"property", p,
"value", re.Map[p],
"error", err,
)
}
m.IncrementInterface(p, d.Name, d.Address, name, v)
}
}
}
l.Debugw("done fetching interface metrics",
"device", d.Name,
)
return nil
}
func (d *Device) CollectMetrics(p PromMetrics, l *zap.SugaredLogger) error {
c, err := routeros.Dial(d.Address+apiPort, d.User, d.Password)
if err != nil {
l.Errorw("error dialing device",
"device", d.Name,
"error", err,
)
return err
}
defer c.Close()
if err := d.fetchInterfaceMetrics(c, p, l); err != nil {
l.Errorw("error fetching interface metrics",
"device", d.Name,
"error", err,
)
return err
}
return nil
}

103
exporter/prometheus.go Normal file
View File

@@ -0,0 +1,103 @@
package exporter
import (
"strings"
"fmt"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"net/http"
)
const (
promNamespace = "mikrotik"
)
var (
interfaceLabelNames = []string{"name", "address", "interface"}
InterfaceProps = []string{"name", "rx-byte", "tx-byte", "rx-packet", "tx-packet", "rx-error", "tx-error", "rx-drop", "tx-drop"}
resourceLabelNames = []string{"name", "address"}
ResourceProps = []string{"free-memory", "total-memory", "cpu-load", "free-hdd-space", "total-hdd-space"}
)
type PromMetrics struct {
InterfaceMetrics map[string]*prometheus.CounterVec
ResourceMetrics map[string]*prometheus.GaugeVec
}
func metricStringCleanup(in string) string {
return strings.Replace(in, "-", "_", -1)
}
func (p *PromMetrics) makeLabels(name, address string) prometheus.Labels {
labels := make(prometheus.Labels)
labels["name"] = metricStringCleanup(name)
labels["address"] = metricStringCleanup(address)
return labels
}
func (p *PromMetrics) makeInterfaceLabels(name, address, intf string) prometheus.Labels {
l := p.makeLabels(name, address)
l["interface"] = intf
return l
}
func (p *PromMetrics) SetupPrometheus(l zap.SugaredLogger) (http.Handler, error) {
p.InterfaceMetrics = make(map[string]*prometheus.CounterVec)
p.ResourceMetrics = make(map[string]*prometheus.GaugeVec)
for _, v := range InterfaceProps {
n := metricStringCleanup(v)
c := prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: promNamespace,
Subsystem: "interface",
Name: n,
Help: fmt.Sprintf("Interface %s counter", v),
}, interfaceLabelNames)
if err := prometheus.Register(c); err != nil {
l.Errorw("error creating interface counter vector",
"property", v,
"error", err,
)
return nil, err
}
p.InterfaceMetrics[v] = c
}
for _, v := range ResourceProps {
n := metricStringCleanup(v)
c := prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: promNamespace,
Subsystem: "resource",
Name: n,
Help: fmt.Sprintf("Resource %s counter", v),
}, resourceLabelNames)
if err := prometheus.Register(c); err != nil {
l.Errorw("error creating resource counter vec",
"property", v,
"error", err,
)
return nil, err
}
p.ResourceMetrics[v] = c
}
return promhttp.Handler(), nil
}
func (p *PromMetrics) IncrementInterface(prop, name, address, intf string, cnt float64) {
l := p.makeInterfaceLabels(name, address, intf)
p.InterfaceMetrics[prop].With(l).Add(cnt)
}
func (p *PromMetrics) UpdateResource(res, name, address string, v float64) {
l := p.makeLabels(name, address)
p.ResourceMetrics[res].With(l).Set(v)
}

64
exporter/server.go Normal file
View File

@@ -0,0 +1,64 @@
package exporter
import (
"net"
"net/http"
"time"
)
type Server struct {
l net.Listener
}
func runCollector(cfg Config) {
cfg.Logger.Info("starting collector")
for {
for _, d := range cfg.Devices {
d.CollectMetrics(cfg.Metrics, cfg.Logger)
}
time.Sleep(15 * time.Second)
}
}
func (s *Server) Run(cfg Config, mh http.Handler, port *string) error {
cfg.Logger.Infow("starting server",
"port", *port,
)
var err error
s.l, err = net.Listen("tcp", *port)
if err != nil {
cfg.Logger.Errorw("error creating listener",
"port", *port,
"error", err,
)
return err
}
go func() {
runCollector(cfg)
}()
mux := http.NewServeMux()
mux.Handle("/metrics", mh)
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("ok"))
})
go func() {
if err := http.Serve(s.l, mux); err != nil {
cfg.Logger.Errorw("unable to start service",
"error", err,
)
}
}()
return nil
}
func (s *Server) Stop() error {
return s.l.Close()
}