<?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>kakkoyun</title><link>https://kakkoyun.me/</link><description>Recent content on kakkoyun</description><generator>Hugo -- 0.157.0</generator><language>en</language><managingEditor>kakkoyun@gmail.com (Kemal Akkoyun)</managingEditor><webMaster>kakkoyun@gmail.com (Kemal Akkoyun)</webMaster><atom:link href="https://kakkoyun.me/index.xml" rel="self" type="application/rss+xml"/><item><title>Measuring Software Performance: Why Your Benchmarks Are Probably Lying</title><link>https://kakkoyun.me/posts/fosdem-2026-measuring-software-performance/</link><pubDate>Fri, 06 Mar 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/fosdem-2026-measuring-software-performance/</guid><description>A practical guide to reliable software benchmarking — from controlling hardware noise to statistical analysis, with experiments and actionable advice.</description><content:encoded><![CDATA[<h3 id="a-loose-cable-that-broke-physics">A Loose Cable That Broke Physics</h3>
<p>In 2006, a team of physicists began building the <a href="https://en.wikipedia.org/wiki/OPERA_experiment">OPERA experiment</a> — a 730-kilometer underground tunnel from CERN in Switzerland to Gran Sasso in Italy, designed to measure the speed of neutrinos. Five years of construction. Roughly 100 million euros. The most rigorous experimental physics on the planet.</p>
<p>In September 2011, the results came back. Neutrinos were traveling <a href="https://profmattstrassler.com/articles-and-posts/particle-physics-basics/neutrinos/neutrinos-faster-than-light/opera-what-went-wrong/">faster than the speed of light</a>. The team had just broken the laws of physics.</p>
<p>Except they hadn&rsquo;t. After months of rechecking the math, the sensors, and the calibration, they found the root cause: a single fiber-optic cable that wasn&rsquo;t fully plugged in. A loose connector had introduced a 73-nanosecond timing error — enough to make neutrinos appear superluminal.</p>
<p>Most of us aren&rsquo;t building 730-kilometer tunnels. But we deal with &ldquo;loose cables&rdquo; every day when measuring software performance. A benchmark that shows a 5% speedup might be measuring thermal throttling, CPU frequency scaling, or a noisy neighbor on a shared cloud instance. The signal is real, but so is the noise — and telling them apart requires discipline.</p>
<p><img src="/uploads/fosdem26_crowd.jpeg"
    alt="Software Performance Devroom audience at FOSDEM 2026"
    loading="lazy"
    decoding="async"></p>
<p>This post expands on the talk <a href="https://github.com/igoragoli">Augusto de Oliveira</a> and I gave at the <a href="/talks/how-to-reliably-measure-software-performance/">FOSDEM 2026 Software Performance Devroom</a>. The <a href="https://github.com/igoragoli/fosdem-2026-software-performance">slides and experiments</a> are all open source.</p>
<hr>
<h3 id="why-benchmarking-is-hard">Why Benchmarking Is Hard</h3>
<p>Measuring software performance is a specialized version of a more general problem: finding a signal in a world full of noise.</p>
<p>Modern systems have layers of non-determinism that conspire against repeatable measurements. The CPU dynamically adjusts its clock frequency based on load and temperature. The OS scheduler moves threads between cores. Caches warm and cool. Background processes steal cycles. VMs share physical resources with other tenants. Memory layout changes between runs due to address space layout randomization (ASLR).</p>
<p>Any one of these factors can shift your numbers by a few percent. Stack them up, and a benchmark that reports a 5% improvement might just be measuring random variation. You run it again and the improvement vanishes — or reverses.</p>
<p>The gap between &ldquo;I ran a quick benchmark on my laptop&rdquo; and &ldquo;this measurement is reliable enough to make decisions on&rdquo; is enormous. Closing that gap requires controlling the environment, designing the benchmark properly, interpreting results with statistical rigor, and integrating the whole process into your development workflow.</p>
<hr>
<h3 id="environment-control">Environment Control</h3>
<p>This is the foundation. No amount of statistical sophistication will compensate for a noisy measurement environment. The sources of noise come from every layer of the stack:</p>
<table>
  <thead>
      <tr>
          <th>Layer</th>
          <th>Sources of Noise</th>
          <th>Mitigations</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>External</strong></td>
          <td>Network, temperature, vibration, virtualization</td>
          <td>Bare metal instances, dedicated hardware</td>
      </tr>
      <tr>
          <td><strong>Application</strong></td>
          <td>Memory layout, compilation/linking</td>
          <td>Fixed builds, disable ASLR</td>
      </tr>
      <tr>
          <td><strong>Kernel</strong></td>
          <td>Scheduling, caching</td>
          <td>CPU affinity, process priority, cache management</td>
      </tr>
      <tr>
          <td><strong>CPU</strong></td>
          <td>SMT contention, dynamic frequency scaling</td>
          <td>Disable SMT, disable DFS</td>
      </tr>
  </tbody>
</table>
<h4 id="noisy-neighbors-and-bare-metal">Noisy Neighbors and Bare Metal</h4>
<p>If you&rsquo;re running benchmarks on a shared cloud VM, you&rsquo;re sharing physical CPU cores, memory bandwidth, and last-level cache with other tenants. Their workload affects your numbers. This is the classic noisy neighbor problem.</p>
<p>The fix: use bare metal cloud instances (e.g., AWS <code>m5.metal</code>). They cost more, but they give you exclusive access to the underlying hardware. Just as importantly, bare metal access lets you apply the kernel-level and CPU-level mitigations below — none of which are possible on shared VMs.</p>
<p><a href="https://www.mongodb.com/company/blog/engineering/reducing-variability-performance-tests-ec2-setup-key-results">MongoDB&rsquo;s engineering team documented this well</a> — their work on reducing variability in EC2 performance tests is an excellent reference for anyone setting up cloud-based benchmarking infrastructure.</p>
<h4 id="cpu-affinity-and-process-priority">CPU Affinity and Process Priority</h4>
<p>The OS scheduler moves processes between CPU cores to balance load. Each migration can evict warm cache lines and introduce jitter. Pinning your benchmark to specific cores with <code>taskset</code> eliminates this:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Pin benchmark to CPU 0</span>
</span></span><span style="display:flex;"><span>taskset -c <span style="color:#ae81ff">0</span> ./benchmark
</span></span></code></pre></td></tr></table>
</div>
</div><p>Similarly, raising process priority with <code>nice</code> reduces scheduling interference from other processes:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Higher priority (niceness -5, where -20 is highest)</span>
</span></span><span style="display:flex;"><span>nice -n -5 ./benchmark
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="cache-management">Cache Management</h4>
<p>If your benchmark touches the filesystem, cold vs. warm page cache can dramatically change results. Either warm the cache deliberately before measurement, or drop it to start from a known state:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Drop all caches (requires root)</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#ae81ff">3</span> &gt; /proc/sys/vm/drop_caches <span style="color:#f92672">&amp;&amp;</span> sync
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="simultaneous-multithreading-smt">Simultaneous Multithreading (SMT)</h4>
<p>SMT (marketed as Hyper-Threading on Intel CPUs) allows two hardware threads to share a single physical core. They share execution resources — ALUs, caches, branch predictors — while maintaining separate architectural state.</p>
<p>For I/O-bound workloads, this is fine: one thread executes while the other waits for I/O. But for CPU-bound benchmarks, SMT introduces severe contention. Two threads fight over the same execution units, and the resulting interference shows up as variance in your measurements.</p>
<p>We ran a simple experiment on an AWS <code>m5.metal</code> instance with DFS disabled, measuring two CPU-bound tasks running on the same core (SMT enabled) vs. separate cores (SMT disabled):</p>
<table>
  <thead>
      <tr>
          <th>Configuration</th>
          <th>Mean</th>
          <th>Coeff. of Variation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>SMT enabled, task 1</td>
          <td>1537.64 +/- 367.29 ms</td>
          <td><strong>23.887%</strong></td>
      </tr>
      <tr>
          <td>SMT enabled, task 2</td>
          <td>1536.88 +/- 366.84 ms</td>
          <td><strong>23.869%</strong></td>
      </tr>
      <tr>
          <td>SMT disabled, task 1</td>
          <td>737.37 +/- 0.32 ms</td>
          <td><strong>0.044%</strong></td>
      </tr>
      <tr>
          <td>SMT disabled, task 2</td>
          <td>737.93 +/- 1.74 ms</td>
          <td><strong>0.235%</strong></td>
      </tr>
  </tbody>
</table>
<p>That&rsquo;s <strong>100x less variance</strong> with SMT disabled. The tasks also run twice as fast because they&rsquo;re no longer contending for shared execution resources.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Disable SMT</span>
</span></span><span style="display:flex;"><span>echo off &gt; /sys/devices/system/cpu/smt/control
</span></span></code></pre></td></tr></table>
</div>
</div><h4 id="dynamic-frequency-scaling-dfs">Dynamic Frequency Scaling (DFS)</h4>
<p>Modern CPUs adjust their clock frequency dynamically based on workload, thermals, and power budgets. Intel calls the upward scaling &ldquo;Turbo Boost.&rdquo; This is great for general-purpose computing but terrible for benchmarking — the frequency varies based on how many cores are active, the ambient temperature, and the power headroom.</p>
<p>A single-threaded benchmark might run at 3.5 GHz. Start another workload on a neighboring core and the frequency drops to 3.1 GHz. Your benchmark just got 11% slower, and the code didn&rsquo;t change.</p>
<p>We measured this on the same <code>m5.metal</code> instance with SMT disabled, varying the number of concurrent CPU-bound tasks:</p>
<table>
  <thead>
      <tr>
          <th>Configuration</th>
          <th>Mean</th>
          <th>Coeff. of Variation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>DFS on, 1 task</td>
          <td>533.97 +/- 2.046 ms</td>
          <td><strong>0.383%</strong></td>
      </tr>
      <tr>
          <td>DFS on, 8 tasks</td>
          <td>578.67 +/- 0.287 ms</td>
          <td>0.050%</td>
      </tr>
      <tr>
          <td>DFS off, 1 task</td>
          <td>738.18 +/- 0.306 ms</td>
          <td><strong>0.041%</strong></td>
      </tr>
      <tr>
          <td>DFS off, 8 tasks</td>
          <td>739.18 +/- 0.351 ms</td>
          <td>0.047%</td>
      </tr>
  </tbody>
</table>
<p>With DFS enabled, the single-task case shows ~10x more variance than with DFS disabled. The absolute runtime is higher with DFS off (the CPU runs at its base frequency rather than boosting), but the measurements are rock-solid. When benchmarking, consistency matters more than raw speed.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Pin clock rate to base frequency</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#ae81ff">2500000</span> &gt; /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Set scaling governor to &#34;performance&#34;</span>
</span></span><span style="display:flex;"><span>echo performance &gt; /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Disable Turbo Boost (Intel CPUs)</span>
</span></span><span style="display:flex;"><span>echo <span style="color:#ae81ff">1</span> &gt; /sys/devices/system/cpu/intel_pstate/no_turbo
</span></span></code></pre></td></tr></table>
</div>
</div><p>Denis Bakhvalov&rsquo;s <a href="https://github.com/dendibakh/perf-book">Performance Analysis and Tuning on Modern CPUs</a> covers CPU-level tuning in depth and is the definitive reference on this topic.</p>
<hr>
<h3 id="benchmark-design">Benchmark Design</h3>
<p>Environment control reduces noise. Good benchmark design ensures the signal you&rsquo;re measuring is actually meaningful.</p>
<h4 id="representative-workloads">Representative Workloads</h4>
<p>A benchmark is only useful if it measures something that matters. What does your application actually do?</p>
<table>
  <thead>
      <tr>
          <th>Archetype</th>
          <th>Pattern</th>
          <th>Characteristics</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Idle</strong></td>
          <td>Background workers, minimal load</td>
          <td>Low RPS, minimal CPU</td>
      </tr>
      <tr>
          <td><strong>Latency</strong></td>
          <td>Microservices, APIs</td>
          <td>High RPS, low CPU per request</td>
      </tr>
      <tr>
          <td><strong>Throughput</strong></td>
          <td>Queue workers, batch processing</td>
          <td>Moderate RPS, high CPU</td>
      </tr>
      <tr>
          <td><strong>Enterprise</strong></td>
          <td>Business apps with DB/API calls</td>
          <td>Moderate RPS, mixed CPU/IO</td>
      </tr>
  </tbody>
</table>
<p>Your benchmark workload should match your production workload. A microbenchmark that measures a tight loop in isolation won&rsquo;t tell you much about how your API server handles realistic traffic patterns.</p>
<p>That said, microbenchmarks have their place. They&rsquo;re invaluable for comparing algorithms, validating specific optimizations, and catching regressions in hot paths. The key is knowing which type fits your question:</p>
<table>
  <thead>
      <tr>
          <th>Use Case</th>
          <th>Benchmark Type</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Comparing algorithms</td>
          <td>Micro</td>
      </tr>
      <tr>
          <td>Validating optimizations</td>
          <td>Micro</td>
      </tr>
      <tr>
          <td>Regression detection</td>
          <td>Both</td>
      </tr>
      <tr>
          <td>Capacity planning</td>
          <td>Macro</td>
      </tr>
      <tr>
          <td>User experience</td>
          <td>Macro</td>
      </tr>
  </tbody>
</table>
<p>Best practice: use both in your pipeline.</p>
<h4 id="the-coordinated-omission-problem">The Coordinated Omission Problem</h4>
<p>If your load generator waits for each response before sending the next request, it&rsquo;s probably lying to you. When the system under test slows down, the generator slows down too — sending fewer requests per second, which artificially improves the measured latencies.</p>
<p>Gil Tene&rsquo;s talk <a href="https://www.youtube.com/watch?v=lJ8ydIuPFeU">&ldquo;How NOT to Measure Latency&rdquo;</a> is the definitive explanation of this problem. The short version: use load generators that maintain a constant request rate regardless of response time. Tools like <a href="https://k6.io/">k6</a> and <a href="https://github.com/giltene/wrk2">wrk2</a> handle this correctly.</p>
<h4 id="warm-up-and-steady-state">Warm-Up and Steady State</h4>
<p>We learned this the hard way with a Java benchmark. The goal: measure instrumentation overhead on a Spring application. Initial setup: 20-second warmup, 15 seconds of measurements, collecting one sample per second.</p>
<p>The coefficient of variation was <strong>11.80%</strong> — far too noisy to detect real changes.</p>
<p>The problem was warmup. The JVM compiles methods on the fly (JIT compilation). Each method needs to be called enough times to hit the compilation threshold, then you wait for the compiler to finish. Twenty seconds wasn&rsquo;t nearly enough. By extending the warmup to 160 seconds and the measurement period to match, the picture changed completely.</p>
<p>From the experiments:</p>
<p><strong>Tip 1</strong>: Run benchmarks long enough to uncover perturbations like warmup effects.</p>
<p><strong>Tip 2</strong>: Collect enough samples to reduce intra-run variation. N &gt;= 30 is a reasonable minimum.</p>
<p><strong>Tip 3</strong>: Rerun benchmarks multiple times to reduce inter-run variation. M &gt;= 5 runs helps account for <a href="https://link.springer.com/chapter/10.1007/11758525_26">random initial state effects</a> (cache layout, memory placement).</p>
<p>Applying all three tips reduced the coefficient of variation from <strong>11.80% to 2.94%</strong> — a 4x improvement from benchmark design alone, before any environment control.</p>
<p><strong>Tip 4</strong>: Use deterministic inputs. Non-deterministic data leads to non-deterministic measurements.</p>
<hr>
<h3 id="statistical-methods">Statistical Methods</h3>
<p>You&rsquo;ve controlled the environment and designed a good benchmark. Now you have data. The question is: is the difference you&rsquo;re seeing real, or noise?</p>
<h4 id="why-averages-lie">Why Averages Lie</h4>
<p>Consider a throughput benchmark run before and after a code change. The &ldquo;before&rdquo; mean is 102.7 req/s. The &ldquo;after&rdquo; mean is 105.0 req/s. That&rsquo;s a 2.3% improvement. Ship it?</p>
<p>Not so fast. Each of those means summarizes a distribution of individual measurements. If those distributions overlap significantly, the difference between the means might not be statistically significant — it could easily arise from random variation alone.</p>
<h4 id="hypothesis-testing">Hypothesis Testing</h4>
<p>The intuition is straightforward: compare the size of the difference to the size of the noise.</p>
<p>The <a href="https://en.wikipedia.org/wiki/Welch%27s_t-test">Welch&rsquo;s t-test</a> formalizes this. It computes a test statistic <em>t</em> that is essentially the ratio of the mean difference to the standard error. If <em>t</em> exceeds a critical value (determined by your chosen false positive rate, alpha), you can conclude the difference is statistically significant.</p>
<p>The key insight: <strong>a statistically significant result tells you the difference is unlikely to be zero, but not that the difference is large or practically meaningful.</strong> Always pair hypothesis testing with effect size estimates. A 0.1% improvement might be statistically significant with enough samples — but not worth the code complexity.</p>
<h4 id="change-point-detection">Change Point Detection</h4>
<p>Hypothesis testing works well when you have a clear &ldquo;before&rdquo; and &ldquo;after.&rdquo; But what about continuous benchmarking, where you&rsquo;re tracking performance across hundreds of commits?</p>
<p>Change point detection algorithms scan a time series and identify where the underlying distribution shifts. The <a href="https://aakinshin.net/posts/edpelt/">e-divisive method</a> (ED-PELT) is particularly effective for benchmark data. It handles non-normal distributions, detects multiple change points, and works well with the kind of noisy data that benchmarks produce.</p>
<p>Netflix&rsquo;s engineering team wrote an excellent post on <a href="https://netflixtechblog.com/fixing-performance-regressions-before-they-happen-eab2602b86fe">fixing performance regressions before they happen</a>, which covers their use of change point detection in continuous benchmarking.</p>
<p><a href="https://blog.nyrkio.com/2025/06/12/slides-from-presentation-to-spec-devops-performance-wg/">Henrik Ingo</a> (who spoke in the same Software Performance Devroom at FOSDEM) has published extensively on applying these methods in practice.</p>
<h4 id="visualization-strip-plots-over-boxplots">Visualization: Strip Plots Over Boxplots</h4>
<p>Boxplots hide too much. They show quartiles and a median, but they obscure the actual distribution shape — bimodality, outlier clusters, and gaps all disappear into a box.</p>
<p>Strip plots (dot plots of every individual measurement) are better for benchmark data. They make outliers obvious, reveal distribution shape at a glance, and scale well for the sample sizes typical in benchmarking (30-200 points).</p>
<p>Brendan Gregg&rsquo;s work on <a href="https://www.brendangregg.com/FrequencyTrails/outliers.html#Causes">frequency trails</a> is excellent on this topic — showing how visualization choices affect your ability to detect real patterns in performance data.</p>
<hr>
<h3 id="integrating-into-development-workflows">Integrating Into Development Workflows</h3>
<p>Reliable measurement is only half the problem. The other half is making performance a first-class part of the development process.</p>
<h4 id="the-feedback-loop">The Feedback Loop</h4>
<p>The ideal: a developer opens a pull request, benchmarks run automatically, and within minutes they see whether their changes have performance implications. If there&rsquo;s a regression, they know about it before the code merges — not weeks later when a customer notices.</p>
<p>This requires:</p>
<ol>
<li><strong>Automated benchmark execution</strong> triggered by code changes</li>
<li><strong>Statistical analysis</strong> to distinguish real regressions from noise</li>
<li><strong>Clear reporting</strong> that developers can act on — not a wall of numbers, but a concise &ldquo;this got 3% slower, here&rsquo;s the data&rdquo;</li>
<li><strong>Local reproducibility</strong> so developers can investigate and fix regressions on their own machines</li>
</ol>
<h4 id="performance-quality-gates">Performance Quality Gates</h4>
<p>Beyond PR-level feedback, performance quality gates can block releases that don&rsquo;t meet defined SLOs. The philosophy is the same as any other quality gate — you wouldn&rsquo;t ship without passing tests, so don&rsquo;t ship without passing performance benchmarks.</p>
<h4 id="when-to-benchmark">When to Benchmark</h4>
<p>The answer depends on your resources and risk tolerance:</p>
<table>
  <thead>
      <tr>
          <th>Strategy</th>
          <th>Cost</th>
          <th>Coverage</th>
          <th>Best For</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Every PR</td>
          <td>High</td>
          <td>Complete</td>
          <td>Critical paths, performance-sensitive libraries</td>
      </tr>
      <tr>
          <td>Periodic (nightly/weekly)</td>
          <td>Medium</td>
          <td>Trend detection</td>
          <td>General regression catching</td>
      </tr>
      <tr>
          <td>On-demand</td>
          <td>Low</td>
          <td>Targeted</td>
          <td>Investigation, optimization validation</td>
      </tr>
  </tbody>
</table>
<p>For most teams, a combination works best: lightweight benchmarks on every PR, comprehensive macrobenchmarks nightly, and on-demand deep dives when investigating specific issues.</p>
<h4 id="open-source-tools">Open Source Tools</h4>
<p>You don&rsquo;t need to build a benchmarking platform from scratch. Several open source projects can get you started:</p>
<ul>
<li><a href="https://bencher.dev/"><strong>bencher.dev</strong></a> — Continuous benchmarking as a service. Tracks benchmark results over time, detects regressions, and integrates with CI/CD.</li>
<li><a href="https://github.com/sharkdp/hyperfine"><strong>hyperfine</strong></a> — A CLI benchmarking tool for comparing command execution times. Handles warmup, statistical analysis, and parameterized runs.</li>
<li><a href="https://github.com/benchmark-action/github-action-benchmark"><strong>github-action-benchmark</strong></a> — GitHub Action for running benchmarks and tracking results over time, with support for Go, Python, Rust, and other language-specific benchmark formats.</li>
<li><a href="https://github.com/dandavison/chronologer"><strong>chronologer</strong></a> — Benchmark tracking focused on Go benchmarks with historical comparison.</li>
<li><a href="https://blog.nyrkio.com/2025/05/08/welcome-apache-otava-incubating-project/"><strong>Apache Otava</strong></a> (formerly Nyrkio, incubating) — Performance change point detection service, built on the e-divisive algorithm.</li>
<li><a href="https://github.com/aclements/perflock"><strong>perflock</strong></a> — A tool for locking CPU frequency and other system settings during benchmarks. Useful for local development.</li>
</ul>
<p>The right tool depends on your language ecosystem, CI system, and how much you want to self-host vs. use a managed service.</p>
<hr>
<h3 id="key-takeaways">Key Takeaways</h3>
<p>Four things to remember:</p>
<ol>
<li>
<p><strong>Control your benchmarking environment.</strong> Bare metal instances, CPU isolation, disable SMT, disable dynamic frequency scaling. Environment noise is the single largest source of unreliable measurements.</p>
</li>
<li>
<p><strong>Design your benchmarks to be representative and repeatable.</strong> Match your production workload. Run long enough. Collect enough samples. Rerun multiple times.</p>
</li>
<li>
<p><strong>Interpret results with statistical rigor.</strong> Don&rsquo;t trust averages. Use hypothesis testing or change point detection. Always ask: is this difference real, or noise?</p>
</li>
<li>
<p><strong>Integrate benchmarks into your development workflow.</strong> Run continuously. Catch regressions on PRs. Make performance feedback as fast as test feedback.</p>
</li>
</ol>
<hr>
<h3 id="performance-matters">Performance Matters</h3>
<p>Performance is not always the first thing we think about when building software. We focus on features, correctness, security. And those are right to come first. But in the end, performance is what users experience.</p>
<p>Low latency means your users aren&rsquo;t waiting. High throughput means your system handles the load. Cost-efficient performance means you&rsquo;re not burning money (and energy) on infrastructure that could be halved with the right optimization. A <a href="https://www.brendangregg.com/blog/2020-07-15/systems-performance-2nd-edition.html">500ms delay costs Google 20% of their traffic</a>. A 400ms improvement gave Yahoo 5-9% more traffic. The numbers are real.</p>
<blockquote>
<p>&ldquo;Not all fast software is world-class, but all world-class software is fast.&rdquo;
&ndash; Tobi Lutke, CEO of Shopify</p>
</blockquote>
<p>So write benchmarks. Run them continuously. Catch regressions before your users do.</p>
<p>And don&rsquo;t shout in the datacenter.</p>
<hr>
<h3 id="resources">Resources</h3>
<ul>
<li><a href="https://github.com/igoragoli/fosdem-2026-software-performance">Slides and experiments (GitHub)</a></li>
<li><a href="https://www.youtube.com/watch?v=8211fNI_nc4">Talk recording (YouTube)</a></li>
<li><a href="/talks/how-to-reliably-measure-software-performance/">Talk page</a></li>
<li><a href="/posts/fosdem-2026/">FOSDEM 2026 recap</a></li>
<li><a href="/posts/otel-unplugged-eu-2026/">OTel Unplugged EU 2026: Field Notes</a></li>
<li>Bakhvalov, D. — <a href="https://github.com/dendibakh/perf-book">Performance Analysis and Tuning on Modern CPUs</a></li>
<li>Gregg, B. — <a href="https://www.brendangregg.com/blog/2020-07-15/systems-performance-2nd-edition.html">Systems Performance: Enterprise and the Cloud</a>, 2nd ed.</li>
<li>Tene, G. — <a href="https://www.youtube.com/watch?v=lJ8ydIuPFeU">How NOT to Measure Latency</a></li>
<li>Kalibera, T. et al. — <a href="https://link.springer.com/chapter/10.1007/11758525_26">Benchmark Precision and Random Initial State</a></li>
<li>Leiserson, C. et al. — <a href="https://science.sciencemag.org/content/368/6495/eaam9744">There&rsquo;s Plenty of Room at the Top</a> (Science, 2020)</li>
<li>Netflix Engineering — <a href="https://netflixtechblog.com/fixing-performance-regressions-before-they-happen-eab2602b86fe">Fixing Performance Regressions Before They Happen</a></li>
<li>Ingo, H. — <a href="https://blog.nyrkio.com/2025/06/12/slides-from-presentation-to-spec-devops-performance-wg/">Change Point Detection for Performance</a></li>
<li>Gregg, B. — <a href="https://www.brendangregg.com/FrequencyTrails/outliers.html#Causes">Frequency Trails: Outliers</a></li>
<li><a href="https://www.mongodb.com/company/blog/engineering/reducing-variability-performance-tests-ec2-setup-key-results">MongoDB: Reducing Variability in EC2 Performance Tests</a></li>
</ul>
]]></content:encoded></item><item><title>Auto-Instrumenting Go: From eBPF to USDT Probes</title><link>https://kakkoyun.me/posts/fosdem-2026-auto-instrumenting-go/</link><pubDate>Fri, 27 Feb 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/fosdem-2026-auto-instrumenting-go/</guid><description>A deep dive into approaches for instrumenting Go applications without changing source code — comparing compile-time, eBPF, runtime injection, and USDT methods with benchmarks.</description><content:encoded><![CDATA[<p>This post expands on the <a href="/talks/how-to-instrument-go-without-changing-code/">FOSDEM 2026 Go Devroom talk</a> I co-presented with <a href="https://hannahkm.github.io">Hannah S. Kim</a>. The talk, demo code, and all benchmark scenarios are available in the <a href="https://github.com/kakkoyun/fosdem-2026">fosdem-2026 repository</a>.</p>
<hr>
<h3 id="the-problem">The Problem</h3>
<p>Go is one of the best languages for building production backend services. It compiles to native binaries, has excellent concurrency primitives, and produces predictable performance characteristics. But when it comes to auto-instrumentation — adding observability without modifying source code — Go is uniquely difficult.</p>
<p>In the JVM world, bytecode manipulation gives you powerful hooks. Java agents can intercept method calls, inject tracing, and propagate context without the application developer knowing. Python and Node.js have similar dynamic capabilities. Go has none of this.</p>
<p>The reasons are structural:</p>
<ul>
<li><strong>Static compilation.</strong> Go compiles to a single native binary. There is no intermediate bytecode to rewrite at load time, no classloader to intercept, no dynamic linking by default.</li>
<li><strong>No <code>LD_PRELOAD</code>.</strong> Go&rsquo;s default static linking means the <code>LD_PRELOAD</code> trick that works for C/C++ applications (and that the <a href="https://github.com/open-telemetry/opentelemetry-injector">OTel Injector</a> uses for Java, .NET, and Node.js) doesn&rsquo;t apply.</li>
<li><strong>Unique calling convention.</strong> Go&rsquo;s ABI passes arguments in registers with a convention different from the platform C ABI. This makes dynamic hooking with tools like Frida or ptrace significantly harder — you can&rsquo;t just read standard frame pointers.</li>
<li><strong>Goroutine stack management.</strong> Goroutines use segmented, growable stacks that the runtime can move at any time. Traditional stack-walking assumptions break.</li>
</ul>
<p>The gap between &ldquo;Go is great for production&rdquo; and &ldquo;Go is hard to auto-instrument&rdquo; is real. This is the gap we set out to map.</p>
<hr>
<h3 id="the-comparison-framework">The Comparison Framework</h3>
<p>We built a <a href="https://github.com/kakkoyun/fosdem-2026">demo repository</a> with the same Go HTTP server implemented across seven scenarios, each using a different instrumentation approach. The application is deliberately simple — an HTTP server with configurable CPU load, memory allocation, and off-CPU time — so that instrumentation overhead is isolated and measurable.</p>
<h4 id="the-seven-scenarios">The Seven Scenarios</h4>
<table>
  <thead>
      <tr>
          <th>#</th>
          <th>Scenario</th>
          <th>Approach</th>
          <th>What It Does</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>1</td>
          <td><code>default</code></td>
          <td>None</td>
          <td>Baseline. No instrumentation of any kind.</td>
      </tr>
      <tr>
          <td>2</td>
          <td><code>manual</code></td>
          <td>OTel SDK</td>
          <td>Manual OpenTelemetry SDK integration — explicit tracer initialization, span creation via <code>otelhttp</code>, and context propagation. The &ldquo;standard&rdquo; way.</td>
      </tr>
      <tr>
          <td>3</td>
          <td><code>obi</code></td>
          <td>eBPF (OBI)</td>
          <td><a href="https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation">OpenTelemetry eBPF Instrumentation</a>. Network-level eBPF hooks. Runs as a sidecar, attaches to the running process. No code changes.</td>
      </tr>
      <tr>
          <td>4</td>
          <td><code>ebpf</code></td>
          <td>eBPF (Auto)</td>
          <td><a href="https://github.com/open-telemetry/opentelemetry-go-instrumentation">OpenTelemetry Go Auto-Instrumentation</a>. Uprobe-based eBPF hooks targeting Go runtime functions. No code changes.</td>
      </tr>
      <tr>
          <td>5</td>
          <td><code>orchestrion</code></td>
          <td>Compile-time</td>
          <td><a href="https://github.com/datadog/orchestrion">Datadog Orchestrion</a> with OTel SDK. AST transformation via <code>-toolexec</code> at compile time. Requires a rebuild but no source changes.</td>
      </tr>
      <tr>
          <td>6</td>
          <td><code>libstabst</code></td>
          <td>USDT (salp)</td>
          <td>USDT probes via <a href="https://github.com/mmcshane/salp">salp</a>/<a href="https://github.com/sthima/libstapsdt">libstapsdt</a>, consumed by a bpftrace sidecar that exports to OTLP. Proof of concept.</td>
      </tr>
      <tr>
          <td>7</td>
          <td><code>usdt</code></td>
          <td>USDT (native)</td>
          <td>Native USDT probes via a <a href="https://github.com/kakkoyun/go/tree/poc_usdt">custom Go fork</a> that adds probe points to <code>net/http</code>, <code>database/sql</code>, <code>crypto/tls</code>, and <code>net</code>. Proof of concept.</td>
      </tr>
  </tbody>
