mirror of
https://github.com/nshttpd/mikrotik-exporter.git
synced 2025-01-07 18:38:28 +01:00
Merge pull request #102 from oGi4i/feature/dhcp_server
Added support for multiple dhcp servers
This commit is contained in:
commit
81e30792fe
@ -1,10 +1,12 @@
|
|||||||
package collector
|
package collector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"gopkg.in/routeros.v2/proto"
|
"gopkg.in/routeros.v2/proto"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type dhcpLeaseCollector struct {
|
type dhcpLeaseCollector struct {
|
||||||
@ -13,9 +15,9 @@ type dhcpLeaseCollector struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *dhcpLeaseCollector) init() {
|
func (c *dhcpLeaseCollector) init() {
|
||||||
c.props = []string{"active-mac-address", "status", "expires-after", "active-address", "host-name"}
|
c.props = []string{"active-mac-address", "server", "status", "expires-after", "active-address", "host-name"}
|
||||||
|
|
||||||
labelNames := []string{"name", "address", "activemacaddress", "status", "expiresafter", "activeaddress", "hostname"}
|
labelNames := []string{"name", "address", "activemacaddress", "server", "status", "expiresafter", "activeaddress", "hostname"}
|
||||||
c.descriptions = description("dhcp", "leases_metrics", "number of metrics", labelNames)
|
c.descriptions = description("dhcp", "leases_metrics", "number of metrics", labelNames)
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -44,7 +46,7 @@ func (c *dhcpLeaseCollector) collect(ctx *collectorContext) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *dhcpLeaseCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
|
func (c *dhcpLeaseCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, error) {
|
||||||
reply, err := ctx.client.Run("/ip/dhcp-server/lease/print", "=.proplist="+strings.Join(c.props, ","))
|
reply, err := ctx.client.Run("/ip/dhcp-server/lease/print", "?status=bound", "=.proplist="+strings.Join(c.props, ","))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithFields(log.Fields{
|
log.WithFields(log.Fields{
|
||||||
"device": ctx.device.Name,
|
"device": ctx.device.Name,
|
||||||
@ -59,11 +61,22 @@ func (c *dhcpLeaseCollector) fetch(ctx *collectorContext) ([]*proto.Sentence, er
|
|||||||
func (c *dhcpLeaseCollector) collectMetric(ctx *collectorContext, re *proto.Sentence) {
|
func (c *dhcpLeaseCollector) collectMetric(ctx *collectorContext, re *proto.Sentence) {
|
||||||
v := 1.0
|
v := 1.0
|
||||||
|
|
||||||
|
f, err := parseDuration(re.Map["expires-after"])
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"device": ctx.device.Name,
|
||||||
|
"property": "expires-after",
|
||||||
|
"value": re.Map["expires-after"],
|
||||||
|
"error": err,
|
||||||
|
}).Error("error parsing duration metric value")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
activemacaddress := re.Map["active-mac-address"]
|
activemacaddress := re.Map["active-mac-address"]
|
||||||
|
server := re.Map["server"]
|
||||||
status := re.Map["status"]
|
status := re.Map["status"]
|
||||||
expiresafter := re.Map["expires-after"]
|
|
||||||
activeaddress := re.Map["active-address"]
|
activeaddress := re.Map["active-address"]
|
||||||
hostname := re.Map["host-name"]
|
hostname := re.Map["host-name"]
|
||||||
|
|
||||||
ctx.ch <- prometheus.MustNewConstMetric(c.descriptions, prometheus.CounterValue, v, ctx.device.Name, ctx.device.Address, activemacaddress, status, expiresafter, activeaddress, hostname)
|
ctx.ch <- prometheus.MustNewConstMetric(c.descriptions, prometheus.GaugeValue, v, ctx.device.Name, ctx.device.Address, activemacaddress, server, status, strconv.FormatFloat(f, 'f', 0, 64), activeaddress, hostname)
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,25 @@
|
|||||||
package collector
|
package collector
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var durationRegex *regexp.Regexp
|
||||||
|
var durationParts [5]time.Duration
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
durationRegex = regexp.MustCompile(`(?:(\d*)w)?(?:(\d*)d)?(?:(\d*)h)?(?:(\d*)m)?(?:(\d*)s)?`)
|
||||||
|
durationParts = [5]time.Duration{time.Hour * 168, time.Hour * 24, time.Hour, time.Minute, time.Second}
|
||||||
|
}
|
||||||
|
|
||||||
func metricStringCleanup(in string) string {
|
func metricStringCleanup(in string) string {
|
||||||
return strings.Replace(in, "-", "_", -1)
|
return strings.Replace(in, "-", "_", -1)
|
||||||
}
|
}
|
||||||
@ -49,3 +61,30 @@ func splitStringToFloats(metric string) (float64, float64, error) {
|
|||||||
}
|
}
|
||||||
return m1, m2, nil
|
return m1, m2, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func parseDuration(duration string) (float64, error) {
|
||||||
|
var u time.Duration
|
||||||
|
|
||||||
|
reMatch := durationRegex.FindAllStringSubmatch(duration, -1)
|
||||||
|
|
||||||
|
// should get one and only one match back on the regex
|
||||||
|
if len(reMatch) != 1 {
|
||||||
|
return 0, fmt.Errorf("invalid duration value sent to regex")
|
||||||
|
} else {
|
||||||
|
for i, match := range reMatch[0] {
|
||||||
|
if match != "" && i != 0 {
|
||||||
|
v, err := strconv.Atoi(match)
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(log.Fields{
|
||||||
|
"duration": duration,
|
||||||
|
"value": match,
|
||||||
|
"error": err,
|
||||||
|
}).Error("error parsing duration field value")
|
||||||
|
return float64(0), err
|
||||||
|
}
|
||||||
|
u += time.Duration(v) * durationParts[i-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return u.Seconds(), nil
|
||||||
|
}
|
||||||
|
137
collector/helper_test.go
Normal file
137
collector/helper_test.go
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
package collector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSplitStringToFloats(t *testing.T) {
|
||||||
|
var testCases = []struct {
|
||||||
|
input string
|
||||||
|
expected struct {
|
||||||
|
f1 float64
|
||||||
|
f2 float64
|
||||||
|
}
|
||||||
|
isNaN bool
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"1.2,2.1",
|
||||||
|
struct {
|
||||||
|
f1 float64
|
||||||
|
f2 float64
|
||||||
|
}{
|
||||||
|
1.2,
|
||||||
|
2.1,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "1.2,",
|
||||||
|
isNaN: true,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: ",2.1",
|
||||||
|
isNaN: true,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"1.2,2.1,3.2",
|
||||||
|
struct {
|
||||||
|
f1 float64
|
||||||
|
f2 float64
|
||||||
|
}{
|
||||||
|
1.2,
|
||||||
|
2.1,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
isNaN: true,
|
||||||
|
hasError: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
f1, f2, err := splitStringToFloats(testCase.input)
|
||||||
|
|
||||||
|
switch testCase.hasError {
|
||||||
|
case true:
|
||||||
|
assert.Error(t, err)
|
||||||
|
case false:
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch testCase.isNaN {
|
||||||
|
case true:
|
||||||
|
assert.True(t, math.IsNaN(f1))
|
||||||
|
assert.True(t, math.IsNaN(f2))
|
||||||
|
case false:
|
||||||
|
assert.Equal(t, testCase.expected.f1, f1)
|
||||||
|
assert.Equal(t, testCase.expected.f2, f2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseDuration(t *testing.T) {
|
||||||
|
var testCases = []struct {
|
||||||
|
input string
|
||||||
|
output float64
|
||||||
|
hasError bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"3d3h42m53s",
|
||||||
|
272573,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"15w3d3h42m53s",
|
||||||
|
9344573,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"42m53s",
|
||||||
|
2573,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"7w6d9h34m",
|
||||||
|
4786440,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"59",
|
||||||
|
0,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"s",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"",
|
||||||
|
0,
|
||||||
|
false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range testCases {
|
||||||
|
f, err := parseDuration(testCase.input)
|
||||||
|
|
||||||
|
switch testCase.hasError {
|
||||||
|
case true:
|
||||||
|
assert.Error(t, err)
|
||||||
|
case false:
|
||||||
|
assert.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.Equal(t, testCase.output, f)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user