├── .gitattributes ├── .github └── workflows │ └── nodejs.yml ├── .gitignore ├── .npmignore ├── LICENSE ├── package.json ├── readme.md ├── test └── v8-profiler.js └── v8-profiler.js /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Continuous integration 2 | 3 | on: 4 | push: 5 | branches: [ '**' ] 6 | tags: v*.*.* 7 | pull_request: 8 | branches: [ '**' ] 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.ci-type.os }} 13 | 14 | strategy: 15 | fail-fast: false 16 | matrix: 17 | ci-type: [ 18 | {os: "windows-latest", arch: "x86"}, 19 | {os: "windows-latest", arch: "x64"}, 20 | {os: "macos-latest", arch: "x64"}, 21 | {os: "macos-latest", arch: "arm64"}, 22 | {os: "ubuntu-latest", arch: "x64"}, 23 | {os: "ubuntu-latest", arch: "arm64"}, 24 | ] 25 | node-version: [ 12, 14, 16, 18 ] 26 | 27 | steps: 28 | - uses: actions/checkout@v2 29 | - name: Use Node.js ${{ matrix.node-version }} 30 | uses: actions/setup-node@v1 31 | with: 32 | node-version: ${{ matrix.node-version }} 33 | 34 | - name: Add msbuild to PATH 35 | uses: microsoft/setup-msbuild@v1.1 36 | if: ${{ matrix.ci-type.os == 'windows-latest' }} 37 | 38 | - name: Install Dependencies 39 | run: npm i 40 | 41 | - name: Continuous integration 42 | run: npm run test 43 | 44 | # - name: Build Binary 45 | # run: npm run build-${{ matrix.ci-type.arch }} 46 | 47 | # - name: Package Binary 48 | # run: npm run pack-actions-${{ matrix.ci-type.arch }} 49 | 50 | # - name: Drat Release 51 | # uses: softprops/action-gh-release@v1 52 | # if: startsWith(github.ref, 'refs/tags/') 53 | # with: 54 | # files: release/** 55 | # fail_on_unmatched_files: true 56 | # draft: true 57 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | node_modules 3 | npm-debug.log 4 | .vscode 5 | release 6 | package-lock.json 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | *.log 2 | build/!(profiler) 3 | node_modules 4 | test 5 | v8-profiler-*.tgz 6 | .gitattributes 7 | .gitignore 8 | .npmignore 9 | .travis.yml 10 | appveyor.yml 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011, Danny Coates & hyj1991 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 5 | 6 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 7 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 9 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "v8-profiler-node8", 3 | "main": "v8-profiler", 4 | "version": "6.4.0", 5 | "author": { 6 | "name": "hyj1991", 7 | "email": "yeekwanvong@gmail.com" 8 | }, 9 | "bugs": { 10 | "url": "https://github.com/hyj1991/v8-profiler-node8/issues" 11 | }, 12 | "description": "based on v8-profiler@5.7.0, solved the v8-profiler segment fault error in node 8.x+", 13 | "contributors": [ 14 | { 15 | "name": "Miroslav Bajtoš" 16 | }, 17 | { 18 | "name": "3y3", 19 | "email": "3y3@bk.ru" 20 | }, 21 | { 22 | "name": "hyj1991", 23 | "email": "yeekwanvong@gmail.com" 24 | } 25 | ], 26 | "dependencies": { 27 | "v8-profiler-next": "^1.7.1" 28 | }, 29 | "devDependencies": { 30 | "chai": "^4.3.6", 31 | "mocha": "^5.2.0" 32 | }, 33 | "engines": { 34 | "node": ">=0.10" 35 | }, 36 | "homepage": "http://github.com/hyj1991/v8-profiler-node8", 37 | "keywords": [ 38 | "profiler", 39 | "inspector" 40 | ], 41 | "license": "BSD-2-Clause", 42 | "repository": { 43 | "type": "git", 44 | "url": "git://github.com/hyj1991/v8-profiler-node8.git" 45 | }, 46 | "scripts": { 47 | "test": "mocha -t 0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | [![npm version](https://img.shields.io/npm/v/v8-profiler-node8/latest.svg)](https://www.npmjs.com/package/v8-profiler-node8) 2 | [![Continuous integration](https://github.com/hyj1991/v8-profiler-node8/actions/workflows/nodejs.yml/badge.svg?branch=master)](https://github.com/hyj1991/v8-profiler-node8/actions/workflows/nodejs.yml?query=branch:master) 3 | [![downloads info](https://img.shields.io/npm/dm/v8-profiler-node8.svg)](https://www.npmjs.com/package/v8-profiler-node8) 4 | [![license](https://img.shields.io/npm/l/v8-profiler-node8.svg)](LICENSE) 5 | 6 | # Description 7 | Based on v8-profiler-node8@5.7.0, Solved the v8-profiler segment fault error in node-v8.x+. 8 | 9 | v8-profiler-node8 provides [node](http://github.com/nodejs/node) bindings for the v8 10 | profiler and integration with [node-inspector](http://github.com/node-inspector) 11 | 12 | # Compatibility 13 | * **Platform** 14 | * mac (x64 / arm64) 15 | * linux (x64 / arm64) 16 | * windows (x64) 17 | 18 | * **Node version** 19 | * v4.x 20 | * v5.x 21 | * v6.x 22 | * v7.x 23 | * v8.x 24 | * v9.x 25 | * v10.x 26 | * v11.x 27 | * v12.x 28 | * v13.x 29 | * v14.x 30 | * v15.x 31 | * v16.x 32 | * v17.x 33 | * v18.x 34 | 35 | ## Installation 36 | ```sh 37 | npm install v8-profiler-node8 38 | ``` 39 | ## Usage 40 | ```js 41 | var profiler = require('v8-profiler-node8'); 42 | ``` 43 | ## API 44 | `takeSnapshot([name])` - returns new HEAP Snapshot instance. `name` is optional argument, by default snapshot name will be constructed from his uid. 45 | 46 | `deleteAllSnapshots()` - works as described in name. 47 | 48 | ```js 49 | const profiler = require('v8-profiler-node8') 50 | var snapshot1 = profiler.takeSnapshot('1'); 51 | var snapshot2 = profiler.takeSnapshot() 52 | console.log(profiler.snapshots); 53 | profiler.deleteAllSnapshots(); 54 | ``` 55 | 56 | `startProfiling([name], [recsamples])` - start CPU profiling. `name` is optional argument, by default profile name will be constructed from his uid. `recsamples` is true by default. 57 | 58 | `stopProfiling([name])` - returns new CPU Profile instance. There is no strictly described behavior for usage without `name` argument. 59 | 60 | `setSamplingInterval([num])` - Changes default CPU profiler sampling interval to the specified number of microseconds. Default interval is 1000us. This method must be called when there are no profiles being recorded. If called without arguments it resets interval to default. 61 | 62 | `deleteAllProfiles()` - works as described in name. 63 | 64 | `collectSample()` - causes all active profiles to synchronously record the current callstack. Does not add a new top level sample, only adds more detail to the call graph. 65 | 66 | ```js 67 | const profiler = require('v8-profiler-node8') 68 | profiler.startProfiling('', true); 69 | setTimeout(function() { 70 | var profile = profiler.stopProfiling(''); 71 | console.log(profile) 72 | profiler.deleteAllProfiles(); 73 | }, 1000); 74 | ``` 75 | 76 | ### HEAP Snapshot API 77 | `Snapshot.getHeader()` - provides short information about snapshot. 78 | 79 | `Snapshot.compare(snapshot)` - creates HEAP diff for two snapshots. 80 | 81 | `Snapshot.delete()` - removes snapshot from memory. 82 | 83 | `Snapshot.export([callback])` - provides simple export API for snapshot. `callback(error, data)` receives serialized snapshot as second argument. (Serialization is not equal to `JSON.stringify` result). 84 | 85 | If callback will not be passed, `export` returns transform stream. 86 | 87 | `Snapshot.serialize` - low level serialization method. Look `Snapshot.export` source for usage example. 88 | 89 | ```js 90 | var fs = require('fs'); 91 | var profiler = require('v8-profiler-node8'); 92 | var snapshot1 = profiler.takeSnapshot(); 93 | var snapshot2 = profiler.takeSnapshot(); 94 | 95 | console.log(snapshot1.getHeader(), snapshot2.getHeader()); 96 | 97 | console.log(snapshot1.compare(snapshot2)); 98 | 99 | // Export snapshot to file file 100 | snapshot1.export(function(error, result) { 101 | fs.writeFileSync('snapshot1.json', result); 102 | snapshot1.delete(); 103 | }); 104 | 105 | // Export snapshot to file stream 106 | snapshot2.export() 107 | .pipe(fs.createWriteStream('snapshot2.json')) 108 | .on('finish', snapshot2.delete.bind(snapshot2)); 109 | ``` 110 | 111 | ## CPU Profile API 112 | `Profile.getHeader()` - provides short information about profile. 113 | 114 | `Profile.delete()` - removes profile from memory. 115 | 116 | `Profile.export([callback])` - provides simple export API for profile. `callback(error, data)` receives serialized profile as second argument. (Serialization is equal to `JSON.stringify` result). 117 | 118 | ```js 119 | var fs = require('fs'); 120 | var profiler = require('v8-profiler-node8'); 121 | profiler.startProfiling('1', true); 122 | var profile1 = profiler.stopProfiling(); 123 | profiler.startProfiling('2', true); 124 | var profile2 = profiler.stopProfiling(); 125 | 126 | console.log(profile1.getHeader(), profile2.getHeader()); 127 | 128 | profile1.export(function(error, result) { 129 | fs.writeFileSync('profile1.json', result); 130 | profile1.delete(); 131 | }); 132 | 133 | profile2.export() 134 | .pipe(fs.createWriteStream('profile2.json')) 135 | .on('finish', function() { 136 | profile2.delete(); 137 | }); 138 | ``` 139 | 140 | ## node-inspector 141 | 142 | Cpu profiles can be viewed and heap snapshots may be taken and viewed from the 143 | profiles panel. 144 | -------------------------------------------------------------------------------- /test/v8-profiler.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | const expect = require('chai').expect, 3 | path = require('path'), 4 | profiler = require('../'); 5 | 6 | const NODE_V_010 = /^v0\.10\.\d+$/.test(process.version); 7 | const SOURCE_PATH = path.join(__dirname, '..'); 8 | 9 | function escape(str) { 10 | return str 11 | .replace(/[|\\{}()[\]^$+*?.]/g, '\\$&') 12 | .replace(/-/g, '\\x2d'); 13 | }; 14 | 15 | let workerThreads; 16 | 17 | try { 18 | workerThreads = require('worker_threads'); 19 | } catch (e) { 20 | // worker threads are not supported 21 | } 22 | 23 | describe('v8-profiler', function() { 24 | describe('CPU', function() { 25 | after(deleteAllProfiles); 26 | 27 | describe('Profiler', function() { 28 | 29 | it('should start profiling', function() { 30 | expect(profiler.startProfiling).to.not.throw(); 31 | }); 32 | 33 | it('should stop profiling', function() { 34 | expect(profiler.stopProfiling).to.not.throw(); 35 | }); 36 | 37 | it('should cache profiles', function() { 38 | expect(Object.keys(profiler.profiles)).to.have.length(1); 39 | }); 40 | 41 | it('should replace profile title, if started with name argument', function() { 42 | profiler.startProfiling('P'); 43 | var profile = profiler.stopProfiling(); 44 | expect(profile.title).to.equal('P'); 45 | }); 46 | 47 | it('should record samples, if started with recsamples argument', function() { 48 | if (NODE_V_010) return; 49 | 50 | profiler.startProfiling(true); 51 | var profile = profiler.stopProfiling(); 52 | expect(profile.samples.length > 0).to.equal(true); 53 | }); 54 | 55 | it('should throw on setSamplingInterval if profile recording in progress', function() { 56 | profiler.startProfiling(); 57 | expect(profiler.setSamplingInterval).to.throw( 58 | 'setSamplingInterval must be called when there are no profiles being recorded.'); 59 | profiler.stopProfiling(); 60 | }); 61 | 62 | it('should set sampling interval', function() { 63 | profiler.setSamplingInterval(1000); 64 | }); 65 | }); 66 | 67 | describe('Profile', function() { 68 | it('should export itself with callback', function() { 69 | profiler.startProfiling('', true); 70 | var profile = profiler.stopProfiling(); 71 | profile.export(function(error, result) { 72 | var _result = JSON.parse(result); 73 | 74 | expect(result).to.be.a('string'); 75 | expect(_result).to.be.an('object'); 76 | }); 77 | }); 78 | 79 | it('should export itself with stream', function(done) { 80 | profiler.startProfiling('', true); 81 | var profile = profiler.stopProfiling(); 82 | var fs = require('fs'), 83 | ws = fs.createWriteStream('profile.json'); 84 | profile.export().pipe(ws); 85 | 86 | ws.on('finish', function() { 87 | fs.unlink('profile.json', done); 88 | }); 89 | }); 90 | }); 91 | 92 | function deleteAllProfiles() { 93 | Object.keys(profiler.profiles).forEach(function(key) { 94 | profiler.profiles[key].delete(); 95 | }); 96 | } 97 | }); 98 | 99 | describe('HEAP', function() { 100 | after(deleteAllSnapshots); 101 | 102 | describe('Profiler', function() { 103 | 104 | it('should take snapshot without arguments', function() { 105 | expect(profiler.takeSnapshot).to.not.throw(); 106 | }); 107 | 108 | it('should cache snapshots', function() { 109 | expect(Object.keys(profiler.snapshots)).to.have.length(1); 110 | }); 111 | 112 | it('should replace snapshot title, if started with name argument', function() { 113 | var snapshot = profiler.takeSnapshot('S'); 114 | expect(snapshot.title).to.equal('S'); 115 | }); 116 | 117 | it('should write heap stats', function(done) { 118 | expect(profiler.startTrackingHeapObjects).to.not.throw(); 119 | var lastSeenObjectId = profiler.getHeapStats( 120 | function(samples) { 121 | expect(samples).to.instanceof(Array); 122 | }, 123 | function() { 124 | expect(profiler.stopTrackingHeapObjects).to.not.throw(); 125 | done(); 126 | } 127 | ); 128 | expect(typeof lastSeenObjectId).to.be.equal('number'); 129 | }); 130 | 131 | it('should return undefined for wrong params in getObjectByHeapObjectId', function() { 132 | expect(profiler.getObjectByHeapObjectId('a')).to.be.equal(undefined); 133 | }); 134 | 135 | it('should return id for object in getHeapObjectId', function() { 136 | var obj = {}; 137 | var snapshot = profiler.takeSnapshot(); 138 | expect(profiler.getHeapObjectId(obj)).to.be.gt(0); 139 | }); 140 | 141 | it('should return id for undefined param in getHeapObjectId', function() { 142 | var snapshot = profiler.takeSnapshot(); 143 | expect(profiler.getHeapObjectId(undefined)).to.be.gt(0); 144 | }); 145 | 146 | it('should return undefined for wrong params in getHeapObjectId', function() { 147 | var snapshot = profiler.takeSnapshot(); 148 | expect(profiler.getHeapObjectId()).to.be.equal(undefined); 149 | }); 150 | }); 151 | 152 | describe('Snapshot', function() { 153 | 154 | it('should delete itself from profiler cache', function() { 155 | var snapshot = profiler.takeSnapshot(); 156 | var uid = snapshot.uid; 157 | 158 | var oldSnapshotsLength = Object.keys(profiler.snapshots).length; 159 | snapshot.delete(); 160 | 161 | expect(Object.keys(profiler.snapshots).length == oldSnapshotsLength - 1).to.equal(true); 162 | expect(profiler.snapshots[uid]).to.be.equal(undefined); 163 | }); 164 | 165 | it('should serialise itself', function(done) { 166 | var snapshot = profiler.takeSnapshot(); 167 | var buffer = ''; 168 | snapshot.serialize( 169 | function iterator(data, length) { 170 | buffer += data; 171 | }, 172 | function callback() { 173 | expect(JSON.parse.bind(JSON, buffer)).to.not.throw(); 174 | done(); 175 | } 176 | ); 177 | }); 178 | 179 | it('should pipe itself to stream', function(done) { 180 | var snapshot = profiler.takeSnapshot(); 181 | var fs = require('fs'), 182 | ws = fs.createWriteStream('snapshot.json') 183 | .on('finish', function() { 184 | fs.unlink('snapshot.json', done); 185 | }); 186 | 187 | snapshot.export().pipe(ws); 188 | }); 189 | 190 | it('should export itself to callback', function(done) { 191 | var snapshot = profiler.takeSnapshot(); 192 | 193 | snapshot.export(function(err, result) { 194 | expect(!err); 195 | expect(typeof result == 'string'); 196 | done(); 197 | }); 198 | }); 199 | 200 | it('should compare itself with other snapshot', function() { 201 | this.timeout(5000); 202 | var snapshot1 = profiler.takeSnapshot(); 203 | var snapshot2 = profiler.takeSnapshot(); 204 | 205 | expect(snapshot1.compare.bind(snapshot1, snapshot2)).to.not.throw(); 206 | }); 207 | }); 208 | 209 | function deleteAllSnapshots() { 210 | Object.keys(profiler.snapshots).forEach(function(key) { 211 | profiler.snapshots[key].delete(); 212 | }); 213 | } 214 | }); 215 | 216 | if (workerThreads) { 217 | describe('worker_threads compatibility', function() { 218 | it('supports profiling workers', function(done) { 219 | const worker = new workerThreads.Worker(` 220 | const {parentPort} = require('worker_threads'); 221 | const profiler = require('${escape(SOURCE_PATH)}'); 222 | profiler.startProfiling('worker'); 223 | let a = 1; 224 | for (let i = 0; i<1e6; i++) { a += a }; 225 | const profile = profiler.stopProfiling('worker'); 226 | parentPort.postMessage(JSON.stringify(profile)); 227 | `, {eval: true}); 228 | 229 | worker.on('message', function (serializedProfile) { 230 | const profile = JSON.parse(serializedProfile); 231 | 232 | expect(Object.keys(profile).sort()).to.deep.eq([ 233 | "typeId", 234 | "uid", 235 | "title", 236 | "head", 237 | "startTime", 238 | "endTime", 239 | "samples", 240 | "timestamps" 241 | ].sort()); 242 | 243 | done(); 244 | }); 245 | 246 | worker.on('error', done) 247 | }); 248 | }); 249 | } 250 | }); -------------------------------------------------------------------------------- /v8-profiler.js: -------------------------------------------------------------------------------- 1 | module.exports = require('v8-profiler-next'); 2 | --------------------------------------------------------------------------------