When an etcd server burns high CPU, the fastest way to find the cause is to profile the live process and read its Go-level CPU flame graph. In this tutorial, OpenResty XRay analyzes an unmodified etcd binary consuming over 70% of a CPU core — with no code changes and no restart — and pinpoints the hottest Go code paths: gRPC request handling (processUnaryRPC), the key-value range and put handlers, and Go runtime stack growth (runtime.newstack).

In this tutorial, I will demonstrate how the CPU time is spent qualitatively inside Go’s etcd server with OpenResty XRay. I will show the most CPU-intensive Go code paths in it. OpenResty XRay will automatically analyze the Go (golang) language level CPU flame graph.

Symptom: etcd Consuming Over 70% of a CPU Core

Running top shows the etcd process consuming more than 70% of a CPU core (74.3% here):

The top command showing the etcd process using 74.3% of a CPU core

The ps command confirms this is a standard etcd binary (/usr/local/bin/etcd) started with ordinary cluster flags — no custom build, no profiling instrumentation, and no changes of any kind:

The ps command showing the unmodified /usr/local/bin/etcd binary and its command line

Find etcd’s Hottest Go Code Paths with Guided Analysis

With OpenResty XRay, we can analyze this unmodified, running process in real time. The “Guided Analysis” wizard walks through selecting a diagnosis type, a target, and an application type. We pick High CPU usage as the problem to diagnose:

OpenResty XRay Guided Analysis problem-type menu with High CPU usage among the options

Then we select the live etcd process as the analysis target. OpenResty XRay detects it as a Go application (PID 3638889, ~76% CPU), and we keep the default Go language level and the default 300-second maximum running time before starting the analysis:

Selecting the live etcd Go process as the analysis target in OpenResty XRay

After a couple of sampling rounds, OpenResty XRay generates a report. It ranks the hottest Go-land code paths for CPU time, alongside how much CPU the read and write system calls consume:

OpenResty XRay CPU report ranking etcd’s hottest Go code paths, led by processUnaryRPC at 17.6%

This is the #1 hottest Go-land code path for CPU time, taking 17.6% of it:

The #1 hottest Go code path in the report, taking 17.6% of the CPU time

The processUnaryRPC function is in the Go gRPC library. It’s responsible for handling the simplest type of gRPC messages — a single request answered by a single response:

The processUnaryRPC function from the Go gRPC library highlighted in the code path

This function is called by the handleStream function:

The code path showing handleStream as the caller of processUnaryRPC

Click “More” to see details about this code path:

Clicking the More button to see details about this code path

Reading the Go CPU Flame Graph

The code path above was automatically derived from this Go-land CPU flame graph. In the flame graph, the width of each frame is proportional to its share of CPU time:

Go-land CPU time flame graph for the live etcd process, sampled by OpenResty XRay

Click the icon to enlarge the flame graph:

Clicking the icon to enlarge the Go CPU flame graph

Continue to zoom in:

Zooming further into the hottest backtrace of the flame graph

The function _KV_Range_Handler gets the keys in the range from the key-value store:

Flame graph zoomed into etcd’s _KV_Range_Handler function under processUnaryRPC

The function _KV_Put_Handler puts the given key into the key-value store:

The etcd _KV_Put_Handler function in the flame graph, writing a key into the key-value store

The Range function is used to query the key-value data stored in etcd by range:

The etcd Range function in the flame graph, querying key-value data by range

It calls runtime.newobject to create a large number of golang GC objects:

Flame graph showing runtime.newobject allocating many Go GC objects under the Range query

The function runtime.newstack has a high CPU overhead when writing data to etcd. This function is an internal function of the Go language runtime. It is used to create a new stack space for a goroutine:

Flame graph showing the runtime.newstack overhead on etcd’s write path

Below are more detailed explanations and suggestions regarding the current issue, such as reducing the data sent over the network, using connection pooling, and tuning gRPC settings:

OpenResty XRay explanation of the processUnaryRPC code path with optimization suggestions

It mentions the function processUnaryRPC:

The processUnaryRPC function mentioned in the explanation text

And it processes the unary RPC:

The explanation noting that processUnaryRPC handles unary RPCs

Jumping to the Source Line

Let’s go back to the code path. Hover the mouse over the green box for the first function:

Hovering the mouse over the green box for the first function in the code path

We can see the source file of this function, and the full path for the server.go file in the tooltip:

OpenResty XRay tooltip showing the full path of the processUnaryRPC source file grpc server.go

The source line number is 1024:

The tooltip showing source line number 1024

Click the icon to copy the full Go source file path for this function:

Clicking the icon to copy the full Go source file path

Use the find command to find the source file:

Using the find command in the terminal to locate the Go source file

Paste the file path we just copied:

Pasting the copied source file path into the find command

Copy the full file path. Use the vim editor to open the source file and look at the golang code in this file. You can use any editors you like:

Opening the grpc server.go source file in the vim editor

