From e3002288741bb0ec3993dcf8d4abc42f740152e7 Mon Sep 17 00:00:00 2001 From: Kpa-clawbot <259247574+Kpa-clawbot@users.noreply.github.com> Date: Fri, 27 Mar 2026 15:44:06 -0700 Subject: [PATCH] perf: add TTL cache for subpaths API + build timestamp in stats/health - Add 15s TTL cache to GetAnalyticsSubpaths with composite key (region|minLen|maxLen|limit), matching the existing cache pattern used by RF, topology, hash, channel, and distance analytics. Cache hits return instantly vs 900ms+ computation. fixes #168 - Add BuildTime to /api/stats and /api/health responses, injected via ldflags at build time. Dockerfile.go now accepts BUILD_TIME build arg. fixes #165 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- Dockerfile.go | 3 ++- cmd/server/main.go | 8 ++++++++ cmd/server/routes.go | 4 ++++ cmd/server/store.go | 27 +++++++++++++++++++++++++-- cmd/server/types.go | 2 ++ 5 files changed, 41 insertions(+), 3 deletions(-) diff --git a/Dockerfile.go b/Dockerfile.go index 554ba3c5..427a5d4f 100644 --- a/Dockerfile.go +++ b/Dockerfile.go @@ -4,13 +4,14 @@ RUN apk add --no-cache build-base ARG APP_VERSION=unknown ARG GIT_COMMIT=unknown +ARG BUILD_TIME=unknown # Build server WORKDIR /build/server COPY cmd/server/go.mod cmd/server/go.sum ./ RUN go mod download COPY cmd/server/ ./ -RUN go build -ldflags "-X main.Version=${APP_VERSION} -X main.Commit=${GIT_COMMIT}" -o /meshcore-server . +RUN go build -ldflags "-X main.Version=${APP_VERSION} -X main.Commit=${GIT_COMMIT} -X main.BuildTime=${BUILD_TIME}" -o /meshcore-server . # Build ingestor WORKDIR /build/ingestor diff --git a/cmd/server/main.go b/cmd/server/main.go index 8d880733..d21a9cba 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -20,6 +20,7 @@ import ( // Set via -ldflags at build time var Version string var Commit string +var BuildTime string func resolveCommit() string { if Commit != "" { @@ -45,6 +46,13 @@ func resolveVersion() string { return "unknown" } +func resolveBuildTime() string { + if BuildTime != "" { + return BuildTime + } + return "unknown" +} + func main() { var ( configDir string diff --git a/cmd/server/routes.go b/cmd/server/routes.go index 504e7ae3..75281021 100644 --- a/cmd/server/routes.go +++ b/cmd/server/routes.go @@ -26,6 +26,7 @@ type Server struct { perfStats *PerfStats version string commit string + buildTime string } // PerfStats tracks request performance. @@ -61,6 +62,7 @@ func NewServer(db *DB, cfg *Config, hub *Hub) *Server { perfStats: NewPerfStats(), version: resolveVersion(), commit: resolveCommit(), + buildTime: resolveBuildTime(), } } @@ -320,6 +322,7 @@ func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) { Engine: "go", Version: s.version, Commit: s.commit, + BuildTime: s.buildTime, Uptime: int(uptime), UptimeHuman: fmt.Sprintf("%dh %dm", int(uptime)/3600, (int(uptime)%3600)/60), Memory: MemoryStats{ @@ -374,6 +377,7 @@ func (s *Server) handleStats(w http.ResponseWriter, r *http.Request) { Engine: "go", Version: s.version, Commit: s.commit, + BuildTime: s.buildTime, Counts: RoleCounts{ Repeaters: counts["repeaters"], Rooms: counts["rooms"], diff --git a/cmd/server/store.go b/cmd/server/store.go index 51da03ea..add7d3f4 100644 --- a/cmd/server/store.go +++ b/cmd/server/store.go @@ -80,6 +80,7 @@ type PacketStore struct { hashCache map[string]*cachedResult // region → cached hash-sizes result chanCache map[string]*cachedResult // region → cached channels result distCache map[string]*cachedResult // region → cached distance result + subpathCache map[string]*cachedResult // params → cached subpaths result rfCacheTTL time.Duration cacheHits int64 cacheMisses int64 @@ -111,6 +112,7 @@ func NewPacketStore(db *DB) *PacketStore { hashCache: make(map[string]*cachedResult), chanCache: make(map[string]*cachedResult), distCache: make(map[string]*cachedResult), + subpathCache: make(map[string]*cachedResult), rfCacheTTL: 15 * time.Second, } } @@ -508,7 +510,7 @@ func (s *PacketStore) GetPerfStoreStats() map[string]interface{} { // GetCacheStats returns RF cache hit/miss statistics. func (s *PacketStore) GetCacheStats() map[string]interface{} { s.cacheMu.Lock() - size := len(s.rfCache) + len(s.topoCache) + len(s.hashCache) + len(s.chanCache) + len(s.distCache) + size := len(s.rfCache) + len(s.topoCache) + len(s.hashCache) + len(s.chanCache) + len(s.distCache) + len(s.subpathCache) hits := s.cacheHits misses := s.cacheMisses s.cacheMu.Unlock() @@ -531,7 +533,7 @@ func (s *PacketStore) GetCacheStats() map[string]interface{} { // GetCacheStatsTyped returns cache stats as a typed struct. func (s *PacketStore) GetCacheStatsTyped() CacheStats { s.cacheMu.Lock() - size := len(s.rfCache) + len(s.topoCache) + len(s.hashCache) + len(s.chanCache) + len(s.distCache) + size := len(s.rfCache) + len(s.topoCache) + len(s.hashCache) + len(s.chanCache) + len(s.distCache) + len(s.subpathCache) hits := s.cacheHits misses := s.cacheMisses s.cacheMu.Unlock() @@ -3518,6 +3520,27 @@ func (s *PacketStore) GetBulkHealth(limit int, region string) []map[string]inter // --- Subpaths Analytics --- func (s *PacketStore) GetAnalyticsSubpaths(region string, minLen, maxLen, limit int) map[string]interface{} { + cacheKey := fmt.Sprintf("%s|%d|%d|%d", region, minLen, maxLen, limit) + + s.cacheMu.Lock() + if cached, ok := s.subpathCache[cacheKey]; ok && time.Now().Before(cached.expiresAt) { + s.cacheHits++ + s.cacheMu.Unlock() + return cached.data + } + s.cacheMisses++ + s.cacheMu.Unlock() + + result := s.computeAnalyticsSubpaths(region, minLen, maxLen, limit) + + s.cacheMu.Lock() + s.subpathCache[cacheKey] = &cachedResult{data: result, expiresAt: time.Now().Add(s.rfCacheTTL)} + s.cacheMu.Unlock() + + return result +} + +func (s *PacketStore) computeAnalyticsSubpaths(region string, minLen, maxLen, limit int) map[string]interface{} { s.mu.RLock() defer s.mu.RUnlock() diff --git a/cmd/server/types.go b/cmd/server/types.go index 8ac1d9bb..0c101137 100644 --- a/cmd/server/types.go +++ b/cmd/server/types.go @@ -65,6 +65,7 @@ type StatsResponse struct { Engine string `json:"engine"` Version string `json:"version"` Commit string `json:"commit"` + BuildTime string `json:"buildTime"` Counts RoleCounts `json:"counts"` } @@ -132,6 +133,7 @@ type HealthResponse struct { Engine string `json:"engine"` Version string `json:"version"` Commit string `json:"commit"` + BuildTime string `json:"buildTime"` Uptime int `json:"uptime"` UptimeHuman string `json:"uptimeHuman"` Memory MemoryStats `json:"memory"`