</table>
<p>Each scenario runs in Docker with an identical observability stack (OTel Collector, Jaeger, Prometheus) and is load-tested with identical parameters.</p>
<h4 id="evaluation-axes">Evaluation Axes</h4>
<p>We compared the approaches across three dimensions:</p>
<ul>
<li><strong>Performance overhead</strong> — latency, CPU, memory (RSS), throughput</li>
<li><strong>Robustness</strong> — stability across Go versions, container environments, failure modes</li>
<li><strong>Operational friction</strong> — deployment complexity, privilege requirements, debugging</li>
</ul>
<hr>
<h3 id="manual-otel-sdk-baseline-for-comparison">Manual OTel SDK (Baseline for Comparison)</h3>
<p>The manual scenario is not auto-instrumentation — it is the standard way to instrument a Go service. You import the OTel SDK, initialize a tracer provider, wrap your HTTP handler with <code>otelhttp.NewHandler</code>, and create spans explicitly.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">setupHandlers</span>(<span style="color:#a6e22e">inputs</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Input</span>) <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Handler</span> {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mux</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">NewServeMux</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/health&#34;</span>, <span style="color:#a6e22e">HealthHandler</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">mux</span>.<span style="color:#a6e22e">HandleFunc</span>(<span style="color:#e6db74">&#34;/load&#34;</span>, <span style="color:#a6e22e">inputs</span>.<span style="color:#a6e22e">LoadHandler</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">otelhttp</span>.<span style="color:#a6e22e">NewHandler</span>(<span style="color:#a6e22e">mux</span>, <span style="color:#e6db74">&#34;&#34;</span>)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Input</span>) <span style="color:#a6e22e">LoadHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">tracer</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">otel</span>.<span style="color:#a6e22e">Tracer</span>(<span style="color:#e6db74">&#34;manual&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">_</span>, <span style="color:#a6e22e">span</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">tracer</span>.<span style="color:#a6e22e">Start</span>(<span style="color:#a6e22e">r</span>.<span style="color:#a6e22e">Context</span>(), <span style="color:#e6db74">&#34;manual.handler&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">span</span>.<span style="color:#a6e22e">End</span>()
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ... business logic</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>This gives you full control — custom span attributes, context propagation, error recording. But it requires code changes in every service, and those changes accumulate. Multiply by a hundred microservices and you understand why auto-instrumentation matters.</p>
<hr>
<h3 id="compile-time-orchestrion-and-otel-compile-time-instrumentation">Compile-Time: Orchestrion and OTel Compile-Time Instrumentation</h3>
<p>Orchestrion uses Go&rsquo;s <code>-toolexec</code> flag to intercept the compilation pipeline. During the AST transformation phase, it injects instrumentation code — adding OTel spans, wrapping handlers, propagating context — without the developer modifying source files.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>go build -toolexec <span style="color:#e6db74">&#39;orchestrion toolexec&#39;</span> -o myapp .
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Or equivalently:</span>
</span></span><span style="display:flex;"><span>orchestrion go build -o myapp .
</span></span></code></pre></td></tr></table>
</div>
</div><p>The mechanism is aspect-oriented: you declare join points (e.g., &ldquo;any function in package <code>main</code> named <code>LoadHandler</code>&rdquo;) and advice (e.g., &ldquo;prepend a span creation statement&rdquo;). The transformation happens at the AST level before the compiler emits machine code.</p>
<p>Orchestrion supports OpenTelemetry natively — it is not Datadog-specific. In January 2025, Datadog and Alibaba began merging their compile-time instrumentation efforts into a unified solution under the <a href="https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation">OpenTelemetry Compile-Time Instrumentation SIG</a>.</p>
<p><strong>Trade-offs:</strong></p>
<ul>
<li>Requires a rebuild. You cannot instrument already-deployed binaries.</li>
<li>Deepest instrumentation of all approaches — it can instrument stdlib and dependencies.</li>
<li>Zero runtime overhead from the instrumentation mechanism itself (the injected OTel code has the same cost as manual instrumentation).</li>
<li>Stable across Go versions (the toolexec interface is stable).</li>
<li>No kernel privileges required.</li>
</ul>
<p>For a deeper dive into the <code>-toolexec</code> mechanism, see my earlier <a href="/talks/unleashing-the-go-toolchain/">Unleashing the Go Toolchain</a> talk from GopherCon UK 2025.</p>
<hr>
<h3 id="ebpf-approaches">eBPF Approaches</h3>
<h4 id="obi-opentelemetry-ebpf-instrumentation">OBI (OpenTelemetry eBPF Instrumentation)</h4>
<p><a href="https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation">OBI</a> takes a network-level approach. It uses eBPF programs to hook into kernel-level network operations, intercepting HTTP/S and gRPC traffic. It is multi-language — Go, Java, .NET, Python, Node.js, Ruby, Rust — because it operates at the protocol layer rather than the language runtime layer.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>docker run --privileged <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --pid<span style="color:#f92672">=</span>container:myapp <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  -e OTEL_EXPORTER_OTLP_ENDPOINT<span style="color:#f92672">=</span>http://collector:4318 <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  otel/ebpf-instrumentation:latest
</span></span></code></pre></td></tr></table>
</div>
</div><p>OBI runs as a sidecar container. It attaches to the target process&rsquo;s PID namespace and loads eBPF programs that intercept network system calls. No source code modification, no recompilation, no restart.</p>
<p><strong>Trade-offs:</strong></p>
<ul>
<li>Requires <code>CAP_SYS_ADMIN</code> or privileged containers. Security teams push back on this.</li>
<li>Limited to what eBPF can observe at the network level. Application-internal spans are not visible.</li>
<li>Protocol coverage is growing: HTTP/S, gRPC, TLS visibility.</li>
<li>Excellent for topology mapping and network observability beyond just tracing.</li>
</ul>
<h4 id="otel-go-auto-instrumentation">OTel Go Auto-Instrumentation</h4>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-go-instrumentation">OpenTelemetry Go Auto-Instrumentation</a> project uses uprobe-based eBPF hooks that target specific Go runtime functions. Unlike OBI&rsquo;s network-level approach, this hooks directly into Go function prologues.</p>
<p>This project is effectively in maintenance mode. Several of its contributors have moved to OBI. At <a href="/posts/otel-unplugged-eu-2026/">OTel Unplugged EU 2026</a>, the frank assessment was: the people moved to where the momentum is.</p>
<hr>
<h3 id="runtime-injection-frida-and-ptrace">Runtime Injection: Frida and ptrace</h3>
<p>The <code>injector</code> scenario explores dynamic instrumentation via <a href="https://frida.re/">Frida</a>, a ptrace-based toolkit for runtime function hooking. The idea is conceptually simple: attach to a running process, find the function you want to hook, and replace its prologue with a trampoline that calls your instrumentation code.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#75715e">// The application code uses //go:noinline to keep functions hookable.</span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">//go:noinline</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> (<span style="color:#a6e22e">c</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">Input</span>) <span style="color:#a6e22e">LoadHandler</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">_</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ... business logic</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>In practice, this is extremely hard for Go binaries. <a href="https://blog.quarkslab.com/lets-go-into-the-rabbit-hole-part-1-the-challenges-of-dynamically-hooking-golang-program.html">Quarkslab&rsquo;s excellent three-part series</a> documents the challenges in detail: Go&rsquo;s register-based calling convention, goroutine stack relocation, and compiler optimizations (inlining, dead code elimination) all conspire against reliable dynamic hooking.</p>
<p>The demo&rsquo;s injector scenario includes a helper tool that uses <code>unsafe.Offsetof</code> to find <code>http.Request</code> struct field offsets — information you need just to read the HTTP method and path from a hooked function&rsquo;s arguments.</p>
<p><strong>Trade-offs:</strong></p>
<ul>
<li>Works with existing binaries. No rebuild required.</li>
<li>Requires <code>-gcflags=&quot;all=-N -l&quot;</code> to disable optimizations, which defeats the purpose for production.</li>
<li>Fragile across Go versions — struct layouts and calling conventions change.</li>
<li>Limited applicability for Go&rsquo;s statically linked binaries.</li>
</ul>
<p>For Go, this approach is more useful as a debugging tool than a production instrumentation strategy.</p>
<hr>
<h3 id="usdt-probes-the-novel-part">USDT Probes: The Novel Part</h3>
<p>USDT (User Statically-Defined Tracing) probes are a mechanism from the DTrace/SystemTap ecosystem. They are marker points compiled into a binary that external tooling (bpftrace, perf, DTrace) can attach to at runtime. The key property: <strong>when no consumer is attached, the probe site is a NOP instruction with zero overhead.</strong></p>
<p>We built two proof-of-concept implementations.</p>
<h4 id="libstabst-usdt-via-salp-and-bpftrace">libstabst: USDT via salp and bpftrace</h4>
<p>The <code>libstabst</code> scenario uses <a href="https://github.com/mmcshane/salp">salp</a>, a Go binding to <a href="https://github.com/sthima/libstapsdt">libstapsdt</a>, to create USDT probes at runtime. The application defines probe points for request start and end:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">9
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#a6e22e">probes</span> = <span style="color:#a6e22e">salp</span>.<span style="color:#a6e22e">NewProvider</span>(<span style="color:#e6db74">&#34;fosdem&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">reqStart</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">probes</span>.<span style="color:#a6e22e">AddProbe</span>(<span style="color:#e6db74">&#34;request_start&#34;</span>, <span style="color:#a6e22e">salp</span>.<span style="color:#a6e22e">String</span>, <span style="color:#a6e22e">salp</span>.<span style="color:#a6e22e">Int64</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">reqEnd</span>, <span style="color:#a6e22e">_</span> = <span style="color:#a6e22e">probes</span>.<span style="color:#a6e22e">AddProbe</span>(<span style="color:#e6db74">&#34;request_end&#34;</span>, <span style="color:#a6e22e">salp</span>.<span style="color:#a6e22e">String</span>, <span style="color:#a6e22e">salp</span>.<span style="color:#a6e22e">Int64</span>, <span style="color:#a6e22e">salp</span>.<span style="color:#a6e22e">Int64</span>)
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">probes</span>.<span style="color:#a6e22e">Load</span>()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e">// In the handler:</span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">if</span> <span style="color:#a6e22e">reqStart</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#a6e22e">reqStart</span>.<span style="color:#a6e22e">Enabled</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">reqStart</span>.<span style="color:#a6e22e">Fire</span>(<span style="color:#a6e22e">reqID</span>, <span style="color:#a6e22e">startTime</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>A bpftrace sidecar attaches to these probes and exports events as OTLP traces via a custom exporter bridge.</p>
<p><strong>Known limitations:</strong> The salp library has compatibility issues with Go 1.25+, pinning this scenario to Go 1.23.x. It also needs <code>/proc/self/fd/</code> access for mmap, which fails in many container environments. On bare metal Linux or in a Lima VM, it works.</p>
<p><img src="/uploads/fosdem26_go_usdt.png"
    alt="Presenting the USDT &#43; eBPF proof of concept at the Go Devroom"
    loading="lazy"
    decoding="async"></p>
<h4 id="native-usdt-custom-go-fork">Native USDT: Custom Go Fork</h4>
<p>The more ambitious PoC is a <a href="https://github.com/kakkoyun/go/tree/poc_usdt">custom Go fork</a> that adds USDT probe points directly to the Go standard library — <code>net/http</code>, <code>database/sql</code>, <code>crypto/tls</code>, and <code>net</code>.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;runtime/trace/usdt&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">handleRequest</span>(<span style="color:#a6e22e">w</span> <span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">ResponseWriter</span>, <span style="color:#a6e22e">r</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">http</span>.<span style="color:#a6e22e">Request</span>) {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">usdt</span>.<span style="color:#a6e22e">Probe</span>(<span style="color:#e6db74">&#34;myapp&#34;</span>, <span style="color:#e6db74">&#34;request_start&#34;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">usdt</span>.<span style="color:#a6e22e">Probe1</span>(<span style="color:#e6db74">&#34;myapp&#34;</span>, <span style="color:#e6db74">&#34;request_end&#34;</span>, int32(<span style="color:#a6e22e">w</span>.<span style="color:#a6e22e">StatusCode</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#75715e">// ... handle request</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></td></tr></table>
</div>
</div><p>The fork includes a <code>go tool usdt</code> command for listing probes in a binary and generating bpftrace scripts:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ go tool usdt list ./myserver
</span></span><span style="display:flex;"><span>PROVIDER   NAME                  ADDRESS     ARGUMENTS
</span></span><span style="display:flex;"><span>net_http   server_request_start  0x63296c    8@%rsi -8@%r8
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>$ go tool usdt bpftrace ./myserver &gt; trace.bt
</span></span><span style="display:flex;"><span>$ sudo bpftrace trace.bt
</span></span></code></pre></td></tr></table>
</div>
</div><p>This PoC proves that native USDT support in Go is technically feasible. The standard library instrumentation is automatically available in any binary built with the fork — no application code changes, no SDK imports.</p>
<p><strong>Known limitations:</strong> ARM64 argument parsing in bpftrace has issues with the probe argument notation emitted by the fork. The fork is strictly a proof of concept and not suitable for production.</p>
<hr>
<h3 id="go-runtime-pocs-flight-recording">Go Runtime PoCs: Flight Recording</h3>
<p>Beyond USDT, we explored a <a href="https://github.com/kakkoyun/go/tree/poc_flight_recorder">flight recorder PoC</a> based on <a href="https://github.com/golang/go/issues/63185">golang/go#63185</a>. The concept: always-on distributed tracing built into the Go runtime, with a bounded ring buffer and GODEBUG-based activation.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#f92672">import</span> <span style="color:#e6db74">&#34;runtime/trace/flight&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#a6e22e">flight</span>.<span style="color:#a6e22e">Enable</span>(<span style="color:#a6e22e">flight</span>.<span style="color:#a6e22e">HTTP</span> | <span style="color:#a6e22e">flight</span>.<span style="color:#a6e22e">SQL</span> | <span style="color:#a6e22e">flight</span>.<span style="color:#a6e22e">Net</span>)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">defer</span> <span style="color:#a6e22e">flight</span>.<span style="color:#a6e22e">Flush</span>()  <span style="color:#75715e">// Export on error or crash</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The flight recorder PoC watches for trace files produced by the runtime, converts them to OTLP spans, and exports to a collector. If Go&rsquo;s runtime trace facilities eventually gain W3C Trace Context propagation, this could become the lowest-friction instrumentation path for Go — no SDK, no eBPF, no compile-time tools. Just the runtime doing what runtimes should do.</p>
<hr>
<h3 id="benchmark-results">Benchmark Results</h3>
<p>We ran each scenario under identical load conditions using a Docker-based observability stack with 5-minute sustained load tests.</p>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>CPU</th>
          <th>Memory (RSS)</th>
          <th>Max Latency</th>
          <th>Max Throughput</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Baseline (no instrumentation)</td>
          <td>10.2%</td>
          <td>202 MiB</td>
          <td>4.50 ms</td>
          <td>3.1k req/sec</td>
      </tr>
      <tr>
          <td>Manual OTel SDK</td>
          <td>10.3% (+0.1%)</td>
          <td>210 MiB (+8 MiB)</td>
          <td>3.02 ms</td>
          <td>13.97k req/sec</td>
      </tr>
      <tr>
          <td>eBPF Auto-Instrumentation</td>
          <td>10.0% (-0.3%)</td>
          <td>204 MiB (+2 MiB)</td>
          <td>3.07 ms</td>
          <td>4.57k req/sec</td>
      </tr>
      <tr>
          <td>Compile-time (Orchestrion)</td>
          <td>9.8% (-0.4%)</td>
          <td>210 MiB (+8 MiB)</td>
          <td>2.59 ms</td>
          <td>27.8k req/sec</td>
      </tr>
  </tbody>
</table>
<p>A few things stand out. The CPU and memory overhead across all approaches is negligible for this workload. The throughput differences are more interesting — Orchestrion&rsquo;s compile-time approach achieved the highest throughput, likely because the OTel code injected at compile time benefits from the same optimizations as the rest of the application. The eBPF approach showed lower throughput, consistent with the overhead of crossing the kernel boundary for each intercepted call.</p>
<p>The USDT scenarios (<code>libstabst</code> and <code>usdt</code>) are not included in the table because they are proof-of-concept implementations with different exporter architectures. The core property of USDT — zero overhead when probes are not attached — was confirmed, but end-to-end benchmarking against the other approaches requires further work.</p>
<p>Full benchmark data and reproduction instructions are in the <a href="https://github.com/kakkoyun/fosdem-2026">demo repository</a>.</p>
<hr>
<h3 id="the-ecosystem-picture">The Ecosystem Picture</h3>
<p>The most grounded take from the <a href="/posts/otel-unplugged-eu-2026/">OTel Unplugged EU 2026</a> OBI/eBPF session:</p>
<blockquote>
<p>&ldquo;Document the trade-offs between OBI, compile-time, injector, and SDK. Let people choose. Make them aware of each other and let them work together.&rdquo;</p>
</blockquote>
<p>These approaches are not competing. They serve different deployment scenarios and can coexist.</p>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>Mechanism</th>
          <th>Best For</th>
          <th>Limitation</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Compile-time</strong> (Orchestrion)</td>
          <td>AST transformation via <code>-toolexec</code></td>
          <td>Deepest instrumentation, security-sensitive environments</td>
          <td>Requires rebuild</td>
      </tr>
      <tr>
          <td><strong>eBPF/OBI</strong></td>
          <td>Kernel-level network hooks</td>
          <td>Runtime flexibility, multi-language, no restart</td>
          <td>Needs kernel privileges</td>
      </tr>
      <tr>
          <td><strong>eBPF Auto</strong></td>
          <td>Uprobe hooks on Go functions</td>
          <td>Go-specific deep tracing without code changes</td>
          <td>Maintenance mode, fragile across Go versions</td>
      </tr>
      <tr>
          <td><strong>Injector/SSI</strong></td>
          <td>K8s operator + <code>LD_PRELOAD</code></td>
          <td>Lowest friction onboarding</td>
          <td>Does not work for Go&rsquo;s static binaries</td>
      </tr>
      <tr>
          <td><strong>USDT</strong></td>
          <td>Compiled probe points + bpftrace</td>
          <td>Zero overhead when not tracing, future potential</td>
          <td>Proof of concept, ecosystem immaturity</td>
      </tr>
  </tbody>
</table>
<p>The vision articulated at OTel Unplugged — <code>apt install opentelemetry</code> and everything works — requires all these layers coordinating. OBI detecting the Injector and backing off. Compile-time instrumentation detecting existing SDK usage. USDT probes coexisting with eBPF hooks. We are not there yet, but the direction is clear.</p>
<hr>
<h3 id="future-directions">Future Directions</h3>
<p>Several threads from the talk and surrounding conversations point forward:</p>
<ul>
<li>
<p><strong>OTel Compile-Time SIG.</strong> The merger between Datadog&rsquo;s Orchestrion and Alibaba&rsquo;s compile-time instrumentation under the OpenTelemetry umbrella is the most significant near-term development. A vendor-neutral, community-maintained compile-time instrumentation tool for Go would change the adoption curve.</p>
</li>
<li>
<p><strong>W3C context propagation in runtimes.</strong> If language runtimes and compilers understand trace context natively, the instrumentation story simplifies fundamentally. This was a recurring theme at OTel Unplugged.</p>
</li>
<li>
<p><strong>eBPF Tokens.</strong> <a href="https://fosdem.org/2026/schedule/event/3LLHG9-bpf-tokens-safe-userspace-ebpf/">BPF Tokens</a> could significantly reduce the privilege requirements for eBPF-based instrumentation. Instead of <code>CAP_SYS_ADMIN</code>, a token-based trust model would lower the bar for security teams.</p>
</li>
<li>
<p><strong>Native USDT in Go.</strong> The PoC fork demonstrates feasibility. Whether the Go team would accept USDT probes into the standard library is an open question, but the pattern exists in other ecosystems — Postgres, MySQL, and the JVM all have static tracepoints behind flags.</p>
</li>
<li>
<p><strong>Flight recording.</strong> The <code>golang/go#63185</code> proposal for always-on flight recording in the Go runtime could eventually provide the foundation for zero-touch distributed tracing without any external tooling.</p>
</li>
</ul>
<hr>
<h3 id="closing">Closing</h3>
<p>The instrumentation tax is real and unavoidable. The question is not whether to pay it, but how to manage it. For Go, the answer is increasingly &ldquo;you have options&rdquo; — and those options are getting better.</p>
<p>The slides are available as <a href="https://github.com/kakkoyun/fosdem-2026/blob/main/presentation.md">Markdown</a> in the repository. The demo code, Docker setup, and benchmark scripts are all in the <a href="https://github.com/kakkoyun/fosdem-2026">fosdem-2026 repository</a>. The <a href="https://www.youtube.com/watch?v=0TvrSebuDPk">recording is on YouTube</a>.</p>
<p>If you want to get involved: the <a href="https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation">OTel Compile-Time Instrumentation SIG</a>, <a href="https://github.com/open-telemetry/opentelemetry-ebpf-instrumentation">OBI</a>, and <a href="https://github.com/open-telemetry/opentelemetry-go">OTel Go</a> repositories all accept contributions. The <code>#otel-go</code> and <code>#otel-ebpf-sig</code> channels on <a href="https://slack.cncf.io/">CNCF Slack</a> are where the discussions happen.</p>
<p>See also: <a href="/posts/otel-unplugged-eu-2026/">OTel Unplugged EU 2026 field notes</a> for the broader ecosystem context.</p>
]]></content:encoded></item><item><title>OTel Unplugged EU 2026: Field Notes from the Instrumentation Frontier</title><link>https://kakkoyun.me/posts/otel-unplugged-eu-2026/</link><pubDate>Fri, 20 Feb 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/otel-unplugged-eu-2026/</guid><description>Conference field notes from OTel Unplugged EU 2026 in Brussels — covering Prometheus convergence, the injector vision, eBPF trade-offs, and the future of Go auto-instrumentation.</description><content:encoded><![CDATA[<h3 id="brussels-again-but-make-it-unplugged">Brussels Again, But Make It Unplugged</h3>
<p>The day after FOSDEM, about a hundred of us gathered at <strong>Sparks Meeting</strong> on Rue Ravenstein in Brussels for <a href="https://opentelemetry.io/blog/2025/otel-unplugged-fosdem/">OTel Unplugged EU 2026</a> — an unconference dedicated entirely to OpenTelemetry.
Purple stage lights, a mid-century auditorium with wood paneling, and the familiar buzz of people who spend their days thinking about telemetry pipelines. If you know, you know.</p>
<p><img src="/uploads/otel_unplugged_2026_agenda.jpeg"
    alt="OTel Unplugged agenda projected on stage"
    loading="lazy"
    decoding="async"></p>
<p>The format is simple: no prepared talks, no slides. Morning session brainstorming, dot-voting on topics, then self-organizing into <strong>nine rooms across four breakout slots</strong>.
You vote with your feet. If a conversation isn&rsquo;t working, you move. It&rsquo;s chaotic, it&rsquo;s honest, and it produces the kind of discussions that polished conference talks rarely achieve.</p>
<p>I spent the day bouncing between sessions on <strong>Prometheus and OpenTelemetry convergence</strong>, the <strong>Injector and Operator</strong>, <strong>OBI/eBPF</strong>, and <strong>auto-instrumentation for Go</strong>.
Four rooms, one thread connecting them all: <em>how do we make applications observable without asking developers to change their code?</em></p>
<p>Here&rsquo;s what I learned.</p>
<hr>
<h3 id="prometheus-loves-opentelemetry-its-complicated">Prometheus Loves OpenTelemetry (It&rsquo;s Complicated)</h3>
<p>Prometheus and OpenTelemetry had <strong>two sessions</strong> — one in the morning with end users and contributors, and a follow-up in the afternoon specifically for maintainers. Both were packed.
The relationship between these two projects is the kind you&rsquo;d describe as &ldquo;it&rsquo;s complicated&rdquo; on social media.</p>
<h4 id="the-resource-attributes-mess">The Resource Attributes Mess</h4>
<p>The biggest pain point? Getting OTLP data into Prometheus. <strong>Resource attributes</strong> are the central headache. OTLP has a rich hierarchy — resource, scope, and metric attributes. Prometheus is flat.
Bridging these two models means choosing between promoting all attributes, promoting some, or relying on <code>target_info</code>. There are too many config options, no consistency across deployments, and the <code>info</code> function (using <code>target_info</code>) helps but adoption is uneven.</p>
<p>One person described running an observability platform for <strong>over a thousand developers</strong>, most using Prometheus <code>remote_write</code>. Some teams want a single OTLP endpoint for logs and metrics, but that just shifts the same &ldquo;what to promote, what to drop&rdquo; problem. The frustration was palpable — someone put it bluntly: <em>&ldquo;OTel is rewriting everything again.&rdquo;</em> Different conventions (<code>.</code> vs <code>_</code>), hard <code>target_info</code> joins, and the sense that mature Prometheus semantics (<code>cluster</code>, <code>namespace</code>) are being duplicated under different names (<code>k8s.*</code>).</p>
<h4 id="migration-resistance">Migration Resistance</h4>
<p>Teams recognize the value of OTel&rsquo;s semantic conventions, but the migration path is painful. <strong>Naming inconsistencies</strong> (<code>.</code> vs <code>_</code>), hard <code>target_info</code> joins, and the cognitive overhead of moving from Prometheus&rsquo;s world view to OTel&rsquo;s. Several people mentioned that <code>PromQL IS AWESOME</code> (their emphasis, not mine) and that transformation adds overhead that people who come from a Prometheus background don&rsquo;t want to pay.</p>
<p>On the SDK side, OTel measurements require a <strong>hashmap lookup</strong> while Prometheus doesn&rsquo;t. Too many concepts — meter, instrument, aggregation — versus Prometheus&rsquo;s closer alignment to mechanical sympathy.
The performance direction being pursued? <strong>Zero allocations, no lookups</strong> — the bound instruments PoC is the concrete step toward closing that gap. Nobody in the room uses delta temporality.</p>
<blockquote>
<p>&ldquo;People care about observability, not query languages.&rdquo;</p>
</blockquote>
<h4 id="the-afternoon-maintainers-chart-a-path">The Afternoon: Maintainers Chart a Path</h4>
<p>The afternoon session brought Prometheus and OTel maintainers together. The mood was constructive. <strong>OTel SDK v2</strong> was discussed as an opportunity for the kind of breaking changes that could simplify the metrics API — a simplified, more performant, but less flexible API. The <strong>Prometheus 3.0</strong> experience was instructive: the maintainers planned for major breakage but ended up with almost none.</p>
<p>Concrete progress: <strong>David Ashpole&rsquo;s <a href="https://github.com/open-telemetry/opentelemetry-go/pull/7790">bound instruments PoC in Go</a></strong> — instruments pre-bound to specific attribute sets, eliminating the hashmap lookup.
People in the room care about Go and C++ performance, and this could be a game changer.</p>
<p>On the receiver/exporter convergence front: <strong>cAdvisor is considering archiving its Prometheus exporter</strong> and moving all code into the OTel collector. OTel Kubernetes monitoring is broadly adopted, with near-parity to kube-state-metrics.
The idea of Prometheus carrying an OTel Collector distribution was floated.</p>
<p>The messaging problem came into sharp focus: as one Prometheus maintainer put it, <em>&ldquo;Having joint statements helps towards the perception of working together.&rdquo;</em> The gap isn&rsquo;t just technical — it&rsquo;s about perception.
End users see two projects that look like they&rsquo;re competing, even when the maintainers are collaborating.</p>
<p><strong>Action items</strong>: use the <code>#otel-prometheus</code> Slack channel, meet again in <strong>Amsterdam</strong>, and produce <strong>joint messaging</strong> — &ldquo;this is built together and is compatible.&rdquo; Who owns that messaging? That&rsquo;s the open question.</p>
<hr>
<p><img src="/uploads/otel_unplugged_2026_topics.jpeg"
    alt="Emerging topics sorted on sticky notes during morning brainstorming"
    loading="lazy"
    decoding="async"></p>
<h3 id="the-injector-from-ld_preload-to-apt-install-opentelemetry">The Injector: From LD_PRELOAD to <code>apt install opentelemetry</code></h3>
<p>Two sessions covered the <strong>Injector and Operator</strong> ecosystem — one focused on the general architecture, the other specifically on <strong>OBI and Injector coordination for Go</strong>.
The framing that stuck with me came early: <em>&ldquo;OTel instrumentation feels more like a collection of tools than a product.&rdquo;</em> That&rsquo;s why the Injector exists — to close the gap between what OTel offers and what users expect to just work.</p>
<h4 id="injector-vs-operator">Injector vs Operator</h4>
<p>The <strong>Injector</strong> is opinionated and out-of-the-box. It aims for <strong>80% coverage with zero configuration</strong>. The <strong>Operator</strong> is for power users who want fine-grained control. Both end users and OTel maintainers were in the room, and more people knew about the Operator than the Injector.</p>
<p>The Injector works via <code>LD_PRELOAD</code> — it hooks into process loading to activate SDK instrumentation for Java, .NET, Node.js, and soon Python. It&rsquo;s being used <strong>in production at scale</strong> on Kubernetes. It can detect libc vs musl. Blocking system start during injection? Not perceived as a problem by anyone in the room.</p>
<p>The inevitable question came up: <strong>&ldquo;What about Go?&rdquo;</strong> For Go&rsquo;s statically linked binaries, there&rsquo;s no <code>LD_PRELOAD</code> equivalent. The answer is either eBPF or compile-time instrumentation. Go remains the special case that requires different thinking.</p>
<h4 id="beyond-kubernetes">Beyond Kubernetes</h4>
<p>There&rsquo;s clear demand for the Injector <strong>outside of Kubernetes</strong> — EC2, bare metal, traditional VMs. Users not on K8s or Docker <em>&ldquo;end up using custom Ansibles&rdquo;</em> — the packaging gap is real and concrete. System packages (Debian, RPM) are needed, but hosting for them doesn&rsquo;t exist yet. <strong>Red Hat is looking into packaging OTel components.</strong> Multiple projects are independently solving the same packaging problems — signatures, distribution, hosting — which led to a proposal for a <strong>new SIG on OS packaging</strong>.</p>
<h4 id="the-vision-one-package-to-rule-them-all">The Vision: One Package to Rule Them All</h4>
<p>The afternoon session on OBI and Injector for Go articulated a bold vision:</p>
<blockquote>
<p>Run <code>apt install opentelemetry</code> and get everything running — SDKs, Injector, OBI, all coordinated.</p>
</blockquote>
<p>This would require massive coordination between instrumentation providers — and it would include the <strong>OTel profiler</strong> alongside the SDKs and Injector. The group discussed how to avoid <strong>double instrumentation</strong> when both OBI and the Injector are present — OBI should detect the Injector and back off (similar to how it already detects other SDKs). A creative proposal emerged: <strong>OBI injecting the Injector</strong> instead of the Operator, since eBPF can intercept process loading natively.</p>
<p>The reality is that <strong>OTel declarative configuration</strong> doesn&rsquo;t cleanly fit either project&rsquo;s model yet. The Injector has its own config format. OBI instruments many applications from a single daemon, which doesn&rsquo;t map neatly to per-application YAML. This is a design problem that needs solving before the <code>apt install</code> dream becomes real.</p>
<p>And the question that kept coming back in both sessions — <em>&ldquo;What about Go?&rdquo;</em> — led naturally into the next room.</p>
<hr>
<h3 id="ebpf-and-the-instrumentation-tax">eBPF and the Instrumentation Tax</h3>
<p>The <strong>OBI/eBPF session</strong> drew a crowd interested in the promise and the trade-offs of non-invasive auto-instrumentation.</p>
<p><img src="/uploads/otel_unplugged_2026_stickies.jpeg"
    alt="Session brainstorming — Go instrumentation topics cluster together"
    loading="lazy"
    decoding="async"></p>
<p><a href="https://github.com/open-telemetry/opentelemetry-go-instrumentation">OBI</a> (eBPF-based auto-instrumentation) uses <strong>uprobes</strong> to hook into application functions at the kernel level. No source code modification, no recompilation, no SDK integration. The trade-off? You need <strong>privileges</strong>. <code>CAP_SYS_ADMIN</code> or root access is a hard sell for security teams, and the discussion around reducing privilege requirements was lively.</p>
<p>The operational reality came through early: someone described a case where <em>&ldquo;the instrumentation was bringing down the pod&rdquo;</em> — auto-injection and sidecars destabilizing the very workloads they&rsquo;re supposed to observe. That anecdote set the tone for the rest of the session and led directly to the quote that stuck with me most.</p>
<p>A bright spot: <strong><a href="https://fosdem.org/2026/schedule/event/3LLHG9-bpf-tokens-safe-userspace-ebpf/">eBPF Tokens</a></strong>, a newer Linux facility for safer userspace eBPF, could significantly lower the trust bar. There was optimism in the room about this direction.</p>
<p>OBI isn&rsquo;t just about application tracing. It shines in <strong>network observability</strong> — topology mapping, correlating network stack behavior with application layer events. Someone asked about lock observability — <em>&ldquo;Maybe profiling&rdquo;</em> was the answer, hinting at the breadth of what people want from eBPF beyond just tracing. And there&rsquo;s an underexplored opportunity around <strong>USDTs</strong> (user-defined static tracepoints). Postgres and MySQL already have them behind flags. Rust makes them easy to add. But we need to convince <strong>popular libraries across more languages</strong> to adopt them.</p>
<p>A broader point was raised: <strong>W3C context propagation</strong> should be pushed into language runtimes and compilers, not just libraries. If the runtime itself understands trace context, the instrumentation story becomes fundamentally simpler.</p>
<p>The most grounded take came during the Go discussion:</p>
<blockquote>
<p>&ldquo;Document the trade-offs between OBI, compile-time, injector, and SDK. Let people choose. Make them aware of each other and let them work together.&rdquo;</p>
</blockquote>
<p>And the reality check:</p>
<blockquote>
<p>&ldquo;Instrumentation tax is inevitable. Manage it, don&rsquo;t pretend it&rsquo;s free.&rdquo;</p>
</blockquote>
<p>The consensus across multiple sessions: <strong>stop treating these approaches as competing camps</strong>.
They&rsquo;re complementary layers for different deployment scenarios:</p>
<table>
  <thead>
      <tr>
          <th>Approach</th>
          <th>Mechanism</th>
          <th>Trade-off</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td><strong>Compile-time</strong></td>
          <td>AST transformation via <code>-toolexec</code></td>
          <td>Deepest instrumentation, zero runtime overhead, requires rebuild</td>
      </tr>
      <tr>
          <td><strong>eBPF/OBI</strong></td>
          <td>Kernel-level uprobe hooking</td>
          <td>No app modification, needs kernel privileges</td>
      </tr>
      <tr>
          <td><strong>Injector/SSI</strong></td>
          <td>K8s operator triggering instrumentation</td>
          <td>Lowest friction onboarding, abstracts complexity</td>
      </tr>
  </tbody>
