【收录】如何在 Node.js 中自动化性能分析

737 阅读5分钟

为 Node.js 应用程序创建性能指标

性能分析是对于找出应用程序运行缓慢以及如何使其更高效运行的重要手段。

用对了工具,分析 Node.js 应用程序可以变得相当简单。但是许多教程所描述的经验非常复杂,并且需要开发人员每次都一步一步地完成分析过程。

本文介绍了如何自动化该过程。

Useful tools

除了需要一份的 Node.js 项目源码外,我们还需要一些工具。在概述该过程时,我们将更深入地介绍这些内容:

A simple application

让我们从一个我们想要分析的简单应用程序开始:一个 Express 服务器。应用程序可以是任何 Node.js 程序,无论是 Web 服务器、图像文件处理器、复杂的数学算法,还是其他任何东西。

我们用来分析 Express 服务器的原则可以应用于任何地方。

import express from 'express';
import { 
  simpleCalculation, 
  complexCalculation
} from './utils'

// Set up app
const app = express();
const port = process.env.PORT ?? 8080;

// Set up routes
const router = express.Router();

router.get('/api/simple', (req, res) => {
  const result = simpleCalculation();
  res.json(result);
});
router.get('/api/complex', (req, res) => {
  const result = complexCalculation();
  res.json(result);
});

app.use('/', router);

// Start server
const server = app.listen(port, () => {
  console.log(`Server listening on port ${port}`);
});

我们已经建立了一个简单的 web 服务器,它有两个路由。

一个执行简单计算并返回结果。

另一个执行复杂的、占用大量 CPU 的计算并返回结果。

服务器在指定的端口上运行process.env.PORT或默认为 8080 端口。在我们的 package.json 中,我们创建一个脚本来运行服务器:

{
    "scripts": {
        "start": "node ./src/index.js"
    }
}

Create a profiler

v8-profiler-next是官方但已过时的v8-profiler的非官方(但非常需要)的继任者。它提供了用于从您的代码中启动和停止 Node.js 分析器的编程工具,并且与最新版本的 Node.js 兼容。

为方便起见,我们可以创建一个分析器类,它可以帮助我们指定何时何地使用 v8-profiler:

import fs from 'fs';
import v8profiler from 'v8-profiler-next';

export class Profiler {
  constructor(options) {
    const { title, active, outputDir } = options;
      this.title = title;
      this.active = active;
      this.outputDir = outputDir;
    }

  start() {
    if (this.active) {
      v8profiler.startProfiling(this.title, true);
    }
  }

  finish() {
    if (this.active) {
      const profile = v8profiler.stopProfiling(this.title);
      profile.export((error, result) => {
        if (error) {
          console.log('error', error);
        } else {
          fs.writeFileSync(
            `${process.cwd()}/${this.outputDir}/${this.title}.cpuprofile`,
            result
          );
        }
      });
    }
  }
}

export default Profiler;

Profiler类有方法来启动和完成剖析程序,如果active条件得到满足。

完成后, .cpuprofile文件将写入/<outputDir>目录 稍后将 详细介绍 .cpuprofile 文件)

我建议将profiles目录添加到您的.gitignore文件中。

Use the profiler…sometimes

探查器是一个强大的工具,但您不希望每次服务器运行时都使用它。相反,您只在想要分析应用程序时才需要使用。

我们可以通过active在创建新探查器时将环境变量连接到选项来控制是否使用探查器的流程。

让我们再添加一些 co转到我们的index.js文件:

import express from 'express';
import { ... } from './utils'
import Profiler from './Profiler'

// Create a profiler
const profiler = new Profiler({
  active: !!process.env.PROFILER,
  title: new Date.getTime(),
  outputDir: process.env.OUTPUT_DIR
});

// Set up app ...
// Set up routes ...

router.get('/api/simple', (req, res) => {
  profiler.start();
  const result = simpleCalculation();
  
  res.on('finish', () => {
    profiler.finish();
  });
  res.json(result);
});

router.get('/api/complex', (req, res) => {
  profiler.start();
  const result = complexCalculation();
  
  res.on('finish', () => {
    profiler.finish();
  });
  res.json(result);
});


// Start server ...

我们创建了一个Profiler仅在process.env.PROFILER存在时运行 v8 分析器的新实例。一旦访问路由,分析器就会启动。

执行计算,并在将它们发送回请求者后,分析器完成并写入 .cpuprofile

所有部分都已准备就绪,可以按路线分析应用程序路线。

Automating the process

虽然我们Profiler的非常有用,但它仍然需要大量的手动工作来生成应用程序性能的指标。

我们需要使用某些环境变量启动服务器,然后使用工具向某些路由(浏览器、Postman、Apache Bench 等)发出请求,然后关闭服务器。

