mirror of
https://github.com/nshttpd/mikrotik-exporter.git
synced 2024-11-15 10:15:18 +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
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"gopkg.in/routeros.v2/proto"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type dhcpLeaseCollector struct {
|
||||
@ -13,9 +15,9 @@ type dhcpLeaseCollector struct {
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
}
|
||||
@ -44,7 +46,7 @@ func (c *dhcpLeaseCollector) collect(ctx *collectorContext) 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 {
|
||||
log.WithFields(log.Fields{
|
||||
"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) {
|
||||
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"]
|
||||
server := re.Map["server"]
|
||||
status := re.Map["status"]
|
||||
expiresafter := re.Map["expires-after"]
|
||||
activeaddress := re.Map["active-address"]
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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 {
|
||||
return strings.Replace(in, "-", "_", -1)
|
||||
}
|
||||
@ -49,3 +61,30 @@ func splitStringToFloats(metric string) (float64, float64, error) {
|
||||
}
|
||||
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