Detect the Memory Leak In Your Node App | Profiling Node App

Shubham Verma
The Startup
Published in
7 min readSep 25, 2019

Memory Leak is a dangerous and big problem for your app. So we need to learn how we can detect the memory leak in your node app.
Before doing this you need to know how you can detect the what memory is being used by your application, to learn this you can check out my previous blog:

Profiling Node App: Detect the memory uses of node app | Use of -inspect | Heapdump | Heap Snapshot

In this blog we will achieve our goal doing below stages, so let’s have a look on following stages:

Stage 1: Create a basic node app

Step 1: Let’s create a basic node.js application, create two files with the name app.js and package.json:

package.json

{
"name": "memory-leak",
"version": "1.0.0",
"private": true,
"scripts": {
"start": "node --inspect app.js"
},
"author": "Shubham Verma",
"license": "",
"dependencies": {
"express": "^4.14.1",
"fast-levenshtein": "^2.0.6"
}
}

app.js:

const express = require('express');
const console = require('console');
const levenshtein = require('fast-levenshtein');//When set to 100 it should be the only visible operation.
const HOW_OBVIOUS_THE_FLAME_GRAPH_SHOULD_BE_ON_SCALE_1_TO_100 = 10;
const someFakeModule = (function someFakeModule () {return {
calculateStringDistance (a, b) {
//Here's where heavy sunchronous computation happens
return levenshtein.get(a, b, {
useCollator: true
})
}
}
})()const app = express();
app.get('/', (req, res) => {
res.send(`
<h2>Take a look at the network tab in devtools</h2>
<script>
function loops(func) {
return func().then(_ => setTimeout(loops, 20, func))
}
loops(_ => fetch('api/tick'))
</script>
`)
});app.get('/api/tick', (req, res) => {
Promise.resolve('asynchronous flow will make our stacktrace more realistic'.repeat(HOW_OBVIOUS_THE_FLAME_GRAPH_SHOULD_BE_ON_SCALE_1_TO_100))
.then(text => {
const randomText = Math.random().toString(32).repeat(HOW_OBVIOUS_THE_FLAME_GRAPH_SHOULD_BE_ON_SCALE_1_TO_100)
return someFakeModule.calculateStringDistance(text, randomText)
})
.then(result => res.end(`result: ${result}, ${arr.length}`))
});app.get('/api/end', () => process.exit())
app.listen(8080, () => {
console.log(`go to http://localhost:8080/ to generate traffic`)
})

Step 2: Go to the app location and run below command

npm install

Step 3: After the successful installation, run bellow command:

node --inspect app.js

Step 4: Be careful at this step, after running the `node — inspect app.js` command you can see an URL start with ‘ws’, just like in above snapshot ‘ws:127.0.0.1:9229/4ba52d1b-95c8–4b19-a9bf-bfb2b5ef8732

Now what you need to do is, you need to make an URL like this:

devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/4ba52d1b-95c8–4b19-a9bf-bfb2b5ef8732

Stage 2: open our app in chrome dev tool

Step 1: Now, open the above URL in the browser, make sure the URL is correct, otherwise, you will get the following error:

Error snapshots
Error snapshots

Step 2: Click on the link ‘http://localhost:8080/’, it will open a tab with URL ‘http://localhost:8080/’ ( you can directly open the URL `http://localhost:8080/` into browser )

Step 3: After clicking/opening URL `http://localhost:8080/` you can see the below page, also open network tab in dev tools.

You can see there are many requests are hitting by the browser.
if not then check all the steps again ( you are doing wrong somewhere )
If yes then move to the next step.

Stage 3: Create the heap snapshots and get the memory size, consumed by our application

Step 1: Now open the dev tool tab ( which is previously opened in “stage 2 & step 1” ) Click on “Memory”, Select “heap snapshot” under the “select profiling type” and click on the below button “Take snapshot”.

Step 2: After clicking the “Take snapshot”, you can see there is a snapshot taken under the “profiles-> HEAP SNAPSHOT”. Now click on that “snapshot 1”.

Step 3: After clicking on that “Snapshot 1”, You can see that each and every object created for this app.
* You can also see the num of that variable which is created to run this app.

* You can see how much memory is being used for this app.

You can see in the above snapshot, the app is consuming total of 8.3 MB memory.

* You can see the “Shallow size” of that kind of variables.

* You can see the “Retained Size” of that kind of variables.

Stage 4: Let’s compare the heap snapshots

To compare the heap snapshots, we need to multiple heap snapshot. You can take multiple snapshots and compare each and everything by clicking on the left button as shown in the below snapshot ( Make sure you are taking snapshot after 3 to 5 seconds )

Snapshot button ( Left most side )

I have taken the multiple snapshots as :

Comparing multiple heap snapshot

In the above image, we can see the memory size consumed by our app, it shows that it usages limited ( No increment of memory size) memory space and its fixed to 7.9 MB. It means there is no memory leak. Now we will change our code to see the memory leak.

Stage 5: Let’s change some code in our app so that we can achieve the goal.

Step 1: In this, we will add an array of object and will insert some data so that each time we hit the API ( hitting constantly already in our app ), the size of array will increase and in last it will consume our all memory and app will be crashed ( but it will not be happening here because of good memory in my system).

So just copy and paste below code into your app.js file:

app.js:

const express = require('express');const console = require('console');const levenshtein = require('fast-levenshtein');//When set to 100 it should be the only visible operation;const HOW_OBVIOUS_THE_FLAME_GRAPH_SHOULD_BE_ON_SCALE_1_TO_100 = 10;let memory_leak=[];const someFakeModule = (function someFakeModule() {return {calculateStringDistance(a, b) {return levenshtein.get(a, b, {useCollator: true})}}})();const app = express();app.get('/', (req, res) => {res.send(`<h2>Take a look at the network tab in devtools</h2><script>function loops(func) {return func().then(_ => setTimeout(loops,        20, func))}loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));loops(_ => fetch('api/tick'));</script>`)});app.get('/api/tick', (req, res) => {memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});memory_leak.push({name:"Shubham Verma"});Promise.resolve('asynchronous flow will make our stacktrace more realistic'.repeat(HOW_OBVIOUS_THE_FLAME_GRAPH_SHOULD_BE_ON_SCALE_1_TO_100)).then(text => {const randomText = Math.random().toString(32).repeat(HOW_OBVIOUS_THE_FLAME_GRAPH_SHOULD_BE_ON_SCALE_1_TO_100)return someFakeModule.calculateStringDistance(text, randomText)}).then(result => res.end(`result: ${result}, ${memory_leak.length}`))}); app.get('/api/end', () => process.exit())app.listen(8080, () => {console.log(`go to http://localhost:8080/ to generate traffic`)});

Step 2: Go to the app location and run the command

node --inspect app.js

Step 3: Run the code and open dev tool by creating new URL ( as we described in stage 1+ step 3 & 4 then follow stage 2 ).

Stage 6: Detect the memory leak in our app.

Step 1: Now take the multiple heap snapshots as:

In the above image, you can see the memory size of each snapshot is increasing. It means something went wrong in your code, You code is storing memory which is not good ( As we did it to see the memory leak ).

So make sure your code should not store the memory at all, and your heap size should be the same after accepting the “n” number of request.

That's it….

Congratulations… You are becoming an expert in node.js.
Thanks for reading…
Keep reading… Keep getting…

--

--

Shubham Verma
The Startup

A full-stack developer, In my free time, I write blogs and play guitar. I am both driven and self-motivated.