Performance Analysis — Node.js

Rishabh Jain
8 min readFeb 6, 2020

--

What is Profiling ?

Process of drilling down the application code and configuration to find the potential performance bottlenecks. Profiling is one most important activity needed to enable the application to be scalable. So with profiling we will able to find:

  1. Where the CPU time is spent?
  2. How much memory is allocated to which objects? (Also helps to check if there is memory leak — Space is occupied by unused object)

Profiling also helps to do efficient Capacity Planning(link present in end of blog) means how much CPU/Memory required for application to perform efficiently as per SLA.This in turn also helps in saving cost.

Memory Management in Node.JS:

Memory management in Node is actually is memory management in V8. V8 memory management is almost similar to JVM.Divided into 3 segments:

  1. Code: Actual code execution space.
  2. Stack: Local Variables space
  3. Heap: Storing references like strings, objects and closures

V8 Heap Segments — On broader level below are the segments:

  1. New Space: Newly created objects are allocated here (small and quickly garbage collected). — Young Generation
  2. Old Pointer Space: Objects which may have pointer to other objects present here. Objects that survived in New Space moved here. — Old Generation
  3. Old Data Space: Objects that contain data ( No Pointers) i.e Strings, boxed numbers and array of unboxed numbers are moved here after surviving New Space. — Old Generation
  4. Large Object Space: Contains objects that are larger than size of other spaces. Each object gets its own mmap region in memory. — Old Generation
  5. Code Space (Executable Code) & Map Space(Hidden Classes)- Old Generation.

Each space is composed of set of pages(contiguous clunk of memory). Pages other than large object size are of 1MB.

V8 Garbage Collection Algorithms:

  1. Short GC/ Scavenging (Young Space) — Allocation is very cheap in Young generation. It maintains a pointer which increases whenever new object is added and when the space is full / Pointer reaches end , Scavenge is triggered. Its a tracing garbage collection which operates by relocating reachable objects and reclaim unreachable one’s. Only suitable for small space as it has large space overhead. Pause time is generally ~2ms
  2. Full GC/Mark Sweep & Mark Compact — Objects which survived few short GC are promoted to Old Space. Full GC(Major GC) is triggered when the old generation space is full. Full GC generally contains hundreds of MB’s of data and it cause overhead on performance( Full GC initiates STOP THE WORLD EVENT i.e Application is unresponsive as during Full GC long pause is required) so Full GC Cycles should be minimal to have Application Performance in check. For Full GC, V8 is dependent on mainly Mark Sweep Collection and Mark Compacting.

Mark Sweep is tracing garbage collection in which we first mark all the reachable objects and then sweeping memory(for unreachable objects). It might cause fragmentation.

Mark Compacting (Next step of Mark Sweep) compacts the all reachable objects and then moved into contiguous memory block(to be used efficiently). No Fragmentation

Pause Time for Mark Sweep: ~50ms

Pause Time for Mark Compact: ~100ms

With above algorithms lot of pause times are added for our application and performance of application Suffer. To Reduce this pause time Google introduced Incremental Marking & Lazing Sweeping in 2012.

In this marking begins when the heap reaches certain threshold and inspite of having ~100ms pause time, it pauses for ~5ms and mark , again mutator(JS program) run a bit and again marking initiated and this goes on. In parallel Sweeping is initiated on unreachable objects and pauses are minimized (performance of application is taken care). But if the allocation rate is very high during incremental GC then JS engine may run OOM and non incremental GC need to take care.

Before moving to actual Node.JS profiling lets start with taking GC logs on running node program using trace_gc:

node — trace_gc build/server.js ( if want to dump in some file use >> dump.txt). Below is output:

Here we can see which garbage collection is initiated and 2.5 -> 2.3 likely to denote the memory before GC and memory after GC. Also we go got the time spent for doing specific GC)

How to profile Node.JS / Javascript applications using Freeware/utilities?

  1. Using Chrome V8(dev tools)
  2. V8-Profiler / V8-Profiler-next (npm module)
  3. HeapDump Module ( Can be used for production)
  4. Sampling Heap Profiler (Can be used for Production)
  5. Memory Usage using node process

Using chrome V8:

