MaaS: How to Monitor Node.js App Performance with PM2 & InfluxDB

Navigate to:

Are you curious about how your web app is performing? Ever wanted to have real-time insights into your application’s health and optimize its performance for variable workloads to improve performance, hosting-related costs, and user experience?

In this tutorial, we’ll walk you through the process of building a robust Metrics as a Service (MaaS) solution for your Node.js with an express based web application. The application uses InfluxDB and PM2 to collect, store, and analyze critical performance metrics, enabling you to make data-driven decisions and deliver a superior user experience. Let’s begin by covering fundamentals to build the MVP version. We’ll expand on that in a second blog that features data visualization and a real-world example of a stock trading app.

Key Technologies

  • Node.js with Express framework: Our server-side runtime environment with a bare-bones front end that serves a static HTML file where we see application monitoring metrics.

  • PM2: A popular open source process manager for Node.js applications that provides all system metrics. Aside from process monitoring, this tool also keeps Node.js server alive in production for zero downtime, log management, etc.

  • InfluxDB 3.0 Cloud: Our time series database for storing metrics and querying them in almost real time for application performance monitoring. We will use the InfluxDB Cloud Serverless environment. If you don’t already have an account, you will need one (it’s free).

  • InfluxDB Node.js v3 Client Library: Interacts with InfluxDB from our Node.js application via JavaScript APIs.

  • Other NPM dependencies: dotenv keeps our InfluxDB credentials private.

  • Download/clone the app from GitHub to build on, or build from scratch by setting up the Node.js server and InfluxDB connection.

import express from 'express';
import { InfluxDBClient } from '@influxdata/influxdb3-client';
import dotenv from 'dotenv'; // influxDB credentials in .env file
dotenv.config();

const app = express();
const port = 3000;

const client = new InfluxDBClient({
  host: process.env.INFLUXDB_URL,
  token: process.env.INFLUXDB_TOKEN,
  org: process.env.INFLUXDB_ORG
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});
1. Collecting Node.js server metrics with PM2: CPU, memory, UpTime, restarts, number of active requests
import pm2 from 'pm2';

function getPM2Metrics() {
  return new Promise((resolve, reject) => {
    pm2.describe('node-perf-demo', (err, processDescription) => {
      if (err) {
        reject(err);
        return;

      }
      const metrics = processDescription[0].monit;
      resolve({
        cpu: metrics.cpu,
        memory: metrics.memory,
        uptime: processDescription[0].pm2_env.pm_uptime,
        restarts: processDescription[0].pm2_env.restart_time,
        activeRequests: processDescription[0].pm2_env.axm_monitor['Active requests']?.value || 0
      });
    });
  });
}
2. Write metrics to InfluxDB bucket every 5 seconds
async function writeMetrics(metrics) {
  const point = Point.measurement('node_metrics')
    .setFloatField('cpu', metrics.cpu)
    .setFloatField('memory', metrics.memory)
    .setIntField('restarts', Number(metrics.restarts))
    .setIntField('uptime', Number(metrics.uptime))
    .setIntField('requestsPerFiveSeconds', metrics.requestsPerFiveSeconds)
    .setIntField('activeRequests', metrics.activeRequests)
    .setTimestamp(new Date());

  await client.write(point, process.env.INFLUXDB_DATABASE);
}

setInterval(async () => {
  const pm2Metrics = await getPM2Metrics();
  await writeMetrics(pm2Metrics);
}, 5000);
3. Querying metrics using SQL from InfluxDB every 5 seconds for our web app (/api/metrics)
async function queryMetrics() {
  const query = `
    SELECT *
    FROM "node_metrics"
    WHERE time >= now() - interval '5 seconds'
    ORDER BY time DESC
    LIMIT 1
  `;

  const results = [];
  const queryResult = client.query(query, process.env.INFLUXDB_DATABASE, { type: 'sql' });
  for await (const row of queryResult) {
    results.push(row);
  }
  return results;
}

app.get('/api/metrics', async (req, res) => {
  try {
    const metrics = await queryMetrics();
    res.json(metrics);
  } catch (error) {
    res.status(500).json({ error: 'Internal server error' });
  }

});

4. Metrics can also be queried and visualized in InfluxDB Cloud Data Explorer

5. Build & run the app

Start the application using pm2 instead of node for all it’s benefits:

pm2 start server.js --name "node-perf-demo"

PM2 offers several advantages over running Node.js directly (node server.js) as it keeps your app running in the background, automatically restarts it if it crashes, and provides built-in monitoring and log management.

Bringing It Home

You have now learned an easy way to build a simple yet effective application performance monitoring system for Node.js applications using PM2 and InfluxDB with a basic front end. This serves as a starting point you can clone/form on GitHub for building your MaaS-powered web applications. In Part 2, we’ll explore a more complex stock trading application to demonstrate real-world use cases and advanced data visualization techniques.