add switch-wide poe budget tracking via snmp

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Ian Gulliver
2026-01-22 22:56:47 -08:00
parent 5333d3e11e
commit d2a09250d0
2 changed files with 61 additions and 2 deletions

View File

@@ -94,10 +94,16 @@ func joinParts(parts []string) string {
return result return result
} }
type PoEBudget struct {
Power float64 // watts in use
MaxPower float64 // watts total budget
}
type Node struct { type Node struct {
Name string Name string
Interfaces map[string]*Interface Interfaces map[string]*Interface
MACTable map[string]string // peer MAC -> local interface name MACTable map[string]string // peer MAC -> local interface name
PoEBudget *PoEBudget
} }
func (n *Node) String() string { func (n *Node) String() string {
@@ -106,13 +112,22 @@ func (n *Node) String() string {
name = "??" name = "??"
} }
var parts []string
parts = append(parts, name)
if n.PoEBudget != nil {
parts = append(parts, fmt.Sprintf("[poe:%.0f/%.0fW]", n.PoEBudget.Power, n.PoEBudget.MaxPower))
}
var ifaces []string var ifaces []string
for _, iface := range n.Interfaces { for _, iface := range n.Interfaces {
ifaces = append(ifaces, iface.String()) ifaces = append(ifaces, iface.String())
} }
sort.Slice(ifaces, func(i, j int) bool { return sortorder.NaturalLess(ifaces[i], ifaces[j]) }) sort.Slice(ifaces, func(i, j int) bool { return sortorder.NaturalLess(ifaces[i], ifaces[j]) })
return fmt.Sprintf("%s {%v}", name, ifaces) parts = append(parts, fmt.Sprintf("{%v}", ifaces))
return joinParts(parts)
} }
type Nodes struct { type Nodes struct {
@@ -365,7 +380,11 @@ func (n *Nodes) logNode(node *Node) {
if name == "" { if name == "" {
name = "??" name = "??"
} }
if node.PoEBudget != nil {
log.Printf("[node] %s [poe:%.0f/%.0fW]", name, node.PoEBudget.Power, node.PoEBudget.MaxPower)
} else {
log.Printf("[node] %s", name) log.Printf("[node] %s", name)
}
var ifaceKeys []string var ifaceKeys []string
for ifaceKey := range node.Interfaces { for ifaceKey := range node.Interfaces {

40
snmp.go
View File

@@ -125,6 +125,7 @@ func (t *Tendrils) querySNMPDevice(node *Node, ip net.IP) {
t.querySysName(snmp, node) t.querySysName(snmp, node)
t.queryInterfaceMACs(snmp, node) t.queryInterfaceMACs(snmp, node)
t.queryInterfaceStats(snmp, node) t.queryInterfaceStats(snmp, node)
t.queryPoEBudget(snmp, node)
t.queryBridgeMIB(snmp, node) t.queryBridgeMIB(snmp, node)
} }
@@ -242,6 +243,45 @@ func (t *Tendrils) queryInterfaceStats(snmp *gosnmp.GoSNMP, node *Node) {
} }
} }
func (t *Tendrils) queryPoEBudget(snmp *gosnmp.GoSNMP, node *Node) {
maxPowerOID := "1.3.6.1.2.1.105.1.3.1.1.2.1"
powerOID := "1.3.6.1.2.1.105.1.3.1.1.4.1"
result, err := snmp.Get([]string{maxPowerOID, powerOID})
if err != nil {
return
}
var power, maxPower float64
for _, v := range result.Variables {
var val int
switch x := v.Value.(type) {
case int:
val = x
case uint:
val = int(x)
case int64:
val = int(x)
case uint64:
val = int(x)
default:
continue
}
if v.Name == "."+powerOID {
power = float64(val)
} else if v.Name == "."+maxPowerOID {
maxPower = float64(val)
}
}
if maxPower > 0 {
t.nodes.mu.Lock()
node.PoEBudget = &PoEBudget{Power: power, MaxPower: maxPower}
t.nodes.mu.Unlock()
}
}
func (t *Tendrils) getInterfaceTable(snmp *gosnmp.GoSNMP, oid string) map[int]int { func (t *Tendrils) getInterfaceTable(snmp *gosnmp.GoSNMP, oid string) map[int]int {
results, err := snmp.BulkWalkAll(oid) results, err := snmp.BulkWalkAll(oid)
if err != nil { if err != nil {