<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet href="/feed.css?v=208f42b55cff" type="text/css"?><feed xmlns="http://www.w3.org/2005/Atom" xmlns:site="https://lalitm.com/feed/ns#"><title>Lalit Maganti (Atom, Tag: Perfetto)</title><subtitle>Recent content tagged Perfetto on Lalit Maganti</subtitle><site:notice>This is a feed.
Feeds let you subscribe to updates from this site using a feed reader. Copy this page's URL from your address bar and paste it into your reader.
New to feeds? Read: https://aboutfeeds.com</site:notice><link href="https://lalitm.com/tags/perfetto/atom.xml" rel="self" type="application/atom+xml"/><link href="https://lalitm.com/" rel="alternate" type="text/html"/><link href="https://aboutfeeds.com" rel="help" type="text/html"/><id>https://lalitm.com/tags/perfetto/</id><updated>2026-07-17T05:47:11Z</updated><author><name>Lalit Maganti</name></author><generator>Hugo -- gohugo.io</generator><entry><title>Perfetto v57: fixing PyTorch traces, plus journald logs and an AI skill</title><link href="https://lalitm.com/post/perfetto-v57/" rel="alternate" type="text/html"/><id>https://lalitm.com/post/perfetto-v57/</id><published>2026-07-03T00:23:00+01:00</published><updated>2026-07-03T00:23:00+01:00</updated><summary type="text">We just released Perfetto v57 and I wanted to share the new things I’m most excited about. This is something I wanted to do for past releases but I just never quite got round to it. It’s also something I plan on doing more of going forward: there might even be dedicated pieces if I think the feature deserves it!
PyTorch traces reliably work! What I’m most excited about in this release isn’t a feature but a bugfix. If you used the PyTorch profiler and opened the resulting trace in Perfetto, there was a decent chance some of your events would just not show up; specifically this would happen when these events overlapped each other on a single track.
Technically, PyTorch is in the wrong here. The Chrome Trace Event (JSON) format says duration events on a track have to nest and can’t overlap; if you need overlap, you’re supposed to use async events. chrome://tracing appears to handle them, but its rendering is actually buggy as soon as a trace has real overlaps; people just learned to live with it. So when a bug came in January about overlapping events being broken, I closed it as working-as-intended because I couldn’t see any easy fix from our end.</summary><content type="html">&lt;p&gt;We just
&lt;a href="https://github.com/google/perfetto/releases/tag/v57.1"&gt;released Perfetto v57&lt;/a&gt;
and I wanted to share the new things I&amp;rsquo;m most excited about. This is something I
wanted to do for past releases but I just never quite got round to it. It&amp;rsquo;s also
something I plan on doing more of going forward: there might even be dedicated
pieces if I think the feature deserves it!&lt;/p&gt;
&lt;h2 id="pytorch-traces-reliably-work"&gt;PyTorch traces reliably work!&lt;/h2&gt;
&lt;p&gt;What I&amp;rsquo;m most excited about in this release isn&amp;rsquo;t a feature but a bugfix. If you
used the
&lt;a href="https://docs.pytorch.org/tutorials/recipes/recipes/profiler_recipe.html"&gt;PyTorch profiler&lt;/a&gt;
and opened the resulting trace in Perfetto, there was a decent chance some of
your events would just not show up; specifically this would happen when these
events overlapped each other on a single track.&lt;/p&gt;
&lt;p&gt;Technically, PyTorch is in the wrong here. The Chrome Trace Event (JSON) format
says duration events on a track have to nest and can&amp;rsquo;t overlap; if you need
overlap, you&amp;rsquo;re supposed to use async events. &lt;code&gt;chrome://tracing&lt;/code&gt; appears to
handle them, but its rendering is actually buggy as soon as a trace has real
overlaps; people just learned to live with it. So when a
&lt;a href="https://github.com/google/perfetto/issues/4280"&gt;bug&lt;/a&gt; came in January about
overlapping events being broken, I closed it as working-as-intended because I
couldn&amp;rsquo;t see any easy fix from our end.&lt;/p&gt;
&lt;p&gt;Then over time, I realized it wasn&amp;rsquo;t just one person with a weird trace. The bug
had affected many different PyTorch traces, and even
&lt;a href="https://github.com/google/perfetto/issues/6277"&gt;JAX traces&lt;/a&gt;. I even discovered
that people were maintaining forks and hosting old versions of Perfetto just to
deal with this issue. This is basically
&lt;a href="https://www.hyrumslaw.com/"&gt;Hyrum&amp;rsquo;s Law&lt;/a&gt;: it doesn&amp;rsquo;t much matter what the spec
says, once enough people rely on some behaviour, that behaviour is your
contract.&lt;/p&gt;
&lt;p&gt;This is also a very similar situation to what I discussed in
&lt;a href="https://lalitm.com/perfetto-oss-company-prio/"&gt;Perfetto and open source&lt;/a&gt;: it
was genuinely hard for me to tell just how many people it was affecting at the
beginning: if I had understood, I would have dealt with this very differently.&lt;/p&gt;
&lt;p&gt;In any case, in v57, Perfetto now imports the overlapping events instead of
dropping them, overflowing the extras onto a different line in the same UI
track. I do have to credit &lt;a href="https://github.com/i-komarov"&gt;@i_komarov&lt;/a&gt; who helped
me understand how big this issue was and to converge on the fix.&lt;/p&gt;
&lt;h2 id="recording-systemd-journald-logs"&gt;Recording systemd journald logs&lt;/h2&gt;
&lt;p&gt;Perfetto has always tried to give you as much context as possible about what the
system is doing and put it all on a single timeline. One missing piece of that
on Linux has been in logs from journald; there&amp;rsquo;s a ton of useful information
there but until now it was hard to correlate that with e.g. scheduling or other
app level information.&lt;/p&gt;
&lt;p&gt;With v57, there&amp;rsquo;s now a &lt;code&gt;linux.systemd_journald&lt;/code&gt; data source that records
journald entries straight into the trace, on the same timeline as everything
else. You can filter by minimum syslog priority, by &lt;code&gt;SYSLOG_IDENTIFIER&lt;/code&gt;, or by
systemd unit, so you are not pulling in the entire journal when you only care
about one service.&lt;/p&gt;
&lt;p&gt;To show it in action, here&amp;rsquo;s
&lt;a href="https://ui.perfetto.dev/#!/?url=https://lalitm.com/traces/journald-demo.pftrace"&gt;a trace I recorded on my own machine&lt;/a&gt;.
Alongside a few log lines I generated myself, it happened to capture a real
udisks SMART error that fired while I was recording; exactly the kind of thing
you&amp;rsquo;d otherwise miss.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/perfetto-v57/journald-logs.png" alt="Journald logs on the Perfetto timeline, with the log panel showing the captured entries including a udisks SMART error"&gt;&lt;/p&gt;
&lt;p&gt;I have to shout out &lt;a href="https://github.com/staticfloat"&gt;@staticfloat&lt;/a&gt; who
contributed this feature; thanks a lot for doing this!&lt;/p&gt;
&lt;h2 id="an-ai-skill-for-recording-and-analysis"&gt;An AI skill for recording and analysis&lt;/h2&gt;
&lt;p&gt;For a while now, people have been building their own AI tooling around Perfetto.
&lt;a href="https://github.com/Gracker/SmartPerfetto"&gt;SmartPerfetto&lt;/a&gt; and
&lt;a href="https://github.com/antarikshc/perfetto-mcp"&gt;perfetto-mcp&lt;/a&gt; are two public
examples I&amp;rsquo;ve come across but many teams inside Google have built their versions
of these which each handle a subset of Perfetto&amp;rsquo;s functionality. To us, this is
a sign that there&amp;rsquo;s demand out there to build something upstream so that
everyone can benefit.&lt;/p&gt;
&lt;p&gt;v57 includes an installable AI skill that works with Claude Code, Codex, Pi,
Opencode, Antigravity, and anything else that speaks the Agent Skills format. It
explains to the agent how to record traces (Android only for now!) and then
query them. It also includes dedicated workflows for debugging Android memory, a
specific area of focus for us; we plan on expanding these workflows over time
including to Linux.&lt;/p&gt;
&lt;p&gt;The skill is just one piece of a much larger AI plan, which I wrote up as
&lt;a href="https://github.com/google/perfetto/discussions/5763"&gt;an RFC&lt;/a&gt;. We will be doing
much more in this space in the coming months.&lt;/p&gt;
&lt;h2 id="state-tracks"&gt;State tracks&lt;/h2&gt;
&lt;p&gt;State tracks are a new kind of primitive in Perfetto, sitting alongside slices
and counters. They represent something that is always in exactly one state: a
connection that is open or closed, a device that is on or off, a worker that is
idle or busy. You could already model this with slices, by emitting a slice that
starts when a state begins and ends when it changes. This works but you have to
be careful to emit events &lt;em&gt;exactly&lt;/em&gt; at the same timestamps or you end up having
gaps.&lt;/p&gt;
&lt;p&gt;With a state track you don&amp;rsquo;t emit start and end events at all. At each point
where the state changes, you emit one event that says what the state is now. The
previous state ends automatically. Clearing the track marks it idle. This just
makes it a lot simpler to manage.&lt;/p&gt;
&lt;p&gt;While the simpler emission model is the obvious win, the reason we made it a
first-class primitive goes further than that. Because a state track holds
exactly one value at any moment, it can never overlap itself, and the trace
processor can lean on that guarantee to treat it specially. You can attach
arguments that change as the state does. And in ring-buffer traces, where old
events get overwritten as the buffer wraps, you can periodically re-emit the
current state, so a trace that starts mid-stream still knows what state
everything is in.&lt;/p&gt;
&lt;p&gt;State tracks are still quite new so there are likely to be some rough edges
here, but we think they&amp;rsquo;ll become an important primitive: because they&amp;rsquo;re
guaranteed never to overlap, you can do things with them you simply couldn&amp;rsquo;t
before.&lt;/p&gt;
&lt;h2 id="querying-traces-over-https"&gt;Querying traces over HTTPS&lt;/h2&gt;
&lt;p&gt;I recently discovered that quite a few folks have written scripts which download
a trace and then run queries against it with the trace_processor CLI. Which made
me think: why don&amp;rsquo;t we just support that natively inside the CLI itself?&lt;/p&gt;
&lt;p&gt;So in v57, &lt;code&gt;trace_processor&lt;/code&gt; takes an &lt;code&gt;http(s)&lt;/code&gt; URL or a &lt;code&gt;ui.perfetto.dev&lt;/code&gt; share
link directly. It streams the trace and runs your query with no local copy. Here
it is against the journald trace from the previous section:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ trace_processor query \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;https://lalitm.com/traces/journald-demo.pftrace&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;select log_source, count(*) as n from logs where msg is not null group by log_source&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;log_source&amp;#34;,&amp;#34;n&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;systemd_journald&amp;#34;,30
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s one of those small polish features which you wouldn&amp;rsquo;t necessarily think of
immediately but when you have it, you&amp;rsquo;d never want to go back.&lt;/p&gt;
&lt;h2 id="lightning-round"&gt;Lightning round&lt;/h2&gt;
&lt;p&gt;A few smaller things also landed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shift + mouse wheel scrolls the timeline horizontally.&lt;/strong&gt; A tiny change that
removes a daily papercut when you are panning around a wide trace.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The query results grid got a real overhaul.&lt;/strong&gt; You can sort, reorder, and
hide columns, the sidebar collapses, and SQL errors now show up in the grid
instead of disappearing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adjacent slices that share a boundary render with a 1px gap,&lt;/strong&gt; so a run of
back-to-back slices reads as separate slices rather than one solid block.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saved textproto record configs are editable,&lt;/strong&gt; saving the trouble of
downloading the config, deleting it, editing it locally, and then reuploading.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;/h2&gt;
&lt;p&gt;Overall this was a pretty solid release of Perfetto; it&amp;rsquo;s always satisfying to
fix a bug blocking a lot of people. AI skills and state tracks are both first
steps in a much longer journey, and journald logs are an important new
capability that improves our Linux support. The rest are mostly a bunch of small
quality of life improvements which are always nice to see!&lt;/p&gt;</content></entry><entry><title>Security analysis is finally reaching software's long tail</title><link href="https://lalitm.com/post/perfetto-security-bugs-ai/" rel="alternate" type="text/html"/><id>https://lalitm.com/post/perfetto-security-bugs-ai/</id><published>2026-06-07T16:26:00+01:00</published><updated>2026-06-07T16:26:00+01:00</updated><summary type="text">This post was formerly titled “17 bugs in 10 weeks from AI security scanning”. It was changed upon feedback from trusted readers who suggested, while accurate, it did not give a good sense of what I’m actually arguing in the post.
Over the last several weeks, I’ve been receiving more security bug reports for Perfetto’s trace processor than I ever have before, all of them found by AI. And I’m very happy about it! These are bugs that would almost certainly not have been found a year ago and it feels good to close these loopholes even though trace processor is by no means security critical.
For years, security researchers concentrated their time on the highest-stakes targets: kernels, cryptography libraries, password managers. But there’s a lot of code out there which is security-relevant but not truly security-critical. In my experience, these sorts of projects didn’t draw much attention. Now systems in the long tail can get that attention which they wouldn’t have before.</summary><content type="html">&lt;p&gt;&lt;em&gt;This post was formerly titled &amp;ldquo;17 bugs in 10 weeks from AI security scanning&amp;rdquo;.
It was changed upon feedback from trusted readers who suggested, while accurate,
it did not give a good sense of what I&amp;rsquo;m &lt;em&gt;actually&lt;/em&gt; arguing in the post.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Over the last several weeks, I&amp;rsquo;ve been receiving more security bug reports for
Perfetto&amp;rsquo;s trace processor than I ever have before, all of them found by AI. And
I&amp;rsquo;m very happy about it! These are bugs that would almost certainly not have
been found a year ago and it feels good to close these loopholes even though
trace processor is by no means security critical.&lt;/p&gt;
&lt;p&gt;For years, security researchers concentrated their time on the highest-stakes
targets: kernels, cryptography libraries, password managers. But there&amp;rsquo;s a lot
of code out there which is security-relevant but not truly security-critical. In
my experience, these sorts of projects didn&amp;rsquo;t draw much attention. Now systems
in the long tail can get that attention which they wouldn&amp;rsquo;t have before.&lt;/p&gt;
&lt;h2 id="why-is-this-happening"&gt;Why is this happening&lt;/h2&gt;
&lt;p&gt;Trace processor is a project which sits squarely in that long tail. It&amp;rsquo;s a C++
library (yes, Rust would be the obvious choice today but it&amp;rsquo;s not practical to
rewrite, see footnote &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;) for processing recorded traces of various
formats. These are typically traces you collected yourself or in your test infra
and process offline so &amp;ldquo;untrusted input&amp;rdquo; isn&amp;rsquo;t much of a concern.&lt;/p&gt;
&lt;p&gt;However, some people &lt;em&gt;do&lt;/em&gt; process traces they didn&amp;rsquo;t collect themselves (e.g.
user bug reports, automated collection from dogfood users). For those cases
we&amp;rsquo;ve strongly recommended sandboxing trace processor (e.g.
&lt;a href="https://gvisor.dev/"&gt;gvisor&lt;/a&gt;,
&lt;a href="https://developers.google.com/code-sandboxing/sandbox2"&gt;sandbox2&lt;/a&gt;, or
&lt;a href="https://google.github.io/minijail/"&gt;minijail&lt;/a&gt;) or, for even more sensitive use
cases, a VM.&lt;/p&gt;
&lt;p&gt;Beyond sandboxing, for catching issues proactively, we mainly relied on fuzzing
running internally in Google. These fuzzers occasionally surfaced real,
actionable bugs: we set them up to pass in arbitrary trace bytes (as this is the
main &amp;ldquo;attack surface&amp;rdquo;) but over time these became quite rare as they discovered
much of the low hanging fruit, which we quickly fixed. The bugs that remained
tend to live deep in the internals, reachable only with a very precisely crafted
sequence of bytes that a fuzzer is unlikely to hit by mutation alone.&lt;/p&gt;
&lt;p&gt;Apart from this, there has rarely been any bandwidth or resources for a human,
either a security expert or someone from my team, to spend lots of time finding
security issues in trace processor. There were always other parts of Perfetto
more worth spending security time on (e.g. the tracing service, on-device
profilers) as they&amp;rsquo;re actively running in production systems.&lt;/p&gt;
&lt;p&gt;All of this changed as of a couple of months ago. We started receiving bug
reports filed by some central team which appears to be running AI-based security
scanning against various projects throughout Google. Unfortunately, I have to be
hand wavy about what exactly they&amp;rsquo;re doing as their work doesn&amp;rsquo;t appear to be
public.&lt;/p&gt;
&lt;p&gt;Starting in early April, we had a slow drip of 1 bug a week, but since the end
of April this increased to a rate of several a week, with some days having 3 or
4 being opened in quick succession. This lasted until mid-May, at which point it
started tapering back to 1-2 a week with some weeks having none.&lt;/p&gt;
&lt;p&gt;I also want to say that the quality of the bugs is high. They&amp;rsquo;re well-described,
often with the relevant attacker model already worked out and even minimal fixes
proposed: basically everything I could ask for from a bug report. This matches
what both &lt;a href="https://daniel.haxx.se/blog/2026/04/22/high-quality-chaos/"&gt;curl&lt;/a&gt; and
&lt;a href="https://www.theregister.com/2026/03/26/greg_kroahhartman_ai_kernel/"&gt;Linux kernel&lt;/a&gt;
maintainers have noted about security bugs they&amp;rsquo;ve received, especially how
sharply quality has improved in the last few months.&lt;/p&gt;
&lt;p&gt;As I can only see the bugs that get filed against me, not the raw output of the
AI scanner, I don&amp;rsquo;t know exactly how much triage happens upstream. My guess is
there&amp;rsquo;s a human doing a light pass to drop obvious noise before reports reach
client teams, but judging from the rate at which bugs are opened and the way
they&amp;rsquo;re filed, I doubt anyone is deeply triaging each one.&lt;/p&gt;
&lt;p&gt;In total, we&amp;rsquo;ve received &lt;strong&gt;21&lt;/strong&gt; bugs (17 real issues and 4 not actionable),
which can be broken down into the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;10 bounds checking&lt;/strong&gt;: arbitrary trace data flowing into fixed-size buffers
or unchecked array indices, leading to out-of-bounds reads or writes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5 use-after-free&lt;/strong&gt;: back-pointers, pointer snapshots, or hashmap keys
outliving the object they refer to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1 stack overflow&lt;/strong&gt;: unbounded recursion when input is deeply nested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1 access control&lt;/strong&gt;: not enforcing allowlists on some rare codepaths.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 closed as not actionable&lt;/strong&gt;: either where the chance of exploit was purely
hypothetical or where fixing would have required fundamental design changes
which were not worth the tiny security risk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All 17 real issues have been fixed, almost all shipping in Perfetto v56.0
&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="how-it-feels-to-get-a-report"&gt;How it feels to get a report&lt;/h2&gt;
&lt;p&gt;How does receiving one of these reports actually feel? Well not as bad as you&amp;rsquo;d
think. Unlike a security-critical application like OpenSSL or curl, in trace
processor, a security issue is very unlikely to be a P0 I have to drop
everything to fix. Don&amp;rsquo;t get me wrong, it&amp;rsquo;s still a priority but one where I
have the luxury of taking a few days to figure out the right answer and can
release fixes according to our normal schedule, instead of trying to rush out a
CVE and get everyone to patch immediately.&lt;/p&gt;
&lt;p&gt;Also thankfully, because the majority of the issues are mechanical, the fixes
are generally quite straightforward.
&lt;a href="https://github.com/google/perfetto/pull/5586"&gt;Take this PR&lt;/a&gt;, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We build a key string into a fixed-size stack buffer while parsing some
metadata.&lt;/li&gt;
&lt;li&gt;The bounds check only runs in debug builds, and the metadata name comes
straight from the trace. Putting a long enough name means you would escape the
buffer.&lt;/li&gt;
&lt;li&gt;The fix is a simple matter of swapping the stack buffer for a std::string. The
code path is very cold (only once or twice in a trace) so the extra heap
allocation doesn&amp;rsquo;t matter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In fact, these sorts of issues are &lt;em&gt;so&lt;/em&gt; mechanical that I trust a coding agent
to just fix them with minimal guidance: take the well-written report, feed it to
the agent, and within ~10 minutes there&amp;rsquo;s a 10-20 line PR which fixes it. I
review every line thoroughly and make sure I understand it, but these tasks are
not difficult and firmly &lt;em&gt;inside&lt;/em&gt; the &amp;ldquo;jagged frontier&amp;rdquo; of what AI can do.&lt;/p&gt;
&lt;p&gt;I want to stress though that not every issue is mechanical or can be left to AI;
a few reports actually point more to design problems than incorrect function
implementations.
&lt;a href="https://github.com/google/perfetto/pull/5593"&gt;This use after free&lt;/a&gt; is a good
example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The problem was a state object held a back-pointer that could end up pointing
to freed memory given certain data appearing in the trace.&lt;/li&gt;
&lt;li&gt;The immediate dangling case was easy to patch by just having a callback which
invalidated the back-pointer on free. But this is a horrible hack which makes
the lifetimes of the objects involved impossible to reason about.&lt;/li&gt;
&lt;li&gt;The real problem here is that you had a child object whose parent could go
away before it, which really shouldn&amp;rsquo;t happen if this code is properly
architected.&lt;/li&gt;
&lt;li&gt;Fixing it properly meant restructuring the ownership model so the lifetime was
correct by construction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The interesting thing was that this was a problem I was aware of and that I had
been meaning to clean up for close to a year but never got round to: the
security bug just gave me the push and justification to do it. This applied in a
couple of other bugs as well and made me internalize that security issues can
sometimes be correlated with deeper design flaws or hacky code so there are
wider benefits to &amp;ldquo;security scanning&amp;rdquo; than just the direct bugs they find.&lt;/p&gt;
&lt;h2 id="will-this-last"&gt;Will this last?&lt;/h2&gt;
&lt;p&gt;One thing I am wary of is how long this stream of bugs will keep up; I&amp;rsquo;m feeling
good about it given it&amp;rsquo;s only been going on for a couple of months, but I can
easily imagine that if this goes on for several more months, it might become
mentally exhausting.&lt;/p&gt;
&lt;p&gt;But my suspicion is that this will go to zero. Why? It&amp;rsquo;s to do with the pattern
of how these bugs are being filed. Each part of the codebase seems like it&amp;rsquo;s
getting a day or two of attention (and associated bugs) before moving on to a
different part. Repeats are rare, and the pace of bugs has slowed especially in
the last couple of weeks: we had a lot more in the start of May (several a week)
but now we&amp;rsquo;re down to 1-2 a week. There are a finite number of files, so
eventually my gut tells me they will run out.&lt;/p&gt;
&lt;p&gt;An important consideration is whether we&amp;rsquo;ll add new bugs faster than the scanner
can find old ones. My suspicion is no; the 17 real issues so far are from
scanning across 9 years of development. Even if that number triples before
things settle, the scanner is still working through years of accumulated code.
And we wrote a lot more code, a lot faster, in the earlier years of the project,
so the rate of new code being added now is lower than it once was.&lt;/p&gt;
&lt;p&gt;The other question is whether new model releases will find more complex design
issues rather than the simple issues we&amp;rsquo;re finding today. Those take
significantly more time and effort to fix and so would be a lot more painful if
we were to get many of those. I&amp;rsquo;m very unsure on this so we&amp;rsquo;ll just have to wait
and see!&lt;/p&gt;
&lt;h2 id="where-this-leaves-us"&gt;Where this leaves us&lt;/h2&gt;
&lt;p&gt;I feel Daniel Stenberg (curl maintainer) phrased it well in this
&lt;a href="https://daniel.haxx.se/blog/2026/05/11/mythos-finds-a-curl-vulnerability/"&gt;post&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Any project that has not scanned their source code with AI powered tooling
will likely find huge number of flaws, bugs and possible vulnerabilities with
this new generation of tools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This rings very true to me. More broadly, I think folks will have one of three
experiences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Untrusted input + security critical&lt;/em&gt; (e.g. curl, kernel, OpenSSL): many
complex reports, with a higher false positive rate than the other categories,
because there&amp;rsquo;s a lot of attention on the project and much of the low hanging
fruit would already have been picked in the critical codepaths. Though
codepaths for lesser-used functionality (e.g. legacy drivers) could end up in
category 2 instead.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Untrusted input + not previously audited&lt;/em&gt; (e.g. trace processor): a wave of
mechanical bugs at a manageable pace and low individual stress because the
project is not on a security critical code path. This is where both Daniel
and I expect AI security scanners to have the most impact.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;No untrusted input&lt;/em&gt; (internal tools, math libs, anything operating only on
trusted data): you probably won&amp;rsquo;t notice this shift at all.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My own case sits squarely in that second bucket. But I don&amp;rsquo;t want to
over-generalize from my experience, because there are three things that make
this manageable for me that wouldn&amp;rsquo;t be true for everyone: a) I&amp;rsquo;m paid to
maintain trace processor as part of my full time job; b) someone else is taking
the effort to run the AI scans and discover the bugs in the first place; c) the
reports appear to be lightly filtered by an upstream human reviewer, enough to
strip obvious noise but probably not a deep triage.&lt;/p&gt;
&lt;p&gt;To me, this points to a gap in the ecosystem: most open-source projects cannot
afford to have a dedicated team doing security scanning for them, and telling a
maintainer to stand up their own pipeline when their security risk is marginal
will restrict this to only the most motivated projects. I would guess we&amp;rsquo;re
going to see a lot more innovation in this space, including from the big AI
labs.&lt;/p&gt;
&lt;p&gt;All in all, I&amp;rsquo;m cautiously positive about my own experience: most of the bugs
are mechanical, a few have nudged long-overdue design cleanups, and the pace is
manageable. There&amp;rsquo;s plenty I don&amp;rsquo;t know about how this evolves: whether the pace
holds, whether future models start finding harder design issues. So this should
very much be treated as a snapshot, not a forecast!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;A common response I expect is: &amp;ldquo;if it&amp;rsquo;s parsing arbitrary binary data like
traces, it should be written in Rust.&amp;rdquo; In a vacuum I agree and if I was
writing trace processor from scratch today, I would definitely use Rust. But
&lt;em&gt;switching&lt;/em&gt; to Rust is unfortunately quite impractical; the library is a
significant amount of code and is embedded in hundreds of downstream tools,
many in environments that don&amp;rsquo;t have a Rust toolchain. Asking all our
embedders to start using Rust would be a significant burden and one I don&amp;rsquo;t
want to impose. Not to mention that our team doesn&amp;rsquo;t actually have any Rust
expertise so reviewing this code at a standard I want from trace processor
would be a significant productivity hit until folks got up to speed. And
unlike others in the industry, I don&amp;rsquo;t feel comfortable just rewriting the
whole project in one shot and calling it a day&amp;hellip;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;The couple of remaining bugs were found after the v56.0 release was cut and
are low-priority enough that it&amp;rsquo;s not worth rushing out a release for them.
They have already been fixed on main and will be fixed in an upcoming point
release.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content></entry><entry><title>syntaqlite: high-fidelity devtools that SQLite deserves</title><link href="https://lalitm.com/post/syntaqlite/" rel="alternate" type="text/html"/><id>https://lalitm.com/post/syntaqlite/</id><published>2026-03-17T05:35:45Z</published><updated>2026-03-17T05:35:45Z</updated><summary type="text">Most SQL tools treat SQLite as a “flavor” of a generic SQL parser. They approximate the language, which means they break on SQLite-exclusive features like virtual tables, miss syntax like UPSERT, and ignore the 22 compile-time flags that change the syntax SQLite accepts.
So I built syntaqlite: an open-source parser, formatter, validator, and LSP built directly on SQLite’s own Lemon-generated grammar. It sees SQL exactly how SQLite sees it, no matter which version of SQLite you’re using or which feature flags you compiled with.
It ships as a CLI, VS Code extension, Claude Code LSP plugin, and C/Rust libraries.
There’s also a web playground which you can try now: paste any SQLite SQL and see parsing, formatting, and validation live in the browser, no install needed. Full documentation is available here.
Here’s syntaqlite in action:
Formatting with the CLI
&gt; syntaqlite fmt -e "select u.name,u.email,count(e.id) as events from users u join events e on e.user_id=u.id where u.signed_up_at&gt;=date('now','-30 days') group by u.name,u.email having count(e.id)&gt;10 order by events desc" SELECT u.name, u.email, count(e.id) AS events FROM users AS u JOIN events AS e ON e.user_id = u.id WHERE u.signed_up_at &gt;= date('now', '-30 days') GROUP BY u.name, u.email HAVING count(e.id) &gt; 10 ORDER BY events DESC; Validation with the CLI</summary><content type="html">&lt;p&gt;Most SQL tools treat SQLite as a &amp;ldquo;flavor&amp;rdquo; of a generic SQL parser. They approximate the language, which means they break on SQLite-exclusive features like &lt;a href="https://www.sqlite.org/vtab.html"&gt;virtual tables&lt;/a&gt;, miss syntax like &lt;a href="https://www.sqlite.org/lang_upsert.html"&gt;UPSERT&lt;/a&gt;, and ignore the &lt;a href="https://www.sqlite.org/compile.html"&gt;22 compile-time flags&lt;/a&gt; that change the syntax SQLite accepts.&lt;/p&gt;
&lt;p&gt;So I built &lt;a href="https://github.com/lalitMaganti/syntaqlite"&gt;syntaqlite&lt;/a&gt;: an open-source parser, formatter, validator, and LSP built directly on SQLite&amp;rsquo;s own Lemon-generated grammar. It sees SQL exactly how SQLite sees it, no matter which version of SQLite you&amp;rsquo;re using or which feature flags you compiled with.&lt;/p&gt;
&lt;p&gt;It ships as a &lt;a href="https://docs.syntaqlite.com/main/getting-started/cli"&gt;CLI&lt;/a&gt;, &lt;a href="https://docs.syntaqlite.com/main/getting-started/vscode"&gt;VS Code extension&lt;/a&gt;, &lt;a href="https://docs.syntaqlite.com/main/getting-started/claude-code"&gt;Claude Code LSP plugin&lt;/a&gt;, and &lt;a href="https://docs.syntaqlite.com/main/getting-started/c-parser/"&gt;C&lt;/a&gt;/&lt;a href="https://docs.syntaqlite.com/main/getting-started/rust/"&gt;Rust&lt;/a&gt; libraries.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a &lt;strong&gt;&lt;a href="https://playground.syntaqlite.com/"&gt;web playground&lt;/a&gt;&lt;/strong&gt; which you can try now: paste any SQLite SQL and see parsing, formatting, and validation live in the browser, no install needed. &lt;a href="https://docs.syntaqlite.com/"&gt;Full documentation is available here&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s syntaqlite in action:&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Formatting with the CLI&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; syntaqlite fmt -e &lt;span class="s2"&gt;&amp;#34;select u.name,u.email,count(e.id) as events from users u join events e on e.user_id=u.id where u.signed_up_at&amp;gt;=date(&amp;#39;now&amp;#39;,&amp;#39;-30 days&amp;#39;) group by u.name,u.email having count(e.id)&amp;gt;10 order by events desc&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;users&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;JOIN&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;ON&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;signed_up_at&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;now&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;-30 days&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;HAVING&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;Validation with the CLI&lt;/em&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; syntaqlite --sqlite-version 3.37.0 validate &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -e &lt;span class="s2"&gt;&amp;#34;SELECT json_extract(data, &amp;#39;&lt;/span&gt;$&lt;span class="s2"&gt;.name&amp;#39;) FROM events;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;error: unknown function &amp;#39;json_extract&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; &amp;lt;expression&amp;gt;:1:8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1 | SELECT json_extract(data, &amp;#39;$.name&amp;#39;) FROM events;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | ^~~~~~~~~~~~
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;em&gt;LSP support in VSCode&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/syntaqlite-vscode.png" alt="VS Code showing syntaqlite catching a misspelled column name"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;LSP in Claude Code&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/syntaqlite-cc.png" alt="Claude Code using syntaqlite to catch and fix column name errors"&gt;&lt;/p&gt;
&lt;h2 id="why-it-exists"&gt;Why it exists&lt;/h2&gt;
&lt;p&gt;I maintain &lt;a href="https://perfetto.dev/docs/analysis/perfetto-sql-syntax"&gt;PerfettoSQL&lt;/a&gt;, a dialect of SQLite SQL used for trace analysis in &lt;a href="https://perfetto.dev/"&gt;Perfetto&lt;/a&gt;. Across Google there are 200K+ lines of the language. At that scale, you hit &lt;em&gt;every&lt;/em&gt; corner of it. I tried every SQLite tool I could find and time after time I was disappointed: false positives on valid syntax, misparsed statements, formatters that silently corrupted the SQL by changing semantics. It always came down to a hand-written or generic grammar approximating SQLite rather than matching it to the letter.&lt;/p&gt;
&lt;p&gt;Moreover, SQLite&amp;rsquo;s SQL is not one fixed language. Apart from flags that change the syntax, there are another 12 flags that gate &lt;a href="https://www.sqlite.org/compile.html"&gt;built-in functions&lt;/a&gt;, as well as APIs for programs to &lt;a href="https://www.sqlite.org/c3ref/create_function.html"&gt;register custom functions&lt;/a&gt; and &lt;a href="https://www.sqlite.org/vtab.html"&gt;virtual tables&lt;/a&gt;. In Perfetto, we make &lt;em&gt;extensive&lt;/em&gt; use of both virtual tables and functions and most tools just don&amp;rsquo;t give us the flexibility to treat these as first-class extensions of the language.&lt;/p&gt;
&lt;p&gt;The SQLite language also constantly evolves: in the last several years, 3.25.0 added window functions, 3.35.0 added &lt;code&gt;RETURNING&lt;/code&gt;, 3.38.0 added built-in JSON functions to the amalgamation. And because SQLite is embedded, you can&amp;rsquo;t assume everyone is on the latest version. This is certainly the case for Perfetto: people link against different versions of SQLite and we need to make sure our standard library is resilient to that.&lt;/p&gt;
&lt;p&gt;Now with all of this said, I could have built all these tools just for PerfettoSQL and called it a day. But the more I thought about it, the more I felt that the difference between &amp;ldquo;good tooling for PerfettoSQL&amp;rdquo; and &amp;ldquo;good tooling for SQLite&amp;rdquo; is so small that it&amp;rsquo;s worth solving for the wider use case. SQLite is ubiquitous, yet it still lacks the quality tooling other languages have had for over a decade.&lt;/p&gt;
&lt;p&gt;Android is one case I think could especially benefit. SQLite is integral to almost every Android app not to mention the platform itself. But the ecosystem is also famously fragmented: if you&amp;rsquo;re an Android app developer, you &lt;strong&gt;need&lt;/strong&gt; to reason about older platform versions, which in turn means older versions of SQLite.&lt;/p&gt;
&lt;p&gt;As an example, Android 13 ships with SQLite 3.32 (&lt;a href="https://developer.android.com/reference/android/database/sqlite/package-summary"&gt;link&lt;/a&gt;). If you use the &lt;code&gt;RETURNING&lt;/code&gt; keyword and your users are on Android 13, it will fail at runtime even though it passes fine on your test device. Wouldn&amp;rsquo;t it be nice to statically verify this on your host instead of discovering it on a real device?&lt;/p&gt;
&lt;p&gt;No tool I found handled all of this: the precise SQLite grammar, version matching, compile flags, dialect extensions. And that&amp;rsquo;s exactly why syntaqlite exists.&lt;/p&gt;
&lt;h2 id="highlights"&gt;Highlights&lt;/h2&gt;
&lt;h3 id="validate-semantics-with-great-error-messages"&gt;Validate semantics with great error messages&lt;/h3&gt;
&lt;p&gt;Parsing alone is table stakes. But I also wanted to catch common mistakes like reaching for &lt;code&gt;user_name&lt;/code&gt; when the column is actually &lt;code&gt;username&lt;/code&gt;. For this reason, syntaqlite also resolves table, column, and function references, catching errors like misspelled column names or references to tables that don&amp;rsquo;t exist.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve also tried to make the error messages &lt;em&gt;really&lt;/em&gt; good: at the quality level you would expect to see from modern tools in other languages like rustc or tsc. This means both accurate, line/column level indication with snippets of the error but also &amp;ldquo;did you mean&amp;rdquo; hints, pointing you in the right direction. The error messages should be &lt;em&gt;significantly&lt;/em&gt; better than SQLite&amp;rsquo;s and it&amp;rsquo;s something I plan on making even better with time.&lt;/p&gt;
&lt;p&gt;As for correctness, I&amp;rsquo;ve tested syntaqlite against ~396K statements from &lt;a href="https://sqlite.org/testing.html"&gt;SQLite&amp;rsquo;s upstream test suite&lt;/a&gt; with ~99.7% agreement on parse acceptance &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;. Before we get to 1.0, I fully expect this to reach 100%.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also a &lt;a href="https://docs.syntaqlite.com/main/reference/comparison/"&gt;detailed comparison&lt;/a&gt; against other tools covering parser accuracy, formatter correctness, validator quality, and performance. Of course the benchmarks and stress tests were written by me, so take it with a grain of salt. But hopefully the results speak for themselves.&lt;/p&gt;
&lt;h3 id="lsp-for-editors-and-coding-agents"&gt;LSP for editors and coding agents&lt;/h3&gt;
&lt;p&gt;syntaqlite ships a full language server: completions, hover, go-to-definition, find references, rename, diagnostics, and formatting. Unlike most languages, SQL queries depend on an external schema that isn&amp;rsquo;t in the source file. The LSP needs to know about that schema to give you useful diagnostics.&lt;/p&gt;
&lt;p&gt;The most common way to provide this is with a &lt;code&gt;syntaqlite.toml&lt;/code&gt; at the root of your project &lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;. Point it at your &lt;code&gt;CREATE TABLE&lt;/code&gt; definitions (which you can dump using &lt;code&gt;.schema&lt;/code&gt; on your SQLite database; see the &lt;a href="https://docs.syntaqlite.com/main/guides/project-setup/"&gt;project setup guide&lt;/a&gt; for details) and syntaqlite uses those for autocompletion, diagnostics, and find references.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;schemas&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;**/*.sql&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;db/schema.sql&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;This tells syntaqlite: for every &lt;code&gt;.sql&lt;/code&gt; file in the project, validate against the definitions in &lt;code&gt;db/schema.sql&lt;/code&gt;. You can point at multiple schema files, and scope different schemas to different directories.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;syntaqlite.toml&lt;/code&gt; can be used to configure other preferences like indentation for formatting and the SQLite version as well. The idea is that you check this into your repo and the whole team gets the same experience.&lt;/p&gt;
&lt;h3 id="it-validates-sql-inside-other-languages-experimental"&gt;It validates SQL inside other languages (experimental)&lt;/h3&gt;
&lt;p&gt;SQL is unusual in that it often lives &lt;em&gt;inside&lt;/em&gt; other languages: string literals in Python, template literals in TypeScript, raw strings in C++, and so on.&lt;/p&gt;
&lt;p&gt;As an experimental feature (and with caveats!), syntaqlite can extract and validate SQL &lt;em&gt;inside&lt;/em&gt; other languages, even if you have string interpolation. For example, in a Python f-string:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# app.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_user_stats&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SELECT name, ROUDN(score, 2) FROM users WHERE id = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;syntaqlite validate --experimental-lang python app.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;warning: unknown function &amp;#39;ROUDN&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --&amp;gt; app.py:4:23
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4 | f&amp;#34;SELECT name, ROUDN(score, 2) FROM users WHERE id = {user_id}&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | ^~~~~
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; = help: did you mean &amp;#39;round&amp;#39;?
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It handles the &lt;code&gt;{user_id}&lt;/code&gt; interpolation hole and still catches the function typo with a did-you-mean suggestion.&lt;/p&gt;
&lt;h3 id="and-more"&gt;And more&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SQLite version- and flag-aware.&lt;/strong&gt; Tell syntaqlite which SQLite version and compile-time flags your target has; the formatter, validator, and LSP all respect them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Custom dialect support.&lt;/strong&gt; Define grammar, formatting, and validation rules, compile to a shared library, load at runtime. I built this for PerfettoSQL, but it works for any engine that extends SQLite&amp;rsquo;s syntax. For example, a mobile analytics engine with custom aggregation functions or a local-first app framework with its own replication primitives.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fast.&lt;/strong&gt; The formatter processes 3,500 lines of SQL in ~5ms. Full benchmarks on the &lt;a href="https://docs.syntaqlite.com/main/reference/comparison/"&gt;comparison page&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-it-works"&gt;How it works&lt;/h2&gt;
&lt;p&gt;I plan on writing a detailed followup on the technical side, but I did want to give a short summary here.&lt;/p&gt;
&lt;p&gt;Fundamentally syntaqlite is a C/Rust/C sandwich:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------+---------------+--------------+-------+--------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| VS Code | Claude Code | Playground | CLI | C bindings |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Extension | / Agents | (WASM) | | (Go, Kotlin...) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------+---------------+--------------+-------+--------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Language Server (LSP) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Formatter · Validator · Analyzer (Rust) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;| Parser · Tokenizer · AST Arena (C, from SQLite) |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+-------------------------------------------------------------------------+
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The bottom layer is C: SQLite&amp;rsquo;s Lemon-generated grammar and tokenizer, plus an arena storing a &lt;a href="https://jhwlr.io/super-flat-ast/"&gt;super flat AST&lt;/a&gt;. This layer has no Rust dependency and can be used entirely on its own, which matters if you&amp;rsquo;re building a database engine or other tool in C/C++ that just needs to parse SQLite SQL &lt;sup id="fnref:3"&gt;&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref"&gt;3&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;The middle layer is Rust. It reads the C arena directly with no copying or deserialization. The formatter generates a &lt;a href="https://lindig.github.io/papers/strictly-pretty-2000.pdf"&gt;Wadler-Lindig document&lt;/a&gt; using an AST-walking interpreter; the semantic analyzer walks the same AST, resolving references and producing diagnostics with byte-accurate source locations.&lt;/p&gt;
&lt;p&gt;The top layer is C again, completing the sandwich: the Rust tools are exported back through FFI so C/C++ projects can link syntaqlite as a plain C library. This also makes bindings for other languages possible: Go, Kotlin/Java, and Python are planned before 1.0. Of course, if you&amp;rsquo;re using Rust directly you can just use the Rust library, skipping the top C layer.&lt;/p&gt;
&lt;p&gt;The obvious question I&amp;rsquo;m expecting is &amp;ldquo;why not just write everything in Rust&amp;rdquo;? I wanted the parser to work &lt;em&gt;exactly&lt;/em&gt; as SQLite does, which meant &lt;em&gt;literally&lt;/em&gt; extracting SQLite&amp;rsquo;s own code and adapting it to my needs. And writing it in C with the same constraints as SQLite itself (no dependencies beyond the C standard library) means it can be linked into practically anything: database engines, C++ projects like Perfetto&amp;rsquo;s trace processor, or any environment where SQLite runs.&lt;/p&gt;
&lt;p&gt;On the other hand, I really did not want to write a formatter, semantic analyzer, and LSP in C. While I do like the &amp;ldquo;simplicity&amp;rdquo; of C, writing a full JSON-RPC server and protocol sounds like a nightmare.&lt;/p&gt;
&lt;h2 id="where-this-is-going"&gt;Where this is going&lt;/h2&gt;
&lt;p&gt;In my head, there&amp;rsquo;s a clear roadmap for the project side:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;100% upstream parity.&lt;/strong&gt; The ~0.3% gap against SQLite&amp;rsquo;s test suite is mostly from not fully tracking built-in tables and virtual tables. It just needs another round of &amp;ldquo;source code introspection&amp;rdquo; across all the SQLite versions and integrating those tables into syntaqlite&amp;rsquo;s builtin catalog.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LSP enhancements.&lt;/strong&gt; Code actions, document symbols, inlay hints for inferred column types. All of these basically making the editor experience even better.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Robust embedded SQL.&lt;/strong&gt; The Python/TypeScript support is really just hacked together with regexes as a proof of concept. My goal is to extend this to use proper parser libraries &lt;em&gt;and&lt;/em&gt; add support for more languages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Polish and performance.&lt;/strong&gt; The focus for 0.1 was getting the architecture right. There&amp;rsquo;s room to improve performance, API surface, and documentation across the whole stack.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;As for the blog, this project was the reason I&amp;rsquo;ve been completely silent over the last month, but now I&amp;rsquo;m excited to start writing a lot more again. For one, there&amp;rsquo;s the technical followup for the decisions I took and what I learned along the way.&lt;/p&gt;
&lt;p&gt;For another, I used coding agents (mainly Claude Code) extensively while building syntaqlite. There were plenty of moments where following them blindly would have sunk the project, but I can also say with certainty that syntaqlite would not exist without them. I want to dive into that tension: what worked, what didn&amp;rsquo;t, and the complicated feelings I came away with.&lt;/p&gt;
&lt;p&gt;So if you work with SQLite SQL at any scale, I&amp;rsquo;d appreciate you giving syntaqlite a try and telling me what breaks! SQLite is a project which famously plans to be &lt;a href="https://sqlite.org/lts.html"&gt;supported until 2050&lt;/a&gt;. My hope is that syntaqlite will be there as a companion every step of the way, making the experience of writing SQLite SQL that much nicer.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://github.com/lalitMaganti/syntaqlite"&gt;syntaqlite on GitHub&lt;/a&gt; | &lt;a href="https://docs.syntaqlite.com/"&gt;Docs&lt;/a&gt; | &lt;a href="https://playground.syntaqlite.com/"&gt;Playground&lt;/a&gt;&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;Per-statement results are in &lt;a href="https://github.com/lalitMaganti/syntaqlite/tree/main/tests/upstream_baselines"&gt;&lt;code&gt;tests/upstream_baselines/&lt;/code&gt;&lt;/a&gt;. The remaining 0.3% is largely down to not perfectly matching the builtin tables and then edge cases around where and when column aliases come into scope: it&amp;rsquo;s actually really complicated!&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;You can also pass schema files directly via &lt;code&gt;--schema&lt;/code&gt; on the command line without needing a config file.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:3"&gt;
&lt;p&gt;The plan is to replace the PerfettoSQL parser in Perfetto&amp;rsquo;s trace processor with this C layer.&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content></entry><entry><title>Rendering 100k trace events faster with exponential search</title><link href="https://lalitm.com/post/exponential-search/" rel="alternate" type="text/html"/><id>https://lalitm.com/post/exponential-search/</id><published>2026-02-03T21:07:04Z</published><updated>2026-02-03T21:07:04Z</updated><summary type="text">We’ve recently been looking into optimizing rendering performance of the Perfetto UI on large traces. We discovered that there was some inefficiency in our data fetching logic, especially when you’re very zoomed out.
In this case, there can be a lot of slices (spans) which are so small that they take less than one pixel of width. So for each pixel, we need to figure out “what is the event which we should draw for this pixel”. Over time we’ve come to the conclusion that the best thing to draw is the slice with the largest duration in that pixel.
We can break this into two sub-problems:
What is the range of events which correspond to each pixel? What is the event with the maximum duration for that pixel? We’re going to focus on 1) in this post as that’s where the slowdown was. 2) is fascinating but also surprisingly orthogonal. If you’re interested, I would suggest reading this excellent post from Tristan Hume explaining the basic algorithm we use.</summary><content type="html">&lt;p&gt;We&amp;rsquo;ve recently been looking into optimizing rendering performance of the Perfetto UI on large traces. We discovered that there was some inefficiency in our data fetching logic, especially when you&amp;rsquo;re very zoomed out.&lt;/p&gt;
&lt;p&gt;In this case, there can be a lot of slices (spans) which are so small that they take less than one pixel of width. So for each pixel, we need to figure out &amp;ldquo;what is the event which we should draw for this pixel&amp;rdquo;. Over time we&amp;rsquo;ve come to the conclusion that the best thing to draw is the slice with the largest duration in that pixel.&lt;/p&gt;
&lt;p&gt;We can break this into two sub-problems:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What is the range of events which correspond to each pixel?&lt;/li&gt;
&lt;li&gt;What is the event with the maximum duration for that pixel?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We&amp;rsquo;re going to focus on 1) in this post as that&amp;rsquo;s where the slowdown was. 2) is fascinating but also surprisingly orthogonal. If you&amp;rsquo;re interested, I would suggest reading &lt;a href="https://thume.ca/2021/03/14/iforests/"&gt;this excellent post&lt;/a&gt; from Tristan Hume explaining the basic algorithm we use.&lt;/p&gt;
&lt;p&gt;So let&amp;rsquo;s formalize our setup for 1): we have a sorted array containing N (where N is O(100k)) int64 timestamps. The timestamps come directly from the Perfetto trace file so we cannot make any assumptions on values or the distribution. But generally speaking, traces tend to have a mix of dense clusters of events (i.e. lot of activity) and large gaps between these clusters (i.e. not much going on). So it makes sense to optimize for this sort of data pattern.&lt;/p&gt;
&lt;p&gt;We then need to find the indexes corresponding to the &amp;ldquo;lower bound&amp;rdquo; of M regularly-spaced timestamps (A, A+S, A+2S&amp;hellip; B), where each corresponds to a pixel boundary and S corresponds to the &amp;ldquo;time in the trace represented by one pixel&amp;rdquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Data: | | | · · | | | | | | | | | | · · · | | · · · · | | | | | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; dense sparse dense
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Queries: ↓ ↓ ↓ ↓ ↓
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; A A+S A+2S A+3S B
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; │ │ │ │ │
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ▼ ▼ ▼ ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Result: lb(A) lb(A+S) lb(A+2S) lb(A+3S) lb(B)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The obvious solution, and what we&amp;rsquo;ve been doing for the last few years, is to just binary search each of the M values independently giving us O(M × log N). This is very fast when N is reasonable but when we get into O(100k) on high resolution screens (meaning M is also relatively large), you end up taking several milliseconds to do these queries.&lt;/p&gt;
&lt;p&gt;My first approach was to use &amp;ldquo;static search trees&amp;rdquo; to speed up the searches; ever since I had come across &lt;a href="https://curiouscoding.nl/posts/static-search-tree/"&gt;this amazing post&lt;/a&gt; last year, I had been itching to find a place to use them. They worked great and definitely made things faster, but it was a &lt;em&gt;lot&lt;/em&gt; of code and required hand-written AVX and a deep understanding, which I knew would be difficult to get through review. And I had this nagging feeling there was something much simpler I could do.&lt;/p&gt;
&lt;p&gt;Eventually I had a key realization: I wasn&amp;rsquo;t using the fact that the &lt;em&gt;queries themselves are sorted&lt;/em&gt;! By using this, we can do &lt;em&gt;much&lt;/em&gt; better and achieve O(M + log(N)) performance instead!&lt;/p&gt;
&lt;p&gt;How? Using a cool technique called &lt;a href="https://en.wikipedia.org/wiki/Exponential_search"&gt;exponential search&lt;/a&gt;!&lt;/p&gt;
&lt;p&gt;You start by binary searching for A just like before giving you an index I. But for every timestamp after that, instead of binary searching, you do something which looks like a hybrid between linear search and binary search.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Found A at index i, now searching for A+S:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Index: i i+1 i+2 i+3 i+4 i+5 i+6 i+7 i+8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Data: [A] [ ] [ ] [ ] [ ] [A+S] [ ] [ ] [ ]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↑ ↑ ↑ ↑ ↑
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; start +1 +2 +4 +8
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;A+S &amp;lt;A+S &amp;lt;A+S &amp;gt;A+S
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; overshot!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └───────────────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; binary search here
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Take A + S: at I+1, I+2, I+4, I+8&amp;hellip;, you check if the timestamp at that position is greater than A + S. If not, you continue to the next one. If it is, then you stop and instead binary search between the previous position you checked and the current one.&lt;/p&gt;
&lt;p&gt;This is much faster than just binary searching both theoretically &lt;em&gt;and&lt;/em&gt; practically. Theoretically, notice how we scan the same memory &lt;strong&gt;at most twice&lt;/strong&gt;; once for the exponential scan, once in the binary search afterwards. This is unlike the &amp;ldquo;binary search everything&amp;rdquo; approach where the same piece of memory could be touched M times, if the data layout was pathological. This is where the O(M × log N) -&amp;gt; O(M + log N) improvement comes from.&lt;/p&gt;
&lt;p&gt;Note that this &lt;em&gt;also&lt;/em&gt; applies to static search trees which also have O(M × log N) complexity; exponential search turned out to beat them too!&lt;/p&gt;
&lt;p&gt;Practically, the initial exponential scan is great for CPUs which love scanning through memory linearly instead of jumping around randomly like binary search does. By significantly reducing the binary search range, you significantly reduce the chance of cache misses.&lt;/p&gt;
&lt;p&gt;A final micro-optimization you can do is, when the number of indices you are binary searching is fewer than 16 (empirically chosen), you can just replace with a linear scan instead. At those sizes, you find that the branching and looping from binary search is actually &lt;em&gt;slower&lt;/em&gt; than brute force checking everything (especially with the auto-vectorizer helping you out!).&lt;/p&gt;
&lt;p&gt;We switched to exponential search in &lt;a href="https://github.com/google/perfetto/pull/4648"&gt;this Perfetto PR&lt;/a&gt; and in our microbenchmark (on real trace data!), we saw an improvement from 1.5ms to 180us - a speedup of 8x! This, along with a bunch of other improvements we made, should help make the Perfetto UI faster and smoother on large traces!&lt;/p&gt;</content></entry><entry><title>Perfetto: Swiss Army Knife for Linux Client Tracing</title><link href="https://lalitm.com/perfetto-swiss-army-knife/" rel="alternate" type="text/html"/><id>https://lalitm.com/perfetto-swiss-army-knife/</id><published>2025-10-31T00:00:00Z</published><updated>2025-10-31T00:00:00Z</updated><summary type="text">I gave a talk at the 2025 Tracing Summit last month titled “Perfetto: The Swiss Army Knife of Linux Client/Embedded Tracing”. My goal in this talk was to show how Linux kernel, systems and embedded developers can use Perfetto when debugging and root-causing performance issues in their respective domains. Even though the Perfetto UI is primarily built for viewing Android or Chrome traces, it is a flexible tool and can be used in many other ways!
The talk was recorded and is available on YouTube. Taking inspiration from Simon Willison, this post is an annotated presentation containing my slides and detailed notes on them. The talk also has a lot of UI demos: for these, I’ll have a screenshot but also a link to the relevant part of the video (videos are unbeatable for UI!).
First, what is Perfetto? Perfetto is fundamentally a suite of tools: it’s not just one thing but a bunch of different tools working together to help you debug and root-cause problems. This diagram shows how everything fits together, with the core parts of the Perfetto project in the center.
The recording tools for Perfetto consist of 1) an SDK for C++ apps 2) a daemon that can collect data from ftrace, /proc, /sys, and various kernel interfaces 3) another daemon that amalgamates trace data from multiple processes into a single trace file. These tools all speak the Perfetto protobuf format, a high-performance trace format designed to be very efficient to write but not to analyze or consume directly.
That’s where the trace processor comes in. It’s a C++ library that parses the protobuf format, but also a bunch of other widely used trace formats. It exposes this data via an SQL query interface to any embedding program including Perfetto UI (which is what most of this talk is about) and also to the Python API if you want to do ad-hoc scripting or analysis in Python.
There are also very common tracing/profiling formats used by the Linux community: perf.data, ftrace text format, Firefox profiler format, and many others. Perfetto supports quite a few of those directly. There’s also the Chrome JSON format (AKA the Trace Event Format) which is a simpler interchange format. It’s not the most efficient to read or write, but it does the job for a lot of use cases.
Often people write converters. They have their own tracing format, maybe proprietary, maybe open source but something we don’t know about, and it’s very common that people convert to one of the formats we understand, most commonly our protobuf or Chrome JSON formats.
The Perfetto UI is fundamentally a web-based trace visualizer, combining timeline visualization, user-driven selection/aggregation, and SQL queries all in one interface. Because it has the trace processor as a backend, it works with a bunch of different trace formats.
It’s very important to note that even though the Perfetto UI is web-based, everything happens inside your browser and trace data never leaves your system. You can even build it and host it yourself on any static server: we’ve made it extremely easy to do so!
At the start of 2025, we actually moved our whole development to GitHub. In the past, we used to develop on Android and GitHub was just a mirror. That’s no longer the case, GitHub is actually where we develop and take pull requests.
Most of this talk, I’m going to spend actually showing you how you can use the Perfetto UI to debug performance issues on Linux. I don’t want to show you an Android trace which needs a lot of context about how the Android system works and so you think, “oh, that was cool, but I didn’t really understand what was happening.”
So to make this talk more approachable, I wrote a straightforward demo program you can look at yourself! So it’s obviously not a production system but I’ve tried to make it as representative of the sort of issues we use Perfetto for every day.
It’s a Rust program which generates a Julia set and visualizes it over time. The technologies I used: Vulkan, GPU rendering and also multi-threaded CPU computation. So how it works is that computation of various parameters is happening on background threads, and then that’s being passed to the main thread for rendering.
And then, for demonstration purposes, there is a performance bug; rendering should run at 60 FPS, but every so often, the frame rate drops dramatically. Here’s what that looks like:
The code is on GitHub and if you’re interested in following along. The traces are there as well - you don’t have to collect the traces yourself, but you can if you want. All the instructions and information is in the README.
So the first suspicion we may have is that maybe it’s some CPU problem. A lot of engineers I know would reach for perf immediately whenever they see a problem like this. The main reason is that if perf can capture the problem, they can go straight to the line of code without needing to spend time debugging using more complex approaches.</summary><content type="html">&lt;p&gt;I gave a talk at the &lt;a href="https://tracingsummit.org/"&gt;2025 Tracing Summit&lt;/a&gt; last month titled &amp;ldquo;&lt;a href="https://cfp.tracingsummit.org/ts2025/talk/TKVU8W/"&gt;Perfetto: The Swiss Army Knife of Linux Client/Embedded Tracing&lt;/a&gt;&amp;rdquo;. My goal in this talk was to show how Linux kernel, systems and embedded developers can use &lt;a href="https://docs.perfetto.dev"&gt;Perfetto&lt;/a&gt; when debugging and root-causing performance issues in their respective domains. Even though the Perfetto UI is primarily built for viewing &lt;a href="https://perfetto.dev/docs/#why-would-you-use-perfetto-"&gt;Android or Chrome traces&lt;/a&gt;, it is a flexible tool and can be used in many other ways!&lt;/p&gt;
&lt;p&gt;The talk was recorded and is available on &lt;a href="https://www.youtube.com/watch?v=VzTwul2Qb3g"&gt;YouTube&lt;/a&gt;. Taking inspiration from &lt;a href="https://simonwillison.net/"&gt;Simon Willison&lt;/a&gt;, this post is an &lt;a href="https://simonwillison.net/2023/Aug/6/annotated-presentations/"&gt;annotated presentation&lt;/a&gt; containing my slides and detailed notes on them. The talk also has a lot of UI demos: for these, I&amp;rsquo;ll have a screenshot but also a link to the relevant part of the video (videos are unbeatable for UI!).&lt;/p&gt;
&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%;"&gt;
&lt;iframe style="position: absolute; top: 0; left: 0; width: 100%; height: 100%;" src="https://www.youtube.com/embed/VzTwul2Qb3g" title="TS2025 - Perfetto: The Swiss Army Knife of Linux Client/Embedded Tracing" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen&gt;&lt;/iframe&gt;
&lt;/div&gt;
&lt;hr&gt;
&lt;p&gt;First, what is Perfetto? Perfetto is fundamentally a suite of tools: it&amp;rsquo;s not just one thing but a bunch of different tools working together to help you debug and root-cause problems. This diagram shows how everything fits together, with the core parts of the Perfetto project in the center.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-03.jpg" alt="diagram of all the pieces of Perfetto, Recording tools, protobuf format, trace processor, Perfetto UI, Python API, Format converters. Chrome JSON, Other profiling formats"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;The recording tools for Perfetto consist of 1) an &lt;a href="https://perfetto.dev/docs/getting-started/in-app-tracing"&gt;SDK for C++ apps&lt;/a&gt; 2) a &lt;a href="https://perfetto.dev/docs/getting-started/system-tracing"&gt;daemon&lt;/a&gt; that can collect data from ftrace, /proc, /sys, and various kernel interfaces 3) another &lt;a href="https://perfetto.dev/docs/concepts/service-model"&gt;daemon&lt;/a&gt; that amalgamates trace data from multiple processes into a single trace file. These tools all speak the Perfetto &lt;a href="https://perfetto.dev/docs/getting-started/converting"&gt;protobuf format&lt;/a&gt;, a high-performance trace format designed to be very efficient to write but not to analyze or consume directly.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s where the &lt;a href="https://perfetto.dev/docs/analysis/trace-processor"&gt;trace processor&lt;/a&gt; comes in. It&amp;rsquo;s a C++ library that parses the protobuf format, but also a bunch of other widely used trace formats. It exposes this data via an SQL query interface to any embedding program including &lt;a href="https://perfetto.dev/docs/visualization/perfetto-ui"&gt;Perfetto UI&lt;/a&gt; (which is what most of this talk is about) and also to the &lt;a href="https://perfetto.dev/docs/analysis/trace-processor-python"&gt;Python API&lt;/a&gt; if you want to do ad-hoc scripting or analysis in Python.&lt;/p&gt;
&lt;p&gt;There are also very common tracing/profiling formats used by the Linux community: perf.data, ftrace text format, Firefox profiler format, and many others. Perfetto &lt;a href="https://perfetto.dev/docs/getting-started/other-formats"&gt;supports&lt;/a&gt; quite a few of those directly. There&amp;rsquo;s also the Chrome JSON format (AKA the &lt;a href="https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU/preview?tab=t.0"&gt;Trace Event Format&lt;/a&gt;) which is a simpler interchange format. It&amp;rsquo;s not the most efficient to read or write, but it does the job for a lot of use cases.&lt;/p&gt;
&lt;p&gt;Often people write &lt;a href="https://perfetto.dev/docs/getting-started/converting"&gt;converters&lt;/a&gt;. They have their own tracing format, maybe proprietary, maybe open source but something we don&amp;rsquo;t know about, and it&amp;rsquo;s very common that people convert to one of the formats we understand, most commonly our protobuf or Chrome JSON formats.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-04.jpg" alt="Slide showing Perfetto UI architecture with web-based trace visualizer features"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;The &lt;a href="https://perfetto.dev/docs/visualization/perfetto-ui"&gt;Perfetto UI&lt;/a&gt; is fundamentally a web-based trace visualizer, combining timeline visualization, user-driven selection/aggregation, and SQL queries all in one interface. Because it has the trace processor as a backend, it works with a bunch of different trace formats.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s very important to note that even though the Perfetto UI is web-based, everything happens inside your browser and trace data never leaves your system. You can even &lt;a href="https://perfetto.dev/docs/contributing/ui-getting-started"&gt;build it&lt;/a&gt; and host it yourself on any static server: we&amp;rsquo;ve made it extremely easy to do so!&lt;/p&gt;
&lt;p&gt;At the start of 2025, we actually moved our whole development to GitHub. In the past, we used to develop on Android and GitHub was just a mirror. That&amp;rsquo;s no longer the case, GitHub is actually where we develop and take pull requests.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-05.jpg" alt="Slide introducing the fractal renderer demo program with Vulkan and multi-threading"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;Most of this talk, I&amp;rsquo;m going to spend actually showing you how you can use the Perfetto UI to debug performance issues on Linux. I don&amp;rsquo;t want to show you an Android trace which needs a lot of context about how the Android system works and so you think, &amp;ldquo;oh, that was cool, but I didn&amp;rsquo;t really understand what was happening.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;So to make this talk more approachable, I wrote a &lt;a href="https://github.com/LalitMaganti/fractal-renderer-vk"&gt;straightforward demo program&lt;/a&gt; you can look at yourself! So it&amp;rsquo;s obviously not a production system but I&amp;rsquo;ve tried to make it as representative of the sort of issues we use Perfetto for every day.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s a Rust program which generates a Julia set and visualizes it over time. The technologies I used: &lt;a href="https://vulkano.rs/"&gt;Vulkan&lt;/a&gt;, GPU rendering and also multi-threaded CPU computation. So how it works is that computation of various parameters is happening on background threads, and then that&amp;rsquo;s being passed to the main thread for rendering.&lt;/p&gt;
&lt;p&gt;And then, for demonstration purposes, there is a performance bug; rendering should run at 60 FPS, but every so often, the frame rate drops dramatically. Here&amp;rsquo;s what that looks like:&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/test.avif" alt="Animated visualization of Julia set fractal renderer showing frame rate drops"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;The code is on &lt;a href="https://github.com/LalitMaganti/fractal-renderer-vk"&gt;GitHub&lt;/a&gt; and if you&amp;rsquo;re interested in following along. The traces are there as well - you don&amp;rsquo;t have to collect the traces yourself, but you can if you want. All the instructions and information is in the README.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-06.jpg" alt="Slide titled &amp;ldquo;Suspicion #1: Maybe it&amp;rsquo;s a CPU problem?&amp;rdquo; with perf command examples"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;So the first suspicion we may have is that maybe it&amp;rsquo;s some CPU problem. A lot of engineers I know would reach for perf immediately whenever they see a problem like this. The main reason is that if perf can capture the problem, they can go straight to the line of code without needing to spend time debugging using more complex approaches.&lt;/p&gt;
&lt;p&gt;You can record a perf profile with &lt;code&gt;perf record -k mono -g ./fractal_renderer&lt;/code&gt;. The standard post-processing step which I think a lot of people do would be to &lt;a href="https://github.com/brendangregg/FlameGraph"&gt;generate&lt;/a&gt; an SVG flame graph out of this with &lt;code&gt;perf script | stack-collapse-perf.pl --all | flamegraph.pl &amp;gt; fractal-frame.svg&lt;/code&gt;. Here&amp;rsquo;s how that looks like for our perf profile:&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/fractal.svg" alt="Interactive flame graph showing CPU profiling data with thread names and function call stacks dominated by cos and sin operations"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;See also the &lt;a href="/img/fractal.svg"&gt;interactive version of the profile&lt;/a&gt;&lt;/center&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The flame graph shows the thread names at the bottom, and then stacked above are the call stacks of what the program is doing. The width represents how much time is spent in each function. In this case, the worker threads are spending most of their time in &lt;code&gt;cos&lt;/code&gt; and &lt;code&gt;sin&lt;/code&gt; operations, doing math computation. Exactly what you&amp;rsquo;d expect for this type of program. &lt;em&gt;Aside: Interestingly demangling didn&amp;rsquo;t seem to work for Rust out of the box which I find a bit unusual but I didn&amp;rsquo;t dig too much into why this was.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Looking at this, nothing really stands out. There&amp;rsquo;s no smoking gun that immediately reveals the problem. But here&amp;rsquo;s the fundamental limitation: the flame graph aggregates data across the entire trace, losing the time dimension. The performance problem we&amp;rsquo;re seeing happens every 2-3 seconds - brief drops in an otherwise normally functioning program.&lt;/p&gt;
&lt;p&gt;The flame graph shows me the aggregate, dominated by the correct behavior, making it nearly impossible to spot those occasional problematic moments. So how do I find the places where it&amp;rsquo;s doing the wrong thing?&lt;/p&gt;
&lt;p&gt;Well, that&amp;rsquo;s maybe where Perfetto can help you out a little bit! The thing I find lots of people don&amp;rsquo;t know is that perf actually &lt;em&gt;preserves&lt;/em&gt; timestamp information about when samples were taken: many tools drop this information but Perfetto is pretty good at showing that to you. I just need post-process the trace with &lt;code&gt;perf script &amp;gt; fractal.perftext&lt;/code&gt; to generate a text version of the profile which we can then open in the &lt;a href="https://ui.perfetto.dev"&gt;Perfetto UI&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My demo talking through how to open the perf profile and navigate it starts at &lt;a href="https://youtu.be/VzTwul2Qb3g?t=551"&gt;9:11&lt;/a&gt; in the video.&lt;/em&gt;&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot3.png" alt="Perfetto UI screenshot showing perf profile timeline with thread tracks and CPU sample markers across time axis"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=590"&gt;9:50&lt;/a&gt; in the video&lt;/center&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The x-axis of what you&amp;rsquo;re seeing is time. And every horizontal line represents a thread: we call these lanes &amp;ldquo;tracks&amp;rdquo;. And each of the small arrowheads on the tracks are CPU samples for the associated thread.&lt;/p&gt;
&lt;p&gt;The behavior of the worker threads over time is really interesting. Most of the lifetime of the program, it&amp;rsquo;s doing continuous computation, basically. And then there&amp;rsquo;s this period of time in the middle where there&amp;rsquo;s this very interesting staircase pattern where it seems like only one thread is running at any one time: we&amp;rsquo;ll want to keep a note of this as this is quite important.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot4.png" alt="Perfetto UI screenshot demonstrating area selection feature with timeline selection and flame graph visualization below"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=637"&gt;10:37&lt;/a&gt; in the video&lt;/center&gt;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;One very cool thing about Perfetto is that it allows me to quickly generate visualizations of whatever I&amp;rsquo;m selecting on the timeline. We call this &amp;ldquo;area selection&amp;rdquo; and it&amp;rsquo;s where I drag my mouse and select a region both horizontally and vertically. This is on one track but even &lt;em&gt;across&lt;/em&gt; multiple tracks. So in the timeline it shows me the selection I&amp;rsquo;m making at the top, plus a flame graph at the bottom representing the aggregation of the samples in just that time period.&lt;/p&gt;
&lt;p&gt;The key advantage is that I can look at individual regions of time interactively. You can also do this with &lt;code&gt;flamegraph.pl&lt;/code&gt; but you need to pre-filter the data to what you&amp;rsquo;re looking for. This assumes you already know what the problem is before you find it. I always find that a bit counterintuitive. I prefer this view where I can see everything first, then decide &amp;ldquo;I want to look at this specific part in more detail.&amp;rdquo; That&amp;rsquo;s what Perfetto lets me do.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot5.png" alt="Perfetto UI screenshot showing enhanced flame graph with zoom, frame hiding, regex search, and bottom-up view features"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=751"&gt;12:31&lt;/a&gt; in the video&lt;/em&gt;&lt;/center&gt;&lt;/p&gt;
&lt;p&gt;So now on to the flame graph itself: in the last year, I spent a bunch of time on improving the flame graph visualization in Perfetto UI. At Google, we have an internal tool called pprof (related to but not the same as the open-source one), and I&amp;rsquo;ve always loved the visualization it has. So I worked to make Perfetto&amp;rsquo;s flame graph look and behave very similarly.&lt;/p&gt;
&lt;p&gt;There are a bunch of features here. You can zoom in like most flame graph, but you can also say &amp;ldquo;I only want to look at the stack starting from this point&amp;rdquo; — it gets rid of everything above and starts fresh from there. If I don&amp;rsquo;t want to see a particular function like &lt;code&gt;sin&lt;/code&gt;, I can just hide the frame and it gets merged into its parent. The search is regex-based so I can also just type the specific things I&amp;rsquo;m interested in: it&amp;rsquo;s pretty powerful.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also something I don&amp;rsquo;t think I&amp;rsquo;ve seen in other visualizers (I might be wrong, please do correct me!): what I call &amp;ldquo;bottom up&amp;rdquo; view. Imagine you take all the leaves across your entire program and visualize who&amp;rsquo;s calling into them. If you have a leaf function that&amp;rsquo;s called everywhere in your program but reached from many different places, it&amp;rsquo;ll be split across your flame chart. This is the inverse — you&amp;rsquo;re looking at the leaves and asking &amp;ldquo;who is calling me?&amp;rdquo; This is particularly useful when you&amp;rsquo;re trying to optimize very low-level functions that are always at the leaf level — things like memcpy or hashmap operations.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-07.jpg" alt="Slide titled &amp;ldquo;Suspicion #2: Sleeping on what?&amp;rdquo; discussing need for scheduler trace analysis"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;So our main takeaway is that our worker threads all had gaps in their CPU execution in a staircase pattern. This means the threads weren&amp;rsquo;t actually on the CPU and that&amp;rsquo;s usually a sign that they&amp;rsquo;re sleeping.&lt;/p&gt;
&lt;p&gt;But sleeping on what? Locks? Disk? Network? What exactly is happening? To answer that, I need a scheduler trace to show me the wake-up patterns, who&amp;rsquo;s being scheduled when, what type of sleep threads are in, that sort of thing.&lt;/p&gt;
&lt;p&gt;We recently added support for trace-cmd&amp;rsquo;s text format in Perfetto (&lt;em&gt;aside: &lt;a href="https://github.com/google/perfetto/issues/1819"&gt;there&amp;rsquo;s&lt;/a&gt; an open issue to support the binary format too!&lt;/em&gt;). For this demo, I&amp;rsquo;m collecting sched_switch and sched_waking events with &lt;code&gt;sudo trace-cmd record -e sched:sched_switch -e sched:sched_waking -C mono ./fractal-renderer-vk&lt;/code&gt; and we can open it in the UI once we do &lt;code&gt;trace-cmd report -N &amp;gt; fractal.sched&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The demo showing off scheduler traces starts at &lt;a href="https://youtu.be/VzTwul2Qb3g?t=916"&gt;15:16&lt;/a&gt; in the video.&lt;/em&gt;&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot6.png" alt="Perfetto UI screenshot showing scheduler trace with CPU scheduling tracks at top, white regions indicating idle time"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=950"&gt;15:49&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;So this is what a scheduler trace looks like. Again as before, the x-axis is time. At the top of the trace, you&amp;rsquo;ve got the CPU scheduling tracks. For each CPU, they tell us what was scheduled on that CPU at each point in time. The white regions are where there&amp;rsquo;s nothing scheduled. As I zoom in, you start to see information like the name of the thread that&amp;rsquo;s scheduled, the TID of the thread, and so on. This is the CPU view of things, looking at it from a CPU perspective.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot7.png" alt="Perfetto UI screenshot showing thread state tracks with dark green for running, light green for runnable, and white for interruptible sleep"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1008"&gt;16:48&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;There&amp;rsquo;s also a thread perspective on scheduling: what we call this &amp;ldquo;thread state&amp;rdquo;. This shows the scheduling state of each thread at every point in time. Dark green represents the thread running, light green represents runnable (the thread is on the runqueue so it&amp;rsquo;s eligible to run, but the scheduler hasn&amp;rsquo;t actually let it run yet), and white represents interruptible sleep (S sleep). There are also orange regions for uninterruptible sleep (D sleep), though that doesn&amp;rsquo;t show up much in this trace.&lt;/p&gt;
&lt;p&gt;You can again see a very clear staircase-like pattern. Again, only one thread seems to be running at any one time. You&amp;rsquo;re maybe getting the sense of what the problem might be at this point.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot8.png" alt="Perfetto UI screenshot showing area selection on CPU track with tables displaying time spent by each thread"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1063"&gt;17:43&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;Like with the perf visualization, you can do area selections here too. I can select a region on a CPU and get tables showing time spent by each thread (shown in the screenshot above). I can also do this for thread state, you can also see that runnable time is basically negligible compared to running and sleeping, which is where most of the time is being spent.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot9.png" alt="Perfetto UI screenshot showing scheduler visualizations including run queue length and active CPU count metrics"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1092"&gt;18:12&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;There are also scheduler-specific visualizations like run queue length and active CPU count. You can see in the problematic region, the active CPU count also went down, which makes sense.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-08.jpg" alt="Slide titled &amp;ldquo;Suspicion #3: What is the program doing?&amp;rdquo; discussing need for application-level visibility"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;So the scheduler trace shows when threads are sleeping, but it&amp;rsquo;s not telling us why. What was the program doing in that region? What code was it running? The sequential pattern suggests some sort of serialization—something is causing only one thread to run at a time. But we need application-level visibility.&lt;/p&gt;
&lt;p&gt;Since this program was written in Rust, I just used the off-the-shelf &lt;a href="https://docs.rs/tracing/latest/tracing/"&gt;&lt;code&gt;tracing&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://docs.rs/tracing-perfetto/latest/tracing_perfetto/"&gt;&lt;code&gt;tracing-perfetto&lt;/code&gt;&lt;/a&gt; crates; I did have to make some small &lt;a href="https://github.com/LalitMaganti/tracing-perfetto"&gt;modifications&lt;/a&gt; to tracing-perfetto that I&amp;rsquo;m hoping to upstream. These output a Perfetto trace without needing to use the Perfetto SDK. These libraries are also maintained by others: we don&amp;rsquo;t have any hand in them.&lt;/p&gt;
&lt;p&gt;All I needed to do was integrate these into my program and then add a command line switch to write out the collected trace to a file. So I just do &lt;code&gt;./fractal_renderer --trace fractal.pftrace&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;My demo for app tracing starts at &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1177"&gt;19:37&lt;/a&gt; in the video.&lt;/em&gt;&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot10.png" alt="Perfetto UI screenshot showing app trace with userspace instrumentation slices on thread tracks representing program activity"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1206"&gt;20:06&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;Looking at the app trace: as before x-axis is time and each of the tracks represents a thread. Instead of scheduling activity or CPU stack samples, this time the tracks is userspace instrumentation of the program itself. Each of these rectangles is a named period of time representing what the program was doing at that time.&lt;/p&gt;
&lt;p&gt;There are lots of different names for these in the tracing world (e.g. spans, intervals) but we call these &amp;ldquo;slices&amp;rdquo;. The main thread is rendering frames as you would expect and the workers are each computing tiles of that frame, which eventually feed back to the main thread and sync to the GPU.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot11.png" alt="Perfetto UI screenshot showing flow visualization with causal links between render frame and worker thread operations"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1425"&gt;23:42&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;There&amp;rsquo;s also this thing called flows, which shows the causal links between different pieces. Render frame is calling into all these places and causing them to happen. It&amp;rsquo;s sort of similar to wakeup graphs but for user space: basically, this is what caused me to start running this function.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot12.png" alt="Perfetto UI screenshot zoomed into problematic region showing 1.8 second duration for update adaptive quality operation"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1431"&gt;23:51&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;So you can see very clearly there&amp;rsquo;s a normal region and then a region where things are taking 1.8 seconds: almost certainly the cause of the frame drops.&lt;/p&gt;
&lt;p&gt;And if we look at the slice in question, it seems to be doing something called &amp;ldquo;update adaptive quality.&amp;rdquo; Basically, I wrote some code to dynamically change the rendering quality based on frame rate. If I&amp;rsquo;m running faster, I can render at higher quality. If I&amp;rsquo;m running slower, I can do lower quality. That&amp;rsquo;s what this adaptive quality thing is supposed to do.&lt;/p&gt;
&lt;p&gt;But clearly something has gone wrong. I&amp;rsquo;m causing frame drops because of updates to adaptive quality. A little bit ironic, to say the least. Now we know what the program is actually doing during that time span.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-09.jpg" alt="Slide titled &amp;ldquo;Putting it all together: The merged trace&amp;rdquo; explaining trace merging feature"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;So now we&amp;rsquo;ve looked at three different sources of debugging data separately. In perf, we got told there are few or no CPU samples and weird staircase patterns. In ftrace, we saw only one worker seems to be active and the rest are sleeping in interruptible sleep. And in app tracing, we see it&amp;rsquo;s because of adaptive quality updates that workers are working on, and they shouldn&amp;rsquo;t be doing it this way.&lt;/p&gt;
&lt;p&gt;Obviously, the theory is that it&amp;rsquo;s all the same problem. But we can confirm this theory with a very new (and still experimental) feature of the UI: trace merging.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: as this part of Perfetto is still experimental, if you want to try it yourself, you need to go to plugins and enable the &amp;ldquo;MultiTraceOpen&amp;rdquo; plugin. Also, unlike I what say in the talk, you do &lt;strong&gt;not&lt;/strong&gt; need to be on Autopush as all the features I showed in my talk are now available in Stable.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The demo showing off merged traces starts at &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1472"&gt;24:32&lt;/a&gt; in the video.&lt;/em&gt;&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot13.png" alt="Perfetto UI screenshot showing merged trace with CPU scheduling tracks, stack samples, and app instrumentation on single timeline"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1544"&gt;25:44&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;This is now the merged trace file containing all the different information. You can see the CPU scheduling tracks like we had in the scheduler trace, the stack samples like we had in the perf trace, and the app instrumentation, all beside each other on one timeline.
You can very clearly see the adaptive quality update running, then this period where it ran, and then it was done. It&amp;rsquo;s very cool to be able to see the pattern of how my program is sleeping and where and what it was running on one timeline.&lt;/p&gt;
&lt;p&gt;The thing is, you&amp;rsquo;ve always been able to do this with Perfetto if you collect a Perfetto trace with all of these integrated. The new capability is that this is now being brought to traces you didn&amp;rsquo;t collect with Perfetto. Collect it with whatever you like, and we&amp;rsquo;ll still manage to do this visualization. That&amp;rsquo;s the new capability we have.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot14.png" alt="Perfetto UI screenshot showing query interface with pivot tables and filter options for trace data analysis"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1798"&gt;29:58&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;There&amp;rsquo;s also a query interface which we&amp;rsquo;ve been building out recently and there are some pretty powerful things you can do with these tables. As well as the flat tables of data, there&amp;rsquo;s also a more dynamic pivot table and if I click the arrow on the left, I can get into a detailed filter table, similar to a spreadsheet but optimize for things people want to do on traces.&lt;/p&gt;
&lt;p&gt;For example, by doing an area select, I can first get a list of all the events that happened during a time region. Then using the table I can filter for things - say I only care about slices longer than some duration, like 319 microseconds for whatever reason. I can click and add a filter for things greater than this.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/screenshot15.png" alt="Perfetto UI screenshot demonstrating Show Debug Track feature with filtered events visualized as timeline track"&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Screenshot from &lt;a href="https://youtu.be/VzTwul2Qb3g?t=1811"&gt;30:11&lt;/a&gt; in the video&lt;/em&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;There&amp;rsquo;s also a feature called &amp;ldquo;Show Debug Track&amp;rdquo; that&amp;rsquo;s also very powerful. The table shows rows with timestamped duration information, and this feature lets you visualize that data as a track on the timeline. It adds a track at the top showing the regions of time where these events happened—in this case, where ComputeTile took longer than a certain threshold.&lt;/p&gt;
&lt;p&gt;This is particularly useful for correlation analysis. For example, in Android, we&amp;rsquo;re often looking at system A and trying to understand its effect on system B. We find all the places where one thing is slow, then look for correlations. Being able to see a track in the UI where you can quickly have context and say &amp;ldquo;oh, during this period of time, this other thing was happening&amp;rdquo; is invaluable. It&amp;rsquo;s probably one of our most-used features.&lt;/p&gt;
&lt;p&gt;You can also copy the SQL query from these tables and start doing your own aggregations on top of it. This eases the burden of starting with a completely blank canvas. Instead of wondering &amp;ldquo;where do I even start querying my data? What tables is my data in?&amp;rdquo;, the UI gives you a starting query that you can build on without needing to write something from scratch.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-10.jpg" alt="Slide titled &amp;ldquo;The root cause&amp;rdquo; summarizing findings about dynamic quality updates causing frame drops"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;So the dynamic quality updates were stopping the world. Perf profiling showed the problem, scheduler traces found the sleeping pattern, app tracing confirmed it was the quality adjustment code, and the single timeline view let me see everything happening at once.&lt;/p&gt;
&lt;p&gt;Interestingly, I tried to fix this by removing the lock contention—which I thought was the bug I had introduced. But it turns out I shouldn&amp;rsquo;t have been doing this code in the workers in the first place. Even after removing the lock contention, just the CPU activity of doing that work was enough to cause frame drops. The right solution was to move it to a background thread. As part of debugging this for the demo, I discovered something even better that I could be doing.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-11.jpg" alt="Slide titled &amp;ldquo;Perfetto in the Open-Source Community&amp;rdquo; listing projects using Perfetto including Mesa, VizTracer, pthread_trace, magic-trace, sched-analyzer, and systing"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.perfetto.dev"&gt;Android and Chrome&lt;/a&gt; are our bread and butter—that&amp;rsquo;s what we officially support as a team, and why Google staffs us. But there are many other interesting uses.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://docs.mesa3d.org/perfetto.html"&gt;Mesa&lt;/a&gt; uses Perfetto as one of its tracing systems. One thing I could have shown is collecting a Mesa trace alongside all the other traces we looked at—you can actually see what the GPU is doing at the same time, which would have been very cool, but I just didn&amp;rsquo;t have time.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/gaogaotiantian/viztracer"&gt;VizTracer&lt;/a&gt; is function tracing for Python, similar to uftrace, but you don&amp;rsquo;t have to recompile anything or do anything special.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/dsharlet/pthread_trace"&gt;pthread_trace&lt;/a&gt; is for visualizing pthread mutex contention. The author has a very efficient way of doing this and writes protobuf using heavy use of constexpr to make it very low overhead. It&amp;rsquo;s a very interesting project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/janestreet/magic-trace"&gt;magic-trace&lt;/a&gt; uses Intel Processor Trace for function-level tracing at the processor level with lower overhead. They wrote a converter from that to the Perfetto format so you can visualize that data in Perfetto.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://github.com/qais-yousef/sched-analyzer"&gt;Qais Yousef&amp;rsquo;s sched-analyzer&lt;/a&gt; enriches Perfetto traces with scheduler internals. It&amp;rsquo;s a very cool project that I find particularly interesting.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;a href="https://gist.github.com/johnstultz-work/0ec4974e0929c4707bfd89c876ae4735"&gt;John Stultz&amp;rsquo;s all-in-one Perfetto recording script&lt;/a&gt; for kernel developers addresses a common complaint: &amp;ldquo;Perfetto is so complicated, I don&amp;rsquo;t know what events I want to record.&amp;rdquo; He just wrote a script that configures everything for you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, &lt;a href="https://josefbacik.github.io/kernel/systing/debugging/2025/05/08/systing.html"&gt;Josef Bacik&amp;rsquo;s systing&lt;/a&gt; is a bit experimental but fascinating. He re-implemented tracing daemons on top of BPF instead of Perfetto&amp;rsquo;s native implementation, combining BPF-based tracing with perf stack traces in a single binary. He has a blog post explaining why. I thought it was a fascinating use of Perfetto.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-12.jpg" alt="Slide encouraging users to try the demo program and record their own traces"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;If you want to try this yourself, you can use the &lt;a href="https://github.com/LalitMaganti/fractal-renderer-vk"&gt;demo program I&amp;rsquo;ve provided&lt;/a&gt;. But even better: record traces on your own programs. Open scheduler traces and perf at the same time. Instrument your program, convert it to Perfetto format, and visualize everything together.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-13.jpg" alt="Slide promoting comprehensive tutorial for converting ad-hoc timestamped data to Perfetto format"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;If you want to convert ad-hoc timestamped data to Perfetto, we wrote a &lt;a href="https://perfetto.dev/docs/getting-started/converting"&gt;comprehensive tutorial&lt;/a&gt; with Python snippets for everything you might want to visualize. It covers all the features I showed and how to write Python code to generate them. We have a library for writing these traces: besides this one library, you don&amp;rsquo;t need to install anything else. You can go ahead and convert your own data to Perfetto.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-14.jpg" alt="Slide welcoming contributions and pull requests to the Perfetto project"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;p&gt;We&amp;rsquo;re very happy to accept contributions and review pull requests pretty quickly. We&amp;rsquo;ve had a lot of open-source contributors over the years and have been quite lucky with some very high-quality contributions. If you want to contribute yourself or have a feature you feel is missing, send it our way.&lt;/p&gt;
&lt;center&gt;
&lt;p&gt;&lt;img src="/img/perfetto-swiss-army-15.jpg" alt="Final slide with contact information and links to demo repo, documentation, and Perfetto UI"&gt;&lt;/p&gt;
&lt;/center&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Demo repo: &lt;a href="https://github.com/LalitMaganti/fractal-renderer-vk"&gt;github.com/LalitMaganti/fractal-renderer-vk&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Learn more: &lt;a href="https://docs.perfetto.dev"&gt;docs.perfetto.dev&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Perfetto UI: &lt;a href="https://ui.perfetto.dev"&gt;ui.perfetto.dev&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Contact: &lt;a href="mailto:lalitm@google.com"&gt;lalitm@google.com&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;</content></entry><entry><title>On Perfetto, Open Source, and Company Priorities</title><link href="https://lalitm.com/perfetto-oss-company-prio/" rel="alternate" type="text/html"/><id>https://lalitm.com/perfetto-oss-company-prio/</id><published>2025-10-17T15:00:00+01:00</published><updated>2025-10-17T15:00:00+01:00</updated><summary type="text">I recently stumbled across this post on lobste.rs about a project called traceboot which allows visualizing the Linux boot process using lightweight ftrace events and Perfetto. The author had some commentary about their experience trying to order tracks in Perfetto:
Ordering tracks with perfetto has been ridiculously complicated. It has taken the majority of the time of this project! Upstream’s answers are basically that the main user is Android (Perfetto is a Google project) so others come second if at all. While I get the reasons to do so, I read that as a caution against depending on it as a third-party. Google is notorious for (…) completely killing projects
Honestly? All of these points are right:
It is really unfortunate that doing something so simple took so much effort. It’s true that external users are supported at a lower priority than Android users. It’s also the case that Google has historically wound down projects when priorities shift The good news is that we just landed support for trace writers to specify explicitly how traces should be ordered with the JSON format in Perfetto without any extreme workarounds! This feature is already available on the “Canary” UI channel and on “Stable” within 3-4 weeks.</summary><content type="html">&lt;p&gt;I recently stumbled across
&lt;a href="https://lobste.rs/s/fl7ly9/traceboot_precise_lightweight_tracing"&gt;this post on lobste.rs&lt;/a&gt;
about a project called
&lt;a href="https://codeberg.org/SpecialSnowflake/traceboot"&gt;traceboot&lt;/a&gt; which allows
visualizing the Linux boot process using lightweight ftrace events and Perfetto.
The author had
&lt;a href="https://codeberg.org/SpecialSnowflake/traceboot#woes-with-a-rant-and-ideas-last-paragraph-of-this-section"&gt;some commentary&lt;/a&gt;
about their experience trying to order tracks in Perfetto:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Ordering tracks with perfetto has been ridiculously complicated. It has taken
the majority of the time of this project! Upstream&amp;rsquo;s answers are basically
that the main user is Android (Perfetto is a Google project) so others come
second if at all. While I get the reasons to do so, I read that as a caution
against depending on it as a third-party. Google is notorious for (&amp;hellip;)
completely killing projects&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Honestly? All of these points are right:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is really unfortunate that doing something so simple took so much effort.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s true that external users are supported at a lower priority than Android
users.&lt;/li&gt;
&lt;li&gt;It&amp;rsquo;s also the case that Google has historically wound down projects when
priorities shift&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The good news is that we just
&lt;a href="https://github.com/google/perfetto/pull/3273"&gt;landed&lt;/a&gt; support for trace
writers to specify explicitly how traces should be ordered with the JSON format
in Perfetto without any extreme workarounds! This feature is already available on
the &amp;ldquo;Canary&amp;rdquo; UI channel and on &amp;ldquo;Stable&amp;rdquo; within 3-4 weeks.&lt;/p&gt;
&lt;p&gt;&lt;img src="/img/perfetto-json-ordering.png" alt="Json Track ordering in action!"&gt;&lt;/p&gt;
&lt;p style="text-align: center;"&gt;&lt;i&gt;process_sort_index and thread_sort_index are now respected by Perfetto!&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;So we solved the imediate problem! But here&amp;rsquo;s what concerns me: I found this
issue totally by chance while browsing lobste.rs. There were several issues
filed in the Perfetto repo over several years asking for this feature
(&lt;a href="https://github.com/google/perfetto/issues/555"&gt;#555&lt;/a&gt;,
&lt;a href="https://github.com/google/perfetto/issues/764"&gt;#764&lt;/a&gt;,
&lt;a href="https://github.com/google/perfetto/issues/378"&gt;#378&lt;/a&gt;) but none of them managed
to communicate to us just how painful this issue was and the lengths people were
willing to go to work around it. Seeing the annoyance in such stark terms
definitely put into perspective the importance of this feature.&lt;/p&gt;
&lt;p&gt;You might be thinking: &amp;ldquo;surely if a feature is requested that much, you should
consider it important and work on fixing it&amp;rdquo;. And for a general open source
project, I&amp;rsquo;d agree with you. But there are some unique challenges which come
with being an open source project under the umbrella of a Big Tech company.&lt;/p&gt;
&lt;p&gt;It&amp;rsquo;s all about how the Perfetto team fits into Google. We&amp;rsquo;re part of the Android
Diagnostics organization whose mandate is to build tools and systems to
root-cause problems on Android. Performance debugging would be impossible
without a system-wide tracing tool like Perfetto (which is why we even exist at
all!). But because we are funded by Android, it&amp;rsquo;s also expected that we
prioritise its needs.&lt;/p&gt;
&lt;p&gt;However, I, and others in the team, also deeply believe in the mission of open
source. We want to build something useful not just for Android engineers inside
Google but for the broader community as a whole. That creates a tension we
struggle with constantly: we value external users and their use cases, but the
reality of our team&amp;rsquo;s existence means Google&amp;rsquo;s priorities often win out.&lt;/p&gt;
&lt;p&gt;This tension is compounded by the fact that different types of users want
fundamentally different things from Perfetto. If you use Perfetto purely as a
trace viewer, the expectation is &amp;ldquo;I write the trace file and the Perfetto UI
should display exactly what I want&amp;rdquo;. But Android OS and app developers expect
&amp;ldquo;Perfetto should intelligently figure out what is most important in my trace and
highlight that to me.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;In the early days of Perfetto, we took on a lot of technical debt to meet the
immediate needs of Android developers. We built an &amp;lsquo;intelligence&amp;rsquo; layer that
automatically reordered tracks based on Android-specific heuristics. This was
great for our primary users, but it made it immensely difficult to later support
features for the &amp;lsquo;pure trace viewer&amp;rsquo; use case, like manually ordering tracks via
the JSON file, because the Android logic would always override it.&lt;/p&gt;
&lt;p&gt;The good news is that over the past 12 months, we&amp;rsquo;ve spent a lot of time trying
to decouple the &amp;ldquo;core&amp;rdquo; layer of Perfetto UI from the &amp;ldquo;intelligence&amp;rdquo; layer for
Android. This separation means we can now preserve the original intent from the
trace format (like &amp;ldquo;this track should be ordered here&amp;rdquo;) all the way to the UI,
rather than having it get lost or overridden by Android-specific heuristics
along the way. This allows us to build features like JSON track ordering without
creating impossible-to-maintain code that hurts us in the long run.&lt;/p&gt;
&lt;p&gt;It’s also understandable that people worry about Google’s project longevity.
Thankfully Perfetto&amp;rsquo;s core design principles of a) being 100% open source b)
fully client side significantly mitigate these risks. For example, running the
Perfetto UI is as simple as cloning the repo, running
&lt;code&gt;tools/install-build-deps --ui &amp;amp;&amp;amp; ui/run-dev-server&lt;/code&gt; and navigating to
&lt;code&gt;localhost:10000&lt;/code&gt;. The site is entirely static capable of being hosted anywhere.
There is no &amp;ldquo;backend server&amp;rdquo; to be taken down, everything runs in your browser
only. So if for some reason, if the Perfetto team was to disappear, it would be
trivially easy to host it yourself or even fork the project completely and start
maintaining it.&lt;/p&gt;
&lt;p&gt;With all that said, here are my takeaways from this experience:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;There are still painpoints lurking in issues from the past that external
users are still hitting today. We should maybe do a pass over historical bugs
and figure out what issues are hiding there.&lt;/li&gt;
&lt;li&gt;Moving forward, we need to be more transparent and communicative about &lt;em&gt;why&lt;/em&gt;
we&amp;rsquo;re not prioritising a certain issue. I feel our terse replies about our
priorities are giving misleading indications on the value we place on
external users. Maybe we should also be more open about what we &lt;em&gt;are&lt;/em&gt;
prioritizing, even if that means we have to say &amp;ldquo;we&amp;rsquo;re not going to be able
to do much for external users this quarter&amp;rdquo;.&lt;/li&gt;
&lt;li&gt;We also need to set up a regular routine for going through our bug tracker
and checking &amp;ldquo;has anything changed which now makes this bug possible&amp;rdquo; and if
so, we should follow up and post on the issue to make everyone aware of this
and keep bugs up to date.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Here&amp;rsquo;s also where community help would be invaluable: if you&amp;rsquo;re facing an issue
with Perfetto, tell us on &lt;a href="https://github.com/google/perfetto/issues/new"&gt;Github&lt;/a&gt; or on our
&lt;a href="https://groups.google.com/g/perfetto-dev"&gt;mailing list&lt;/a&gt;! Comment on old bugs if
one already exists or file new ones if they don&amp;rsquo;t. Bonus points if you can tell
us why the issue is so painful for you and what you&amp;rsquo;re doing to work around it
if it&amp;rsquo;s not fixed. The more information we have, the better judgement calls we
can make on prioritization.&lt;/p&gt;</content></entry></feed>