Eli Weinstock-Herman

Resolving High npm CPU Usage w/ NCrunch

February 04, 2019 ▪ technical posts
Photo by Andrik Langfield on Unsplash

A couple weeks ago I noticed my laptop fans running non-stop as I was working on a .Net Core web application. It's an ASP.Net Core 2.2 web app running a react front-end and it's wired to dual launch .Net and the react dev server when we start debugging (based on the SPA template from Visual Studio).

Diagnosing the problem

My first stop was Task Manager to see what was eating the CPU.

Screenshot of node.js CPU utilization at 32%
Quick Tip: Press Ctrl + Shift + Esc to open Task Manager quickly

I'm not currently debugging, but node.js is really chewing up the CPU. If it wasn't the debugger, than it could only be NCrunch, but why?

It turns out we have a common target in our project to npm install when we build in DEBUG:

<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" 
    Condition=" '$(Configuration)' == 'Debug'">
    <!-- ... important tasks here ... -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>

When I touch nearly any code file in the solution, NCrunch rebuilds all of the projects downstream from that change. This task runs before each of those builds, adding npm install CPU usage and delay each time.

There are two ways to fix this:

  • Only npm install if node_modules is missing
  • Ignore the npm install if I'm running from NCrunch

If you use an ASP.Net template to start your project, you get that first option out of the box. The original Condition looks like this:

Condition=" '$(Configuration)' == 'Debug' And !Exists('$(SpaRoot)node_modules') ">

However, I'm coding as a member of a small full-stack development team, which means on any given day I'm merging my changes in with other changes, switching between branches, rebasing, and so on. Where possible, I want my tools to magically take care of things for me, so you can see we removed the !Exists condition so that when we start debugging npm just takes care of itself.

Even if you choe to leave that condition, however, it would still slow down NCrunch on initial builds of workspaces.

Luckily, NCrunch provides guidance on NCrunch-Specific Overrides, one of which is an environment variable that it sets for each build.

So we can alter the csproj like so:

<Target Name="DebugEnsureNodeEnv" BeforeTargets="Build" 
    Condition=" '$(Configuration)' == 'Debug' And '$(NCrunch)' != '1'">
    <!-- ... important tasks here ... -->
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm install" />
</Target>

And now when NCrunch builds, it skips the npm step and our CPU stays low (and we get results faster):

Screenshot of no node.js CPU usage
Even at the highest NCrunch CPU usage, node.js doesn't spin up now

If you're using NCrunch, it's worth scanning your project files for tasks that are pure overhead during an NCrunch run and skipping them.

Share: