Profiling ClickHouse with LLVM's XRay
Types of profilers
LLVM already includes a tool that instruments the code that allows us to do instrumentation profiling. As opposed to sampling or statistical profiling, it's very precise without losing any calls, at the expense of needing to instrument the code and be more resource expensive.
In a few words, an instrumentation profiler introduces new code to track the call to all functions. Statistical profilers allow us to run the code without requiring any changes, taking snapshots periodically to see the state of the application. So, only the functions running while the snapshot is taken are considered. perf is a very well-known statistical profiler.
Profiling ClickHouse using XRay's integration
On ClickHouse 25.12, XRay is integrated to seamlessly add new instrumentation points to functions. So, any official release already comes with this feature that can be triggered on demand, without affecting the overall performance when not enabled. The idea is to enable the minimum amount of instrumentation points to get valuable information.
We can add a new profile instrumentation point using the SYSTEM INSTRUMENT ADD
PROFILE
statement. The functions to be instrumented can be collected from
system.symbols system table. Say we
want to profile the sleepForNanoseconds function, which is a convenient function to check how long
it takes to run.
Then, we leave it running for the time period we want to profile and stop it.
We convert the data collected in system.trace_log to Chrome format to visualize it in Perfetto. Notice the query_id, cpu_id and stacktrace for every entry.
Profiling a native application using XRay
The following section is left as a reference to know how XRay works under the hood and how it can be used out of the box to profile a native application.
Instrument the code
Imagine the following souce code:
In order to instrument with XRay, we need to add some flags like so:
-fxray-instrumentis needed to instrument the code.-fxray-instruction-threshold=1is used so that it instruments all functions, even if they're very small as in our example. By default, it instruments functions with at least 200 instructions.
We can ensure the code has been instrumented correctly by checking there's a new section in the binary:
Run the process with proper env var values to collect the trace
By default, there is no profiler collection unless explicitly asked for. In other words, unless
we're profiling the overhead is negligible. We can set different values for XRAY_OPTIONS to
configure when the profiler starts collecting and how it does so.
Convert the trace
XRay's traces can be converted to several formats. The trace_event format is very useful because
it's easy to parse and there are already a number of tools that support it, so we'll use that one:
Visualize the trace
We can use web-based UIs like speedscope.app or Perfetto.
While Perfetto makes visualizing multiple threads and querying the data easier, speedscope is better generating a flamegraph and a sandwich view of your data.
Time Order
Left Heavy
Sandwitch
Check out the docs
- SYSTEM INSTRUMENT — Add or remove instrumentation points.
- system.instrumentation — Inspect instrumented points.
- system.symbols — Inspect symbols to add instrumentation points.
- system.trace_log — Inspect data collected using instrumentation points.
- XRay Instrumentation
- Debugging with XRay documentation to learn more details.