diff --git a/core/http/server.go b/core/http/server.go
index ea0eac9..5930f43 100644
--- a/core/http/server.go
+++ b/core/http/server.go
@@ -74,6 +74,18 @@ func (s *Server) Shutdown(ctx context.Context) error {
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
start := time.Now()
+ // Special case for debug stats when debug mode is enabled
+ if s.debugMode && r.URL.Path == "/debug/stats" {
+ s.handleDebugStats(w, r)
+
+ // Calculate and log request duration
+ duration := time.Since(start)
+ if s.loggingEnabled {
+ LogRequest(s.logger, http.StatusOK, r, duration)
+ }
+ return
+ }
+
// Wrap the ResponseWriter to capture status code
wrappedWriter := newStatusCaptureWriter(w)
@@ -281,3 +293,27 @@ func setContentTypeIfMissing(w http.ResponseWriter, contentType string) {
w.Header().Set("Content-Type", contentType)
}
}
+
+// handleDebugStats displays debug statistics
+func (s *Server) handleDebugStats(w http.ResponseWriter, r *http.Request) {
+ // Collect system stats
+ stats := utils.CollectSystemStats()
+
+ // Add component stats
+ routeCount, bytecodeBytes := s.luaRouter.GetRouteStats()
+ moduleCount := s.luaRunner.GetModuleCount()
+
+ stats.Components = utils.ComponentStats{
+ RouteCount: routeCount,
+ BytecodeBytes: bytecodeBytes,
+ ModuleCount: moduleCount,
+ }
+
+ // Generate HTML page
+ html := utils.DebugStatsPage(stats)
+
+ // Send the response
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ w.WriteHeader(http.StatusOK)
+ w.Write([]byte(html))
+}
diff --git a/core/routers/luarouter.go b/core/routers/luarouter.go
index 18b6288..4bd8439 100644
--- a/core/routers/luarouter.go
+++ b/core/routers/luarouter.go
@@ -349,3 +349,50 @@ func (r *LuaRouter) GetNodeWithError(method, path string, params *Params) (*Node
Error: node.err,
}, true
}
+
+// GetRouteStats returns statistics about the router
+func (r *LuaRouter) GetRouteStats() (int, int64) {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+
+ routeCount := 0
+ bytecodeBytes := int64(0)
+
+ // Count routes and bytecode size
+ for _, root := range r.routes {
+ count, bytes := countNodesAndBytecode(root)
+ routeCount += count
+ bytecodeBytes += bytes
+ }
+
+ return routeCount, bytecodeBytes
+}
+
+// countNodesAndBytecode traverses the tree and counts nodes and bytecode size
+func countNodesAndBytecode(n *node) (count int, bytecodeBytes int64) {
+ if n == nil {
+ return 0, 0
+ }
+
+ // Count this node if it has a handler
+ if n.handler != "" {
+ count = 1
+ bytecodeBytes = int64(len(n.bytecode))
+ }
+
+ // Count static children
+ for _, child := range n.staticChild {
+ childCount, childBytes := countNodesAndBytecode(child)
+ count += childCount
+ bytecodeBytes += childBytes
+ }
+
+ // Count parameter child
+ if n.paramChild != nil {
+ childCount, childBytes := countNodesAndBytecode(n.paramChild)
+ count += childCount
+ bytecodeBytes += childBytes
+ }
+
+ return count, bytecodeBytes
+}
diff --git a/core/runner/luarunner.go b/core/runner/luarunner.go
index a694bd0..00633cf 100644
--- a/core/runner/luarunner.go
+++ b/core/runner/luarunner.go
@@ -305,3 +305,29 @@ func (r *LuaRunner) RefreshModuleByName(modName string) bool {
func (r *LuaRunner) AddModule(name string, module any) {
r.sandbox.AddModule(name, module)
}
+
+// GetModuleCount returns the number of loaded modules
+func (r *LuaRunner) GetModuleCount() int {
+ r.mu.RLock()
+ defer r.mu.RUnlock()
+
+ count := 0
+
+ // Get module count from Lua
+ if r.state != nil {
+ // Execute a Lua snippet to count modules
+ if res, err := r.state.ExecuteWithResult(`
+ local count = 0
+ for _ in pairs(package.loaded) do
+ count = count + 1
+ end
+ return count
+ `); err == nil {
+ if num, ok := res.(float64); ok {
+ count = int(num)
+ }
+ }
+ }
+
+ return count
+}
diff --git a/core/utils/debug.go b/core/utils/debug.go
new file mode 100644
index 0000000..9439b02
--- /dev/null
+++ b/core/utils/debug.go
@@ -0,0 +1,165 @@
+package utils
+
+import (
+ "fmt"
+ "html/template"
+ "runtime"
+ "strings"
+ "time"
+)
+
+// ComponentStats holds stats from various system components
+type ComponentStats struct {
+ RouteCount int // Number of routes
+ BytecodeBytes int64 // Total size of bytecode in bytes
+ ModuleCount int // Number of loaded modules
+}
+
+// SystemStats represents system statistics for debugging
+type SystemStats struct {
+ Timestamp time.Time
+ GoVersion string
+ GoRoutines int
+ Memory runtime.MemStats
+ Components ComponentStats
+}
+
+// CollectSystemStats gathers basic system statistics
+func CollectSystemStats() SystemStats {
+ var stats SystemStats
+ var mem runtime.MemStats
+
+ // Collect basic system info
+ stats.Timestamp = time.Now()
+ stats.GoVersion = runtime.Version()
+ stats.GoRoutines = runtime.NumGoroutine()
+
+ // Collect memory stats
+ runtime.ReadMemStats(&mem)
+ stats.Memory = mem
+
+ return stats
+}
+
+// DebugStatsPage generates an HTML debug stats page
+func DebugStatsPage(stats SystemStats) string {
+ const debugTemplate = `
+
+
+
+ Moonshark
+
+
+
+ Moonshark
+ Generated at: {{.Timestamp.Format "2006-01-02 15:04:05"}}
+
+
+
System
+
+
+ Go Version | {{.GoVersion}} |
+ Goroutines | {{.GoRoutines}} |
+
+
+
+
+
+
Memory
+
+
+ Allocated | {{ByteCount .Memory.Alloc}} |
+ Total Allocated | {{ByteCount .Memory.TotalAlloc}} |
+ System Memory | {{ByteCount .Memory.Sys}} |
+ GC Cycles | {{.Memory.NumGC}} |
+
+
+
+
+
+
Lua Engine
+
+
+ Active Routes | {{.Components.RouteCount}} |
+ Bytecode Size | {{ByteCount .Components.BytecodeBytes}} |
+ Loaded Modules | {{.Components.ModuleCount}} |
+
+
+
+
+
+`
+
+ // Create a template function map
+ funcMap := template.FuncMap{
+ "ByteCount": func(b interface{}) string {
+ var bytes uint64
+
+ // Convert the value to uint64
+ switch v := b.(type) {
+ case uint64:
+ bytes = v
+ case int64:
+ bytes = uint64(v)
+ case int:
+ bytes = uint64(v)
+ default:
+ return "Unknown"
+ }
+
+ const unit = 1024
+ if bytes < unit {
+ return fmt.Sprintf("%d B", bytes)
+ }
+ div, exp := uint64(unit), 0
+ for n := bytes / unit; n >= unit; n /= unit {
+ div *= unit
+ exp++
+ }
+ return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
+ },
+ }
+
+ // Parse the template
+ tmpl, err := template.New("debug").Funcs(funcMap).Parse(debugTemplate)
+ if err != nil {
+ return fmt.Sprintf("Error parsing template: %v", err)
+ }
+
+ // Execute the template
+ var output strings.Builder
+ if err := tmpl.Execute(&output, stats); err != nil {
+ return fmt.Sprintf("Error executing template: %v", err)
+ }
+
+ return output.String()
+}