start the server with below command in inspect mode:

node — inspect build/server.js

Now open the chrome and type chrome://inspect/#devices in address bar. Then below window will appear. click on the link below target to select the target:

Now we get the devtools like below window.

CPU Profiling using Chrome V8 :

Select the profile button on the dev tools window.

Click on start and then when want to Stop CPU profiling click on Stop(it will appear once Start is clicked). We will get the Snapshot like below

Select the chart from the drop down below the profiler. Below chart will be shown that gives the time spent for each part on cpu:

And when we click on the any part it will take to the code point where actual time is spent.

Memory Profiling V8 heap Snapshots:

Json file with each object memory allocation.

To capture the heap snapshots click on the memory button (on left of profiler). Below screen will come up:

Click on Take snapshot button

This is actually a heap snapshot(memory allocation at a particular instant). This give full view which objects is taking how much memory for its working.

Also we can see aggregated stats by selecting the Statistics from drop down below profiler:

Now one way of finding leaks is to compare the heap snapshots different snapshots and we can identify which objects is retaining memory for longest time. Chrome V8 gives the power to compare different snapshots:

Take another screen by click on the dot(black) on the top left. Below we can see both screenshots:

Now to get the diff between screenshots, select the objects allocated between snapshot 1 and snapshot 2 from the drop down on right side of class filter and we can also selection comparison in place of summary(below of profiler).

Above will the give common objects between these 2 snapshots — Same objects taking memory in both snapshots.

Now in addition to analysing we can save the CPU and Memory snapshots on our machine to be shared or to be analysed at later point of time.

V8-profile / v8-profiler-next npm module:

This is internally using the same chrome v8 but its provides alternative way of taking snapshots.

To get snapshots we need to add few lines of code.

CPU Snapshots

I have created one _status get end point and added few code lines in that.

Now i will hit the /_status get end point and we will get the cpu_snapshot file(cpu time dump)

Now to analyse this file we can open this snapshot in chrome dev tools.As we have used the same dev tools for cpu snapshot in 1st method. Just click on the load.

Memory/Heap Snapshots:

Add the below code and run the _status api. We will get heap_snapshot.heapdump

Lets open the same in heap snapshot in dev tools by selecting from Load button:

Heapdump module:

Above mentioned methods are not suitable for get the heap dumps on production. So for production we can go heapdump that is light weight (less overhead).

npm run install heapdump ( or node.js v0.6 or v0.8 use heapdump 0.1.0)

We can integrate in code with many ways. Few are listed below:

  1. Create a end point that will generate heapdump
  2. On Unix platforms we can take kill -USR2 <PID>

Demonstrating 1st method:

Generate file for heap dump

Heapdump file:

Now we can copy the heap snapshot from remote server and open easily in chrome dev tools memory profiler part.

Sampling Heap Profiler (Lightweight- Production Specific)

Basically its takes random sample of objects as they are allocated and maintain the statistical data for heap/memory. We can also view the life of objects from this data(from live to die ).

npm install heap-profile

Add below code in main.js file. Below code is generating heap snapshots in every 1000 seconds.

We will load this file in chrome dev tools and its will give the chart like below(something similar to what we got for CPU)

Memory Usage using node process ( Not good for production as such)

On high level we can divide the memory consumption into 4 parts:

  1. rss — Resident Set Size. Amount of RAM node process is using(V8, Node core, any dependent library). Expected stable value for rss as all these things will be take memory on process start only. If rss size is increasing we may think there is memory leak.
  2. heapTotal — Total space available for javascript objects. Its will go up and down with GC.
  3. heapUsed — Total Space occupied by JS objects at current instant.
  4. external: Memory consumed by node for off-heap objects like buffers

const { rss, heapTotal } = process.memoryUsage();

For Performance Analysis and Memory Management of Java applications, Refer below posts:

In case of any query and suggestions please connect with me https://www.linkedin.com/in/rishabh-jain-a5978583/ or drop a mail @rishabh171192@gmail.com

--

--

Rishabh Jain

Full Stack Developer — React, Node, Mongo DB, Postgres, RabbitMQ, AWS, Native Performance Engineering, Lambda, Javascript, Kubernetes, Docker.