Add shared_names config and port uptime tracking

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-31 12:06:59 -08:00
parent df0c999284
commit d1b4de01e8
5 changed files with 56 additions and 4 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ log
*~ *~
cert.pem cert.pem
key.pem key.pem
status

View File

@@ -8,7 +8,8 @@ import (
) )
type Config struct { type Config struct {
Locations []*Location `yaml:"locations" json:"locations"` Locations []*Location `yaml:"locations" json:"locations"`
SharedNames []string `yaml:"shared_names,omitempty" json:"shared_names,omitempty"`
} }
type Location struct { type Location struct {

View File

@@ -113,6 +113,9 @@ func (n *Nodes) findOrMergeByName(target *Node, nodeName string) *Node {
if nodeName == "" { if nodeName == "" {
return target return target
} }
if n.isSharedName(nodeName) {
return target
}
found := n.nameIndex[nodeName] found := n.nameIndex[nodeName]
if found == nil { if found == nil {
return target return target
@@ -126,6 +129,18 @@ func (n *Nodes) findOrMergeByName(target *Node, nodeName string) *Node {
return target return target
} }
func (n *Nodes) isSharedName(name string) bool {
if n.t.config == nil {
return false
}
for _, shared := range n.t.config.SharedNames {
if shared == name {
return true
}
}
return false
}
func (n *Nodes) createNode() *Node { func (n *Nodes) createNode() *Node {
node := &Node{ node := &Node{
ID: newID("node"), ID: newID("node"),

38
snmp.go
View File

@@ -73,10 +73,14 @@ func snmpToInt(val interface{}) (int, bool) {
switch v := val.(type) { switch v := val.(type) {
case int: case int:
return v, true return v, true
case uint: case int32:
return int(v), true return int(v), true
case int64: case int64:
return int(v), true return int(v), true
case uint:
return int(v), true
case uint32:
return int(v), true
case uint64: case uint64:
return int(v), true return int(v), true
default: default:
@@ -126,12 +130,33 @@ func (t *Tendrils) querySNMPDevice(node *Node, ip net.IP) {
t.querySysName(snmp, node) t.querySysName(snmp, node)
t.queryInterfaceMACs(snmp, node, ifNames) t.queryInterfaceMACs(snmp, node, ifNames)
t.queryInterfaceStats(snmp, node, ifNames) sysUpTime := t.getSysUpTime(snmp)
t.queryInterfaceStats(snmp, node, ifNames, sysUpTime)
t.queryPoEBudget(snmp, node) t.queryPoEBudget(snmp, node)
t.queryBridgeMIB(snmp, node, ifNames) t.queryBridgeMIB(snmp, node, ifNames)
t.queryDHCPBindings(snmp) t.queryDHCPBindings(snmp)
} }
func (t *Tendrils) getSysUpTime(snmp *gosnmp.GoSNMP) uint64 {
oid := "1.3.6.1.2.1.1.3.0"
result, err := snmp.Get([]string{oid})
if err != nil {
return 0
}
if len(result.Variables) == 0 {
return 0
}
v, ok := snmpToInt(result.Variables[0].Value)
if !ok {
log.Printf("[ERROR] failed to parse sysUpTime: type=%T value=%v", result.Variables[0].Value, result.Variables[0].Value)
return 0
}
return uint64(v)
}
func (t *Tendrils) querySysName(snmp *gosnmp.GoSNMP, node *Node) { func (t *Tendrils) querySysName(snmp *gosnmp.GoSNMP, node *Node) {
oid := "1.3.6.1.2.1.1.5.0" oid := "1.3.6.1.2.1.1.5.0"
@@ -195,8 +220,9 @@ func (t *Tendrils) queryInterfaceMACs(snmp *gosnmp.GoSNMP, node *Node, ifNames m
} }
} }
func (t *Tendrils) queryInterfaceStats(snmp *gosnmp.GoSNMP, node *Node, ifNames map[int]string) { func (t *Tendrils) queryInterfaceStats(snmp *gosnmp.GoSNMP, node *Node, ifNames map[int]string, sysUpTime uint64) {
ifOperStatus := t.getInterfaceTable(snmp, "1.3.6.1.2.1.2.2.1.8") ifOperStatus := t.getInterfaceTable(snmp, "1.3.6.1.2.1.2.2.1.8")
ifLastChange := t.getInterfaceTable(snmp, "1.3.6.1.2.1.2.2.1.9")
ifHighSpeed := t.getInterfaceTable(snmp, "1.3.6.1.2.1.31.1.1.1.15") ifHighSpeed := t.getInterfaceTable(snmp, "1.3.6.1.2.1.31.1.1.1.15")
ifInErrors := t.getInterfaceTable(snmp, "1.3.6.1.2.1.2.2.1.14") ifInErrors := t.getInterfaceTable(snmp, "1.3.6.1.2.1.2.2.1.14")
ifOutErrors := t.getInterfaceTable(snmp, "1.3.6.1.2.1.2.2.1.20") ifOutErrors := t.getInterfaceTable(snmp, "1.3.6.1.2.1.2.2.1.20")
@@ -236,6 +262,12 @@ func (t *Tendrils) queryInterfaceStats(snmp *gosnmp.GoSNMP, node *Node, ifNames
stats.Speed = uint64(speed) * 1000000 stats.Speed = uint64(speed) * 1000000
} }
if lastChange, ok := ifLastChange[ifIndex]; ok && sysUpTime > 0 {
if uint64(lastChange) <= sysUpTime {
stats.Uptime = (sysUpTime - uint64(lastChange)) / 100
}
}
if inErr, ok := ifInErrors[ifIndex]; ok { if inErr, ok := ifInErrors[ifIndex]; ok {
stats.InErrors = uint64(inErr) stats.InErrors = uint64(inErr)
} }

View File

@@ -384,6 +384,7 @@ func (i *Interface) MarshalJSON() ([]byte, error) {
type InterfaceStats struct { type InterfaceStats struct {
Speed uint64 `json:"speed,omitempty"` Speed uint64 `json:"speed,omitempty"`
Uptime uint64 `json:"uptime,omitempty"`
InErrors uint64 `json:"in_errors,omitempty"` InErrors uint64 `json:"in_errors,omitempty"`
OutErrors uint64 `json:"out_errors,omitempty"` OutErrors uint64 `json:"out_errors,omitempty"`
InPktsRate float64 `json:"in_pkts_rate,omitempty"` InPktsRate float64 `json:"in_pkts_rate,omitempty"`
@@ -400,6 +401,7 @@ func round2(v float64) float64 {
func (s *InterfaceStats) MarshalJSON() ([]byte, error) { func (s *InterfaceStats) MarshalJSON() ([]byte, error) {
type statsJSON struct { type statsJSON struct {
Speed uint64 `json:"speed,omitempty"` Speed uint64 `json:"speed,omitempty"`
Uptime uint64 `json:"uptime,omitempty"`
InErrors uint64 `json:"in_errors,omitempty"` InErrors uint64 `json:"in_errors,omitempty"`
OutErrors uint64 `json:"out_errors,omitempty"` OutErrors uint64 `json:"out_errors,omitempty"`
InPktsRate float64 `json:"in_pkts_rate,omitempty"` InPktsRate float64 `json:"in_pkts_rate,omitempty"`
@@ -410,6 +412,7 @@ func (s *InterfaceStats) MarshalJSON() ([]byte, error) {
} }
return json.Marshal(statsJSON{ return json.Marshal(statsJSON{
Speed: s.Speed, Speed: s.Speed,
Uptime: s.Uptime,
InErrors: s.InErrors, InErrors: s.InErrors,
OutErrors: s.OutErrors, OutErrors: s.OutErrors,
InPktsRate: round2(s.InPktsRate), InPktsRate: round2(s.InPktsRate),