Dynatrace OneAgent tracks numerous Go-related metrics.
The Go metrics and Go HTTP metrics tabs on the process overview page contain the most important Go-specific and HTTP metrics. The Go managed memory, Heap details, and Scheduling tabs—all accessible via the Further details tab—bring even more valuable insights into your Go-based application performance.
The Go metrics tab provides an overview of the following important Golang metrics:
On the Go HTTP metrics tab, you can explore the following HTTP metrics:
The Go managed memory tab breaks down memory metrics into various categories:
The Heap details tab digs deeper into the anatomy of the Go heap.
A basic understanding of the internal scheduling principles will help you read scheduling metrics and detect potential anomalies.
The implementation of Goroutine scheduling deals with three central object types: M (Machine), P (Processor), and G (Goroutine). You can find multiple instances of these object types in the Go runtime source code. For simplicity's sake, Dynatrace uses the following alternative terms:
Go executes Goroutines in the context of worker threads that are acquired from a pool of native operating system threads. A Goroutine can be assigned to a different worker thread at any time unless runtime.LockOSThread
is used to enforce worker thread affinity.
Multiple Goroutines are usually assigned to a single worker thread. A scheduling context is responsible for the cooperative scheduling of Goroutines. The Go compiler adds code to each Go function prologue that checks if the currently executed Goroutine consumed its 10 milliseconds execution time slice. The actual mechanism cleverly uses stack guards to enforce rescheduling. If the time slice has exceeded, the scheduling context sets up the next Goroutine to execute. Prior to Go 1.14, scheduling was fully cooperative, leading to an issue with Goroutines that didn't invoke Go functions and therefore were not rescheduled. Go 1.14 introduced a mechanism that allows preemptive scheduling of Goroutines to handle this special case.
Each set of Goroutines is executed by a worker thread. Execution is controlled by a scheduling context. When a Goroutine writes a large chunk of data to the disk or blocks waiting for an incoming connection, the Goroutine is blocked in a system call, and no other Goroutine is scheduled. In such a situation, the scheduling context with other Goroutines is assigned to a different worker thread, either parked or newly instantiated. These Goroutines are not blocked by the system call and can continue execution. Therefore, the total number of worker threads is greater than the number of scheduling context objects. After the blocking call returns, the Goroutine is again assigned to a scheduling context. If the assignment fails, the Goroutine is sent to the global Goroutine run queue. The same principle applies to cgo (Go to C language) calls.
The number of scheduling contexts is the only setting that you can configure in the Go scheduling algorithm. You have no control over the number of worker threads. That is why writing a large chunk of data to the disk or waiting for incoming connections won't block other Goroutines that are initially assigned to the same worker thread. These Goroutines continue execution on their newly assigned worker threads.
The Scheduling tab provides unique insights into Goroutine scheduling.