replace fmt.Sprintf with strconv in hot paths

Use strconv.FormatInt/FormatUint instead of fmt.Sprintf for integer
conversions in snmpValueToString. Use encoding/hex + strings.Builder
instead of fmt.Sprintf loop in formatHex. Use strconv.FormatUint for
WebSocket ref counter. Reduces allocations per OID significantly.
This commit is contained in:
Graham McIntire 2026-02-12 09:47:42 -06:00
parent 82df96cd59
commit 07e7ed2745
No known key found for this signature in database
2 changed files with 18 additions and 17 deletions

View file

@ -8,6 +8,7 @@ import (
"log/slog" "log/slog"
"os" "os"
"runtime" "runtime"
"strconv"
"strings" "strings"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -89,8 +90,7 @@ func runSession(ctx context.Context, baseURL, token string) error {
refCounter.Store(1) refCounter.Store(1)
nextRef := func() string { nextRef := func() string {
r := refCounter.Add(1) return strconv.FormatUint(refCounter.Add(1), 10)
return fmt.Sprintf("%d", r)
} }
sendMsg := func(event string, payload json.RawMessage) { sendMsg := func(event string, payload json.RawMessage) {

31
snmp.go
View file

@ -1,8 +1,11 @@
package main package main
import ( import (
"encoding/hex"
"fmt" "fmt"
"log/slog" "log/slog"
"strconv"
"strings"
"time" "time"
"unicode/utf8" "unicode/utf8"
@ -254,13 +257,12 @@ func mapPrivProtocol(p string) gosnmp.SnmpV3PrivProtocol {
func snmpValueToString(pdu gosnmp.SnmpPDU) string { func snmpValueToString(pdu gosnmp.SnmpPDU) string {
switch pdu.Type { switch pdu.Type {
case gosnmp.Integer: case gosnmp.Integer:
return fmt.Sprintf("%d", gosnmp.ToBigInt(pdu.Value).Int64()) return strconv.FormatInt(gosnmp.ToBigInt(pdu.Value).Int64(), 10)
case gosnmp.OctetString: case gosnmp.OctetString:
b := pdu.Value.([]byte) b := pdu.Value.([]byte)
if !utf8.Valid(b) { if !utf8.Valid(b) {
return formatHex(b) return formatHex(b)
} }
// Check for non-printable control chars
for _, c := range b { for _, c := range b {
if c < 0x20 && c != '\n' && c != '\r' && c != '\t' { if c < 0x20 && c != '\n' && c != '\r' && c != '\t' {
return formatHex(b) return formatHex(b)
@ -270,13 +272,13 @@ func snmpValueToString(pdu gosnmp.SnmpPDU) string {
case gosnmp.ObjectIdentifier: case gosnmp.ObjectIdentifier:
return pdu.Value.(string) return pdu.Value.(string)
case gosnmp.Counter32: case gosnmp.Counter32:
return fmt.Sprintf("%d", pdu.Value.(uint)) return strconv.FormatUint(uint64(pdu.Value.(uint)), 10)
case gosnmp.Counter64: case gosnmp.Counter64:
return fmt.Sprintf("%d", pdu.Value.(uint64)) return strconv.FormatUint(pdu.Value.(uint64), 10)
case gosnmp.Gauge32: case gosnmp.Gauge32:
return fmt.Sprintf("%d", pdu.Value.(uint)) return strconv.FormatUint(uint64(pdu.Value.(uint)), 10)
case gosnmp.TimeTicks: case gosnmp.TimeTicks:
return fmt.Sprintf("%d", pdu.Value.(uint32)) return strconv.FormatUint(uint64(pdu.Value.(uint32)), 10)
case gosnmp.IPAddress: case gosnmp.IPAddress:
return pdu.Value.(string) return pdu.Value.(string)
case gosnmp.Null, gosnmp.NoSuchObject, gosnmp.NoSuchInstance, gosnmp.EndOfMibView: case gosnmp.Null, gosnmp.NoSuchObject, gosnmp.NoSuchInstance, gosnmp.EndOfMibView:
@ -292,16 +294,15 @@ func formatHex(b []byte) string {
if len(b) == 0 { if len(b) == 0 {
return "" return ""
} }
parts := make([]string, len(b)) h := hex.EncodeToString(b)
for i, v := range b { var buf strings.Builder
parts[i] = fmt.Sprintf("%02x", v) buf.Grow(len(h) + len(b) - 1)
} for i := 0; i < len(h); i += 2 {
result := ""
for i, p := range parts {
if i > 0 { if i > 0 {
result += ":" buf.WriteByte(':')
} }
result += p buf.WriteByte(h[i])
buf.WriteByte(h[i+1])
} }
return result return buf.String()
} }