From 6d31cb2ad6df6837226f329e852e12a9ae47fffc Mon Sep 17 00:00:00 2001 From: Kpa-clawbot <259247574+Kpa-clawbot@users.noreply.github.com> Date: Sat, 28 Mar 2026 11:18:33 -0700 Subject: [PATCH] feat: add pprof profiling controlled by ENABLE_PPROF env var MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add net/http/pprof support to both Go server (default port 6060) and ingestor (default port 6061). Profiling is off by default — only starts the pprof HTTP listener when ENABLE_PPROF=true. PPROF_PORT env var overrides the default port for each binary. Enable on staging-go in docker-compose with exposed ports 6060/6061. Not enabled on prod. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cmd/ingestor/main.go | 53 ++++++++++++++++++++++++++++++++++++++++++-- cmd/server/main.go | 13 +++++++++++ docker-compose.yml | 4 ++++ 3 files changed, 68 insertions(+), 2 deletions(-) diff --git a/cmd/ingestor/main.go b/cmd/ingestor/main.go index 6d548429..af08e2db 100644 --- a/cmd/ingestor/main.go +++ b/cmd/ingestor/main.go @@ -8,6 +8,9 @@ import ( "flag" "fmt" "log" + "math" + "net/http" + _ "net/http/pprof" "os" "os/signal" "path/filepath" @@ -19,6 +22,18 @@ import ( ) func main() { + // pprof profiling — off by default, enable with ENABLE_PPROF=true + if os.Getenv("ENABLE_PPROF") == "true" { + pprofPort := os.Getenv("PPROF_PORT") + if pprofPort == "" { + pprofPort = "6061" + } + go func() { + log.Printf("[pprof] ingestor profiling at http://localhost:%s/debug/pprof/", pprofPort) + log.Fatal(http.ListenAndServe(":"+pprofPort, nil)) + }() + } + configPath := flag.String("config", "config.json", "path to config file") flag.Parse() @@ -193,7 +208,8 @@ func handleMessage(store *Store, tag string, source MQTTSource, m mqtt.Message, observerID := parts[2] name, _ := msg["origin"].(string) iata := parts[1] - if err := store.UpsertObserver(observerID, name, iata); err != nil { + meta := extractObserverMeta(msg) + if err := store.UpsertObserver(observerID, name, iata, meta); err != nil { log.Printf("MQTT [%s] observer status error: %v", tag, err) } log.Printf("MQTT [%s] status: %s (%s)", tag, firstNonEmpty(name, observerID), iata) @@ -260,7 +276,7 @@ func handleMessage(store *Store, tag string, source MQTTSource, m mqtt.Message, // Upsert observer if observerID != "" { origin, _ := msg["origin"].(string) - if err := store.UpsertObserver(observerID, origin, region); err != nil { + if err := store.UpsertObserver(observerID, origin, region, nil); err != nil { log.Printf("MQTT [%s] observer upsert error: %v", tag, err) } } @@ -446,6 +462,39 @@ func toFloat64(v interface{}) (float64, bool) { } } +// extractObserverMeta extracts hardware metadata from an MQTT status message. +// Casts battery_mv and uptime_secs to integers (they're always whole numbers). +func extractObserverMeta(msg map[string]interface{}) *ObserverMeta { + meta := &ObserverMeta{} + hasData := false + + if v, ok := msg["battery_mv"]; ok { + if f, ok := toFloat64(v); ok { + iv := int(math.Round(f)) + meta.BatteryMv = &iv + hasData = true + } + } + if v, ok := msg["uptime_secs"]; ok { + if f, ok := toFloat64(v); ok { + iv := int64(math.Round(f)) + meta.UptimeSecs = &iv + hasData = true + } + } + if v, ok := msg["noise_floor"]; ok { + if f, ok := toFloat64(v); ok { + meta.NoiseFloor = &f + hasData = true + } + } + + if !hasData { + return nil + } + return meta +} + func firstNonEmpty(vals ...string) string { for _, v := range vals { if v != "" { diff --git a/cmd/server/main.go b/cmd/server/main.go index d21a9cba..7c14a0f4 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -6,6 +6,7 @@ import ( "fmt" "log" "net/http" + _ "net/http/pprof" "os" "os/exec" "os/signal" @@ -54,6 +55,18 @@ func resolveBuildTime() string { } func main() { + // pprof profiling — off by default, enable with ENABLE_PPROF=true + if os.Getenv("ENABLE_PPROF") == "true" { + pprofPort := os.Getenv("PPROF_PORT") + if pprofPort == "" { + pprofPort = "6060" + } + go func() { + log.Printf("[pprof] profiling UI at http://localhost:%s/debug/pprof/", pprofPort) + log.Fatal(http.ListenAndServe(":"+pprofPort, nil)) + }() + } + var ( configDir string port int diff --git a/docker-compose.yml b/docker-compose.yml index 453045b5..9136d67c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -58,12 +58,16 @@ services: ports: - "${STAGING_GO_HTTP_PORT:-82}:80" - "${STAGING_GO_MQTT_PORT:-1885}:1883" + - "6060:6060" # pprof server + - "6061:6061" # pprof ingestor volumes: - ${STAGING_DATA_DIR:-~/meshcore-staging-data}/config.json:/app/config.json:ro - ${STAGING_DATA_DIR:-~/meshcore-staging-data}:/app/data - caddy-data-staging-go:/data/caddy environment: - NODE_ENV=staging + - ENABLE_PPROF=true + - PPROF_PORT=6060 healthcheck: test: ["CMD", "wget", "-qO-", "http://localhost:3000/api/stats"] interval: 30s