Move stats counters into engine to eliminate hot path map lookup
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
22
main.go
22
main.go
@@ -11,7 +11,6 @@ import (
|
|||||||
"os/signal"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -32,8 +31,6 @@ type App struct {
|
|||||||
artTargets map[uint16]*net.UDPAddr
|
artTargets map[uint16]*net.UDPAddr
|
||||||
sacnTargets map[uint16][]*net.UDPAddr
|
sacnTargets map[uint16][]*net.UDPAddr
|
||||||
debug bool
|
debug bool
|
||||||
statsMu sync.Mutex
|
|
||||||
stats map[config.Universe]uint64
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@@ -150,7 +147,6 @@ func main() {
|
|||||||
artTargets: artTargets,
|
artTargets: artTargets,
|
||||||
sacnTargets: sacnTargets,
|
sacnTargets: sacnTargets,
|
||||||
debug: *debug,
|
debug: *debug,
|
||||||
stats: map[config.Universe]uint64{},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create ArtNet receiver if enabled
|
// Create ArtNet receiver if enabled
|
||||||
@@ -245,9 +241,6 @@ func (a *App) HandleDMX(src *net.UDPAddr, pkt *artnet.DMXPacket) {
|
|||||||
src.IP, pkt.Universe, pkt.Sequence, pkt.Length)
|
src.IP, pkt.Universe, pkt.Sequence, pkt.Length)
|
||||||
}
|
}
|
||||||
u := config.Universe{Protocol: config.ProtocolArtNet, Number: uint16(pkt.Universe)}
|
u := config.Universe{Protocol: config.ProtocolArtNet, Number: uint16(pkt.Universe)}
|
||||||
a.statsMu.Lock()
|
|
||||||
a.stats[u]++
|
|
||||||
a.statsMu.Unlock()
|
|
||||||
a.sendOutputs(a.engine.Remap(u, pkt.Data))
|
a.sendOutputs(a.engine.Remap(u, pkt.Data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,9 +266,6 @@ func (a *App) HandleSACN(universe uint16, data [512]byte) {
|
|||||||
log.Printf("[<-sacn] universe=%d", universe)
|
log.Printf("[<-sacn] universe=%d", universe)
|
||||||
}
|
}
|
||||||
u := config.Universe{Protocol: config.ProtocolSACN, Number: universe}
|
u := config.Universe{Protocol: config.ProtocolSACN, Number: universe}
|
||||||
a.statsMu.Lock()
|
|
||||||
a.stats[u]++
|
|
||||||
a.statsMu.Unlock()
|
|
||||||
a.sendOutputs(a.engine.Remap(u, data))
|
a.sendOutputs(a.engine.Remap(u, data))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -334,19 +324,13 @@ func (a *App) handleConfig(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) printStats() {
|
func (a *App) printStats() {
|
||||||
a.statsMu.Lock()
|
if len(a.cfg.Mappings) == 0 {
|
||||||
stats := a.stats
|
|
||||||
a.stats = map[config.Universe]uint64{}
|
|
||||||
a.statsMu.Unlock()
|
|
||||||
|
|
||||||
if len(stats) == 0 && len(a.cfg.Mappings) == 0 {
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
counts := a.engine.SwapStats()
|
||||||
log.Printf("[stats] mapping traffic (last 10s):")
|
log.Printf("[stats] mapping traffic (last 10s):")
|
||||||
for _, m := range a.cfg.Mappings {
|
for _, m := range a.cfg.Mappings {
|
||||||
count := stats[m.From.Universe]
|
log.Printf("[stats] %s -> %s: %d packets", m.From, m.To, counts[m.From.Universe])
|
||||||
log.Printf("[stats] %s -> %s: %d packets", m.From, m.To, count)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package remap
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
|
||||||
"github.com/gopatchy/artmap/config"
|
"github.com/gopatchy/artmap/config"
|
||||||
)
|
)
|
||||||
@@ -12,19 +13,30 @@ type Output struct {
|
|||||||
Data [512]byte
|
Data [512]byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// sourceEntry holds mappings and stats for a source universe
|
||||||
|
type sourceEntry struct {
|
||||||
|
mappings []config.NormalizedMapping
|
||||||
|
counter atomic.Uint64
|
||||||
|
}
|
||||||
|
|
||||||
// Engine handles DMX channel remapping
|
// Engine handles DMX channel remapping
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
mappings []config.NormalizedMapping
|
mappings []config.NormalizedMapping
|
||||||
bySource map[config.Universe][]config.NormalizedMapping
|
bySource map[config.Universe]*sourceEntry
|
||||||
state map[config.Universe]*[512]byte
|
state map[config.Universe]*[512]byte
|
||||||
stateMu sync.Mutex
|
stateMu sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewEngine creates a new remapping engine
|
// NewEngine creates a new remapping engine
|
||||||
func NewEngine(mappings []config.NormalizedMapping) *Engine {
|
func NewEngine(mappings []config.NormalizedMapping) *Engine {
|
||||||
bySource := make(map[config.Universe][]config.NormalizedMapping)
|
bySource := map[config.Universe]*sourceEntry{}
|
||||||
for _, m := range mappings {
|
for _, m := range mappings {
|
||||||
bySource[m.From] = append(bySource[m.From], m)
|
entry := bySource[m.From]
|
||||||
|
if entry == nil {
|
||||||
|
entry = &sourceEntry{}
|
||||||
|
bySource[m.From] = entry
|
||||||
|
}
|
||||||
|
entry.mappings = append(entry.mappings, m)
|
||||||
}
|
}
|
||||||
|
|
||||||
state := make(map[config.Universe]*[512]byte)
|
state := make(map[config.Universe]*[512]byte)
|
||||||
@@ -43,17 +55,18 @@ func NewEngine(mappings []config.NormalizedMapping) *Engine {
|
|||||||
|
|
||||||
// Remap applies mappings to incoming DMX data and returns outputs
|
// Remap applies mappings to incoming DMX data and returns outputs
|
||||||
func (e *Engine) Remap(src config.Universe, srcData [512]byte) []Output {
|
func (e *Engine) Remap(src config.Universe, srcData [512]byte) []Output {
|
||||||
mappings, ok := e.bySource[src]
|
entry := e.bySource[src]
|
||||||
if !ok {
|
if entry == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
entry.counter.Add(1)
|
||||||
|
|
||||||
e.stateMu.Lock()
|
e.stateMu.Lock()
|
||||||
defer e.stateMu.Unlock()
|
defer e.stateMu.Unlock()
|
||||||
|
|
||||||
affected := make(map[config.Universe]bool)
|
affected := make(map[config.Universe]bool)
|
||||||
|
|
||||||
for _, m := range mappings {
|
for _, m := range entry.mappings {
|
||||||
affected[m.To] = true
|
affected[m.To] = true
|
||||||
outState := e.state[m.To]
|
outState := e.state[m.To]
|
||||||
|
|
||||||
@@ -77,6 +90,15 @@ func (e *Engine) Remap(src config.Universe, srcData [512]byte) []Output {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SwapStats returns packet counts per source universe since last call and resets them
|
||||||
|
func (e *Engine) SwapStats() map[config.Universe]uint64 {
|
||||||
|
result := map[config.Universe]uint64{}
|
||||||
|
for u, entry := range e.bySource {
|
||||||
|
result[u] = entry.counter.Swap(0)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
// SourceArtNetUniverses returns source ArtNet universe numbers (for discovery)
|
// SourceArtNetUniverses returns source ArtNet universe numbers (for discovery)
|
||||||
func (e *Engine) SourceArtNetUniverses() []uint16 {
|
func (e *Engine) SourceArtNetUniverses() []uint16 {
|
||||||
seen := make(map[uint16]bool)
|
seen := make(map[uint16]bool)
|
||||||
|
|||||||
Reference in New Issue
Block a user