<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Ruby on Haseeb Annadamban</title><link>https://haseebeqx.com/tags/ruby/</link><description>Recent content in Ruby on Haseeb Annadamban</description><generator>Hugo -- gohugo.io</generator><language>en</language><copyright>Haseeb Annadamban</copyright><lastBuildDate>Sat, 12 Sep 2026 13:34:28 +0530</lastBuildDate><atom:link href="https://haseebeqx.com/tags/ruby/index.xml" rel="self" type="application/rss+xml"/><item><title>Using speedscope.app to Profile Ruby and Rails</title><link>https://haseebeqx.com/posts/using-speedscope-to-profile-ruby-and-rails/</link><pubDate>Sat, 12 Sep 2026 13:34:28 +0530</pubDate><guid>https://haseebeqx.com/posts/using-speedscope-to-profile-ruby-and-rails/</guid><description>&lt;p&gt;A request that takes 900 ms tells us &lt;strong&gt;that&lt;/strong&gt; something is slow, but not &lt;strong&gt;where&lt;/strong&gt; the time went. A call-stack profiler supplies the missing data, and &lt;a href="https://www.speedscope.app/"&gt;speedscope&lt;/a&gt; turns that data into an interactive flamegraph.&lt;/p&gt;
&lt;p&gt;This guide is for Rails developers and progresses through three levels:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Quick diagnosis:&lt;/strong&gt; profile one Rails request with rack-mini-profiler.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focused investigation:&lt;/strong&gt; profile a service, script, or job with StackProf.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reliable analysis:&lt;/strong&gt; choose the right sampling mode, reduce noise, and validate an optimization.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;You do not need to learn every speedscope feature before using it. Start with a specific performance question, capture the smallest useful profile, and move to the more advanced techniques only when the first profile cannot answer that question.&lt;/p&gt;
&lt;h2 id="what-is-a-flame-graph"&gt;What is a flame graph?&lt;/h2&gt;
&lt;p&gt;Before looking at a flame graph, it helps to understand a &lt;strong&gt;call stack&lt;/strong&gt;. A call stack is the chain of methods that led to the code currently running. For example, Rails might handle a request through this chain:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ReportsController#show
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Reports::Monthly#render
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ReportSerializer#as_json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Here, the controller called the report object, which then called the serializer.&lt;/p&gt;
&lt;h3 id="tracing-sampling-and-continuous-profiling"&gt;Tracing, sampling, and continuous profiling&lt;/h3&gt;
&lt;p&gt;A profiler must collect information about what the program is doing. There are two common ways to collect it: tracing and sampling.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;tracing profiler&lt;/strong&gt; follows the program and records every method call and return. It is like recording a video: we can see the full sequence of events, how many times each method was called, and how long individual calls took. The trade-off is that recording every event creates a lot of data and can add enough overhead to change the program&amp;rsquo;s performance. Tracing is most useful when that detail is important and the workload is small enough to trace safely.&lt;/p&gt;
&lt;p&gt;A &lt;strong&gt;sampling profiler&lt;/strong&gt; checks the program at regular intervals and records the call stack active at each check. It is like taking photographs instead of recording a complete video. If an expensive method stays active for a long time, it is likely to appear in many photographs. Sampling usually creates less data and adds less overhead, so it works well for longer or realistic workloads. However, it is an estimate: a method that starts and finishes between two samples may not be recorded at all.&lt;/p&gt;
&lt;p&gt;Neither method is always better. Use tracing when you need an exact sequence or call count. Use sampling when you want a lower-overhead picture of where a program spends most of its time. StackProf, which this guide uses, is a sampling profiler.&lt;/p&gt;
&lt;p&gt;You may also hear the term &lt;strong&gt;continuous profiler&lt;/strong&gt;. “Continuous” describes how long the profiler runs, not how it collects data. A continuous profiler stays active in the background and observes an application over hours or days, often in production. Because low overhead is essential for such a long recording, continuous profilers commonly use sampling. By contrast, this guide captures short profiles around one request or operation.&lt;/p&gt;
&lt;p&gt;A profiler might collect hundreds or thousands of call stacks during one request. Reading all those stacks as text would be difficult, so a flame graph combines them into a picture. Flame graphs are most commonly associated with sampled stacks, but tracing profiles can also be displayed with flame-like views when the viewer supports their format.&lt;/p&gt;
&lt;p&gt;Each box in that picture represents a method or function:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A box above another box was called by the one below it.&lt;/li&gt;
&lt;li&gt;The height shows how deeply methods are nested. A tall stack is not necessarily a slow stack.&lt;/li&gt;
&lt;li&gt;The width shows how often a method appeared in the recorded samples. A wide box usually points to a part of the program where a lot of the measured work or waiting occurred.&lt;/li&gt;
&lt;li&gt;The horizontal position does &lt;strong&gt;not&lt;/strong&gt; represent time in an original flame graph. A box on the right did not necessarily run after a box on the left.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Imagine that &lt;code&gt;ReportSerializer#as_json&lt;/code&gt; appears in half of the samples. Its box will occupy about half of the graph&amp;rsquo;s width under that call path. That makes it an obvious place to investigate. It is still only a clue: the serializer may be doing expensive work itself, or it may be waiting for a method that it calls.&lt;/p&gt;
&lt;h3 id="why-were-flame-graphs-invented"&gt;Why were flame graphs invented?&lt;/h3&gt;
&lt;p&gt;Brendan Gregg invented flame graphs while investigating a CPU-performance problem in MySQL. The tools available to him presented two practical problems.&lt;/p&gt;
&lt;p&gt;First, tracing every function call added too much overhead and could change the behavior being measured. Periodic sampling was less intrusive because it recorded only occasional snapshots.&lt;/p&gt;
&lt;p&gt;Second, the results were hard to read. Several seconds of function activity produced a dense visualization or a wall of profiler text. Gregg solved this by placing matching call paths next to one another and merging them. In the original flame graph, paths are ordered alphabetically to make that merging possible. Frequently sampled paths become wide and stand out immediately.&lt;/p&gt;
&lt;p&gt;The boxes were drawn with warm colors because the CPUs were “hot,” and the result looked like flames. The colors in the original design were mainly decorative; a red box was not necessarily slower than a yellow one.&lt;/p&gt;
&lt;h3 id="how-is-speedscope-different-from-the-original-flame-graph"&gt;How is speedscope different from the original flame graph?&lt;/h3&gt;
&lt;p&gt;An original flame graph gives us one combined overview of a profile. It is very good at answering:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Which call paths appeared most often across the whole recording?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Combining matching paths has a trade-off: it removes their original order. We can see that two pieces of work were important, but not which happened first.&lt;/p&gt;
&lt;p&gt;speedscope loads the recorded profile and lets us reorganize the same data into three views:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Time Order&lt;/strong&gt; keeps the stacks in the order they were recorded. Earlier work appears toward the left and later work toward the right. This helps us see phases such as loading records, rendering a template, and serializing JSON. This type of visualization is more precisely called a &lt;strong&gt;flame chart&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Left Heavy&lt;/strong&gt; combines matching call stacks, much like the original flame graph. Instead of sorting paths alphabetically, it puts the widest branches first. This makes the largest areas of work easier to find on the left side of the graph.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sandwich&lt;/strong&gt; replaces the boxes with a table of methods. After selecting a method, we can see which methods called it and which methods it called. This is useful when the same method appears in several different parts of the application.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For example, suppose a request loads database records, performs a calculation, and then creates JSON. &lt;strong&gt;Time Order&lt;/strong&gt; shows when those three phases occurred. &lt;strong&gt;Left Heavy&lt;/strong&gt; shows which call path took the largest share of the profile. &lt;strong&gt;Sandwich&lt;/strong&gt; helps us inspect a particular calculation method wherever it was called.&lt;/p&gt;
&lt;p&gt;speedscope also adds searching, zooming, and panning. It does not collect the measurements itself; it is a viewer for profiles recorded by tools such as StackProf. The important difference is therefore not a new meaning for a flame graph. It is the ability to explore the same profile in several ways instead of relying on one combined view.&lt;/p&gt;
&lt;h2 id="the-profiling-stack"&gt;The profiling stack&lt;/h2&gt;
&lt;p&gt;speedscope is a &lt;strong&gt;profile viewer&lt;/strong&gt;, not a profiler. The tools have separate responsibilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;StackProf&lt;/strong&gt; samples Ruby call stacks.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;rack-mini-profiler&lt;/strong&gt; makes it convenient to profile a Rails request and uses StackProf for flamegraphs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;speedscope&lt;/strong&gt; displays the recorded stacks and helps us explore them.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide uses the tools Rails developers are most likely to encounter, but speedscope is not limited to Ruby. It also supports profiles from rbspy, ruby-prof, browser developer tools, and profilers for other runtimes.&lt;/p&gt;
&lt;p&gt;You might have already used it if you ever used rack-mini-profiler with your Rails app and viewed flamegraph.&lt;/p&gt;
&lt;p&gt;Because StackProf samples rather than traces the program, its output is an estimate rather than an exact accounting of every method call. Lower overhead does not mean zero overhead, so measurements made with the profiler should still be validated without it.&lt;/p&gt;
&lt;p&gt;In a speedscope flamegraph:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;vertical position represents stack depth;&lt;/li&gt;
&lt;li&gt;a frame above another frame was called by it; and&lt;/li&gt;
&lt;li&gt;width represents the frame&amp;rsquo;s weight in the profile.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The meaning of that weight depends on the recording mode. It can represent CPU samples, elapsed-time samples, or sampled allocations. A wide frame is worth investigating, but it does not necessarily indicate a frequently called method or prove that the method itself is the root cause.&lt;/p&gt;
&lt;h2 id="level-1-profile-a-rails-request"&gt;Level 1: Profile a Rails request&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Use this level when:&lt;/strong&gt; one controller action or page is slow and you want a fast first look.&lt;/p&gt;
&lt;p&gt;Add the profiling gems to the application if they are not already present:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Gemfile&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gem &lt;span style="color:#e6db74"&gt;&amp;#34;rack-mini-profiler&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;gem &lt;span style="color:#e6db74"&gt;&amp;#34;stackprof&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;After running &lt;code&gt;bundle install&lt;/code&gt;, start the application and append &lt;code&gt;?pp=flamegraph&lt;/code&gt; to the request URL:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;http://localhost:3000/reports/42?pp=flamegraph
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;rack-mini-profiler profiles that request and returns its speedscope flamegraph instead of the normal response.&lt;/p&gt;
&lt;p&gt;For a JSON endpoint, XHR request, or another request whose response must remain intact, use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;?pp=async-flamegraph
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The request completes normally and rack-mini-profiler stores the result for later viewing. The response also includes the viewer path in the &lt;code&gt;X-MiniProfiler-Flamegraph-Path&lt;/code&gt; header.&lt;/p&gt;
&lt;h3 id="read-the-profile-in-three-passes"&gt;Read the profile in three passes&lt;/h3&gt;
&lt;p&gt;speedscope offers three views, selected with the number keys &lt;code&gt;1&lt;/code&gt;, &lt;code&gt;2&lt;/code&gt;, and &lt;code&gt;3&lt;/code&gt;. Each answers a different question.&lt;/p&gt;
&lt;h4 id="pass-1-sandwich--which-methods-stand-out"&gt;Pass 1: Sandwich — which methods stand out?&lt;/h4&gt;
&lt;p&gt;Start in &lt;strong&gt;Sandwich&lt;/strong&gt; view. It presents a sortable function table and shows the callers and callees of the selected method.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Sort by &lt;strong&gt;self time&lt;/strong&gt; to find methods that do substantial work directly.&lt;/li&gt;
&lt;li&gt;Sort by &lt;strong&gt;total time&lt;/strong&gt; to find methods whose complete subtree is expensive.&lt;/li&gt;
&lt;li&gt;Select an application method and inspect what calls it and what it calls.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A Rails or gem method with high total time and low self time may only be coordinating expensive children. Follow its callees before deciding what to optimize.&lt;/p&gt;
&lt;h4 id="pass-2-left-heavy--which-call-path-dominates"&gt;Pass 2: Left Heavy — which call path dominates?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Left Heavy&lt;/strong&gt; combines identical stacks and places the heaviest child first. Use it to answer, “Which route through the code accounts for most of this profile?”&lt;/p&gt;
&lt;p&gt;Follow a wide branch from a controller, job, or service into its children. Prefer an application-owned frame that leads to the expensive work over a generic framework frame such as &lt;code&gt;process_action&lt;/code&gt;.&lt;/p&gt;
&lt;h4 id="pass-3-time-order--when-did-the-work-happen"&gt;Pass 3: Time Order — when did the work happen?&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Time Order&lt;/strong&gt; preserves the sequence in which stacks were sampled. Use it to separate phases such as loading records, transforming them, and serializing the response.&lt;/p&gt;
&lt;p&gt;Zoom into the relevant phase instead of interpreting the entire request at once. Select a range in the minimap to narrow the profile, then double-click a frame to fit it in the viewport. The horizontal axis reflects sampled weight, so treat it as an execution sequence rather than a precise event timeline.&lt;/p&gt;
&lt;h3 id="what-this-first-profile-can-tell-you"&gt;What this first profile can tell you&lt;/h3&gt;
&lt;p&gt;A request flamegraph can reveal that most observed work sits under serialization, a template partial, a calculation, or a database adapter. It cannot by itself explain every underlying cause.&lt;/p&gt;
&lt;p&gt;For example, a wide database adapter frame in a wall-time profile indicates where the request waited. Use query logs and the database&amp;rsquo;s &lt;code&gt;EXPLAIN (ANALYZE)&lt;/code&gt; to determine why the query was slow. The flamegraph identifies the path to investigate; it is not the final diagnosis.&lt;/p&gt;
&lt;p&gt;One rack-mini-profiler detail matters here: normal SQL timings are not recorded during a flamegraph request, which keeps that timing instrumentation out of the sampled graph. Inspect the SQL list in an ordinary profiled request and inspect call stacks in a flamegraph request.&lt;/p&gt;
&lt;h2 id="level-2-profile-a-focused-operation-with-stackprof"&gt;Level 2: Profile a focused operation with StackProf&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Use this level when:&lt;/strong&gt; you need to isolate a service object, background job, import, or script instead of profiling a complete web request.&lt;/p&gt;
&lt;p&gt;A focused profile is usually easier to interpret because Rails boot, middleware, authentication, and response handling are absent. Put setup outside the profiling block and wrap only the operation related to the question.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# script/profile_report.rb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;require &lt;span style="color:#e6db74"&gt;&amp;#34;stackprof&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;account &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Account&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;find(&lt;span style="color:#ae81ff"&gt;42&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;report &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Reports&lt;/span&gt;&lt;span style="color:#f92672"&gt;::&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;Monthly&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;new(&lt;span style="color:#e6db74"&gt;account&lt;/span&gt;: account)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;StackProf&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;mode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:cpu&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;raw&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;,
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;out&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Rails&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;root&lt;span style="color:#f92672"&gt;.&lt;/span&gt;join(&lt;span style="color:#e6db74"&gt;&amp;#34;tmp/report.dump&amp;#34;&lt;/span&gt;)&lt;span style="color:#f92672"&gt;.&lt;/span&gt;to_s
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; report&lt;span style="color:#f92672"&gt;.&lt;/span&gt;render
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Run it in the Rails environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bin/rails runner script/profile_report.rb
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Then convert the native StackProf dump to JSON:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bundle exec stackprof tmp/report.dump --json &amp;gt; tmp/report.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Open &lt;a href="https://www.speedscope.app/"&gt;speedscope.app&lt;/a&gt; and drag &lt;code&gt;tmp/report.json&lt;/code&gt; onto the page. The hosted viewer processes the profile in the browser rather than uploading it. speedscope&amp;rsquo;s StackProf importer requires StackProf 0.2.11 or newer, and &lt;code&gt;raw: true&lt;/code&gt; preserves the samples needed to reconstruct the stacks.&lt;/p&gt;
&lt;p&gt;You can also write importable JSON directly when keeping a native dump is unnecessary:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;require &lt;span style="color:#e6db74"&gt;&amp;#34;json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;require &lt;span style="color:#e6db74"&gt;&amp;#34;stackprof&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;profile &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;StackProf&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(&lt;span style="color:#e6db74"&gt;mode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:wall&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;raw&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;ImportCustomers&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;call
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;File&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;write(
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;Rails&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;root&lt;span style="color:#f92672"&gt;.&lt;/span&gt;join(&lt;span style="color:#e6db74"&gt;&amp;#34;tmp/import-customers.json&amp;#34;&lt;/span&gt;),
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;JSON&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;generate(profile)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="choose-the-mode-before-interpreting-width"&gt;Choose the mode before interpreting width&lt;/h3&gt;
&lt;p&gt;The mode should match the performance question, not merely the type of code being profiled.&lt;/p&gt;
&lt;h4 id="cpu-mode-where-is-ruby-computing"&gt;CPU mode: where is Ruby computing?&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;StackProf&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(&lt;span style="color:#e6db74"&gt;mode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:cpu&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;raw&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;) { report&lt;span style="color:#f92672"&gt;.&lt;/span&gt;render }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use CPU mode for calculations, parsing, rendering, and serialization. Waiting on the database, network, a lock, or &lt;code&gt;sleep&lt;/code&gt; will not dominate this profile even when it dominates the user&amp;rsquo;s elapsed time.&lt;/p&gt;
&lt;h4 id="wall-mode-where-does-elapsed-time-pass"&gt;Wall mode: where does elapsed time pass?&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;StackProf&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(&lt;span style="color:#e6db74"&gt;mode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:wall&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;raw&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;) { &lt;span style="color:#66d9ef"&gt;ImportCustomers&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;call }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Use wall mode for operations that mix Ruby work with database or network I/O. It is usually the better starting point for end-to-end requests and jobs.&lt;/p&gt;
&lt;h4 id="object-mode-where-are-objects-allocated"&gt;Object mode: where are objects allocated?&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;StackProf&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(&lt;span style="color:#e6db74"&gt;mode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:object&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;raw&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;) { &lt;span style="color:#66d9ef"&gt;ExportCustomers&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;call }
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;In object mode, width represents sampled allocations—not elapsed time and not retained memory. Use it to locate allocation-heavy paths that may increase garbage-collection pressure. Use a memory profiler when the question is which objects remain in memory.&lt;/p&gt;
&lt;p&gt;A practical rule is to begin with wall mode when the complaint is “users wait too long,” then capture a CPU profile if the wall profile points to Ruby computation. This avoids trying to answer an elapsed-time question with CPU-only evidence.&lt;/p&gt;
&lt;h2 id="level-3-produce-a-profile-you-can-trust"&gt;Level 3: Produce a profile you can trust&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Use this level when:&lt;/strong&gt; the graph is noisy, results change between runs, or you need evidence that an optimization worked.&lt;/p&gt;
&lt;h3 id="1-state-one-question"&gt;1. State one question&lt;/h3&gt;
&lt;p&gt;“Why is the application slow?” is too broad. Prefer a question that determines both the boundary and the mode:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Why is report serialization CPU-heavy?&lt;/li&gt;
&lt;li&gt;Where does this import wait on I/O?&lt;/li&gt;
&lt;li&gt;Which path allocates objects while rendering this collection?&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-exclude-unrelated-setup"&gt;2. Exclude unrelated setup&lt;/h3&gt;
&lt;p&gt;Rails boot, constant loading, connection establishment, and cold caches can overwhelm a short recording. Warm up first and prepare records before the block unless startup or record lookup is part of the question.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;report &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;Reports&lt;/span&gt;&lt;span style="color:#f92672"&gt;::&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;Monthly&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;new(&lt;span style="color:#e6db74"&gt;account&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;Account&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;find(&lt;span style="color:#ae81ff"&gt;42&lt;/span&gt;))
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;report&lt;span style="color:#f92672"&gt;.&lt;/span&gt;render &lt;span style="color:#75715e"&gt;# warm caches and lazy initialization&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;StackProf&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;run(&lt;span style="color:#e6db74"&gt;mode&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;:cpu&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;raw&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;out&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;tmp/report.dump&amp;#34;&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;times { report&lt;span style="color:#f92672"&gt;.&lt;/span&gt;render }
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Repeating a short operation can collect more samples, but only do so when repetition preserves realistic behavior. A cached second call may exercise a different path from the first.&lt;/p&gt;
&lt;h3 id="3-use-representative-inputs"&gt;3. Use representative inputs&lt;/h3&gt;
&lt;p&gt;Profile production-like data volume and shape. A report with ten rows may use a different algorithm, query pattern, or allocation profile from one with 100,000 rows. Keep the inputs consistent when comparing before and after profiles.&lt;/p&gt;
&lt;h3 id="4-check-self-time-and-total-time-together"&gt;4. Check self time and total time together&lt;/h3&gt;
&lt;p&gt;A frame&amp;rsquo;s &lt;strong&gt;total time&lt;/strong&gt; includes samples in its descendants. Its &lt;strong&gt;self time&lt;/strong&gt; includes samples where that frame is the top of the observed stack.&lt;/p&gt;
&lt;p&gt;This distinction prevents a common mistake: optimizing a wrapper because it is wide even though almost all of its weight belongs to a child. Use Sandwich view to compare the two, then use Left Heavy to locate the responsible branch.&lt;/p&gt;
&lt;h3 id="5-repeat-the-measurement"&gt;5. Repeat the measurement&lt;/h3&gt;
&lt;p&gt;Run the workload several times. Garbage collection, cache state, database state, and machine load can change one run. StackProf also reports recorded and missed samples; a high number of missed samples lowers confidence in the profile&amp;rsquo;s shape.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;interval&lt;/code&gt; option changes sampling frequency. A smaller interval can provide more detail, but also increases overhead and profile size. Begin with the default and adjust it only when the workload is too short or the profile lacks enough samples.&lt;/p&gt;
&lt;h3 id="6-validate-outside-the-flamegraph"&gt;6. Validate outside the flamegraph&lt;/h3&gt;
&lt;p&gt;After making one change:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;run the same workload with the same input;&lt;/li&gt;
&lt;li&gt;capture another profile;&lt;/li&gt;
&lt;li&gt;compare the relevant branch, self time, and total time; and&lt;/li&gt;
&lt;li&gt;measure the user-facing duration without the profiler.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A branch becoming narrower does not guarantee that the request became faster. The work may have moved elsewhere, and profiling itself affects execution.&lt;/p&gt;
&lt;h3 id="how-speedscope-keeps-large-profiles-explorable"&gt;How speedscope keeps large profiles explorable&lt;/h3&gt;
&lt;p&gt;StackProf&amp;rsquo;s sampling reduces recording overhead by observing the current call stack periodically instead of tracing every method call. That efficiency belongs to the profiler; speedscope contributes a different kind of efficiency when exploring the result.&lt;/p&gt;
&lt;p&gt;speedscope parses a profile into reusable in-memory representations. Left Heavy merges identical paths, turning many interleaved samples into weighted branches. Its flamechart uses batched drawing rather than one HTML element per frame, and long lists render only rows in or near the viewport. Searching, sorting, panning, and zooming then happen locally in the browser.&lt;/p&gt;
&lt;p&gt;Large profiles can still require noticeable time and memory to parse. The practical goal is responsive exploration, not zero-cost profiling, so it is still best to capture the smallest representative workload that answers the question.&lt;/p&gt;
&lt;h2 id="advanced-rails-configuration"&gt;Advanced Rails configuration&lt;/h2&gt;
&lt;p&gt;rack-mini-profiler&amp;rsquo;s defaults are suitable for an initial request profile. Adjust them only when the default recording cannot answer the question:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# config/initializers/mini_profiler.rb&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Rack&lt;/span&gt;&lt;span style="color:#f92672"&gt;::&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;MiniProfiler&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config&lt;span style="color:#f92672"&gt;.&lt;/span&gt;flamegraph_mode &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;:wall&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Rack&lt;/span&gt;&lt;span style="color:#f92672"&gt;::&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;MiniProfiler&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config&lt;span style="color:#f92672"&gt;.&lt;/span&gt;flamegraph_sample_rate &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#75715e"&gt;# milliseconds&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;Rack&lt;/span&gt;&lt;span style="color:#f92672"&gt;::&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;MiniProfiler&lt;/span&gt;&lt;span style="color:#f92672"&gt;.&lt;/span&gt;config&lt;span style="color:#f92672"&gt;.&lt;/span&gt;flamegraph_ignore_gc &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;Wall mode is the default because web requests often wait on I/O. Switch to CPU mode when you specifically need to isolate processor work. Keeping garbage-collection frames visible can expose allocation pressure; hiding them reduces visual noise but removes that context.&lt;/p&gt;
&lt;p&gt;Do not expose profiling controls to every production user. Profiles can reveal class names, method names, file paths, and application behavior. rack-mini-profiler supports explicit authorization, and multi-server deployments require shared storage such as Redis or Memcache.&lt;/p&gt;
&lt;h2 id="beyond-rails-profile-frontend-javascript"&gt;Beyond Rails: Profile frontend JavaScript&lt;/h2&gt;
&lt;p&gt;The same three-view workflow applies to browser JavaScript. In Chrome DevTools, open the &lt;strong&gt;Performance&lt;/strong&gt; panel, start recording, reproduce the slow interaction, stop recording, and save the profile. Drag the resulting JSON file into &lt;a href="https://www.speedscope.app/"&gt;speedscope.app&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Use Time Order to isolate the interaction, Left Heavy to find the JavaScript call paths consuming the most sampled CPU time, and Sandwich to inspect an expensive function&amp;rsquo;s callers and callees. This is useful for long-running event handlers, repeated renders, and CPU-heavy parsing or transformation.&lt;/p&gt;
&lt;p&gt;Return to the browser&amp;rsquo;s Performance panel for network activity, layout, painting, and Web Vitals: speedscope focuses on call stacks rather than the browser&amp;rsquo;s complete performance timeline. speedscope also supports profiles exported by Firefox and Safari.&lt;/p&gt;
&lt;h2 id="common-interpretation-mistakes"&gt;Common interpretation mistakes&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Width is not call count.&lt;/strong&gt; One slow call can remain on the stack across many samples, while many fast calls can occur between samples.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A sample is not an exact timer.&lt;/strong&gt; Small differences between frames or runs may be sampling noise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;High total time does not mean high self time.&lt;/strong&gt; Inspect children before changing a wide method.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPU mode does not explain I/O waits.&lt;/strong&gt; Use wall mode for end-to-end latency.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Object mode does not measure retained memory.&lt;/strong&gt; It shows sampled allocations.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A library frame is not automatically the cause.&lt;/strong&gt; Follow callers and callees to understand how application code reached it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;One run is weak evidence.&lt;/strong&gt; Repeat the workload and verify the result with normal timing.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep generated dumps under &lt;code&gt;tmp/&lt;/code&gt;, out of version control, and inspect them before sharing. Useful speedscope shortcuts are &lt;code&gt;Cmd/Ctrl+F&lt;/code&gt; to search for a frame, &lt;code&gt;0&lt;/code&gt; to reset zoom, &lt;code&gt;r&lt;/code&gt; to collapse recursion, and the arrow keys or &lt;code&gt;w/a/s/d&lt;/code&gt; to pan.&lt;/p&gt;
&lt;h2 id="a-repeatable-workflow"&gt;A repeatable workflow&lt;/h2&gt;
&lt;p&gt;For most intermediate Rails investigations, this is enough:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define one narrow performance question.&lt;/li&gt;
&lt;li&gt;Start with a request flamegraph or a focused StackProf block.&lt;/li&gt;
&lt;li&gt;Select wall, CPU, or object mode to match the question.&lt;/li&gt;
&lt;li&gt;Use Sandwich to find candidates, Left Heavy to follow dominant paths, and Time Order to isolate phases.&lt;/li&gt;
&lt;li&gt;Read the relevant application code and confirm the suspected cause with a companion tool when necessary.&lt;/li&gt;
&lt;li&gt;Change one thing, profile again, and measure elapsed time without profiling.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Treat speedscope as a map rather than a verdict. It shows where sampled work occurred; a reliable optimization still depends on representative inputs, the correct mode, a testable hypothesis, and before-and-after measurements.&lt;/p&gt;
&lt;h2 id="references"&gt;References&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.brendangregg.com/flamegraphs.html"&gt;Brendan Gregg: Flame Graphs—summary, presentation, and origin&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlfwong/speedscope/blob/main/README.md"&gt;speedscope README: views, navigation, privacy, and supported formats&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlfwong/speedscope/blob/main/src/views/flamechart-pan-zoom-view.tsx"&gt;speedscope flamechart rendering implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlfwong/speedscope/blob/main/src/views/scrollable-list-view.tsx"&gt;speedscope virtualized list implementation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlfwong/speedscope/wiki/Importing-from-stackprof-(ruby)"&gt;speedscope: importing from StackProf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/jlfwong/speedscope/wiki/Importing-from-Chrome"&gt;speedscope: importing from Chrome&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/tmm1/stackprof"&gt;StackProf README: modes, raw profiles, intervals, and reports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/MiniProfiler/rack-mini-profiler"&gt;rack-mini-profiler README: flamegraphs and configuration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>