├── .gitignore ├── .mailmap ├── .travis.yml ├── LICENSE ├── README.md ├── binding.gyp ├── linux-perf-listener.cc ├── linux-perf.cc ├── linux-perf.h ├── linux-perf.js ├── package.json ├── test.sh └── test ├── test-linux-perf-restart.js ├── test-linux-perf-start.js └── test-linux-perf-stop.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (https://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # TypeScript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | # next.js build output 61 | .next 62 | 63 | build/ 64 | .vscode/ 65 | 66 | package-lock.json 67 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | Mary Marchini 2 | Mary Marchini 3 | Mary Marchini 4 | Mary Marchini 5 | Mary Marchini 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | matrix: 3 | include: 4 | # Test on Ubuntu Xenial 5 | - dist: xenial 6 | node_js: "10" 7 | # Test on Ubuntu Xenial 8 | - dist: xenial 9 | node_js: "12" 10 | # Test Node.js master nightly build 11 | - node_js: "node" 12 | sudo: required 13 | dist: xenial 14 | install: 15 | - npm install --nodedir=$(dirname $(dirname $(which node)))/ 16 | env: 17 | - NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly 18 | - NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly 19 | # Test Node.js v8-canary nightly build 20 | - node_js: "node" 21 | sudo: required 22 | dist: xenial 23 | before_install: 24 | - sudo apt-get -qq update 25 | - sudo apt-get install lldb-3.9 liblldb-3.9-dev -y 26 | install: 27 | - npm install --nodedir=$(dirname $(dirname $(which node)))/ 28 | env: 29 | - NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/v8-canary 30 | - NODEJS_ORG_MIRROR=https://nodejs.org/download/v8-canary 31 | branches: 32 | only: 33 | - master 34 | script: npm test 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mary Marchini 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-linux-perf 2 | 3 | Library to replace V8's `--perf-basic-prof` flag, with the ability to toggle 4 | creation of Linux `perf` map files during runtime. 5 | 6 | It's recommended to run Node.js with the `--interpreted-frames-native-stack` 7 | flag enabled, otherwise Linux perf will not be able to translate the name of 8 | many JavaScript functions. 9 | 10 | ## Build Status 11 | 12 | | Version | Status | 13 | |-----------------------|--------------------------------------------| 14 | | Node.js v10.x | [![v10.x badge][v10-badge]][travis] | 15 | | Node.js v12.x | [![v12.x badge][v12-badge]][travis] | 16 | | nodejs/node@master | [![master badge][master-badge]][travis] | 17 | | nodejs/node-v8@canary | [![v8-canary badge][canary-badge]][travis] | 18 | 19 | [travis]: https://travis-ci.com/mmarchini/node-linux-perf 20 | [master]: https://github.com/nodejs/node/tree/master 21 | [canary]: https://github.com/nodejs/node-v8/tree/canary 22 | [v10-badge]: https://travisci-matrix-badges.herokuapp.com/repos/mmarchini/node-linux-perf/branches/master/1?use_travis_com=true 23 | [v12-badge]: https://travisci-matrix-badges.herokuapp.com/repos/mmarchini/node-linux-perf/branches/master/2?use_travis_com=true 24 | [master-badge]: https://travisci-matrix-badges.herokuapp.com/repos/mmarchini/node-linux-perf/branches/master/3?use_travis_com=true 25 | [canary-badge]: https://travisci-matrix-badges.herokuapp.com/repos/mmarchini/node-linux-perf/branches/master/4?use_travis_com=true 26 | 27 | ## Installation 28 | 29 | ```bash 30 | $ npm install linux-perf 31 | ``` 32 | 33 | ## Usage 34 | 35 | ```javascript 36 | const linuxPerf = require('linux-perf'); 37 | 38 | // Generated a /tmp/perf-PID.map file and updates it when necessary 39 | linuxPerf.start(); 40 | 41 | // **YOUR CODE HERE** 42 | 43 | // Stops writing to /tmp/perf-PID.map 44 | linuxPerf.stop(); 45 | ``` 46 | 47 | ## API 48 | 49 | ### `start(): bool` 50 | 51 | Generates a `/tmp/perf-PID.map` file and updates it when necessary (for example, 52 | when new functions are declared). If a `/tmp/perf-PID.map` file already exists, 53 | its content will be erased, and a new file will be generated. 54 | 55 | **Return**: `true` if the file was generated successfully, `false` otherwise. 56 | 57 | ### `stop(): bool` 58 | 59 | Stops writing to `/tmp/perf-PID.map`. The content written on the file is 60 | preserved. 61 | 62 | **Return**: `true` if it was able to stop writting to the file, `false` 63 | otherwise. 64 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "linux-perf", 5 | "sources": [ 6 | "linux-perf.cc", 7 | "linux-perf-listener.cc", 8 | ], 9 | "include_dirs": [" 2 | 3 | #include "linux-perf.h" 4 | 5 | namespace node { 6 | 7 | LinuxPerfHandler::LinuxPerfHandler(v8::Isolate* isolate) : v8::CodeEventHandler(isolate) { 8 | // TODO(mmarchini):: ideally this should be handled in another thread. 9 | auto pid = static_cast(uv_os_getpid()); 10 | 11 | mapFile.open("/tmp/perf-" + std::to_string(pid) + ".map"); 12 | } 13 | 14 | LinuxPerfHandler::~LinuxPerfHandler() { 15 | mapFile.close(); 16 | } 17 | 18 | std::string LinuxPerfHandler::FormatName(v8::CodeEvent* code_event) { 19 | auto comment = std::string(code_event->GetComment()); 20 | if (not comment.empty()) return comment; 21 | 22 | char buffer[1000]; 23 | std::ostringstream formattedName; 24 | 25 | v8::Isolate* isolate = v8::Isolate::GetCurrent(); 26 | 27 | auto functionName = code_event->GetFunctionName(); 28 | functionName->WriteUtf8(isolate, buffer, 1000); 29 | formattedName << buffer; 30 | 31 | auto scriptName = code_event->GetScriptName(); 32 | scriptName->WriteUtf8(isolate, buffer, 1000); 33 | if(buffer[0] != '\0') { 34 | formattedName << " " << buffer << ":" << code_event->GetScriptLine(); 35 | } 36 | 37 | return formattedName.str(); 38 | } 39 | 40 | void LinuxPerfHandler::Handle(v8::CodeEvent* code_event) { 41 | mapFile << std::hex << code_event->GetCodeStartAddress() << " " 42 | << std::hex << code_event->GetCodeSize() << " " 43 | << v8::CodeEvent::GetCodeEventTypeName(code_event->GetCodeType()) 44 | << ":" << FormatName(code_event) << std::endl; 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /linux-perf.cc: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "linux-perf.h" 4 | 5 | namespace node { 6 | 7 | void LinuxPerf::Initialize(v8::Local target) { 8 | Nan::HandleScope scope; 9 | auto className = Nan::New("LinuxPerf").ToLocalChecked(); 10 | 11 | v8::Local t = 12 | Nan::New(LinuxPerf::New); 13 | t->InstanceTemplate()->SetInternalFieldCount(1); 14 | t->SetClassName(className); 15 | 16 | Nan::SetPrototypeMethod(t, "start", LinuxPerf::Start); 17 | Nan::SetPrototypeMethod(t, "stop", LinuxPerf::Stop); 18 | 19 | Nan::Set(target, className, Nan::GetFunction(t).ToLocalChecked()); 20 | } 21 | 22 | NAN_METHOD(LinuxPerf::New) { 23 | Nan::HandleScope scope; 24 | LinuxPerf *linuxPerf = new LinuxPerf(); 25 | linuxPerf->handler = nullptr; 26 | linuxPerf->Wrap(info.This()); 27 | 28 | info.GetReturnValue().Set(info.This()); 29 | } 30 | 31 | NAN_METHOD(LinuxPerf::Start) { 32 | Nan::HandleScope scope; 33 | LinuxPerf *linuxPerf = Nan::ObjectWrap::Unwrap(info.Holder()); 34 | 35 | if (linuxPerf->handler == nullptr) { 36 | linuxPerf->handler = new LinuxPerfHandler(info.GetIsolate()); 37 | linuxPerf->handler->Enable(); 38 | info.GetReturnValue().Set(true); 39 | return; 40 | } 41 | info.GetReturnValue().Set(false); 42 | } 43 | 44 | NAN_METHOD(LinuxPerf::Stop) { 45 | Nan::HandleScope scope; 46 | LinuxPerf *linuxPerf = Nan::ObjectWrap::Unwrap(info.Holder()); 47 | 48 | if (linuxPerf->handler != nullptr) { 49 | linuxPerf->handler->Disable(); 50 | delete linuxPerf->handler; 51 | linuxPerf->handler = nullptr; 52 | info.GetReturnValue().Set(true); 53 | return; 54 | } 55 | info.GetReturnValue().Set(false); 56 | } 57 | 58 | extern "C" void 59 | init(v8::Local target) { 60 | LinuxPerf::Initialize(target); 61 | } 62 | 63 | NODE_MODULE(LiuxPerfBindings, init) 64 | 65 | }; 66 | -------------------------------------------------------------------------------- /linux-perf.h: -------------------------------------------------------------------------------- 1 | #ifndef __LINUX_PERF_H 2 | #define __LINUX_PERF_H 3 | 4 | #include "v8-profiler.h" 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | namespace node { 11 | 12 | class LinuxPerfHandler : public v8::CodeEventHandler { 13 | public: 14 | explicit LinuxPerfHandler(v8::Isolate* isolate); 15 | ~LinuxPerfHandler() override; 16 | 17 | 18 | void Handle(v8::CodeEvent* code_event) override; 19 | private: 20 | std::ofstream mapFile; 21 | std::string FormatName(v8::CodeEvent* code_event); 22 | }; 23 | 24 | class LinuxPerf : public Nan::ObjectWrap { 25 | public: 26 | static void Initialize(v8::Local target); 27 | 28 | static NAN_METHOD(New); 29 | static NAN_METHOD(Start); 30 | static NAN_METHOD(Stop); 31 | 32 | LinuxPerfHandler* handler; 33 | 34 | LinuxPerf() = default; 35 | ~LinuxPerf() = default; 36 | }; 37 | 38 | }; 39 | 40 | #endif // __LINUX_PERF_H 41 | -------------------------------------------------------------------------------- /linux-perf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const binding = require('./build/Release/linux-perf'); 4 | 5 | module.exports = new binding.LinuxPerf(); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "linux-perf", 3 | "version": "0.3.1", 4 | "description": "", 5 | "main": "linux-perf.js", 6 | "scripts": { 7 | "test": "./test.sh" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/mmarchini/node-linux-perf.git" 12 | }, 13 | "keywords": [ 14 | "linux", 15 | "perf" 16 | ], 17 | "author": "Mary Marchini ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/mmarchini/node-linux-perf/issues" 21 | }, 22 | "homepage": "https://github.com/mmarchini/node-linux-perf#readme", 23 | "dependencies": { 24 | "nan": "^2.10.0" 25 | }, 26 | "devDependencies": { 27 | "clang-format": "^1.2.3", 28 | "tap": "^12.6.5" 29 | }, 30 | "engines": { 31 | "node": ">=10.4.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Testing with no extra flags" 4 | tap "test/*.js" 5 | 6 | # Variant: --interpreted-frames-native-stack 7 | echo "Testing with --interpreted-frames-native-stack" 8 | tap --node-arg=--interpreted-frames-native-stack "test/*.js" 9 | -------------------------------------------------------------------------------- /test/test-linux-perf-restart.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require('tap'); 4 | const { existsSync, readFileSync } = require('fs'); 5 | 6 | const linuxPerf = require('../linux-perf.js'); 7 | const mapFileName = `/tmp/perf-${process.pid}.map`; 8 | 9 | linuxPerf.start(); 10 | 11 | function foo() { 12 | return true; 13 | } 14 | foo(); 15 | 16 | linuxPerf.stop(); 17 | 18 | function bar() { 19 | return true; 20 | } 21 | bar(); 22 | 23 | const resultRegex = /[a-z0-9]+ [a-z0-9]+ [a-zA-Z]+:bar/; 24 | const content = readFileSync(mapFileName, { encoding: 'utf-8' }); 25 | 26 | tap.notMatch(content, resultRegex); 27 | 28 | tap.ok(linuxPerf.start()); 29 | 30 | const content2 = readFileSync(mapFileName, { encoding: 'utf-8' }); 31 | 32 | tap.match(content2, resultRegex); 33 | -------------------------------------------------------------------------------- /test/test-linux-perf-start.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require('tap'); 4 | const { existsSync, readFileSync } = require('fs'); 5 | 6 | const linuxPerf = require('../linux-perf.js'); 7 | const mapFileName = `/tmp/perf-${process.pid}.map`; 8 | 9 | 10 | tap.notOk(existsSync(mapFileName)); 11 | 12 | tap.ok(linuxPerf.start()); 13 | tap.notOk(linuxPerf.start()); 14 | 15 | tap.ok(existsSync(mapFileName)); 16 | 17 | function foo() { 18 | return true; 19 | } 20 | foo(); 21 | 22 | const resultRegex = /[a-z0-9]+ [a-z0-9]+ [a-zA-Z]+:foo/; 23 | 24 | const content = readFileSync(mapFileName, { encoding: 'utf-8' }); 25 | 26 | tap.match(content, resultRegex); 27 | -------------------------------------------------------------------------------- /test/test-linux-perf-stop.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const tap = require('tap'); 4 | const { existsSync, readFileSync } = require('fs'); 5 | 6 | const linuxPerf = require('../linux-perf.js'); 7 | const mapFileName = `/tmp/perf-${process.pid}.map`; 8 | 9 | tap.notOk(linuxPerf.stop()); 10 | 11 | linuxPerf.start(); 12 | 13 | function foo() { 14 | return true; 15 | } 16 | foo(); 17 | 18 | tap.ok(linuxPerf.stop()); 19 | 20 | function bar() { 21 | return true; 22 | } 23 | bar(); 24 | 25 | const resultRegex = /[a-z0-9]+ [a-z0-9]+ [a-zA-Z]+:bar/; 26 | 27 | const content = readFileSync(mapFileName, { encoding: 'utf-8' }); 28 | 29 | tap.notMatch(content, resultRegex); 30 | --------------------------------------------------------------------------------