$re = '/(<\s*[^>]*)(href=|src=)(.?)(\/[^\/])/m';
$str = '<div id="blog"><div id="content">
<div id="content">
<div class="Article" data-slug="/blog/execution-traces-2024">
<h1 class="small"><a href="//go.dev/blog/">The Go Blog</a></h1>
<h1>More powerful Go execution traces</h1>
<p class="author">
Michael Knyszek<br>
14 March 2024
</p>
<p>The <a href="/pkg/runtime/trace">runtime/trace</a> package contains a powerful tool for understanding and
troubleshooting Go programs.
The functionality within allows one to produce a trace of each goroutine’s execution over some
time period.
With the <a href="/pkg/cmd/trace"><code>go tool trace</code> command</a> (or the excellent open source
<a href="https://gotraceui.dev/" rel="noreferrer" target="_blank">gotraceui tool</a>), one may then visualize and explore the data within these
traces.</p>
<p>The magic of a trace is that it can easily reveal things about a program that are hard to see in
other ways.
For example, a concurrency bottleneck where lots of goroutines block on the same channel might be
quite difficult to see in a CPU profile, because there’s no execution to sample.
But in an execution trace, the <em>lack</em> of execution will show up with amazing clarity, and the stack
traces of blocked goroutines will quickly point at the culprit.</p>
<div class="image">
<img src="execution-traces-2024/gotooltrace.png" alt="">
</div>
<p>Go developers are even able to instrument their own programs with <a href="//go.dev/pkg/runtime/trace#Task">tasks</a>,
<a href="//go.dev/pkg/runtime/trace#WithRegion">regions</a>, and <a href="//go.dev/pkg/runtime/trace#Log">logs</a> that
they can use to correlate their higher-level concerns with lower-level execution details.</p>
<h2 id="issues">Issues</h2>
<p>Unfortunately, the wealth of information in execution traces can often be out of reach.
Four big issues with traces have historically gotten in the way.</p>
<ul>
<li>Traces had high overheads.</li>
<li>Traces didn’t scale well, and could become too big to analyze.</li>
<li>It was often unclear when to start tracing to capture a specific bad behavior.</li>
<li>Only the most adventurous gophers could programmatically analyze traces, given the lack of a
public package for parsing and interpreting execution traces.</li>
</ul>
<p>If you’ve used traces in the last few years, you’ve likely been frustrated by one or more of these
problems.
But we’re excited to share that over the last two Go releases we’ve made big progress in all four
of these areas.</p>
<h2 id="low-overhead-tracing">Low-overhead tracing</h2>
<p>Prior to Go 1.21, the run-time overhead of tracing was somewhere between 10–20% CPU for many
applications, which limits tracing to situational usage, rather than continuous usage like CPU
profiling.
It turned out that much of the cost of tracing came down to tracebacks.
Many events produced by the runtime have stack traces attached, which are invaluable to actually
identifying what goroutines were doing at key moments in their execution.</p>
<p>Thanks to work by Felix Geisendörfer and Nick Ripley on optimizing the efficiency of tracebacks,
the run-time CPU overhead of execution traces has been cut dramatically, down to 1–2% for many
applications.
You can read more about the work done here in <a href="https://blog.felixge.de/reducing-gos-execution-tracer-overhead-with-frame-pointer-unwinding/" rel="noreferrer" target="_blank">Felix’s great blog
post</a>
on the topic.</p>
<h2 id="scalable-traces">Scalable traces</h2>
<p>The trace format and its events were designed around relatively efficient emission, but required
tooling to parse and keep around the state of the entirety of a trace.
A few hundred MiB trace could require several GiB of RAM to analyze!</p>
<p>This issue is unfortunately fundamental to how traces are generated.
To keep run-time overheads low, all events are written to the equivalent of thread-local buffers.
But this means events appear out of their true order, and the burden is placed on the trace
tooling to figure out what really happened.</p>
<p>The key insight to making traces scale while keeping overheads low was to occasionally split the
trace being generated.
Each split point would behave a bit like simultaneously disabling and reenabling tracing in one
go.
All the trace data so far would represent a complete and self-contained trace, while the new trace
data would seamlessly pick up from where it left off.</p>
<p>As you might imagine, fixing this required <a href="//go.dev/issue/60773">rethinking and rewriting a lot of the foundation of
the trace implementation</a> in the runtime.
We’re happy to say that the work landed in Go 1.22 and is now generally available.
<a href="//go.dev/doc/go1.22#runtime/trace">A lot of nice improvements</a> came with the rewrite, including some
improvements to the <a href="//go.dev/doc/go1.22#trace"><code>go tool trace</code> command</a> as well.
The gritty details are all in the <a href="https://github.com/golang/proposal/blob/master/design/60773-execution-tracer-overhaul.md" rel="noreferrer" target="_blank">design
document</a>,
if you’re curious.</p>
<p>(Note: <code>go tool trace</code> still loads the full trace into memory, but <a href="//go.dev/issue/65315">removing this
limitation</a> for traces produced by Go 1.22+ programs is now feasible.)</p>
<h2 id="flight-recording">Flight recording</h2>
<p>Suppose you work on a web service and an RPC took a very long time.
You couldn’t start tracing at the point you knew the RPC was already taking a while, because the
root cause of the slow request already happened and wasn’t recorded.</p>
<p>There’s a technique that can help with this called flight recording, which you may already be
familiar with from other programming environments.
The insight with flight recording is to have tracing on continuously and always keep the most
recent trace data around, just in case.
Then, once something interesting happens, the program can just write out whatever it has!</p>
<p>Before traces could be split, this was pretty much a non-starter.
But because continuous tracing is now viable thanks to low overheads, and the fact that the runtime
can now split traces any time it needs, it turns out it was straightforward to implement flight
recording.</p>
<p>As a result, we’re happy to announce a flight recorder experiment, available in the
<a href="//go.dev/pkg/golang.org/x/exp/trace#FlightRecorder">golang.org/x/exp/trace package</a>.</p>
<p>Please try it out!
Below is an example that sets up flight recording to capture a long HTTP request to get you started.</p>
<div class="code">
<pre> <span class="comment">// Set up the flight recorder.</span>
fr := trace.NewFlightRecorder()
fr.Start()
<span class="comment">// Set up and run an HTTP server.</span>
var once sync.Once
http.HandleFunc("/my-endpoint", func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
<span class="comment">// Do the work...</span>
doWork(w, r)
<span class="comment">// We saw a long request. Take a snapshot!</span>
if time.Since(start) > 300*time.Millisecond {
<span class="comment">// Do it only once for simplicity, but you can take more than one.</span>
once.Do(func() {
<span class="comment">// Grab the snapshot.</span>
var b bytes.Buffer
_, err = fr.WriteTo(&b)
if err != nil {
log.Print(err)
return
}
<span class="comment">// Write it to a file.</span>
if err := os.WriteFile("trace.out", b.Bytes(), 0o755); err != nil {
log.Print(err)
return
}
})
}
})
log.Fatal(http.ListenAndServe(":8080", nil))
</pre>
</div>
<p>If you have any feedback, positive or negative, please share it to the <a href="//go.dev/issue/63185">proposal
issue</a>!</p>
<h2 id="trace-reader-api">Trace reader API</h2>
<p>Along with the trace implementation rewrite came an effort to clean up the other trace internals,
like <code>go tool trace</code>.
This spawned an attempt to create a trace reader API that was good enough to share and that could
make traces more accessible.</p>
<p>Just like the flight recorder, we’re happy to announce that we also have an experimental trace reader
API that we’d like to share.
It’s available in the <a href="//go.dev/pkg/golang.org/x/exp/trace#Reader">same package as the flight recorder,
golang.org/x/exp/trace</a>.</p>
<p>We think it’s good enough to start building things on top of, so please try it out!
Below is an example that measures the proportion of goroutine block events that blocked to wait on
the network.</p>
<div class="code">
<pre> <span class="comment">// Start reading from STDIN.</span>
r, err := trace.NewReader(os.Stdin)
if err != nil {
log.Fatal(err)
}
var blocked int
var blockedOnNetwork int
for {
<span class="comment">// Read the event.</span>
ev, err := r.ReadEvent()
if err == io.EOF {
break
} else if err != nil {
log.Fatal(err)
}
<span class="comment">// Process it.</span>
if ev.Kind() == trace.EventStateTransition {
st := ev.StateTransition()
if st.Resource.Kind == trace.ResourceGoroutine {
from, to := st.Goroutine()
<span class="comment">// Look for goroutines blocking, and count them.</span>
if from.Executing() && to == trace.GoWaiting {
blocked++
if strings.Contains(st.Reason, "network") {
blockedOnNetwork++
}
}
}
}
}
<span class="comment">// Print what we found.</span>
p := 100 * float64(blockedOnNetwork) / float64(blocked)
fmt.Printf("%2.3f%% instances of goroutines blocking were to block on the network\\n", p)
</pre>
</div>
<p>And just like the flight recorder, there’s a <a href="//go.dev/issue/62627">proposal issue</a> that would
be a great place to leave feedback!</p>
<p>We’d like to quickly call out Dominik Honnef as someone who tried it out early, provided great
feedback, and has contributed support for older trace versions to the API.</p>
<h2 id="thank-you">Thank you!</h2>
<p>This work was completed, in no small part, thanks to the help of the those in the <a href="//go.dev/issue/57175">diagnostics
working group</a>, started over a year ago as a collaboration between stakeholders from
across the Go community, and open to the public.</p>
<p>We’d like to take a moment to thank those community members who have attended the diagnostic
meetings regularly over the last year: Felix Geisendörfer, Nick Ripley, Rhys Hiltner, Dominik
Honnef, Bryan Boreham, thepudds.</p>
<p>The discussions, feedback, and work you all put in have been instrumental to getting us to where we
are today.
Thank you!</p>
</div>
<div class="Article prevnext">
<p>
<b>Previous article: </b><a href="//go.dev/blog/generic-slice-functions">Robust generic functions on slices</a><br>
<b><a href="//go.dev/blog/all">Blog Index</a></b>
</div>
</div>
</div>
<script src="/js/jquery.js"></script>
<script src="/js/playground.js"></script>
<script src="/js/play.js"></script>
<script src="/js/godocs.js"></script>';
preg_match_all($re, $str, $matches, PREG_SET_ORDER, 0);
// Print the entire match result
var_dump($matches);
Please keep in mind that these code samples are automatically generated and are not guaranteed to work. If you find any syntax errors, feel free to submit a bug report. For a full regex reference for PHP, please visit: http://php.net/manual/en/ref.pcre.php