Feat v0.6.4 health metrics (#39)
All checks were successful
CI/CD / detect-changes (push) Successful in 3s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m42s
CI/CD / test-sqlite (push) Successful in 2m48s
CI/CD / build-and-deploy (push) Successful in 1m5s

Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #39.
This commit is contained in:
2026-03-31 14:05:49 +00:00
committed by xcaliber
parent 3d4228f868
commit 36d6158940
22 changed files with 937 additions and 286 deletions

View File

@@ -23,16 +23,37 @@ import (
"os"
"path/filepath"
"strings"
"sync/atomic"
"time"
"go.starlark.net/starlark"
starlarkjson "go.starlark.net/lib/json"
"switchboard-core/events"
"switchboard-core/metrics"
"switchboard-core/models"
"switchboard-core/store"
)
// sandboxStats tracks cumulative execution counters for the admin metrics endpoint.
var sandboxStats struct {
execCount atomic.Int64
errorCount atomic.Int64
totalDurationNs atomic.Int64
}
// SandboxStats returns cumulative execution counters.
func SandboxStats() (execCount, errorCount uint64, avgDurationMs float64) {
exec := sandboxStats.execCount.Load()
errs := sandboxStats.errorCount.Load()
totalNs := sandboxStats.totalDurationNs.Load()
if exec > 0 {
avgDurationMs = float64(totalNs) / float64(exec) / 1e6
}
return uint64(exec), uint64(errs), avgDurationMs
}
// RunContext carries per-invocation state that modules need but which
// varies per caller (API route vs filter vs task). Nil is safe — modules
// that need RunContext fields gracefully degrade.
@@ -142,7 +163,20 @@ func (r *Runner) ExecPackage(ctx context.Context, pkg *store.PackageRegistration
log.Printf(" 🔧 runner: exec %s (%d modules granted)", pkg.ID, len(modules))
return r.sandbox.ExecWithLoader(ctx, pkg.ID+"/script.star", script, modules, loader)
start := time.Now()
result, err := r.sandbox.ExecWithLoader(ctx, pkg.ID+"/script.star", script, modules, loader)
duration := time.Since(start)
sandboxStats.execCount.Add(1)
sandboxStats.totalDurationNs.Add(int64(duration))
status := "success"
if err != nil {
sandboxStats.errorCount.Add(1)
status = "error"
}
metrics.SandboxExecutionsTotal.WithLabelValues("exec", status).Inc()
return result, err
}
// loadScript reads the entry point script from disk (primary path)
@@ -266,8 +300,20 @@ func (r *Runner) CallEntryPoint(ctx context.Context, pkg *store.PackageRegistrat
return nil, result.Output, fmt.Errorf("package %q: %s is not callable", pkg.ID, entryPoint)
}
val, callOutput, err := r.sandbox.Call(ctx, callable, args, kwargs)
return val, result.Output + callOutput, err
start := time.Now()
val, callOutput, callErr := r.sandbox.Call(ctx, callable, args, kwargs)
duration := time.Since(start)
sandboxStats.execCount.Add(1)
sandboxStats.totalDurationNs.Add(int64(duration))
status := "success"
if callErr != nil {
sandboxStats.errorCount.Add(1)
status = "error"
}
metrics.SandboxExecutionsTotal.WithLabelValues(entryPoint, status).Inc()
return val, result.Output + callOutput, callErr
}
// buildModules assembles the module map based on granted permissions.