</table>
<p>On the Kubernetes operations side, there was a concrete proposal: a <strong>CRD for otel-operator</strong> to deploy OBI daemonsets — with config validation and selective node deployment via workload labels. Not theoretical; the group was sketching the API surface.</p>
<hr>
<p><img src="/uploads/otel_unplugged_2026_community.jpeg"
    alt="Community and ecosystem topics — the other half of the brainstorming table"
    loading="lazy"
    decoding="async"></p>
<h3 id="patterns-across-the-day">Patterns Across the Day</h3>
<p>Beyond the sessions I attended, three themes kept surfacing throughout the unconference.</p>
<h4 id="ship-faster-vs-stable-by-default">Ship Faster vs Stable by Default</h4>
<p>Two rooms, opposite tensions. One group argued: <em>&ldquo;We discourage people from trying. Processes feel rigid. We can only learn if we actually build something.&rdquo;</em> The Prometheus model — experiment first, let things mature, specify later — was held up as the better feedback loop. The other group was laser-focused on <strong>stability</strong>: feature gates, opt-in experimental features, the pain of breaking changes in semantic conventions. The impatience was clear: <em>&ldquo;Less bike-shedding, more doing.&rdquo;</em></p>
<p>Both are right. The community is threading a needle between moving fast enough to stay relevant and being stable enough that enterprises trust the project. The gap between these two positions is where a lot of energy gets spent.</p>
<h4 id="the-maintainer-crisis">The Maintainer Crisis</h4>
<p>This came up in at least three rooms. <strong>Not enough maintainers, too many PRs, codeowners disappearing.</strong> The JavaScript SIG has an automated script to move inactive maintainers to emeritus after three months.
Other SIGs handle it manually. Some SIGs have tried a buddy/mentor system for onboarding new contributors — it helps, but it doesn&rsquo;t scale across all SIGs when the existing maintainers barely have time to review PRs. The phrase that stuck with me:</p>
<blockquote>
<p>&ldquo;Maintainership is privilege AND responsibility.&rdquo;</p>
</blockquote>
<p>And a new problem: as one maintainer put it directly, <em>&ldquo;AI slop creates a lot of work for maintainers.&rdquo;</em> Low-quality AI-generated PRs need review just like everything else, but they rarely lead to productive outcomes — creating a treadmill of review work that burns out the very people the project can&rsquo;t afford to lose.</p>
<h4 id="opentelemetry-go-auto-quietly-fading">opentelemetry-go-auto: Quietly Fading</h4>
<p>During the Go-focused session, someone asked about <code>opentelemetry-go-auto</code> — the eBPF-based Go auto-instrumentation project.
The answer was frank: the project <strong>&ldquo;seems in maintenance mode, some of their maintainers are already contributing to OBI.&rdquo;</strong> The group decided to keep it out of the discussions unless those maintainers want to participate.
No drama, just the natural evolution of open-source projects. The people moved to where the momentum is.</p>
<hr>
<h3 id="what-comes-next">What Comes Next</h3>
<p>The unconference produced concrete next steps across every thread:</p>
<ul>
<li><strong>Prometheus + OTel</strong>: Convergence work continues. Joint messaging, Amsterdam meetup, bound instruments moving forward.</li>
<li><strong>Injector</strong>: Merge functionality into the Operator starting with one language. System packages for non-K8s environments.</li>
<li><strong>OBI</strong>: Gradual protocol expansion, packaging SIG proposal, exploration of an eBPF-based OTel Collector.</li>
<li><strong>Go auto-instrumentation</strong>: Coordinate all three approaches, document trade-offs clearly for end users.</li>
</ul>
<hr>
<h3 id="unplugged">Unplugged</h3>
<p>The unconference format works because <strong>the hardest problems in observability right now are not technical</strong> — they&rsquo;re social.
Governance, maintenance burden, convergence between projects that grew up independently, vendor-neutrality when vendors are the primary contributors.
You can&rsquo;t solve these with a slide deck. You need a room, a whiteboard, and honest conversation.</p>
<p>As I always say — <strong>the hallway track is the real conference.</strong> OTel Unplugged is an entire day of hallway track, and it&rsquo;s exactly what the community needs.</p>
<p>If you want to get involved: join the <a href="https://slack.cncf.io/">CNCF Slack</a> and find the <code>#otel-prometheus</code>, <code>#otel-ebpf-instrumentation</code>, and <code>#otel-go-instrumentation</code> channels.
The SIG meetings are open and listed on the <a href="https://github.com/open-telemetry/community">OTel community repo</a>.
Show up, contribute, and help shape the future of observability.</p>
<p>Already looking forward to the next one.</p>
]]></content:encoded></item><item><title>FOSDEM 2026: Even Bigger, Even Better</title><link>https://kakkoyun.me/posts/fosdem-2026/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/fosdem-2026/</guid><description>FOSDEM 2026 highlights — OTel Unplugged, Go Devroom, Software Performance Devroom, and the hallway track that never disappoints.</description><content:encoded><![CDATA[<h3 id="another-year-another-fosdem">Another Year, Another FOSDEM</h3>
<p><strong>FOSDEM</strong> — the annual Brussels pilgrimage. If you&rsquo;ve been, you know the drill: too many talks, too little time, questionable coffee, and the kind of conversations that only happen when you pack thousands of open-source developers into a university campus in the dead of winter.</p>
<p>This year was different for me, though. Two talks in two devrooms, three sessions at OTel Unplugged — and this time, I brought the whole family. My wife and our toddler (who has graduated from &ldquo;can barely walk&rdquo; to &ldquo;can absolutely destroy a hotel room in under four minutes&rdquo;) came along, and we turned it into a proper trip — FOSDEM, then a few days exploring <strong>Ghent</strong> and <strong>Antwerp</strong> before heading home.</p>
<p>The conference part was incredible. The journey home&hellip; well, we&rsquo;ll get to that.</p>
<h3 id="saturday-morning-ebpf-devroom">Saturday Morning: eBPF Devroom</h3>
<p>Last year the eBPF Devroom was impenetrable — nobody leaves, nobody gets in. This year I made it in early and spent the morning there.</p>
<p>Three sessions stood out:</p>
<ul>
<li>
<p><strong>&quot;<a href="https://fosdem.org/2026/schedule/event/8GVBN7-ebpf-hooks-gotchas/">eBPF Hookpoint Gotchas: Why Your Program Fires (or Fails) in Unexpected Ways</a>&quot;</strong> — Donia Chaiehloudj and Chris Tarazi walked through the subtle behaviors of kprobes, fentry, tracepoints, and uprobes that catch everyone off guard. The kind of talk where half the room is nodding along because they&rsquo;ve hit these exact edge cases in production. If you write eBPF programs, this is required viewing.</p>
</li>
<li>
<p><strong>&quot;<a href="https://fosdem.org/2026/schedule/event/H3LM7G-performance_and_reliability_pitfalls_of_ebpf/">Performance and Reliability Pitfalls of eBPF</a>&quot;</strong> — Usama Saqib shared hard-won lessons from running eBPF at scale: kprobe performance varying across kernel versions, fentry stability issues, and the challenges of scaling uprobes. Directly relevant to anyone using eBPF-based auto-instrumentation — the kind of detail you don&rsquo;t find in documentation.</p>
</li>
<li>
<p><strong>&quot;<a href="https://fosdem.org/2026/schedule/event/VTXQSK-oomprof/">OOMProf: Profiling Go Heap Memory at OOM Time</a>&quot;</strong> — Tommy Reilly presented OOMProf, a Go library that uses eBPF to hook into Linux OOM tracepoints and capture heap profiles right before the kernel kills your process. Exports to pprof or Parca. The intersection of Go, eBPF, and profiling — three things I care deeply about.</p>
</li>
</ul>
<p>The eBPF Devroom continues to be one of the most technically dense tracks at FOSDEM. Every talk assumes you already know the basics and goes straight to the edge cases and production realities.</p>
<h3 id="sunday-two-talks-two-devrooms">Sunday: Two Talks, Two Devrooms</h3>
<p>Sunday was a double-header. Two talks in two devrooms.</p>
<p><strong>Augusto de Oliveira</strong> and I co-presented <strong>&ldquo;How to Reliably Measure Software Performance&rdquo;</strong> in the <strong>Software Performance Devroom</strong>.</p>
<p><img src="/uploads/fosdem26_perf_talk_close.jpeg"
    alt="Kemal and Augusto presenting at the Software Performance Devroom"
    loading="lazy"
    decoding="async"></p>
<p>The talk opened with one of my favorite stories in science: the OPERA experiment that appeared to show neutrinos traveling faster than the speed of light, only for the root cause to be a single fiber-optic cable that wasn&rsquo;t fully plugged in. That&rsquo;s benchmarking in a nutshell — a world where loose cables are everywhere and your numbers are lying to you until you prove otherwise.</p>
<p>We covered the full stack of what it takes to measure reliably. <strong>Environment control</strong>: bare metal instances, disabling SMT, CPU affinity, cache management. <strong>Benchmark design</strong>: making measurements representative and repeatable. <strong>Statistical rigor</strong>: because if you&rsquo;re not thinking about variance, you&rsquo;re not thinking. And then the part I&rsquo;m most excited about — <strong>integrating benchmarks into development workflows</strong>. Performance quality gates on PRs, auto-generated regression comments, continuous benchmarking infrastructure. We showed what we&rsquo;ve built at Datadog and pointed to the open-source alternatives available today.</p>
<blockquote>
<p>&ldquo;Performance matters. It&rsquo;s not always the first thing we think about when building software. But in the end, performance is what users experience.&rdquo;</p>
</blockquote>
<p>The Performance Devroom had a strong lineup all day. The audience was deeply technical — people who care about p99 latencies and can argue for an hour about whether your benchmark harness is introducing measurement bias. My kind of crowd.</p>
<p>The <a href="/posts/fosdem-2026-measuring-software-performance/">technical blog post</a> goes deeper, and the <a href="/talks/how-to-reliably-measure-software-performance/">talk page</a> has the slides and recording.</p>
<p>Then I crossed campus to the <strong>Go Devroom</strong>. My kind of room.</p>
<p><strong>Hannah S. Kim</strong> and I presented <strong>&ldquo;How to Instrument Go Without Changing a Single Line of Code&rdquo;</strong> — a talk comparing every strategy available today for zero-touch Go observability.</p>
<p><img src="/uploads/fosdem26_go_crowd.jpeg"
    alt="Hannah and Kemal presenting at the Go Devroom"
    loading="lazy"
    decoding="async"></p>
