runtimeanalyz.ing

← All capabilities

where_time_goes

CLI · MCP Pro

The call chains that run most often. The most-travelled call chains, ranked by how often they ran in the recording, so you can cache and optimise the chains with the most calls.

DashboardController #show
progress
30 calls
Enrollment
summary
46 calls
Report
Enrollment
unlocked_for ( student Student )
600 calls
Curriculum
Report
weeks_for ( book Book )
46 calls · 0.2%
Curriculum
unlocked? ( student Student )
12,000 calls · 65%
Book the hot end
Each call is rebuilt into its chain back to the root, and identical chains are counted together. The method that looked expensive is on a chain with few calls. Most of the calls are on the chain of the method that is called once per book and checks every book each time.

The problem

The student dashboard is slow, and someone has already opened a pull request to cache Curriculum#weeks_for, because the report that calls it sums it over the final books on every page:

def summary(curriculum)
  curriculum.final_books.map { |book| summary_line(book) }.join(", ") +
    " · #{curriculum.final_books.sum { |book| curriculum.weeks_for(book) }} weeks to finish"
end

weeks_for looks like the expensive part when you read the code. A method that is slow once and a method that is called constantly are different problems, though, and only the first of them is visible in the code. A profiler run on your laptop covers the one request you happened to make. The mix of requests your users make is not in it.

The fix

Ask which chains run most often:

$ ra where_time_goes --entry DashboardController --top 3
most-travelled chains
· 30 trees · 49710 calls
  1  DashboardController#show └ Enrollment#progress └ Curriculum#unlocked_for └ Curriculum#eligible └ Book#unlocked?                              12,000  24.1%
  2  DashboardController#show └ Enrollment#progress └ Curriculum#unlocked_for └ Curriculum#eligible └ Curriculum#prerequisites                    11,400  22.9%
  3  DashboardController#show └ Enrollment#progress └ Curriculum#unlocked_for └ Curriculum#eligible └ Student#completed? └ Enrollment#completed?  10,800  21.7%

weeks_for is 62 calls in the full list, a tenth of a percent. Seven in ten calls under the dashboard go through Enrollment#progress into Curriculum#unlocked_for, because progress asks for the unlocked books once per book on the page, and each of those requests checks every book again:

def progress
  curriculum.books.map do |book|
    Progress.new(book, unlocked: curriculum.unlocked_for(student).include?(book))
  end
end

Thirty dashboards of twenty books each, with twenty eligibility checks per request for the unlocked books, make the twelve thousand calls. The cache belongs in progress:

def progress
  unlocked = curriculum.unlocked_for(student).to_set
  curriculum.books.map { |book| Progress.new(book, unlocked: unlocked.include?(book)) }
end

That is one call to unlocked_for per page instead of one per book, and the weeks_for pull request can wait.

Where it shows up

  • Over MCP, the ranked chains come back as a table your agent can read before it decides what to cache.
  • On the command line, run ra where_time_goes, with --entry to pick the trees whose entry point mentions a controller, job or spec, and --top to cut the list.

How it works

Every call in the recording belongs to a call tree and records the method that made it, so each call’s chain back to its root can be rebuilt. where_time_goes rebuilds them, merges identical chains and ranks them by count, with each chain’s share of every call in the selected trees. --entry keeps the trees whose entry point contains the text you give, so DashboardController matches every GET /dashboard/7 (DashboardController#show) request.

It ranks chains instead of single methods because the same method can be cheap from one caller and hot from another, and the chain includes the caller to change.

Run it on your app

The output on this page comes from our example Rails app, and it is regenerated whenever a tool changes. These are the commands behind it. On your own app, run ra record first, then put your own class, method or file in their place.

ra where_time_goes --entry DashboardController

The repeated call is app/models/enrollment.rb:20, and the traffic script requests thirty dashboards.

Reading the result

  • The recording counts calls and does not time them. A chain that runs twelve thousand times is the place to look first, but a single slow query can be in a chain that runs once. Use a profiler to find that, and use this report to decide what to profile.
  • The ranking comes from the runs you recorded. Your test suite makes a different mix of calls from your users, so record the app itself when you plan to act on the ranking.
  • Chains are made of app methods. Time spent inside a gem or the database appears as calls to the app method that called it and does not get a chain of its own.

Every tool reads the same recording. What gets recorded and how far to trust an answer are on the questions page.

whats_looping

CLI · MCP Pro

N+1 and hot loops. Call sites that ran many times in one call tree, ranked by repeats, with whether each one called many different objects, as an N+1 query does, or asked one object the same thing repeatedly.

See more →

how_it_got_here

CLI · MCP Pro

How execution got here. The call chains that reached a method, in call order, taken from recorded runs. It is the stack trace you'd have got if the method had raised, for every route into it.

See more →

what_calls

CLI · LSP · MCP free

The callers of a method. Every caller of a method in the recorded runs, with the file and line of each call and how often it was made, including calls made through public_send. what_it_calls lists the methods it calls.

See more →