squash merge from dev branch
This commit is contained in:
32
exporter/config.go
Normal file
32
exporter/config.go
Normal 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
81
exporter/device.go
Normal 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
103
exporter/prometheus.go
Normal 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
64
exporter/server.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user