Merge pull request #102 from oGi4i/feature/dhcp_server

Added support for multiple dhcp servers
This commit is contained in:
Steve Brunton 2021-03-25 20:10:54 -04:00 committed by GitHub
commit 81e30792fe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 195 additions and 6 deletions

View File

@ -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)
}

View File

@ -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
View 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)
}
}