JavaScript Tracing: How to Find Slow Code

Michiel Mulders Developer Tips, Tricks & Resources

Finding slow JavaScript code can be a tricky problem to solve. Small code changes can have a big impact on the performance of your code. Fortunately, many different approaches can help you nail down the exact source of the problem. In this post, you’ll learn about three methods that’ll bring you the results you’re seeking.

You can trust manual code inspection, but that has its disadvantages. Another option is using the native console.trace command JavaScript offers to trace your code. It can help you to get a better grasp on your code and filter down the exact problem. Last, you can opt for code profiling, which is often slower than console.trace but gives much better results.

Let’s take a look at those three approaches to find slow code.

#1: Manual Code Inspection

First, take a high-level look at your code. Analyze the architecture of your software. Try to identify which architecture you’ve used and if you’ve correctly implemented it.

For example, you might have chosen event-driven architecture, but you might also be instructing a particular component through direct calls and awaiting its result. This is a serious violation of the independence of software components and how they should communicate. The goal of an event-driven system is to create an asynchronous flow of communication that’s non-blocking. But in this scenario, the chances are high that this component blocks communication.

Next, verify the implemented design patterns. Look for incorrectly used design patterns that might affect the code’s performance.

Here’s an important side note: Design patterns don’t have a big impact on your code. We’re talking about a couple of nanoseconds. But often, you need design patterns to make code more readable. Improved readability is more important than these very small performance gains. It’s worth it to take a glance at design patterns to find big mistakes—for example, a pattern that copies large objects while you can also pass the original object or parts of the original object.

Measure Performance to Spot Regressions

Tip: Measure your application’s performance, even during the development phase. This allows you to keep track of the application’s performance over time and spot regressions. Doing this can give you a good indication of which release might be problematic. Next, you can analyze the code commits for that particular release to find possible problems.

However, if you don’t work with small releases, then you’ll have to scan many hundreds of code commits. In the end, this is a very laborious process that requires a lot of time from your employees. Also, humans tend to make mistakes, and you might miss snippets of code that cause performance problems. Therefore, it’s better to use a more structured approach to finding slow code.

Next, let’s dive into the console.trace functionality JavaScript offers. This technique should quickly give you insights in the application’s call stack and how this might affect the application’s performance.

#2: Console Trace by JavaScript

A console.trace prints the call stack for a particular point in your code. This includes all functions that have been previously called, leading up to the point where you’ve placed your console.trace command in the code. A call stack allows you as a developer to gain a deeper understanding of the flow of code.

In case of slow code, you might find out that a particular function is being called that shouldn’t be called. However, this method tells you only the path of your code. It can’t tell you which exact parts of your code are slow; you’ll need more advanced tools for this kind of analysis. Still, a call stack can give you a good indication of how you can filter down the source of the slow performance.

Benefits of Tracing Using the console.trace Command

You already know that the console.trace command helps you pinpoint the exact source of the performance issue. Here are other benefits it offers:

  • It allows you to easily trace your code without using any third-party tools that require setup.
  • It’s easy to use, it’s fast, and you can remove it quickly—all of which make it convenient.
  • It prints a detailed call stack.

Next, you can use the technique of code profiling to get more details about your code’s performance.

selective focus photography of graph

#3: Code Profiling in JavaScript

Code profiling helps you as a developer to gain a deeper understanding of the code. Code profiling displays values (such as overall execution time), memory allocation (such as heap memory), and operating system resource usage. For Node.js specifically, a code profiling tool can also show the usage of the Node.js event loop. This is a very important element because a continuously blocked event loop can easily expose serious problems with your code.

Most often, you’ll want to use a code profiling tool that doesn’t require a change to the source code. On some rare occasions, you’ll want to use a tool that modifies the source code to measure the performance of specific parts of your code. This latter approach is very slow, though.

In short, code profiling helps you answer questions like these:

  • How many times did a method get called?
  • How long does each function invocation take?
  • What’s the performance of a dependency call, such as an SQL database call or accessing Redis cache?
  • How much memory is allocated, and what happens during garbage collection?

Next, let’s take a look at high-level code profiling.

High-Level Code Tracing

High-level code tracing is a type of application performance monitoring (APM). When developers speak about high-level code profiling, they’re actually referring to measuring the performance of a running application. This means you have to deploy the application on a server and measure its performance.

Often, you’ll have to mock up some fake users using a load testing tool that performs various operations. Also, Retrace offers a tool that helps with real user monitoring.

Moreover, an APM tool can provide you with key information, such as memory usage, garbage collection, statistics about the Node.js event loop, and CPU usage. Combining this data helps you further nail down the exact source of the performance issue.

Advantages: High-level code tracing is easy to set up. Most often, you’ll have to install a tool that helps with monitoring a running application. That’s it! Besides, high-level code profiling can give you accurate results about the performance of your code.

Next, did you know you can use low-level code tracing that involves modifying the source code? Let’s explore!

Low-Level Code Tracing

In case you still haven’t found the exact problem, you can opt for low-level code tracing. When developers speak about low-level code profiling, they mean that the tool has to modify the source code. For JavaScript tracing, the tool will add timers in the code to measure the exact performance and memory usage of specific functions. It’s a great approach because you can measure the performance and memory usage of individual functions.

Low-level code tracing makes the code up to 100 times slower. This means your code executes very slowly, of course. That’s why you should use low-level code tracing only when you really don’t know where the performance issue is located in your code.

Low-level code tracing provides very accurate measurements for specific functions in your code. However, it’s a slow approach to debugging performance issues.

JavaScript Tracing Summarized

Tracing JavaScript can be a laborious process. Start by manually inspecting your code for major problems with the architecture or design patterns. Next, dig deeper using the console.trace command that prints the call stack for a specific point in your code. And last, you can fall back on code profiling.

Use low-level code profiling only when you’re unsure about the exact source of the performance issue. Low-level code profiling requires a lot of time to execute. Therefore, it’s better to use high-level profiling to get a better understanding of parameters including memory allocation, CPU usage, and Node.js event loop usage.

About Michiel Mulders

This post was written by Michiel Mulders. Michiel is a passionate blockchain developer who loves writing technical content. Besides that, he loves learning about marketing, UX psychology, and entrepreneurship. When he's not writing, he's probably enjoying a Belgian beer!