debug page
This commit is contained in:
parent
454e8fa5b0
commit
da48f65e43
|
@ -74,6 +74,18 @@ func (s *Server) Shutdown(ctx context.Context) error {
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
start := time.Now()
|
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
|
// Wrap the ResponseWriter to capture status code
|
||||||
wrappedWriter := newStatusCaptureWriter(w)
|
wrappedWriter := newStatusCaptureWriter(w)
|
||||||
|
|
||||||
|
@ -281,3 +293,27 @@ func setContentTypeIfMissing(w http.ResponseWriter, contentType string) {
|
||||||
w.Header().Set("Content-Type", contentType)
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -349,3 +349,50 @@ func (r *LuaRouter) GetNodeWithError(method, path string, params *Params) (*Node
|
||||||
Error: node.err,
|
Error: node.err,
|
||||||
}, true
|
}, 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
|
||||||
|
}
|
||||||
|
|
|
@ -305,3 +305,29 @@ func (r *LuaRunner) RefreshModuleByName(modName string) bool {
|
||||||
func (r *LuaRunner) AddModule(name string, module any) {
|
func (r *LuaRunner) AddModule(name string, module any) {
|
||||||
r.sandbox.AddModule(name, module)
|
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
|
||||||
|
}
|
||||||
|
|
165
core/utils/debug.go
Normal file
165
core/utils/debug.go
Normal file
|
@ -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 = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Moonshark</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: sans-serif;
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background-color: #333;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
h1 {
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #4F5B93;
|
||||||
|
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
h2 { margin-top: 25px; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin: 15px 0; }
|
||||||
|
th { width: 1%; white-space: nowrap; border-right: 1px solid rgba(0, 0, 0, 0.1); }
|
||||||
|
th, td { text-align: left; padding: 0.75rem 0.5rem; border-bottom: 1px solid #ddd; }
|
||||||
|
tr:last-child th, tr:last-child td { border-bottom: none; }
|
||||||
|
table tr:nth-child(even), tbody tr:nth-child(even) { background-color: rgba(0, 0, 0, 0.1); }
|
||||||
|
.card {
|
||||||
|
background: #F2F2F2;
|
||||||
|
color: #333;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
box-shadow: 0 2px 4px 0px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
.timestamp { color: #999; font-size: 0.9em; }
|
||||||
|
.section { margin-bottom: 30px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>Moonshark</h1>
|
||||||
|
<div class="timestamp">Generated at: {{.Timestamp.Format "2006-01-02 15:04:05"}}</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>System</h2>
|
||||||
|
<div class="card">
|
||||||
|
<table>
|
||||||
|
<tr><th>Go Version</th><td>{{.GoVersion}}</td></tr>
|
||||||
|
<tr><th>Goroutines</th><td>{{.GoRoutines}}</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Memory</h2>
|
||||||
|
<div class="card">
|
||||||
|
<table>
|
||||||
|
<tr><th>Allocated</th><td>{{ByteCount .Memory.Alloc}}</td></tr>
|
||||||
|
<tr><th>Total Allocated</th><td>{{ByteCount .Memory.TotalAlloc}}</td></tr>
|
||||||
|
<tr><th>System Memory</th><td>{{ByteCount .Memory.Sys}}</td></tr>
|
||||||
|
<tr><th>GC Cycles</th><td>{{.Memory.NumGC}}</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="section">
|
||||||
|
<h2>Lua Engine</h2>
|
||||||
|
<div class="card">
|
||||||
|
<table>
|
||||||
|
<tr><th>Active Routes</th><td>{{.Components.RouteCount}}</td></tr>
|
||||||
|
<tr><th>Bytecode Size</th><td>{{ByteCount .Components.BytecodeBytes}}</td></tr>
|
||||||
|
<tr><th>Loaded Modules</th><td>{{.Components.ModuleCount}}</td></tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user