<p>We walked through eBPF-based auto-instrumentation with OBI, compile-time manipulation with tools like Orchestrion and the OTel Go compile-time instrumentation project, runtime injection via LD_PRELOAD, and the emerging world of USDTs for Go.</p>
<p>The core of the talk was practical: benchmark results and small realistic services, compared along three axes — <strong>performance overhead</strong>, <strong>robustness across Go versions</strong>, and <strong>operational friction</strong>. We showed the trade-offs honestly. eBPF gives you zero code changes but needs kernel privileges. Compile-time rewriting gives you the deepest instrumentation but requires a rebuild. The Injector abstracts complexity but is currently Kubernetes-only. There&rsquo;s no silver bullet, just choices with different costs.</p>
<p>We also looked forward at how upcoming work in the Go runtime — flight recording, improved diagnostics primitives, USDT probe generation — could unlock cleaner hooks for future instrumentation. The room was full. The questions were sharp. Hannah handled the eBPF deep-dives while I covered the compile-time and operational integration angles. It worked.</p>
<p>If you want the full technical breakdown, I wrote a <a href="/posts/fosdem-2026-auto-instrumenting-go/">companion blog post</a> and the <a href="/talks/how-to-instrument-go-without-changing-code/">talk page has the slides and recording</a>.</p>
<h3 id="monday-otel-unplugged">Monday: OTel Unplugged</h3>
<p>The day after FOSDEM, about a hundred of us gathered at <strong>Sparks Meeting</strong> on Rue Ravenstein for <a href="https://opentelemetry.io/blog/2025/otel-unplugged-fosdem/">OTel Unplugged EU 2026</a> — an unconference dedicated entirely to OpenTelemetry. No slides, no prepared talks, just session brainstorming, dot-voting, and then splitting into nine rooms across four breakout slots.</p>
<p>I led or co-led <strong>three sessions</strong>: one on <strong>Prometheus and OTel convergence</strong>, one on <strong>OBI/eBPF-based auto-instrumentation</strong>, and one on <strong>the Injector and OBI coordination for Go</strong>. The thread connecting all three was the same question that keeps me up at night: <em>how do we make applications observable without asking developers to change their code?</em></p>
<p>I wrote a <a href="/posts/otel-unplugged-eu-2026/">dedicated post covering the full day</a>, so I won&rsquo;t repeat it here. The short version: the community is converging. Prometheus and OTel maintainers are charting a path together, the Injector vision is expanding beyond Kubernetes, and the various auto-instrumentation approaches for Go are finally being treated as complementary layers rather than competing camps. Read the post for the details.</p>
<h3 id="the-hallway-track">The Hallway Track</h3>
<p>As always — <strong>the hallway track is the real conference.</strong></p>
<p>Some of the best conversations happened between sessions, over coffee, during the frantic sprints between ULB buildings. Catching up with <strong>Prometheus maintainers</strong> about v3 adoption and the road ahead. Talking auto-instrumentation strategy with OTel contributors who I&rsquo;d only known through GitHub issues. Comparing notes on performance engineering practices with people running infrastructure at wildly different scales.</p>
<p>The informal <strong>Prometheus maintainers gathering</strong> was a highlight. Getting the people who build and maintain the project into the same room, away from structured agendas, just talking about what&rsquo;s working and what isn&rsquo;t — that&rsquo;s where real alignment happens. No Zoom call will ever replicate that.</p>
<p>I&rsquo;m incredibly grateful for the people I managed to see this year. And as always, slightly heartbroken about the ones I missed. FOSDEM is four thousand developers in one place for a weekend, and no matter how fast you move, you can&rsquo;t see everyone.</p>
<h3 id="travel-and-logistics-the-sequel-nobody-asked-for">Travel and Logistics: The Sequel Nobody Asked For</h3>
<p>A FOSDEM without travel drama is, apparently, not something the universe allows for me.</p>
<p>We flew <strong>Berlin to Brussels</strong> on Friday and had a great time — FOSDEM all weekend, OTel Unplugged on Monday, then a few days of family time in <strong>Ghent</strong> and <strong>Antwerp</strong>. Belgian frites, Belgian waffles, Belgian everything. The toddler approved.</p>
<p>Then came Thursday. Our flight home from Brussels to Berlin: first delayed, then cancelled. <strong>Berlin airport shut down.</strong> The coldest winter in twenty years had frozen the city solid. We ended up at a hotel near Brussels airport with a very tired toddler and no plan B.</p>
<p>Friday morning we flew to <strong>Frankfurt</strong> instead. Surely a train from Frankfurt to Berlin would be straightforward? Of course not. The Frankfurt-to-Berlin flight: also cancelled. We rebooked on a train, but our checked luggage was&hellip; somewhere. The airline couldn&rsquo;t tell us where. We waited two hours at the airport, watching the carousel go around empty, then gave up and headed to the train station.</p>
<p>Four and a half hours of train ride later, we were finally home. <strong>Antwerp to Berlin: 29.5 hours, door to door.</strong> With a toddler. In the coldest week Germany had seen in two decades.</p>
<p>The luggage? It arrived ten days later. Intact, thankfully. But ten days.</p>
<p>Last year&rsquo;s transport chaos was cute by comparison.</p>
<h3 id="looking-forward">Looking Forward</h3>
<p><strong>FOSDEM 2026</strong> was my best one yet. Two talks across the weekend, three unconference sessions on Monday, and more hallway track conversations than I can count. The open-source observability community is in a remarkable place right now — Prometheus and OpenTelemetry converging, auto-instrumentation maturing across multiple approaches, and performance engineering finally getting the attention it deserves.</p>
<p>Already thinking about next year. If you&rsquo;re into open source and haven&rsquo;t experienced FOSDEM, just go. You won&rsquo;t regret it.</p>
]]></content:encoded></item><item><title>Fix Go Module Downloads Behind a Corporate VPN</title><link>https://kakkoyun.me/posts/goproxy-fallback-behind-vpn/</link><pubDate>Thu, 12 Feb 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/goproxy-fallback-behind-vpn/</guid><description>How to use the GOPROXY pipe separator to gracefully fall back to the public proxy when your corporate Go module proxy is unreachable behind a VPN.</description><content:encoded><![CDATA[<p>If you work at a company that runs its own Go module proxy and you connect through a VPN, you&rsquo;ve probably seen this:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>Get &#34;https://binaries.example.com/google.golang.org/grpc/@v/v1.77.0.mod&#34;:
</span></span><span style="display:flex;"><span>  dial tcp 172.27.5.36:443: i/o timeout
</span></span></code></pre></td></tr></table>
</div>
</div><p>The module has nothing to do with your company. It&rsquo;s a public dependency. Yet Go refuses to fetch it from the public proxy and just dies with a timeout. The frustrating part: you know <code>proxy.golang.org</code> has the module, and your config lists it as a fallback. So why doesn&rsquo;t it fall through?</p>
<h2 id="the-comma-trap">The comma trap</h2>
<p>A typical corporate Go setup looks like this:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export GOPROXY<span style="color:#f92672">=</span>corp-proxy.internal,https://proxy.golang.org,direct
</span></span></code></pre></td></tr></table>
</div>
</div><p>The comma separator between proxies looks harmless, but it controls exactly when Go tries the next proxy in the chain. With commas, Go only falls through on <strong>HTTP 404 or 410</strong> — meaning the proxy responded and said &ldquo;I don&rsquo;t have this module.&rdquo; Any other error, including TCP timeouts, DNS failures, and 5xx server errors, is treated as a <strong>hard failure</strong>. Go stops and reports the error.</p>
<p>When your VPN is disconnected, the corporate proxy is unreachable. That&rsquo;s a TCP timeout, not a 404. Go never tries <code>proxy.golang.org</code>.</p>
<h2 id="the-pipe-fix">The pipe fix</h2>
<p>Go 1.15 introduced the pipe separator (<code>|</code>) as an alternative to commas. With a pipe, Go falls through on <strong>any error</strong>, including network failures:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export GOPROXY<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;corp-proxy.internal|https://proxy.golang.org,direct&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Notice the mix of separators. The pipe between the corporate proxy and the public proxy means &ldquo;if the corporate proxy is unreachable, try the public one.&rdquo; The comma between the public proxy and <code>direct</code> means &ldquo;only go direct if the public proxy returns 404&rdquo; — which is the safer default for the last hop.</p>
<h2 id="why-not-use-pipes-everywhere">Why not use pipes everywhere?</h2>
<p>The comma separator exists for a reason: <strong>privacy</strong>. When Go tries to fetch a module from a proxy, it reveals the module path in the request URL. If your corporate proxy is down and you use pipes everywhere, Go would send your private module paths (<code>github.com/your-company/secret-service</code>) to <code>proxy.golang.org</code> before finally trying to fetch them directly.</p>
<p>The <code>GOPRIVATE</code> and <code>GONOPROXY</code> environment variables mitigate this. Modules matching those patterns bypass the proxy chain entirely and are fetched directly from source. If you set <code>GOPRIVATE</code> correctly, the pipe separator is safe for your use case:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export GOPRIVATE<span style="color:#f92672">=</span>github.com/your-company
</span></span><span style="display:flex;"><span>export GONOPROXY<span style="color:#f92672">=</span>github.com/your-company
</span></span><span style="display:flex;"><span>export GONOSUMDB<span style="color:#f92672">=</span>github.com/your-company,go.internal.example.com
</span></span><span style="display:flex;"><span>export GOPROXY<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;corp-proxy.internal|https://proxy.golang.org,direct&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>With this setup, private modules never touch any proxy. Public modules try the corporate proxy first (fast, cached, available on VPN), fall back to the public proxy on failure, and go direct as a last resort.</p>
<h2 id="the-full-picture">The full picture</h2>
<p>Here&rsquo;s how Go resolves a module with this configuration:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>go get google.golang.org/grpc@v1.77.0
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>1. Does &#34;google.golang.org/grpc&#34; match GOPRIVATE? No.
</span></span><span style="display:flex;"><span>2. Try corp-proxy.internal -&gt; TCP timeout (VPN off)
</span></span><span style="display:flex;"><span>3. Separator is &#34;|&#34; -&gt; fall through on any error
</span></span><span style="display:flex;"><span>4. Try proxy.golang.org -&gt; 200 OK, module found
</span></span><span style="display:flex;"><span>5. Done.
</span></span></code></pre></td></tr></table>
</div>
</div><p>And for a private module:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-fallback" data-lang="fallback"><span style="display:flex;"><span>go get github.com/your-company/secret-service@latest
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>1. Does &#34;github.com/your-company/secret-service&#34; match GOPRIVATE? Yes.
</span></span><span style="display:flex;"><span>2. Skip proxy chain entirely.
</span></span><span style="display:flex;"><span>3. Fetch directly from github.com via git.
</span></span><span style="display:flex;"><span>4. Done.
</span></span></code></pre></td></tr></table>
</div>
</div><h2 id="one-line-fix">One-line fix</h2>
<p>If you&rsquo;re in this situation, the fix is a single character change in your shell config:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">- export GOPROXY=corp-proxy.internal,https://proxy.golang.org,direct
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ export GOPROXY=&#34;corp-proxy.internal|https://proxy.golang.org,direct&#34;
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>Reload your shell (<code>source ~/.zshrc</code>) and Go will gracefully fall back to the public proxy whenever your corporate proxy is unreachable. No more waiting for timeouts to tell you what you already know.</p>
]]></content:encoded></item><item><title>Stop Putting API Keys in Your Shell Config</title><link>https://kakkoyun.me/posts/stop-putting-api-keys-in-shell-config/</link><pubDate>Thu, 12 Feb 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/stop-putting-api-keys-in-shell-config/</guid><description>The agentic coding boom has developers pasting API keys into .zshrc in plaintext. Use the 1Password CLI to manage secrets properly in five minutes.</description><content:encoded><![CDATA[<p>We all know better. Don&rsquo;t hardcode secrets. Use a vault. Rotate your keys. We&rsquo;ve been saying this for years.</p>
<p>And then the <strong>agentic coding boom</strong> happened.</p>
<p>Suddenly every tool wants an API key. OpenAI, Anthropic, Gemini, Groq, Mistral, Replicate—the list grows weekly. And where do those keys end up? Right there in <code>.zshrc</code>, in plain text, because you needed it working <em>right now</em> and you were going to fix it later.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># The &#34;I&#39;ll fix this later&#34; hall of shame</span>
</span></span><span style="display:flex;"><span>export OPENAI_API_KEY<span style="color:#f92672">=</span>sk-proj-abc123...
</span></span><span style="display:flex;"><span>export ANTHROPIC_API_KEY<span style="color:#f92672">=</span>sk-ant-xyz789...
</span></span><span style="display:flex;"><span>export GEMINI_API_KEY<span style="color:#f92672">=</span>AIzaSy...
</span></span></code></pre></td></tr></table>
</div>
</div><p>I caught myself doing exactly this. Two API keys, sitting in my dotfiles, probably backed up to Time Machine, possibly in shell history, definitely in my terminal scrollback. Let&rsquo;s fix this properly.</p>
<h2 id="the-problem">The Problem</h2>
<p>Plain text API keys in shell configs are bad for reasons you already know:</p>
<ol>
<li><strong>Shell history</strong> — <code>~/.zsh_history</code> records commands, and sometimes you <code>echo $OPENAI_API_KEY</code> to debug something</li>
<li><strong>Backup snapshots</strong> — Time Machine, cloud backups, dotfile repos all capture the file</li>
<li><strong>Shoulder surfing</strong> — <code>cat ~/.zshrc</code> during a screen share or a pairing session</li>
<li><strong>Terminal scrollback</strong> — the key is sitting in your terminal buffer right now</li>
</ol>
<p>And this isn&rsquo;t just a theoretical risk. Attackers actively scan repos and backups for unprotected credentials — and when they find stolen API keys, they rack up thousands of dollars in charges. The platform bills the original owner.</p>
<p>The &ldquo;I&rsquo;ll rotate it later&rdquo; never comes. Meanwhile these keys have billing attached to them.</p>
<h2 id="the-fix-1password-cli">The Fix: 1Password CLI</h2>
<p>If you use 1Password, you already have a secret manager with biometric unlock, audit logging, and team sharing. The <code>op</code> CLI lets you pull secrets into your shell without ever writing them to disk.</p>
<h3 id="step-1-install-the-cli">Step 1: Install the CLI</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>brew install --cask 1password-cli
</span></span></code></pre></td></tr></table>
</div>
</div><p>Enable the CLI integration in 1Password desktop app: <strong>Settings &gt; Developer &gt; Connect with 1Password CLI</strong>. This lets the CLI authenticate via the desktop app (Touch ID on Mac) instead of requiring a separate login.</p>
<h3 id="step-2-store-your-keys">Step 2: Store Your Keys</h3>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>op item create <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --category<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;API Credential&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --title<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;OpenAI API Key&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --vault<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Private&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;credential=sk-proj-your-key-here&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>op item create <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --category<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;API Credential&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --title<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Gemini API Key&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  --vault<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;Private&#34;</span> <span style="color:#ae81ff">\
</span></span></span><span style="display:flex;"><span>  <span style="color:#e6db74">&#34;credential=AIzaSy-your-key-here&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="step-3-replace-hardcoded-values">Step 3: Replace Hardcoded Values</h3>
<p>In your <code>.zshrc</code> (or <code>.bashrc</code>, <code>.profile</code>, whatever you use):</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-diff" data-lang="diff"><span style="display:flex;"><span><span style="color:#f92672">- export OPENAI_API_KEY=sk-proj-abc123...
</span></span></span><span style="display:flex;"><span><span style="color:#f92672">- export GEMINI_API_KEY=AIzaSy...
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ export OPENAI_API_KEY=$(op read &#34;op://Private/OpenAI API Key/credential&#34; --no-newline 2&gt;/dev/null)
</span></span></span><span style="display:flex;"><span><span style="color:#a6e22e">+ export GEMINI_API_KEY=$(op read &#34;op://Private/Gemini API Key/credential&#34; --no-newline 2&gt;/dev/null)
</span></span></span></code></pre></td></tr></table>
</div>
</div><p>That&rsquo;s it. Three steps. The keys now live in 1Password, protected by your master password and biometric auth.</p>
<p>One catch: this triggers a 1Password biometric prompt every time you open a terminal. If that bothers you (it bothered me), see <a href="#what-about-shell-startup-speed">Shell Startup Speed</a> for the lazy-loading version that only prompts when you actually run a command.</p>
<h3 id="step-4-rotate-the-old-keys">Step 4: Rotate the Old Keys</h3>
<p>This is the step people skip. <strong>Do it now.</strong> The old keys have been in plaintext. Assume they&rsquo;re compromised.</p>
<ul>
<li>OpenAI: <a href="https://platform.openai.com/api-keys">platform.openai.com/api-keys</a></li>
<li>Google AI: <a href="https://aistudio.google.com/apikey">aistudio.google.com/apikey</a></li>
<li>Anthropic: <a href="https://console.anthropic.com/settings/keys">console.anthropic.com/settings/keys</a></li>
</ul>
<p>Generate new keys, update the 1Password items with <code>op item edit</code>, and you&rsquo;re done.</p>
<h2 id="the-details-worth-knowing">The Details Worth Knowing</h2>
<h3 id="why---no-newline">Why <code>--no-newline</code>?</h3>
<p><code>op read</code> appends a trailing newline by default. API keys with a stray newline cause cryptic authentication failures—the kind where the key &ldquo;looks right&rdquo; but every request returns 401. The <code>--no-newline</code> flag strips it.</p>
<h3 id="why-2devnull">Why <code>2&gt;/dev/null</code>?</h3>
<p>If 1Password is locked or the CLI isn&rsquo;t authenticated, <code>op read</code> writes an error to stderr. The redirect silences that so you don&rsquo;t get a wall of errors every time you open a terminal without 1Password unlocked. The variable simply becomes empty.</p>
<p>The tradeoff: a misconfigured vault path also fails silently. Test it once after setup, and you&rsquo;re fine.</p>
<h3 id="what-about-shell-startup-speed">What About Shell Startup Speed?</h3>
<p>The eager approach above runs <code>op read</code> at shell init, which means every new terminal triggers a 1Password biometric prompt. If you open terminals frequently, this gets old fast.</p>
<p>The fix is lazy loading with command-specific triggers. In zsh, the <code>preexec</code> hook fires right before a command executes and receives the command string — perfect for deciding <em>which</em> secrets to load <em>when</em>:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Map: env var → 1Password secret reference</span>
</span></span><span style="display:flex;"><span>typeset -A _op_refs<span style="color:#f92672">=(</span>
</span></span><span style="display:flex;"><span>  OPENAI_API_KEY  <span style="color:#e6db74">&#34;op://Private/OpenAI API Key/credential&#34;</span>
</span></span><span style="display:flex;"><span>  GEMINI_API_KEY  <span style="color:#e6db74">&#34;op://Private/Gemini API Key/credential&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Map: command → which keys it needs</span>
</span></span><span style="display:flex;"><span>typeset -A _op_cmd_keys<span style="color:#f92672">=(</span>
</span></span><span style="display:flex;"><span>  codex   <span style="color:#e6db74">&#34;OPENAI_API_KEY&#34;</span>
</span></span><span style="display:flex;"><span>  aider   <span style="color:#e6db74">&#34;OPENAI_API_KEY GEMINI_API_KEY&#34;</span>
</span></span><span style="display:flex;"><span>  gemini  <span style="color:#e6db74">&#34;GEMINI_API_KEY&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>_maybe_load_op_secrets<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  local cmd<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>1%% *<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>     <span style="color:#75715e"># extract first word</span>
</span></span><span style="display:flex;"><span>  cmd<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>cmd##*/<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>         <span style="color:#75715e"># strip path prefix</span>
</span></span><span style="display:flex;"><span>  local keys<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>_op_cmd_keys[$cmd]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">[[</span> -z <span style="color:#e6db74">&#34;</span>$keys<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> key in <span style="color:#e6db74">${</span>=keys<span style="color:#e6db74">}</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">[[</span> -n <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>(P)key<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">continue</span>   <span style="color:#75715e"># already loaded</span>
</span></span><span style="display:flex;"><span>    export <span style="color:#e6db74">&#34;</span>$key<span style="color:#e6db74">=</span><span style="color:#66d9ef">$(</span>op read <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>_op_refs[$key]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> --no-newline 2&gt;/dev/null<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>preexec_functions<span style="color:#f92672">+=(</span>_maybe_load_op_secrets<span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Manual fallback: load everything</span>
</span></span><span style="display:flex;"><span>load-secrets<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> key ref in <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>(@kv)_op_refs<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    export <span style="color:#e6db74">&#34;</span>$key<span style="color:#e6db74">=</span><span style="color:#66d9ef">$(</span>op read <span style="color:#e6db74">&#34;</span>$ref<span style="color:#e6db74">&#34;</span> --no-newline 2&gt;/dev/null<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This gives you three properties:</p>
<ul>
<li><strong>No startup cost</strong> — terminal opens instantly, no biometric prompt</li>
<li><strong>Least privilege</strong> — <code>codex</code> only loads <code>OPENAI_API_KEY</code>, not every secret you have</li>
<li><strong>Load once</strong> — each key is fetched at most once per session (the <code>${(P)key}</code> guard skips keys that are already set)</li>
</ul>
<p>Adding a new tool is one line in <code>_op_cmd_keys</code>. Adding a new key is one line in <code>_op_refs</code>.</p>
<p>If you have multiple 1Password accounts (personal + work), add <code>--account=my.1password.com</code> to the <code>op read</code> calls to avoid vault name collisions.</p>
<p>For even more granularity:</p>
<ol>
<li><strong><code>op run</code></strong> — inject secrets into a specific command rather than the global environment:</li>
</ol>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Only injects the key for this one command</span>
</span></span><span style="display:flex;"><span>op run --env-file<span style="color:#f92672">=</span>.env.1password -- python train.py
</span></span></code></pre></td></tr></table>
</div>
</div><ol start="2">
<li><strong><code>op inject</code></strong> — when you have a dozen keys, individual <code>op read</code> calls add up. With <code>op inject</code>, you define all your secrets in a single template and load them in one shot:</li>
</ol>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># ~/.env.op (template — safe to commit, contains no secrets)</span>
</span></span><span style="display:flex;"><span>export OPENAI_API_KEY<span style="color:#f92672">={{</span> op://Private/OpenAI API Key/credential <span style="color:#f92672">}}</span>
</span></span><span style="display:flex;"><span>export GEMINI_API_KEY<span style="color:#f92672">={{</span> op://Private/Gemini API Key/credential <span style="color:#f92672">}}</span>
</span></span><span style="display:flex;"><span>export ANTHROPIC_API_KEY<span style="color:#f92672">={{</span> op://Private/Anthropic API Key/credential <span style="color:#f92672">}}</span>
</span></span></code></pre></td></tr></table>
</div>
</div><div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># In .zshrc — one CLI call loads everything</span>
</span></span><span style="display:flex;"><span>eval <span style="color:#e6db74">&#34;</span><span style="color:#66d9ef">$(</span>op inject --in-file ~/.env.op<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>This is substantially faster than N individual <code>op read</code> calls — the CLI resolves all references in a single authentication round-trip.</p>
<ol start="3">
<li><strong>Scoped injection</strong> — skip the global environment entirely and inject a key for exactly one command&rsquo;s lifetime:</li>
</ol>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>OPENAI_API_KEY<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>op read <span style="color:#e6db74">&#34;op://Private/OpenAI API Key/credential&#34;</span> --no-newline<span style="color:#66d9ef">)</span> python train.py
</span></span></code></pre></td></tr></table>
</div>
</div><p>The key exists only in that command&rsquo;s process environment. Nothing touches your shell, nothing lingers after the process exits. This is the most paranoid option, and it&rsquo;s great for CI scripts or one-off runs.</p>
<h3 id="what-about-macos-keychain">What About macOS Keychain?</h3>
<p>macOS Keychain (<code>security find-generic-password</code>) works too and has zero startup overhead since it&rsquo;s always unlocked when you&rsquo;re logged in. I use it for some tokens:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export GITLAB_TOKEN<span style="color:#f92672">=</span><span style="color:#66d9ef">$(</span>security find-generic-password -a <span style="color:#e6db74">${</span>USER<span style="color:#e6db74">}</span> -s gitlab_token -w<span style="color:#66d9ef">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>The advantage of 1Password over Keychain: cross-device sync, team sharing, audit logs, and a UI that doesn&rsquo;t make you question your life choices. Use whichever fits your workflow. The point is to stop storing secrets in plain text.</p>
<h2 id="the-agentic-boom-made-this-worse">The Agentic Boom Made This Worse</h2>
<p>A year ago, most developers had maybe one or two API keys. Now? I know people with <strong>six or more</strong> AI service keys in their shell config. Coding agents need them. MCP servers need them. Every new tool in the ecosystem asks you to &ldquo;just export your API key&rdquo; and the docs always show the hardcoded version because it&rsquo;s simpler to explain.</p>
<p>MCP servers are the newest vector here. Tools like Claude Code, Cursor, and Windsurf use configuration files (<code>claude_desktop_config.json</code>, <code>mcp.json</code>) that store API keys for tool servers. The LLM itself never sees the secret values — the MCP server process does — but only if you inject them properly. Hardcoding keys in MCP configs is the same mistake as hardcoding them in <code>.zshrc</code>, just in a newer file. The <code>op</code> CLI works here too: use <code>op run</code> or environment variable references in your MCP server configs instead of raw keys.</p>
<p>This is a tooling culture problem. The default getting-started experience for almost every AI API is:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>export MAGIC_AI_KEY<span style="color:#f92672">=</span>your-key-here  <span style="color:#75715e"># don&#39;t do this</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>We should normalize showing the secure version in documentation. Until that happens, take five minutes and move your keys to a vault. Your future self (and your billing page) will thank you.</p>
<h2 id="tldr">TL;DR</h2>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span><span style="color:#75715e"># Before: plain text keys in .zshrc</span>
</span></span><span style="display:flex;"><span>export OPENAI_API_KEY<span style="color:#f92672">=</span>sk-proj-...
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># After: lazy-loaded from 1Password, per-command per-key</span>
</span></span><span style="display:flex;"><span>typeset -A _op_refs<span style="color:#f92672">=(</span>
</span></span><span style="display:flex;"><span>  OPENAI_API_KEY  <span style="color:#e6db74">&#34;op://Private/OpenAI API Key/credential&#34;</span>
</span></span><span style="display:flex;"><span>  GEMINI_API_KEY  <span style="color:#e6db74">&#34;op://Private/Gemini API Key/credential&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>typeset -A _op_cmd_keys<span style="color:#f92672">=(</span>
</span></span><span style="display:flex;"><span>  codex  <span style="color:#e6db74">&#34;OPENAI_API_KEY&#34;</span>
</span></span><span style="display:flex;"><span>  aider  <span style="color:#e6db74">&#34;OPENAI_API_KEY GEMINI_API_KEY&#34;</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">)</span>
</span></span><span style="display:flex;"><span>_maybe_load_op_secrets<span style="color:#f92672">()</span> <span style="color:#f92672">{</span>
</span></span><span style="display:flex;"><span>  local cmd<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>1%% *<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>; cmd<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>cmd##*/<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  local keys<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>_op_cmd_keys[$cmd]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">[[</span> -z <span style="color:#e6db74">&#34;</span>$keys<span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">return</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">for</span> key in <span style="color:#e6db74">${</span>=keys<span style="color:#e6db74">}</span>; <span style="color:#66d9ef">do</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">[[</span> -n <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>(P)key<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> <span style="color:#f92672">]]</span> <span style="color:#f92672">&amp;&amp;</span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span>    export <span style="color:#e6db74">&#34;</span>$key<span style="color:#e6db74">=</span><span style="color:#66d9ef">$(</span>op read <span style="color:#e6db74">&#34;</span><span style="color:#e6db74">${</span>_op_refs[$key]<span style="color:#e6db74">}</span><span style="color:#e6db74">&#34;</span> --no-newline 2&gt;/dev/null<span style="color:#66d9ef">)</span><span style="color:#e6db74">&#34;</span>
</span></span><span style="display:flex;"><span>  <span style="color:#66d9ef">done</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">}</span>
</span></span><span style="display:flex;"><span>preexec_functions<span style="color:#f92672">+=(</span>_maybe_load_op_secrets<span style="color:#f92672">)</span>
</span></span></code></pre></td></tr></table>
</div>
</div><p>Install <code>op</code>, store your keys, replace the exports, rotate the old keys. Five minutes. Zero excuses.</p>
<h2 id="further-reading">Further Reading</h2>
<ul>
<li><a href="https://1password.com/blog/securing-mcp-servers-with-1password-stop-credential-exposure-in-your-agent">Securing MCP Servers with 1Password</a> — 1Password&rsquo;s take on stopping credential exposure in agent configurations</li>
<li><a href="https://williamcallahan.com/blog/secure-environment-variables-1password-doppler-llms-mcps-ai-tools">Secure Environment Variables for LLMs, MCPs, and AI Tools</a> — William Callahan&rsquo;s walkthrough of using 1Password CLI and Doppler for AI tool secrets</li>
<li><a href="https://1password.com/blog/where-mcp-fits-and-where-it-doesnt">Where MCP Fits and Where It Doesn&rsquo;t</a> — 1Password on the security model of MCP and credential boundaries</li>
<li><a href="https://developer.1password.com/docs/cli/secret-references/">1Password CLI: Secret References</a> — official docs on the <code>op://</code> URI scheme</li>
<li><a href="https://developer.1password.com/docs/cli/reference/commands/inject/">1Password CLI: <code>op inject</code></a> — batch-load secrets from template files</li>
<li><a href="https://developer.1password.com/docs/cli/shell-plugins/">1Password Shell Plugins</a> — native integrations for CLI tools like <code>gh</code>, <code>aws</code>, and <code>stripe</code></li>
</ul>
]]></content:encoded></item><item><title>talk: How to Instrument Go Without Changing a Single Line of Code</title><link>https://kakkoyun.me/talks/how-to-instrument-go-without-changing-code/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/how-to-instrument-go-without-changing-code/</guid><description>Comparing eBPF, compile-time, runtime injection, and USDT approaches to zero-touch Go instrumentation — with benchmarks across 7 scenarios.</description><content:encoded><![CDATA[<p>Zero-touch observability for Go is finally becoming real. In this talk, we walk through the different strategies you can use to instrument Go applications without changing a single line of code, and what they cost you in terms of overhead, stability, and security.</p>
<p>We compare several concrete approaches and projects: eBPF-based auto-instrumentation using OpenTelemetry&rsquo;s Go auto-instrumentation agent and OBI (OpenTelemetry eBPF Instrumentation), compile-time manipulation using tools like Orchestrion and the OpenTelemetry Compile-Time Instrumentation SIG, runtime injection via Frida/ptrace, and USDT (User Statically-Defined Tracing) probes — both via libstapsdt and a custom Go runtime fork.</p>
<p>Beyond what exists today, we look at how ongoing work in the Go runtime and diagnostics ecosystem could unlock cleaner, safer hooks for future auto-instrumentation, including flight recording proposals and native USDT support in the Go toolchain.</p>
<p>Throughout the talk, we use benchmark results and small, realistic services to compare these strategies along three axes: performance overhead (latency, allocations, CPU impact), robustness and upgradeability across Go versions and container images, and operational friction (rollout complexity, debugging, and failure modes).</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/0TvrSebuDPk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://github.com/kakkoyun/fosdem-2026/blob/main/presentation.md">Slides - Markdown</a></li>
</ul>
<p><strong>Demo/Code</strong></p>
<ul>
<li><a href="https://github.com/kakkoyun/fosdem-2026">fosdem-2026</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://fosdem.org/2026/schedule/track/go/">FOSDEM 2026 — Go Devroom</a>
<ul>
<li><a href="https://www.youtube.com/watch?v=0TvrSebuDPk">Recording</a></li>
</ul>
</li>
</ul>
<p><strong>Related</strong></p>
<ul>
<li><a href="/posts/fosdem-2026-auto-instrumenting-go/">Auto-Instrumenting Go: From eBPF to USDT Probes</a> — full technical blog post expanding on this talk</li>
</ul>
]]></content:encoded></item><item><title>talk: How to Reliably Measure Software Performance</title><link>https://kakkoyun.me/talks/how-to-reliably-measure-software-performance/</link><pubDate>Fri, 13 Feb 2026 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/how-to-reliably-measure-software-performance/</guid><description>Why your benchmarks are probably lying to you — controlling hardware noise, statistical methods, and integrating performance into development workflows.</description><content:encoded><![CDATA[<p>Measuring software performance reliably is remarkably difficult. It&rsquo;s a specialized version of a more general problem: trying to find a signal in a world full of noise. A benchmark that reports a 5% improvement might just be measuring thermal throttling, noisy neighbors, or the phase of the moon.</p>
<p>In this talk, we walk through the full stack of reliable performance measurement — from controlling your benchmarking environment (bare metal instances, CPU affinity, disabling SMT and dynamic frequency scaling) to designing benchmarks that are both representative and repeatable. We cover the statistical methods needed to interpret results correctly (hypothesis testing, change point detection) and show how to integrate continuous benchmarking into development workflows so regressions are caught before they reach production.</p>
<p>Along the way, we share experiments demonstrating how environment control alone can reduce measurement variance by 100x, and practical tips for anyone who writes benchmarks — whether you&rsquo;re optimizing a hot loop or validating a system-wide change.</p>
<p>Co-presented with <a href="https://github.com/igoragoli">Augusto de Oliveira</a>.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/8211fNI_nc4" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://github.com/igoragoli/fosdem-2026-software-performance/blob/main/presentation.md">Slides - Markdown</a></li>
</ul>
<p><strong>Demo/Code</strong></p>
<ul>
<li><a href="https://github.com/igoragoli/fosdem-2026-software-performance">fosdem-2026-software-performance</a> — includes Jupyter notebooks in <code>experiments/</code> with benchmark design and results interpretation visualizations</li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://fosdem.org/2026/schedule/track/software-performance/">FOSDEM 2026 — Software Performance Devroom</a>
<ul>
<li><a href="https://www.youtube.com/watch?v=8211fNI_nc4">Recording</a></li>
</ul>
</li>
</ul>
<p><strong>Related</strong></p>
<ul>
<li><a href="/posts/fosdem-2026-measuring-software-performance/">Measuring Software Performance: Why Your Benchmarks Are Probably Lying</a> — full technical blog post expanding on this talk</li>
</ul>
]]></content:encoded></item><item><title>talk: Unleashing the Go Toolchain</title><link>https://kakkoyun.me/talks/unleashing-the-go-toolchain/</link><pubDate>Fri, 15 Aug 2025 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/unleashing-the-go-toolchain/</guid><description>Discover how the -toolexec flag unlocks programmable build pipelines in Go, enabling custom analysis, code generation, and instrumentation at compile time for organization-wide practices.</description><content:encoded><![CDATA[<p>The -toolexec flag hides a super-power in the Go toolchain: it lets you turn every go build into a programmable pipeline. In this session we’ll reveal how a simple wrapper command can inject custom analysis, code generation, and instrumentation—without changing a line of application code.</p>
<p>You’ll see how platform and tooling teams use -toolexec to weave organisation-wide practices directly into the build, from enforcing error-handling standards to automatically adding observability hooks. We’ll map the journey from a “hello-world” wrapper to full Aspect-Oriented compile-time transformations, and discuss the trade-offs that come with this new power.</p>
<p>Along the way we’ll spotlight real projects—such as Datadog’s Orchestrion—and community efforts in compile-time instrumentation, showing what’s already possible and where the ecosystem is heading. Finally, we’ll share practical tips for keeping builds fast and reliable when you venture beyond the default toolchain path.</p>
<p>If you’re curious about bending the Go compiler to your will—and doing so responsibly—this talk will equip you with the concepts, examples, and inspiration to start experimenting the moment you’re back at your editor.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/8Rw-fVEjihw" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://github.com/kakkoyun/public-content/blob/main/media/export/GopherCon%20UK%202025%20-%20Unleashing%20the%20Go%20Toolchain.pdf">Unleashing the Go Toolchain</a>
<ul>
<li><a href="https://github.com/kakkoyun/public-content/blob/main/presentations/2025/GopherCon%20UK%202025%20-%20Unleashing%20the%20Go%20Toolchain.md">Slides - Markdown</a></li>
</ul>
</li>
</ul>
<p><strong>Demo/Code</strong></p>
<ul>
<li><a href="https://github.com/kakkoyun/gopherconuk25-demo">gopherconuk25-demo</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://www.gophercon.co.uk/">GopherCon UK 2025</a>
<ul>
<li><a href="https://www.youtube.com/watch?v=8Rw-fVEjihw">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>Vibe Coding with Cursor: My R&amp;D Week Adventure 🚀</title><link>https://kakkoyun.me/posts/2024-03-21-vibe-coding-with-cursor/</link><pubDate>Wed, 12 Mar 2025 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/2024-03-21-vibe-coding-with-cursor/</guid><description>Discovering the joy of AI-powered development and note-taking with Cursor during R&amp;amp;D week</description><content:encoded><![CDATA[<blockquote>
<p>TL;DR: Spent a week building cool stuff with <a href="https://cursor.com">Cursor</a>, an AI-powered IDE. Found it surprisingly effective for both coding and managing my <a href="https://www.buildingasecondbrain.com/">second brain</a>. When your requirements are clear, it&rsquo;s almost magical! ✨ <span class="sidenote-ref" id="ref-sn-1" role="doc-noteref">
  <sup><a href="#sn-1" aria-describedby="sn-1">1</a></sup>
</span>
<aside class="sidenote sidenote--right" id="sn-1" role="note" aria-label="Sidenote 1 (right margin)">
  <span class="sidenote-label">1</span>
  <span class="sidenote-content">&ldquo;Magic&rdquo; here = fast iteration because the AI had unambiguous intent + cohesive context windows.</span>
  <a class="sidenote-back" href="#ref-sn-1" aria-label="Back to reference">↩</a>
</aside>
</p>
</blockquote>
<h2 id="the-setup-rd-week-vibes">The Setup: R&amp;D Week Vibes</h2>
<p>You know that feeling when R&amp;D week rolls around, and you&rsquo;re caught between &ldquo;I should learn something useful&rdquo; and &ldquo;I want to have fun&rdquo;? Well, this time I decided to combine both by diving deep into <a href="https://cursor.com">Cursor</a>, an AI-powered code editor that&rsquo;s been making waves in the developer community.</p>
<p>The mission was simple: Use Cursor for    <strong>everything</strong> - from managing my notes to building small task-specific projects. And by everything, I mean <em>everything</em>. <span class="sidenote-ref" id="ref-sn-2" role="doc-noteref">
  <sup><a href="#sn-2" aria-describedby="sn-2">why</a></sup>
</span>
<aside class="sidenote sidenote--left" id="sn-2" role="note" aria-label="Sidenote why (left margin)">
  <span class="sidenote-label">why</span>
  <span class="sidenote-content">Deliberate constraint: forcing one environment exposes friction you normally gloss over.</span>
  <a class="sidenote-back" href="#ref-sn-2" aria-label="Back to reference">↩</a>
</aside>
</p>
<h2 id="what-makes-cursor-different">What Makes Cursor Different?</h2>
<p>Unlike traditional IDEs that just help you write code, Cursor feels more like having a pair programmer who actually gets your <span class="tooltip" data-placement="top">
  <span class="tooltip-term" tabindex="0" aria-describedby="tt-1">context</span>
  <span role="tooltip" id="tt-1" class="tooltip-bubble">The active project knowledge the AI leverages (rules, open files, repo structure).</span>
</span>
. It&rsquo;s built on top of VSCode (so you get all the good stuff you&rsquo;re used to) but adds a layer of <span class="tooltip" data-placement="top">
  <span class="tooltip-term" tabindex="0" aria-describedby="tt-2">AI-powered</span>
  <span role="tooltip" id="tt-2" class="tooltip-bubble">Augmented by model-assisted completions, edits, refactors, and semantic search.</span>
</span>
 features that make development feel more&hellip; vibey? 😎</p>
<h3 id="the-good-parts">The Good Parts</h3>
<ol>
<li>
<p><strong>Context-Aware AI</strong>: The AI understands your project structure and can help with everything from code completion to refactoring. For example, when working on a React component, it automatically suggested appropriate hooks and state management patterns based on my component&rsquo;s purpose. When your requirements are clear, it&rsquo;s almost magical how it can <span class="tooltip" data-placement="top">
  <span class="tooltip-term" tabindex="0" aria-describedby="tt-3">scaffold</span>
  <span role="tooltip" id="tt-3" class="tooltip-bubble">Generate initial file / function structures &amp; boilerplate quickly.</span>
</span>
 projects and implement patterns! <span class="sidenote-ref" id="ref-sn-3" role="doc-noteref">
  <sup><a href="#sn-3" aria-describedby="sn-3">3</a></sup>
</span>
<aside class="sidenote sidenote--right" id="sn-3" role="note" aria-label="Sidenote 3 (right margin)">
  <span class="sidenote-label">3</span>
  <span class="sidenote-content">High leverage = spend 5 min articulating intent; save 30+ min of boilerplate churn.</span>
  <a class="sidenote-back" href="#ref-sn-3" aria-label="Back to reference">↩</a>
</aside>
</p>
</li>
<li>
<p><strong><a href="https://docs.cursor.com/context/rules-for-ai">Rules Feature</a></strong>: This is where things get interesting. You can create custom rules and context for different types of work, both at the project and global level. Think project-specific coding standards, documentation patterns, and even architecture guidelines. <span class="tooltip" data-placement="right">
  <span class="tooltip-term" tabindex="0" aria-describedby="tt-4">Rules</span>
  <span role="tooltip" id="tt-4" class="tooltip-bubble">Small YAML/Markdown-like spec files in <code>.cursor/rules</code> shaping AI behavior &amp; attached context.</span>
</span>
</p>
</li>
<li>
<p><strong><a href="https://docs.cursor.com/beta/notepads">Notepads</a></strong>: Quick thoughts? Code snippets? The notepad feature is like having a smart scratchpad that understands code and can share context between different parts of your development workflow. <span class="sidenote-ref" id="ref-sn-4" role="doc-noteref">
  <sup><a href="#sn-4" aria-describedby="sn-4">4</a></sup>
</span>
<aside class="sidenote sidenote--left" id="sn-4" role="note" aria-label="Sidenote 4 (left margin)">
  <span class="sidenote-label">4</span>
  <span class="sidenote-content">They become living design docs when you link them with @ mentions.</span>
  <a class="sidenote-back" href="#ref-sn-4" aria-label="Back to reference">↩</a>
</aside>
</p>
</li>
</ol>
<h2 id="second-brain-management-a-pleasant-surprise">Second Brain Management: A Pleasant Surprise</h2>
<p>One of my unexpected discoveries was how well Cursor handles note-taking and <a href="https://www.buildingasecondbrain.com/">second brain</a> management. Here&rsquo;s what made it click for me:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-markdown" data-lang="markdown"><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Markdown support is top-notch
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> AI understands context across files
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Easy to maintain structure with rules
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Quick navigation between related notes
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> File attachments for enhanced documentation
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">-</span> Dynamic references using @ mentions
</span></span></code></pre></td></tr></table>
</div>
</div><h3 id="the-rules-feature-a-game-changer">The Rules Feature: A Game Changer</h3>
<p>The rules feature deserves its own spotlight. Cursor offers two powerful ways to customize AI behavior (note that the older <code>.cursorrules</code> file is being deprecated in favor of this new system):</p>
<ol>
<li>
<p><strong>Project Rules</strong> (<code>.cursor/rules</code> directory):</p>
<ul>
<li>Semantic descriptions for specific use cases</li>
<li>File pattern matching with glob patterns</li>
<li>Automatic attachment when matching files are referenced</li>
<li>Chain multiple rules using @file references</li>
<li>Version controlled with your project</li>
<li>Create new rules via command palette (<code>Cmd + Shift + P</code> &gt; <code>New Cursor Rule</code>)</li>
</ul>
</li>
<li>
<p><strong>Global Rules</strong> (Cursor Settings):</p>
<ul>
<li>Applied across all projects</li>
<li>Perfect for consistent preferences</li>
<li>Control output language and response style</li>
<li>Set universal development guidelines <span class="tooltip" data-placement="bottom">
  <span class="tooltip-term" tabindex="0" aria-describedby="tt-5">universal</span>
  <span role="tooltip" id="tt-5" class="tooltip-bubble">Baseline defaults before project-specific specialization.</span>
</span>
</li>
</ul>
</li>
</ol>
<p>Pro tip: Use project rules whenever possible - they&rsquo;re more flexible, can be version controlled, and provide better granular control over different parts of your project. <span class="sidenote-ref" id="ref-sn-5" role="doc-noteref">
  <sup><a href="#sn-5" aria-describedby="sn-5">5</a></sup>
</span>
<aside class="sidenote sidenote--right" id="sn-5" role="note" aria-label="Sidenote 5 (right margin)">
  <span class="sidenote-label">5</span>
  <span class="sidenote-content">Global rules = broad tone; project rules = domain-specific precision.</span>
  <a class="sidenote-back" href="#ref-sn-5" aria-label="Back to reference">↩</a>
</aside>
</p>
<p>I&rsquo;ve set up different contexts for various types of work:</p>
<ul>
<li>Technical blog posts (with specific writing guidelines)</li>
<li>Project documentation (with architecture patterns)</li>
<li>Personal notes (with custom templates)</li>
<li>Code standards (with framework-specific rules)</li>
</ul>
<p>Each context comes with its own set of rules and AI behavior. It&rsquo;s like having multiple specialized assistants at your disposal.</p>
<h3 id="notepads-beyond-simple-notes">Notepads: Beyond Simple Notes</h3>
<p>The Notepads feature (currently in beta) has been a revelation. Think of them as enhanced reference documents that go beyond regular <code>.cursorrules</code>. I use them for:</p>
<ol>
<li>
<p><strong>Dynamic Boilerplate Generation</strong>:</p>
<ul>
<li>Templates for common code patterns</li>
<li>Project-specific scaffolding rules</li>
<li>Consistent code structure templates</li>
</ul>
</li>
<li>
<p><strong>Architecture Documentation</strong>:</p>
<ul>
<li>Frontend specifications</li>
<li>Backend design patterns</li>
<li>Data model documentation</li>
</ul>
</li>
<li>
<p><strong>Development Guidelines</strong>:</p>
<ul>
<li>Team conventions</li>
<li>Best practices</li>
<li>Project-specific rules <span class="tooltip" data-placement="left">
  <span class="tooltip-term" tabindex="0" aria-describedby="tt-6">scaffolding</span>
  <span role="tooltip" id="tt-6" class="tooltip-bubble">Automated generation of repeatable structure so humans focus on edge cases &amp; design.</span>
</span>
</li>
</ul>
</li>
</ol>
<p>The ability to share context between composers and chat interactions makes them incredibly powerful. Plus, you can attach files and use @ mentions to create a web of connected knowledge. <span class="sidenote-ref" id="ref-sn-6" role="doc-noteref">
  <sup><a href="#sn-6" aria-describedby="sn-6">6</a></sup>
</span>
<aside class="sidenote sidenote--left" id="sn-6" role="note" aria-label="Sidenote 6 (left margin)">
  <span class="sidenote-label">6</span>
  <span class="sidenote-content">This starts resembling a lightweight knowledge graph.</span>
  <a class="sidenote-back" href="#ref-sn-6" aria-label="Back to reference">↩</a>
</aside>
</p>
<h2 id="small-projects-big-impact">Small Projects, Big Impact</h2>
<p>During the week, I worked on several small, task-specific projects. The workflow typically went like this:</p>
<ol>
<li>Create a new project with clear requirements</li>
<li>Set up project-specific rules and templates</li>
<li>Let the AI handle boilerplate and routine coding</li>
<li>Focus on architecture and edge cases</li>
</ol>
<p>The AI handled a lot of the repetitive work, letting me focus on the creative aspects of each project. The clearer my requirements were, the more magical the results became. ✨ <span class="sidenote-ref" id="ref-sn-7" role="doc-noteref">
  <sup><a href="#sn-7" aria-describedby="sn-7">7</a></sup>
</span>
<aside class="sidenote sidenote--right" id="sn-7" role="note" aria-label="Sidenote 7 (right margin)">
  <span class="sidenote-label">7</span>
  <span class="sidenote-content">Clarity compounds: every precise artifact feeds back into higher-quality suggestions.</span>
  <a class="sidenote-back" href="#ref-sn-7" aria-label="Back to reference">↩</a>
</aside>
</p>
<h2 id="lessons-learned">Lessons Learned</h2>
<ol>
<li>
<p><strong>AI-Powered Doesn&rsquo;t Mean AI-Dependent</strong>: Cursor enhances your workflow without taking over.</p>
</li>
<li>
<p><strong>Rules Are Your Friend</strong>: Taking time to set up proper rules pays off immensely.</p>
</li>
<li>
<p><strong>Context is King</strong>: The more context you provide, the better the AI assistance becomes. <span class="tooltip" data-placement="top">
  <span class="tooltip-term" tabindex="0" aria-describedby="tt-7">context</span>
  <span role="tooltip" id="tt-7" class="tooltip-bubble">The total of rules, open buffers, selected text, and repo semantics the model sees.</span>
</span>
</p>
</li>
<li>
<p><strong>Second Brain Benefits</strong>: It&rsquo;s not just for coding; it&rsquo;s a genuine knowledge management tool.</p>
</li>
<li>
<p><strong>Clear Requirements = Magic</strong>: The more precise your task definition, the better the results. <span class="sidenote-ref" id="ref-sn-8" role="doc-noteref">
  <sup><a href="#sn-8" aria-describedby="sn-8">8</a></sup>
</span>
<aside class="sidenote sidenote--left" id="sn-8" role="note" aria-label="Sidenote 8 (left margin)">
  <span class="sidenote-label">8</span>
  <span class="sidenote-content">Most &ldquo;AI failed&rdquo; stories trace back to vague prompts, not model limits.</span>
  <a class="sidenote-back" href="#ref-sn-8" aria-label="Back to reference">↩</a>
</aside>
</p>
</li>
</ol>
<h2 id="whats-next">What&rsquo;s Next?</h2>
<p>I&rsquo;m planning to:</p>
<ul>
<li>Expand my rule sets for different types of work</li>
<li>Create more structured templates for common architectural patterns</li>
<li>Explore advanced AI features like multi-file refactoring</li>
<li>Share my rules and templates with the community</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>R&amp;D weeks are about trying new things and finding better ways to work. This experiment with Cursor turned out to be more than just playing with a new tool - it&rsquo;s changed how I think about IDE capabilities and knowledge management.</p>
<p>The combination of familiar VSCode features with AI assistance, especially the rules system, makes it a powerful tool for both coding and knowledge work. It&rsquo;s not perfect (what is?), but it&rsquo;s definitely earned its place in my daily toolkit.</p>
<blockquote>
<p>Remember: The best tools are the ones that enhance your natural workflow rather than forcing you to adapt to them. Cursor does this surprisingly well. 👍</p>
</blockquote>
]]></content:encoded></item><item><title>FOSDEM 2025: Blimey, What a Weekend!</title><link>https://kakkoyun.me/posts/fosdem-2025/</link><pubDate>Tue, 04 Feb 2025 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/fosdem-2025/</guid><description>FOSDEM 2025 highlights from the Go and Observability devrooms, featuring talks on Swiss Maps, GC-friendly code, and Prometheus 3. Plus the inevitable hallway track conversations and travel chaos.</description><content:encoded><![CDATA[<h3 id="another-year-another-fosdem">Another Year, Another FOSDEM</h3>
<p><strong>FOSDEM</strong>—the annual pilgrimage to <strong>Brussels</strong> for a weekend of open-source brilliance, hallway track magic, and the inevitable sleep deprivation. This year&rsquo;s <strong>Free and Open Source Software Developers&rsquo; European Meeting</strong> was, as always, a whirlwind of ideas, people, and tech so bleeding-edge it practically needed bandages.</p>
<p>But for me? It was all about <strong>seeing friends</strong>. Catching up, syncing, and squeezing in as many conversations as humanly possible. As we always say—the <strong>hallway track is the real conference</strong>. I&rsquo;m beyond grateful for the people I managed to see, and equally bummed about those I missed. But with a toddler waiting at home, even carving out this limited time was a logistical miracle.</p>
<h3 id="saturday-go-go-go-and-the-ebpf-black-hole">Saturday: Go, Go, Go&hellip; and the eBPF Black Hole</h3>
<p>Saturday kicked off with a deep dive into the <strong>Go DevRoom</strong>, before a (failed) mission to infiltrate the <strong>eBPF</strong> talks.</p>
<h4 id="go-goodness"><strong>Go Goodness</strong></h4>
<p>The <strong>Go DevRoom</strong> delivered as expected:</p>
<ul>
<li><strong>&quot;<a href="https://fosdem.org/2025/schedule/event/fosdem-2025-5353-the-state-of-go/">The State of Go</a>&quot;</strong> – Maartje Eyskens gave a solid rundown on where Go is headed.</li>
<li><strong>&quot;<a href="https://fosdem.org/2025/schedule/event/fosdem-2025-6049-swiss-maps-in-go/">Swiss Maps in Go</a>&quot;</strong> – Bryan Boreham took us through these lightning-fast maps.
<img src="/uploads/fosdem25_swissmaps.jpeg"
    alt="Swiss Maps in Go talk"
    loading="lazy"
    decoding="async"></li>
<li><strong>&quot;<a href="https://fosdem.org/2025/schedule/event/fosdem-2025-5343-go-ing-easy-on-memory-writing-gc-friendly-code/">Go-ing Easy on Memory: Writing GC-Friendly Code</a>&quot;</strong> – Sümer Cip’s talk was a timely reminder that, yes, your garbage collection problems are (probably) your fault.</li>
</ul>
<h4 id="ebpf-fail"><strong>eBPF Fail</strong></h4>
<p>The <strong>eBPF DevRoom</strong>? Packed. Absolutely impenetrable. As someone put it on Twitter:</p>
<blockquote>
<p>&ldquo;Nobody leaves #eBPF room at #FOSDEM, so nobody gets in. 🥲&rdquo;</p>
</blockquote>
<p>Next year, I&rsquo;m bringing a tent and camping outside the door.</p>
<h3 id="sunday-monitoring-metrics-and-maybe-too-many-frites">Sunday: Monitoring, Metrics, and Maybe Too Many Frites</h3>
<p>Sunday was all about observability, performance, and squeezing every bit of insight from running systems.</p>
<h4 id="observability-overload"><strong>Observability Overload</strong></h4>
<p>The <strong>Monitoring and Observability DevRoom</strong> had a strong lineup:</p>
<ul>
<li>Richard &ldquo;RichiH&rdquo; Hartmann <a href="https://fosdem.org/2025/schedule/event/fosdem-2025-6715-monitoring-and-observability-devroom-opening/">set the stage</a>.</li>
<li><strong>&quot;<a href="https://fosdem.org/2025/schedule/event/fosdem-2025-5502-the-performance-impact-of-auto-instrumentation/">The Performance Impact of Auto-Instrumentation</a>&quot;</strong> – James Belchamber gave a fantastic talk on the hidden costs of auto-instrumentation.</li>
<li><strong>&quot;<a href="https://fosdem.org/2025/schedule/event/fosdem-2025-6571-prometheus-version-3/">Prometheus Version 3</a>&quot;</strong> – Jan Fajerski and Bryan Boreham gave us the lowdown on what’s next for Prometheus.
<img src="/uploads/fosdem25_prometheus.jpeg"
    alt="Prometheus 3 talk"
    loading="lazy"
    decoding="async"></li>
</ul>
<h3 id="community-vibes">Community Vibes</h3>
<p>Like I said—<strong>FOSDEM</strong> is really about the people. The talks are great, but the real magic happens in the hallway track. Some of the best conversations weren’t planned; they just happened over coffee, between sessions, or during a frantic sprint between buildings.</p>
<p>I’m incredibly happy for the folks I got to see, and at the same time, I wish I had more time to catch up with everyone I missed. But life is about balance, and with a little one waiting at home, I had to make every moment count.</p>
<p>Oh, and the <strong>frites</strong>? Still undefeated.</p>
<h3 id="bonus-trains-chaos-and-a-race-against-time">Bonus: Trains, Chaos, and a Race Against Time</h3>
<p>Because no trip is complete without <strong>public transport drama</strong>, my journey back home came with an extra dose of stress. Trains? Cancelled. Schedule? A mess. Plane? Hanging by a thread.
<img src="/uploads/fosdem25_train.jpeg"
    alt="Train chaos while trying to catch my flight"
    loading="lazy"
    decoding="async"></p>
<p>Somehow, I made it. But FOSDEM weekend wouldn’t be complete without at least one unexpected adventure.</p>
<h3 id="final-thoughts">Final Thoughts</h3>
<p><strong>FOSDEM 2025</strong> delivered. Again. Already looking forward to next year. If you&rsquo;re into open source and haven’t experienced <strong>FOSDEM</strong>, sort it out.</p>
]]></content:encoded></item><item><title>When Hustle Culture and Personal Values Collide: Lessons from My AI/ML Startup Journey</title><link>https://kakkoyun.me/posts/hustle-culture-startup-lessons/</link><pubDate>Wed, 16 Oct 2024 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/hustle-culture-startup-lessons/</guid><description>Reflections on navigating misaligned priorities, overwork, and personal growth during a brief stint at a fast-paced startup.</description><content:encoded><![CDATA[<p>Startups can be exciting arenas of innovation, filled with ambitious goals, rapid development cycles, and the allure of shaping the future.
But when the pace becomes unsustainable, and personal values clash with company culture, the dream can quickly lose its luster.
My recent experience at a machine learning inference startup taught me invaluable lessons about overwork, alignment, and the balance between idealism and pragmatism.</p>
<h2 id="why-i-decided-to-leave">Why I Decided to Leave</h2>
<p>The decision to leave wasn’t easy, but it became necessary when I realized that the environment was not compatible with my personal and professional priorities.</p>
<ol>
<li>
<p><strong>Overwork as a Default</strong>
The company embraced a hustle culture where working over 10 hours a day and being on-call 24/7 was normalized.
This wasn’t limited to crunch times—it was the baseline expectation.
For someone with a newborn at home, this level of overwork was unsustainable and detrimental to my family life.</p>
</li>
<li>
<p><strong>Lack of Empathy and Transparency</strong>
Despite knowing about my life situation, the company struggled to adjust its expectations.
With no parents on the team, there was little understanding of what it meant to balance work and family.
Additionally, expectations around work hours and deliverables weren’t clearly communicated during onboarding.</p>
</li>
<li>
<p><strong>Misaligned Values</strong>
I joined with the goal of building resilient, scalable, high-performance systems while contributing to open-source projects—a passion of mine.
However, the company prioritized rapid feature delivery and short-term metrics over reliability, sustainability, or open-source contributions.
This fundamental misalignment created constant friction.</p>
</li>
</ol>
<h2 id="my-mistakes">My Mistakes</h2>
<p>While the cultural mismatch played a significant role, I also made mistakes that compounded the challenges:</p>
<ol>
<li>
<p><strong>Over-Optimizing Instead of Delivering</strong>
I leaned into finding ideal solutions rather than delivering quick, practical implementations. In a fast-paced startup, speed often outweighs perfection.</p>
</li>
<li>
<p><strong>Focusing Too Much on Learning</strong>
My desire to deeply understand and control every detail of the platform slowed me down.
While this mindset works well in some roles, it was counterproductive in a high-pressure, delivery-focused environment.</p>
</li>
<li>
<p><strong>Prioritizing Reliability Over Features</strong>
I instinctively gravitated toward improving system reliability and long-term sustainability, even when it was clear the company valued rapid feature delivery instead.
This misalignment of priorities made my efforts less impactful in their eyes.</p>
</li>
<li>
<p><strong>Spending Time on Open Source</strong>
I worked on improving and contributing to open-source tools, which I saw as valuable.
However, the company didn’t share this enthusiasm, and my efforts were viewed as misaligned with their goals.</p>
</li>
</ol>
<h2 id="lessons-learned">Lessons Learned</h2>
<p>Reflecting on this experience, I’ve taken away several key lessons:</p>
<ol>
<li>
<p><strong>Cultural Fit is Critical</strong>
No matter how exciting the technology or mission, if the company’s culture doesn’t align with your values, frustrations will inevitably arise.
Startups that glorify overwork are not sustainable for someone who values balance.</p>
</li>
<li>
<p><strong>Clarify Expectations Early</strong>
Misaligned expectations around priorities and success metrics can derail even the most skilled engineers.
Asking detailed questions during interviews and onboarding is essential to ensure alignment.</p>
</li>
<li>
<p><strong>Balance Idealism with Pragmatism</strong>
Striking a balance between delivering quick wins and building sustainable systems is key, especially in startups.
Knowing when to prioritize speed over perfection is a crucial skill.</p>
</li>
<li>
<p><strong>Stay True to Your Priorities</strong>
For me, being present for my family and maintaining a balanced life outweighs any professional ambition.
Leaving the role wasn’t easy, but it was the right decision for my well-being and values.</p>
</li>
</ol>
<h2 id="final-thoughts">Final Thoughts</h2>
<p>This experience was a humbling reminder of the importance of alignment—between personal values, company culture, and role expectations.
While I’ve always thrived at the intersection of systems engineering and challenging problems, this chapter underscored the need for environments that respect the individual, not just the output.</p>
<p>Startups can be transformative experiences for those who thrive on rapid growth and ambiguity. But for those who prioritize balance and long-term thinking, it’s critical to choose an organization that values these traits. For me, this experience reaffirmed the importance of staying true to my values, even when the professional stakes are high.</p>
]]></content:encoded></item><item><title>I left Polar Signals or A new chapter in my professional journey</title><link>https://kakkoyun.me/posts/i-left-polar-signals/</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/i-left-polar-signals/</guid><description>Reflections on leaving Polar Signals as the second employee and joining Datadog to continue working on observability, profiling, and performance engineering in a new environment.</description><content:encoded><![CDATA[<p>A new chapter in my professional journey</p>
<p>As the flowers bloom and the world awakens to the vibrant colors of spring, a season of renewal and growth, I find myself embarking on a significant transition in my professional journey. (Too cheesy? I know, but bear with me.)</p>
<p>This year, I find myself absent from the vibrant buzz of KubeCon, a place of learning and connection that I hold dear. Instead, I&rsquo;m on a different kind of duty — one that involves embracing new roles and responsibilities in life.</p>
<p>In the spirit of transitions, I have some personal news to share. Last week was my final week at <a href="https://www.polarsignals.com/">@PolarSignals</a>, marking the end of a chapter that has been incredibly fulfilling and challenging in equal measure. The decision to move on wasn&rsquo;t made lightly. It stemmed from much introspection about my personal and professional growth, as well as the realization that alignment between values and actions within an organization plays a pivotal role in one&rsquo;s journey.</p>
<h3 id="the-challenge-of-letting-go">The Challenge of Letting Go</h3>
<p>Leaving <a href="https://www.polarsignals.com/">@PolarSignals</a> was challenging for several reasons. As the second full-time employee, I&rsquo;ve had the unique opportunity to be woven into the fabric of the company&rsquo;s culture, and, in turn, it has become a part of me. The bonds formed and lessons learned from working closely with the same team for years are invaluable, though I found myself yearning for an environment that would foster greater growth opportunities and align with my aspirations. Admittedly, I&rsquo;ve never been good at letting go, making this transition even more poignant.</p>
<h3 id="the-right-decision">The Right Decision</h3>
<p>However, I firmly believe that to continue growing, sometimes you must step out of your comfort zone and challenge the status quo. My passion for exploring the world of observability, performance, and profiling remains undiminished, and it&rsquo;s this curiosity that propels me forward into new adventures. Ultimately, I came to understand that professional growth requires not just hard work and passion, but also an ecosystem that nurtures and supports it — something I realized I had to seek elsewhere.</p>
<h3 id="gratitude-and-looking-forward">Gratitude and Looking Forward</h3>
<p>I extend my deepest gratitude to everyone at <a href="https://www.polarsignals.com/">@PolarSignals</a> who made it a remarkable place to work. The camaraderie, challenges, and triumphs we shared will always be cherished memories, even as I reflect on moments that tested my patience and resilience.</p>
<p>As I embark on this new journey, I&rsquo;m reminded of the ethos that permeates the open-source community — &ldquo;Same team, different companies.&rdquo; It&rsquo;s a reminder that while our paths may diverge, the bonds we&rsquo;ve built and the collective pursuit of innovation and excellence continue to unite us.</p>
<p>Here&rsquo;s to new beginnings, the unknown adventures that lie ahead, and the continuous pursuit of growth and learning.</p>
<p>Let&rsquo;s see what the future brings.</p>
]]></content:encoded></item><item><title>Profiling Python with eBPF: A New Frontier in Performance Analysis</title><link>https://kakkoyun.me/posts/profiling-python-with-ebpf/</link><pubDate>Mon, 12 Feb 2024 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/profiling-python-with-ebpf/</guid><description>Discover how eBPF and Parca are transforming Python profiling, enabling continuous, efficient, and non-intrusive performance analysis directly in production.</description><content:encoded><![CDATA[<h1 id="profiling-python-with-ebpf-a-new-frontier-in-performance-analysis">Profiling Python with eBPF: A New Frontier in Performance Analysis</h1>
<p>Profiling Python applications can be challenging, especially in scenarios involving high-performance requirements or complex workloads. Existing tools often require code instrumentation, making them impractical for certain use cases. Enter <a href="https://ebpf.io/">eBPF</a> (Extended Berkeley Packet Filter)—a revolutionary Linux technology—and the open-source project <a href="https://parca.dev">Parca</a>, which together are reshaping the landscape of Python profiling.</p>
<p>In this post, I’ll explore how eBPF enables continuous profiling, discuss challenges like stack unwinding in Python, and demonstrate the power of modern profiling tools.</p>
<p>You can also watch my <a href="https://youtu.be/nNbU26CoMWA?si=t3Mh1z6XfNwa5r7M">full talk here</a> or refer to the <a href="https://kakkoyun.me/notes/presentations/FOSDEM24+-+Profiling+Python+with+eBPF+-+A+New+Frontier+in+Performance+Analysis">slides from the presentation</a>.</p>
<hr>
<h2 id="why-do-we-need-profiling">Why Do We Need Profiling?</h2>
<p>Profiling helps optimize performance and troubleshoot issues, such as CPU spikes, memory leaks, or out-of-memory (OOM) events. For instance:</p>
<ul>
<li><strong>Performance optimization:</strong> Identifying bottlenecks in code.</li>
<li><strong>Incident resolution:</strong> Determining which function or component caused a memory spike or CPU overload.</li>
</ul>
<p>Traditional Python profiling tools, like <a href="https://docs.python.org/3/library/profile.html"><code>cProfile</code></a> or <a href="https://github.com/benfred/py-spy"><code>py-spy</code></a>, require application instrumentation, which isn&rsquo;t always feasible—especially in production environments where code access might be restricted. This is where eBPF shines, offering non-intrusive, external profiling.</p>
<hr>
<h2 id="existing-profiling-solutions-in-python">Existing Profiling Solutions in Python</h2>
<p>The Python ecosystem offers several profiling tools, each with unique strengths:</p>
<ul>
<li><a href="https://docs.python.org/3/library/profile.html"><code>cProfile</code></a>: A built-in module for deterministic profiling.</li>
<li><a href="https://github.com/joerick/pyinstrument"><code>pyinstrument</code></a>: A call stack profiler for Python.</li>
<li><a href="https://github.com/benfred/py-spy"><code>py-spy</code></a>: A sampling profiler for Python programs.</li>
<li><a href="https://github.com/sumerc/yappi"><code>yappi</code></a>: Yet Another Python Profiler, supports multithreaded programs.</li>
<li><a href="https://pyflame.readthedocs.io/en/latest/"><code>Pyflame</code></a>: A ptracing profiler for Python.</li>
<li><a href="https://github.com/plasma-umass/scalene"><code>Scalene</code></a>: A high-performance CPU and memory profiler.</li>
</ul>
<p>While these tools are valuable, many require code instrumentation or introduce significant overhead, making them less suitable for continuous profiling in production environments.</p>
<hr>
<h2 id="what-is-ebpf">What Is eBPF?</h2>
<p>Originally designed for network packet filtering, <a href="https://ebpf.io/">eBPF</a> has evolved into a versatile event-driven system. It enables safe execution of custom programs inside the Linux kernel, using:</p>
<ul>
<li><strong>Performance Monitoring Units (PMUs):</strong> Efficient hardware units built into CPUs that track performance events like CPU cycles, cache misses, and branch predictions.</li>
<li><strong><a href="https://perf.wiki.kernel.org/index.php/Main_Page">Perf subsystem</a>:</strong> A Linux facility for hooking into kernel and user-space events, such as CPU activity, memory allocation, or I/O.</li>
</ul>
<p>By leveraging eBPF with PMUs, profiling becomes faster and more efficient than traditional approaches.</p>
<hr>
<h2 id="continuous-profiling-with-parca">Continuous Profiling with Parca</h2>
<p><a href="https://parca.dev">Parca</a> is an open-source project enabling continuous profiling. Its eBPF agent hooks into <a href="https://perf.wiki.kernel.org/index.php/Tutorial">perf events</a>, collects stack traces, and aggregates data for visualization. The process involves:</p>
<ol>
<li><strong>Hooking into CPU events</strong> to monitor active functions.</li>
<li><strong>Stack unwinding</strong> to trace function calls.</li>
<li><strong>Data aggregation and visualization</strong> in a web-based UI.</li>
</ol>
<p>Unlike traditional profilers, Parca introduces minimal runtime overhead, making it ideal for production workloads.</p>
<hr>
<h2 id="stack-unwinding-a-key-challenge">Stack Unwinding: A Key Challenge</h2>
<h3 id="native-code">Native Code</h3>
<p>Profiling native code is straightforward: we unwind the stack by reading memory addresses from the CPU and resolving them into human-readable symbols using debug information (e.g., <a href="https://dwarfstd.org/">DWARF</a>).</p>
<h3 id="python-code">Python Code</h3>
<p>For Python, stack unwinding is complex due to its interpreter-based execution. Python maintains execution state in custom data structures, such as:</p>
<ul>
<li><strong>Interpreter state:</strong> Tracks threads and their execution context.</li>
<li><strong>Thread state:</strong> A linked list of threads running in the interpreter.</li>
<li><strong>Frame state:</strong> Represents the current execution frame.</li>
</ul>
<p>To unwind Python stacks, we must traverse these structures, extract relevant information, and map them to human-readable symbols.</p>
<hr>
<h2 id="how-parca-profiles-python">How Parca Profiles Python</h2>
<p>Here’s how Parca handles Python profiling:</p>
<ol>
<li>
<p><strong>Reverse Engineering the Python Runtime:</strong></p>
<ul>
<li>Analyze Python’s internal structures (e.g., thread and frame states).</li>
<li>Identify offsets and symbols using tools like <a href="https://www.gnu.org/software/gdb/">GDB</a> or DWARF debuggers.</li>
</ul>
</li>
<li>
<p><strong>Unwinding Python Stacks:</strong></p>
<ul>
<li>Traverse thread states to locate the active <a href="https://wiki.python.org/moin/GlobalInterpreterLock">Global Interpreter Lock (GIL)</a> holder.</li>
<li>Walk through execution frames to collect function call data.</li>
</ul>
</li>
<li>
<p><strong>Mapping Symbols:</strong></p>
<ul>
<li>Resolve function addresses to readable symbols.</li>
<li>Encode line numbers and function names for better traceability.</li>
</ul>
</li>
<li>
<p><strong>Efficient Data Handling:</strong></p>
<ul>
<li>Use eBPF maps for kernel-to-user space communication.</li>
<li>Optimize symbol resolution by caching frequently seen traces.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="python-313-a-game-changer-for-profiling">Python 3.13: A Game-Changer for Profiling</h2>
<p>The upcoming Python 3.13 release introduces a debug offset structure that simplifies stack unwinding. It provides precomputed offsets for key runtime fields, eliminating much of the manual reverse engineering required for earlier versions. This improvement marks a significant leap forward for tools like Parca.</p>
<hr>
<h2 id="visualizing-profiles-with-parca">Visualizing Profiles with Parca</h2>
<p>Parca’s UI provides a comprehensive view of application performance:</p>
<ul>
<li><strong>Flame graphs</strong>: Visualize stack traces over time, highlighting bottlenecks.</li>
<li><strong>Filtering and Metadata</strong>: Focus on specific languages (e.g., Python) or layers (e.g., C libraries).</li>
<li><strong>Continuous Insights</strong>: Compare profiles across deployments to monitor performance regressions.</li>
</ul>
<p>For example, a flame graph might reveal inefficient recursion in a Python function, enabling developers to pinpoint and optimize the problematic code.</p>
<hr>
<h2 id="supported-python-versions">Supported Python Versions</h2>
<p>Parca supports profiling for Python versions from 2.7 to 3.11, with ongoing work for 3.12 and full support anticipated for 3.13. The project’s modular design allows quick adaptation to new Python runtime changes.</p>
<hr>
<h2 id="conclusion">Conclusion</h2>
<p>Profiling Python applications with eBPF and Parca represents a new frontier in performance analysis. By leveraging eBPF and continuous profiling, we can gain invaluable insights into our applications, enabling effective performance optimization. I encourage you to explore Parca, provide feedback, and contribute to the project—it’s a collaborative effort that can benefit us all as we tackle the challenges of modern software development.</p>
<h3 id="get-started">Get Started</h3>
<p>Watch my <a href="https://youtu.be/nNbU26CoMWA?si=t3Mh1z6XfNwa5r7M">full talk</a> or check out the <a href="https://kakkoyun.me/notes/presentations/FOSDEM24+-+Profiling+Python+with+eBPF+-+A+New+Frontier+in+Performance+Analysis">presentation slides</a>. Explore Parca on <a href="https://github.com/parca-dev/parca">GitHub</a> and join the community. Your feedback helps improve the tooling and shape the future of observability.</p>
]]></content:encoded></item><item><title>Profiling Python and Ruby using eBPF</title><link>https://kakkoyun.me/posts/profiling-python-and-ruby-using-ebpf/</link><pubDate>Wed, 04 Oct 2023 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/profiling-python-and-ruby-using-ebpf/</guid><description>Dive into the internals of profiling interpreted Python and Ruby code using eBPF</description><content:encoded>&lt;p>&lt;a href="https://www.polarsignals.com/blog/posts/2023/10/04/profiling-python-and-ruby-with-ebpf/">https://www.polarsignals.com/blog/posts/2023/10/04/profiling-python-and-ruby-with-ebpf/&lt;/a>&lt;/p>
</content:encoded></item><item><title>Ice and Fire: How to read icicle and flame graphs</title><link>https://kakkoyun.me/posts/ice-and-fire/</link><pubDate>Tue, 28 Mar 2023 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/ice-and-fire/</guid><description>A comprehensive guide to reading and understanding icicle and flame graphs for performance profiling and analysis. Learn the differences, when to use each, and how to interpret them effectively.</description><content:encoded><![CDATA[<p>I am too lazy now a days to re-post the blog post with all its assets and animations here. So until I get to it, I have put a link to it here. Enjoy :)</p>
<p><a href="https://www.polarsignals.com/blog/posts/2023/03/28/how-to-read-icicle-and-flame-graphs">https://www.polarsignals.com/blog/posts/2023/03/28/how-to-read-icicle-and-flame-graphs</a></p>
]]></content:encoded></item><item><title>talk: Best Practices and Pitfalls of Instrumenting Your Cloud-Native Application</title><link>https://kakkoyun.me/talks/best-practices-and-pitfalls-of-instrumenting-your-cloud-native-application/</link><pubDate>Tue, 08 Nov 2022 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/best-practices-and-pitfalls-of-instrumenting-your-cloud-native-application/</guid><description>Patterns and best practices for instrumenting cloud-native services with Prometheus metrics, covering common pitfalls, correlation with other observability signals, and client_golang improvements.</description><content:encoded><![CDATA[<p>Observability is crucial for understanding how your application operates in real-time. Among various observability signals—such as logs, traces, and continuous profiling—metrics play a significant role. They provide sampled measurements throughout the system, essential for ensuring service quality, improving performance, scalability, debuggability, security, and enabling real-time, actionable alerting.</p>
<p>Building observable applications begins with proper instrumentation. While the Prometheus ecosystem offers tools that simplify this process, there are still numerous opportunities for mistakes or misuse.</p>
<p>In this talk, Jéssica Lins and Kemal Akkoyun present several useful patterns, best practices, and idiomatic methods for instrumenting critical services. They discuss common pitfalls, failure cases, and instrumentation strategies, sharing valuable insights and methods to avoid these mistakes. Additionally, they provide tips for writing simple, maintainable, and robust instrumentation facilities using real-life examples. The talk also demonstrates how to enrich metrics by correlating them with other observability signals and discusses how to best utilize recent changes in <code>client_golang</code>, the Go client library for Prometheus.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/B6Ds2myOIRc" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://docs.google.com/presentation/d/1uRyWxPGTTfn9_UnX4sWyUd5Lcf7MKg-qvi3hajFtLZI/edit?usp=sharing">Best Practices and Pitfalls of Instrumenting Your Cloud-Native Application</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://promcon.io/2022-munich/talks/best-practices-and-pitfalls-of-i/">PromCon EU 2022</a>
<ul>
<li><a href="https://www.youtube.com/watch?v=B6Ds2myOIRc">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>About</title><link>https://kakkoyun.me/about/</link><pubDate>Fri, 21 Oct 2022 21:45:25 +0100</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/about/</guid><description>&lt;p&gt;I&amp;rsquo;m a software infrastructure engineer obsessed with understanding how systems behave in production.
My work centers on observability, instrumentation, profiling, and performance engineering—helping engineers build and operate reliable, fast software.&lt;/p&gt;
&lt;h3 id="professional-journey"&gt;Professional Journey&lt;/h3&gt;
&lt;p&gt;I started tinkering with computers early, drawn to the idea of making machines do useful things. Over time, that curiosity evolved into a career focused on systems programming, low-level tooling, and observability.
I&amp;rsquo;ve worked across distributed systems, time-series databases, continuous profiling, and eBPF-based instrumentation.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I&rsquo;m a software infrastructure engineer obsessed with understanding how systems behave in production.
My work centers on observability, instrumentation, profiling, and performance engineering—helping engineers build and operate reliable, fast software.</p>
<h3 id="professional-journey">Professional Journey</h3>
<p>I started tinkering with computers early, drawn to the idea of making machines do useful things. Over time, that curiosity evolved into a career focused on systems programming, low-level tooling, and observability.
I&rsquo;ve worked across distributed systems, time-series databases, continuous profiling, and eBPF-based instrumentation.</p>
<p>Before Datadog, I helped build Parca (continuous profiling) and Thanos (Prometheus at scale). I&rsquo;ve been a maintainer and contributor across the CNCF ecosystem—Prometheus, Prometheus Operator, and various observability projects. I&rsquo;ve mentored through CNCF LFX, Google Summer of Code, CommunityBridge, and GoBridge, helping others find their way into open source.</p>
<p>I&rsquo;ve also had the privilege of angel investing in two promising companies, supporting innovation in the tech ecosystem.</p>
<h4 id="what-i-do-recently">What I Do recently</h4>
<p>I work at <strong>Datadog - APM</strong>, building <strong>Go for Go</strong>—instrumenting and optimizing performance in Go applications at scale. My focus is on <strong>instrumentation, profiling, performance monitoring, and observability tooling</strong> that helps engineers understand and optimize their systems.</p>
<p>In <strong>2024</strong>, I explored <strong>Python tooling, ML inference infrastructure, MLOps, and container runtimes</strong>, designing systems to optimize ML workflows. While this broadened my understanding of ML infrastructure, I realized that <strong>my passion remains in observability</strong>. Now, I’m doubling down on <strong>instrumentation, performance monitoring, and profiling</strong>, ensuring that engineers have the tools they need to build and operate high-performance systems.</p>
<p>Before that, I was <strong>the monitoring and profiling guy</strong>—building Linux observability tools for software and reliability engineers.
My work was deeply rooted in <strong>profiling, eBPF, performance engineering, time-series, and columnar databases</strong>.</p>
<h3 id="beyond-professional-life">Beyond Professional Life</h3>
<p>In my free time, I&rsquo;m a <strong>tinkerer</strong>—I open things up, inspect what&rsquo;s inside, and fix them. I&rsquo;m also a <strong>bookworm, a mechanical keyboard and LEGO builder, a single malt taster, and a coffee drinker in training</strong> (peer pressure).</p>
<p>I enjoy <strong>sci-fi and fantasy literature, board games, and programming fun side projects</strong> (from Raspberry Pi cluster experiments to Flipper Zero hardware hacking). I&rsquo;m passionate about <strong>traveling, cooking vegan and vegetarian dishes</strong>, and constantly <strong>challenging myself to learn new languages</strong>—one day, I hope to become a proper polyglot.</p>
<p><strong>Gravel cycling</strong> is my escape. I&rsquo;m dreaming of camping and bikepacking adventures—trips I want to take with my son when he&rsquo;s grown up enough to join me on two wheels.</p>
<p>As an <strong>angel investor</strong>, I&rsquo;ve had the privilege of investing in two promising companies, helping support innovation and growth in the tech ecosystem.</p>
<p>One of my long-term goals is <strong>early retirement</strong>, spending most of my time outdoors—maybe overlanding with a Land Rover, bikepacking through mountains, or camping under the stars. Who knows?</p>
<h4 id="home">Home</h4>
<p>I currently live in <strong>Berlin, Germany</strong>, with my beloved significant other, <strong>Rüya</strong>, and our one-year-old son, <strong>Atlas Robin</strong>. Our home is often filled with toys and creative projects. Though we recently lost our beloved dog, she lives on in spirit—and through the tattoo on my left forearm.</p>
<h3 id="more">More</h3>
<p><a href="/now/">What I&rsquo;m doing now</a> | <a href="/uses/">Tools I use</a> | <a href="/misc/">Everything else</a></p>
<hr>
<h2 id="cv">CV</h2>
<p>It&rsquo;s 202x, so just visit: <a href="https://linkedin.com/in/kakkoyun/">linkedin.com/in/kakkoyun</a></p>
<h2 id="where-to-find-me">Where to Find Me</h2>
<p>You can find me across various platforms under the handle <strong>kakkoyun</strong>, except on Twitter/X: <strong>kakkoyun_me</strong>.
If you wish to reach out and chit-chat, you may email me: <strong><em>kakkoyun<code>_at_</code>gmail<code>_dot_</code>com</em></strong></p>
<h2 id="pgp-public-key">PGP public key</h2>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>-----BEGIN PGP PUBLIC KEY BLOCK-----
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>mQINBFTtf5UBEADCJEjNSseUeDPawrK9wocC3gfer0f6HJpqTkUHTbCF6gZVmZNu
</span></span><span style="display:flex;"><span>5eaLLaLLs3C793EkqzWPX9jIfdYHFcs2x0aQeXESdk365IUKiNpqfhWYRcBOFbqt
</span></span><span style="display:flex;"><span>XZXHV8Sl6VBgxotVmjFqxOYPgsRBkuNs7PJYD4R16AiKI7gqQWiyatVRIRAPe9+N
</span></span><span style="display:flex;"><span>Eii4mW8R6bvmVTikSiEa/O/PA2hocOjXpAUyLCOtFeumiFp45ug7eDpiKyQE2keo
</span></span><span style="display:flex;"><span>jS70yvlVxkPr0EXSd5TFomm6HRkAywGVAK/yPu53EZWs6of/mCHnJ4wIu7GtHxUB
</span></span><span style="display:flex;"><span>RqUuhs4qLNh3DamhZnYQ/OHKMdWgD+f5VPkH/Kcrk5oEVjhvDT8ay6s/yvdA0u1g
</span></span><span style="display:flex;"><span>VeEqALPfbsuzOuUDPnfyXEByWD6xJ5EZTpDU5FrG6DOmrBsQn85MkN2nMh6BExMi
</span></span><span style="display:flex;"><span>0BI6OCS5PWFLbRIeQNDRZIbjkhnmqnVG0uRCt9fjy6dUqL0h41IJ3/sn0gfKbWiX
</span></span><span style="display:flex;"><span>koBEQ1//SNVsIoAsPkuNwk0hXae/SelcGH1akGKOkdbozMnv8xyFIRuwYR97dhwT
</span></span><span style="display:flex;"><span>3wREdk4No+S1k55GAW367W/P9dXb6FO+P7DpCxptN/xlGJwbXS0spVnZ/BI57/uk
</span></span><span style="display:flex;"><span>yu1wCFOKVdlmJO0INdQCOOe8SAvJUlUJ+Gf9lylUtDraSSiWnATWKWZ4SQARAQAB
</span></span><span style="display:flex;"><span>tCJLZW1hbCBBa2tveXVuIDxrYWtrb3l1bkBnbWFpbC5jb20+iQI9BBMBCgAnBQJU
</span></span><span style="display:flex;"><span>7X+VAhsDBQkJa0eABQsJCAcDBRUKCQgLBRYCAwEAAh4BAheAAAoJEEc6M3uqhnqO
</span></span><span style="display:flex;"><span>tEMP/1htXJILi2VXb1tfgfQIOpTtxLwk8davTiFGfXJTu5HFmL+fEeeHPT7iqKLf
</span></span><span style="display:flex;"><span>yvYhD167VQHKkyJq7KY58iHfqgmxjGYOFWnRcOBehG4K/uVMF9mDT7xdhNGh9Swk
</span></span><span style="display:flex;"><span>jAECPJqWOycnMIDiBmaXhmGW22qFnq+8DRAzQ4e6zDWH+elSMLK68npk6KMAgt36
</span></span><span style="display:flex;"><span>l6nlWjPvB35bvpm8F0DoseW1yBKIjWMAtnKoYOrotKEGHODxHNTxSPdCS8zSfGp0
</span></span><span style="display:flex;"><span>OW2xyBhHVP9+IHBgw64O6OJcGWP0Il86rKnFFGxnLRJYlJUJtbQo7oo6W+IhIdK1
</span></span><span style="display:flex;"><span>/HuX931D/jpMbZhsq4qndAUfSu7LU6ORijsHVt8JVheZiuwUAuVqDfagJZLDibYZ
</span></span><span style="display:flex;"><span>iofVliLRoTkDnneX9kgirwhugJMFkK/Bcvc1BnYccGyTM1wm0TmDtYyDnoPsUvK+
</span></span><span style="display:flex;"><span>RfMx0o7WwweXZ+aZbBxhP9uZJ1cE1huYn/cxFVhk5rSRJACVx1iKFPVEiwlsHCL8
</span></span><span style="display:flex;"><span>YIgcc2flzx9x/N+NX9Sfi+9etlAtJF8rh/kDUZt7tH/zAy7tDJmmXM9LSlpf7UxE
</span></span><span style="display:flex;"><span>QmYHPEcyoalBlInI1pmn21HElxqMTZ3Uj1To5mo1HYTs/goSxBfjyEwc/Po6DUTF
</span></span><span style="display:flex;"><span>NSm3g/URVAfevSINAl2XakAppN0nb4uJLfBSyQMVtpZAof7nuQINBFTtf5UBEADa
</span></span><span style="display:flex;"><span>4n8xbJNFUcyNaBCpKy46WSu4InGqWF8Ka8Do4HqctpcdcqedeOYfuTYIuJvS90iz
</span></span><span style="display:flex;"><span>Ac6qkMrmHksU/MFoNJZtaaYq3OPOlyNSEbF/T1ZA+E765/aLlujWGrZZtwKfOdeo
</span></span><span style="display:flex;"><span>5yzATHygfvM3Sv72h0Q4P4dBV/EtkKisEf1Hx6S1NEqiaSk4zLGfA02AY6DOekU/
</span></span><span style="display:flex;"><span>inFZI1YYfdQyQj4dvmn9Y4mbyEcLxn9qIl/cl55xhVQdTklvvpGbJ+4XVnZQ4sMo
</span></span><span style="display:flex;"><span>Sy45XSilH9EUY5QSDQ6yHYDRzUvoLoxpGIj0gZEZKS+NqVv2pSjv8o2qKnkawtpz
</span></span><span style="display:flex;"><span>foziutk5Csxh6APdhuFgrUeW/nW5tgYvL/9hnqA3mSTqvoozaf7BJ5jaeUddjdII
</span></span><span style="display:flex;"><span>qsK7BI9sfHfIdT0iWBmxHsVDbo/Xzecmhnrqs+OcAjBAD3j6W9puLRSBAFPKZ1MT
</span></span><span style="display:flex;"><span>znCtgVtkRrsJ3CT2gJovUFDVhvlKgQ6EgMJBrSuLPZeshhXWfyd7V40bdxoo7qwW
</span></span><span style="display:flex;"><span>HkyfmZL36W0umsefpTvsNIGA4uyt1BSQNTe+bzUebU8gNq6HyT5IRmhgJlXsvYvw
</span></span><span style="display:flex;"><span>GOX5Jo3TMW+OcoOWnc3IaJxZ5bjXdmg0Q34XVa5CPx/cXnkn0gRGYVWWF+9Ma22V
</span></span><span style="display:flex;"><span>APTvsI+cBatQhs2VVdzMFMzKvuL3Lr+FyXnqyoW6yQARAQABiQIlBBgBCgAPBQJU
</span></span><span style="display:flex;"><span>7X+VAhsMBQkJa0eAAAoJEEc6M3uqhnqOHQgP/17coahhXDRe4/JdFruPVl+4PgK1
</span></span><span style="display:flex;"><span>Tn63WMEsa0Djg4pGTk46apvGrmJo7ZAwhdCuoQWCz/frxgY/i1Y5Skqn6VDmVhMB
</span></span><span style="display:flex;"><span>y0hHgBCTe5pJqf/NMsZ6MgqYJX/aiIo4H91reW+7an8wX8V/UYPp5/h8hycQc9FS
</span></span><span style="display:flex;"><span>V3uwNSEyfLCzPH+a3+TpTNAtGuF3KapWmr9jrZ423hU4puysP27yaFvqANeJkFXo
</span></span><span style="display:flex;"><span>OT4CnVsshJTaJ/U7W0fqA/6yUtWp+sKL51ShMEv9ZJFj0a28ZSrRhtLQVQOjfR6X
</span></span><span style="display:flex;"><span>J4ntW4WJfISgz2s3KM8FuIC54F5f64f1Vts+6rZAwTGfhcZo+ePOC292/PPHMb+O
</span></span><span style="display:flex;"><span>jBqHIJG+WGRKpJsHwgrFHsO9x+j91dKDN93abcD4pT41WuP5MvQM31gqBYxjvdu4
</span></span><span style="display:flex;"><span>h6qF7w1m5eTM5R5aUUnI/i9OAyv51r8yPdxYHgvx5Adf7YpnQzErmeCZDxzLH8v1
</span></span><span style="display:flex;"><span>61IPl5ONAYsESJfjKrPLcwlB5o0xv6CKdaMUmWNF7zcLERoqRYPHb6RugNoWavMW
</span></span><span style="display:flex;"><span>i67uv22Ngf1ruC33nuRZhRcXJJwDaM1wU0d8XImgpAX7l9UM9PmaRFtdESee4QEh
</span></span><span style="display:flex;"><span>rcrXF3Yjs3E5cdJ8+zt3tzNjsBUH7u/qfhPkOaC7GqE0ImcM18d3x4l0+WbhPscN
</span></span><span style="display:flex;"><span>f0D/QordQaUmE/K/
</span></span><span style="display:flex;"><span>=zKIC
</span></span><span style="display:flex;"><span>-----END PGP PUBLIC KEY BLOCK-----
</span></span></code></pre></td></tr></table>
</div>
</div>]]></content:encoded></item><item><title>Blogroll</title><link>https://kakkoyun.me/blogroll/</link><pubDate>Fri, 21 Oct 2022 21:45:25 +0100</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/blogroll/</guid><description>&lt;p&gt;If you like some of my blog posts, I&amp;rsquo;d recommend reading some of the following blogs, which I read regularly.&lt;/p&gt;
&lt;p&gt;This list is organized alphabetically and includes engineers, researchers, and technologists whose work I find valuable.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="people--blogs"&gt;People &amp;amp; Blogs&lt;/h2&gt;
&lt;h3 id="bartlomiej-plotka"&gt;Bartlomiej Plotka&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://www.bwplotka.dev/"&gt;bwplotka.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/bwplotka"&gt;@bwplotka&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Observability, Prometheus, Thanos, Go&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="brendan-gregg"&gt;Brendan Gregg&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://www.brendangregg.com/"&gt;brendangregg.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/brendangregg"&gt;@brendangregg&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Performance engineering, eBPF, systems observability&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="bryan-cantrill"&gt;Bryan Cantrill&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/bcantrill"&gt;@bcantrill&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Systems programming, debugging, distributed systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="charity-majors"&gt;Charity Majors&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://charity.wtf/"&gt;charity.wtf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/mipsytipsy"&gt;@mipsytipsy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Observability, engineering culture, distributed systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="cindy-sridharan"&gt;Cindy Sridharan&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://copyconstruct.medium.com/"&gt;copyconstruct.medium.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/copyconstruct"&gt;@copyconstruct&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Distributed systems, observability, testing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="dave-cheney"&gt;Dave Cheney&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://dave.cheney.net/"&gt;dave.cheney.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/davecheney"&gt;@davecheney&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Go programming, performance, simplicity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="derek-sivers"&gt;Derek Sivers&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://sive.rs/"&gt;sive.rs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Life philosophy, entrepreneurship, writing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="frederic-branczyk"&gt;Frederic Branczyk&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://brancz.com/"&gt;brancz.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/fredbrancz"&gt;@fredbrancz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Observability, Prometheus, Kubernetes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="gergely-orosz"&gt;Gergely Orosz&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://blog.pragmaticengineer.com/"&gt;blog.pragmaticengineer.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/GergelyOrosz"&gt;@GergelyOrosz&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Engineering culture, career development, tech industry&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="jamie-tanna"&gt;Jamie Tanna&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://www.jvt.me/"&gt;jvt.me&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/JamieTanna"&gt;@JamieTanna&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: DevOps, automation, IndieWeb, Go&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="jan-fabian-meier"&gt;Jan Fabian Meier&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://jfm.carcosa.net/"&gt;jfm.carcosa.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Systems programming, performance, Zig&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="jesse-duffield"&gt;Jesse Duffield&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://jesseduffield.com/"&gt;jesseduffield.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/DuffieldJesse"&gt;@DuffieldJesse&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Developer tools, Go, productivity&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="julia-evans"&gt;Julia Evans&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://jvns.ca/"&gt;jvns.ca&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/b0rk"&gt;@b0rk&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Systems programming, learning, zines&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="liz-rice"&gt;Liz Rice&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/lizrice"&gt;@lizrice&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: eBPF, containers, security, Kubernetes&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="loris-cro"&gt;Loris Cro&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://kristoff.it/"&gt;kristoff.it&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/croloris"&gt;@croloris&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Zig, systems programming, developer experience&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="martin-kleppmann"&gt;Martin Kleppmann&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://martin.kleppmann.com/"&gt;martin.kleppmann.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/martinkl"&gt;@martinkl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Distributed systems, databases, CRDTs&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="mat-ryer"&gt;Mat Ryer&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://matryer.com/"&gt;matryer.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/matryer"&gt;@matryer&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Go programming, API design, testing&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="matthias-endler"&gt;Matthias Endler&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://endler.dev/"&gt;endler.dev&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/matthiasendler"&gt;@matthiasendler&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Rust, performance, developer tools&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="matthias-loibl"&gt;Matthias Loibl&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://metalmatze.de/"&gt;metalmatze.de&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/MetalMatze"&gt;@MetalMatze&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Observability, Prometheus, Kubernetes, Go&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="nick-craver"&gt;Nick Craver&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://nickcraver.com/"&gt;nickcraver.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/Nick_Craver"&gt;@Nick_Craver&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Performance, architecture, Stack Overflow infrastructure&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="russ-cox"&gt;Russ Cox&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://research.swtch.com/"&gt;research.swtch.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/_rsc"&gt;@_rsc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Go, programming languages, software engineering&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="simon-willison"&gt;Simon Willison&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://simonwillison.net/"&gt;simonwillison.net&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/simonw"&gt;@simonw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Python, databases, AI/ML, web development&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="steve-yegge"&gt;Steve Yegge&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://steve-yegge.blogspot.com/"&gt;steve-yegge.blogspot.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Programming languages, platforms, software engineering&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="thorsten-ball"&gt;Thorsten Ball&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://thorstenball.com/"&gt;thorstenball.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/thorstenball"&gt;@thorstenball&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Compilers, interpreters, programming languages&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tom-macwright"&gt;Tom MacWright&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://macwright.com/"&gt;macwright.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/tmcw"&gt;@tmcw&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Mapping, web development, open source&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="tyler-neely"&gt;Tyler Neely&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://sled.rs/"&gt;sled.rs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/sadisticsystems"&gt;@sadisticsystems&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Databases, Rust, distributed systems&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="will-larson"&gt;Will Larson&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://lethain.com/"&gt;lethain.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Twitter: &lt;a href="https://twitter.com/Lethain"&gt;@Lethain&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Engineering leadership, systems thinking, career development&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="company-blogs"&gt;Company Blogs&lt;/h2&gt;
&lt;h3 id="datadog-engineering"&gt;Datadog Engineering&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://www.datadoghq.com/blog/engineering/"&gt;datadoghq.com/blog/engineering&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Observability, infrastructure, performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="grafana-labs"&gt;Grafana Labs&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://grafana.com/blog/"&gt;grafana.com/blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Observability, visualization, open source&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="netflix-tech-blog"&gt;Netflix Tech Blog&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://netflixtechblog.com/"&gt;netflixtechblog.com&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Distributed systems, DevOps, performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="polar-signals"&gt;Polar Signals&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Website: &lt;a href="https://www.polarsignals.com/blog/"&gt;polarsignals.com/blog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Focus: Continuous profiling, eBPF, performance&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;p&gt;This is not the perfect way to share these—I&amp;rsquo;m working on an OPML export and better organization. If you have suggestions, &lt;a href="https://kakkoyun.me/misc/"&gt;let me know&lt;/a&gt;.&lt;/p&gt;</description><content:encoded><![CDATA[<p>If you like some of my blog posts, I&rsquo;d recommend reading some of the following blogs, which I read regularly.</p>
<p>This list is organized alphabetically and includes engineers, researchers, and technologists whose work I find valuable.</p>
<hr>
<h2 id="people--blogs">People &amp; Blogs</h2>
<h3 id="bartlomiej-plotka">Bartlomiej Plotka</h3>
<ul>
<li>Website: <a href="https://www.bwplotka.dev/">bwplotka.dev</a></li>
<li>Twitter: <a href="https://twitter.com/bwplotka">@bwplotka</a></li>
<li>Focus: Observability, Prometheus, Thanos, Go</li>
</ul>
<h3 id="brendan-gregg">Brendan Gregg</h3>
<ul>
<li>Website: <a href="https://www.brendangregg.com/">brendangregg.com</a></li>
<li>Twitter: <a href="https://twitter.com/brendangregg">@brendangregg</a></li>
<li>Focus: Performance engineering, eBPF, systems observability</li>
</ul>
<h3 id="bryan-cantrill">Bryan Cantrill</h3>
<ul>
<li>Twitter: <a href="https://twitter.com/bcantrill">@bcantrill</a></li>
<li>Focus: Systems programming, debugging, distributed systems</li>
</ul>
<h3 id="charity-majors">Charity Majors</h3>
<ul>
<li>Website: <a href="https://charity.wtf/">charity.wtf</a></li>
<li>Twitter: <a href="https://twitter.com/mipsytipsy">@mipsytipsy</a></li>
<li>Focus: Observability, engineering culture, distributed systems</li>
</ul>
<h3 id="cindy-sridharan">Cindy Sridharan</h3>
<ul>
<li>Website: <a href="https://copyconstruct.medium.com/">copyconstruct.medium.com</a></li>
<li>Twitter: <a href="https://twitter.com/copyconstruct">@copyconstruct</a></li>
<li>Focus: Distributed systems, observability, testing</li>
</ul>
<h3 id="dave-cheney">Dave Cheney</h3>
<ul>
<li>Website: <a href="https://dave.cheney.net/">dave.cheney.net</a></li>
<li>Twitter: <a href="https://twitter.com/davecheney">@davecheney</a></li>
<li>Focus: Go programming, performance, simplicity</li>
</ul>
<h3 id="derek-sivers">Derek Sivers</h3>
<ul>
<li>Website: <a href="https://sive.rs/">sive.rs</a></li>
<li>Focus: Life philosophy, entrepreneurship, writing</li>
</ul>
<h3 id="frederic-branczyk">Frederic Branczyk</h3>
<ul>
<li>Website: <a href="https://brancz.com/">brancz.com</a></li>
<li>Twitter: <a href="https://twitter.com/fredbrancz">@fredbrancz</a></li>
<li>Focus: Observability, Prometheus, Kubernetes</li>
</ul>
<h3 id="gergely-orosz">Gergely Orosz</h3>
<ul>
<li>Website: <a href="https://blog.pragmaticengineer.com/">blog.pragmaticengineer.com</a></li>
<li>Twitter: <a href="https://twitter.com/GergelyOrosz">@GergelyOrosz</a></li>
<li>Focus: Engineering culture, career development, tech industry</li>
</ul>
<h3 id="jamie-tanna">Jamie Tanna</h3>
<ul>
<li>Website: <a href="https://www.jvt.me/">jvt.me</a></li>
<li>Twitter: <a href="https://twitter.com/JamieTanna">@JamieTanna</a></li>
<li>Focus: DevOps, automation, IndieWeb, Go</li>
</ul>
<h3 id="jan-fabian-meier">Jan Fabian Meier</h3>
<ul>
<li>Website: <a href="https://jfm.carcosa.net/">jfm.carcosa.net</a></li>
<li>Focus: Systems programming, performance, Zig</li>
</ul>
<h3 id="jesse-duffield">Jesse Duffield</h3>
<ul>
<li>Website: <a href="https://jesseduffield.com/">jesseduffield.com</a></li>
<li>Twitter: <a href="https://twitter.com/DuffieldJesse">@DuffieldJesse</a></li>
<li>Focus: Developer tools, Go, productivity</li>
</ul>
<h3 id="julia-evans">Julia Evans</h3>
<ul>
<li>Website: <a href="https://jvns.ca/">jvns.ca</a></li>
<li>Twitter: <a href="https://twitter.com/b0rk">@b0rk</a></li>
<li>Focus: Systems programming, learning, zines</li>
</ul>
<h3 id="liz-rice">Liz Rice</h3>
<ul>
<li>Twitter: <a href="https://twitter.com/lizrice">@lizrice</a></li>
<li>Focus: eBPF, containers, security, Kubernetes</li>
</ul>
<h3 id="loris-cro">Loris Cro</h3>
<ul>
<li>Website: <a href="https://kristoff.it/">kristoff.it</a></li>
<li>Twitter: <a href="https://twitter.com/croloris">@croloris</a></li>
<li>Focus: Zig, systems programming, developer experience</li>
</ul>
<h3 id="martin-kleppmann">Martin Kleppmann</h3>
<ul>
<li>Website: <a href="https://martin.kleppmann.com/">martin.kleppmann.com</a></li>
<li>Twitter: <a href="https://twitter.com/martinkl">@martinkl</a></li>
<li>Focus: Distributed systems, databases, CRDTs</li>
</ul>
<h3 id="mat-ryer">Mat Ryer</h3>
<ul>
<li>Website: <a href="https://matryer.com/">matryer.com</a></li>
<li>Twitter: <a href="https://twitter.com/matryer">@matryer</a></li>
<li>Focus: Go programming, API design, testing</li>
</ul>
<h3 id="matthias-endler">Matthias Endler</h3>
<ul>
<li>Website: <a href="https://endler.dev/">endler.dev</a></li>
<li>Twitter: <a href="https://twitter.com/matthiasendler">@matthiasendler</a></li>
<li>Focus: Rust, performance, developer tools</li>
</ul>
<h3 id="matthias-loibl">Matthias Loibl</h3>
<ul>
<li>Website: <a href="https://metalmatze.de/">metalmatze.de</a></li>
<li>Twitter: <a href="https://twitter.com/MetalMatze">@MetalMatze</a></li>
<li>Focus: Observability, Prometheus, Kubernetes, Go</li>
</ul>
<h3 id="nick-craver">Nick Craver</h3>
<ul>
<li>Website: <a href="https://nickcraver.com/">nickcraver.com</a></li>
<li>Twitter: <a href="https://twitter.com/Nick_Craver">@Nick_Craver</a></li>
<li>Focus: Performance, architecture, Stack Overflow infrastructure</li>
</ul>
<h3 id="russ-cox">Russ Cox</h3>
<ul>
<li>Website: <a href="https://research.swtch.com/">research.swtch.com</a></li>
<li>Twitter: <a href="https://twitter.com/_rsc">@_rsc</a></li>
<li>Focus: Go, programming languages, software engineering</li>
</ul>
<h3 id="simon-willison">Simon Willison</h3>
<ul>
<li>Website: <a href="https://simonwillison.net/">simonwillison.net</a></li>
<li>Twitter: <a href="https://twitter.com/simonw">@simonw</a></li>
<li>Focus: Python, databases, AI/ML, web development</li>
</ul>
<h3 id="steve-yegge">Steve Yegge</h3>
<ul>
<li>Website: <a href="https://steve-yegge.blogspot.com/">steve-yegge.blogspot.com</a></li>
<li>Focus: Programming languages, platforms, software engineering</li>
</ul>
<h3 id="thorsten-ball">Thorsten Ball</h3>
<ul>
<li>Website: <a href="https://thorstenball.com/">thorstenball.com</a></li>
<li>Twitter: <a href="https://twitter.com/thorstenball">@thorstenball</a></li>
<li>Focus: Compilers, interpreters, programming languages</li>
</ul>
<h3 id="tom-macwright">Tom MacWright</h3>
<ul>
<li>Website: <a href="https://macwright.com/">macwright.com</a></li>
<li>Twitter: <a href="https://twitter.com/tmcw">@tmcw</a></li>
<li>Focus: Mapping, web development, open source</li>
</ul>
<h3 id="tyler-neely">Tyler Neely</h3>
<ul>
<li>Website: <a href="https://sled.rs/">sled.rs</a></li>
<li>Twitter: <a href="https://twitter.com/sadisticsystems">@sadisticsystems</a></li>
<li>Focus: Databases, Rust, distributed systems</li>
</ul>
<h3 id="will-larson">Will Larson</h3>
<ul>
<li>Website: <a href="https://lethain.com/">lethain.com</a></li>
<li>Twitter: <a href="https://twitter.com/Lethain">@Lethain</a></li>
<li>Focus: Engineering leadership, systems thinking, career development</li>
</ul>
<hr>
<h2 id="company-blogs">Company Blogs</h2>
<h3 id="datadog-engineering">Datadog Engineering</h3>
<ul>
<li>Website: <a href="https://www.datadoghq.com/blog/engineering/">datadoghq.com/blog/engineering</a></li>
<li>Focus: Observability, infrastructure, performance</li>
</ul>
<h3 id="grafana-labs">Grafana Labs</h3>
<ul>
<li>Website: <a href="https://grafana.com/blog/">grafana.com/blog</a></li>
<li>Focus: Observability, visualization, open source</li>
</ul>
<h3 id="netflix-tech-blog">Netflix Tech Blog</h3>
<ul>
<li>Website: <a href="https://netflixtechblog.com/">netflixtechblog.com</a></li>
<li>Focus: Distributed systems, DevOps, performance</li>
</ul>
<h3 id="polar-signals">Polar Signals</h3>
<ul>
<li>Website: <a href="https://www.polarsignals.com/blog/">polarsignals.com/blog</a></li>
<li>Focus: Continuous profiling, eBPF, performance</li>
</ul>
<hr>
<p>This is not the perfect way to share these—I&rsquo;m working on an OPML export and better organization. If you have suggestions, <a href="/misc/">let me know</a>.</p>
]]></content:encoded></item><item><title>Now</title><link>https://kakkoyun.me/now/</link><pubDate>Fri, 21 Oct 2022 21:45:25 +0100</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/now/</guid><description>&lt;p&gt;What I&amp;rsquo;m focused on now. Learn more about the Now page idea at &lt;a href="https://sive.rs/now"&gt;https://sive.rs/now&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="work"&gt;Work&lt;/h2&gt;
&lt;p&gt;Building Go-for-Go at Datadog APM: compile-time and runtime instrumentation, profiling, and performance tooling for Go services.&lt;/p&gt;
&lt;p&gt;Currently focused on Go runtime and compiler internals, building AST manipulation tools for injecting instrumentation systems. This work spans OpenTelemetry Go compile instrumentation, Datadog Orchestrion, and dd-trace-go (see &lt;a href="https://kakkoyun.me/open_source/"&gt;Open Source&lt;/a&gt; for details).&lt;/p&gt;
&lt;h2 id="learning--exploration"&gt;Learning &amp;amp; Exploration&lt;/h2&gt;
&lt;p&gt;Focused on writing, blogging, engineering leadership, and the internals of LLMs. Exploring how these tools work under the hood and how to build better systems with them.&lt;/p&gt;</description><content:encoded><![CDATA[<p>What I&rsquo;m focused on now. Learn more about the Now page idea at <a href="https://sive.rs/now">https://sive.rs/now</a>.</p>
<h2 id="work">Work</h2>
<p>Building Go-for-Go at Datadog APM: compile-time and runtime instrumentation, profiling, and performance tooling for Go services.</p>
<p>Currently focused on Go runtime and compiler internals, building AST manipulation tools for injecting instrumentation systems. This work spans OpenTelemetry Go compile instrumentation, Datadog Orchestrion, and dd-trace-go (see <a href="/open_source/">Open Source</a> for details).</p>
<h2 id="learning--exploration">Learning &amp; Exploration</h2>
<p>Focused on writing, blogging, engineering leadership, and the internals of LLMs. Exploring how these tools work under the hood and how to build better systems with them.</p>
<p>Experimenting with Claude Code, Claude Desktop, and MCPs to fully automate my Personal Knowledge Management on top of Obsidian.</p>
<h2 id="hobby-tech">Hobby Tech</h2>
<p>Showing some love to my Raspberry Pi cluster and Flipper Zero. Small projects, big fun.</p>
<h2 id="family--life">Family &amp; Life</h2>
<p>Based in Berlin with my partner and our son.</p>
<p>Gravel cycling whenever I can. Dreaming of camping and bikepacking trips—something I want to do with my boy when he&rsquo;s grown up enough. For now, prioritizing outdoor time and sleep.</p>
<h2 id="reading--writing">Reading &amp; Writing</h2>
<p>Reading a mix of systems papers, engineering leadership books, and sci-fi. Check my <a href="/reading/">reading page</a> for current books.</p>
<p>Notes and insights land in the <strong><a href="/notes/">notes</a></strong> section when useful.</p>
]]></content:encoded></item><item><title>Open Source</title><link>https://kakkoyun.me/open_source/</link><pubDate>Fri, 21 Oct 2022 21:45:25 +0100</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/open_source/</guid><description>&lt;p&gt;This section highlights selected open-source projects I contribute to or helped maintain.&lt;/p&gt;
&lt;h2 id="projects-that-i-help-to-maintain"&gt;Projects that I help to maintain&lt;/h2&gt;
&lt;h3 id="prometheusclient_"&gt;&lt;a href="https://github.com/prometheus/client_golang"&gt;prometheus/client_golang&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prometheus instrumentation library for Go applications.&lt;/p&gt;
&lt;h3 id="opentelemetrygo-compile-instrumentation"&gt;&lt;a href="https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation"&gt;opentelemetry/go-compile-instrumentation&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Prototype compile-time instrumentation for Go, exploring zero-touch observability.&lt;/p&gt;
&lt;h3 id="datadogorchestrion"&gt;&lt;a href="https://github.com/Datadog/Orchestrion"&gt;Datadog/Orchestrion&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Source-based compile-time instrumentation for Go, enabling deep APM without manual code changes.&lt;/p&gt;
&lt;h3 id="datadogdd-trace-go"&gt;&lt;a href="https://github.com/DataDog/dd-trace-go"&gt;Datadog/dd-trace-go&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Datadog APM tracing client for Go.&lt;/p&gt;
&lt;h2 id="projects-that-i-used-to-maintain"&gt;Projects that I USED to maintain&lt;/h2&gt;
&lt;h3 id="thanos"&gt;&lt;a href="https://github.com/thanos-io/thanos"&gt;thanos&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Highly available Prometheus setup with long term storage capabilities. CNCF Sandbox project. &lt;a href="https://thanos.io"&gt;https://thanos.io&lt;/a&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>This section highlights selected open-source projects I contribute to or helped maintain.</p>
<h2 id="projects-that-i-help-to-maintain">Projects that I help to maintain</h2>
<h3 id="prometheusclient_"><a href="https://github.com/prometheus/client_golang">prometheus/client_golang</a></h3>
<p>Prometheus instrumentation library for Go applications.</p>
<h3 id="opentelemetrygo-compile-instrumentation"><a href="https://github.com/open-telemetry/opentelemetry-go-compile-instrumentation">opentelemetry/go-compile-instrumentation</a></h3>
<p>Prototype compile-time instrumentation for Go, exploring zero-touch observability.</p>
<h3 id="datadogorchestrion"><a href="https://github.com/Datadog/Orchestrion">Datadog/Orchestrion</a></h3>
<p>Source-based compile-time instrumentation for Go, enabling deep APM without manual code changes.</p>
<h3 id="datadogdd-trace-go"><a href="https://github.com/DataDog/dd-trace-go">Datadog/dd-trace-go</a></h3>
<p>Datadog APM tracing client for Go.</p>
<h2 id="projects-that-i-used-to-maintain">Projects that I USED to maintain</h2>
<h3 id="thanos"><a href="https://github.com/thanos-io/thanos">thanos</a></h3>
<p>Highly available Prometheus setup with long term storage capabilities. CNCF Sandbox project. <a href="https://thanos.io">https://thanos.io</a></p>
<h3 id="kube-thanos"><a href="https://github.com/thanos-io/kube-thanos">kube-thanos</a></h3>
<p>Kubernetes specific configuration for deploying Thanos.</p>
<h3 id="parca"><a href="https://github.com/parca-dev/parca">parca</a></h3>
<p>Parca is a continuous profiling project for applications and infrastructure. It helps you save money, improve performance and understand incidents better. <a href="https://parca.dev">https://parca.dev</a></p>
<h3 id="parca-agent"><a href="https://github.com/parca-dev/parca-agent">parca-agent</a></h3>
<p>eBPF based always-on profiler auto-discovering targets in Kubernetes and systemd, zero code changes or restarts needed! <a href="https://parca.dev">https://parca.dev</a></p>
<h3 id="kube-prometheus"><a href="https://github.com/prometheus-operator/kube-prometheus">kube-prometheus</a></h3>
<p>Use Prometheus to monitor Kubernetes and applications running on Kubernetes.</p>
<h3 id="drone-cache"><a href="https://github.com/meltwater/drone-cache">drone-cache</a></h3>
<p>A Drone plugin for caching current workspace files between builds to reduce your build times. drone-cache is a small CLI program, written in Go without any external OS dependencies (such as tar, etc).</p>
]]></content:encoded></item><item><title>Sponsorship</title><link>https://kakkoyun.me/sponsorship/</link><pubDate>Fri, 21 Oct 2022 21:45:25 +0100</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/sponsorship/</guid><description>&lt;p&gt;I’m working toward spending more time building open‑source tooling for observability, instrumentation, and performance engineering.&lt;/p&gt;
&lt;p&gt;Your support helps me:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dedicate time to maintainers’ work and triage&lt;/li&gt;
&lt;li&gt;Prototype new tooling (profilers, compile‑time instrumentation, eBPF experiments)&lt;/li&gt;
&lt;li&gt;Write posts/talks and share repeatable techniques&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-support"&gt;How to support&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;GitHub Sponsors: &lt;a href="https://github.com/sponsors/kakkoyun"&gt;https://github.com/sponsors/kakkoyun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Patreon: &lt;a href="https://patreon.com/kakkoyun"&gt;https://patreon.com/kakkoyun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Buy Me a Coffee: &lt;a href="https://buymeacoffee.com/kakkoyun"&gt;https://buymeacoffee.com/kakkoyun&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PayPal: &lt;a href="https://paypal.me/kakkoyun"&gt;https://paypal.me/kakkoyun&lt;/a&gt; or &lt;a href="https://www.paypal.com/donate/?hosted_button_id=GNPYRG2S3UH5U"&gt;https://www.paypal.com/donate/?hosted_button_id=GNPYRG2S3UH5U&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;No pressure:&lt;/strong&gt; Reading and sharing my work already helps a lot. Thank you.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Privacy:&lt;/strong&gt; I don’t sell email lists. You’ll get only occasional updates relevant to the work.&lt;/p&gt;</description><content:encoded><![CDATA[<p>I’m working toward spending more time building open‑source tooling for observability, instrumentation, and performance engineering.</p>
<p>Your support helps me:</p>
<ul>
<li>Dedicate time to maintainers’ work and triage</li>
<li>Prototype new tooling (profilers, compile‑time instrumentation, eBPF experiments)</li>
<li>Write posts/talks and share repeatable techniques</li>
</ul>
<h2 id="how-to-support">How to support</h2>
<ul>
<li>GitHub Sponsors: <a href="https://github.com/sponsors/kakkoyun">https://github.com/sponsors/kakkoyun</a></li>
<li>Patreon: <a href="https://patreon.com/kakkoyun">https://patreon.com/kakkoyun</a></li>
<li>Buy Me a Coffee: <a href="https://buymeacoffee.com/kakkoyun">https://buymeacoffee.com/kakkoyun</a></li>
<li>PayPal: <a href="https://paypal.me/kakkoyun">https://paypal.me/kakkoyun</a> or <a href="https://www.paypal.com/donate/?hosted_button_id=GNPYRG2S3UH5U">https://www.paypal.com/donate/?hosted_button_id=GNPYRG2S3UH5U</a></li>
</ul>
<p><strong>No pressure:</strong> Reading and sharing my work already helps a lot. Thank you.</p>
<p><strong>Privacy:</strong> I don’t sell email lists. You’ll get only occasional updates relevant to the work.</p>
]]></content:encoded></item><item><title>Uses</title><link>https://kakkoyun.me/uses/</link><pubDate>Fri, 21 Oct 2022 21:45:25 +0100</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/uses/</guid><description>&lt;p&gt;Updated occasionally; this is what I actually use day-to-day.&lt;/p&gt;
&lt;h2 id="hardware--desk"&gt;Hardware / Desk&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Laptop&lt;/strong&gt;: &lt;a href="https://www.apple.com/de/shop/buy-mac/macbook-pro/16-zoll-m4-max"&gt;MacBook Pro 16&amp;quot; M4 Max&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keyboard&lt;/strong&gt;: &lt;a href="https://bastardkb.com/product/dilemma-max/"&gt;Bastard Keyboards Dilemma Max&lt;/a&gt; - Split ergonomic mechanical keyboard&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mouse&lt;/strong&gt;: &lt;a href="https://www.logitech.com/de-de/shop/p/mx-ergo-s-wireless-trackball-mouse.910-007260"&gt;Logitech MX Ergo S&lt;/a&gt; - Wireless trackball&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor&lt;/strong&gt;: &lt;a href="https://consumer.huawei.com/en/monitors/mateview-gt/"&gt;Huawei MateView GT&lt;/a&gt; - 27&amp;quot; curved display&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webcam&lt;/strong&gt;: &lt;a href="https://www.logitech.com/en-us/products/webcams/brio-4k-hdr-webcam.960-001105.html"&gt;Logitech Brio 4K&lt;/a&gt; - 4K HDR webcam&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Microphone&lt;/strong&gt;: &lt;a href="https://www.elgato.com/de/de/p/wave-3-black"&gt;Elgato Wave:3&lt;/a&gt; - USB condenser mic&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audio&lt;/strong&gt;: AirPods Pro 2&lt;/li&gt;
&lt;li&gt;USB-C hub&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="dev-machines"&gt;Dev Machines&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Desktop&lt;/strong&gt;: &lt;a href="https://omarchy.org/"&gt;Omarchy Linux&lt;/a&gt; - Custom Arch-based distribution&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Laptop&lt;/strong&gt;: macOS on M4 Max&lt;/li&gt;
&lt;li&gt;Containers for reproducible toolchains&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="terminal--cli"&gt;Terminal / CLI&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;zsh, ripgrep, fd, fzf, jq, make&lt;/li&gt;
&lt;li&gt;tmux for long-running sessions&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="editors"&gt;Editors&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Cursor / VS Code for most work&lt;/li&gt;
&lt;li&gt;Neovim for quick edits and terminal workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="languages--toolchains"&gt;Languages &amp;amp; Toolchains&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Go (primary language)&lt;/li&gt;
&lt;li&gt;Bit of Rust and Python&lt;/li&gt;
&lt;li&gt;eBPF tooling, perf, flamegraphs, pprof&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="services--infra"&gt;Services / Infra&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: Version control, CI/CD, project management&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Netlify&lt;/strong&gt;: Site hosting and deployment&lt;/li&gt;
&lt;li&gt;Plausible analytics&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="note-taking--knowledge-management"&gt;Note-Taking &amp;amp; Knowledge Management&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://obsidian.md/"&gt;Obsidian&lt;/a&gt;&lt;/strong&gt;: All notes, knowledge management, and daily writing&lt;/li&gt;
&lt;li&gt;Published through Obsidian Publish at &lt;a href="https://kakkoyun.me/notes/"&gt;/notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Claude Code + Claude Desktop&lt;/strong&gt;: Automating PKM workflows with MCP servers&lt;/li&gt;
&lt;li&gt;Experimenting with AI-assisted note organization and content discovery&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="hobby-tech"&gt;Hobby Tech&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Raspberry Pi cluster&lt;/strong&gt;: Home lab for experiments and learning&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Flipper Zero&lt;/strong&gt;: Hardware hacking and security research&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="cycling"&gt;Cycling&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Bike&lt;/strong&gt;: &lt;a href="https://boodabike.com/en/product/booda-bike-hiker-231"&gt;Booda Bike Hiker 231&lt;/a&gt; - Gearhub carbon belt drive with slick design&lt;/li&gt;
&lt;li&gt;Dreaming of the next iteration: Rohloff gearhub, more carbon, hidden front suspension (need someone to build it for me)&lt;/li&gt;
&lt;li&gt;Gravel cycling, bikepacking adventures (future trips with my son)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="content--reading"&gt;Content / Reading&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Readwise for highlights and review&lt;/li&gt;
&lt;li&gt;Notes captured and published through Obsidian&lt;/li&gt;
&lt;/ul&gt;</description><content:encoded><![CDATA[<p>Updated occasionally; this is what I actually use day-to-day.</p>
<h2 id="hardware--desk">Hardware / Desk</h2>
<ul>
<li><strong>Laptop</strong>: <a href="https://www.apple.com/de/shop/buy-mac/macbook-pro/16-zoll-m4-max">MacBook Pro 16&quot; M4 Max</a></li>
<li><strong>Keyboard</strong>: <a href="https://bastardkb.com/product/dilemma-max/">Bastard Keyboards Dilemma Max</a> - Split ergonomic mechanical keyboard</li>
<li><strong>Mouse</strong>: <a href="https://www.logitech.com/de-de/shop/p/mx-ergo-s-wireless-trackball-mouse.910-007260">Logitech MX Ergo S</a> - Wireless trackball</li>
<li><strong>Monitor</strong>: <a href="https://consumer.huawei.com/en/monitors/mateview-gt/">Huawei MateView GT</a> - 27&quot; curved display</li>
<li><strong>Webcam</strong>: <a href="https://www.logitech.com/en-us/products/webcams/brio-4k-hdr-webcam.960-001105.html">Logitech Brio 4K</a> - 4K HDR webcam</li>
<li><strong>Microphone</strong>: <a href="https://www.elgato.com/de/de/p/wave-3-black">Elgato Wave:3</a> - USB condenser mic</li>
<li><strong>Audio</strong>: AirPods Pro 2</li>
<li>USB-C hub</li>
</ul>
<h2 id="dev-machines">Dev Machines</h2>
<ul>
<li><strong>Desktop</strong>: <a href="https://omarchy.org/">Omarchy Linux</a> - Custom Arch-based distribution</li>
<li><strong>Laptop</strong>: macOS on M4 Max</li>
<li>Containers for reproducible toolchains</li>
</ul>
<h2 id="terminal--cli">Terminal / CLI</h2>
<ul>
<li>zsh, ripgrep, fd, fzf, jq, make</li>
<li>tmux for long-running sessions</li>
</ul>
<h2 id="editors">Editors</h2>
<ul>
<li>Cursor / VS Code for most work</li>
<li>Neovim for quick edits and terminal workflows</li>
</ul>
<h2 id="languages--toolchains">Languages &amp; Toolchains</h2>
<ul>
<li>Go (primary language)</li>
<li>Bit of Rust and Python</li>
<li>eBPF tooling, perf, flamegraphs, pprof</li>
</ul>
<h2 id="services--infra">Services / Infra</h2>
<ul>
<li><strong>GitHub</strong>: Version control, CI/CD, project management</li>
<li><strong>Netlify</strong>: Site hosting and deployment</li>
<li>Plausible analytics</li>
</ul>
<h2 id="note-taking--knowledge-management">Note-Taking &amp; Knowledge Management</h2>
<ul>
<li><strong><a href="https://obsidian.md/">Obsidian</a></strong>: All notes, knowledge management, and daily writing</li>
<li>Published through Obsidian Publish at <a href="/notes/">/notes</a></li>
<li><strong>Claude Code + Claude Desktop</strong>: Automating PKM workflows with MCP servers</li>
<li>Experimenting with AI-assisted note organization and content discovery</li>
</ul>
<h2 id="hobby-tech">Hobby Tech</h2>
<ul>
<li><strong>Raspberry Pi cluster</strong>: Home lab for experiments and learning</li>
<li><strong>Flipper Zero</strong>: Hardware hacking and security research</li>
</ul>
<h2 id="cycling">Cycling</h2>
<ul>
<li><strong>Bike</strong>: <a href="https://boodabike.com/en/product/booda-bike-hiker-231">Booda Bike Hiker 231</a> - Gearhub carbon belt drive with slick design</li>
<li>Dreaming of the next iteration: Rohloff gearhub, more carbon, hidden front suspension (need someone to build it for me)</li>
<li>Gravel cycling, bikepacking adventures (future trips with my son)</li>
</ul>
<h2 id="content--reading">Content / Reading</h2>
<ul>
<li>Readwise for highlights and review</li>
<li>Notes captured and published through Obsidian</li>
</ul>
]]></content:encoded></item><item><title>talk: Story of Correlation - Integrating Thanos Metrics with Observability Signals</title><link>https://kakkoyun.me/talks/story-of-correlation-integrating-thanos-metrics/</link><pubDate>Wed, 15 Jun 2022 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/story-of-correlation-integrating-thanos-metrics/</guid><description>How to correlate Thanos metrics with logs, traces, and continuous profiling for enhanced observability, demonstrating end-to-end integration patterns across multiple backends.</description><content:encoded><![CDATA[<p>The CNCF Incubated Thanos project with the large open-source community continues to push boundaries regarding observability and monitoring using Prometheus-based metrics. Together with the Prometheus community, it improves the metric story for Kubernetes clusters and beyond. Things like improved performance, better scalability, debuggability, security, metrics backfilling and query QoS is only the tip of the iceberg. As we know, observability nowadays comes in many flavours. Bunching them together is not a trivial side, given many shapes and collection points. Aside from metrics, we have logs, traces or even continuous profiling. In this talk, Kemal and Bartek, Thanos maintainers, after a quick overview of Thanos, will explain how Thanos can be integrated with those non-metric observability signals. The audience will learn an example, end-to-end ways to correlate multiple observability backends with Thanos for enhanced observability and monitoring experience.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/rWFb01GW0mQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://docs.google.com/presentation/d/1FvMqgD5jL5_eoUs6CgIFiBS06U0Ge1CBSXZKz26fsac/edit?usp=sharing">Story of Correlation: Integrating Thanos Metrics with Observability Signals</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://sched.co/ytsK">KubeCon EU 2022</a>
<ul>
<li><a href="https://youtu.be/rWFb01GW0mQ">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>talk: eBPF? Safety First!</title><link>https://kakkoyun.me/talks/ebpf-safety-first/</link><pubDate>Tue, 10 May 2022 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/ebpf-safety-first/</guid><description>Building safe eBPF programs by combining Rust in the data plane for compile-time safety and Go in the control plane for rapid development, targeting Kubernetes workloads.</description><content:encoded><![CDATA[<p>eBPF being a promising technology is no news. And C is the defacto choice for writing eBPF programs. The act of writing C programs in an error-prone process. Even the eBPF verifier makes life a lot easier; it is still possible to write unsafe programs and make trivial mistakes that elude the compiler but are detected by the verifier in the load time, which are preventable with compile-time checks. It is where Rust comes in. Rust is a language designed for safety. Recently the Rust compiler gained the ability to compile to the eBPF virtual machine, and Rust became an official language for Linux. We discover more and more use cases where eBPF can be helpful. We find more efficient ways to build safe eBPF programs that are parallel to these developments. We will demonstrate how we made applications combined with Rust in the data plane for more safety and Go in the control plane for a higher development pace to target Kubernetes for security, observability and performance tuning.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/oWHQrlE2-G8" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://docs.google.com/presentation/d/1hKqxAC9aaWLPM4xwXyXuK5cp2LBAewOVqZ05qjLNnK8/edit?usp=sharing">eBPF? Safety First!</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://sched.co/zrPZ">Cloud-Native eBPF Day EU 2022</a>
<ul>
<li><a href="https://youtu.be/oWHQrlE2-G8">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>talk: Building a Go Profiler Using Go</title><link>https://kakkoyun.me/talks/building-a-go-profiler-using-go/</link><pubDate>Sun, 20 Mar 2022 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/building-a-go-profiler-using-go/</guid><description>Learn how to build a Go profiler using eBPF technology, combining portable eBPF programs with Go for continuous profiling and performance analysis in cloud-native environments.</description><content:encoded><![CDATA[<p>Profiling has long been part of the Go developer’s toolbox to analyze the resource usage of a running process. But do you ever wonder how profilers built? In this talk, I will bring eBPF (a promising Kernel technology) and Go together to build a profiler for understanding Go code at runtime.</p>
<p>Profiling has long been part of the developer’s toolbox to analyze the resource usage of a running process. Go users are very familiar with the concept thanks to state-of-art Go tooling. For years Google has consistently been able to cut down multiple percentage points in their fleet-wide resource usage every quarter, using techniques described in their “Google-Wide Profiling” paper, which is called continuous profiling. Through continuous profiling, the systematic collection of profiles, entirely new workflows suddenly become possible.</p>
<p>In parallel, eBPF became a new promising technology, is likely not news to most people in cloud space. We are discovering more use cases where eBPF can be useful, especially when combined with Go and modern infrastructure, from security, over observability to performance tuning. For a long time, eBPF has struggled with portability, it needed to be compiled for each kernel, or a compiler and kernel headers needed to be shipped to execute effectively arbitrary code. The eBPF community acknowledged this and started the CO:RE (compile once-run everywhere) initiative, which is young but quickly maturing in the form of libbpf and libbpf-go.</p>
<p>In this talk, we will bring these two concepts together, and explain how to write portable eBPF programs and embed them in Go applications. And what libbpf-go does in order to achieve compile once-run everywhere, how it can be used in portable Go applications. We will demonstrate all concepts together by using real-life examples to help measure and improve performance systematically.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/OlHQ6gkwqyA" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<h4 id="demo-application"><a href="https://github.com/kakkoyun/tiny-profiler">Demo Application</a></h4>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://docs.google.com/presentation/d/1hKqxAC9aaWLPM4xwXyXuK5cp2LBAewOVqZ05qjLNnK8/edit?usp=sharing">Building a Go Profiler Using Go</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://gophercon.eu">GopherCon EU 2022</a>
<ul>
<li><a href="#building-a-go-profiler-using-go">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>Fantastic Symbols and Where to Find Them - Part 2</title><link>https://kakkoyun.me/posts/fantastic-symbols-and-where-to-find-them-part-2/</link><pubDate>Thu, 27 Jan 2022 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/fantastic-symbols-and-where-to-find-them-part-2/</guid><description>Part 2 explores symbolization in interpreted and JIT-compiled languages including Python, Ruby, JavaScript, JVM, and .NET, covering how each runtime handles symbol information differently.</description><content:encoded><![CDATA[<blockquote>
<p>This is a blog post series. If you haven’t read <a href="https://www.polarsignals.com/blog/posts/2022/01/13/fantastic-symbols-and-where-to-find-them">Part 1</a> we recommend you to do so first!</p>
</blockquote>
<p>In <a href="https://www.polarsignals.com/blog/posts/2022/01/13/fantastic-symbols-and-where-to-find-them">the first blog post</a>, we learned about the fantastic symbols (<a href="https://en.wikipedia.org/wiki/Debug_symbol">debug symbols</a>), how the symbolization process works and lastly, how to find the symbolic names of addresses in a compiled binary.</p>
<p>The actual location of the symbolic information depends on the programming language implementation the program is written in.
We can categorize the programming language implementations into three groups: compiled languages (with or without a runtime), interpreted languages, and <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">JIT-compiled</a> languages.</p>
<p>In this post, we will continue our journey to find fantastic symbols. And we will look into where to find them for the other types of programming language implementations.</p>
<h2 id="jit-compiled-language-implementations">JIT-compiled language implementations</h2>
<p>Examples of JIT-compiled languages include Java, .NET, Erlang, JavaScript (Node.js) and many others.</p>
<p><a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">Just-In-Time</a> compiled languages compile the source code into <a href="https://en.wikipedia.org/wiki/Bytecode">bytecode</a>, which is then compiled into <a href="https://en.wikipedia.org/wiki/Machine_code">machine code</a> at runtime,
often using direct feedback from runtime to guide compiler optimizations on the fly.</p>
<p>Because functions are compiled on the fly, there is no pre-built, discoverable symbol table in any object files. Instead, the symbol table is created on the fly.
The symbol mappings (location to symbol) are usually stored in the <em>memory</em> of the <a href="https://en.wikipedia.org/wiki/Runtime_(program_lifecycle_phase)">runtime</a> or <a href="https://en.wikipedia.org/wiki/Virtual_machine">virtual machine</a>
and used for rendering human-readable stack traces when it is needed <em>, e. g.</em> when an exception occurs, the runtime will use the symbol mappings to render a human-readable stack trace.</p>
<p>The good thing is that most of the runtimes provide supplemental symbol mappings for the just-in-time compiled code for Linux to use <code>perf</code>.</p>
<p><code>perf</code> defines <a href="https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/jit-interface.txt">an interface</a> to resolve symbols for dynamically generated code by a JIT compiler.
These files usually can be found in <code>/tmp/perf-$PID.map</code>, where <code>$PID</code> is the process ID of the process of the runtime that is running on the system.</p>
<p>The runtimes usually don&rsquo;t enable providing symbol mappings by default.
You might need to change a configuration, run the virtual machine with a specific flag/environment variable or run an additional program to obtain these mappings.
For example, JVM needs an agent to provide supplemental symbol mapping files, called <a href="https://github.com/jvm-profiling-tools/perf-map-agent">perf-map-agent</a>.</p>
<p>Let&rsquo;s see an example <code>perf map</code> file for NodeJS. The runtimes out there output this file with <em>more or less</em> the same format, <a href="https://github.com/parca-dev/parca-agent/issues/139">more or less!</a></p>
<p>To generate a similar file for <a href="https://en.wikipedia.org/wiki/Node.js">Node.js</a>, we need to run <code>node</code> with <code>--perf-basic-prof</code> option.</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span><span style="color:#75715e"># With Node.js &gt;=v0.11.15 the following command will create a map file for NodeJS:</span>
</span></span><span style="display:flex;"><span>node --perf-basic-prof your-app.js
</span></span></code></pre></td></tr></table>
</div>
</div><p>This will create a map file at <code>/tmp/perf-&lt;pid&gt;.map</code> that looks like this:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>3ef414c0 398 RegExp:[{(]
</span></span><span style="display:flex;"><span>3ef418a0 398 RegExp:[})]
</span></span><span style="display:flex;"><span>59ed4102 26 LazyCompile:~REPLServer.self.writer repl.js:514
</span></span><span style="display:flex;"><span>59ed44ea 146 LazyCompile:~inspect internal/util/inspect.js:152
</span></span><span style="display:flex;"><span>59ed4e4a 148 LazyCompile:~formatValue internal/util/inspect.js:456
</span></span><span style="display:flex;"><span>59ed558a 25f LazyCompile:~formatPrimitive internal/util/inspect.js:768
</span></span><span style="display:flex;"><span>59ed5d62 35 LazyCompile:~formatNumber internal/util/inspect.js:761
</span></span><span style="display:flex;"><span>59ed5fca 5d LazyCompile:~stylizeWithColor internal/util/inspect.js:267
</span></span><span style="display:flex;"><span>4edd2e52 65 LazyCompile:~Domain.exit domain.js:284
</span></span><span style="display:flex;"><span>4edd30ea 14b LazyCompile:~lastIndexOf native array.js:618
</span></span><span style="display:flex;"><span>4edd3522 35 LazyCompile:~online internal/repl.js:157
</span></span><span style="display:flex;"><span>4edd37f2 ec LazyCompile:~setTimeout timers.js:388
</span></span><span style="display:flex;"><span>4edd3cca b0 LazyCompile:~Timeout internal/timers.js:55
</span></span><span style="display:flex;"><span>4edd40ba 55 LazyCompile:~initAsyncResource internal/timers.js:45
</span></span><span style="display:flex;"><span>4edd42da f LazyCompile:~exports.active timers.js:151
</span></span><span style="display:flex;"><span>4edd457a cb LazyCompile:~insert timers.js:167
</span></span><span style="display:flex;"><span>4edd4962 50 LazyCompile:~TimersList timers.js:195
</span></span><span style="display:flex;"><span>4edd4cea 37 LazyCompile:~append internal/linkedlist.js:29
</span></span><span style="display:flex;"><span>4edd4f12 35 LazyCompile:~remove internal/linkedlist.js:15
</span></span><span style="display:flex;"><span>4edd5132 d LazyCompile:~isEmpty internal/linkedlist.js:44
</span></span><span style="display:flex;"><span>4edd529a 21 LazyCompile:~ok assert.js:345
</span></span><span style="display:flex;"><span>4edd555a 68 LazyCompile:~innerOk assert.js:317
</span></span><span style="display:flex;"><span>4edd59a2 27 LazyCompile:~processTimers timers.js:220
</span></span><span style="display:flex;"><span>4edd5d9a 197 LazyCompile:~listOnTimeout timers.js:226
</span></span><span style="display:flex;"><span>4edd6352 15 LazyCompile:~peek internal/linkedlist.js:9
</span></span><span style="display:flex;"><span>4edd66ca a1 LazyCompile:~tryOnTimeout timers.js:292
</span></span><span style="display:flex;"><span>4edd6a02 86 LazyCompile:~ontimeout timers.js:429
</span></span><span style="display:flex;"><span>4edd7132 d7 LazyCompile:~process.kill internal/process/per_thread.js:173
</span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>Each line has <code>START</code>, <code>SIZE</code> and <code>symbolname</code> fields, separated with spaces. <code>START</code> and <code>SIZE</code> are hex numbers without 0x.
<code>symbolname</code> is the rest of the line, so it could contain special characters.</p>
</blockquote>
<p>With the help of this mapping file, we have everything we need to symbolize the addresses in the stack trace. Of course, as always, this is just an oversimplification.</p>
<p>For example, these mappings might change as the runtime decides to recompile the bytecode. So we need to keep an eye on these files and keep track of the changes to resolve the address correctly with their most recent mapping.</p>
<p>Each runtime and virtual machine has its peculiarities that we need to adapt. But those are out of the scope of this post.</p>
<h2 id="interpreted-language-implementations">Interpreted language implementations</h2>
<p>Examples of interpreted languages include Python, Ruby, and again many others.
There are also languages that commonly use interpretation as a stage before <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">JIT compilation</a>, e. g. Java.
Symbolization for this stage of compilation is similar to interpreted languages.</p>
<p>Interpreted language runtimes do not compile the program to machine code.
Instead, interpreters and virtual machines parse and execute the source code using their <a href="https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop">REPL</a> routines.
Or execute their own virtual processor. So they have their own way of executing functions and managing stacks.</p>
<p>If you observe (profile or debug) these runtimes using something like <code>perf</code>,
you will see symbols for the runtime. However, you won&rsquo;t see the language-level context you might be expecting.</p>
<p>Moreover, the interpreter itself is probably written in a more low-level language like C or C++.
And when you inspect the object file of the runtime/interpreter, the symbol table that you would find would show the internals of the interpreter, not the symbols from the provided source code.</p>
<h3 id="finding-the-symbols-for-our-runtime">Finding the symbols for our runtime</h3>
<p>The runtime symbols are useful because they allow you to see the internal routines of the interpreter. e. g. how much time your program spends on garbage collection.
And it&rsquo;s mostly like the stack traces you would see in the debugger or profiler will have calls to the internals of the runtime.
So these symbols are also helpful for debugging.</p>
<p><img src="https://www.polarsignals.com/blog/posts/2022/01/node_stack_trace.png"
    alt="Node Stack Trace"
    loading="lazy"
    decoding="async"></p>