Go to line 1024, as OpenResty XRay suggested:

Jumping to line 1024 of grpc server.go in vim

The function md.Handler calls different message handlers depending on the type of gRPC message. The _KV_Range_Handler and _KV_Put_Handler we saw earlier are two examples of such md.Handler callbacks:

The md.Handler call at source line 1024 dispatching to the gRPC message handlers

On the status bar you can see that this source line is indeed inside the processUnaryRPC function, as the report mentioned:

The vim status bar confirming line 1024 is inside the processUnaryRPC function

The Other Hot Code Paths

The second hottest code path consumes about 12% of the CPU time:

The #2 hottest Go code path in the report, taking about 12% of the CPU time

From this function, we know it is writing to a network socket:

The code path showing the function writing response data to a network socket

This is invoking the write system call:

The write system call at the bottom of the code path

This function sends response data to the network sockets via the HTTP/2 protocol:

The function sending response data over the HTTP/2 protocol in the code path

The third hottest code path consumes about 11% of the CPU time:

The #3 hottest Go code path in the report, taking about 11% of the CPU time

Here, runtime.mcall mainly executes the scheduling of goroutines:

The runtime.mcall function in the code path, scheduling goroutines

Now we see the fourth hottest Go code path, taking about 10.5% of the CPU time:

The #4 hottest Go code path in the report, taking about 10.5% of the CPU time

This is for logging every unary gRPC call. We might want to skip such logging to save CPU time:

The code path for logging every unary gRPC call

Automatic etcd CPU Monitoring and Reports

Beyond on-demand Guided Analysis, OpenResty XRay can also monitor online processes automatically and produce daily and weekly reports on the “Insights” page. For this etcd instance, the daily report shows CPU usage averaging 51.33% (peaking at 164%), with the write system call and Go GC object allocation among the top CPU consumers:

OpenResty XRay Insights daily report for etcd, showing average and peak CPU usage and the top CPU consumers

Because these reports are generated automatically, you don’t have to run Guided Analysis yourself — Guided Analysis is most useful for application development and demonstration.

FAQ

Why is my etcd server using so much CPU?

In this case, most of etcd’s CPU time went to handling gRPC requests. The single hottest Go code path was processUnaryRPC in the Go gRPC library (17.6% of CPU time), which dispatches to etcd’s _KV_Range_Handler and _KV_Put_Handler — the functions that read key ranges from, and write keys to, the key-value store. Sending response data over HTTP/2 and Go runtime work (runtime.newobject, runtime.newstack, and goroutine scheduling via runtime.mcall) accounted for most of the rest. The same techniques apply to any Go program with high CPU usage; see how to find the hottest Go code paths.

How can I profile etcd CPU usage without changing or restarting it?

OpenResty XRay analyzes the live, unmodified etcd process directly — no code changes, no rebuild, and no restart. Its “Guided Analysis” feature samples the running process and automatically derives the Go-level CPU flame graph and the hottest code paths.

What are the hottest code paths in etcd under high CPU?

For this workload the top paths were: (1) processUnaryRPC handling unary gRPC calls into the KV range and put handlers (17.6%); (2) writing response data to network sockets over HTTP/2 (11.7%); (3) runtime.mcall scheduling goroutines (11.1%); and (4) logging every unary gRPC call (10.5%) — which you may be able to skip to save CPU time.

What is OpenResty XRay

OpenResty XRay is a dynamic-tracing product that automatically analyzes your running applications to troubleshoot performance problems, behavioral issues, and security vulnerabilities with actionable suggestions. Under the hood, OpenResty XRay is powered by our Y language targeting various runtimes like Stap+, eBPF+, GDB, and ODB, depending on the contexts.

If you like this tutorial, please subscribe to this blog site and/or our YouTube channel. Thank you!

About The Author

Yichun Zhang (Github handle: agentzh), is the original creator of the OpenResty® open-source project and the CEO of OpenResty Inc..

Yichun is one of the earliest advocates and leaders of “open-source technology”. He worked at many internationally renowned tech companies, such as Cloudflare, Yahoo!. He is a pioneer of “edge computing”, “dynamic tracing” and “machine coding”, with over 22 years of programming and 16 years of open source experience. Yichun is well-known in the open-source space as the project leader of OpenResty®, adopted by more than 40 million global website domains.

OpenResty Inc., the enterprise software start-up founded by Yichun in 2017, has customers from some of the biggest companies in the world. Its flagship product, OpenResty XRay, is a non-invasive profiling and troubleshooting tool that significantly enhances and utilizes dynamic tracing technology. And its OpenResty Edge product is a powerful distributed traffic management and private CDN software product.

As an avid open-source contributor, Yichun has contributed more than a million lines of code to numerous open-source projects, including Linux kernel, Nginx, LuaJIT, GDB, SystemTap, LLVM, Perl, etc. He has also authored more than 60 open-source software libraries.