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>
This commit is contained in:
Kpa-clawbot
2026-03-27 15:44:06 -07:00
co-authored by Copilot
parent 4a6ac482e6
commit e300228874
5 changed files with 41 additions and 3 deletions
+2 -1
View File
@@ -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
+8
View File
@@ -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
+4
View File
@@ -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"],
+25 -2
View File
@@ -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()
+2
View File
@@ -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"`