Skip to content

Commit 074bfc7

Browse files
committed
feat(pprof): add pprofIndexPage for runtime profiling visualization
1 parent 1109d4c commit 074bfc7

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed

pprof.go

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
package forge
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"runtime"
7+
"runtime/pprof"
8+
)
9+
10+
// pprofIndexPage renders a styled HTML index of all available pprof profiles.
11+
func pprofIndexPage(w http.ResponseWriter, _ *http.Request, prefix string) {
12+
var m runtime.MemStats
13+
runtime.ReadMemStats(&m)
14+
15+
profiles := pprof.Profiles()
16+
17+
w.Header().Set("Content-Type", "text/html; charset=utf-8")
18+
w.Header().Set("X-Content-Type-Options", "nosniff")
19+
20+
fmt.Fprintf(w, `<!DOCTYPE html>
21+
<html lang="en">
22+
<head>
23+
<meta charset="utf-8">
24+
<meta name="viewport" content="width=device-width, initial-scale=1">
25+
<title>Forge Profiling</title>
26+
<style>
27+
*{box-sizing:border-box;margin:0;padding:0}
28+
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:#0f172a;color:#e2e8f0;padding:2rem}
29+
h1{font-size:1.5rem;margin-bottom:.25rem;color:#f8fafc}
30+
.sub{color:#94a3b8;margin-bottom:2rem;font-size:.875rem}
31+
.grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:1rem;margin-bottom:2rem}
32+
.card{background:#1e293b;border:1px solid #334155;border-radius:.5rem;padding:1.25rem}
33+
.card h2{font-size:1rem;color:#f1f5f9;margin-bottom:.25rem}
34+
.card p{font-size:.8rem;color:#94a3b8;margin-bottom:.75rem}
35+
.card .count{font-size:1.75rem;font-weight:700;color:#38bdf8;margin-bottom:.75rem}
36+
.links{display:flex;gap:.75rem}
37+
.links a{font-size:.8rem;color:#38bdf8;text-decoration:none;padding:.25rem .5rem;border:1px solid #334155;border-radius:.25rem;transition:background .15s}
38+
.links a:hover{background:#334155}
39+
.stats{display:grid;grid-template-columns:repeat(auto-fill,minmax(160px,1fr));gap:.75rem;margin-bottom:2rem}
40+
.stat{background:#1e293b;border:1px solid #334155;border-radius:.5rem;padding:1rem}
41+
.stat .label{font-size:.75rem;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em}
42+
.stat .value{font-size:1.25rem;font-weight:600;color:#f1f5f9;margin-top:.25rem}
43+
.section{font-size:.875rem;font-weight:600;color:#94a3b8;text-transform:uppercase;letter-spacing:.05em;margin-bottom:.75rem}
44+
.special{margin-top:1rem}
45+
.special .card{border-color:#854d0e}
46+
.special .card h2{color:#fbbf24}
47+
.tip{margin-top:2rem;padding:1rem;background:#1e293b;border:1px solid #334155;border-radius:.5rem;font-size:.8rem;color:#94a3b8}
48+
.tip code{background:#334155;padding:.125rem .375rem;border-radius:.25rem;font-size:.8rem;color:#e2e8f0}
49+
</style>
50+
</head>
51+
<body>
52+
<h1>Forge Profiling</h1>
53+
<p class="sub">Runtime profiling data for diagnostics and performance analysis</p>
54+
55+
<div class="section">Runtime Stats</div>
56+
<div class="stats">
57+
<div class="stat"><div class="label">Goroutines</div><div class="value">%d</div></div>
58+
<div class="stat"><div class="label">Heap In-Use</div><div class="value">%.1f MB</div></div>
59+
<div class="stat"><div class="label">Heap Objects</div><div class="value">%d</div></div>
60+
<div class="stat"><div class="label">Stack In-Use</div><div class="value">%.1f MB</div></div>
61+
<div class="stat"><div class="label">Sys Memory</div><div class="value">%.1f MB</div></div>
62+
<div class="stat"><div class="label">GC Cycles</div><div class="value">%d</div></div>
63+
</div>
64+
`,
65+
runtime.NumGoroutine(),
66+
float64(m.HeapInuse)/1024/1024,
67+
m.HeapObjects,
68+
float64(m.StackInuse)/1024/1024,
69+
float64(m.Sys)/1024/1024,
70+
m.NumGC,
71+
)
72+
73+
// Profile cards
74+
fmt.Fprint(w, `<div class="section">Profiles</div><div class="grid">`)
75+
76+
descriptions := map[string]string{
77+
"goroutine": "Stack traces of all current goroutines",
78+
"heap": "Heap memory allocations of live objects",
79+
"allocs": "All past memory allocations (including freed)",
80+
"threadcreate": "Stack traces that led to new OS threads",
81+
"block": "Stack traces of goroutines blocked on synchronization",
82+
"mutex": "Stack traces of mutex contention holders",
83+
}
84+
85+
for _, p := range profiles {
86+
name := p.Name()
87+
desc := descriptions[name]
88+
if desc == "" {
89+
desc = "Profile data"
90+
}
91+
92+
fmt.Fprintf(w, `<div class="card">
93+
<h2>%s</h2>
94+
<p>%s</p>
95+
<div class="count">%d</div>
96+
<div class="links">
97+
<a href="%s/%s?debug=1">View</a>
98+
<a href="%s/%s?debug=2">Full</a>
99+
<a href="%s/%s" download="%s.pb.gz">Download</a>
100+
</div>
101+
</div>`, name, desc, p.Count(), prefix, name, prefix, name, prefix, name, name)
102+
}
103+
104+
fmt.Fprint(w, `</div>`)
105+
106+
// Special profiling endpoints
107+
fmt.Fprintf(w, `<div class="section">CPU &amp; Trace</div>
108+
<div class="grid special">
109+
<div class="card">
110+
<h2>CPU Profile</h2>
111+
<p>Record a CPU profile for the specified duration (default 30s)</p>
112+
<div class="links">
113+
<a href="%s/profile?seconds=5" download="cpu.pb.gz">5s profile</a>
114+
<a href="%s/profile?seconds=15" download="cpu.pb.gz">15s profile</a>
115+
<a href="%s/profile?seconds=30" download="cpu.pb.gz">30s profile</a>
116+
</div>
117+
</div>
118+
<div class="card">
119+
<h2>Execution Trace</h2>
120+
<p>Record an execution trace for the specified duration (default 1s)</p>
121+
<div class="links">
122+
<a href="%s/trace?seconds=1" download="trace.out">1s trace</a>
123+
<a href="%s/trace?seconds=5" download="trace.out">5s trace</a>
124+
</div>
125+
</div>
126+
<div class="card">
127+
<h2>Command Line</h2>
128+
<p>The command line invocation of the running program</p>
129+
<div class="links">
130+
<a href="%s/cmdline">View</a>
131+
</div>
132+
</div>
133+
</div>`, prefix, prefix, prefix, prefix, prefix, prefix)
134+
135+
// Usage tip
136+
fmt.Fprintf(w, `<div class="tip">
137+
<strong>Tip:</strong> For interactive flamegraphs, use <code>go tool pprof</code>:<br><br>
138+
<code>go tool pprof -http=:6060 http://localhost:PORT%s/heap</code><br>
139+
<code>go tool pprof -http=:6060 http://localhost:PORT%s/profile?seconds=10</code>
140+
</div>
141+
</body></html>`, prefix, prefix)
142+
}

0 commit comments

Comments
 (0)