├── .gitignore ├── .eslintrc.js ├── README.md ├── LICENSE ├── package.json ├── test └── index.test.js └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | .vscode -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: false, 4 | commonjs: true, 5 | es2021: true, 6 | }, 7 | extends: ['eslint:recommended', 'airbnb'], 8 | overrides: [ 9 | { 10 | env: { 11 | node: true, 12 | }, 13 | files: [ 14 | '.eslintrc.{js,cjs}', 15 | ], 16 | parserOptions: { 17 | sourceType: 'script', 18 | }, 19 | }, 20 | ], 21 | parserOptions: { 22 | ecmaVersion: 'latest', 23 | }, 24 | rules: { 25 | 'no-undef': 'off', 26 | 'no-use-before-define': 'off', 27 | 'no-empty': 'off', 28 | 'global-require': 'off', 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-pprof-middleware 2 | Express middleware that exposes pprof endpoints for easy profiling 3 | 4 | ## Installation 5 | ``` 6 | npm i -S express-pprof-middleware 7 | ``` 8 | 9 | ## Supported Profiles 10 | * Heap 11 | * Wall Time 12 | 13 | ## Usage 14 | 15 | ```js 16 | const pprof = require('express-pprof-middleware'); 17 | 18 | const app = express(); 19 | app.use(pprof); 20 | ``` 21 | 22 | ### Getting a heap profile: 23 | ``` 24 | curl http://localhost:8000/debug/pprof/heap -o heap.pb.gz 25 | ``` 26 | 27 | ### Getting a wall time profile: 28 | ``` 29 | curl http://localhost:8000/debug/pprof/wall?seconds=5 -o wall.pb.gz 30 | ``` 31 | 32 | ## Viewing Profiles 33 | Full details on the pprof tool here: https://github.com/google/pprof 34 | ### Installing pprof: 35 | ``` 36 | go install github.com/google/pprof@latest 37 | ``` 38 | 39 | ### Viewing a profile in graph format: 40 | ``` 41 | pprof -web heap.gz 42 | ``` 43 | 44 | ### Viewing on an interactive web interface: 45 | ``` 46 | pprof -http=":" heap.gz 47 | ``` -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Tesla, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-pprof-middleware", 3 | "version": "0.0.8", 4 | "description": "Express middleware that exposes pprof endpoints for easy profiling", 5 | "main": "index.js", 6 | "devDependencies": { 7 | "chai": "~3.5.0", 8 | "co-mocha": "1.2.2", 9 | "coveralls": "3.0.2", 10 | "eslint": "8.57.0", 11 | "eslint-config-airbnb": "19.0.4", 12 | "express": "^4.14.0", 13 | "is-path-in-cwd": "^2.0.0", 14 | "istanbul": "~0.4.2", 15 | "mocha": "5.2.0", 16 | "mocha-lcov-reporter": "~1.0.0" 17 | }, 18 | "scripts": { 19 | "test": "mocha --timeout 5000", 20 | "lint": "eslint .", 21 | "lint:fix": "eslint --fix --ext .js .", 22 | "start": "npm test" 23 | }, 24 | "keywords": [ 25 | "express", 26 | "pprof", 27 | "middleware", 28 | "heap", 29 | "profiling" 30 | ], 31 | "homepage": "https://github.com/teslamotors/express-pprof-middleware", 32 | "author": "Leon Li", 33 | "license": "MIT", 34 | "dependencies": { 35 | "pprof": "^3.2.0" 36 | }, 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/teslamotors/express-pprof-middleware.git" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /test/index.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | 3 | describe('module main file - index.js', () => { 4 | it('should call without error', () => { 5 | require('../index'); 6 | }); 7 | }); 8 | 9 | describe('heap profile', () => { 10 | it('should be correctly created and called', async () => { 11 | const middleware = require('../index'); 12 | expect(middleware).to.be.instanceof(Function); 13 | 14 | let heapFilePath = ''; 15 | const req = { get: () => {}, path: '/debug/pprof/heap' }; 16 | const res = { set: () => {}, sendFile: (file) => { heapFilePath = file; } }; 17 | 18 | await middleware(req, res, () => {}); 19 | expect(heapFilePath).to.equal('/tmp/heap.pb.gz'); 20 | }); 21 | }); 22 | 23 | describe('wall profile', () => { 24 | it('should be correctly created and called', async () => { 25 | const middleware = require('../index'); 26 | expect(middleware).to.be.instanceof(Function); 27 | 28 | let wallFilePath = ''; 29 | const req = { get: () => {}, path: '/debug/pprof/wall', query: { seconds: 1 } }; 30 | const res = { set: () => {}, sendFile: (file) => { wallFilePath = file; } }; 31 | 32 | await middleware(req, res, () => {}); 33 | expect(wallFilePath).to.equal('/tmp/wall.pb.gz'); 34 | }); 35 | }); 36 | 37 | describe('heap stop', () => { 38 | it('should be correctly created and called', async () => { 39 | const middleware = require('../index'); 40 | expect(middleware).to.be.instanceof(Function); 41 | 42 | let sendResult = ''; 43 | const req = { get: () => {}, path: '/debug/pprof/heap/stop' }; 44 | const res = { set: () => {}, send: (msg) => { sendResult = msg; } }; 45 | 46 | await middleware(req, res, () => {}); 47 | expect(sendResult).to.equal(''); 48 | }); 49 | }); 50 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const pprof = require('pprof'); 2 | const fs = require('fs'); 3 | 4 | const heapFilePath = '/tmp/heap.pb.gz'; 5 | const wallFilePath = '/tmp/wall.pb.gz'; 6 | 7 | const heapWebPath = '/debug/pprof/heap'; 8 | const heapStopPath = '/debug/pprof/heap/stop'; 9 | const wallWebPath = '/debug/pprof/wall'; 10 | 11 | const intervalBytes = 512 * 1024; 12 | const stackDepth = 64; 13 | 14 | // Start Heap Profiler 15 | pprof.heap.start(intervalBytes, stackDepth); 16 | 17 | /** 18 | * @param {Object} req 19 | * @param {Object} res 20 | * @param {Function} next 21 | */ 22 | const middleware = async (req, res, next) => { 23 | switch (req.path) { 24 | case heapWebPath: 25 | try { 26 | await heap(heapFilePath); 27 | } catch (err) { 28 | return res.status(500).send(err.message); 29 | } 30 | return res.sendFile(heapFilePath); 31 | case wallWebPath: 32 | try { 33 | let millis = 5000; 34 | if (req.query.seconds) { 35 | const secs = parseInt(req.query.seconds, 10); 36 | if (!Number.isNaN(secs) && secs > 0) { 37 | millis = secs * 1000; 38 | } 39 | } 40 | await wall(wallFilePath, millis); 41 | } catch (err) { 42 | return res.status(500).send(err.message); 43 | } 44 | return res.sendFile(wallFilePath); 45 | case heapStopPath: 46 | try { 47 | pprof.heap.stop(); 48 | return res.send(''); 49 | } catch (err) { 50 | return res.status(500).send(err.message); 51 | } 52 | default: 53 | return next(); 54 | } 55 | }; 56 | 57 | /** 58 | * @param {string} outPath - output path for the heap protobuf output 59 | */ 60 | const heap = async (outPath) => { 61 | try { 62 | pprof.heap.start(intervalBytes, stackDepth); 63 | } catch { } 64 | const profile = await pprof.heap.profile(); 65 | const buf = await pprof.encode(profile); 66 | return new Promise((resolve, reject) => { 67 | fs.writeFile(outPath, buf, (err) => { 68 | if (err) { 69 | reject(err); 70 | } else { 71 | resolve(); 72 | } 73 | }); 74 | }); 75 | }; 76 | 77 | /** 78 | * @param {string} outPath - output path for the heap protobuf output 79 | * @param {number} durationMillis - sampling duration 80 | */ 81 | const wall = async (outPath, durationMillis) => { 82 | const profile = await pprof.time.profile({ 83 | durationMillis, 84 | }); 85 | const buf = await pprof.encode(profile); 86 | return new Promise((resolve, reject) => { 87 | fs.writeFile(outPath, buf, (err) => { 88 | if (err) { 89 | reject(err); 90 | } else { 91 | resolve(); 92 | } 93 | }); 94 | }); 95 | }; 96 | 97 | module.exports = middleware; 98 | --------------------------------------------------------------------------------