<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>eBPF · XB Field Notes</title><link>https://hk.xbcatm.com/tags/ebpf/</link><description>Illustrated field notes on HTTP, DNS, TLS, Caddy, SSH, releases, and recovery.</description><generator>Hugo</generator><language>en-us</language><atom:link href="https://hk.xbcatm.com/tags/ebpf/index.xml" rel="self" type="application/rss+xml"/><item><title>BPF tracepoints peek inside the running kernel</title><link>https://hk.xbcatm.com/posts/bpf-tracepoint-perf-profiling/</link><pubDate>Mon, 06 Mar 2028 00:00:00 +0000</pubDate><guid>https://hk.xbcatm.com/posts/bpf-tracepoint-perf-profiling/</guid><description>Kernel tracepoints and BPF count selected live events; attachment permissions, measurement scope, and overhead still need checking.</description><content:encoded>&lt;p&gt;BPF programs can attach to selected events in a running kernel without rebuilding the application or restarting its workload. A userspace loader still loads and manages the probe. Collection changes resource use, so “live” does not mean invisible or free. Other profilers can also observe running programs; choosing BPF depends on the question and available instrumentation.&lt;/p&gt;
&lt;p&gt;Define the event, aggregation key, observation interval, and excluded work before interpreting output. A syscall count, a scheduling delay, and a device latency distribution answer different questions. An entry count alone cannot reveal successful operations, time spent waiting, or which storage path served a request.&lt;/p&gt;
&lt;h2 id="probe-without-pausing"&gt;Probe without pausing&lt;/h2&gt;
&lt;p&gt;An eBPF program can accumulate counts or histograms in kernel maps while the workload continues. Aggregation reduces userspace output, but every observed event may still execute probe work. The verifier checks program safety under kernel rules; acceptance is not proof of correct measurement, negligible overhead, or an impossible kernel failure.&lt;/p&gt;
&lt;p&gt;Start with a bounded question. Counting syscall entries needs an entry event; measuring syscall duration normally requires matching entry and exit state. Block-device latency needs its own event lifecycle. High-level tools such as bpftrace help express these measurements, but a short program does not establish that its units, filters, and correlations are correct.&lt;/p&gt;
&lt;h2 id="tracepoints-are-named-events"&gt;Tracepoints are named events&lt;/h2&gt;
&lt;p&gt;Tracepoints expose named kernel events and are often less coupled to implementation details than probes on internal functions. They are not a stable ABI: names and fields can change across kernels. Kprobes can attach by symbol, rather than only by a hardcoded address, but internal function contracts can change too. Pin maintained probes to tested versions.&lt;/p&gt;
&lt;p&gt;Inspect the available event and its field format on the intended kernel. A listing establishes visibility, not permission to load and attach a program. If an event is unavailable, record that limitation rather than silently substituting a different event whose count or timing means something else.&lt;/p&gt;
&lt;h2 id="read-a-histogram-not-a-firehose"&gt;Read a histogram, not a firehose&lt;/h2&gt;
&lt;p&gt;Use aggregation to reveal distributions without printing every event. A latency histogram can suggest fast and slow paths, but its shape is evidence for investigation, not a diagnosis by itself. It cannot establish which path caused an outlier unless the probe measures the relevant distinction. Counts are not latency histograms.&lt;/p&gt;
&lt;p&gt;Keep keys deliberate. Grouping by thread name can merge unrelated tasks with the same name; grouping by identifiers or arguments changes cardinality and cost. Apply relevant filters early, bound collection, and preserve the exact probe and workload context when reporting a finding. Compare against independent observations before attributing an application problem to one component.&lt;/p&gt;
&lt;h2 id="when-it-breaks"&gt;When it breaks&lt;/h2&gt;
&lt;p&gt;Loading can fail because of permissions, kernel configuration, unavailable events, or tool-specific BTF or header requirements. Preserve the actual error. Listing tracepoints is not a successful attachment, and absent counts can mean an inactive workload, a mismatched filter, or failed collection. Container access also depends on the host kernel and its security policy; do not weaken protections to force a demonstration.&lt;/p&gt;
&lt;p&gt;Hot-path probes can distort the workload even when they aggregate in-kernel. Compare cost under representative conditions and narrow collection when needed. Changing a filter can change the population itself, so a different distribution is not automatically proof that the cheaper observation is more accurate.&lt;/p&gt;
&lt;figure class="technical-diagram"&gt;
&lt;img loading="lazy" src="https://hk.xbcatm.com/visuals/posts-bpf-tracepoint-perf-profiling.detail.en.svg" alt="Openat entry events merge by current thread name into counts, without measuring success or latency." width="560" height="1857"&gt;
&lt;figcaption&gt;Entry attempts merge by current thread name. Illustrative counts are not measured output; the proposed scoped fixture remains unexecuted.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;h2 id="check-it-quickly"&gt;Check it quickly&lt;/h2&gt;
&lt;p&gt;This proposed foreground fixture targets bpftrace 0.24 and an already authorized host; it was not run here. Confirm that version, the exact tracepoint, and &lt;code&gt;/bin/cat&lt;/code&gt; before continuing. Keep the evidence directory, stderr and status; do not add privileges to make it pass.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;work&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;mktemp -d&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Evidence directory: %s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$work&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;controlled input\n&amp;#39;&lt;/span&gt; &amp;gt; input &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bpftrace --version &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; bpftrace -lv &lt;span class="s1"&gt;&amp;#39;tracepoint:syscalls:sys_enter_openat&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; bpftrace -o counts.log -c &lt;span class="s1"&gt;&amp;#39;/bin/cat input&amp;#39;&lt;/span&gt; -e &lt;span class="s1"&gt;&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; tracepoint:syscalls:sys_enter_openat /pid == cpid/ {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; @opens[comm] = count();
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; }&amp;#39;&lt;/span&gt; &amp;gt; workload.out 2&amp;gt; collector.err&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;collector_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;collector_status&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;collector_status=%s\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$collector_status&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat collector.err
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$collector_status&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -ne &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$collector_status&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cmp input workload.out &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat counts.log
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In the reviewed 0.24 implementation, &lt;code&gt;-c&lt;/code&gt; starts this finite child after probe attachment; &lt;code&gt;pid == cpid&lt;/code&gt; restricts the event population. The observation follows the child lifetime, not a ten-second sleep. The child inherits the collector&amp;rsquo;s privileges. The shell records the collector&amp;rsquo;s exact foreground status, not the workload&amp;rsquo;s exit code. &lt;code&gt;cmp&lt;/code&gt; checks copied bytes separately; it does not establish that exit code.&lt;/p&gt;
&lt;p&gt;Expect entry counts for cat, including startup opens and failed attempts, rather than exactly one successful open. Equal thread names still merge within the selected population. Empty maps or errors are inconclusive. Attachment, shutdown and workload-exit behavior still need a permitted runtime review; source inspection is not execution evidence.&lt;/p&gt;
&lt;h2 id="sources"&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/"&gt;bpftrace project&lt;/a&gt;, project overview and tracing examples, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://ebpf.io/what-is-ebpf/"&gt;eBPF introduction&lt;/a&gt;, Official eBPF documentation, checked 2026-06-01.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/docs/release_024/language"&gt;bpftrace 0.24 language&lt;/a&gt;, tracepoint fields and count versus histogram aggregation, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/docs/release_024/stdlib"&gt;bpftrace 0.24 standard library&lt;/a&gt;, comm identifies the current thread name, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.kernel.org/bpf/bpf_design_QA.html"&gt;Linux BPF design Q&amp;amp;A&lt;/a&gt;, tracepoint and kprobe attachment is not a stable ABI, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.kernel.org/bpf/verifier.html"&gt;Linux BPF verifier&lt;/a&gt;, verification checks rather than measurement or overhead guarantees, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://www.gnu.org/s/bash/manual/bash.html"&gt;GNU Bash manual&lt;/a&gt;, wait without an explicit child ID does not return that child’s failure, checked 2026-10-18.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://bpftrace.org/docs/release_024/cli"&gt;bpftrace 0.24 CLI&lt;/a&gt;, child lifetime, privileges, cpid filtering and separate tracing output, checked 2026-10-19.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/bpftrace/bpftrace/blob/v0.24.0/src/bpftrace.cpp"&gt;bpftrace v0.24.0 runtime source&lt;/a&gt;, attachment precedes starting the non-USDT child in BPFtrace::run, checked 2026-10-19.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content:encoded></item></channel></rss>