但是通过 bash 脚本和对 PM2 的一些创造性使用,这一切都可以浓缩为单个 NPM 命令。

让我们添加一个新的 NPM 脚本来运行 bash 脚本:

{
    "scripts": {
        "start": "node ./src/index.js"
        "profile": "sh ./scripts/profile.sh"
    }
}

此 NPM 脚本将在项目/scripts目录中运行本地 bash 脚本。

bash 脚本可能如下所示:

# Set environment variables
export PORT=4000 
export PROFILER=true 
export OUTPUT_DIR="profiles/$(date +%s)"

# Leverage pm2 to start server on specific port:
pm2 start npm --name server-profiler -- start

# Wait for server to start and then ping with post request to run campaign
npx wait-on http://localhost:4000

# Use apache benchmark to post request to complex route
ab -n 1 http://localhost:4000/api/complex > "./${OUTPUT_DIR}/ABResults.txt" 2>&1

# Cleanup running process
pm2 stop server-profiler
pm2 delete server-profiler

Let’s break this down step by step

export PORT=4000

export PROFILER=true

export OUTPUT_DIR=”profiles/$(date +%s)”

  • 在这里,我们建立了一些我们之前在代码中看到的关键环境变量,这些变量将在运行此脚本的 shell 的整个生命周期中持续存在。
  • 服务器将在端口 4000 上运行,但您可以选择任何不会干扰您正在运行的其他进程的端口。它将PROFILER环境变量设置为 true,这意味着您的探查器将实际运行。
  • 它还将任何分析指标文件的输出目录设置到统一位置。我将其设置为以当前时刻的 unix 时间戳命名的文件夹,但您可以选择任何唯一的 id。

pm2 start npm — name server-profiler — start

  • 这里我们利用 PM2 来运行npm startpackage.json的命令。
  • 它为 PM2 内部使用的过程命名,我们将在最后一步进行清理。
  • 运行此命令后 PM2 运行进程的快速视图将如下所示 ( pm2 ls):

image.png

npx wait-on http://localhost:4000

  • 根据您的代码库,您可能需要等待您的服务器启动并在继续之前可以接受请求。
  • 我们wait on过去常常等待服务器准备就绪,然后再使用任何请求对其进行 ping 操作。

ab -n 1 http://localhost:4000/api/complex > "./${OUTPUT_DIR}/ABResults.txt" 2>&1

  • Apache Bench ( ab) 是一个强大的工具,用于对服务器对请求的响应进行基准测试。它预装在许多操作系统中,值得拥有自己的系列文章/教程来涵盖它的全部功能。
  • 在这种情况下,我们使用它GET向我们服务器的api/complex路由发出单个请求。
  • Apache Bench 创建自己的输出,其中包含有关服务器响应速度的结果。此命令将结果写入我们目录中名为 ABResults.txt 的文件中,而不只是将它们打印到 shell 控制台。
  • Apache Bench 还有其他用于将结果输出到文件的选项,但我发现它的标准输出是最有用的。

pm2 stop server-profiler

pm2 delete server-profiler

  • 在这里,我们使用我们之前分配给我们的进程的名称进行一些清理。
  • PM2 停止服务器,并删除进程。

The Results

当 bash 脚本完成后,我们将在我们的项目根目录中profiles有一个名为的新文件夹,其子目录以脚本运行的 unix 时间戳命名。

在该文件夹中,我们将有 2 个文件 - Apache Bench 的结果和 .cpuprofile文件:

image.png

示例项目的 Apache Bench 结果如下所示:

image.png

我们看到每个请求的时间超过 17 秒。糟糕的!

我们可以查看我们的 .cpuprofile文件以获取有关正在发生的事情的一些提示。该 .cpuprofile文件是CPU花费多长时间执行代码中的各种功能和操作的措施。

它可以在 Chrome 或 VSCode 中以各种方式查看(作为火焰图、JSON 或可排序列表)。

我个人认为查看按总时间排序的可排序列表最有用:

image.png

我们可以看到 CPU 花费了相当多的时间来执行lodash克隆功能。仔细看看 CPU 支出最高的人:

image.png

显然,complexCalculation函数中存在一些错误的逻辑,导致递归使用克隆函数,这会降低性能。现在我们知道在提高代码效率方面应该把精力集中在哪里。

Conclusion

为 Node.js 应用程序设置分析程序可能既复杂又笨拙。

现在,我们可以用一个脚本NPM做到这一点:npm run profile。这将启动您的应用程序,发出网络请求(如有必要),记录请求的性能以及代码本身,并执行所有必要的清理。

分析您的 Node 应用程序的性能变得轻而易举!

来源

How to Automate Performance Profiling in Node.js - Seth Lutske