<blockquote>
<p>Most of the runtimes are compiled with <code>production</code> mode, and they most likely lack the debug symbols in their release binaries.
You might need to manually compile your runtime in <code>debug mode</code> to actually have them in the resulting binary.
Some runtimes, such as Node.js, already have them in their <code>production</code> distributions.</p>
</blockquote>
<p>Lastly, to completely resolve the stack traces of the runtime, we might need to obtain the debug information for the linked libraries.
If you remember from <a href="/blog/posts/2022/01/13/fantastic-symbols-and-where-to-find-them">the first blog post</a>, debuginfo files can help us.
Debuginfo files for software packages are available through package managers in Linux distributions.
Usually for an available package called <code>mypackage</code> there exists a <code>mypackage-dbgsym</code>, <code>mypackage-dbg</code> or <code>mypackage-debuginfo</code> package.
There are also <a href="https://sourceware.org/elfutils/Debuginfod.html">public servers</a> that serve debug information.
So we need to find the debuginfo files for the runtime we are using and all the linked libraries.</p>
<h3 id="finding-the-symbols-for-our-target-program">Finding the symbols for our target program</h3>
<p>The symbols that we look for in our own program likely are stored in a memory table that is specific to the runtime.
For example, in Python, the symbol mappings can be accessed using <a href="https://docs.python.org/3/library/symtable.html"><code>symtable</code></a>.</p>
<p>As a result, you need to craft a specific routine for each interpreter runtime (in some cases, each version of that runtime) to obtain symbol information.
Educated eyes might have already noticed, it&rsquo;s not an easy undertaking considering the sheer amount of interpreted languages out there.
For example, a very well known Ruby profiler, <a href="https://github.com/rbspy/rbspy/blob/master/ARCHITECTURE.md">rbspy</a>, generates code for reading internal structs of the Ruby runtime for each version.</p>
<p>If you were to write a general-purpose profiler, <em>like us</em>, you would need to write a special subroutine in your profiler for each runtime that you want to support.</p>
<h2 id="again-dont-worry-we-got-you-covered"><em>Again</em>, don&rsquo;t worry, we got you covered</h2>
<p>The good news is we got you covered. If you are using <a href="https://github.com/parca-dev/parca-agent">Parca Agent</a>, we already do <a href="https://www.parca.dev/docs/symbolization">the heavy lifting</a> for you to symbolize captured stack traces.
And we keep extending our support for the different languages and runtimes.
For example, Parca has already support for parsing <code>perf</code> JIT interface to resolve the symbols for collected stack traces.</p>
<p>Check <a href="https://www.parca.dev/">Parca</a> out and let us know what you think, on <a href="https://discord.gg/ZgUpYgpzXy">Discord</a> channel.</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li><a href="https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/jit-interface.txt">perf JIT Interface</a></li>
<li><a href="https://www.brendangregg.com/perf.html#JIT_Symbols">perf JIT Symbols</a></li>
<li><a href="https://joyeecheung.github.io/blog/2018/12/31/tips-and-tricks-node-core/">Node.js profiling tips and tricks</a></li>
<li><a href="https://www.brendangregg.com/blog/2014-09-17/node-flame-graphs-on-linux.html">Node.js Flamegraphs</a></li>
</ul>
]]></content:encoded></item><item><title>Fantastic Symbols and Where to Find Them - Part 1</title><link>https://kakkoyun.me/posts/fantastic-symbols-and-where-to-find-them/</link><pubDate>Thu, 27 Jan 2022 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/fantastic-symbols-and-where-to-find-them/</guid><description>Deep dive into symbolization: how debuggers and profilers translate memory addresses to human-readable function names using ELF, DWARF, and symbol tables in compiled languages.</description><content:encoded><![CDATA[<p>Symbolization is a technique that allows you to translate machine memory addresses to human-readable symbol information (symbols).</p>
<p>Why do we need to read what programs do anyways? We usually do not need to translate everything to a human-readable format when things run smoothly. But when things go south, we need to understand what is going on under the hood.
Symbolization is needed by introspection tools like <a href="https://en.wikipedia.org/wiki/Debugger">debuggers</a>, <a href="https://en.wikipedia.org/wiki/Profiling_(computer_programming)">profilers</a> and <a href="https://en.wikipedia.org/wiki/Core_dump">core dumps</a> or any other program that needs to trace the execution of another program.
While a target program is executing on a machine, these types of programs capture the stack traces of the program that is being executed.</p>
<blockquote>
<p>A <a href="https://en.wikipedia.org/wiki/Stack_trace">stack trace</a> (also called stack backtrace or stack traceback) is a report of the active stack frames at a certain point in time during the execution of a program.</p>
</blockquote>
<p><img src="https://www.polarsignals.com/blog/posts/2022/01/call_stack_layout.svg"
    alt="Call Stack Layout"
    loading="lazy"
    decoding="async"></p>
