@@ -50,6 +50,8 @@ func main() {
5050 }
5151}
5252` ;
53+
54+ let burst = ` hey -n 10000 -c 100 http://localhost:3010/liveness ` ;
5355 </script >
5456
5557<PageLayout {backHref } {backText } {title } {date }>
@@ -59,6 +61,9 @@ func main() {
5961
6062 <img src =" {base }/blog/10/statsviz.png" alt =" cert" />
6163
64+ <PageParagraph >
65+ Code on <a href =" https://github.com/gradientsearch/go-service-profiling" class =" underline underline-offset-8 text-sky-500" >GitHub</a >
66+ </PageParagraph >
6267 <PageParagraph >
6368 In this post, we’ll walk through setting up debug endpoints to profile your Go application in
6469 real time. We'll also integrate <a href =" https://github.com/arl/statsviz" class =" text-sky-500"
@@ -89,24 +94,237 @@ func main() {
8994 concurrent request to the liveness endpoint.
9095 </PageParagraph >
9196
92- <Code code ={serve } lang =" go " ></Code >
97+ <Code code ={burst } lang =" bash " ></Code >
9398
94- <PageParagraph >
95- Heap free: is that is free memory that could be returned to the OS, but has not been. Heap
96- released: is memory that is free memory that has been returned to the OS.
97- </PageParagraph >
9899 <PageParagraph >Before burst graph:</PageParagraph >
99100
100101 <img src =" {base }/blog/10/heap-graphs.png" alt =" heap graphs" />
102+
103+ <PageParagraph >After burst graphs:</PageParagraph >
104+
101105 <img src =" {base }/blog/10/heap-details.png" alt =" heap details" />
102106 <img src =" {base }/blog/10/heap-global.png" alt =" heap global" />
103107 <img src =" {base }/blog/10/goroutines.png" alt =" goroutines" />
104108
105- <p >
106- <a
107- target =" _blank"
108- class =" text-sky-500"
109- href =" https://www.ardanlabs.com/training/individual-on-demand/ultimate-go-bundle/" >Courses</a
110- >
111- </p >
109+ <PageParagraph >
110+ From the graphs, you can see that the burst of requests put pressure on the heap, increasing the
111+ heap goal from 4MB to over 6MB. There's also a noticeable spike in goroutines—from 6 to just
112+ over 100—which makes sense given that our command triggered 100 concurrent requests.
113+ </PageParagraph >
114+
115+ <PageParagraph >
116+ Using profiling tools like pprof and Statsviz helps us visualize and understand how our
117+ application behaves under load. In general, a consistent and stable heap graph is desirable, as
118+ it indicates efficient memory usage and garbage collection. Spikes, especially under heavy
119+ traffic, are expected—but persistent growth or erratic behavior could signal memory leaks,
120+ inefficient object lifetimes, or other performance issues worth investigating.
121+ </PageParagraph >
122+
123+ <h2 >⚠️ Warning</h2 >
124+
125+ <PageParagraph >
126+ <strong >Do not expose the debug endpoints to the public or production environments.</strong > The
127+ profiling endpoints provided by the <code >net/http/pprof</code > package expose sensitive runtime
128+ information that could potentially be used for malicious purposes, including heap dumps, goroutine
129+ stacks, and other performance data. These endpoints should be restricted to trusted internal users
130+ or disabled in production.
131+ </PageParagraph >
132+
133+ <PageParagraph >To avoid exposing these endpoints, you can:</PageParagraph >
134+ <PageParagraph >
135+ <ul >
136+ <li >Bind the server to <code >localhost</code > or an internal network interface.</li >
137+ <li >
138+ Use environment variables or build tags to conditionally include profiling in development
139+ environments only.
140+ </li >
141+ <li >
142+ Protect the endpoints with authentication or access control mechanisms (e.g., basic auth or
143+ IP whitelisting).
144+ </li >
145+ </ul >
146+ </PageParagraph >
147+ <PageParagraph >
148+ Be cautious when running this in a production environment and always ensure these endpoints are
149+ not publicly accessible.
150+ </PageParagraph >
151+
152+
153+ <h2 >Metric Descriptions</h2 >
154+
155+ <PageParagraph >
156+ Below are descriptions of the values shown in the legends of the Statsviz graphs.
157+ </PageParagraph >
158+
159+ <table border =" 1" cellpadding =" 8" cellspacing =" 0" >
160+ <thead >
161+ <tr >
162+ <th >Metric</th >
163+ <th >Description</th >
164+ </tr >
165+ </thead >
166+ <tbody >
167+ <tr
168+ ><td >Heap in use</td ><td
169+ >Memory occupied by live objects and dead objects not yet freed by the GC: <code
170+ >/memory/classes/heap/objects + /memory/classes/heap/unused</code
171+ >.</td
172+ ></tr
173+ >
174+ <tr
175+ ><td >Heap free</td ><td
176+ >Memory that could be returned to the OS but hasn't been: <code
177+ >/memory/classes/heap/free</code
178+ >.</td
179+ ></tr
180+ >
181+ <tr
182+ ><td >Heap released</td ><td
183+ >Memory that has been returned to the OS: <code >/memory/classes/heap/free</code >.</td
184+ ></tr
185+ >
186+ <tr
187+ ><td >Heap sys</td ><td
188+ >Estimate of all heap memory obtained from the OS: <code
189+ >/memory/classes/heap/objects + unused + released + free</code
190+ >.</td
191+ ></tr
192+ >
193+ <tr
194+ ><td >Heap objects</td ><td
195+ >Memory used by live and not-yet-freed objects: <code >/memory/classes/heap/objects</code
196+ >.</td
197+ ></tr
198+ >
199+ <tr
200+ ><td >Heap stacks</td ><td
201+ >Memory used for stack space: <code >/memory/classes/heap/stacks</code >.</td
202+ ></tr
203+ >
204+ <tr
205+ ><td >Heap goal</td ><td
206+ >Heap size target for the end of the GC cycle: <code >gc/heap/goal</code >.</td
207+ ></tr
208+ >
209+ <tr
210+ ><td >Live objects</td ><td
211+ >Number of live or unswept objects occupying heap memory: <code >/gc/heap/objects</code
212+ >.</td
213+ ></tr
214+ >
215+ <tr
216+ ><td >Live bytes</td ><td
217+ >Currently allocated bytes (not yet GC’ed): <code >/gc/heap/allocs - /gc/heap/frees</code
218+ >.</td
219+ ></tr
220+ >
221+ <tr
222+ ><td >Mspan in-use</td ><td
223+ >Memory used by active <code >mspan</code > structures:
224+ <code >/memory/classes/metadata/mspan/inuse</code >.</td
225+ ></tr
226+ >
227+ <tr
228+ ><td >Mspan free</td ><td
229+ >Reserved but unused memory for <code >mspan</code >:
230+ <code >/memory/classes/metadata/mspan/free</code >.</td
231+ ></tr
232+ >
233+ <tr
234+ ><td >Mcache in-use</td ><td
235+ >Memory used by active <code >mcache</code > structures:
236+ <code >/memory/classes/metadata/mcache/inuse</code >.</td
237+ ></tr
238+ >
239+ <tr
240+ ><td >Mcache free</td ><td
241+ >Reserved but unused memory for <code >mcache</code >:
242+ <code >/memory/classes/metadata/mcache/free</code >.</td
243+ ></tr
244+ >
245+ <tr ><td >Goroutines</td ><td >Count of live goroutines: <code >/sched/goroutines</code >.</td ></tr >
246+ <tr
247+ ><td >Events per second</td ><td
248+ >Total number of goroutine scheduling events, from <code >/sched/latencies:seconds</code >,
249+ multiplied by 8.</td
250+ ></tr
251+ >
252+ <tr
253+ ><td >Events per second per P</td ><td
254+ >Events per second divided by <code >GOMAXPROCS</code >:
255+ <code >/sched/gomaxprocs:threads</code >.</td
256+ ></tr
257+ >
258+ <tr
259+ ><td >OS stacks</td ><td
260+ >Stack memory allocated by the OS: <code >/memory/classes/os-stacks</code >.</td
261+ ></tr
262+ >
263+ <tr
264+ ><td >Other</td ><td
265+ >Memory for trace buffers, debugging structures, finalizers, and more: <code
266+ >/memory/classes/other</code
267+ >.</td
268+ ></tr
269+ >
270+ <tr
271+ ><td >Profiling buckets</td ><td
272+ >Memory used by profiling stack trace hash map: <code
273+ >/memory/classes/profiling/buckets</code
274+ >.</td
275+ ></tr
276+ >
277+ <tr
278+ ><td >Total</td ><td
279+ >Total memory mapped by the Go runtime: <code >/memory/classes/total</code >.</td
280+ ></tr
281+ >
282+ <tr
283+ ><td >Mark assist</td ><td
284+ >CPU time goroutines spent helping the GC: <code >/cpu/classes/gc/mark/assist</code >.</td
285+ ></tr
286+ >
287+ <tr
288+ ><td >Mark dedicated</td ><td
289+ >CPU time on dedicated processors for GC tasks: <code >/cpu/classes/gc/mark/dedicated</code
290+ >.</td
291+ ></tr
292+ >
293+ <tr
294+ ><td >Mark idle</td ><td
295+ >CPU time for GC tasks using otherwise idle CPU: <code >/cpu/classes/gc/mark/idle</code
296+ >.</td
297+ ></tr
298+ >
299+ <tr
300+ ><td >Pause</td ><td
301+ >Total CPU time the app was paused by the GC: <code >/cpu/classes/gc/pause</code >.</td
302+ ></tr
303+ >
304+ <tr
305+ ><td >GC total</td ><td >Total CPU time spent doing GC: <code >/cpu/classes/gc/total</code >.</td
306+ ></tr
307+ >
308+ <tr
309+ ><td >Mutex wait</td ><td
310+ >Time goroutines spent blocked on a <code >sync.Mutex</code > or <code >sync.RWMutex</code >:
311+ <code >/sync/mutex/wait/total</code >.</td
312+ ></tr
313+ >
314+ <tr
315+ ><td >Scannable globals</td ><td
316+ >Amount of scannable global variable space: <code >/gc/scan/globals</code >.</td
317+ ></tr
318+ >
319+ <tr
320+ ><td >Scannable heap</td ><td >Amount of scannable heap space: <code >/gc/scan/heap</code >.</td
321+ ></tr
322+ >
323+ <tr
324+ ><td >Scanned stack</td ><td
325+ >Bytes of stack scanned during the last GC cycle: <code >/gc/scan/stack</code >.</td
326+ ></tr
327+ >
328+ </tbody >
329+ </table >
112330</PageLayout >
0 commit comments