Profiling Gadget backends 

Your Gadget backend runs on Node.js, which means a Node.js profiler can be used to get detailed information about the performance of your actions and routes.

A profiler can help you identify bottlenecks and computationally expensive functions or third party libraries, optimize your actions, and improve the performance of your backend.

Setting up a profiler 

You need to use ggt to set up a connection to your backend, then a Node.js profiler like Chrome DevTools to capture and analyze the profile.

  1. Install ggt locally if you haven't already:
terminal
npm install -g ggt
  1. Use ggt dev to pull down your project's files locally:
terminal
ggt dev --app <app-name> --env <env-name>
  1. Run ggt debugger to set up a websocket connection to your backend:
terminal
ggt debugger

The localhost address for the websocket connection will be listed in your terminal. Use this address to connect your Node.js profiler to your backend. This guide will use Chrome DevTools to profile your backend.

  1. Open Chrome and navigate to chrome://inspect. You should see your localhost address listed as a target.
  2. Click inspect under your localhost address to open the Chrome DevTools inspector for your backend.

You are now set up and ready to start profiling your backend.

Profiling CPU time 

The Node.js profiler is a sampling profiler. It works by capturing a stack trace of the JavaScript code running in your backend at regular intervals.

This means it is important to run the code you want to profile multiple times to get a representative sample of the code's execution. A single execution of an action may not be enough for the profiler to capture an accurate profile.

To capture a profile for an action, you can:

  1. Start the profiler in the Performance tab of Chrome DevTools.
  2. Use the API playground in the Gadget editor to call your backend action or route multiple times:
Run your action multiple times to generate a profile
JavaScript
for (let i of Array(100)) { await api.generateReport(); }
  1. Stop the profiler in Chrome DevTools to finish capturing the profile.
  2. (Optional) Save the profile locally.

Now you can analyze your profile.

Getting a representative sample

You need enough runs of your action or route to get a representative sample. If you see functions appearing and disappearing between runs, or if the relative time spent in functions keeps changing, you need more runs. The profile should show consistent patterns before you can trust the results.

Tips for generating a good profile 

There are things you can do to generate a more useful profile.

  • It is worth repeating: run the code you want to profile multiple times to get a representative sample of the code's execution. A single execution of an action may not be enough for the profiler to capture an accurate profile.
  • Break down your code into smaller functions so the profile captures more granular information. Profilers display the time spent in each function, so the more granular the functions, the more detailed the profile.

Analyzing your profile 

Once you have captured a profile of your actions, you can start to analyze it and identify bottlenecks in your code.

The Chrome DevTools profiler shows two key metrics for each function:

  • Total time: The CPU time spent in a function and all functions it calls. If functionA() calls functionB(), the total time for functionA includes all the time spent executing functionB.
  • Self time: The CPU time spent only in that function's own code, excluding any time spent in called functions.

What to look for 

Focus on functions that combine high self time with high call frequency, these are your biggest optimization opportunities. For example, a function with 30% self time called 1000 times is a better target than one with 50% self time called once.

Functions with high total time but low self time are calling expensive subfunctions, drill down into what they call. Look for patterns like nested loops, repeated calculations, or inefficient data transformations.

If you see expensive functions in third-party libraries, look at the call stack to see what your code is calling that leads to those expensive library functions. You may be able to optimize how you're using the library, or find an alternative approach.

How to take action 

Once you've identified a bottleneck, here are common optimization strategies:

  • Cache expensive computations: If you're recalculating the same value repeatedly, cache it, or store the result in the database.
  • Reduce iterations: Look for opportunities to reduce loop iterations or use more efficient algorithms. Computed views can be helpful for reading and aggregating large sets of data from the database.
  • Batch operations: Instead of processing items one-by-one, batch them together. Your app API includes bulk action endpoints that can and should be used, including bulk endpoints for background actions.
  • Optimize data structures: Use the right data structure for your use case, for example, Set for speedy lookups, Map for key-value pairs.
  • Move work outside hot paths: If possible, precompute values or move expensive operations to less frequently called code.

After making changes, re-profile your code to verify the improvement. Compare the self time and total time of the optimized functions before and after your changes.

More information 

See the Google Chrome DevTools docs for more information on using the Node.js profiler.

How profiler times relate to Gadget billing 

The times shown in the profiler represent CPU time, not wall-clock time. This is the same metric Gadget uses for billing, Gadget only bills for actual CPU time consumed by your code.

Importantly, idle time is not billed. When your code is waiting for async operations like network requests, database queries, or file I/O, the CPU is idle and no time accumulates. This means:

  • A function with high total time but low self time is calling expensive subfunctions—look at what it calls.
  • A function with high self time is doing CPU-intensive work itself—optimize its logic directly.
  • Time spent awaiting external services won't affect your CPU billing.

Was this page helpful?