<p>In raw stack traces, the addresses of the functions that are being called are recorded. The addresses are hexadecimal numbers representing the memory return addresses of the functions. Symbols are needed to translate memory addresses into function and variable names precisely as in the program’s source code to be read by us humans.
Without symbols, all we see are hexadecimal numbers representing the memory addresses that we have captured.</p>
<p><img src="https://www.polarsignals.com/blog/posts/2022/01/unsymbolized_stack.png"
    alt="Unsymbolized Stack"
    loading="lazy"
    decoding="async"></p>
<p>It sounds simple enough, right? Well, it&rsquo;s not. As with everything else about computers, it&rsquo;s a bit of sorcery. It has its challenges, such as associating them with correct symbols, transforming addresses, and most importantly, actually finding the symbols!
The strategies to get symbol information varies depending on the platform and the programming language implementation that the program is written in.</p>
<p><em>For the sake of simplicity, we will be focusing on Linux as the target platform and ignore Windows, macOS and many other platforms. Otherwise, I could end up writing a small size book in here :)</em></p>
<h2 id="fantastic-symbols-">Fantastic Symbols &hellip;</h2>
<p>A symbol (or debug symbol, to be precise) is a special kind of <a href="https://en.wikipedia.org/wiki/Symbol_(programming)">symbol</a> that attaches additional information to the symbol table of a program.
This symbol information allows a debugger or a profiler to gain access to information from the program&rsquo;s source code, such as the names of identifiers, including variables and functions.
But where can we find these symbols?</p>
<h2 id="-and-where-to-find-them">&hellip; and Where to Find Them</h2>
<p>The actual location of the symbolic information depends on the programming language implementation the program is written in.
We can categorize the programming language implementations into three groups: compiled languages (with or without a runtime), interpreted languages, and <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">JIT-compiled</a> languages.</p>
<p>If the program is a compiled one, these may be compiled together with the binary file, distributed in a separate file, or discarded during the compilation and/or linking.
Or, if the program is interpreted, these may be stored in the program itself. Let&rsquo;s briefly look at where and how we can find these symbols depending on the programming language implementation.</p>
<h3 id="compiled-language-implementations">Compiled language implementations</h3>
<p>Examples of compiled languages include C, C++, Go, Rust and many others.</p>
<p>The compiled languages usually have a <a href="https://en.wikipedia.org/wiki/Symbol_table">symbol table</a> that contains all the symbols used in the program.
The symbol table is usually compiled in the executable binary file. And the binary file is typically in the <a href="https://en.wikipedia.org/wiki/Executable_and_Linkable_Format">ELF</a> format (for Linux systems).
Symbol tables are included in the ELF binary file, specifically for mapping the addresses to function names and object names.
In rare cases, it is stored in a separate file, usually with the same name as the binary file, but with a different extension.</p>
<p><img src="https://www.polarsignals.com/blog/posts/2022/01/elf.png"
    alt="ELF"
    loading="lazy"
    decoding="async"></p>
