Loading
Current section: Performance 6 exercises
solution

Profiling Slow Tests Solution

Use vitest-profiler to generate CPU and memory profiles of your test suite, spot slow functions, and identify performance bottlenecks.

Loading solution

Transcript

00:00 In order for us to understand the result of our profiling better, let's take a look at how VTest runs our tests. So to start from, VTest has a single Node.js process, and we will call it a main process or a main thread. This process is responsible for a number of things, like collecting the test files from the file system,

00:17 applying global setup, orchestrating the test run, and collecting the results. And then once it analyzes all your test files, it has this list of files to run, and it spawns individual workers to run those test files. So I will call these workers. And these can be workers or forked processes, depending on your configuration,

00:41 but I will refer to them as workers for simplicity. And their responsibilities are different. They apply your test environment, the setup files, and run the tests themselves. So even from this high-level overview, you can already see that we have two main processes or groups of processes to profile. The main thread, because it has its own responsibilities,

01:02 and the workers that do their own things as well. And the root cause for performance degradation may lie in either of these processes or in both of them, and we will use profiling to figure it out. You can profile your test runs in VTest manually, or you can also use a package I created called VTestProfiler, which will help you do all of that a little bit more easier,

01:20 especially if you've never done profiling before. So to install the package, I will head to my terminal, run npm install vtest-profiler. And now that I have the profiler installed, I will use it with VTest. So I will head to my VTest configuration, and import from VTestProfiler slash plugin, VTestProfilerPlugin. Then I will head to this defined config, add the plugins property,

01:44 and in this array, I will call this VTestProfilerPlugin. What this plugin will do, it will provision all the necessary options for VTest to run your tests in profiler mode. And this addition will only affect your workers threads, so only your tests. Now to profile the main thread, I need to wrap my entire test run in the VTestProfiler CLI.

02:02 To do that, I will head to package.json, and introduce a new script. I will call it test-profiler or test-profile. And I will use VTestProfiler, and then just provide the command I normally use to run my tests. So npm test. And now let's use the test-profile command to run our tests in the profile mode. So once this is done,

02:26 the plugin will output some additional information for us to read. Let's take a look at that. So first of all, we see our regular test run right here with the regular metrics, basically our test summary. And then after this, we can see the test profiling is complete, and the plugin has generated a few profiles for us that are split for main thread and our tests, our worker thread. With the VTestProfilerPlugin,

02:45 you can find two groups of profiles, the CPU profiles, which describe the CPU consumption during a test run, and then the memory profiles or heap profiles that describe how much memory was consumed when your test run. So let's take a closer look at these profiles, starting from the CPU profile for the main thread. I can see all of these profiles

03:04 in the new directory called test profiles, and I can open the one for the main thread over here. Visual Studio Code comes with a built-in profile viewer, which you can see on the screen, and it's fantastic. So this CPU profile describes the CPU consumption during my test run. And don't worry if this looks cryptic and unclear to you, that's okay, because this will include everything

03:24 from your code and your tests to VTest internals and even Node.js internals. This is the whole snapshot of the entire process that run your tests. So based on what we see right now, there's nothing suspicious, nothing to suggest the issue here. So let's keep digging. Let's close this one and take a look at the CPU profile for our tests. So once I open this,

03:43 I can immediately see something off. I can see that this expensive compute function took 450 milliseconds to run, which is almost half of the problematic long test run that I have. So that's a good indication that perhaps this function is either leaking memory or just consumes too much memory, and that slows down VTest

04:03 and my test run as the result. So I can go to this function straight from here, and I can see that in my case, well, yeah, I'm simulating this scenario that I'm creating 100 million entries in an array, and that's going to be pretty slow. But regardless of what your code does, this is how you can find the stack trace to it. This is your roadmap. So straight from the profiler, you can find this function

04:23 and the function when it used that, for example, my test, and it will take you to the exact location of that file. What is really neat about Visual Studio Code is that it will also inline this profiler information right here. You see, I can see the expensive compute, and then it takes 458 milliseconds to run. This is really nice, and Visual Studio Code pulls that information

04:43 from this profile that we have generated. So in this case, we already found the root cause for the issue, but let's also take a look at the memory consumption of the tests. In this heap profile, and here, nothing is unusual. Everything is a pretty low numbers. Profiling is extremely handy because it allows you to understand how your system runs better. In this case, how your tests run.

05:02 And based on that understanding, you can define further debugging steps and eventually fix the issue. Because even knowing the problematic area, whether it's CPU or memory consumption, can help you figure out what to fix in your code. For example, if your test run consumed a lot of CPU, perhaps the logic that you're testing is very CPU intensive, and you should consider refactoring it.

05:21 Utilizing things like asynchronicity and parallelization, and even caching to make sure that you don't consume more resources than you need. On the other hand, if the issue lies in excessive memory consumption, that's often a sign that your code is leaking memory. So you have to analyze those problematic entries in your heap profile and make sure that you're managing the memory correctly. For example, you're not having any rogue processes

05:41 or unterminated loops, or the timers or intervals you forgot to clear. And keep in mind that when you're profiling your test run, you're profiling both the main and the worker threads. And sometimes performance degradation may come from different sources, not necessarily your tests. For example, maybe in your test setup, you're spawning an HTTP server before every test suite, but you're forgetting to properly close it.

06:01 And this will result in excessive memory consumption, and you will see that in your heap profile. I highly encourage you to utilize profiling as the first step of debugging anything performance related.