<p>The ELF format is not an easy one to describe in a couple of sentences. For the purpose of this article, we will focus on what we need to know about the ELF format.
Each ELF file is made up of one ELF header, followed by file data. The ELF header is a fixed size and contains information about the data sections.
The relevant part for us is the symbols can live in a special section called <code>.symtab</code> and <code>.dynsym</code>.
<code>.dynsym</code> is the “dynamic symbol table” and it is a smaller version of the <code>.symtab</code> that only contains global symbols.</p>
<p>Contents of <code>.dynsym</code> and <code>.symtab</code> section using <code>readelf -s /bin/go</code>:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>Symbol table &#39;.dynsym&#39; contains 38 entries:
</span></span><span style="display:flex;"><span>   Num: Value Size Type Bind Vis Ndx Name
</span></span><span style="display:flex;"><span>     0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
</span></span><span style="display:flex;"><span>     1: 00000000006355e0 99 FUNC GLOBAL DEFAULT 1 crosscall2
</span></span><span style="display:flex;"><span>     2: 00000000006355a0 55 FUNC GLOBAL DEFAULT 1 _cgo_panic
</span></span><span style="display:flex;"><span>     3: 0000000000465560 25 FUNC GLOBAL DEFAULT 1 _cgo_topofstack
</span></span><span style="display:flex;"><span>     4: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (6)
</span></span><span style="display:flex;"><span>     5: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (4)
</span></span><span style="display:flex;"><span>     6: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (4)
</span></span><span style="display:flex;"><span>     7: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (4)
</span></span><span style="display:flex;"><span>     8: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (4)
</span></span><span style="display:flex;"><span>     9: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (4)
</span></span><span style="display:flex;"><span>    10: 0000000000000000 0 OBJECT GLOBAL DEFAULT UND [...]@GLIBC_2.2.5 (4)
</span></span><span style="display:flex;"><span>...
</span></span><span style="display:flex;"><span>Symbol table &#39;.symtab&#39; contains 13199 entries:
</span></span><span style="display:flex;"><span>   Num: Value Size Type Bind Vis Ndx Name
</span></span><span style="display:flex;"><span>     0: 0000000000000000 0 NOTYPE LOCAL DEFAULT UND
</span></span><span style="display:flex;"><span>     1: 0000000000000000 0 FILE LOCAL DEFAULT ABS go.go
</span></span><span style="display:flex;"><span>     2: 0000000000401000 0 FUNC LOCAL DEFAULT 1 runtime.text
</span></span><span style="display:flex;"><span>     3: 0000000000401000 214 FUNC LOCAL DEFAULT 1 net(.text)
</span></span><span style="display:flex;"><span>     4: 00000000004010e0 214 FUNC LOCAL DEFAULT 1 runtime/cgo(.text)
</span></span><span style="display:flex;"><span>     5: 00000000004011c0 601 FUNC LOCAL DEFAULT 1 runtime/cgo(.text)
</span></span><span style="display:flex;"><span>     6: 0000000000401420 480 FUNC LOCAL DEFAULT 1 runtime/cgo(.text)
</span></span><span style="display:flex;"><span>     7: 0000000000401420 47 FUNC LOCAL HIDDEN 1 threadentry
</span></span><span style="display:flex;"><span>     8: 0000000000401600 70 FUNC LOCAL DEFAULT 1 runtime/cgo(.text)
</span></span><span style="display:flex;"><span>     9: 0000000000401646 5 FUNC LOCAL DEFAULT 1 runtime/cgo(.tex[...]
</span></span><span style="display:flex;"><span>    10: 0000000000401646 5 FUNC LOCAL HIDDEN 1 x_cgo_munmap.cold
</span></span></code></pre></td></tr></table>
</div>
</div><blockquote>
<p>Go has a unique table (of course). It stores its symbols in a section called <a href="https://pkg.go.dev/debug/gosym#LineTable"><code>.gopclntab</code></a>. This is a table of functions, line numbers and addresses.
Go does this because it needs to be able to render human-readable stack traces when a panic occurs in runtime;</p>
</blockquote>
<p>Note that addresses in the symbol table do not move during execution so that they can be read any time during the execution of the program.
They can easily be loaded into memory independent of the running program and an observer can easily read them.</p>
<p>We assumed that the binary file is a statically linked executable until this point. However, this might not be the case. The binary file might be dynamically linked to other libraries.
From now on, we will refer to these shared library files and executables (both in ELF format) as <a href="https://en.wikipedia.org/wiki/Object_file">object files</a>. Each object file can have its own symbol table.</p>
<p>We need to note that when we take a snapshot of the stack (a.k.a stack trace), it could include addresses from linked shared libraries and Kernel functions.</p>
<blockquote>
<p>Kernel-level software differs as it has its own dynamic symbol table in <code>/proc/kallsyms</code>, which is a file that contains all the symbols that are used in the kernel. And it can grow as the kernel modules are loaded.</p>
</blockquote>
<p>We can read the object files by using binary utilities such as <a href="https://en.wikipedia.org/wiki/Objdump">objdump</a>, <a href="https://en.wikipedia.org/wiki/Readelf">readelf</a> and <a href="https://en.wikipedia.org/wiki/Nm_(Unix)">nm</a>.</p>
<p>To read the <code>.symtab</code>:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>nm $FILE
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or</span>
</span></span><span style="display:flex;"><span>objdump --syms $FILE
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or</span>
</span></span><span style="display:flex;"><span>readelf -a $FILE
</span></span></code></pre></td></tr></table>
</div>
</div><p>To read the <code>.dynsym</code>:</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-shell" data-lang="shell"><span style="display:flex;"><span>nm -D $FILE
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or</span>
</span></span><span style="display:flex;"><span>objdump --dynamic-syms $FILE
</span></span><span style="display:flex;"><span><span style="color:#75715e"># or</span>
</span></span><span style="display:flex;"><span>readelf -a $FILE
</span></span></code></pre></td></tr></table>
</div>
</div><p>For the compiled languages, the symbol table is not the only source of symbols. There are also DWARFs!</p>
<h4 id="debuginfo">Debuginfo</h4>
<blockquote>
<p>ELFs and DWARFs, welcome to fairyland.</p>
</blockquote>
<p>Another way to obtain the symbols from an object file is to use the debug information or <code>debuginfo</code> in short.
Same as the symbol table, this information can be compiled in the binary file, formatted in the <a href="https://en.wikipedia.org/wiki/DWARF">DWARF(Debugging With Attributed Record Formats)</a> or in a separate file.</p>
<p>DWARF is the debug information format most commonly used with ELF. It’s not necessarily tied to ELF, but the two were developed in tandem and work very well together.
This information is split across different ELF sections (<code>.debug_*</code> and <code>.zdebug_*</code> for compressed ones), each with its own piece of information to relay.
For our specific needs, we need to use the <code>.debug_info</code> section to find corresponding functions and <code>.debug_line</code> section to corresponding line numbers.</p>
<p>Debuginfo files for software packages are available through package managers in Linux distributions.
Usually for an available package called <code>mypackage</code> there exists a <code>mypackage-dbgsym</code>, <code>mypackage-dbg</code> or <code>mypackage-debuginfo</code> package.
There are also <a href="https://sourceware.org/elfutils/Debuginfod.html">public servers</a> that serve debug information.</p>
<h4 id="one-program-to-bring-them-all-and-in-the-darkness-bind-them-addr2line">One Program to bring them all, and in the darkness bind them: <code>addr2line</code></h4>
<blockquote>
<p>Wait, what?! Isn&rsquo;t that from another fantasy book?</p>
</blockquote>
<p>Now that we have the symbol table or debug information, we can use <code>addr2line</code> (<em>address to line</em>) to get the source code location of a given address.
<a href="https://linux.die.net/man/1/addr2line"><code>addr2line</code></a> converts addresses back to function and line numbers.</p>
<p>Let&rsquo;s see it in action <code>addr2line -a 0x0000000000001154 -e &lt;objectFile&gt;</code>:</p>
<p><em>For addr2line <code>&lt;objectFile&gt;</code> can be any object file compiled with debug information or symbols. It can be an executable, a shared library or output of a strip operation.</em></p>
<p>Voilà!</p>
<div class="highlight"><div style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;">
<table style="border-spacing:0;padding:0;margin:0;border:0;"><tr><td style="vertical-align:top;padding:0;margin:0;border:0;">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
</span><span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
</span></code></pre></td>
<td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
<pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>0x0000000000001154
</span></span><span style="display:flex;"><span>main
</span></span><span style="display:flex;"><span>/home/newt/Sandbox/hello-c/hello.c:14
</span></span></code></pre></td></tr></table>
</div>
</div><p>I used a simple C executable for this example. And we have got our symbol and attached source information for the corresponding address 🎉</p>
<p><img src="https://www.polarsignals.com/blog/posts/2022/01/success_kid.jpg"
    alt="Success"
    loading="lazy"
    decoding="async"></p>
<p>I only wish we had compiled programming language implementations out there, then our job here could have been finished. But we are not. We need to keep digging.
But for that, you need to wait for another week. As we hinted at in the title of this post, there will be a part 2! All the best franchises are sequels, right?!
In part 2, we will see how interpreted languages and <a href="https://en.wikipedia.org/wiki/Just-in-time_compilation">Just-In-Time</a> compiled languages handle symbols.</p>
<p><em>Please stay tuned!</em></p>
<h2 id="dont-worry-we-got-you-covered">Don&rsquo;t worry we got you covered</h2>
<p>Even though we simplified things a bit here, if you want to write a program to utilize symbolization, you still have a lot of work to do.
Many open-source tools out there already handle nitty-gritty details of symbolization, like <a href="https://www.brendangregg.com/perf.html"><code>perf</code></a>.</p>
<p>The good news is we got you covered. If you are using <a href="https://github.com/parca-dev/parca-agent">Parca Agent</a>, we already do <a href="https://www.parca.dev/docs/symbolization">the heavy lifting</a> for you to symbolize captured stack traces.
And we keep extending our support for the different languages and runtimes.</p>
<p>Check <a href="https://www.parca.dev/">Parca</a> out and let us know what you think, on <a href="https://discord.gg/ZgUpYgpzXy">Discord</a> channel.</p>
<h3 id="further-reading">Further reading</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Debug_symbol">https://en.wikipedia.org/wiki/Debug_symbol</a></li>
<li><a href="https://www.brendangregg.com/bpf-performance-tools-book.html">https://www.brendangregg.com/bpf-performance-tools-book.html</a></li>
<li><a href="https://github.com/DataDog/go-profiler-notes/blob/main/stack-traces.md">https://github.com/DataDog/go-profiler-notes/blob/main/stack-traces.md</a></li>
<li><a href="https://www.brendangregg.com/perf.html">https://www.brendangregg.com/perf.html</a></li>
<li><a href="https://jvns.ca/blog/2018/01/09/resolving-symbol-addresses/">https://jvns.ca/blog/2018/01/09/resolving-symbol-addresses/</a></li>
</ul>
<h3 id="sources">Sources</h3>
<ul>
<li><a href="https://en.wikipedia.org/wiki/File:Call_stack_layout.svg">Call Stack Layout</a></li>
<li><a href="https://github.com/corkami/pics/blob/28cb0226093ed57b348723bc473cea0162dad366/binary/elf101/elf101-64.svg">ELF Executable and Linkable Format diagram by Ange Albertini</a></li>
</ul>
]]></content:encoded></item><item><title>talk: Parca - Profiling in the Cloud-Native Era</title><link>https://kakkoyun.me/talks/parca-profiling-in-the-cloud-native-era/</link><pubDate>Sat, 25 Sep 2021 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/parca-profiling-in-the-cloud-native-era/</guid><description>Introduction to continuous profiling with Parca, demonstrating how systematic profile collection enables fleet-wide code understanding at runtime and systematic cloud cost reduction.</description><content:encoded><![CDATA[<p>For years Google has consistently been able to cut down multiple percentage points in their fleet-wide resource usage every quarter, using techniques described in their “Google-Wide Profiling” paper. Ad-hoc profiling has long been part of the developer’s toolbox to analyze CPU and memory usage of a running process, however, through continuous profiling, the systematic collection of profiles, entirely new workflows suddenly become possible. Matthias and Kemal will start this talk with an introduction to profiling with Go and demonstrate via Conprof - an open-source continuous profiling project - how continuous profiling allows for an unprecedented fleet-wide understanding of code at runtime. Attendees will learn how to continuously profile Go code to help guide building robust, reliable, and performant software and reduce cloud spend systematically.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/ficc6_6RYQk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://docs.google.com/presentation/d/1cPdcLLSc_OzlLOnh1vuUaTuVOFjuJ7-NFbC599Pll2I/edit?usp=sharing">Parca - Profiling in the Cloud-Native Era</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://sched.co/zrPZ">KubeCon NA 2021</a>
<ul>
<li><a href="https://youtu.be/ficc6_6RYQk">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>talk: Profiling Go Applications in the Cloud-Native Era</title><link>https://kakkoyun.me/talks/profiling-go-applications-in-the-cloud-native-era/</link><pubDate>Tue, 20 Apr 2021 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/profiling-go-applications-in-the-cloud-native-era/</guid><description>Introduction to continuous profiling for Go applications using open-source tools, demonstrating how systematic profile collection enables fleet-wide code understanding and cloud cost reduction.</description><content:encoded><![CDATA[<p>For years Google has consistently been able to cut down multiple percentage points in their fleet-wide resource usage every quarter, using techniques described in their “Google-Wide Profiling” paper. Ad-hoc profiling has long been part of the developer’s toolbox to analyze the CPU and memory usage of a running process. However, through continuous profiling, and the systematic collection of profiles, entirely new workflows suddenly become possible.</p>
<p>The presenter will start this talk with an introduction to profiling applications, and demonstrate how one can practice it using open-source continuous profiling tools, and how continuous profiling allows for an unprecedented fleet-wide understanding of code at production runtime.</p>
<p>Attendees will learn how to continuously profile their code, guide themselves in building robust, reliable, and performant software and reduce cloud spending systematically.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/-miC_jnQ_Yk" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://docs.google.com/presentation/d/1uue-Mpyw5zSuWfe1qphyhBtrCX4TmBWhN3iMcdYlnek/edit?usp=sharing">Profiling Go Applications in the Cloud-Native Era</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://gophercon.ist/#schedule">GopherCon Turkey 2021</a>
<ul>
<li><a href="https://youtu.be/-miC_jnQ_Yk">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>talk: Building Observable Go Services</title><link>https://kakkoyun.me/talks/building-observable-go-services/</link><pubDate>Tue, 01 Dec 2020 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/building-observable-go-services/</guid><description>Comprehensive guide to the four pillars of observability (logs, metrics, traces, profiling) in Go services, using CNCF tools like Prometheus, Loki, OpenTelemetry, Jaeger, and Conprof.</description><content:encoded><![CDATA[<p>In modern days, we run our applications as loosely coupled micro-services on distributed, elastic infrastructure as (mostly) stateless workloads. Under these circumstances, observability has become a key attribute to understand how our applications run and behave in action, in order to provide highly available and resilient service.</p>
<p>There exist several observability signals, such as “log”, “metric”, “tracing” and “profiling” that can be collected from a running service, which we can also call pillars of observability. Using these signals, we can create real-time, actionable alerts, create panels where we can monitor applications closely, and perform in-depth analysis to find the root of the systems’ failures. Within the Go and CNCF ecosystem, there are a variety of tools that can collect and make these observable signals useful.</p>
<p>During this talk, Kemal will first introduce the tools that can be embedded in the services to make critical services observable, and share the patterns that will enable them to be used efficiently in the applications and services. Moreover,  he will demonstrate how to use these collected signals in real-life scenarios, using tools within the CNCF ecosystem (Loki, Prometheus, OpenTelemetry, Jaeger, Conprof). He also aims to share the methods that are used to build and run applications running under heavy-traffic, and to understand the origin of the problems encountered in running systems.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/xkLyM1Gnaus" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://github.com/kakkoyun/building-observable-go-services">Building Observable Go Services</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://gophercon.ist/en">GopherCon Turkey 2020</a>
<ul>
<li><a href="https://www.youtube.com/watch?v=xkLyM1Gnaus">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>talk: Absorbing Thanos Infinite Powers for Multi-Cluster Telemetry</title><link>https://kakkoyun.me/talks/absorbing-thanos-infinite-powers/</link><pubDate>Tue, 10 Nov 2020 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/absorbing-thanos-infinite-powers/</guid><description>Introduction to Thanos, the CNCF project that horizontally scales Prometheus for global-scale highly available monitoring across multiple clusters and clouds with low maintenance cost.</description><content:encoded><![CDATA[<p>Thanos is an open-source, CNCF’s Incubated project that horizontally scales Prometheus to create a global-scale highly available monitoring system. It seamlessly extends Prometheus in a few simple steps and it is already used in production by hundreds of companies that aim for high multi-cloud scale for metrics while keeping low maintenance cost. During this talk, core Thanos (and Prometheus) maintainers, will briefly introduce basic ideas behind Thanos and deployment models and use cases. After that, to satisfy more experienced users, they will explain more advanced concepts, tips for running on the scale, and the latest shiny usability improvements. Thanks to the growing community there is much to talk about!</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/6Nx2BFyr7qQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://docs.google.com/presentation/d/1gMBQ7wLqAae45uGOcaYex-_9s675yzgexW705D7KM1Y/edit#slide=id.ga47ea1e9a6_0_13">Absorbing Thanos Infinite Powers for Multi-Cluster Telemetry</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://sched.co/zrPZ">KubeConNA 2020</a>
<ul>
<li><a href="https://youtu.be/6Nx2BFyr7qQ">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>talk: The Zen of Prometheus</title><link>https://kakkoyun.me/talks/the-zen-of-prometheus/</link><pubDate>Sun, 20 Sep 2020 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/the-zen-of-prometheus/</guid><description>Best practices, patterns, and common pitfalls for instrumenting cloud-native applications with Prometheus metrics, covering instrumentation, alerting, and monitoring strategies.</description><content:encoded><![CDATA[<p>Live Website: <a href="https://the-zen-of-prometheus.netlify.app/">The Zen of Prometheus</a></p>
<p>In modern days, we run our applications as loosely coupled microservices on distributed, elastic infrastructure as (mostly) stateless workloads. Under these circumstances, observability is the key to understanding how our applications run and behave in action to deliver highly available and resilient service.</p>
<p>Prometheus is born in such an atmosphere as a solution to satisfy the observability needs of the cloud-native era. Among many other observability signals like logs and traces, metrics play the most substantial role. Sampled measurements observed throughout the system are crucial for monitoring the health of the applications and they enable real-time, actionable alerting. Although the tools in the Prometheus ecosystem make life a lot easier, there are still numerous possibilities to make mistakes or misuse them.</p>
<p>During this talk, Kemal will present several valuable patterns, best practices and idiomatic methods for instrumenting critical services. He will discuss common pitfalls and failure cases while sharing useful insights and methods to avoid those mistakes. Last but not least, he will give tips for writing simple, maintainable and robust alerts that derived from real-life experiences. By doing so he will propose “The Zen of Prometheus”.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/Nqp4fjw_omU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://github.com/kakkoyun/the-zen-of-prometheus">The Zen of Prometheus</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://promcon.io/2020-online/">PromCon Online 2020</a>
<ul>
<li><a href="https://www.youtube.com/watch?v=Nqp4fjw_omU">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>talk: Are you testing your observability?</title><link>https://kakkoyun.me/talks/are-you-testing-your-observability/</link><pubDate>Sat, 15 Feb 2020 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/talks/are-you-testing-your-observability/</guid><description>Best practices and patterns for instrumenting Go applications with metrics, including how to use unit tests to verify the correctness of your observability signals and avoid common pitfalls.</description><content:encoded><![CDATA[<p>Observability is the key to understand how your application runs and behaves in action. This is especially vital for distributed environments like Kubernetes, where users run Cloud-Native microservices often written in Go.</p>
<p>Among many other observability signals like logs and traces, the metrics signal has a substantial role. Sampled measurements observed throughout the system are crucial for monitoring the health of the applications and, they enable real-time, actionable alerting. While there are many open-source robust libraries, in various languages, that allow us to easily instrument services for backends like Prometheus, there are still numerous possibilities to make a mistake or misuse those libraries.</p>
<p>During this talk, we discuss valuable patterns and best practices for instrumenting your Go application. The speakers will go through common pitfalls and failure cases while sharing valuable insights and methods to avoid those mistakes. Also, this talk demonstrates, how to leverage Go unit testing to verify the correctness of your observability signals. How it helps and why it is important. Last but not least, the talk covers a demo of the example instrumented Go application based on the experience and projects we maintain.</p>
<h4 id="recording">Recording</h4>
<iframe width="560" height="315" src="https://www.youtube.com/embed/LU6D5cNeHks" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
<p><strong>Slides</strong></p>
<ul>
<li><a href="https://github.com/kakkoyun/are-you-testing-your-observability">Are You Testing Your Observability?</a></li>
</ul>
<p><strong>Events</strong></p>
<ul>
<li><a href="https://www.godays.io/conferenceday1">GoDays Berlin 2020</a>
<ul>
<li><a href="https://youtu.be/LU6D5cNeHks">Recording</a></li>
</ul>
</li>
<li><a href="https://fosdem.org/2020/schedule/event/testing_observability/">FOSDEM 2020</a>
<ul>
<li><a href="https://www.youtube.com/watch?v=-jF4nWfrY3w">Recording</a></li>
</ul>
</li>
</ul>
]]></content:encoded></item><item><title>Making Drone Builds 10 Times Faster!</title><link>https://kakkoyun.me/posts/making-drone-builds-10-times-faster/</link><pubDate>Wed, 10 Apr 2019 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/posts/making-drone-builds-10-times-faster/</guid><description>How we built and open-sourced drone-cache, a Drone CI plugin for caching dependencies and interim files between builds to dramatically reduce build times.</description><content:encoded><![CDATA[<p>We open sourced <a href="https://github.com/meltwater/drone-cache">drone-cache</a>, a plugin for the popular Continuous Delivery platform <a href="https://drone.io/">Drone</a>. It allows you to cache dependencies and interim files between builds to reduce your build times. This post explains why we are using Drone, why we needed a cache plugin, and what I learned while trying to release drone-cache as open source software.</p>
<p>Read on for the story behind drone-cache or if you want to jump into action directly, go to the <a href="https://github.com/meltwater/drone-cache">github.com/meltwater/drone-cache</a>, and try it for yourself.</p>
<p><em>Originally published at</em> <a href="https://underthehood.meltwater.com/blog/2019/04/10/making-drone-builds-10-times-faster/"><em>underthehood.meltwater.com</em></a> <em>on April 10, 2019.</em></p>
<p><img src="https://raw.githubusercontent.com/meltwater/drone-cache/master/images/drone_gopher.png"
    alt="Drone Cache Logo"
    loading="lazy"
    decoding="async"></p>
<h2 id="why-are-we-using-drone">Why are we using Drone?</h2>
<p>At Meltwater, we empower self-sufficient teams. Teams are free to choose their technology stacks. As a result, we have a diverse set of tools in our stack. In my team, we had been using a combination of <a href="https://travis-ci.com/">TravisCI</a>, <a href="https://circleci.com/">CircleCI</a> and <a href="https://jenkins.io/">Jenkins</a> as our <a href="https://en.wikipedia.org/wiki/CI/CD">CI/CD pipeline</a>.</p>
<p>In 2018, we decided to migrate to <a href="https://kubernetes.io/">Kubernetes</a>. In doing so, we wanted to simplify our toolchain and migrate to a more flexible, <a href="https://github.com/cncf/toc/blob/master/DEFINITION.md">cloud-native</a> and on-premise CI/CD pipeline solution. We ended up choosing <a href="https://drone.io/">Drone</a>, and with one year of experience under our belt, we are more than happy with it.</p>
<h2 id="how-we-made-the-builds-faster">How we made the builds faster</h2>
<p>My team lives and breaths the &ldquo;<a href="https://en.wikipedia.org/wiki/Release_early,_release_often">release early, release often</a>&rdquo; philosophy. We release and deploy our software to production several times a day. When we moved from CircleCI to Drone, our build times went up drastically.</p>
<p>Build times went up so much because, for each build, our package manager was downloading the Internet (you know, usual suspects are <a href="https://www.npmjs.com/">npm</a>, <a href="https://rubygems.org/">RubyGems</a>, etc.). This was not a problem with CircleCI because of their built-in caching facilities. So with our pace of continuous releases and increased build times, we got frustrated quickly.</p>
<p><img src="https://raw.githubusercontent.com/meltwater/drone-cache/master/images/diagram.png"
    alt="How does it work?"
    loading="lazy"
    decoding="async"></p>
<p>Since we had been spoiled with the wonderful caching features of CircleCI, we wanted the same features in Drone but they are not available by default. However, Drone offers <a href="http://plugins.drone.io/">plugins</a> which are &ldquo;special Docker containers used to drop preconfigured tasks into a Pipeline&rdquo;. We found tens of plugins related to caching in Drone.</p>
<p>We first tried <a href="https://github.com/Drillster/drone-volume-cache">drone-volume-cache</a>, but because volumes are local to the currently running Drone worker node, you cannot be sure your that next build will run on the same machine. Using a storage layer that could persist the cache between builds would be a better option. So we quickly abandoned this approach.</p>
<p>Our Drone deployment runs on <a href="https://aws.amazon.com/">AWS</a>, hence we looked for plugins that use <a href="https://aws.amazon.com/s3/">S3</a> as their storage. We found lots of them and decided to use <a href="https://github.com/bsm/drone-s3-cache">drone-s3-cache</a>. It’s a well-written, simple Go program which follows the Drone <a href="https://github.com/drone/drone-plugin-starter">plugin starter</a> conventions.</p>
<h2 id="why-did-we-decide-to-build-our-own-caching-plugin">Why did we decide to build our own caching plugin?</h2>
<p>After using <em>drone-s3-cache</em> for a couple of weeks, we needed to add another parameter to pass to S3. To do so we forked <a href="https://github.com/bsm/drone-s3-cache">drone-s3-cache</a> and modified it. We thought that nobody would need those minor changes. So rather than contributing back to upstream, we built a docker image of our own and pushed it to our private registry to use as a custom Drone plugin.</p>
<p><img src="https://raw.githubusercontent.com/meltwater/drone-cache/master/images/slack_comment.png"
    alt="Feature request for drone-cache from a colleague"
    loading="lazy"
    decoding="async"></p>
<p>Months later, I have received a feature request from one of my colleagues working in a different team, and I was surprised because I didn’t think other teams used drone-cache. When I checked, I realised that various teams throughout Meltwater heavily used it. Then we started to get similar messages and requests from other teams.</p>
<p>I received this message when I was looking for a problem to solve during our <a href="https://underthehood.meltwater.com/blog/2014/08/18/meltwhatever-innovation-day-at-meltwater/">internal</a> <a href="https://en.wikipedia.org/wiki/Hackathon">Hackathon</a>. What are the chances? So I decided to work on this plugin and add the requested feature. Building something to make life easy for fellow developers always gives me pure joy. Long story short, stars were aligned, and we decided to work on our fork and improve it.</p>
<p>I had not worked with Go much, but I always wanted to learn. Thanks to this plugin, I have also achieved this goal of mine. I changed, refactored and churned a lot of code. I experimented with a lot of different ideas. I have added features that nobody has asked for. I tried different things just for the sake of trying. That’s why when I decided to open source my changes, I realised I had re-written the plugin. So rather than sending a pull-request, I created a new repository. <a href="https://github.com/meltwater/drone-cache">drone-cache</a> has born!</p>
<p><img src="https://imgs.xkcd.com/comics/standards.png"
    alt="Standards"
    loading="lazy"
    decoding="async"></p>
<h2 id="how-does-it-work">How does it work?</h2>
<p>What does a Drone cache plugin actually have to accomplish? In Drone, each step in the build pipeline is a container which is thrown away after it serves its purpose. So a caching system has to persist current workspace files between builds. You can think of <a href="https://docs.drone.io/user-guide/pipeline/steps">workspace</a> as the root of your git repository. It is a mounted volume shared by all steps in your Drone build pipeline.</p>
<p>With drone-cache, after your initial pipeline run, a snapshot of your current workspace will be stored. Then you can restore that snapshot in your next build, which saves you time.</p>
<p>The best example would be to use this plugin with your package managers such as <a href="https://www.npmjs.com/">npm</a>, <a href="https://elixir-lang.org/getting-started/mix-otp/introduction-to-mix.html">Mix</a>, <a href="https://bundler.io/">Bundler</a> or <a href="https://maven.apache.org/">Maven</a>. With restored dependencies from a cache, commands such as <em>npm install</em> would only need to download new dependencies, rather than re-download every package on each build.</p>
<h2 id="what-makes-drone-cache-different-from-other-drone-caching-solutions">What makes drone-cache different from other Drone caching solutions?</h2>
<p>The most useful feature of drone-cache is that you can provide <a href="https://github.com/meltwater/drone-cache/blob/master/docs/cache_key_templates.md">your own custom cache key templates</a>. This means you can store your cached files under keys which prescribes your use cases. For example, with a custom key generated from a checksum of a file (say <em>package.json</em>), you keep your cached files until you actually touch that file again.</p>
<p>All other caching solutions for drone offer only a single storage form for your cache. drone-cache in contrast offers 2 storage forms out of the box: an <strong>S3 bucket</strong> or a <strong>mounted volume</strong>. Even better, drone-cache provides a pluggable backend system, so you can implement your own storage backend.</p>
<p>Last but not least, drone-cache is a small CLI program, written in Go without any external OS dependencies. So even if you are not using Drone as your build system, you can still fork and tinker with drone-cache to make it fit your needs.</p>
<h2 id="what-we-have-learned">What we have learned?</h2>
<p>Building a caching solution is hard. Especially, if every team in your company uses it every time they push something to their repositories. It is also fun because it means you have users who give you feedback from the beginning. With the help of my colleagues&rsquo; feedback and feature requests, we have crafted this plugin.</p>
<blockquote>
<p>There are only two hard things in Computer Science: cache invalidation and naming things.
&ndash; Phil Karlton</p>
</blockquote>
<p>What could we have done better? As I have mentioned before, rather than forking and modifying a new code base, we could have contributed back to the original project. We could have applied &ldquo;release early and often&rdquo; philosophy to open sourcing this repository, and we would have collected feedback from the outside world as well. However we didn’t, that’s mostly on me. This is the first time I actually open sourced a project and contributed back to the community. So next time I will know better :)</p>
<p>In Meltwater we are using <em>drone-cache</em> in 20 teams and 120 components now. It works and gets things done for us. We have learned a lot while we build it. We hope this also solves similar problems of yours.</p>
<p>Please <a href="https://github.com/meltwater/drone-cache">try it</a> in your pipeline, give us feedback, feel free to open issues and send us pull-requests. Personally, I am also very interested to discuss your experiences with open sourcing in general, so if you have any thoughts on that, please share them in the comments below.</p>
<p><strong>Image Credits</strong></p>
<p>xkcd.com - How standards proliferate <a href="https://xkcd.com/927/">https://xkcd.com/927/</a></p>
<p><em>Originally published at</em> <a href="https://underthehood.meltwater.com/blog/2019/04/10/making-drone-builds-10-times-faster/"><em>underthehood.meltwater.com</em></a> <em>on April 10, 2019.</em></p>
]]></content:encoded></item><item><title>Misc</title><link>https://kakkoyun.me/misc/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/misc/</guid><description>&lt;p&gt;Sometimes I write something that&amp;rsquo;s not a blog post. Or create a page that&amp;rsquo;s not a blog post. You can find that here.&lt;/p&gt;
&lt;h2 id="pages"&gt;Pages&lt;/h2&gt;
&lt;h3 id="open-source"&gt;&lt;a href="https://kakkoyun.me/open_source/"&gt;Open Source&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Projects I maintain or contribute to across the CNCF ecosystem—Prometheus, OpenTelemetry, Datadog instrumentation, and more.&lt;/p&gt;
&lt;h3 id="sponsorship"&gt;&lt;a href="https://kakkoyun.me/sponsorship/"&gt;Sponsorship&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Support my work on open-source observability, instrumentation, and performance tooling. Multiple options available.&lt;/p&gt;
&lt;h3 id="uses"&gt;&lt;a href="https://kakkoyun.me/uses/"&gt;Uses&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Hardware, software, and tools I use daily. Updated occasionally.&lt;/p&gt;
&lt;h3 id="stats"&gt;&lt;a href="https://kakkoyun.me/stats/"&gt;Stats&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;It is an occupatioanl hazard (working on observability) to know how many hours do you spend while doing anything. In this case it is programming.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Sometimes I write something that&rsquo;s not a blog post. Or create a page that&rsquo;s not a blog post. You can find that here.</p>
<h2 id="pages">Pages</h2>
<h3 id="open-source"><a href="/open_source/">Open Source</a></h3>
<p>Projects I maintain or contribute to across the CNCF ecosystem—Prometheus, OpenTelemetry, Datadog instrumentation, and more.</p>
<h3 id="sponsorship"><a href="/sponsorship/">Sponsorship</a></h3>
<p>Support my work on open-source observability, instrumentation, and performance tooling. Multiple options available.</p>
<h3 id="uses"><a href="/uses/">Uses</a></h3>
<p>Hardware, software, and tools I use daily. Updated occasionally.</p>
<h3 id="stats"><a href="/stats/">Stats</a></h3>
<p>It is an occupatioanl hazard (working on observability) to know how many hours do you spend while doing anything. In this case it is programming.</p>
<h3 id="newsletter"><a href="/newsletter/">Newsletter</a></h3>
<p>Occasional notes on observability, instrumentation, profiling, and performance engineering. No spam.</p>
<h3 id="blogroll"><a href="/blogroll/">Blogroll</a></h3>
<p>Blogs and people I follow regularly. If you like my posts, you&rsquo;ll probably enjoy these.</p>
<h3 id="reading"><a href="/reading/">Reading</a></h3>
<p>Books I&rsquo;m currently reading and have read. Synced with my Goodreads shelf.</p>
<h3 id="archives"><a href="/archives/">Archives</a></h3>
<p>All posts organized by date.</p>
<h2 id="contact">Contact</h2>
<p>You can find me across various platforms under the handle <strong>kakkoyun</strong>, except on Twitter/X: <strong>kakkoyun_me</strong>.</p>
<p>If you wish to reach out and chat, you may email me: <strong>kakkoyun<code>_at_</code>gmail<code>_dot_</code>com</strong></p>
]]></content:encoded></item><item><title>Reading</title><link>https://kakkoyun.me/reading/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/reading/</guid><description>&lt;p&gt;Books I&amp;rsquo;m currently reading and have read. For a more comprehensive list, check my &lt;a href="https://www.goodreads.com/kakkoyun"&gt;Goodreads profile&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="currently-reading"&gt;Currently Reading&lt;/h2&gt;
&lt;p&gt;See my &lt;a href="https://www.goodreads.com/review/list/28937882-kemal?shelf=currently-reading"&gt;currently-reading shelf on Goodreads&lt;/a&gt; for the most up-to-date list.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="recent-reads"&gt;Recent Reads&lt;/h2&gt;
&lt;p&gt;This section will be populated with books as I finish them. For now, the full archive lives on Goodreads.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="reading-preferences"&gt;Reading Preferences&lt;/h2&gt;
&lt;p&gt;I tend to gravitate toward:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Systems programming and performance engineering&lt;/li&gt;
&lt;li&gt;Science fiction and fantasy&lt;/li&gt;
&lt;li&gt;Technical deep-dives and research papers&lt;/li&gt;
&lt;li&gt;Philosophy and human behavior&lt;/li&gt;
&lt;li&gt;Distributed systems and databases&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I track everything on &lt;a href="https://www.goodreads.com/kakkoyun"&gt;Goodreads&lt;/a&gt; and occasionally share notes on interesting reads in the &lt;a href="https://kakkoyun.me/notes/"&gt;Notes&lt;/a&gt; section of this site.&lt;/p&gt;</description><content:encoded><![CDATA[<p>Books I&rsquo;m currently reading and have read. For a more comprehensive list, check my <a href="https://www.goodreads.com/kakkoyun">Goodreads profile</a>.</p>
<h2 id="currently-reading">Currently Reading</h2>
<p>See my <a href="https://www.goodreads.com/review/list/28937882-kemal?shelf=currently-reading">currently-reading shelf on Goodreads</a> for the most up-to-date list.</p>
<hr>
<h2 id="recent-reads">Recent Reads</h2>
<p>This section will be populated with books as I finish them. For now, the full archive lives on Goodreads.</p>
<hr>
<h2 id="reading-preferences">Reading Preferences</h2>
<p>I tend to gravitate toward:</p>
<ul>
<li>Systems programming and performance engineering</li>
<li>Science fiction and fantasy</li>
<li>Technical deep-dives and research papers</li>
<li>Philosophy and human behavior</li>
<li>Distributed systems and databases</li>
</ul>
<p>I track everything on <a href="https://www.goodreads.com/kakkoyun">Goodreads</a> and occasionally share notes on interesting reads in the <a href="/notes/">Notes</a> section of this site.</p>
<p>If you have recommendations, especially in systems engineering or sci-fi, <a href="/misc/">reach out</a>!</p>
]]></content:encoded></item><item><title>Stats</title><link>https://kakkoyun.me/stats/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><author>kakkoyun@gmail.com (Kemal Akkoyun)</author><guid>https://kakkoyun.me/stats/</guid><description>&lt;p&gt;It is an occupatioanl hazard (working on observability) to know how many hours do you spend while doing anything. In this case it is programming.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://wakatime.com/share/@kemal/4ab9252a-ab99-400b-ae97-ab2a29c1a35a.svg"
alt="Since start tracking"
loading="lazy"
decoding="async"&gt;
&lt;img src="https://wakatime.com/share/@kemal/863d64ce-fadb-4599-a52c-a43763aefeda.svg"
alt="Last year"
loading="lazy"
decoding="async"&gt;
&lt;img src="https://wakatime.com/share/@kemal/edc85219-3089-42d0-a35a-67958da4d21f.svg"
alt="Last 30 days"
loading="lazy"
decoding="async"&gt;&lt;/p&gt;</description><content:encoded><![CDATA[<p>It is an occupatioanl hazard (working on observability) to know how many hours do you spend while doing anything. In this case it is programming.</p>
<p><img src="https://wakatime.com/share/@kemal/4ab9252a-ab99-400b-ae97-ab2a29c1a35a.svg"
    alt="Since start tracking"
    loading="lazy"
    decoding="async">
<img src="https://wakatime.com/share/@kemal/863d64ce-fadb-4599-a52c-a43763aefeda.svg"
    alt="Last year"
    loading="lazy"
    decoding="async">
<img src="https://wakatime.com/share/@kemal/edc85219-3089-42d0-a35a-67958da4d21f.svg"
    alt="Last 30 days"
    loading="lazy"
    decoding="async"></p>
]]></content:encoded></item></channel></rss>