├── packages ├── diat │ ├── src │ │ ├── index.ts │ │ ├── Error.ts │ │ ├── Types.ts │ │ ├── Logger.ts │ │ ├── LinuxPerf.ts │ │ ├── NodeInspectWrapper.ts │ │ ├── Snippets.ts │ │ ├── StackVis.ts │ │ ├── utils.ts │ │ ├── InspectorWorker.ts │ │ ├── TcpProxy.ts │ │ ├── Perf.ts │ │ ├── InspectorWorkerSession.ts │ │ └── Metric.ts │ ├── .npmrc │ ├── bin │ │ └── diat.js │ ├── __tests__ │ │ ├── test_process │ │ │ ├── thread_worker.js │ │ │ ├── udp_socket.js │ │ │ ├── thread.js │ │ │ ├── testIdleServer.js │ │ │ ├── net_socket_server.js │ │ │ ├── inspector_open.js │ │ │ └── childprocess.js │ │ ├── LinuxPerf.spec.ts │ │ ├── CLI.spec.ts │ │ ├── NodeInspectWrapper.spec.ts │ │ ├── utils.ts │ │ ├── StackVis.spec.ts │ │ ├── Metric.spec.ts │ │ ├── TcpProxy.spec.ts │ │ ├── toggle_perf_basic_prof.spec.ts │ │ ├── utils.spec.ts │ │ ├── Perf.spec.ts │ │ ├── InspectorWorker.spec.ts │ │ ├── Snippets.spec.ts │ │ ├── get_active_handles.spec.ts │ │ ├── InspectorWorkerSession.spec.ts │ │ └── out.perftext │ ├── .prettierrc │ ├── tsconfig.test.json │ ├── snippet │ │ ├── get_active_requests.js │ │ ├── close_inspector.js │ │ ├── open_inspector.js │ │ ├── toggle_perf_basic_prof.js │ │ ├── metric_collect.js │ │ └── get_active_handles.js │ ├── tslint.json │ ├── jest.config.json │ ├── scripts │ │ └── ci.js │ ├── tsconfig.json │ ├── CHANGELOG.md │ └── package.json ├── node-inspect │ ├── examples │ │ ├── empty.js │ │ ├── use-strict.js │ │ ├── three-lines.js │ │ ├── cjs │ │ │ ├── other.js │ │ │ └── index.js │ │ ├── alive.js │ │ ├── exceptions.js │ │ ├── break.js │ │ └── backtrace.js │ ├── cli.js │ ├── .gitignore │ ├── .npmrc │ ├── GOVERNANCE.md │ ├── .editorconfig │ ├── appveyor.yml │ ├── .travis.yml │ ├── test │ │ └── cli │ │ │ ├── help.test.js │ │ │ ├── use-strict.test.js │ │ │ ├── backtrace.test.js │ │ │ ├── profile.test.js │ │ │ ├── heap-profiler.test.js │ │ │ ├── low-level.test.js │ │ │ ├── console.test.js │ │ │ ├── watchers.test.js │ │ │ ├── pid.test.js │ │ │ ├── invalid-args.test.js │ │ │ ├── address.test.js │ │ │ ├── preserve-breaks.test.js │ │ │ ├── exceptions.test.js │ │ │ ├── exec.test.js │ │ │ ├── scripts.test.js │ │ │ ├── autocomplete.test.js │ │ │ └── launch.test.js │ ├── tools │ │ └── eslint-rules │ │ │ ├── require-buffer.js │ │ │ ├── buffer-constructor.js │ │ │ ├── new-with-error.js │ │ │ ├── assert-fail-single-argument.js │ │ │ ├── prefer-assert-methods.js │ │ │ ├── no-let-in-for-declaration.js │ │ │ ├── align-multiline-assignment.js │ │ │ ├── align-function-arguments.js │ │ │ └── required-modules.js │ ├── package.json │ ├── README.md │ ├── LICENSE │ ├── lib │ │ └── cli.js │ └── .eslintrc ├── stackvis-simplified │ ├── .gitmodules │ ├── .gitignore │ ├── .npmrc │ ├── cmd │ │ ├── stackcollapse │ │ ├── stackcollapse-perf │ │ ├── stackcollapse-stap │ │ ├── flamegraph │ │ └── stackvis │ ├── package.json │ ├── lib │ │ ├── output-collapsed.js │ │ ├── input-collapsed.js │ │ ├── demo.js │ │ ├── color.js │ │ ├── xml.js │ │ ├── output-flamegraph-d3.js │ │ ├── input-dtrace.js │ │ ├── input-perf.js │ │ └── input-stap.js │ ├── Makefile │ ├── LICENSE │ ├── share │ │ ├── icicle.htm │ │ └── icicle.css │ └── README.md ├── linux-perf │ ├── .npmrc │ ├── linux-perf.js │ ├── CAHNGELOG.md │ ├── test.sh │ ├── binding.gyp │ ├── test │ │ ├── test-linux-perf-start.js │ │ ├── test-linux-perf-stop.js │ │ └── test-linux-perf-restart.js │ ├── package.json │ ├── linux-perf.h │ ├── .travis.yml │ ├── LICENSE │ ├── .gitignore │ ├── install.js │ ├── linux-perf.cc │ ├── linux-perf-listener.cc │ └── README.md └── live-inspector │ ├── src │ ├── Types.ts │ ├── Error.ts │ ├── index.ts │ ├── utils.ts │ ├── TcpDetector.ts │ └── ProcessInspectPort.ts │ ├── CHANGELOG.md │ ├── __tests__ │ ├── index.spec.ts │ ├── testIdleServer.js │ ├── testInspectServer.js │ ├── utils.ts │ ├── TcpDetector.spec.ts │ ├── ProcessInspectPort.spec.ts │ └── Communication.spec.ts │ ├── tsconfig.test.json │ ├── tslint.json │ ├── jest.config.json │ ├── tsconfig.json │ ├── package.json │ └── README.md ├── .npmrc ├── .gitignore ├── imgs ├── perf_svg.png ├── cpuprofile.png ├── heapsnapshot.png └── heaptimeline.png ├── lerna.json ├── package.json ├── .github └── workflows │ └── nodejs.yml └── LICENSE /packages/diat/src/index.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/empty.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /packages/diat/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /packages/linux-perf/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org/ 2 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.swp 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lerna-debug.log 3 | .DS_Store 4 | 5 | test.js 6 | -------------------------------------------------------------------------------- /imgs/perf_svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/diat/master/imgs/perf_svg.png -------------------------------------------------------------------------------- /packages/diat/bin/diat.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('../lib/CLI').main(); 3 | -------------------------------------------------------------------------------- /packages/node-inspect/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | require('./lib/cli.js'); 3 | -------------------------------------------------------------------------------- /imgs/cpuprofile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/diat/master/imgs/cpuprofile.png -------------------------------------------------------------------------------- /packages/diat/__tests__/test_process/thread_worker.js: -------------------------------------------------------------------------------- 1 | setInterval(() => {}, 1000); 2 | -------------------------------------------------------------------------------- /packages/node-inspect/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | /tmp 4 | /.vs 5 | -------------------------------------------------------------------------------- /imgs/heapsnapshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/diat/master/imgs/heapsnapshot.png -------------------------------------------------------------------------------- /imgs/heaptimeline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyyd/diat/master/imgs/heaptimeline.png -------------------------------------------------------------------------------- /packages/node-inspect/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*" 4 | ], 5 | "version": "1.4.0" 6 | } 7 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/use-strict.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | console.log('first real line'); 3 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/.npmrc: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmjs.org 2 | package-lock=false 3 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/three-lines.js: -------------------------------------------------------------------------------- 1 | let x = 1; 2 | x = x + 1; 3 | module.exports = x; 4 | -------------------------------------------------------------------------------- /packages/diat/src/Error.ts: -------------------------------------------------------------------------------- 1 | export class DiatError extends Error { 2 | public _type: string = 'diat' 3 | } 4 | -------------------------------------------------------------------------------- /packages/live-inspector/src/Types.ts: -------------------------------------------------------------------------------- 1 | export interface IAddr { 2 | host: string; 3 | port: number; 4 | } 5 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/cjs/other.js: -------------------------------------------------------------------------------- 1 | exports.add = function add(a, b) { 2 | return a + b; 3 | }; 4 | -------------------------------------------------------------------------------- /packages/live-inspector/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.0.1 4 | fix: fix ts module type 5 | 6 | ## 1.0.0 7 | -------------------------------------------------------------------------------- /packages/live-inspector/__tests__/index.spec.ts: -------------------------------------------------------------------------------- 1 | describe('hello', () => { 2 | it('should be goold', async () => {}); 3 | }); 4 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/alive.js: -------------------------------------------------------------------------------- 1 | let x = 0; 2 | function heartbeat() { 3 | ++x; 4 | } 5 | setInterval(heartbeat, 50); 6 | -------------------------------------------------------------------------------- /packages/diat/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": false, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /packages/live-inspector/__tests__/testIdleServer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | setInterval(() => {}, 1000); 3 | 4 | process.send('hello'); 5 | -------------------------------------------------------------------------------- /packages/live-inspector/src/Error.ts: -------------------------------------------------------------------------------- 1 | export class LiveInspectorError extends Error { 2 | public _type: string = 'live_inspector'; 3 | } 4 | -------------------------------------------------------------------------------- /packages/live-inspector/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": "." 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /packages/diat/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "module": "CommonJS" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/linux-perf/linux-perf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const binding = require('./build/Release/linux-perf'); 4 | 5 | module.exports = new binding.LinuxPerf(); 6 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/cjs/index.js: -------------------------------------------------------------------------------- 1 | const fourty = 40; 2 | const { add } = require('./other'); 3 | 4 | const sum = add(fourty, 2); 5 | module.exports = sum; 6 | -------------------------------------------------------------------------------- /packages/diat/snippet/get_active_requests.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const requests = process._getActiveRequests(); 3 | return JSON.stringify(requests.map(i => i)); 4 | })(); 5 | -------------------------------------------------------------------------------- /packages/diat/snippet/close_inspector.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const inspector = require('inspector'); 3 | inspector.close(); 4 | return JSON.stringify({}); 5 | })(__OPTIONS__); 6 | -------------------------------------------------------------------------------- /packages/live-inspector/src/index.ts: -------------------------------------------------------------------------------- 1 | export { Communication } from './Communication'; 2 | export { LiveInspectorError } from './Error'; 3 | export { TcpDetector } from './TcpDetector'; 4 | -------------------------------------------------------------------------------- /packages/live-inspector/src/utils.ts: -------------------------------------------------------------------------------- 1 | export function wait(t: number) { 2 | return new Promise(resolve => { 3 | setTimeout(() => { 4 | resolve(); 5 | }, t); 6 | }); 7 | } 8 | -------------------------------------------------------------------------------- /packages/linux-perf/CAHNGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.0.2 4 | fix: ignore "engine" of package.json for yarn 5 | 6 | ## 1.0.1 7 | fix: remove `~LinuxPerf()` to prevent "lazy symbol binding failed" 8 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/exceptions.js: -------------------------------------------------------------------------------- 1 | let error = null; 2 | try { 3 | throw new Error('Caught'); 4 | } catch (e) { 5 | error = e; 6 | } 7 | 8 | if (error) { 9 | throw new Error('Uncaught'); 10 | } 11 | -------------------------------------------------------------------------------- /packages/node-inspect/GOVERNANCE.md: -------------------------------------------------------------------------------- 1 | # node-inspect Project Governance 2 | 3 | The node-inspect project is governed by the Node.js Diagnostics WG as described 4 | at . 5 | -------------------------------------------------------------------------------- /packages/node-inspect/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [*.md] 11 | trim_trailing_whitespace = false 12 | -------------------------------------------------------------------------------- /packages/live-inspector/__tests__/testInspectServer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const inspector = require('inspector'); 3 | 4 | inspector.open(0); 5 | 6 | if (process.send) { 7 | process.send(inspector.url()); 8 | } 9 | 10 | setInterval(() => {}, 1000); 11 | -------------------------------------------------------------------------------- /packages/node-inspect/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | nodejs_version: "6" 3 | 4 | install: 5 | - ps: Install-Product node $env:nodejs_version 6 | - npm install 7 | 8 | test_script: 9 | - node --version 10 | - npm --version 11 | - npm test 12 | 13 | build: off 14 | -------------------------------------------------------------------------------- /packages/linux-perf/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 | -------------------------------------------------------------------------------- /packages/diat/__tests__/test_process/udp_socket.js: -------------------------------------------------------------------------------- 1 | const dgram = require('dgram'); 2 | 3 | module.exports = async () => { 4 | const socket = dgram.createSocket('udp4'); 5 | 6 | await new Promise(resolve => { 7 | socket.bind(0, resolve); 8 | }); 9 | 10 | return {}; 11 | }; 12 | -------------------------------------------------------------------------------- /packages/diat/snippet/open_inspector.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const inspector = require('inspector'); 3 | let url = inspector.url(); 4 | 5 | if (!url) { 6 | inspector.open(0); 7 | url = inspector.url(); 8 | } 9 | return JSON.stringify({ 10 | url 11 | }); 12 | })(__OPTIONS__); 13 | -------------------------------------------------------------------------------- /packages/diat/src/Types.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter as Event } from 'events' 2 | 3 | export type IPostFunc = (method: string, params: any) => Promise 4 | 5 | export interface IComm { 6 | event: Event 7 | post: IPostFunc 8 | } 9 | 10 | export type IUploadFileFunc = (file: string, gzip?: boolean) => Promise 11 | -------------------------------------------------------------------------------- /packages/diat/__tests__/LinuxPerf.spec.ts: -------------------------------------------------------------------------------- 1 | import { LinuxPerf } from '../src/LinuxPerf'; 2 | 3 | describe('LinuxPerf', () => { 4 | it('should return the module path', () => { 5 | const linuxPerf = new LinuxPerf(); 6 | const modulePath = linuxPerf.getModulePath(); 7 | expect(typeof modulePath).toBe('string'); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/diat/src/Logger.ts: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | export class Logger { 3 | log(...args) { 4 | return console.log(...args) 5 | } 6 | 7 | warn(...args) { 8 | return console.warn(...args) 9 | } 10 | 11 | error(...args) { 12 | return console.error(...args) 13 | } 14 | } 15 | 16 | export const logger = new Logger() 17 | -------------------------------------------------------------------------------- /packages/diat/__tests__/test_process/thread.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const thread = require('worker_threads'); 3 | const path = require('path'); 4 | 5 | module.exports = async () => { 6 | setInterval(() => {}, 1000); 7 | for (let i = 0; i < 2; i += 1) { 8 | new thread.Worker(path.resolve(__dirname, './thread_worker.js')); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /packages/node-inspect/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '6.8' 4 | before_deploy: 5 | - git config --global user.email "jan.krems@gmail.com" 6 | - git config --global user.name "Jan Krems" 7 | deploy: 8 | provider: script 9 | script: ./node_modules/.bin/nlm release 10 | skip_cleanup: true 11 | 'on': 12 | branch: master 13 | node: '6.8' 14 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/break.js: -------------------------------------------------------------------------------- 1 | const x = 10; 2 | let name = 'World'; 3 | name = 'Robin'; 4 | function sayHello() { 5 | if (x > 0) { 6 | console.log(`Hello ${name}`); 7 | } 8 | } 9 | sayHello(); 10 | debugger; 11 | setTimeout(sayHello, 10); 12 | 13 | function otherFunction() { 14 | console.log('x = %d', x); 15 | } 16 | setTimeout(otherFunction, 50); 17 | -------------------------------------------------------------------------------- /packages/diat/__tests__/test_process/testIdleServer.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | setInterval(() => {}, 1000); 3 | 4 | if (process.send) { 5 | if (process.argv[2]) { 6 | const func = require(process.argv[2]); 7 | func().then(obj => { 8 | process.send('hello'); 9 | }); 10 | } else { 11 | process.send('hello'); 12 | } 13 | } else { 14 | console.log(process.pid); 15 | } 16 | -------------------------------------------------------------------------------- /packages/diat/__tests__/test_process/net_socket_server.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | const net = require('net'); 3 | 4 | const server = net.createServer(() => {}); 5 | 6 | await new Promise(resolve => { 7 | server.listen(resolve); 8 | }); 9 | 10 | await new Promise(resolve => { 11 | const client = net.connect(server.address().port, resolve); 12 | }); 13 | 14 | return {}; 15 | }; 16 | -------------------------------------------------------------------------------- /packages/diat/snippet/toggle_perf_basic_prof.js: -------------------------------------------------------------------------------- 1 | (async options => { 2 | if (typeof options !== 'object') { 3 | throw new Error('invalid options'); 4 | } 5 | 6 | const { enable, modulePath } = options; 7 | const linuxPerf = require(modulePath); 8 | return JSON.stringify({ 9 | operationReturn: enable ? linuxPerf.start() : linuxPerf.stop(), 10 | pid: process.pid 11 | }); 12 | })(__OPTIONS__); 13 | -------------------------------------------------------------------------------- /packages/diat/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 5 | }, 6 | "rules": { 7 | "ordered-imports": false, 8 | "member-access": false, 9 | "no-empty-interface": false, 10 | "variable-name": false, 11 | "no-console": false, 12 | "object-literal-sort-keys": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/live-inspector/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["tslint:recommended", "tslint-config-prettier"], 3 | "linterOptions": { 4 | "exclude": ["config/**/*.js", "node_modules/**/*.ts"] 5 | }, 6 | "rules": { 7 | "ordered-imports": false, 8 | "member-access": false, 9 | "no-empty-interface": false, 10 | "variable-name": false, 11 | "no-console": false, 12 | "object-literal-sort-keys": false 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /packages/diat/__tests__/test_process/inspector_open.js: -------------------------------------------------------------------------------- 1 | module.exports = async () => { 2 | const inspector = require('inspector') 3 | 4 | setInterval(() => { 5 | 6 | }, 1000) 7 | 8 | inspector.open(0) 9 | const url = inspector.url() 10 | const ret = /ws\:\/\/(.+)\:(\d+)/.exec(url) 11 | 12 | if (!ret) { 13 | process.exit(1) 14 | } 15 | 16 | process.send({ 17 | host: ret[1], 18 | port: Number(ret[2]) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /packages/diat/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "ts-jest": { 4 | "tsConfig": "tsconfig.test.json" 5 | } 6 | }, 7 | "transform": { 8 | "^.+\\.jsx?$": "babel-jest", 9 | "^.+\\.tsx?$": "ts-jest" 10 | }, 11 | "testEnvironment": "node", 12 | "testRegex": "__tests__/.+((\\.|/)(test|spec))\\.(jsx?|tsx?)$", 13 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], 14 | "testURL": "http://localhost" 15 | } 16 | -------------------------------------------------------------------------------- /packages/live-inspector/jest.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "ts-jest": { 4 | "tsConfig": "tsconfig.test.json" 5 | } 6 | }, 7 | "transform": { 8 | "^.+\\.jsx?$": "babel-jest", 9 | "^.+\\.tsx?$": "ts-jest" 10 | }, 11 | "testEnvironment": "node", 12 | "testRegex": "__tests__/.+((\\.|/)(test|spec))\\.(jsx?|tsx?)$", 13 | "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json"], 14 | "testURL": "http://localhost" 15 | } 16 | -------------------------------------------------------------------------------- /packages/diat/__tests__/CLI.spec.ts: -------------------------------------------------------------------------------- 1 | import { CLI } from '../src/CLI' 2 | 3 | describe('CLI', () => { 4 | describe('CLI#getYargsOptions', () => { 5 | it('should return options', () => { 6 | const cli = new CLI({}) 7 | const options = cli.getYargsOptions() 8 | expect(options.length).toBeGreaterThan(0) 9 | const inspectOpt = options.find((i) => i.command === 'inspect') 10 | expect(inspectOpt).toBeTruthy() 11 | }) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /packages/linux-perf/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": [ 10 | " { 9 | process.stdout.write(data) 10 | }) 11 | child.stderr.on('data', (data) => { 12 | process.stderr.write(data) 13 | }) 14 | 15 | child.on('exit', () => { 16 | process.exit() 17 | }) 18 | 19 | setTimeout(() => { 20 | child.kill() 21 | process.exit() 22 | }, kCiTestTimeout) 23 | } 24 | 25 | main() 26 | -------------------------------------------------------------------------------- /packages/diat/__tests__/test_process/childprocess.js: -------------------------------------------------------------------------------- 1 | const childProcess = require('child_process'); 2 | const path = require('path'); 3 | 4 | module.exports = async () => { 5 | const filepath = path.resolve(__dirname, './testIdleServer.js'); 6 | const child = childProcess.fork(filepath, [], { 7 | env: {}, 8 | execArgv: [] 9 | }); 10 | 11 | await new Promise(resolve => { 12 | child.on('message', resolve); 13 | }); 14 | 15 | setTimeout(() => { 16 | child.kill(); 17 | }, 100); 18 | 19 | return { 20 | stop: () => child.kill() 21 | }; 22 | }; 23 | -------------------------------------------------------------------------------- /packages/diat/src/LinuxPerf.ts: -------------------------------------------------------------------------------- 1 | import * as semver from 'semver' 2 | import * as os from 'os' 3 | 4 | export class LinuxPerf { 5 | constructor() {} 6 | 7 | getModulePath(): null | string { 8 | if ( 9 | os.platform() === 'win32' || 10 | !semver.satisfies(process.version, '>=10.4.0') 11 | ) { 12 | /* istanbul ignore next */ 13 | return null 14 | } 15 | 16 | try { 17 | return require.resolve('diat-linux-perf') 18 | } catch (err) { 19 | // 20 | } 21 | /* istanbul ignore next */ 22 | return null 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/help.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('examples/empty.js', (t) => { 7 | const cli = startCLI(['examples/empty.js']); 8 | 9 | function onFatal(error) { 10 | cli.quit(); 11 | throw error; 12 | } 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => cli.command('help')) 17 | .then(() => { 18 | t.match(cli.output, /run, restart, r\s+/m); 19 | }) 20 | .then(() => cli.quit()) 21 | .then(null, onFatal); 22 | }); 23 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/require-buffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function(context) { 4 | function flagIt(reference) { 5 | const msg = 'Use const Buffer = require(\'buffer\').Buffer; ' + 6 | 'at the beginning of this file'; 7 | context.report(reference.identifier, msg); 8 | } 9 | 10 | return { 11 | 'Program:exit': function() { 12 | const globalScope = context.getScope(); 13 | const variable = globalScope.set.get('Buffer'); 14 | if (variable) { 15 | variable.references.forEach(flagIt); 16 | } 17 | } 18 | }; 19 | }; 20 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/cmd/stackcollapse: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * cmd/stackcollapse: emit collapsed stack traces from DTrace output 5 | */ 6 | 7 | var mod_bunyan = require('bunyan'); 8 | var mod_stackvis = require('../lib/stackvis'); 9 | 10 | var log = new mod_bunyan({ 11 | 'name': 'stackcollapse', 12 | 'stream': process.stderr 13 | }); 14 | 15 | var reader = mod_stackvis.readerLookup('dtrace'); 16 | var writer = mod_stackvis.writerLookup('collapsed'); 17 | 18 | mod_stackvis.pipeStacks(log, process.stdin, reader, writer, process.stdout, 19 | function () {}); 20 | process.stdin.resume(); 21 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/cmd/stackcollapse-perf: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * cmd/stackcollapse: emit collapsed stack traces from perf output 5 | */ 6 | 7 | var mod_bunyan = require('bunyan'); 8 | var mod_stackvis = require('../lib/stackvis'); 9 | 10 | var log = new mod_bunyan({ 11 | 'name': 'stackcollapse', 12 | 'stream': process.stderr 13 | }); 14 | 15 | var reader = mod_stackvis.readerLookup('perf'); 16 | var writer = mod_stackvis.writerLookup('collapsed'); 17 | 18 | mod_stackvis.pipeStacks(log, process.stdin, reader, writer, process.stdout, 19 | function () {}); 20 | process.stdin.resume(); 21 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/cmd/stackcollapse-stap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * cmd/stackcollapse: emit collapsed stack traces from stap output 5 | */ 6 | 7 | var mod_bunyan = require('bunyan'); 8 | var mod_stackvis = require('../lib/stackvis'); 9 | 10 | var log = new mod_bunyan({ 11 | 'name': 'stackcollapse', 12 | 'stream': process.stderr 13 | }); 14 | 15 | var reader = mod_stackvis.readerLookup('stap'); 16 | var writer = mod_stackvis.writerLookup('collapsed'); 17 | 18 | mod_stackvis.pipeStacks(log, process.stdin, reader, writer, process.stdout, 19 | function () {}); 20 | process.stdin.resume(); 21 | -------------------------------------------------------------------------------- /packages/node-inspect/examples/backtrace.js: -------------------------------------------------------------------------------- 1 | const { exports: moduleScoped } = module; 2 | 3 | function topFn(a, b = false) { 4 | const l1 = a; 5 | let t = typeof l1; 6 | var v = t.length; 7 | debugger; 8 | return b || t || v || moduleScoped; 9 | } 10 | 11 | class Ctor { 12 | constructor(options) { 13 | this.options = options; 14 | } 15 | 16 | m() { 17 | const mLocal = this.options; 18 | topFn(this); 19 | return mLocal; 20 | } 21 | } 22 | 23 | (function () { 24 | const theOptions = { x: 42 }; 25 | const arr = [theOptions]; 26 | arr.forEach(options => { 27 | const obj = new Ctor(options); 28 | return obj.m(); 29 | }); 30 | }()); 31 | -------------------------------------------------------------------------------- /packages/linux-perf/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 | -------------------------------------------------------------------------------- /packages/linux-perf/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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diat", 3 | "private": true, 4 | "scripts": { 5 | "bootstrap": "lerna bootstrap && lerna link", 6 | "build": "cd packages/live-inspector && npm run build && cd ../diat && npm run build", 7 | "test": "cd packages/diat && npm test", 8 | "postinstall": "npm run bootstrap && npm run build" 9 | }, 10 | "devDependencies": { 11 | "husky": "^4.2.5", 12 | "lerna": "^3.20.2", 13 | "lint-staged": "^10.1.3", 14 | "prettier": "^2.0.4" 15 | }, 16 | "lint-staged": { 17 | "*.ts": [ 18 | "prettier --write" 19 | ] 20 | }, 21 | "husky": { 22 | "hooks": { 23 | "pre-commit": "lint-staged" 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diat-stackvis-simplified", 3 | "version": "1.4.0", 4 | "description": "stack visualization tools", 5 | "main": "./lib/stackvis.js", 6 | "bin": { 7 | "flamegraph": "./cmd/flamegraph", 8 | "stackcollapse": "./cmd/stackcollapse", 9 | "stackcollapse-perf": "./cmd/stackcollapse-perf", 10 | "stackcollapse-stap": "./cmd/stackcollapse-stap", 11 | "stackvis": "./cmd/stackvis" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git://github.com/davepacheco/node-stackvis.git" 16 | }, 17 | "dependencies": { 18 | "bunyan": "1.8.1", 19 | "carrier": "0.1.7", 20 | "hogan.js": "2.0.0", 21 | "jsprim": "0.5.1", 22 | "vasync": "1.4.0", 23 | "verror": "1.3.6" 24 | }, 25 | "license": "MIT" 26 | } 27 | -------------------------------------------------------------------------------- /packages/linux-perf/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 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/use-strict.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('for whiles that starts with strict directive', (t) => { 9 | const script = Path.join('examples', 'use-strict.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => { 20 | const brk = cli.breakInfo; 21 | t.match( 22 | `${brk.line}`, 23 | /^(1|2)$/, 24 | 'pauses either on strict directive or first "real" line'); 25 | }) 26 | .then(() => cli.quit()) 27 | .then(null, onFatal); 28 | }); 29 | -------------------------------------------------------------------------------- /packages/diat/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "lib", 5 | "module": "CommonJS", 6 | "target": "es5", 7 | "lib": ["es6", "dom"], 8 | "sourceMap": true, 9 | "allowJs": false, 10 | "jsx": "preserve", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": false, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "types": ["jest", "node"], 20 | "noUnusedLocals": false 21 | }, 22 | "exclude": [ 23 | "node_modules", 24 | "public", 25 | "build", 26 | "scripts", 27 | "acceptance-tests", 28 | "webpack", 29 | "jest", 30 | "src/setupTests.ts", 31 | "__tests__/" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /packages/live-inspector/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "outDir": "lib", 5 | "module": "CommonJS", 6 | "target": "es5", 7 | "lib": ["es6", "dom"], 8 | "sourceMap": true, 9 | "allowJs": false, 10 | "jsx": "preserve", 11 | "moduleResolution": "node", 12 | "rootDir": "src", 13 | "forceConsistentCasingInFileNames": true, 14 | "noImplicitReturns": true, 15 | "noImplicitThis": true, 16 | "noImplicitAny": false, 17 | "strictNullChecks": true, 18 | "suppressImplicitAnyIndexErrors": true, 19 | "types": ["jest", "node"], 20 | "noUnusedLocals": false 21 | }, 22 | "exclude": [ 23 | "node_modules", 24 | "public", 25 | "build", 26 | "scripts", 27 | "acceptance-tests", 28 | "webpack", 29 | "jest", 30 | "src/setupTests.ts", 31 | "__tests__/" 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/backtrace.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('display and navigate backtrace', (t) => { 9 | const script = Path.join('examples', 'backtrace.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => cli.stepCommand('c')) 20 | .then(() => cli.command('bt')) 21 | .then(() => { 22 | t.match(cli.output, `#0 topFn ${script}:7:2`); 23 | }) 24 | .then(() => cli.command('backtrace')) 25 | .then(() => { 26 | t.match(cli.output, `#0 topFn ${script}:7:2`); 27 | }) 28 | .then(() => cli.quit()) 29 | .then(null, onFatal); 30 | }); 31 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/buffer-constructor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Require use of new Buffer constructor methods in lib 3 | * @author James M Snell 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | const msg = 'Use of the Buffer() constructor has been deprecated. ' + 11 | 'Please use either Buffer.alloc(), Buffer.allocUnsafe(), ' + 12 | 'or Buffer.from()'; 13 | 14 | function test(context, node) { 15 | if (node.callee.name === 'Buffer') { 16 | context.report(node, msg); 17 | } 18 | } 19 | 20 | module.exports = function(context) { 21 | return { 22 | 'NewExpression': (node) => test(context, node), 23 | 'CallExpression': (node) => test(context, node) 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/linux-perf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diat-linux-perf", 3 | "version": "1.3.0", 4 | "description": "", 5 | "main": "linux-perf.js", 6 | "scripts": { 7 | "test": "./test.sh", 8 | "install": "node install.js" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/mmarchini/node-linux-perf.git" 13 | }, 14 | "keywords": [ 15 | "linux", 16 | "perf" 17 | ], 18 | "author": "Matheus Marchini ", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/mmarchini/node-linux-perf/issues" 22 | }, 23 | "homepage": "https://github.com/mmarchini/node-linux-perf#readme", 24 | "dependencies": { 25 | "node-addon-api": "^1.7.1", 26 | "node-gyp": "^4.0.0", 27 | "semver": "^6.3.0" 28 | }, 29 | "devDependencies": { 30 | "clang-format": "^1.2.3", 31 | "tap": "^12.6.5" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/live-inspector/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diat-live-inspector", 3 | "version": "1.3.0", 4 | "description": "开启一个Node.js进程的inspect端口,并与其通信", 5 | "main": "./lib/index.js", 6 | "license": "MIT", 7 | "files": [ 8 | "lib", 9 | "README.md", 10 | "CHANGELOG.md", 11 | "package.json" 12 | ], 13 | "scripts": { 14 | "build": "tsc", 15 | "prepublish": "npm run build", 16 | "test": "jest -c jest.config.json --coverage --passWithNoTests --runInBand" 17 | }, 18 | "devDependencies": { 19 | "@types/jest": "^23.3.7", 20 | "@types/node": "^13.9.0", 21 | "jest": "^24.1.0", 22 | "ts-jest": "^23.10.4", 23 | "tslint": "^5.11.0", 24 | "tslint-config-prettier": "^1.10.0", 25 | "typescript": "^3.6.4" 26 | }, 27 | "dependencies": { 28 | "debug": "^4.1.1", 29 | "node-fetch": "^2.6.0", 30 | "tslib": "^1.10.0", 31 | "ws": "^7.1.2" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/diat/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 1.4.0 4 | feat: add "perf" command to wrap relative commands 5 | refactor: set "includeCommandLineAPI" true to allow "require" in repl 6 | refactor: accept a number as a "scriptId" for "source()" 7 | refactor: rename "scriptSource" "source" 8 | chore: remove redundant console.log 9 | 10 | ## 1.2.1 11 | fix: do not install diat-linux-perf on windows 12 | fix: inspect -a failed for a non 9229 port 13 | 14 | ## 1.2.0 15 | feat: support "attachConsole" in diat-node-inspect 16 | feat: support "setLogpoint" in diat-node-inspect 17 | feat: support "getScripts" in diat-node-inspect 18 | feat: support "scriptSource" in diat-node-inspect 19 | 20 | ## 1.1.6 21 | 22 | ## 1.1.1 23 | docs: update README.md for the page of npm package 24 | 25 | ## 1.1.0 26 | feat: add `--view` option to view the profile with devtools 27 | 28 | ## 1.0.1 29 | fix: fix ts module type 30 | 31 | ## 1.0.0 32 | -------------------------------------------------------------------------------- /packages/node-inspect/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diat-node-inspect", 3 | "version": "1.4.0", 4 | "description": "Node Inspect", 5 | "license": "MIT", 6 | "main": "lib/_inspect.js", 7 | "bin": "cli.js", 8 | "homepage": "https://github.com/nodejs/node-inspect", 9 | "repository": { 10 | "type": "git", 11 | "url": "git+ssh://git@github.com/nodejs/node-inspect" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/nodejs/node-inspect/issues" 15 | }, 16 | "scripts": { 17 | "pretest": "eslint --rulesdir=tools/eslint-rules lib test", 18 | "test": "tap test" 19 | }, 20 | "devDependencies": { 21 | "eslint": "^3.10.2", 22 | "tap": "^10.7.0" 23 | }, 24 | "author": { 25 | "name": "Jan Krems", 26 | "email": "jan.krems@gmail.com" 27 | }, 28 | "files": [ 29 | "*.js", 30 | "lib" 31 | ], 32 | "publishConfig": { 33 | "registry": "https://registry.npmjs.org" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/profile.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | function delay(ms) { 7 | return new Promise((resolve) => setTimeout(resolve, ms)); 8 | } 9 | 10 | test('profiles', (t) => { 11 | const cli = startCLI(['examples/empty.js']); 12 | 13 | function onFatal(error) { 14 | cli.quit(); 15 | throw error; 16 | } 17 | 18 | return cli.waitForInitialBreak() 19 | .then(() => cli.waitForPrompt()) 20 | .then(() => cli.command('exec console.profile()')) 21 | .then(() => { 22 | t.match(cli.output, 'undefined'); 23 | }) 24 | .then(() => cli.command('exec console.profileEnd()')) 25 | .then(() => delay(250)) 26 | .then(() => { 27 | t.match(cli.output, 'undefined'); 28 | t.match(cli.output, 'Captured new CPU profile.'); 29 | }) 30 | .then(() => cli.quit()) 31 | .then(null, onFatal); 32 | }); 33 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/output-collapsed.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/output-collapsed.js: emits StackSets in collapsed format, compatible with 3 | * Brendan Gregg's FlameGraph tool. 4 | */ 5 | 6 | var mod_assert = require('assert'); 7 | 8 | /* 9 | * Arguments: 10 | * 11 | * stacks StackSet Stacks to visualize 12 | * 13 | * output WritableStream Output file 14 | */ 15 | exports.emit = function emitCollapsed(args, callback) 16 | { 17 | mod_assert.ok(args.stacks && args.stacks.constructor && 18 | args.stacks.constructor.name == 'StackSet', 19 | 'required "stacks" argument must be a StackSet'); 20 | mod_assert.ok(args.output && args.output.write && 21 | typeof (args.output.write) == 'function', 22 | 'required "output" argument must be a function'); 23 | 24 | args.stacks.eachStackByCount(function (frames, count) { 25 | args.output.write(frames.join(',') + ' ' + count + '\n'); 26 | }); 27 | callback(); 28 | }; 29 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/heap-profiler.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | const { readFileSync, unlinkSync } = require('fs'); 4 | 5 | const startCLI = require('./start-cli'); 6 | const filename = 'node.heapsnapshot'; 7 | 8 | function cleanup() { 9 | try { 10 | unlinkSync(filename); 11 | } catch (_) { 12 | // Ignore. 13 | } 14 | } 15 | 16 | cleanup(); 17 | 18 | test('Heap profiler take snapshot', (t) => { 19 | const cli = startCLI(['examples/empty.js']); 20 | 21 | function onFatal(error) { 22 | cli.quit(); 23 | throw error; 24 | } 25 | 26 | // Check that the snapshot is valid JSON. 27 | return cli.waitForInitialBreak() 28 | .then(() => cli.waitForPrompt()) 29 | .then(() => cli.command('takeHeapSnapshot()')) 30 | .then(() => JSON.parse(readFileSync(filename, 'utf8'))) 31 | .then(() => cleanup()) 32 | .then(() => cli.quit()) 33 | .then(null, onFatal); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/linux-perf/linux-perf.h: -------------------------------------------------------------------------------- 1 | #ifndef __LINUX_PERF_H 2 | #define __LINUX_PERF_H 3 | 4 | #include "v8-profiler.h" 5 | #include 6 | #include 7 | 8 | 9 | namespace node { 10 | 11 | class LinuxPerfHandler : public v8::CodeEventHandler { 12 | public: 13 | explicit LinuxPerfHandler(v8::Isolate* isolate); 14 | ~LinuxPerfHandler() override; 15 | 16 | 17 | void Handle(v8::CodeEvent* code_event) override; 18 | private: 19 | std::ofstream mapFile; 20 | std::string FormatName(v8::CodeEvent* code_event); 21 | }; 22 | 23 | class LinuxPerf : public Napi::ObjectWrap { 24 | public: 25 | explicit LinuxPerf(const Napi::CallbackInfo& info); 26 | 27 | static void Initialize(Napi::Env env, Napi::Object exports); 28 | 29 | Napi::Value Start(const Napi::CallbackInfo& info); 30 | Napi::Value Stop(const Napi::CallbackInfo& info); 31 | 32 | LinuxPerfHandler* handler; 33 | }; 34 | 35 | }; 36 | 37 | #endif // __LINUX_PERF_H 38 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/new-with-error.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Require `throw new Error()` rather than `throw Error()` 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | module.exports = function(context) { 12 | 13 | var errorList = context.options.length !== 0 ? context.options : ['Error']; 14 | 15 | return { 16 | 'ThrowStatement': function(node) { 17 | if (node.argument.type === 'CallExpression' && 18 | errorList.indexOf(node.argument.callee.name) !== -1) { 19 | context.report(node, 'Use new keyword when throwing.'); 20 | } 21 | } 22 | }; 23 | }; 24 | 25 | module.exports.schema = { 26 | 'type': 'array', 27 | 'additionalItems': { 28 | 'type': 'string' 29 | }, 30 | 'uniqueItems': true 31 | }; 32 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/input-collapsed.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/input-collapsed.js: reads output from the "stackcollapse" script 3 | */ 4 | 5 | var mod_util = require('util'); 6 | var mod_events = require('events'); 7 | 8 | var mod_carrier = require('carrier'); 9 | 10 | exports.reader = CollapsedStreamReader; 11 | 12 | function CollapsedStreamReader(input, log) 13 | { 14 | var reader = this; 15 | 16 | this.csr_log = log; 17 | this.csr_linenum = 0; 18 | this.csr_carrier = mod_carrier.carry(input); 19 | this.csr_carrier.on('line', function (line) { 20 | reader.csr_linenum++; 21 | var match = /^(.*)\s+(\d+)$/.exec(line); 22 | if (!match) { 23 | log.warn('line ' + reader.csr_linenum + ': garbled'); 24 | return; 25 | } 26 | 27 | reader.emit('stack', match[1].split(','), 28 | parseInt(match[2], 10)); 29 | }); 30 | this.csr_carrier.on('end', function () { reader.emit('end'); }); 31 | } 32 | 33 | mod_util.inherits(CollapsedStreamReader, mod_events.EventEmitter); 34 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/assert-fail-single-argument.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prohibit use of a single argument only in `assert.fail()`. It 3 | * is almost always an error. 4 | * @author Rich Trott 5 | */ 6 | 'use strict'; 7 | 8 | //------------------------------------------------------------------------------ 9 | // Rule Definition 10 | //------------------------------------------------------------------------------ 11 | 12 | const msg = 'assert.fail() message should be third argument'; 13 | 14 | function isAssert(node) { 15 | return node.callee.object && node.callee.object.name === 'assert'; 16 | } 17 | 18 | function isFail(node) { 19 | return node.callee.property && node.callee.property.name === 'fail'; 20 | } 21 | 22 | module.exports = function(context) { 23 | return { 24 | 'CallExpression': function(node) { 25 | if (isAssert(node) && isFail(node) && node.arguments.length === 1) { 26 | context.report(node, msg); 27 | } 28 | } 29 | }; 30 | }; 31 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: npm test 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master, dev ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | # need to investigate why `npm test` always halts on 12.x 20 | # node-version: [10.x, 12.x] 21 | node-version: [10.x] 22 | 23 | steps: 24 | - uses: actions/checkout@v2 25 | - name: Use Node.js ${{ matrix.node-version }} 26 | uses: actions/setup-node@v1 27 | with: 28 | node-version: ${{ matrix.node-version }} 29 | - run: npm ci 30 | - run: npm run build --if-present 31 | - run: npm test 32 | env: 33 | CI: true 34 | -------------------------------------------------------------------------------- /packages/diat/__tests__/NodeInspectWrapper.spec.ts: -------------------------------------------------------------------------------- 1 | import { NodeInspectWrapper } from '../src/NodeInspectWrapper' 2 | import { createTestProcess, kTimeout } from './utils'; 3 | 4 | describe('NodeInspectWrapper', () => { 5 | // TODO find out somehow to test this 6 | it('should work', async () => { 7 | const { child, message } = await createTestProcess('inspector_open') 8 | const { host, port } = message 9 | 10 | const wrapper = new NodeInspectWrapper({ 11 | host, 12 | port 13 | }) 14 | 15 | // const bufs: any[] = [] 16 | // process.stdout.on('data', data => { 17 | // bufs.push(data); 18 | // }) 19 | 20 | wrapper.startInspect() 21 | // process.stdin.push('help') 22 | // process.stdin.push(null) 23 | 24 | await new Promise((resolve) => { 25 | setTimeout(() => { 26 | resolve() 27 | }, 100) 28 | }) 29 | 30 | // console.log('content', Buffer.concat(bufs).toString('utf8')) 31 | 32 | child.kill() 33 | }, kTimeout); 34 | }) 35 | -------------------------------------------------------------------------------- /packages/live-inspector/README.md: -------------------------------------------------------------------------------- 1 | # live-inspector 2 | 3 | 开启一个Node.js进程的inspect端口,并与其通信。 4 | 5 | ## 原理 6 | 7 | SIGUSR1 + inspector protocol 8 | 9 | ## 限制 10 | 11 | - 因为`SIGUSR1`没法用在windows上,所以这个lib不支持windows。 12 | - 因为只能监听9229端口,所以如果9229端口已经被占用(比如被其他Node.js的inspect端口占用),则无法正确进行操作。 13 | - 如果进程已经开启了inspector端口并进行了一些操作,同时使用这个lib可能会产生未知后果。 14 | 15 | ## 使用 16 | 17 | ```js 18 | const { Communication } = require('diat-live-inspector') 19 | 20 | (async () => { 21 | const comm = new Communication({ 22 | pid: PID 23 | }) 24 | 25 | // 开始通信 26 | await comm.connect() 27 | 28 | // 执行一个表达式 29 | const ret = await comm.execCode('process.version') 30 | 31 | console.log(ret) // 输出: { result: { type: 'string', value: 'v10.16.0' } } 32 | 33 | // 关闭通信 34 | await comm.disconnect() 35 | })(); 36 | ``` 37 | 38 | ## API 39 | 40 | TODO 41 | 42 | ## 事件 43 | 44 | 通过`Communication.event`是`Events`实例,可以通过其监听到inspector协议中的事件。除了inspector协议的事件外,还包括`Communication`自定义的事件,有: 45 | 46 | - `LiveInspector.close`: ws关闭。参数表示关闭的原因,包含: code, reason 47 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/low-level.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('Debugger agent direct access', (t) => { 7 | const cli = startCLI(['examples/three-lines.js']); 8 | const scriptPattern = /^\* (\d+): examples(?:\/|\\)three-lines.js/; 9 | 10 | function onFatal(error) { 11 | cli.quit(); 12 | throw error; 13 | } 14 | 15 | return cli.waitForInitialBreak() 16 | .then(() => cli.waitForPrompt()) 17 | .then(() => cli.command('scripts')) 18 | .then(() => { 19 | const [, scriptId] = cli.output.match(scriptPattern); 20 | return cli.command( 21 | `Debugger.getScriptSource({ scriptId: '${scriptId}' })` 22 | ); 23 | }) 24 | .then(() => { 25 | t.match( 26 | cli.output, 27 | /scriptSource:[ \n]*'(?:\(function \(|let x = 1)/); 28 | t.match( 29 | cli.output, 30 | /let x = 1;/); 31 | }) 32 | .then(() => cli.quit()) 33 | .then(null, onFatal); 34 | }); 35 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/console.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | const startCLI = require('./start-cli'); 4 | 5 | test('attachConsole', (t) => { 6 | const cli = startCLI(['examples/alive.js']); 7 | 8 | function onFatal(error) { 9 | cli.quit(); 10 | throw error; 11 | } 12 | 13 | return cli.waitForInitialBreak() 14 | .then(() => cli.waitForPrompt()) 15 | .then(() => cli.command('c')) 16 | .then(() => 17 | cli.command('exec setInterval(() => { console.log("hello") }, 100)')) 18 | .then(() => cli.writeLine('attachConsole')) 19 | .then(() => cli.waitFor(/leave console repl/)) 20 | .then(() => { 21 | t.match( 22 | cli.output, 23 | 'Press Ctrl + C to leave console repl', 24 | 'shows hint for how to leave repl'); 25 | t.notMatch(cli.output, 'debug>', 'changes the repl style'); 26 | }) 27 | .then(() => cli.waitFor(/hello/)) 28 | .then(() => cli.waitFor(/hello/)) 29 | .then(() => cli.quit()) 30 | .then(null, onFatal); 31 | }); 32 | -------------------------------------------------------------------------------- /packages/node-inspect/README.md: -------------------------------------------------------------------------------- 1 | # `node-inspect` 2 | 3 | ```bash 4 | npm install --global node-inspect 5 | ``` 6 | 7 | For the old V8 debugger protocol, 8 | node has two options: 9 | 10 | 1. `node --debug `: Start `file` with remote debugging enabled. 11 | 2. `node debug `: Start an interactive CLI debugger for ``. 12 | 13 | But for the Chrome inspector protocol, 14 | there's only one: `node --inspect `. 15 | 16 | This project tries to provide the missing second option 17 | by re-implementing `node debug` against the new protocol. 18 | 19 | ``` 20 | Usage: node-inspect script.js 21 | node-inspect : 22 | ``` 23 | 24 | #### References 25 | 26 | * [Debugger Documentation](https://nodejs.org/api/debugger.html) 27 | * [EPS: `node inspect` CLI debugger](https://github.com/nodejs/node-eps/pull/42) 28 | * [Debugger Protocol Viewer](https://chromedevtools.github.io/debugger-protocol-viewer/) 29 | * [Command Line API](https://developers.google.com/web/tools/chrome-devtools/debug/command-line/command-line-reference?hl=en) 30 | -------------------------------------------------------------------------------- /packages/diat/src/NodeInspectWrapper.ts: -------------------------------------------------------------------------------- 1 | import * as nodeInspect from 'diat-node-inspect/lib/_inspect.js' 2 | 3 | interface IOptions { 4 | host: string 5 | port: number 6 | } 7 | 8 | function getDefaultOptions(): IOptions { 9 | return { 10 | host: '127.0.0.1', 11 | port: 9229, 12 | } 13 | } 14 | 15 | export class NodeInspectWrapper { 16 | private options: IOptions 17 | private instance: any = null 18 | /** 19 | * We always pass then host and port to node-inspect: 20 | * node-inspect ip:port 21 | */ 22 | constructor(options: IOptions = getDefaultOptions()) { 23 | this.options = options 24 | } 25 | 26 | startInspect = () => { 27 | return new Promise((resolve) => { 28 | const { host, port } = this.options 29 | this.instance = nodeInspect.start( 30 | [`${host}:${port}`], 31 | process.stdin, 32 | process.stdout, 33 | false 34 | ) 35 | this.instance.on('replExit', () => { 36 | resolve() 37 | }) 38 | }) 39 | } 40 | 41 | destroy = () => { 42 | // 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (c) 2012, Joyent, Inc. All rights reserved. 3 | # 4 | # Makefile: top-level Makefile 5 | # 6 | # This Makefile contains only repo-specific logic and uses included makefiles 7 | # to supply common targets (javascriptlint, jsstyle, restdown, etc.), which are 8 | # used by other repos as well. 9 | # 10 | 11 | # 12 | # Tools 13 | # 14 | NPM = npm 15 | CATEST = tools/catest 16 | JSL = jsl 17 | JSSTYLE = jsstyle 18 | 19 | # 20 | # Files 21 | # 22 | JS_FILES := $(shell find cmd lib -name '*.js' -not -path 'lib/www/*') 23 | JS_FILES += cmd/flamegraph cmd/stackvis cmd/stackcollapse 24 | 25 | JSL_CONF_NODE = tools/jsl.node.conf 26 | JSL_CONF_WEB = tools/jsl.web.conf 27 | JSL_FILES_NODE = $(JS_FILES) 28 | JSL_FILES_WEB := $(shell find share -name '*.js' -not -name 'd3.*.js') 29 | 30 | JSSTYLE_FLAGS = -oleading-right-paren-ok=1 31 | JSSTYLE_FILES = $(JSL_FILES_NODE) $(JSL_FILES_WEB) 32 | 33 | # 34 | # Repo-specific targets 35 | # 36 | .PHONY: all 37 | all: 38 | $(NPM) install 39 | 40 | .PHONY: test 41 | test: 42 | @echo no tests defined 43 | 44 | include ./Makefile.targ 45 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Bytedance 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 | -------------------------------------------------------------------------------- /packages/linux-perf/.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 | -------------------------------------------------------------------------------- /packages/live-inspector/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import * as childProcess from 'child_process'; 2 | import * as path from 'path'; 3 | 4 | const kIdleFilePath = path.resolve(__dirname, './testIdleServer.js'); 5 | const kInspectFilePath = path.resolve(__dirname, './testInspectServer.js'); 6 | 7 | export function createTestProcess() { 8 | return new Promise(resolve => { 9 | const child = childProcess.fork(kIdleFilePath); 10 | 11 | child.once('message', () => { 12 | resolve(child); 13 | }); 14 | }); 15 | } 16 | 17 | export function createInspectProcess() { 18 | return new Promise<{ child: childProcess.ChildProcess; addr: string }>( 19 | (resolve, reject) => { 20 | const child = childProcess.fork(kInspectFilePath); 21 | // 获取inspecto端口 22 | child.once('message', data => { 23 | const ret = /ws\:\/\/(.+\:\d+)\//.exec(String(data)); 24 | 25 | if (!ret) { 26 | reject(new Error('failed to spwan the process')); 27 | return; 28 | } 29 | 30 | resolve({ 31 | child, 32 | addr: ret[1] 33 | }); 34 | }); 35 | } 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /packages/node-inspect/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Node.js contributors. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to 5 | deal in the Software without restriction, including without limitation the 6 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | sell copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /packages/linux-perf/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Matheus 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 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/prefer-assert-methods.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function isAssert(node) { 4 | return node.expression && 5 | node.expression.type === 'CallExpression' && 6 | node.expression.callee && 7 | node.expression.callee.name === 'assert'; 8 | } 9 | 10 | function getFirstArg(expression) { 11 | return expression.arguments && expression.arguments[0]; 12 | } 13 | 14 | function parseError(method, op) { 15 | return `'assert.${method}' should be used instead of '${op}'`; 16 | } 17 | 18 | const preferedAssertMethod = { 19 | '===': 'strictEqual', 20 | '!==': 'notStrictEqual', 21 | '==': 'equal', 22 | '!=': 'notEqual' 23 | }; 24 | 25 | module.exports = function(context) { 26 | return { 27 | ExpressionStatement(node) { 28 | if (isAssert(node)) { 29 | const arg = getFirstArg(node.expression); 30 | if (arg && arg.type === 'BinaryExpression') { 31 | const assertMethod = preferedAssertMethod[arg.operator]; 32 | if (assertMethod) { 33 | context.report(node, parseError(assertMethod, arg.operator)); 34 | } 35 | } 36 | } 37 | } 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Joyent, Inc. All rights reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/cmd/flamegraph: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * cmd/flamegraph: emit a flame graph SVG from collapsed output 5 | */ 6 | 7 | var mod_bunyan = require('bunyan'); 8 | var mod_getopt = require('posix-getopt'); 9 | var mod_stackvis = require('../lib/stackvis'); 10 | 11 | var log = new mod_bunyan({ 12 | 'name': 'flamegraph', 13 | 'stream': process.stderr 14 | }); 15 | 16 | var reader = new mod_stackvis.readerLookup('collapsed'); 17 | var writer = new mod_stackvis.writerLookup('flamegraph-svg'); 18 | 19 | var parser = new mod_getopt.BasicParser('x:', process.argv); 20 | 21 | var args = {}; 22 | var option; 23 | while ((option = parser.getopt()) !== undefined) { 24 | switch (option.option) { 25 | case 'x': 26 | var arg = option.optarg; 27 | var eq = arg.indexOf('='); 28 | if (eq < 0) { 29 | console.error('warn: ignoring "%s" (no value)', arg); 30 | break; 31 | } 32 | args[arg.substr(0, eq)] = arg.substr(eq + 1); 33 | break; 34 | default: 35 | process.exit(1); 36 | break; 37 | } 38 | } 39 | 40 | mod_stackvis.pipeStacks(log, process.stdin, reader, writer, process.stdout, 41 | args, function () {}); 42 | process.stdin.resume(); 43 | -------------------------------------------------------------------------------- /packages/linux-perf/.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 | -------------------------------------------------------------------------------- /packages/node-inspect/lib/cli.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright Node.js contributors. All rights reserved. 3 | * 4 | * Permission is hereby granted, free of charge, to any person obtaining a copy 5 | * of this software and associated documentation files (the "Software"), to 6 | * deal in the Software without restriction, including without limitation the 7 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 8 | * sell copies of the Software, and to permit persons to whom the Software is 9 | * furnished to do so, subject to the following conditions: 10 | * 11 | * The above copyright notice and this permission notice shall be included in 12 | * all copies or substantial portions of the Software. 13 | * 14 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 20 | * IN THE SOFTWARE. 21 | */ 22 | 'use strict'; 23 | // ~= NativeModule.require('_debugger').start(); 24 | require('./_inspect').start(); 25 | -------------------------------------------------------------------------------- /packages/diat/__tests__/utils.ts: -------------------------------------------------------------------------------- 1 | import * as childProcess from 'child_process' 2 | import * as path from 'path' 3 | export { isNodeVersionLE8 } from '../src/utils' 4 | 5 | const kFilePath = path.resolve(__dirname, './test_process/testIdleServer.js') 6 | 7 | export const kTimeout = 30 * 1000 8 | 9 | export function createTestProcess(requireFile?: string, execArgv?: string[]) { 10 | return new Promise<{ child: childProcess.ChildProcess; message: any }>( 11 | (resolve) => { 12 | const args = requireFile 13 | ? [path.resolve(__dirname, `./test_process/${requireFile}.js`)] 14 | : [] 15 | const options: any = { 16 | env: {}, 17 | } 18 | if (Array.isArray(execArgv)) { 19 | options.execArgv = execArgv 20 | } 21 | const child = childProcess.fork(kFilePath, args, options) 22 | 23 | child.once('message', (message) => { 24 | resolve({ 25 | child, 26 | message, 27 | }) 28 | }) 29 | } 30 | ) 31 | } 32 | 33 | export function hasWorker(): boolean { 34 | try { 35 | return Boolean(require('worker_threads')) 36 | } catch (err) { 37 | // 38 | } 39 | return false 40 | } 41 | 42 | export function wait(t: number) { 43 | return new Promise((resolve) => { 44 | setTimeout(() => { 45 | resolve() 46 | }, t) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /packages/linux-perf/install.js: -------------------------------------------------------------------------------- 1 | const semver = require('semver') 2 | const childProcess = require('child_process') 3 | const os = require('os') 4 | const packageJSON = require('./package.json') 5 | 6 | const kGypRebuildCmd = 'node-gyp rebuild' 7 | 8 | function isEAccesMsg(msg) { 9 | return /EACCES/.test(msg) 10 | } 11 | 12 | async function main() { 13 | if (os.platform() === 'win32') { 14 | console.warn(`win32 doesn't support ${packageJSON.name}`) 15 | } else if (semver.satisfies(process.version, '>=10.4.0')) { 16 | const child = childProcess.exec(kGypRebuildCmd) 17 | let stderrStrs = '' 18 | child.stdout.on('data', (data) => { 19 | process.stdout.write(data) 20 | }) 21 | child.stderr.on('data', (data) => { 22 | stderrStrs += data 23 | }) 24 | child.on('exit', (code) => { 25 | if (code === 0) { 26 | return 27 | } 28 | 29 | const msg = stderrStrs 30 | if (isEAccesMsg(msg)) { 31 | console.warn(`failed to install ${packageJSON.name} because of EACCES, you can add "npm_config_user=$USER" and reinstall`) 32 | return 33 | } 34 | 35 | process.stderr.write(msg) 36 | process.exit(1) 37 | }) 38 | } else { 39 | console.warn(`current version of Node.js is ${process.version} which doesn't support ${packageJSON.name}`) 40 | } 41 | } 42 | 43 | main() 44 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/demo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * demo.js: static-file node HTTP server for demos 3 | * 4 | * Usage: node demo.js [port] 5 | * 6 | * Sets up a web server on the given port (or port 80) serving static files 7 | * out of the given path. This demo is NOT secure and allows anyone with 8 | * network access to this server to read any files on your system. 9 | */ 10 | 11 | var mod_fs = require('fs'); 12 | var mod_http = require('http'); 13 | var mod_path = require('path'); 14 | var mod_url = require('url'); 15 | 16 | var dd_index = 'index.htm'; 17 | var dd_cwd = process.cwd(); 18 | var dd_port = 80; 19 | 20 | var i; 21 | 22 | for (i = 2; i < process.argv.length; i++) { 23 | dd_port = parseInt(process.argv[i], 10); 24 | if (isNaN(dd_port)) { 25 | console.error('usage: node demo.js [port]'); 26 | process.exit(1); 27 | } 28 | } 29 | 30 | mod_http.createServer(function (req, res) { 31 | var uri = mod_url.parse(req.url).pathname; 32 | var path; 33 | var filename; 34 | 35 | path = (uri == '/') ? dd_index : uri; 36 | 37 | filename = mod_path.join(dd_cwd, path); 38 | 39 | mod_fs.readFile(filename, function (err, file) { 40 | if (err) { 41 | res.writeHead(404); 42 | res.end(); 43 | return; 44 | } 45 | 46 | res.writeHead(200); 47 | res.end(file); 48 | }); 49 | }).listen(dd_port, function () { 50 | console.log('HTTP server started on port ' + dd_port); 51 | }); 52 | -------------------------------------------------------------------------------- /packages/diat/src/Snippets.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs' 2 | import * as util from 'util' 3 | import * as path from 'path' 4 | import { DiatError } from './Error' 5 | 6 | const readdir = util.promisify(fs.readdir) 7 | const readFile = util.promisify(fs.readFile) 8 | 9 | const kPath = path.resolve(__dirname, '../snippet') 10 | 11 | export class Snippets { 12 | private snippets: { [name: string]: string } | null = null 13 | 14 | constructor() { 15 | // 16 | } 17 | 18 | getSnippets = async () => { 19 | if (this.snippets) { 20 | return this.snippets 21 | } 22 | 23 | const files = await readdir(kPath) 24 | const snippets = {} 25 | 26 | for (let file of files) { 27 | const content = await readFile(path.resolve(kPath, file)) 28 | const name = path.basename(file, '.js') 29 | snippets[name] = content.toString('utf8') 30 | } 31 | 32 | this.snippets = snippets 33 | 34 | return this.snippets 35 | } 36 | 37 | getSnippet = async (name: string, options: any = {}) => { 38 | const snippets = await this.getSnippets() 39 | 40 | const snippet = snippets[name] 41 | 42 | if (!snippet) { 43 | throw new DiatError(`snippet ${name} doesn't exist`) 44 | } 45 | 46 | const kOptionsReplaceReg = /__OPTIONS__/g 47 | return snippet.replace(kOptionsReplaceReg, JSON.stringify(options)) 48 | } 49 | } 50 | 51 | export const snippets = new Snippets() 52 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/cmd/stackvis: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | /* 4 | * cmd/stackvis: convert stacks between different representations 5 | */ 6 | var mod_bunyan = require('bunyan'); 7 | 8 | var mod_stackvis = require('../lib/stackvis'); 9 | 10 | var log = new mod_bunyan({ 11 | 'name': 'stackvis', 12 | 'stream': process.stderr 13 | }); 14 | 15 | function usage() 16 | { 17 | console.error('usage: stackvis input-format output-format'); 18 | console.error('or stackvis share [FILENAME]'); 19 | process.exit(2); 20 | } 21 | 22 | function main() 23 | { 24 | if (process.argv.length > 4) 25 | usage(); 26 | 27 | if (process.argv.length === 2 || 28 | process.argv[2] != 'share') 29 | cmdTranslate(); 30 | } 31 | 32 | function cmdTranslate() 33 | { 34 | var reader, writer; 35 | var rname = 'dtrace'; 36 | var wname = 'flamegraph-d3'; 37 | 38 | if (process.argv.length > 2) { 39 | if (process.argv[2][0] == '-') 40 | usage(); 41 | rname = process.argv[2]; 42 | } 43 | 44 | if (process.argv.length > 3) 45 | wname = process.argv[3]; 46 | 47 | try { 48 | reader = new mod_stackvis.readerLookup(rname); 49 | writer = new mod_stackvis.writerLookup(wname); 50 | } catch (ex) { 51 | console.error(ex.message); 52 | usage(); 53 | } 54 | 55 | mod_stackvis.pipeStacks(log, process.stdin, reader, writer, 56 | process.stdout, function () {}); 57 | process.stdin.resume(); 58 | } 59 | 60 | main(); 61 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/share/icicle.htm: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{! If you're reading this, you're looking at a template. The following }} 5 | {{! HTML comment applies to files generated from this template, not this }} 6 | {{! template itself. }} 7 | 11 | 12 | {{title}} 13 | 16 | 27 | 28 | 29 |

{{title}}

30 |
31 | Hover over a block for summary information. Click a block for details. 32 | Reset view 33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/no-let-in-for-declaration.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Prohibit the use of `let` as the loop variable 3 | * in the initialization of for, and the left-hand 4 | * iterator in forIn and forOf loops. 5 | * 6 | * @author Jessica Quynh Tran 7 | */ 8 | 9 | 'use strict'; 10 | 11 | //------------------------------------------------------------------------------ 12 | // Rule Definition 13 | //------------------------------------------------------------------------------ 14 | 15 | module.exports = { 16 | create(context) { 17 | 18 | const msg = 'Use of `let` as the loop variable in a for-loop is ' + 19 | 'not recommended. Please use `var` instead.'; 20 | 21 | /** 22 | * Report function to test if the for-loop is declared using `let`. 23 | */ 24 | function testForLoop(node) { 25 | if (node.init && node.init.kind === 'let') { 26 | context.report(node.init, msg); 27 | } 28 | } 29 | 30 | /** 31 | * Report function to test if the for-in or for-of loop 32 | * is declared using `let`. 33 | */ 34 | function testForInOfLoop(node) { 35 | if (node.left && node.left.kind === 'let') { 36 | context.report(node.left, msg); 37 | } 38 | } 39 | 40 | return { 41 | 'ForStatement': testForLoop, 42 | 'ForInStatement': testForInOfLoop, 43 | 'ForOfStatement': testForInOfLoop 44 | }; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/watchers.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('stepping through breakpoints', (t) => { 7 | const cli = startCLI(['examples/break.js']); 8 | 9 | function onFatal(error) { 10 | cli.quit(); 11 | throw error; 12 | } 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => cli.command('watch("x")')) 17 | .then(() => cli.command('watch("\\"Hello\\"")')) 18 | .then(() => cli.command('watch("42")')) 19 | .then(() => cli.command('watch("NaN")')) 20 | .then(() => cli.command('watch("true")')) 21 | .then(() => cli.command('watch("[1, 2]")')) 22 | .then(() => cli.command('watch("process.env")')) 23 | .then(() => cli.command('watchers')) 24 | .then(() => { 25 | t.match(cli.output, 'x is not defined'); 26 | }) 27 | .then(() => cli.command('unwatch("42")')) 28 | .then(() => cli.stepCommand('n')) 29 | .then(() => { 30 | t.match(cli.output, '0: x = 10'); 31 | t.match(cli.output, '1: "Hello" = \'Hello\''); 32 | t.match(cli.output, '2: NaN = NaN'); 33 | t.match(cli.output, '3: true = true'); 34 | t.match(cli.output, '4: [1, 2] = [ 1, 2 ]'); 35 | t.match( 36 | cli.output, 37 | /5: process\.env =\n\s+\{[\s\S]+,\n\s+\.\.\. \}/, 38 | 'shows "..." for process.env'); 39 | }) 40 | .then(() => cli.quit()) 41 | .then(null, onFatal); 42 | }); 43 | -------------------------------------------------------------------------------- /packages/live-inspector/__tests__/TcpDetector.spec.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net'; 2 | import { TcpDetector } from '../src/TcpDetector'; 3 | 4 | describe('TcpDetector', () => { 5 | it('should work', async () => { 6 | const server = new net.Server(() => {}); 7 | 8 | await new Promise(resolve => { 9 | server.listen(0, () => { 10 | resolve(); 11 | }); 12 | }); 13 | 14 | const port = (server.address() as any).port; 15 | 16 | const tcpDetector = new TcpDetector(port); 17 | 18 | const succ = await new Promise(resolve => { 19 | tcpDetector.once('_check', succ => { 20 | resolve(succ); 21 | }); 22 | }); 23 | 24 | expect(succ).toBe(true); 25 | 26 | server.close(); 27 | 28 | await new Promise(resolve => { 29 | tcpDetector.once('close', succ => { 30 | resolve(succ); 31 | }); 32 | }); 33 | }); 34 | 35 | describe('detectPort', () => { 36 | it('should work', async () => { 37 | const server = new net.Server(() => {}); 38 | 39 | await new Promise(resolve => { 40 | server.listen(0, () => { 41 | resolve(); 42 | }); 43 | }); 44 | 45 | const port = (server.address() as any).port; 46 | 47 | expect(await TcpDetector.detectPort(port)).toBe(true); 48 | 49 | await new Promise(resolve => { 50 | server.close(() => { 51 | resolve(); 52 | }); 53 | }); 54 | expect(await TcpDetector.detectPort(port)).toBe(false); 55 | }); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/linux-perf/linux-perf.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "linux-perf.h" 5 | 6 | namespace node { 7 | 8 | void LinuxPerf::Initialize(Napi::Env env, Napi::Object exports) { 9 | Napi::HandleScope scope(env); 10 | 11 | Napi::Function func = DefineClass(env, "LinuxPerf", { 12 | InstanceMethod("start", &LinuxPerf::Start), 13 | InstanceMethod("stop", &LinuxPerf::Stop) 14 | }); 15 | 16 | exports.Set("LinuxPerf", func); 17 | } 18 | 19 | LinuxPerf::LinuxPerf(const Napi::CallbackInfo& info) : Napi::ObjectWrap(info){ 20 | handler = nullptr; 21 | } 22 | 23 | Napi::Value LinuxPerf::Start(const Napi::CallbackInfo& info) { 24 | Napi::Env env = info.Env(); 25 | Napi::HandleScope scope(env); 26 | 27 | if (handler == nullptr) { 28 | handler = new LinuxPerfHandler(v8::Isolate::GetCurrent()); 29 | handler->Enable(); 30 | return Napi::Boolean::New(env, true); 31 | } 32 | return Napi::Boolean::New(env, false); 33 | } 34 | 35 | Napi::Value LinuxPerf::Stop(const Napi::CallbackInfo& info) { 36 | Napi::Env env = info.Env(); 37 | Napi::HandleScope scope(env); 38 | 39 | if (handler != nullptr) { 40 | handler->Disable(); 41 | delete handler; 42 | handler = nullptr; 43 | return Napi::Boolean::New(env, true); 44 | } 45 | return Napi::Boolean::New(env, false); 46 | } 47 | 48 | Napi::Object init(Napi::Env env, Napi::Object exports) { 49 | LinuxPerf::Initialize(env, exports); 50 | return exports; 51 | } 52 | 53 | NODE_API_MODULE(LiuxPerfBindings, init) 54 | 55 | }; 56 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/pid.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { spawn } = require('child_process'); 3 | const Path = require('path'); 4 | 5 | const { test } = require('tap'); 6 | 7 | const startCLI = require('./start-cli'); 8 | 9 | function launchTarget(...args) { 10 | const childProc = spawn(process.execPath, args); 11 | return Promise.resolve(childProc); 12 | } 13 | 14 | // process.debugPort is our proxy for "the version of node used to run this 15 | // test suite doesn't support SIGUSR1 for enabling --inspect for a process". 16 | const defaultsToOldProtocol = process.debugPort === 5858; 17 | 18 | test('examples/alive.js', { skip: defaultsToOldProtocol }, (t) => { 19 | const script = Path.join('examples', 'alive.js'); 20 | let cli = null; 21 | let target = null; 22 | 23 | function cleanup(error) { 24 | if (cli) { 25 | cli.quit(); 26 | cli = null; 27 | } 28 | if (target) { 29 | target.kill(); 30 | target = null; 31 | } 32 | if (error) throw error; 33 | } 34 | 35 | return launchTarget(script) 36 | .then((childProc) => { 37 | target = childProc; 38 | cli = startCLI(['-p', `${target.pid}`]); 39 | return cli.waitForPrompt(); 40 | }) 41 | .then(() => cli.command('sb("alive.js", 3)')) 42 | .then(() => cli.waitFor(/break/)) 43 | .then(() => cli.waitForPrompt()) 44 | .then(() => { 45 | t.match( 46 | cli.output, 47 | '> 3 ++x;', 48 | 'marks the 3rd line'); 49 | }) 50 | .then(() => cleanup()) 51 | .then(null, cleanup); 52 | }); 53 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/share/icicle.css: -------------------------------------------------------------------------------- 1 | /* 2 | * icicle.css: styles for Icicle visualization 3 | */ 4 | body { 5 | font-family: sans-serif; 6 | font-size: small; 7 | padding-left: 20px; 8 | padding-right: 20px; 9 | color: #333333; 10 | } 11 | 12 | a { 13 | color: #CC2900; 14 | } 15 | 16 | div#chart { 17 | width: 100%; 18 | margin-top: 20px; 19 | } 20 | 21 | rect.svBox { 22 | stroke: #fff; 23 | } 24 | 25 | rect.svBox:hover { 26 | cursor: pointer; 27 | } 28 | 29 | .svBoxLabel { 30 | cursor: pointer; 31 | fill: #333333; 32 | } 33 | 34 | div.svTooltip { 35 | opacity: 0; 36 | z-index: 2; 37 | position: absolute; 38 | height: 3em; 39 | white-space: nowrap; 40 | padding-top: 8px; 41 | padding-left: 12px; 42 | padding-right: 12px; 43 | padding-bottom: 18px; 44 | font-size: small; 45 | background: #ffffee; 46 | border: 0px; 47 | border-radius: 2px; 48 | pointer-events: none; 49 | } 50 | 51 | div.svPopout { 52 | position: absolute; 53 | border-radius: 2px; 54 | border: 2px #333333 solid; 55 | padding: 6px; 56 | background-color: #ffffff; 57 | opacity: 0; 58 | z-index: -1; 59 | width: 50%; 60 | } 61 | 62 | .svXAxisLabel { 63 | font-size: large; 64 | font-weight: bold; 65 | font-variant: small-caps; 66 | text-align: center; 67 | width: 100%; 68 | letter-spacing: 0.3em; 69 | margin-bottom: 20px; 70 | } 71 | 72 | .svYAxisLabel { 73 | font-size: large; 74 | font-weight: bold; 75 | font-variant: small-caps; 76 | text-align: center; 77 | letter-spacing: 0.3em; 78 | white-space: nowrap; 79 | } 80 | -------------------------------------------------------------------------------- /packages/linux-perf/linux-perf-listener.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "linux-perf.h" 5 | 6 | namespace node { 7 | 8 | LinuxPerfHandler::LinuxPerfHandler(v8::Isolate* isolate) : v8::CodeEventHandler(isolate) { 9 | // TODO(mmarchini):: ideally this should be handled in another thread. 10 | auto pid = static_cast(uv_os_getpid()); 11 | 12 | mapFile.open("/tmp/perf-" + std::to_string(pid) + ".map"); 13 | } 14 | 15 | LinuxPerfHandler::~LinuxPerfHandler() { 16 | mapFile.close(); 17 | } 18 | 19 | std::string LinuxPerfHandler::FormatName(v8::CodeEvent* code_event) { 20 | auto comment = std::string(code_event->GetComment()); 21 | if (not comment.empty()) return comment; 22 | 23 | char buffer[1000]; 24 | std::ostringstream formattedName; 25 | 26 | v8::Isolate* isolate = v8::Isolate::GetCurrent(); 27 | 28 | auto functionName = code_event->GetFunctionName(); 29 | functionName->WriteUtf8(isolate, buffer, 1000); 30 | formattedName << buffer; 31 | 32 | auto scriptName = code_event->GetScriptName(); 33 | scriptName->WriteUtf8(isolate, buffer, 1000); 34 | if(buffer[0] != '\0') { 35 | formattedName << " " << buffer << ":" << code_event->GetScriptLine(); 36 | } 37 | 38 | return formattedName.str(); 39 | } 40 | 41 | void LinuxPerfHandler::Handle(v8::CodeEvent* code_event) { 42 | mapFile << std::hex << code_event->GetCodeStartAddress() << " " 43 | << std::hex << code_event->GetCodeSize() << " " 44 | << v8::CodeEvent::GetCodeEventTypeName(code_event->GetCodeType()) 45 | << ":" << FormatName(code_event) << std::endl; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /packages/diat/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "diat", 3 | "version": "1.4.0", 4 | "description": "A CLI tool to help with diagnosing Node.js processes basing on inspector.", 5 | "main": "lib/index.js", 6 | "license": "MIT", 7 | "scripts": { 8 | "dev": "npm run build && ./bin/diat.js --help", 9 | "build": "tsc", 10 | "prepublish": "npm run build && cp ../../README_EN.md ./README.md", 11 | "test": "jest -c jest.config.json --coverage --coveragePathIgnorePatterns=__tests__ --passWithNoTests -w=1 --forceExit", 12 | "test:ci": "node scripts/ci", 13 | "prettier": "prettier src/**/*.ts --write" 14 | }, 15 | "bin": { 16 | "diat": "./bin/diat.js" 17 | }, 18 | "engines": { 19 | "node": ">=8" 20 | }, 21 | "files": [ 22 | "snippet", 23 | "bin", 24 | "lib", 25 | "package.json", 26 | "README.md", 27 | "CHANGELOG.md" 28 | ], 29 | "dependencies": { 30 | "ansi-escapes": "^4.3.0", 31 | "bytes": "^3.1.0", 32 | "chalk": "^2.4.2", 33 | "diat-linux-perf": "^1.3.0", 34 | "diat-live-inspector": "^1.3.0", 35 | "diat-node-inspect": "^1.4.0", 36 | "diat-stackvis-simplified": "^1.4.0", 37 | "inquirer": "^7.0.0", 38 | "node-fetch": "^2.6.0", 39 | "semver": "^6.3.0", 40 | "serve-static": "^1.14.1", 41 | "tslib": "^1.10.0", 42 | "yargs": "^14.2.0" 43 | }, 44 | "devDependencies": { 45 | "@types/jest": "^23.3.7", 46 | "@types/node": "^13.9.0", 47 | "jest": "^24.1.0", 48 | "prettier": "^1.19.1", 49 | "ts-jest": "^23.10.4", 50 | "tslint": "^5.11.0", 51 | "tslint-config-prettier": "^1.10.0", 52 | "typescript": "^3.8.3" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/color.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/color.js: color utility functions 3 | */ 4 | 5 | var mod_assert = require('assert'); 6 | 7 | /* 8 | * Convert from HSV to RGB. Ported from the Java implementation by Eugene 9 | * Vishnevsky: 10 | * 11 | * http://www.cs.rit.edu/~ncs/color/t_convert.html 12 | */ 13 | exports.convertHsvToRgb = function convertHsvToRgb(h, s, v) 14 | { 15 | var r, g, b; 16 | var i; 17 | var f, p, q, t; 18 | 19 | mod_assert.ok(h >= 0 && h <= 360, 'hue (' + h + ') out of range'); 20 | mod_assert.ok(s >= 0 && s <= 1, 'saturation (' + s + ') out of range'); 21 | mod_assert.ok(v >= 0 && v <= 1, 'value (' + v + ') out of range'); 22 | 23 | if (s === 0) { 24 | /* 25 | * A saturation of 0.0 is achromatic (grey). 26 | */ 27 | r = g = b = v; 28 | 29 | return ([ Math.round(r * 255), Math.round(g * 255), 30 | Math.round(b * 255) ]); 31 | } 32 | 33 | h /= 60; // sector 0 to 5 34 | 35 | i = Math.floor(h); 36 | f = h - i; // fractional part of h 37 | p = v * (1 - s); 38 | q = v * (1 - s * f); 39 | t = v * (1 - s * (1 - f)); 40 | 41 | switch (i) { 42 | case 0: 43 | r = v; 44 | g = t; 45 | b = p; 46 | break; 47 | 48 | case 1: 49 | r = q; 50 | g = v; 51 | b = p; 52 | break; 53 | 54 | case 2: 55 | r = p; 56 | g = v; 57 | b = t; 58 | break; 59 | 60 | case 3: 61 | r = p; 62 | g = q; 63 | b = v; 64 | break; 65 | 66 | case 4: 67 | r = t; 68 | g = p; 69 | b = v; 70 | break; 71 | 72 | default: // case 5: 73 | r = v; 74 | g = p; 75 | b = q; 76 | break; 77 | } 78 | 79 | return ([ Math.round(r * 255), 80 | Math.round(g * 255), Math.round(b * 255) ]); 81 | }; 82 | -------------------------------------------------------------------------------- /packages/live-inspector/src/TcpDetector.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net'; 2 | import * as events from 'events'; 3 | 4 | const kInterval = 1000; 5 | 6 | interface IOptions { 7 | noTimer: boolean; 8 | } 9 | 10 | export class TcpDetector extends events.EventEmitter { 11 | private port: number; 12 | private options: IOptions; 13 | private timer: any = null; 14 | 15 | static detectPort = async (port: number): Promise => { 16 | const detector = new TcpDetector(port); 17 | 18 | const res = await detector.connect(); 19 | 20 | detector.destroy(); 21 | 22 | return res; 23 | }; 24 | 25 | constructor(port: number, options: IOptions = { noTimer: false }) { 26 | super(); 27 | 28 | this.options = options; 29 | 30 | if (!port) { 31 | throw new Error(`invalid port: ${port}`); 32 | } 33 | 34 | this.port = port; 35 | 36 | if (!this.options.noTimer) { 37 | this.timer = setInterval(() => { 38 | this.connect().then(success => { 39 | this.emit('_check', success); 40 | if (!success) { 41 | this.destroy(); 42 | } 43 | }); 44 | }, kInterval); 45 | } 46 | } 47 | 48 | private connect = (): Promise => { 49 | const { port } = this; 50 | 51 | return new Promise(resolve => { 52 | const socket = net.connect(port); 53 | 54 | socket.once('connect', () => { 55 | socket.destroy(); 56 | resolve(true); 57 | }); 58 | 59 | socket.once('error', () => { 60 | socket.destroy(); 61 | resolve(false); 62 | }); 63 | }); 64 | }; 65 | 66 | destroy = () => { 67 | clearInterval(this.timer); 68 | this.emit('close'); 69 | }; 70 | } 71 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/invalid-args.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | const { createServer } = require('net'); 4 | 5 | const { test } = require('tap'); 6 | 7 | const startCLI = require('./start-cli'); 8 | 9 | test('launch CLI w/o args', (t) => { 10 | const cli = startCLI([]); 11 | return cli.quit() 12 | .then((code) => { 13 | t.equal(code, 1, 'exits with non-zero exit code'); 14 | t.match(cli.output, /^Usage:/, 'Prints usage info'); 15 | }); 16 | }); 17 | 18 | test('launch w/ invalid host:port', (t) => { 19 | const cli = startCLI(['localhost:914']); 20 | return cli.quit() 21 | .then((code) => { 22 | t.match( 23 | cli.output, 24 | 'failed to connect', 25 | 'Tells the user that the connection failed'); 26 | t.equal(code, 1, 'exits with non-zero exit code'); 27 | }); 28 | }); 29 | 30 | test('launch w/ unavailable port', async(t) => { 31 | const blocker = createServer((socket) => socket.end()); 32 | const port = await new Promise((resolve, reject) => { 33 | blocker.on('error', reject); 34 | blocker.listen(0, '127.0.0.1', () => resolve(blocker.address().port)); 35 | }); 36 | 37 | try { 38 | const script = Path.join('examples', 'three-lines.js'); 39 | const cli = startCLI([`--port=${port}`, script]); 40 | const code = await cli.quit(); 41 | 42 | t.notMatch( 43 | cli.output, 44 | 'report this bug', 45 | 'Omits message about reporting this as a bug'); 46 | t.match( 47 | cli.output, 48 | `waiting for 127.0.0.1:${port} to be free`, 49 | 'Tells the user that the port wasn\'t available'); 50 | t.equal(code, 1, 'exits with non-zero exit code'); 51 | } finally { 52 | blocker.close(); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /packages/diat/__tests__/StackVis.spec.ts: -------------------------------------------------------------------------------- 1 | import { StackVis } from '../src/StackVis'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import * as stream from 'stream'; 5 | 6 | const kPerfFile = path.resolve(__dirname, './out.perftext'); 7 | const kPerfSvg = path.resolve(__dirname, './result.svg'); 8 | 9 | describe('StackVis', () => { 10 | it('should work', async () => { 11 | const fileStream = fs.createReadStream(kPerfFile); 12 | const bufs: any[] = []; 13 | const svgBufs: any[] = []; 14 | const writable = new stream.Writable({ 15 | write(chunk, encoding, callback) { 16 | bufs.push(chunk); 17 | callback(); 18 | } 19 | }); 20 | const vis = new StackVis(); 21 | vis.collapsePerfStream(fileStream, writable); 22 | 23 | const result: any = await new Promise(resolve => { 24 | writable.on('finish', () => { 25 | resolve(Buffer.concat(bufs).toString('utf8')); 26 | }); 27 | }); 28 | expect(result.length).toBeGreaterThan(0); 29 | 30 | const readable = new stream.Readable({ 31 | read() { 32 | this.push(null); 33 | } 34 | }); 35 | readable.push(result); 36 | const svgWritable = new stream.Writable({ 37 | write(chunk, encoding, callback) { 38 | svgBufs.push(chunk); 39 | callback(); 40 | } 41 | }); 42 | vis.collapsedToSvg(readable, svgWritable); 43 | 44 | const svg = await new Promise(resolve => { 45 | svgWritable.on('finish', () => { 46 | resolve(Buffer.concat(svgBufs).toString('utf8')); 47 | }); 48 | }); 49 | expect(svg).toContain(' { 53 | const vis = new StackVis(); 54 | 55 | await vis.perfStackToSvg(kPerfFile, kPerfSvg); 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /packages/diat/src/StackVis.ts: -------------------------------------------------------------------------------- 1 | import * as stackvis from 'diat-stackvis-simplified/lib/stackvis' 2 | import * as stream from 'stream' 3 | import * as fs from 'fs' 4 | import { logger } from './Logger' 5 | import { getAbsolutePath } from './utils' 6 | 7 | const emptyCallback = () => {} 8 | 9 | export class StackVis { 10 | constructor() { 11 | // 12 | } 13 | 14 | collapsePerfStream = ( 15 | dataStream: stream.Readable, 16 | outStream: stream.Writable 17 | ) => { 18 | const reader = stackvis.readerLookup('perf') 19 | const writer = stackvis.writerLookup('collapsed') 20 | 21 | stackvis.pipeStacks( 22 | logger, 23 | dataStream, 24 | reader, 25 | writer, 26 | outStream, 27 | emptyCallback 28 | ) 29 | } 30 | 31 | collapsedToSvg = ( 32 | dataStream: stream.Readable, 33 | outStream: stream.Writable 34 | ) => { 35 | const reader = stackvis.readerLookup('collapsed') 36 | const writer = stackvis.writerLookup('flamegraph-svg') 37 | 38 | stackvis.pipeStacks( 39 | logger, 40 | dataStream, 41 | reader, 42 | writer, 43 | outStream, 44 | emptyCallback 45 | ) 46 | } 47 | 48 | perfStackToSvg = async ( 49 | inputFilePath: string, 50 | outputFilePath: string 51 | ): Promise => { 52 | const readable = fs.createReadStream(getAbsolutePath(inputFilePath)) 53 | const pass = new stream.PassThrough() 54 | const writable = fs.createWriteStream(getAbsolutePath(outputFilePath)) 55 | 56 | this.collapsePerfStream(readable, pass) 57 | this.collapsedToSvg(pass, writable) 58 | 59 | await new Promise(resolve => { 60 | writable.on('finish', () => { 61 | resolve() 62 | }) 63 | }) 64 | 65 | return getAbsolutePath(outputFilePath) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /packages/diat/__tests__/Metric.spec.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net'; 2 | import * as fs from 'fs'; 3 | import * as stream from 'stream'; 4 | import * as path from 'path'; 5 | import * as util from 'util'; 6 | import { Metric, kMessageSeperator } from '../src/Metric'; 7 | 8 | const kSocketPath = path.resolve(__dirname, './metric.sock'); 9 | 10 | describe('Metric', () => { 11 | beforeEach(async () => { 12 | try { 13 | await util.promisify(fs.unlink)(kSocketPath); 14 | } catch (err) { 15 | // 16 | } 17 | }); 18 | 19 | it('should work', async () => { 20 | const passthrough = new stream.PassThrough(); 21 | 22 | const metric = new Metric({ 23 | std: passthrough, 24 | socketPath: kSocketPath 25 | }); 26 | 27 | const socketPath = await metric.createServer(); 28 | 29 | expect(socketPath).toEqual(kSocketPath); 30 | 31 | const socket = net.connect(socketPath); 32 | 33 | await new Promise(resolve => { 34 | socket.once('connect', () => { 35 | resolve(); 36 | }); 37 | }); 38 | 39 | socket.write( 40 | JSON.stringify({ 41 | cpuUsage: { 42 | user: 0, 43 | system: 0 44 | }, 45 | memoryUsage: { 46 | rss: 0, 47 | heapTotal: 0, 48 | heapUsed: 0, 49 | external: 0 50 | }, 51 | uv: { 52 | request: 0, 53 | handle: 0, 54 | latency: 0 55 | } 56 | }) + kMessageSeperator 57 | ); 58 | 59 | await new Promise(resolve => { 60 | let msg = ''; 61 | passthrough.on('data', data => { 62 | msg += data.toString('utf8'); 63 | if (msg.indexOf('[uv]') >= 0) { 64 | resolve(); 65 | } 66 | }); 67 | }); 68 | 69 | socket.destroy(); 70 | await metric.destroy(); 71 | }); 72 | }); 73 | -------------------------------------------------------------------------------- /packages/diat/snippet/metric_collect.js: -------------------------------------------------------------------------------- 1 | (async options => { 2 | const { socketPath, interval = 2000, messageSeperator = '__$' } = options; 3 | // connect 4 | const net = require('net'); 5 | 6 | const socket = await new Promise(resolve => { 7 | const socket = net.connect(socketPath, () => { 8 | resolve(socket); 9 | }); 10 | }); 11 | 12 | let timer = null; 13 | let lastCpuUsage = null; 14 | let uvStartTime = null; 15 | 16 | const destroy = () => { 17 | clearInterval(timer); 18 | }; 19 | 20 | const collect = () => { 21 | // cpu 22 | const cpuUsage = process.cpuUsage(); 23 | let cpuUsageUser = 0; 24 | let cpuUsageSystem = 0; 25 | let uvLatency = 0; 26 | 27 | if (lastCpuUsage) { 28 | cpuUsageUser = (cpuUsage.user - lastCpuUsage.user) / (interval * 1e3); 29 | cpuUsageSystem = 30 | (cpuUsage.system - lastCpuUsage.system) / (interval * 1e3); 31 | } 32 | 33 | lastCpuUsage = process.cpuUsage(); 34 | 35 | const now = Date.now(); 36 | 37 | // latency 38 | if (uvStartTime) { 39 | uvLatency = Math.max(now - uvStartTime - interval, 0); 40 | } 41 | 42 | uvStartTime = now; 43 | 44 | const ret = { 45 | cpuUsage: { 46 | user: cpuUsageUser, 47 | system: cpuUsageSystem 48 | }, 49 | memoryUsage: process.memoryUsage(), 50 | uv: { 51 | latency: uvLatency, 52 | handle: process._getActiveHandles().length, 53 | request: process._getActiveRequests().length 54 | } 55 | }; 56 | 57 | socket.write(JSON.stringify(ret) + messageSeperator); 58 | }; 59 | 60 | socket.on('error', () => { 61 | // 62 | }); 63 | 64 | socket.once('close', destroy); 65 | 66 | lastCpuUsage = process.cpuUsage(); 67 | uvStartTime = Date.now(); 68 | timer = setInterval(collect, interval); 69 | })(__OPTIONS__); 70 | -------------------------------------------------------------------------------- /packages/diat/__tests__/TcpProxy.spec.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net' 2 | import { TcpProxy } from '../src/TcpProxy' 3 | 4 | describe('TcpProxy', () => { 5 | const createTest = async () => { 6 | const serverSockets: net.Socket[] = [] 7 | const server = await new Promise((resolve, reject) => { 8 | const server = net.createServer((socket) => { 9 | serverSockets.push(socket) 10 | socket.write('hello') 11 | }) 12 | 13 | server.listen(0, () => { 14 | resolve(server) 15 | }) 16 | }) 17 | 18 | const tcpProxy = new TcpProxy({ 19 | targetHost: '127.0.0.1', 20 | targetPort: (server.address() as any).port 21 | }) 22 | 23 | const { host, port } = await tcpProxy.listen() 24 | 25 | const clientSocket = await new Promise((resolve) => { 26 | const client = net.connect(port, host) 27 | 28 | client.on('data', (data) => { 29 | expect(data.toString('utf8')).toBe('hello') 30 | resolve(client) 31 | }) 32 | }) 33 | 34 | return { 35 | serverSockets, 36 | server, 37 | clientSocket 38 | } 39 | } 40 | 41 | it('should close the client socket if the server socket get closed', async () => { 42 | const {serverSockets, server, clientSocket} = await createTest() 43 | await new Promise((resolve) => { 44 | serverSockets.forEach(socket => socket.destroy()) 45 | clientSocket.on('close', () => { 46 | resolve() 47 | }) 48 | }) 49 | server.close() 50 | }) 51 | 52 | it('should close the server socket if the client socket get closed', async () => { 53 | const { serverSockets, server, clientSocket } = await createTest() 54 | clientSocket.destroy() 55 | await Promise.all(serverSockets.map(socket => new Promise((resolve) => { 56 | socket.on('close', () => { 57 | resolve() 58 | }) 59 | }))) 60 | server.close() 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /packages/diat/__tests__/toggle_perf_basic_prof.spec.ts: -------------------------------------------------------------------------------- 1 | import { Comm } from '../src/Comm'; 2 | import { createTestProcess, isNodeVersionLE8 } from './utils'; 3 | import { LinuxPerf } from '../src/LinuxPerf'; 4 | 5 | const kCmd = 'toggle_perf_basic_prof'; 6 | 7 | describe('toggle_perf_basic_prof', () => { 8 | const linuxPerf = new LinuxPerf(); 9 | const modulePath = linuxPerf.getModulePath(); 10 | 11 | if (!modulePath || isNodeVersionLE8()) { 12 | it('should skip', () => {}); 13 | return; 14 | } 15 | 16 | let child: any; 17 | let comm: any; 18 | 19 | const clear = async () => { 20 | if (comm) { 21 | await comm.disconnect(); 22 | comm = null; 23 | } 24 | 25 | if (child) { 26 | child.kill(); 27 | child = null; 28 | } 29 | }; 30 | 31 | beforeEach(async () => { 32 | child = (await createTestProcess('net_socket_server')).child; 33 | comm = new Comm(child.pid, undefined); 34 | await comm.connect(); 35 | }); 36 | 37 | afterEach(async () => { 38 | await clear(); 39 | }); 40 | 41 | it('should work', async () => { 42 | expect( 43 | await comm.run(kCmd, { 44 | enable: false, 45 | modulePath 46 | }) 47 | ).toEqual({ 48 | type: 'success', 49 | content: expect.stringMatching(/false/) 50 | }); 51 | 52 | expect( 53 | await comm.run(kCmd, { 54 | enable: true, 55 | modulePath 56 | }) 57 | ).toEqual({ 58 | type: 'success', 59 | content: expect.stringMatching(/true/) 60 | }); 61 | 62 | expect( 63 | await comm.run(kCmd, { 64 | enable: true, 65 | modulePath 66 | }) 67 | ).toEqual({ 68 | type: 'success', 69 | content: expect.stringMatching(/false/) 70 | }); 71 | 72 | expect( 73 | await comm.run(kCmd, { 74 | enable: false, 75 | modulePath 76 | }) 77 | ).toEqual({ 78 | type: 'success', 79 | content: expect.stringMatching(/true/) 80 | }); 81 | }); 82 | }); 83 | -------------------------------------------------------------------------------- /packages/diat/__tests__/utils.spec.ts: -------------------------------------------------------------------------------- 1 | jest.mock('node-fetch') 2 | const fetch = require('node-fetch') 3 | 4 | import { getAbsolutePath, getPublicIP, getFirstSessionURL } from '../src/utils' 5 | 6 | fetch.mockImplementation(() => { 7 | return Promise.resolve({ 8 | json: () => 9 | Promise.resolve( 10 | JSON.parse(`[ { 11 | "description": "node.js instance", 12 | "devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=127.0.0.1:9229/9d2df447-011d-4fb1-81b5-0d943a06f9f1", 13 | "devtoolsFrontendUrlCompat": "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=127.0.0.1:9229/9d2df447-011d-4fb1-81b5-0d943a06f9f1", 14 | "faviconUrl": "https://nodejs.org/static/favicon.ico", 15 | "id": "9d2df447-011d-4fb1-81b5-0d943a06f9f1", 16 | "title": "node[67022]", 17 | "type": "node", 18 | "url": "file://", 19 | "webSocketDebuggerUrl": "ws://127.0.0.1:9229/9d2df447-011d-4fb1-81b5-0d943a06f9f1" 20 | } ] 21 | `) 22 | ), 23 | }) 24 | }) 25 | 26 | describe('utils', () => { 27 | describe('getAbsolutePath', () => { 28 | it('should work', async () => { 29 | expect(getAbsolutePath('./abc')).not.toEqual('./abc') 30 | expect(getAbsolutePath('/abc')).toEqual('/abc') 31 | }) 32 | }) 33 | 34 | describe('getPublicIP', () => { 35 | it('should work', async () => { 36 | const ip = await getPublicIP() 37 | expect(typeof ip === 'string' || ip === null).toBe(true) 38 | }) 39 | }) 40 | 41 | describe('getFirstSessionURL', () => { 42 | it('should work', async () => { 43 | const ret = await getFirstSessionURL('127.0.0.1', 9229, '127.0.0.1') 44 | expect(ret).toContain('devtools://') 45 | }) 46 | 47 | it('should supportproxy url', async () => { 48 | const ret = await getFirstSessionURL( 49 | '127.0.0.1', 50 | 9229, 51 | '127.0.0.1', 52 | (url) => { 53 | return Promise.resolve('MY_URL') 54 | } 55 | ) 56 | expect(ret).toContain('devtools://') 57 | expect(ret).toContain('MY_URL') 58 | }) 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /packages/diat/__tests__/Perf.spec.ts: -------------------------------------------------------------------------------- 1 | jest.mock('child_process') 2 | 3 | const childProcess = require('child_process') 4 | 5 | let handleExit: any = () => {} 6 | 7 | childProcess.exec.mockImplementation(() => { 8 | const mockChild = { 9 | on: (eventName, callback) => { 10 | if (eventName === 'exit') { 11 | setTimeout(() => { 12 | handleExit(callback) 13 | }, 100) 14 | } 15 | }, 16 | stderr: { 17 | pipe: jest.fn(), 18 | }, 19 | stdout: { 20 | pipe: jest.fn(), 21 | }, 22 | } 23 | return mockChild 24 | }) 25 | 26 | import { Perf } from '../src/Perf' 27 | 28 | describe('Perf', () => { 29 | describe('hasPerf', () => { 30 | it('should return a boolean', async () => { 31 | expect(typeof (await Perf.hasPerf())).toBe('boolean') 32 | }) 33 | }) 34 | 35 | describe('record', () => { 36 | Perf.perfExist = true 37 | 38 | it('should return a file path', async () => { 39 | handleExit = (callback) => { 40 | callback(0) 41 | } 42 | 43 | const ret = await Perf.record({ 44 | pid: 1, 45 | }) 46 | expect(typeof ret).toBe('string') 47 | }) 48 | 49 | it('should throw when child exits with non-zero code', async () => { 50 | handleExit = (callback) => { 51 | callback(1) 52 | } 53 | 54 | await expect( 55 | Perf.record({ 56 | pid: 1, 57 | }) 58 | ).rejects.toThrow('failed') 59 | }) 60 | }) 61 | 62 | describe('script', () => { 63 | Perf.perfExist = true 64 | 65 | it('should return a file path', async () => { 66 | handleExit = (callback) => { 67 | callback(0) 68 | } 69 | 70 | const ret = await Perf.script({ 71 | dataFile: 'output.data', 72 | }) 73 | expect(typeof ret).toBe('string') 74 | }) 75 | 76 | it('should throw when child exits with non-zero code', async () => { 77 | handleExit = (callback) => { 78 | callback(1) 79 | } 80 | 81 | await expect( 82 | Perf.script({ 83 | dataFile: 'output.data', 84 | }) 85 | ).rejects.toThrow('failed') 86 | }) 87 | }) 88 | }) 89 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/align-multiline-assignment.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Align multiline variable assignments 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | function getBinaryExpressionStarts(binaryExpression, starts) { 11 | function getStartsFromOneSide(side, starts) { 12 | starts.push(side.loc.start); 13 | if (side.type === 'BinaryExpression') { 14 | starts = getBinaryExpressionStarts(side, starts); 15 | } 16 | return starts; 17 | } 18 | 19 | starts = getStartsFromOneSide(binaryExpression.left, starts); 20 | starts = getStartsFromOneSide(binaryExpression.right, starts); 21 | return starts; 22 | } 23 | 24 | function checkExpressionAlignment(expression) { 25 | if (!expression) 26 | return; 27 | 28 | var msg = ''; 29 | 30 | switch (expression.type) { 31 | case 'BinaryExpression': 32 | var starts = getBinaryExpressionStarts(expression, []); 33 | var startLine = starts[0].line; 34 | const startColumn = starts[0].column; 35 | starts.forEach((loc) => { 36 | if (loc.line > startLine) { 37 | startLine = loc.line; 38 | if (loc.column !== startColumn) { 39 | msg = 'Misaligned multiline assignment'; 40 | } 41 | } 42 | }); 43 | break; 44 | } 45 | return msg; 46 | } 47 | 48 | function testAssignment(context, node) { 49 | const msg = checkExpressionAlignment(node.right); 50 | if (msg) 51 | context.report(node, msg); 52 | } 53 | 54 | function testDeclaration(context, node) { 55 | node.declarations.forEach((declaration) => { 56 | const msg = checkExpressionAlignment(declaration.init); 57 | // const start = declaration.init.loc.start; 58 | if (msg) 59 | context.report(node, msg); 60 | }); 61 | } 62 | 63 | module.exports = function(context) { 64 | return { 65 | 'AssignmentExpression': (node) => testAssignment(context, node), 66 | 'VariableDeclaration': (node) => testDeclaration(context, node) 67 | }; 68 | }; 69 | -------------------------------------------------------------------------------- /packages/live-inspector/__tests__/ProcessInspectPort.spec.ts: -------------------------------------------------------------------------------- 1 | import { killPid, ProcessInspectPort } from '../src/ProcessInspectPort'; 2 | import { createTestProcess, createInspectProcess } from './utils'; 3 | 4 | // 通常应该没有这么大的pid 5 | const kInvalidPid = 10000000; 6 | 7 | describe('ProcessInspectPort', () => { 8 | describe('killPid', () => { 9 | it("should reject the promise if the process doesn't exist", async () => { 10 | expect(() => killPid(kInvalidPid, 'SIGUSR1')).toThrow(); 11 | }); 12 | 13 | it('should work', async () => { 14 | const child = await createTestProcess(); 15 | 16 | expect(child.pid).toBeTruthy(); 17 | 18 | const childKilled = new Promise(resolve => { 19 | child.on('exit', () => { 20 | resolve(); 21 | }); 22 | }); 23 | 24 | await killPid(child.pid, 'SIGKILL'); 25 | await childKilled; 26 | }); 27 | }); 28 | 29 | describe('ProcessInspectPort', () => { 30 | it('should throw if failed to connect', async () => { 31 | const pro = new ProcessInspectPort({}); 32 | 33 | await expect(pro.connectByPid(kInvalidPid)).rejects.toThrow(); 34 | }); 35 | 36 | // TODO 这个测试需要本地9229端口没有被占用 37 | it( 38 | 'should connect to the inspector server', 39 | async () => { 40 | const child = await createTestProcess(); 41 | const pro = new ProcessInspectPort({ 42 | retryTimes: 10 43 | }); 44 | 45 | const ws = await pro.connectByPid(child.pid); 46 | expect(ws).toBeTruthy(); 47 | child.kill('SIGKILL'); 48 | ws.terminate(); 49 | }, 50 | 30 * 1000 51 | ); 52 | 53 | it('should support connect by inspect url', async () => { 54 | const { child, addr: addrStr } = await createInspectProcess(); 55 | const pro = new ProcessInspectPort({ 56 | retryTimes: 10 57 | }); 58 | 59 | const arr = addrStr.split(':'); 60 | 61 | const addr = { 62 | host: arr[0], 63 | port: parseInt(arr[1], 10) 64 | }; 65 | 66 | const ws = await pro.connectByAddr(addr); 67 | expect(ws).toBeTruthy(); 68 | 69 | child.kill('SIGKILL'); 70 | ws.terminate(); 71 | }); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/diat/__tests__/InspectorWorker.spec.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import * as util from 'util'; 3 | import * as path from 'path'; 4 | import { InspectorWorker } from '../src/InspectorWorker'; 5 | import { hasWorker, kTimeout } from './utils'; 6 | 7 | describe('InspectorWorker', () => { 8 | const workers: any[] = []; 9 | let session: inspector.Session; 10 | let post: any; 11 | let inspectorWorker: InspectorWorker; 12 | 13 | if (!hasWorker()) { 14 | it('(skipped)', () => {}); 15 | return; 16 | } 17 | const thread = require('worker_threads'); 18 | 19 | beforeAll(async () => { 20 | for (let i = 0; i < 2; i += 1) { 21 | workers.push( 22 | new thread.Worker( 23 | path.resolve(__dirname, './test_process/thread_worker.js') 24 | ) 25 | ); 26 | } 27 | }); 28 | 29 | afterAll(async () => { 30 | // NOTE Workers won't exit so we add '--forceExit' to jest. 31 | for (const worker of workers) { 32 | worker.terminate() 33 | } 34 | }); 35 | 36 | beforeEach(async () => { 37 | session = new inspector.Session(); 38 | session.connect(); 39 | post = util.promisify(session.post.bind(session)); 40 | inspectorWorker = new InspectorWorker({ 41 | post, 42 | event: ({ 43 | removeListener: (name, cb) => { 44 | return session.removeListener(name, cb) 45 | }, 46 | addListener: (name, cb) => { 47 | return session.addListener(name, (msg) => { 48 | cb(msg.params) 49 | }) 50 | } 51 | } as any) 52 | }); 53 | }); 54 | 55 | afterEach(async () => { 56 | await inspectorWorker.destroy(); 57 | await post('NodeWorker.disable'); 58 | session.disconnect(); 59 | }); 60 | 61 | it('should work', async () => { 62 | const infos: any = await inspectorWorker.getWorkers(); 63 | expect(infos).toEqual([expect.anything(), expect.anything()]); 64 | const ret = await inspectorWorker.createWorkerSession( 65 | infos[0].sessionId 66 | ); 67 | expect(ret).toEqual({ 68 | host: expect.anything(), 69 | port: expect.anything(), 70 | address: expect.anything(), 71 | family: expect.anything(), 72 | }); 73 | }, kTimeout); 74 | }); 75 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/address.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { spawn } = require('child_process'); 3 | const Path = require('path'); 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | // NOTE(oyyd): We might want to import this regexp from "lib/_inspect.js"? 9 | const kDebuggerMsgReg = /Debugger listening on ws:\/\/\[?(.+?)\]?:(\d+)\//; 10 | 11 | function launchTarget(...args) { 12 | const childProc = spawn(process.execPath, args); 13 | return new Promise((resolve, reject) => { 14 | const onExit = () => { 15 | reject(new Error('Child process exits unexpectly')); 16 | }; 17 | childProc.on('exit', onExit); 18 | childProc.stderr.setEncoding('utf8'); 19 | childProc.stderr.on('data', (data) => { 20 | const ret = kDebuggerMsgReg.exec(data); 21 | childProc.removeListener('exit', onExit); 22 | if (ret) { 23 | resolve({ 24 | childProc, 25 | host: ret[1], 26 | port: ret[2], 27 | }); 28 | } 29 | }); 30 | }); 31 | } 32 | 33 | // process.debugPort is our proxy for "the version of node used to run this 34 | // test suite doesn't support SIGUSR1 for enabling --inspect for a process". 35 | const defaultsToOldProtocol = process.debugPort === 5858; 36 | 37 | test('examples/alive.js', { skip: defaultsToOldProtocol }, (t) => { 38 | const script = Path.join('examples', 'alive.js'); 39 | let cli = null; 40 | let target = null; 41 | 42 | function cleanup(error) { 43 | if (cli) { 44 | cli.quit(); 45 | cli = null; 46 | } 47 | if (target) { 48 | target.kill(); 49 | target = null; 50 | } 51 | if (error) throw error; 52 | } 53 | 54 | return launchTarget('--inspect=0', script) 55 | .then(({ childProc, host, port }) => { 56 | target = childProc; 57 | cli = startCLI([`${host || '127.0.0.1'}:${port}`]); 58 | return cli.waitForPrompt(); 59 | }) 60 | .then(() => cli.command('sb("alive.js", 3)')) 61 | .then(() => cli.waitFor(/break/)) 62 | .then(() => cli.waitForPrompt()) 63 | .then(() => { 64 | t.match( 65 | cli.output, 66 | '> 3 ++x;', 67 | 'marks the 3rd line'); 68 | }) 69 | .then(() => cleanup()) 70 | .then(null, cleanup); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/preserve-breaks.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('run after quit / restart', (t) => { 9 | const script = Path.join('examples', 'three-lines.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => cli.command('breakpoints')) 20 | .then(() => { 21 | t.match(cli.output, 'No breakpoints yet'); 22 | }) 23 | .then(() => cli.command('sb(2)')) 24 | .then(() => cli.command('sb(3)')) 25 | .then(() => cli.command('breakpoints')) 26 | .then(() => { 27 | t.match(cli.output, `#0 ${script}:2`); 28 | t.match(cli.output, `#1 ${script}:3`); 29 | }) 30 | .then(() => cli.stepCommand('c')) // hit line 2 31 | .then(() => cli.stepCommand('c')) // hit line 3 32 | .then(() => { 33 | t.match(cli.breakInfo, { filename: script, line: 3 }); 34 | }) 35 | .then(() => cli.command('restart')) 36 | .then(() => cli.waitForInitialBreak()) 37 | .then(() => { 38 | t.match(cli.breakInfo, { filename: script, line: 1 }); 39 | }) 40 | .then(() => cli.stepCommand('c')) 41 | .then(() => { 42 | t.match(cli.breakInfo, { filename: script, line: 2 }); 43 | }) 44 | .then(() => cli.stepCommand('c')) 45 | .then(() => { 46 | t.match(cli.breakInfo, { filename: script, line: 3 }); 47 | }) 48 | .then(() => cli.command('breakpoints')) 49 | .then(() => { 50 | if (process.platform === 'aix') { 51 | // TODO: There is a known issue on AIX where the breakpoints aren't 52 | // properly resolved yet when we reach this point. 53 | // Eventually that should be figured out but for now we don't want 54 | // to fail builds because of it. 55 | t.match(cli.output, /#0 [^\n]+three-lines\.js\$?:2/); 56 | t.match(cli.output, /#1 [^\n]+three-lines\.js\$?:3/); 57 | } else { 58 | t.match(cli.output, `#0 ${script}:2`); 59 | t.match(cli.output, `#1 ${script}:3`); 60 | } 61 | }) 62 | .then(() => cli.quit()) 63 | .then(null, onFatal); 64 | }); 65 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/align-function-arguments.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Align arguments in multiline function calls 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | //------------------------------------------------------------------------------ 8 | // Rule Definition 9 | //------------------------------------------------------------------------------ 10 | 11 | function checkArgumentAlignment(context, node) { 12 | 13 | function isNodeFirstInLine(node, byEndLocation) { 14 | const firstToken = byEndLocation === true ? context.getLastToken(node, 1) : 15 | context.getTokenBefore(node); 16 | const startLine = byEndLocation === true ? node.loc.end.line : 17 | node.loc.start.line; 18 | const endLine = firstToken ? firstToken.loc.end.line : -1; 19 | 20 | return startLine !== endLine; 21 | } 22 | 23 | if (node.arguments.length === 0) 24 | return; 25 | 26 | var msg = ''; 27 | const first = node.arguments[0]; 28 | var currentLine = first.loc.start.line; 29 | const firstColumn = first.loc.start.column; 30 | 31 | const ignoreTypes = [ 32 | 'ArrowFunctionExpression', 33 | 'FunctionExpression', 34 | 'ObjectExpression', 35 | ]; 36 | 37 | const args = node.arguments; 38 | 39 | // For now, don't bother trying to validate potentially complicating things 40 | // like closures. Different people will have very different ideas and it's 41 | // probably best to implement configuration options. 42 | if (args.some((node) => { return ignoreTypes.indexOf(node.type) !== -1; })) { 43 | return; 44 | } 45 | 46 | if (!isNodeFirstInLine(node)) { 47 | return; 48 | } 49 | 50 | var misaligned; 51 | 52 | args.slice(1).forEach((argument) => { 53 | if (!misaligned) { 54 | if (argument.loc.start.line === currentLine + 1) { 55 | if (argument.loc.start.column !== firstColumn) { 56 | if (isNodeFirstInLine(argument)) { 57 | msg = 'Function argument in column ' + 58 | `${argument.loc.start.column + 1}, ` + 59 | `expected in ${firstColumn + 1}`; 60 | misaligned = argument; 61 | } 62 | } 63 | } 64 | } 65 | currentLine = argument.loc.start.line; 66 | }); 67 | 68 | if (msg) 69 | context.report(misaligned, msg); 70 | } 71 | 72 | module.exports = function(context) { 73 | return { 74 | 'CallExpression': (node) => checkArgumentAlignment(context, node) 75 | }; 76 | }; 77 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/exceptions.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('break on (uncaught) exceptions', (t) => { 9 | const script = Path.join('examples', 'exceptions.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => { 20 | t.match(cli.breakInfo, { filename: script, line: 1 }); 21 | }) 22 | // making sure it will die by default: 23 | .then(() => cli.command('c')) 24 | // TODO: Remove FATAL ERROR once node doesn't show a FATAL ERROR anymore 25 | .then(() => cli.waitFor(/disconnect|FATAL ERROR/)) 26 | 27 | // Next run: With `breakOnException` it pauses in both places 28 | .then(() => cli.stepCommand('r')) 29 | .then(() => cli.waitForInitialBreak()) 30 | .then(() => { 31 | t.match(cli.breakInfo, { filename: script, line: 1 }); 32 | }) 33 | .then(() => cli.command('breakOnException')) 34 | .then(() => cli.stepCommand('c')) 35 | .then(() => { 36 | t.match(cli.output, `exception in ${script}:3`); 37 | }) 38 | .then(() => cli.stepCommand('c')) 39 | .then(() => { 40 | t.match(cli.output, `exception in ${script}:9`); 41 | }) 42 | 43 | // Next run: With `breakOnUncaught` it only pauses on the 2nd exception 44 | .then(() => cli.command('breakOnUncaught')) 45 | .then(() => cli.stepCommand('r')) // also, the setting survives the restart 46 | .then(() => cli.waitForInitialBreak()) 47 | .then(() => { 48 | t.match(cli.breakInfo, { filename: script, line: 1 }); 49 | }) 50 | .then(() => cli.stepCommand('c')) 51 | .then(() => { 52 | t.match(cli.output, `exception in ${script}:9`); 53 | }) 54 | 55 | // Next run: Back to the initial state! It should die again. 56 | .then(() => cli.command('breakOnNone')) 57 | .then(() => cli.stepCommand('r')) 58 | .then(() => cli.waitForInitialBreak()) 59 | .then(() => { 60 | t.match(cli.breakInfo, { filename: script, line: 1 }); 61 | }) 62 | .then(() => cli.command('c')) 63 | // TODO: Remove FATAL ERROR once node doesn't show a FATAL ERROR anymore 64 | .then(() => cli.waitFor(/disconnect|FATAL ERROR/)) 65 | 66 | .then(() => cli.quit()) 67 | .then(null, onFatal); 68 | }); 69 | -------------------------------------------------------------------------------- /packages/linux-perf/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 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/xml.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/xml.js: XML utility routines 3 | */ 4 | 5 | var mod_assert = require('assert'); 6 | 7 | exports.XmlEmitter = XmlEmitter; 8 | 9 | /* 10 | * Basic interface for emitting well-formed XML. This isn't bulletproof, but it 11 | * does escape values (not tags or keys) and checks for basic errors. 12 | */ 13 | function XmlEmitter(stream) 14 | { 15 | this.xe_stream = stream; 16 | this.xe_stack = []; 17 | } 18 | 19 | XmlEmitter.prototype.emitDoctype = function (name, type, path) 20 | { 21 | this.xe_stream.write('\n'); 22 | this.xe_stream.write('\n'); 24 | }; 25 | 26 | XmlEmitter.prototype.escape = function (str) 27 | { 28 | /* BEGIN JSSTYLED */ 29 | return (str.toString().replace(/&/g, '&'). 30 | replace(//g, '>'). 32 | replace(/"/g, '"')); 33 | /* END JSSTYLED */ 34 | }; 35 | 36 | XmlEmitter.prototype.emitIndent = function () 37 | { 38 | var str = ''; 39 | var i; 40 | 41 | for (i = 0; i < this.xe_stack.length; i++) 42 | str += ' '; 43 | 44 | this.xe_stream.write(str); 45 | }; 46 | 47 | XmlEmitter.prototype.emitEmpty = function (name, attrs) 48 | { 49 | this.emitIndent(); 50 | this.xe_stream.write('<' + name + ' '); 51 | this.emitAttrs(attrs); 52 | this.xe_stream.write('/>\n'); 53 | }; 54 | 55 | XmlEmitter.prototype.emitAttrs = function (attrs) 56 | { 57 | var key; 58 | 59 | if (!attrs) 60 | return; 61 | 62 | for (key in attrs) 63 | this.xe_stream.write(key + '=\"' + 64 | this.escape(attrs[key]) + '\" '); 65 | }; 66 | 67 | XmlEmitter.prototype.emitStart = function (name, attrs, opts) 68 | { 69 | this.emitIndent(); 70 | this.xe_stack.push(name); 71 | 72 | this.xe_stream.write('<' + name + ' '); 73 | this.emitAttrs(attrs); 74 | this.xe_stream.write('>'); 75 | 76 | if (!opts || !opts['bare']) 77 | this.xe_stream.write('\n'); 78 | }; 79 | 80 | XmlEmitter.prototype.emitEnd = function (name, opts) 81 | { 82 | var check = this.xe_stack.pop(); 83 | 84 | mod_assert.equal(name, check); 85 | 86 | if (!opts || !opts['bare']) 87 | this.emitIndent(); 88 | 89 | this.xe_stream.write('\n'); 90 | }; 91 | 92 | XmlEmitter.prototype.emitCData = function (data) 93 | { 94 | this.xe_stream.write(this.escape(data)); 95 | }; 96 | 97 | XmlEmitter.prototype.emitComment = function (content) 98 | { 99 | this.xe_stream.write('\n'); 100 | }; 101 | -------------------------------------------------------------------------------- /packages/diat/__tests__/Snippets.spec.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net' 2 | import * as path from 'path' 3 | import * as fs from 'fs' 4 | import { Snippets, snippets } from '../src/Snippets' 5 | import { kMessageSeperator } from '../src/Metric' 6 | 7 | const kSocketPath = path.resolve(__dirname, './test.sock') 8 | 9 | function removePipeSocket() { 10 | return new Promise(resolve => { 11 | fs.unlink(kSocketPath, () => { 12 | resolve() 13 | }) 14 | }) 15 | } 16 | 17 | describe('Snippets', () => { 18 | describe('Snippets', () => { 19 | it('should get all files', async () => { 20 | const snippets = new Snippets() 21 | const obj = await snippets.getSnippets() 22 | 23 | expect(typeof obj).toBe('object') 24 | expect(Object.keys(obj).length).toBeGreaterThan(0) 25 | 26 | const content = await snippets.getSnippet('get_active_handles', { 27 | a: 10, 28 | }) 29 | expect(content).toBeTruthy() 30 | }) 31 | }) 32 | 33 | describe('metric_collect', () => { 34 | it('should work', async () => { 35 | await removePipeSocket() 36 | 37 | const server = net.createServer() 38 | 39 | await new Promise(resolve => { 40 | server.listen(kSocketPath, () => { 41 | resolve() 42 | }) 43 | }) 44 | 45 | const code = await snippets.getSnippet('metric_collect', { 46 | interval: 500, 47 | messageSeperator: kMessageSeperator, 48 | socketPath: kSocketPath, 49 | }) 50 | 51 | eval(code) 52 | 53 | const socket: net.Socket = await new Promise(resolve => { 54 | server.once('connection', socket => { 55 | resolve(socket) 56 | }) 57 | }) 58 | 59 | const msg = await new Promise(resolve => { 60 | let msg = '' 61 | socket.once('data', data => { 62 | msg += data.toString('utf8') 63 | 64 | const index = msg.indexOf(kMessageSeperator) 65 | if (index >= 0) { 66 | resolve(JSON.parse(msg.slice(0, index))) 67 | } 68 | }) 69 | }) 70 | 71 | expect(msg).toEqual({ 72 | cpuUsage: { user: expect.anything(), system: expect.anything() }, 73 | memoryUsage: { 74 | rss: expect.anything(), 75 | heapTotal: expect.anything(), 76 | heapUsed: expect.anything(), 77 | external: expect.anything(), 78 | }, 79 | uv: { 80 | latency: expect.anything(), 81 | handle: expect.anything(), 82 | request: expect.anything(), 83 | }, 84 | }) 85 | 86 | socket.destroy() 87 | server.close() 88 | }) 89 | }) 90 | }) 91 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/output-flamegraph-d3.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/output-flamegraph-d3.js: emits a D3-based HTML page for the flame graph. 3 | * See lib/stackvis.js for interface details. 4 | */ 5 | 6 | var mod_assert = require('assert'); 7 | var mod_fs = require('fs'); 8 | var mod_path = require('path'); 9 | 10 | var mod_hogan = require('hogan.js'); 11 | var mod_vasync = require('vasync'); 12 | var mod_verror = require('verror'); 13 | 14 | var VError = mod_verror.VError; 15 | 16 | exports.emit = function emitIcicleData(args, callback) 17 | { 18 | mod_assert.ok(args.stacks && args.stacks.constructor && 19 | args.stacks.constructor.name == 'StackSet', 20 | 'required "stacks" argument must be a StackSet'); 21 | mod_assert.ok(args.output && args.output.write && 22 | typeof (args.output.write) == 'function', 23 | 'required "output" argument must be a function'); 24 | mod_assert.ok(args.log, 'required "log" argument must be a logger'); 25 | 26 | var stacks = args.stacks; 27 | var output = args.output; 28 | var tree = {}; 29 | var filecontents = {}; 30 | 31 | stacks.eachStackByStack(function (frames, count) { 32 | var subtree = tree; 33 | var node, i; 34 | 35 | for (i = 0; i < frames.length; i++) { 36 | if (!subtree.hasOwnProperty(frames[i])) 37 | subtree[frames[i]] = { 38 | svUnique: 0, 39 | svTotal: 0, 40 | svChildren: {} 41 | }; 42 | 43 | node = subtree[frames[i]]; 44 | node.svTotal += count; 45 | subtree = node.svChildren; 46 | } 47 | 48 | node.svUnique += count; 49 | }); 50 | 51 | tree = { 52 | '': { 53 | svUnique: 0, 54 | svTotal: Object.keys(tree).reduce( 55 | function (p, c) { return (p + tree[c].svTotal); }, 0), 56 | svChildren: tree 57 | } 58 | }; 59 | 60 | mod_vasync.forEachParallel({ 61 | 'inputs': [ 'icicle.css', 'icicle.js', 'icicle.htm', 'd3.v2.js' ], 62 | 'func': function (filename, stepcb) { 63 | var path = mod_path.join(__dirname, '../share', filename); 64 | var key = filename.replace(/\./g, '_'); 65 | mod_fs.readFile(path, function (err, contents) { 66 | if (err) 67 | err = new VError(err, 'failed to load "%s"', 68 | filename); 69 | else 70 | filecontents[key] = contents.toString('utf8'); 71 | stepcb(err); 72 | }); 73 | } 74 | }, function (err) { 75 | if (err) { 76 | callback(err); 77 | return; 78 | } 79 | 80 | var compiled, rendered; 81 | 82 | filecontents['title'] = 'Flame graph'; 83 | filecontents['rawdata'] = JSON.stringify(tree, null, '\t'); 84 | compiled = mod_hogan.compile(filecontents['icicle_htm']); 85 | rendered = compiled.render(filecontents); 86 | output.write(rendered); 87 | callback(); 88 | }); 89 | }; 90 | -------------------------------------------------------------------------------- /packages/diat/src/utils.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path' 2 | import * as semver from 'semver' 3 | import fetch from 'node-fetch' 4 | import * as os from 'os' 5 | 6 | const kPublicIpPlaceholder = '0.0.0.0' 7 | 8 | export function getAbsolutePath(file: string): string { 9 | if (path.isAbsolute(file)) { 10 | return file 11 | } 12 | 13 | return path.resolve(process.cwd(), file) 14 | } 15 | 16 | export function getDefaultFileName(pid: number, type: string): string { 17 | let pidPart = '' 18 | 19 | if (pid) { 20 | pidPart = `_${pid}` 21 | } 22 | return path.resolve(process.cwd(), `./diat${pidPart}_${Date.now()}${type}`) 23 | } 24 | 25 | export function isNodeVersionLE8( 26 | version: string | null = process.version 27 | ): boolean { 28 | if (!version) { 29 | return false 30 | } 31 | return semver.satisfies(version, '<9') 32 | } 33 | 34 | const kDevtoolsUrl = `devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws=` 35 | 36 | export async function getFirstSessionURL( 37 | host: string, 38 | port: number, 39 | ip: string | null, 40 | getProxyUrl?: (url: string) => Promise 41 | ): Promise { 42 | ip = ip || kPublicIpPlaceholder 43 | const res = await fetch(`http://${host}:${port}/json`) 44 | const meta = await res.json() 45 | 46 | // 在低版本上,可能没有这个数据 47 | if (!Array.isArray(meta) || meta.length === 0) { 48 | throw new Error('invalid meta') 49 | } 50 | 51 | const { id } = meta[0] 52 | 53 | if (!id) { 54 | throw new Error('invalid meta') 55 | } 56 | 57 | let addr = `${ip}:${port}/${id}` 58 | 59 | if (typeof getProxyUrl === 'function') { 60 | const proxyUrl = await getProxyUrl(addr) 61 | if (proxyUrl) { 62 | addr = proxyUrl 63 | } 64 | } 65 | 66 | return `${kDevtoolsUrl}${addr}` 67 | } 68 | 69 | export function getPublicIP(): Promise { 70 | const interfaces = os.networkInterfaces() 71 | 72 | if (Array.isArray(interfaces.en0) && interfaces.en0.length > 0) { 73 | const { en0 } = interfaces 74 | const ipv4Item = en0.find((i) => i.family === 'IPv4') 75 | if (ipv4Item) { 76 | return Promise.resolve(ipv4Item.address) 77 | } 78 | } 79 | 80 | return Promise.resolve(null) 81 | } 82 | 83 | export function parseInspectorUrl( 84 | wsUrl: string 85 | ): { address: string; host: string; port: number } | null { 86 | if (typeof wsUrl !== 'string') { 87 | return null 88 | } 89 | 90 | const ret = /ws\:\/\/(.+)\:(\d+)/.exec(wsUrl) 91 | 92 | if (!ret) { 93 | return null 94 | } 95 | 96 | const host = ret[1] 97 | const port = Number(ret[2]) 98 | const address = `${host}:${port}` 99 | return { 100 | host, 101 | port, 102 | address, 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /packages/diat/__tests__/get_active_handles.spec.ts: -------------------------------------------------------------------------------- 1 | import { Comm } from '../src/Comm' 2 | import { createTestProcess, kTimeout } from './utils' 3 | 4 | describe('get_active_handles', () => { 5 | let child: any 6 | let comm: Comm | null 7 | 8 | const clear = async () => { 9 | if (comm) { 10 | await comm.disconnect() 11 | comm = null 12 | } 13 | 14 | if (child) { 15 | child.kill() 16 | child = null 17 | } 18 | } 19 | 20 | afterEach(async () => { 21 | await clear() 22 | }) 23 | 24 | it( 25 | 'should work with net.Socket and net.Server', 26 | async () => { 27 | child = (await createTestProcess('net_socket_server')).child 28 | comm = new Comm(child.pid, undefined) 29 | await comm.connect() 30 | 31 | const ret = await comm.run('get_active_handles') 32 | const result = JSON.parse(ret.content) 33 | expect(result.statistic['net.Server']).toBe(1) 34 | expect(result.statistic['net.Socket']).toBeGreaterThan(0) 35 | expect(result.extraInfos['net.Server']).toEqual([ 36 | { 37 | address: expect.anything(), 38 | connections: 1, 39 | }, 40 | ]) 41 | const socketInfo = result.extraInfos['net.Socket'] 42 | expect(socketInfo[socketInfo.length - 1]).toEqual({ 43 | localAddress: expect.anything(), 44 | localPort: expect.anything(), 45 | remoteAddress: expect.anything(), 46 | remotePort: expect.anything(), 47 | }) 48 | }, 49 | kTimeout 50 | ) 51 | 52 | it( 53 | 'should work with dgram.Socket', 54 | async () => { 55 | child = (await createTestProcess('udp_socket')).child 56 | comm = new Comm(child.pid, undefined) 57 | await comm.connect() 58 | 59 | const ret = await comm.run('get_active_handles') 60 | expect(ret.type).toBe('success') 61 | const result = JSON.parse(ret.content) 62 | expect(result.statistic['dgram.Socket']).toBe(1) 63 | const socketInfo = result.extraInfos['dgram.Socket'] 64 | expect(socketInfo).toEqual([ 65 | { 66 | address: { 67 | address: expect.anything(), 68 | family: expect.anything(), 69 | port: expect.anything(), 70 | }, 71 | }, 72 | ]) 73 | }, 74 | kTimeout 75 | ) 76 | 77 | // TODO(oyyd): This test will stop the process from exiting. 78 | it('should work with child_process', async () => { 79 | const key = 'child_process.ChildProcess' 80 | child = (await createTestProcess('childprocess')).child 81 | comm = new Comm(child.pid, undefined) 82 | await comm.connect() 83 | 84 | const ret = await comm.run('get_active_handles') 85 | expect(ret.type).toBe('success') 86 | // const result = JSON.parse(ret.content) 87 | // expect(result.statistic[key]).toBe(1); 88 | // expect(result.extraInfos[key]).toEqual([ 89 | // { 90 | // pid: expect.anything(), 91 | // }, 92 | // ]) 93 | }) 94 | }) 95 | -------------------------------------------------------------------------------- /packages/diat/__tests__/InspectorWorkerSession.spec.ts: -------------------------------------------------------------------------------- 1 | import * as inspector from 'inspector'; 2 | import * as util from 'util'; 3 | import * as path from 'path'; 4 | import { InspectorWorkerSession } from '../src/InspectorWorkerSession'; 5 | import { hasWorker, kTimeout } from './utils'; 6 | 7 | describe('InspectorWorkerSession', () => { 8 | const workers: any[] = []; 9 | const sessionId = '1'; 10 | let session: inspector.Session; 11 | let post: any; 12 | let workerSession: InspectorWorkerSession; 13 | 14 | if (!hasWorker()) { 15 | it('(skipped)', () => {}); 16 | return; 17 | } 18 | 19 | const thread = require('worker_threads'); 20 | 21 | beforeAll(async () => { 22 | for (let i = 0; i < 2; i += 1) { 23 | const worker = new thread.Worker( 24 | path.resolve(__dirname, './test_process/thread_worker.js') 25 | ) 26 | await new Promise((resolve) => { 27 | worker.once('online', () => { 28 | resolve() 29 | }) 30 | }) 31 | worker.unref(); 32 | workers.push(worker); 33 | } 34 | }); 35 | 36 | afterAll(async () => { 37 | // NOTE Workers won't exit so we add '--forceExit' to jest. 38 | for (const worker of workers) { 39 | worker.terminate() 40 | } 41 | }); 42 | 43 | beforeEach(async () => { 44 | session = new inspector.Session(); 45 | session.connect(); 46 | post = util.promisify(session.post.bind(session)); 47 | workerSession = new InspectorWorkerSession({ 48 | comm: { 49 | post, 50 | event: ({ 51 | removeListener: (name, cb) => { 52 | return session.removeListener(name, cb) 53 | }, 54 | addListener: (name, cb) => { 55 | return session.addListener(name, (msg) => { 56 | cb(msg.params) 57 | }) 58 | } 59 | } as any) 60 | }, 61 | host: '0.0.0.0', 62 | port: 0, 63 | sessionId 64 | }); 65 | 66 | await new Promise(resolve => { 67 | session.post( 68 | 'NodeWorker.enable', 69 | { 70 | waitForDebuggerOnStart: false 71 | }, 72 | resolve 73 | ); 74 | }); 75 | }); 76 | 77 | afterEach(async () => { 78 | await workerSession.destroy(); 79 | await post('NodeWorker.disable'); 80 | session.disconnect(); 81 | }); 82 | 83 | it('should work', async () => { 84 | const ret = await workerSession.inspect(); 85 | 86 | expect(ret).toEqual({ 87 | host: expect.anything(), 88 | port: expect.anything(), 89 | address: expect.anything(), 90 | family: expect.anything() 91 | }); 92 | }); 93 | 94 | it('should emit close when detached', async () => { 95 | await workerSession.inspect(); 96 | 97 | const p = new Promise(resolve => { 98 | workerSession.once('close', () => { 99 | resolve(); 100 | }); 101 | }); 102 | 103 | await post('NodeWorker.detach', { 104 | sessionId 105 | }); 106 | 107 | await p; 108 | }); 109 | }); 110 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/input-dtrace.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/input-dtrace.js: reads output from a DTrace profiling script, which emits 3 | * stanzas that look like this: 4 | * 5 | * prog`foo+0x8 6 | * prog`main+0x21 7 | * prog`_start+0x80 8 | * 14 9 | * 10 | * This examples shows that that particular stacktrace was seen 14 times. You 11 | * can generate such output with: 12 | * 13 | * # dtrace -o stacks.out \ 14 | * -n 'profile-97/execname == "myprogram"/{ @[ustack()] = count(); }' 15 | */ 16 | 17 | var mod_util = require('util'); 18 | var mod_events = require('events'); 19 | 20 | var mod_carrier = require('carrier'); 21 | 22 | /* We always ignore the first 3 lines. */ 23 | var NHEADERLINES = 3; 24 | 25 | exports.reader = DTraceStreamReader; 26 | 27 | function DTraceStreamReader(input, log) 28 | { 29 | this.dsr_log = log; 30 | this.dsr_linenum = 0; 31 | this.dsr_stack = []; 32 | this.dsr_carrier = mod_carrier.carry(input); 33 | this.dsr_carrier.on('line', this.onLine.bind(this)); 34 | this.dsr_carrier.on('end', this.onEnd.bind(this)); 35 | 36 | mod_events.EventEmitter.call(this); 37 | } 38 | 39 | mod_util.inherits(DTraceStreamReader, mod_events.EventEmitter); 40 | 41 | DTraceStreamReader.prototype.onLine = function (line) 42 | { 43 | /* The first three lines are always ignored. */ 44 | if (++this.dsr_linenum <= NHEADERLINES) 45 | return; 46 | 47 | var match = /^\s+(\d+)\s*$/.exec(line); 48 | if (match) { 49 | if (this.dsr_stack.length === 0) { 50 | this.dsr_log.warn('line ' + this.dsr_linenum + 51 | ': found count with no stack'); 52 | return; 53 | } 54 | 55 | this.emit('stack', this.dsr_stack, parseInt(match[1], 10)); 56 | this.dsr_stack = []; 57 | return; 58 | } 59 | 60 | /* 61 | * In general, lines may have leading or trailing whitespace and the 62 | * following components: 63 | * 64 | * module`function+offset 65 | * 66 | * We try to avoid assuming too much about the form in order to support 67 | * various annotations provided by ustack helpers, but we want to strip 68 | * off the offset. 69 | */ 70 | var frame = line; 71 | frame = frame.replace(/^\s+/, ''); 72 | frame = frame.replace(/\s+$/, ''); 73 | /* JSSTYLED */ 74 | frame = frame.replace(/\+.*/, ''); 75 | 76 | /* 77 | * Remove both function and template parameters from demangled C++ 78 | * frames, but skip the first two characters because they're used by the 79 | * Node.js ustack helper as separators. 80 | */ 81 | /* JSSTYLED */ 82 | frame = frame.replace(/(..)[(<].*/, '$1'); 83 | 84 | if (line.length === 0) { 85 | if (this.dsr_stack.length !== 0) 86 | this.dsr_log.warn('line ' + this.dsr_linenum + 87 | ': unexpected blank line'); 88 | return; 89 | } 90 | 91 | this.dsr_stack.unshift(frame); 92 | }; 93 | 94 | DTraceStreamReader.prototype.onEnd = function () 95 | { 96 | if (this.dsr_stack.length !== 0) 97 | this.dsr_log.warn('line ' + this.dsr_linenum + 98 | ': unexpected end of stream'); 99 | 100 | this.emit('end'); 101 | }; 102 | -------------------------------------------------------------------------------- /packages/node-inspect/tools/eslint-rules/required-modules.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview Require usage of specified node modules. 3 | * @author Rich Trott 4 | */ 5 | 'use strict'; 6 | 7 | var path = require('path'); 8 | 9 | //------------------------------------------------------------------------------ 10 | // Rule Definition 11 | //------------------------------------------------------------------------------ 12 | 13 | module.exports = function(context) { 14 | // trim required module names 15 | var requiredModules = context.options; 16 | 17 | var foundModules = []; 18 | 19 | // if no modules are required we don't need to check the CallExpressions 20 | if (requiredModules.length === 0) { 21 | return {}; 22 | } 23 | 24 | /** 25 | * Function to check if a node is a string literal. 26 | * @param {ASTNode} node The node to check. 27 | * @returns {boolean} If the node is a string literal. 28 | */ 29 | function isString(node) { 30 | return node && node.type === 'Literal' && typeof node.value === 'string'; 31 | } 32 | 33 | /** 34 | * Function to check if a node is a require call. 35 | * @param {ASTNode} node The node to check. 36 | * @returns {boolean} If the node is a require call. 37 | */ 38 | function isRequireCall(node) { 39 | return node.callee.type === 'Identifier' && node.callee.name === 'require'; 40 | } 41 | 42 | /** 43 | * Function to check if a node has an argument that is a required module and 44 | * return its name. 45 | * @param {ASTNode} node The node to check 46 | * @returns {undefined|String} required module name or undefined 47 | */ 48 | function getRequiredModuleName(node) { 49 | var moduleName; 50 | 51 | // node has arguments and first argument is string 52 | if (node.arguments.length && isString(node.arguments[0])) { 53 | var argValue = path.basename(node.arguments[0].value.trim()); 54 | 55 | // check if value is in required modules array 56 | if (requiredModules.indexOf(argValue) !== -1) { 57 | moduleName = argValue; 58 | } 59 | } 60 | 61 | return moduleName; 62 | } 63 | 64 | return { 65 | 'CallExpression': function(node) { 66 | if (isRequireCall(node)) { 67 | var requiredModuleName = getRequiredModuleName(node); 68 | 69 | if (requiredModuleName) { 70 | foundModules.push(requiredModuleName); 71 | } 72 | } 73 | }, 74 | 'Program:exit': function(node) { 75 | if (foundModules.length < requiredModules.length) { 76 | var missingModules = requiredModules.filter( 77 | function(module) { 78 | return foundModules.indexOf(module === -1); 79 | } 80 | ); 81 | missingModules.forEach(function(moduleName) { 82 | context.report( 83 | node, 84 | 'Mandatory module "{{moduleName}}" must be loaded.', 85 | { moduleName: moduleName } 86 | ); 87 | }); 88 | } 89 | } 90 | }; 91 | }; 92 | 93 | module.exports.schema = { 94 | 'type': 'array', 95 | 'additionalItems': { 96 | 'type': 'string' 97 | }, 98 | 'uniqueItems': true 99 | }; 100 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/exec.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('examples/alive.js', (t) => { 7 | const cli = startCLI(['examples/alive.js']); 8 | 9 | function onFatal(error) { 10 | cli.quit(); 11 | throw error; 12 | } 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => cli.command('exec [typeof heartbeat, typeof process.exit]')) 17 | .then(() => { 18 | t.match(cli.output, '[ \'function\', \'function\' ]', 'works w/o paren'); 19 | }) 20 | .then(() => cli.command('repl')) 21 | .then(() => { 22 | t.match( 23 | cli.output, 24 | 'Press Ctrl + C to leave debug repl\n> ', 25 | 'shows hint for how to leave repl'); 26 | t.notMatch(cli.output, 'debug>', 'changes the repl style'); 27 | }) 28 | .then(() => cli.command('[typeof heartbeat, typeof process.exit]')) 29 | .then(() => cli.waitFor(/function/)) 30 | .then(() => cli.waitForPrompt()) 31 | .then(() => { 32 | t.match( 33 | cli.output, 34 | '[ \'function\', \'function\' ]', 'can evaluate in the repl'); 35 | t.match(cli.output, /> $/); 36 | }) 37 | .then(() => cli.command('require')) 38 | .then(() => cli.waitFor(/require/)) 39 | .then(() => cli.waitForPrompt()) 40 | .then(() => { 41 | t.match( 42 | cli.output, 43 | '[Function: require]', 'can access "require"'); 44 | t.match(cli.output, /> $/); 45 | }) 46 | .then(() => cli.command('global')) 47 | .then(() => cli.waitFor(/clearTimeout/)) 48 | .then(() => cli.waitForPrompt()) 49 | .then(() => { 50 | t.match( 51 | cli.output, 52 | 'clearTimeout: [Function: clearTimeout]', 53 | 'show function properties correctly'); 54 | t.match(cli.output, /> $/); 55 | }) 56 | .then(() => cli.ctrlC()) 57 | .then(() => cli.waitFor(/debug> $/)) 58 | .then(() => cli.command('exec("[typeof heartbeat, typeof process.exit]")')) 59 | .then(() => { 60 | t.match(cli.output, '[ \'function\', \'function\' ]', 'works w/ paren'); 61 | }) 62 | .then(() => cli.command('cont')) 63 | .then(() => cli.command('exec [typeof heartbeat, typeof process.exit]')) 64 | .then(() => { 65 | t.match( 66 | cli.output, 67 | '[ \'undefined\', \'function\' ]', 68 | 'non-paused exec can see global but not module-scope values'); 69 | }) 70 | .then(() => cli.quit()) 71 | .then(null, onFatal); 72 | }); 73 | 74 | test('exec .scope', (t) => { 75 | const cli = startCLI(['examples/backtrace.js']); 76 | 77 | function onFatal(error) { 78 | cli.quit(); 79 | throw error; 80 | } 81 | 82 | return cli.waitForInitialBreak() 83 | .then(() => cli.waitForPrompt()) 84 | .then(() => cli.stepCommand('c')) 85 | .then(() => cli.command('exec .scope')) 86 | .then(() => { 87 | t.match( 88 | cli.output, 89 | '\'moduleScoped\'', 'displays closure from module body'); 90 | t.match(cli.output, '\'a\'', 'displays local / function arg'); 91 | t.match(cli.output, '\'l1\'', 'displays local scope'); 92 | t.notMatch(cli.output, '\'encodeURIComponent\'', 'omits global scope'); 93 | }) 94 | .then(() => cli.quit()) 95 | .then(null, onFatal); 96 | }); 97 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/scripts.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('list scripts', (t) => { 9 | const script = Path.join('examples', 'three-lines.js'); 10 | const cli = startCLI([script]); 11 | 12 | function onFatal(error) { 13 | cli.quit(); 14 | throw error; 15 | } 16 | 17 | return cli.waitForInitialBreak() 18 | .then(() => cli.waitForPrompt()) 19 | .then(() => cli.command('scripts')) 20 | .then(() => { 21 | t.match( 22 | cli.output, 23 | /^\* \d+: examples(?:\/|\\)three-lines\.js/, 24 | 'lists the user script'); 25 | t.notMatch( 26 | cli.output, 27 | /\d+: buffer\.js /, 28 | 'omits node-internal scripts'); 29 | }) 30 | .then(() => cli.command('scripts(true)')) 31 | .then(() => { 32 | t.match( 33 | cli.output, 34 | /\* \d+: examples(?:\/|\\)three-lines\.js/, 35 | 'lists the user script'); 36 | t.match( 37 | cli.output, 38 | /\d+: buffer\.js /, 39 | 'includes node-internal scripts'); 40 | }) 41 | .then(() => cli.quit()) 42 | .then(null, onFatal); 43 | }); 44 | 45 | test('get scripts', (t) => { 46 | const script = Path.join('examples', 'three-lines.js'); 47 | const cli = startCLI([script]); 48 | 49 | function onFatal(error) { 50 | cli.quit(); 51 | throw error; 52 | } 53 | 54 | return cli.waitForInitialBreak() 55 | .then(() => cli.waitForPrompt()) 56 | .then(() => cli.command('getScripts()')) 57 | .then(() => { 58 | t.match( 59 | cli.output, 60 | /url.+examples(?:\/|\\)three-lines\.js/, 61 | 'return the url of user scripts'); 62 | t.match( 63 | cli.output, 64 | /scriptId.+\d+/, 65 | 'return the scriptId of user scripts'); 66 | }) 67 | .then(() => cli.quit()) 68 | .then(null, onFatal); 69 | }); 70 | 71 | test('source', (t) => { 72 | const script = Path.join('examples', 'alive.js'); 73 | const cli = startCLI([script]); 74 | let scriptId = null; 75 | 76 | function onFatal(error) { 77 | cli.quit(); 78 | throw error; 79 | } 80 | 81 | return cli.waitForInitialBreak() 82 | .then(() => cli.waitForPrompt()) 83 | .then(() => cli.command('scripts')) 84 | .then(() => { 85 | t.match( 86 | cli.output, 87 | /\* \d+: examples(?:\/|\\)alive\.js/, 88 | 'get source of the script'); 89 | const ret = /\* (\d+): examples(?:\/|\\)alive\.js/.exec(cli.output); 90 | return ret[1]; 91 | }) 92 | .then((id) => { 93 | scriptId = id; 94 | return cli.command(`source("${scriptId}")`); 95 | }) 96 | .then(() => { 97 | t.match(cli.output, /1 let x = 0/, 'print source of the script'); 98 | }) 99 | .then(() => cli.command(`source(${scriptId})`)) 100 | .then(() => { 101 | t.match(cli.output, /1 let x = 0/, 'accept a number as scriptId'); 102 | }) 103 | .then(() => cli.command('source("alive.js")')) 104 | .then(() => { 105 | t.match(cli.output, /1 let x = 0/, 'print source of the script'); 106 | }) 107 | .then(() => cli.quit()) 108 | .then(null, onFatal); 109 | }); 110 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/input-perf.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/input-perf.js: reads output from a perf profiling script, which emits 3 | * stanzas that look like this: 4 | * 5 | * foo 15150 10062.190770: cycles: 6 | * 400675 bar (/tmp/stackvis/foo) 7 | * 400603 foo (/tmp/stackvis/foo) 8 | * 40071f main (/tmp/stackvis/foo) 9 | * 7fb3db1bf76d __libc_start_main (/lib/x86_64-linux-gnu/libc-2.15.so) 10 | * 11 | * You can generate such output with: 12 | * 13 | * # perf record -F 997 -g ./myprogram 14 | * # perf script > perf.out 15 | */ 16 | 17 | var mod_util = require('util'); 18 | var mod_events = require('events'); 19 | 20 | var mod_carrier = require('carrier'); 21 | 22 | exports.reader = PerfStreamReader; 23 | 24 | function PerfStreamReader(input, log) 25 | { 26 | this.dsr_log = log; 27 | this.dsr_linenum = 0; 28 | this.dsr_prefix = ''; 29 | this.dsr_stack = []; 30 | this.dsr_carrier = mod_carrier.carry(input); 31 | this.dsr_carrier.on('line', this.onLine.bind(this)); 32 | this.dsr_carrier.on('end', this.onEnd.bind(this)); 33 | 34 | mod_events.EventEmitter.call(this); 35 | } 36 | 37 | mod_util.inherits(PerfStreamReader, mod_events.EventEmitter); 38 | 39 | PerfStreamReader.prototype.onLine = function (line) 40 | { 41 | ++this.dsr_linenum; 42 | 43 | /* Lines beginning with # are always ignored. */ 44 | if (/^#/.exec(line)) 45 | return; 46 | 47 | /* Get process name from summary line, to use as prefix */ 48 | var match = /(^\w+)\s+/.exec(line); 49 | if (match) { 50 | this.dsr_prefix = match[1]; 51 | return; 52 | } 53 | 54 | /* 55 | * In general, lines may have leading or trailing whitespace and the 56 | * following components: 57 | * 58 | * loc function (module) 59 | * 60 | * We try to avoid assuming too much about the form in order to support 61 | * various annotations provided by ustack helpers. 62 | */ 63 | var frame = line; 64 | frame = frame.replace(/^\s+/, ''); 65 | frame = frame.replace(/\s+$/, ''); 66 | 67 | if (frame.length === 0) { 68 | if (this.dsr_stack.length === 0) { 69 | this.dsr_log.warn('line ' + this.dsr_linenum + 70 | ': found empty line with no stack'); 71 | return; 72 | } 73 | 74 | this.emit('stack', this.dsr_stack, 1); 75 | this.dsr_prefix = ''; 76 | this.dsr_stack = []; 77 | return; 78 | } 79 | 80 | frame = frame.replace(/^\w+ /, ''); 81 | frame = frame.replace(/ \(\S+\)$/, ''); 82 | 83 | /* 84 | * Remove both function and template parameters from demangled C++ 85 | * frames, but skip the first two characters because they're used by the 86 | * Node.js ustack helper as separators. 87 | */ 88 | /* JSSTYLED */ 89 | frame = frame.replace(/(..)[(<].*/, '$1'); 90 | 91 | if (line.length === 0) { 92 | if (this.dsr_stack.length !== 0) 93 | this.dsr_log.warn('line ' + this.dsr_linenum + 94 | ': unexpected blank line'); 95 | return; 96 | } 97 | 98 | /* Add prefix */ 99 | if (this.dsr_prefix.length > 0) { 100 | frame = this.dsr_prefix + '`' + frame; 101 | } 102 | 103 | this.dsr_stack.unshift(frame); 104 | }; 105 | 106 | PerfStreamReader.prototype.onEnd = function () 107 | { 108 | if (this.dsr_stack.length !== 0) 109 | this.dsr_log.warn('line ' + this.dsr_linenum + 110 | ': unexpected end of stream'); 111 | 112 | this.emit('end'); 113 | }; 114 | -------------------------------------------------------------------------------- /packages/diat/__tests__/out.perftext: -------------------------------------------------------------------------------- 1 | node 20616 2285.637821: 1 cycles:u: 2 | 7f34447c2ce7 epoll_pwait+0x87 (/lib/x86_64-linux-gnu/libc-2.24.so) 3 | 4 | node 20616 2285.637872: 1 cycles:u: 5 | 7f34447c2ce7 epoll_pwait+0x87 (/lib/x86_64-linux-gnu/libc-2.24.so) 6 | 7 | node 20616 2285.637916: 25 cycles:u: 8 | 7f34447c2ce7 epoll_pwait+0x87 (/lib/x86_64-linux-gnu/libc-2.24.so) 9 | 10 | node 20616 2285.637958: 752 cycles:u: 11 | 7f34447c2ce7 epoll_pwait+0x87 (/lib/x86_64-linux-gnu/libc-2.24.so) 12 | 13 | node 20616 2285.638000: 23144 cycles:u: 14 | 7f34447c2ce7 epoll_pwait+0x87 (/lib/x86_64-linux-gnu/libc-2.24.so) 15 | 16 | node 20616 2285.638051: 709910 cycles:u: 17 | b01223 _ZN2v811HandleScope10InitializeEPNS_7IsolateE+0x63 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 18 | 9d4a49 _ZN4node12_GLOBAL__N_19TimerWrap9OnTimeoutEP10uv_timer_s+0x29 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 19 | a6ba25 uv__run_timers+0x35 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 20 | a6fd4c uv_run+0x8c (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 21 | 904595 _ZN4node5StartEPN2v87IsolateEPNS_11IsolateDataERKSt6vectorISsSaISsEES9_+0x565 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 22 | 90281f _ZN4node5StartEiPPc+0x48f (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 23 | 7f34446fa2e1 __libc_start_main+0xf1 (/lib/x86_64-linux-gnu/libc-2.24.so) 24 | d81d972d8d48e024 [unknown] ([unknown]) 25 | 26 | node 20616 2289.642973: 18265529 cycles:u: 27 | 195f8e5a3c86 BytecodeHandler:LogicalNot+0x6 (/tmp/perf-20616.map) 28 | 195f8e5118d5 Builtin:InterpreterEntryTrampoline+0x275 (/tmp/perf-20616.map) 29 | 195f8e50ee75 Builtin:JSEntryTrampoline+0x75 (/tmp/perf-20616.map) 30 | 195f8e5092c1 Stub:JSEntryStub+0xa1 (/tmp/perf-20616.map) 31 | e9f333 _ZN2v88internal9Execution4CallEPNS0_7IsolateENS0_6HandleINS0_6ObjectEEES6_iPS6_+0x103 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 32 | b26169 _ZN2v88Function4CallENS_5LocalINS_7ContextEEENS1_INS_5ValueEEEiPS5_+0x179 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 33 | 8fb718 _ZN4node20InternalMakeCallbackEPNS_11EnvironmentEN2v85LocalINS2_6ObjectEEENS3_INS2_8FunctionEEEiPNS3_INS2_5ValueEEENS_13async_contextE+0x1a8 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 34 | 8c35c9 _ZN4node9AsyncWrap12MakeCallbackEN2v85LocalINS1_8FunctionEEEiPNS2_INS1_5ValueEEE+0x89 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 35 | 9d4a84 _ZN4node12_GLOBAL__N_19TimerWrap9OnTimeoutEP10uv_timer_s+0x64 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 36 | a6ba25 uv__run_timers+0x35 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 37 | a6fd4c uv_run+0x8c (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 38 | 904595 _ZN4node5StartEPN2v87IsolateEPNS_11IsolateDataERKSt6vectorISsSaISsEES9_+0x565 (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 39 | 90281f _ZN4node5StartEiPPc+0x48f (/data00/home/ouyangyadong/.nvm/versions/node/v10.17.0/bin/node) 40 | 7f34446fa2e1 __libc_start_main+0xf1 (/lib/x86_64-linux-gnu/libc-2.24.so) 41 | d81d972d8d48e024 [unknown] ([unknown]) 42 | 43 | 44 | -------------------------------------------------------------------------------- /packages/diat/src/InspectorWorker.ts: -------------------------------------------------------------------------------- 1 | import { InspectorWorkerSession } from './InspectorWorkerSession' 2 | import { IComm } from './Types' 3 | 4 | interface IInspectorWorker { 5 | host: string 6 | port: number 7 | } 8 | 9 | const kDefaultConfig: IInspectorWorker = { 10 | host: '0.0.0.0', 11 | port: 0, 12 | } 13 | 14 | export interface IWorkerAttachInfo { 15 | sessionId: string 16 | workerInfo: { 17 | workerId: string 18 | type: string 19 | title: string 20 | url: string 21 | } 22 | waitingForDebugger: boolean 23 | } 24 | 25 | const kWaitWorkerAttaching = 500 26 | 27 | function wait(t) { 28 | return new Promise(resolve => { 29 | setTimeout(() => { 30 | resolve() 31 | }, t) 32 | }) 33 | } 34 | 35 | export class InspectorWorker { 36 | private comm: IComm 37 | private config: IInspectorWorker 38 | private sessionMap: Map = new Map() 39 | 40 | constructor(comm: IComm, config: Partial = {}) { 41 | this.comm = comm 42 | this.config = Object.assign({}, kDefaultConfig, config) 43 | } 44 | 45 | destroy = async () => { 46 | const { post } = this.comm 47 | this.removeAllSessions() 48 | await post('NodeWorker.disable', {}) 49 | } 50 | 51 | getWorkers = async (): Promise => { 52 | // TODO timeout for unsupported versions of Node.js 53 | const { event, post } = this.comm 54 | const waitForDebuggerOnStart = false 55 | 56 | const infos: IWorkerAttachInfo[] = [] 57 | 58 | const onWorker = (info: any) => { 59 | infos.push(info) 60 | } 61 | 62 | event.addListener('NodeWorker.attachedToWorker', onWorker) 63 | 64 | // Assume that all workers will attach before 'NodeWorker.enable' resolved. 65 | await post('NodeWorker.enable', { 66 | waitForDebuggerOnStart, 67 | }) 68 | 69 | await wait(kWaitWorkerAttaching) 70 | 71 | event.removeListener('NodeWorker.attachedToWorker', onWorker) 72 | 73 | return infos 74 | } 75 | 76 | private removeSession = async (sessionId: string) => { 77 | const session = this.sessionMap.get(sessionId) 78 | this.sessionMap.delete(sessionId) 79 | 80 | if (session) { 81 | await session.destroy() 82 | } 83 | } 84 | 85 | private removeAllSessions = async () => { 86 | const keys = this.sessionMap.keys() 87 | let it = keys.next() 88 | 89 | while (!it.done) { 90 | const sessionId = it.value 91 | const session = this.sessionMap.get(sessionId) 92 | if (session) { 93 | session.destroy() 94 | } 95 | it = keys.next() 96 | } 97 | 98 | this.sessionMap.clear() 99 | } 100 | 101 | createWorkerSession = async ( 102 | sessionId: string 103 | ): Promise<{ host: string; port: number }> => { 104 | if (!sessionId) { 105 | throw new Error(`invalid session id: ${sessionId}`) 106 | } 107 | if (this.sessionMap.get(sessionId)) { 108 | throw new Error(`the session id: ${sessionId} is already exists`) 109 | } 110 | const { host, port } = this.config 111 | const session = new InspectorWorkerSession({ 112 | sessionId, 113 | comm: this.comm, 114 | host, 115 | port, 116 | }) 117 | this.sessionMap.set(sessionId, session) 118 | 119 | session.once('close', () => { 120 | this.removeSession(sessionId) 121 | }) 122 | 123 | try { 124 | const addr = await session.inspect() 125 | return addr 126 | } catch (err) { 127 | this.removeSession(sessionId) 128 | throw err 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /packages/live-inspector/__tests__/Communication.spec.ts: -------------------------------------------------------------------------------- 1 | import { Communication } from '../src/Communication'; 2 | import { createTestProcess } from './utils'; 3 | 4 | describe('Communication', () => { 5 | describe('connect', () => { 6 | it('should return null if connecting timeout', async () => { 7 | const child = await createTestProcess(); 8 | const comm = new Communication({ 9 | pid: child.pid, 10 | getBasicInfoTimeout: 1 11 | }); 12 | 13 | const ret = await comm.connect(); 14 | expect(ret).toBe(null); 15 | 16 | await comm.disconnect(); 17 | child.kill('SIGKILL'); 18 | }); 19 | 20 | it('should release the ws', async () => { 21 | const child = await createTestProcess(); 22 | const comm = new Communication({ 23 | pid: child.pid 24 | }); 25 | 26 | const ret = await comm.connect(); 27 | expect(ret).toBeTruthy(); 28 | 29 | await comm.releaseWs(); 30 | expect(() => comm.post('Debugger.enable', {}, null)).toThrow('connect()'); 31 | 32 | const ret2 = await comm.connect(); 33 | expect(ret2).toBeTruthy(); 34 | 35 | await comm.disconnect(); 36 | child.kill('SIGKILL'); 37 | }); 38 | }); 39 | 40 | describe('basic', () => { 41 | let comm: Communication; 42 | let child; 43 | 44 | beforeEach(async () => { 45 | child = await createTestProcess(); 46 | comm = new Communication({ 47 | pid: child.pid 48 | }); 49 | }); 50 | 51 | afterEach(async () => { 52 | await comm.disconnect(); 53 | child.kill('SIGKILL'); 54 | }); 55 | 56 | it('should post', async () => { 57 | const ret = await comm.connect(); 58 | expect(ret).toEqual({ 59 | pid: expect.anything(), 60 | version: expect.anything() 61 | }); 62 | await new Promise(resolve => { 63 | comm.post('Debugger.enable', null, (err, result) => { 64 | expect(err).toBeFalsy(); 65 | expect(result).toBeTruthy(); 66 | resolve(); 67 | }); 68 | }); 69 | }); 70 | 71 | it('should execCode', async () => { 72 | await comm.connect(); 73 | const ret = await comm.execCode(` 74 | (async () => { 75 | const value = await new Promise((resolve) => { 76 | setTimeout(() => { 77 | resolve('hello') 78 | }, 10) 79 | }) 80 | return value 81 | })() 82 | `); 83 | expect(ret).toEqual({ result: { type: 'string', value: 'hello' } }); 84 | }); 85 | 86 | it('should emit events', async () => { 87 | let getCalled = false; 88 | await comm.connect(); 89 | 90 | await new Promise((resolve, reject) => { 91 | comm.event.on('HeapProfiler.addHeapSnapshotChunk', data => { 92 | getCalled = true; 93 | }); 94 | 95 | comm.post('HeapProfiler.takeHeapSnapshot', null, err => { 96 | if (err) { 97 | reject(err); 98 | return; 99 | } 100 | resolve(); 101 | }); 102 | }); 103 | 104 | expect(getCalled).toBeTruthy(); 105 | }); 106 | 107 | it('should emit "close" event', async () => { 108 | await comm.connect(); 109 | 110 | const p = new Promise(resolve => { 111 | comm.event.once('LiveInspector.close', params => { 112 | resolve(params); 113 | }); 114 | }); 115 | 116 | child.kill(); 117 | 118 | const params = await p; 119 | expect(params).toBeTruthy(); 120 | expect(params).toEqual({ 121 | code: 1006, 122 | reason: '' 123 | }); 124 | }); 125 | }); 126 | }); 127 | -------------------------------------------------------------------------------- /packages/diat/src/TcpProxy.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net' 2 | import { EventEmitter as Event } from 'events' 3 | import { TcpDetector } from 'diat-live-inspector' 4 | import { DiatError } from './Error' 5 | import { logger } from './Logger' 6 | 7 | export interface ITcpProxyOptions { 8 | tcpProxyHost: string 9 | tcpProxyPort: number 10 | targetHost: string 11 | targetPort: number 12 | ignoreSocketErrors: boolean 13 | } 14 | 15 | const kDefaultOptions = { 16 | tcpProxyHost: '0.0.0.0', 17 | tcpProxyPort: 0, 18 | targetHost: '127.0.0.1', 19 | targetPort: 9229, 20 | ignoreSocketErrors: true, 21 | } 22 | 23 | export class TcpProxy extends Event { 24 | private options: ITcpProxyOptions 25 | private server: net.Server | null = null 26 | private tcpDetector: TcpDetector | null = null 27 | 28 | constructor(options_?: Partial) { 29 | super() 30 | this.options = Object.assign({}, kDefaultOptions, options_) 31 | } 32 | 33 | private proxySocket = (socket: net.Socket) => { 34 | const { ignoreSocketErrors, targetHost, targetPort } = this.options 35 | // The process will exit if we don't listen to "error". 36 | socket.on('error', err => { 37 | if (ignoreSocketErrors) { 38 | return 39 | } 40 | logger.warn(err) 41 | }) 42 | socket.pause() 43 | 44 | const clientSocket = net.connect(targetPort, targetHost) 45 | clientSocket.on('error', err => { 46 | logger.warn(err) 47 | }) 48 | 49 | const clientSocketConnect = () => { 50 | socket.resume() 51 | socket.pipe(clientSocket) 52 | clientSocket.pipe(socket) 53 | } 54 | 55 | const socketClose = () => { 56 | clientSocket.destroy() 57 | removeListeners() 58 | } 59 | 60 | const clientSocketClose = () => { 61 | socket.destroy() 62 | removeListeners() 63 | } 64 | 65 | const removeListeners = () => { 66 | clientSocket.removeListener('connect', clientSocketConnect) 67 | socket.removeListener('close', socketClose) 68 | clientSocket.removeListener('close', clientSocketClose) 69 | } 70 | 71 | clientSocket.on('connect', clientSocketConnect) 72 | socket.on('close', socketClose) 73 | clientSocket.on('close', clientSocketClose) 74 | } 75 | 76 | listen = async (): Promise<{ host: string; port: number }> => { 77 | const { tcpProxyHost, tcpProxyPort, targetPort } = this.options 78 | return new Promise((resolve, reject) => { 79 | this.tcpDetector = new TcpDetector(targetPort) 80 | this.tcpDetector.once('close', () => { 81 | this.destroy(true) 82 | }) 83 | 84 | const server = new net.Server(this.proxySocket) 85 | this.server = server 86 | 87 | server.listen(tcpProxyPort, tcpProxyHost, () => { 88 | const address = server.address() as any 89 | if (!address) { 90 | reject(new DiatError('failed to listen')) 91 | return 92 | } 93 | resolve( 94 | Object.assign( 95 | { 96 | host: tcpProxyHost, 97 | }, 98 | address 99 | ) 100 | ) 101 | }) 102 | }) 103 | } 104 | 105 | destroy = async (inspectorClosed: boolean = false): Promise => { 106 | if (!this.server) { 107 | return 108 | } 109 | 110 | this.emit('close', inspectorClosed) 111 | 112 | const { server, tcpDetector } = this 113 | this.server = null 114 | this.tcpDetector = null 115 | 116 | if (tcpDetector) { 117 | tcpDetector.destroy() 118 | } 119 | 120 | return new Promise(resolve => { 121 | server.close(() => { 122 | resolve() 123 | }) 124 | }) 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /packages/diat/src/Perf.ts: -------------------------------------------------------------------------------- 1 | import * as os from 'os' 2 | import * as childProcess from 'child_process' 3 | import * as path from 'path' 4 | 5 | interface IPerfRecordOptions { 6 | pid: number 7 | freq: number 8 | duration: number 9 | } 10 | 11 | interface IPerfScriptOptions { 12 | dataFile: string 13 | outputFile: string 14 | } 15 | 16 | async function checkPerf(): Promise { 17 | if (os.platform() !== 'linux') { 18 | return false 19 | } 20 | 21 | return new Promise((resolve) => { 22 | const child = childProcess.exec('perf --version') 23 | 24 | child.on('exit', (code) => { 25 | resolve(code === 0) 26 | }) 27 | }) 28 | } 29 | 30 | function getAbsolutePath(filename): string { 31 | if (path.isAbsolute(filename)) { 32 | return filename 33 | } 34 | 35 | return path.resolve(process.cwd(), filename) 36 | } 37 | 38 | function getFilename(pid: number): string { 39 | return `perf_${pid}.data` 40 | } 41 | 42 | function getOutputFileName(dataFile: string): string { 43 | return `${dataFile}.out` 44 | } 45 | 46 | export class Perf { 47 | static perfExist: any 48 | 49 | static hasPerf = async (): Promise => { 50 | if (Perf.perfExist === undefined) { 51 | Perf.perfExist = await checkPerf() 52 | } 53 | 54 | return Perf.perfExist 55 | } 56 | 57 | /** 58 | * perf record -F 1000 -p -g -m 512B -- sleep 5 59 | * -F, --freq= Profile at this frequency. 60 | * -p, --pid= Record events on existing process ID (comma separated list). 61 | * -g, --call-graph Do call-graph (stack chain/backtrace) recording. 62 | * -m, --mmap-pages= Number of mmap data pages. Must be a power of two. 63 | * -o, --output= Output file name. 64 | */ 65 | static record = async ( 66 | opts: Partial 67 | ): Promise => { 68 | if (!(await Perf.hasPerf())) { 69 | throw new Error('failed to find "perf"') 70 | } 71 | if (!opts.pid) { 72 | throw new Error('Perf.record expect "pid"') 73 | } 74 | const options = Object.assign( 75 | { 76 | freq: 1000, 77 | duration: 5000, 78 | }, 79 | opts 80 | ) 81 | 82 | const { pid, freq, duration } = options 83 | const durationInSeconds = Math.ceil(duration / 1000) 84 | 85 | if (durationInSeconds <= 0) { 86 | throw new Error(`Perf.record duration is too small, value: ${duration}`) 87 | } 88 | 89 | // TODO support choosing a thread 90 | // TODO add timeout 91 | return new Promise((resolve, reject) => { 92 | const filename = getFilename(pid as number) 93 | const cmd = `perf record -F ${freq} -p ${pid} -g -o ${filename} -- sleep ${durationInSeconds}` 94 | const child = childProcess.exec(cmd) 95 | 96 | child.stdout?.pipe(process.stdout) 97 | child.stderr?.pipe(process.stderr) 98 | 99 | child.on('exit', (code) => { 100 | if (code === 0) { 101 | resolve(getAbsolutePath(filename)) 102 | return 103 | } 104 | 105 | reject(new Error(`Perf.record failed, code: ${code}`)) 106 | }) 107 | }) 108 | } 109 | 110 | static script = async ( 111 | options: Partial 112 | ): Promise => { 113 | if (!(await Perf.hasPerf())) { 114 | throw new Error('failed to find "perf"') 115 | } 116 | 117 | const { dataFile } = options 118 | 119 | if (!dataFile) { 120 | throw new Error(`Perf.script receive invalid "dataFile": ${dataFile}`) 121 | } 122 | 123 | const outputFile = options.outputFile || getOutputFileName(dataFile) 124 | 125 | return new Promise((resolve, reject) => { 126 | const child = childProcess.exec( 127 | `perf script -i ${dataFile} > ${outputFile}` 128 | ) 129 | 130 | child.stdout?.pipe(process.stdout) 131 | child.stderr?.pipe(process.stderr) 132 | 133 | child.on('exit', (code) => { 134 | if (code === 0) { 135 | resolve(getAbsolutePath(outputFile)) 136 | return 137 | } 138 | 139 | reject(new Error(`Perf.script failed, code: ${code}`)) 140 | }) 141 | }) 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /packages/live-inspector/src/ProcessInspectPort.ts: -------------------------------------------------------------------------------- 1 | import * as WebSocket from 'ws'; 2 | import fetch from 'node-fetch'; 3 | import { wait } from './utils'; 4 | import { LiveInspectorError } from './Error'; 5 | import { IAddr } from './Types'; 6 | import { TcpDetector } from './TcpDetector'; 7 | 8 | interface IOptions { 9 | retryTimes: number; 10 | retryInterval: number; 11 | inspectPort: number; 12 | } 13 | 14 | const kDefaultOptions = { 15 | retryTimes: 2, 16 | retryInterval: 1000, 17 | inspectPort: 9229 18 | }; 19 | 20 | export function killPid(pid: number, signal: string) { 21 | try { 22 | process.kill(pid, signal); 23 | } catch (err) { 24 | throw new LiveInspectorError( 25 | `Failed to send signal to pid: ${pid}, is the process still alive?` 26 | ); 27 | } 28 | } 29 | 30 | export class ProcessInspectPort { 31 | private options: IOptions; 32 | 33 | constructor(options: Partial) { 34 | this.options = Object.assign({}, kDefaultOptions, options); 35 | } 36 | 37 | private getMeta = async (httpUrl: string): Promise => { 38 | const uri = `${httpUrl}/json`; 39 | const ret = await fetch(uri); 40 | const meta = await ret.json(); 41 | 42 | return meta; 43 | }; 44 | 45 | private verifyInspectorPort = async ( 46 | httpUrl: string, 47 | retryTime: number = 0 48 | ): Promise => { 49 | const { retryInterval, retryTimes } = this.options; 50 | 51 | try { 52 | const meta = await this.getMeta(httpUrl); 53 | if ( 54 | Array.isArray(meta) && 55 | meta.length > 0 && 56 | meta[0].webSocketDebuggerUrl 57 | ) { 58 | return meta[0].webSocketDebuggerUrl; 59 | } 60 | return null; 61 | } catch (err) {} 62 | 63 | if (retryTime >= retryTimes) { 64 | return null; 65 | } 66 | 67 | await wait(retryInterval); 68 | return this.verifyInspectorPort(httpUrl, retryTime + 1); 69 | }; 70 | 71 | private wsConnect = async (wsURI: string): Promise => { 72 | return new Promise((resolve, reject) => { 73 | const ws = new WebSocket(wsURI); 74 | 75 | const removeListener = () => { 76 | ws.removeListener('open', waitReady); 77 | ws.removeListener('close', waitFail); 78 | }; 79 | 80 | const waitReady = () => { 81 | removeListener(); 82 | resolve(ws); 83 | }; 84 | 85 | // TODO type 86 | const waitFail = (code, reason) => { 87 | removeListener(); 88 | reject( 89 | new LiveInspectorError( 90 | `failed to connect to the ws: ${wsURI}, code: ${code}, reason: ${reason}` 91 | ) 92 | ); 93 | }; 94 | 95 | ws.once('open', waitReady); 96 | ws.once('close', waitFail); 97 | }); 98 | }; 99 | 100 | /** 101 | * 让一个进程开启开启inspect端口 102 | * - 发送SIGUSR1信号 103 | * - (多次)试探9229端口是否接受ws请求 104 | */ 105 | public connectByPid = async (pid: number): Promise => { 106 | const { inspectPort } = this.options; 107 | const httpUrl = `http://127.0.0.1:${inspectPort}`; 108 | 109 | const connected = await TcpDetector.detectPort(inspectPort); 110 | 111 | // 如果端口没有被占用,则直接发送信号 112 | if (!connected) { 113 | killPid(Number(pid), 'SIGUSR1'); 114 | } 115 | 116 | const ret = await this.verifyInspectorPort(httpUrl); 117 | 118 | if (!ret) { 119 | const msg = connected 120 | ? `port ${inspectPort} has been occupied by another process` 121 | : `failed to open the inspect port for process of ${pid}`; 122 | 123 | throw new LiveInspectorError(msg); 124 | } 125 | 126 | const ws = await this.wsConnect(ret); 127 | return ws; 128 | }; 129 | 130 | public connectByAddr = async (addr: IAddr): Promise => { 131 | const httpUrl = `http://${addr.host}:${addr.port}`; 132 | 133 | const ret = await this.verifyInspectorPort(httpUrl); 134 | 135 | if (!ret) { 136 | throw new LiveInspectorError( 137 | `failed to open the inspect service at: ${addr.host}:${addr.port}` 138 | ); 139 | } 140 | 141 | const ws = await this.wsConnect(ret); 142 | return ws; 143 | }; 144 | } 145 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/lib/input-stap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * lib/input-stap.js: reads output from a stap profiling script, which emits 3 | * stanzas that look like this: 4 | * 5 | * ubt["bar+0x32 [foo] 6 | * foo+0x57 [foo] 7 | * main+0x48 [foo] 8 | * __libc_start_main+0xed [libc-2.15.so] 9 | * _start+0x29 [foo]"]=0x77 10 | * 11 | * You can generate such output with: 12 | * 13 | * # stap \ 14 | * -e "global ubt; \ 15 | * probe timer.profile { ubt[sprint_ubacktrace()] += 1 }; \ 16 | * probe timer.s(30) { exit() }" \ 17 | * -o stap.out 18 | * 19 | * If stap warns about missing unwind data for a module, and stap 20 | * suggests adding '-d /lib/libquux.so', which you know to be a shared 21 | * library used by the 'foo' binary, add the following to the above 22 | * command: 23 | * 24 | * -d /path/to/foo $(ldd /path/to/foo | awk 'NF==4 { print "-d", $3 }') 25 | * 26 | * to deal with all warnings related to shared libraries used by 'foo', 27 | * all at once. 28 | */ 29 | 30 | var mod_util = require('util'); 31 | var mod_events = require('events'); 32 | 33 | var mod_carrier = require('carrier'); 34 | 35 | exports.reader = PerfStreamReader; 36 | 37 | function PerfStreamReader(input, log) 38 | { 39 | this.dsr_log = log; 40 | this.dsr_linenum = 0; 41 | this.dsr_addingframes = false; 42 | this.dsr_prefixes = []; 43 | this.dsr_stack = []; 44 | this.dsr_carrier = mod_carrier.carry(input); 45 | this.dsr_carrier.on('line', this.onLine.bind(this)); 46 | this.dsr_carrier.on('end', this.onEnd.bind(this)); 47 | 48 | mod_events.EventEmitter.call(this); 49 | } 50 | 51 | mod_util.inherits(PerfStreamReader, mod_events.EventEmitter); 52 | 53 | PerfStreamReader.prototype.onLine = function (line) 54 | { 55 | ++this.dsr_linenum; 56 | 57 | var match; 58 | if (!this.dsr_addingframes) { 59 | /* Skip array name */ 60 | line.replace(/^\w+\[/, ''); 61 | 62 | /* Find and add prefixes */ 63 | while (true) { 64 | /* JSSTYLED */ 65 | match = /(?:"([^"]*)",)(.*$)/.exec(line); 66 | if (!match) 67 | break; 68 | this.dsr_prefixes.push(match[1]); 69 | line = match[2]; 70 | } 71 | 72 | /* Find first frame */ 73 | /* JSSTYLED */ 74 | match = /(?:"(.*$))/.exec(line); 75 | if (!match) { 76 | this.dsr_log.warn('line ' + this.dsr_linenum + 77 | ': no first frame found'); 78 | return; 79 | } 80 | line = match[1]; 81 | this.dsr_addingframes = true; 82 | } 83 | 84 | /* Look for count */ 85 | var count; 86 | /* JSSTYLED */ 87 | match = /(^.*)"\]=(\w+$)/.exec(line); 88 | if (match) { 89 | line = match[1]; 90 | count = parseInt(match[2], 16); 91 | this.dsr_addingframes = false; 92 | } 93 | 94 | /* 95 | * In general, frames have one of the following sets of components: 96 | * 97 | * address 98 | * address [module+offset] 99 | * function+offset [module] 100 | * 101 | * We try to avoid assuming too much about the form in order to support 102 | * various annotations provided by ustack helpers. 103 | */ 104 | var frame = line; 105 | frame = frame.replace(/ \[(\S+)\]$/, ''); 106 | /* JSSTYLED */ 107 | frame = frame.replace(/\+.*/, ''); 108 | 109 | /* 110 | * Remove both function and template parameters from demangled C++ 111 | * frames, but skip the first two characters because they're used by the 112 | * Node.js ustack helper as separators. 113 | */ 114 | /* JSSTYLED */ 115 | frame = frame.replace(/(..)[(<].*/, '$1'); 116 | 117 | if (line.length === 0) { 118 | if (this.dsr_stack.length !== 0) 119 | this.dsr_log.warn('line ' + this.dsr_linenum + 120 | ': unexpected blank line'); 121 | return; 122 | } 123 | 124 | /* Add prefixes */ 125 | if (this.dsr_prefixes.length > 0) { 126 | frame = this.dsr_prefixes.join('`') + '`' + frame; 127 | } 128 | 129 | this.dsr_stack.unshift(frame); 130 | 131 | if (!this.dsr_addingframes) { 132 | this.emit('stack', this.dsr_stack, count); 133 | this.dsr_prefixes = []; 134 | this.dsr_stack = []; 135 | } 136 | }; 137 | 138 | PerfStreamReader.prototype.onEnd = function () 139 | { 140 | if (this.dsr_stack.length !== 0) 141 | this.dsr_log.warn('line ' + this.dsr_linenum + 142 | ': unexpected end of stream'); 143 | 144 | this.emit('end'); 145 | }; 146 | -------------------------------------------------------------------------------- /packages/diat/snippet/get_active_handles.js: -------------------------------------------------------------------------------- 1 | (async () => { 2 | const net = require('net'); 3 | const dgram = require('dgram'); 4 | const tty = require('tty'); 5 | const childProcess = require('child_process'); 6 | // const stream = require('stream'); 7 | 8 | // const kFsWriteStream = 'fs.WriteStream'; 9 | // const kFsReadStream = 'fs.ReadStream'; 10 | const kChildProcessChildProcess = 'child_process.ChildProcess'; 11 | const kTtyReadStream = 'tty.ReadStream'; 12 | const kTtyWriteStream = 'tty.WriteStream'; 13 | const kNetServer = 'net.Server'; 14 | const kNetSocket = 'net.Socket'; 15 | const kDgramSocket = 'dgram.Socket'; 16 | const kTimers = 'timers'; 17 | 18 | // TODO try to get the fd so that we could collaborate with lsof 19 | // NOTE(oyyd) The order of kHandleInfos matters as some instances might 20 | // be instanceof more than one class. 21 | const kHandleInfos = [ 22 | // TODO fs streams are not included in the active handles 23 | // { 24 | // key: kFsWriteStream, 25 | // checkClazz: async instance => { 26 | // return ( 27 | // instance instanceof stream.Writable && 28 | // ['path', 'fd', 'flags', 'mode'].every(i => instance.hasOwnProperty(i)) 29 | // ); 30 | // } 31 | // }, 32 | // { 33 | // key: kFsReadStream, 34 | // checkClazz: async () => { 35 | // // 36 | // } 37 | // }, 38 | { 39 | key: kTtyReadStream, 40 | clazz: tty.ReadStream 41 | }, 42 | { 43 | // TODO some tty.WriteStream might be regared as net.Socket 44 | key: kTtyWriteStream, 45 | clazz: tty.WriteStream 46 | }, 47 | { 48 | key: kNetServer, 49 | clazz: net.Server, 50 | getInfo: async server => { 51 | const address = server.address(); 52 | 53 | const connections = await new Promise(resolve => { 54 | server.getConnections((err, count) => { 55 | resolve(err ? 0 : count); 56 | }); 57 | }); 58 | 59 | return { 60 | address, 61 | connections 62 | }; 63 | } 64 | }, 65 | { 66 | key: kNetSocket, 67 | clazz: net.Socket, 68 | getInfo: async socket => { 69 | const { localAddress, localPort, remoteAddress, remotePort } = socket; 70 | 71 | return { 72 | localAddress, 73 | localPort, 74 | remoteAddress, 75 | remotePort 76 | }; 77 | } 78 | }, 79 | { 80 | key: kDgramSocket, 81 | clazz: dgram.Socket, 82 | getInfo: async socket => { 83 | const address = socket.address(); 84 | return { 85 | address 86 | }; 87 | } 88 | }, 89 | { 90 | key: kChildProcessChildProcess, 91 | clazz: childProcess.ChildProcess, 92 | getInfo: async child => { 93 | return { 94 | pid: child.pid 95 | }; 96 | } 97 | }, 98 | { 99 | key: kTimers, 100 | checkClazz: async instance => { 101 | const name = instance.constructor.name; 102 | return name === 'Timeout' || name === 'Timer' || name === 'Immediate'; 103 | } 104 | } 105 | ]; 106 | const statistic = {}; 107 | const extraInfos = {}; 108 | 109 | kHandleInfos.forEach(info => { 110 | const { key, getInfo } = info; 111 | statistic[key] = 0; 112 | 113 | if (typeof getInfo === 'function') { 114 | extraInfos[key] = []; 115 | } 116 | }); 117 | 118 | const handles = process._getActiveHandles(); 119 | 120 | for (let handleIndex = 0; handleIndex < handles.length; handleIndex += 1) { 121 | const handle = handles[handleIndex]; 122 | 123 | for (let i = 0; i < kHandleInfos.length; i += 1) { 124 | const info = kHandleInfos[i]; 125 | const { key, clazz, checkClazz, getInfo } = info; 126 | 127 | const check = checkClazz 128 | ? checkClazz 129 | : instance => Promise.resolve(instance instanceof clazz); 130 | 131 | if (await check(handle)) { 132 | statistic[key]++; 133 | if (typeof getInfo === 'function') { 134 | const ret = await getInfo(handle); 135 | extraInfos[key].push(ret); 136 | } 137 | break; 138 | } 139 | } 140 | } 141 | 142 | const ret = { 143 | handleLength: handles.length, 144 | statistic, 145 | extraInfos 146 | }; 147 | 148 | return JSON.stringify(ret); 149 | })(); 150 | -------------------------------------------------------------------------------- /packages/node-inspect/.eslintrc: -------------------------------------------------------------------------------- 1 | root: true 2 | 3 | env: 4 | node: true 5 | es6: true 6 | 7 | parserOptions: 8 | ecmaVersion: 2017 9 | 10 | rules: 11 | # Possible Errors 12 | # http://eslint.org/docs/rules/#possible-errors 13 | comma-dangle: [2, only-multiline] 14 | no-control-regex: 2 15 | no-debugger: 2 16 | no-dupe-args: 2 17 | no-dupe-keys: 2 18 | no-duplicate-case: 2 19 | no-empty-character-class: 2 20 | no-ex-assign: 2 21 | no-extra-boolean-cast: 2 22 | no-extra-parens: [2, functions] 23 | no-extra-semi: 2 24 | no-func-assign: 2 25 | no-invalid-regexp: 2 26 | no-irregular-whitespace: 2 27 | no-obj-calls: 2 28 | no-proto: 2 29 | no-template-curly-in-string: 2 30 | no-unexpected-multiline: 2 31 | no-unreachable: 2 32 | no-unsafe-negation: 2 33 | use-isnan: 2 34 | valid-typeof: 2 35 | 36 | # Best Practices 37 | # http://eslint.org/docs/rules/#best-practices 38 | dot-location: [2, property] 39 | no-fallthrough: 2 40 | no-global-assign: 2 41 | no-multi-spaces: 2 42 | no-octal: 2 43 | no-redeclare: 2 44 | no-self-assign: 2 45 | no-unused-labels: 2 46 | no-useless-call: 2 47 | no-useless-escape: 2 48 | no-void: 2 49 | no-with: 2 50 | 51 | # Strict Mode 52 | # http://eslint.org/docs/rules/#strict-mode 53 | strict: [2, global] 54 | 55 | # Variables 56 | # http://eslint.org/docs/rules/#variables 57 | no-delete-var: 2 58 | no-undef: 2 59 | no-unused-vars: [2, {args: none}] 60 | 61 | # Node.js and CommonJS 62 | # http://eslint.org/docs/rules/#nodejs-and-commonjs 63 | no-mixed-requires: 2 64 | no-new-require: 2 65 | no-path-concat: 2 66 | no-restricted-modules: [2, sys, _linklist] 67 | no-restricted-properties: [2, { 68 | object: assert, 69 | property: deepEqual, 70 | message: Please use assert.deepStrictEqual(). 71 | }, { 72 | property: __defineGetter__, 73 | message: __defineGetter__ is deprecated. 74 | }, { 75 | property: __defineSetter__, 76 | message: __defineSetter__ is deprecated. 77 | }] 78 | 79 | # Stylistic Issues 80 | # http://eslint.org/docs/rules/#stylistic-issues 81 | brace-style: [2, 1tbs, {allowSingleLine: true}] 82 | comma-spacing: 2 83 | comma-style: 2 84 | computed-property-spacing: 2 85 | eol-last: 2 86 | func-call-spacing: 2 87 | func-name-matching: 2 88 | indent: [2, 2, {SwitchCase: 1, MemberExpression: 1}] 89 | key-spacing: [2, {mode: minimum}] 90 | keyword-spacing: 2 91 | linebreak-style: [2, unix] 92 | max-len: [2, 80, 2] 93 | new-parens: 2 94 | no-mixed-spaces-and-tabs: 2 95 | no-multiple-empty-lines: [2, {max: 2, maxEOF: 0, maxBOF: 0}] 96 | no-tabs: 2 97 | no-trailing-spaces: 2 98 | quotes: [2, single, avoid-escape] 99 | semi: 2 100 | semi-spacing: 2 101 | space-before-blocks: [2, always] 102 | space-before-function-paren: [2, never] 103 | space-in-parens: [2, never] 104 | space-infix-ops: 2 105 | space-unary-ops: 2 106 | 107 | # ECMAScript 6 108 | # http://eslint.org/docs/rules/#ecmascript-6 109 | arrow-parens: [2, always] 110 | arrow-spacing: [2, {before: true, after: true}] 111 | constructor-super: 2 112 | no-class-assign: 2 113 | no-confusing-arrow: 2 114 | no-const-assign: 2 115 | no-dupe-class-members: 2 116 | no-new-symbol: 2 117 | no-this-before-super: 2 118 | prefer-const: [2, {ignoreReadBeforeAssign: true}] 119 | rest-spread-spacing: 2 120 | template-curly-spacing: 2 121 | 122 | # Custom rules in tools/eslint-rules 123 | align-function-arguments: 2 124 | align-multiline-assignment: 2 125 | assert-fail-single-argument: 2 126 | new-with-error: [2, Error, RangeError, TypeError, SyntaxError, ReferenceError] 127 | 128 | # Global scoped method and vars 129 | globals: 130 | COUNTER_HTTP_CLIENT_REQUEST: false 131 | COUNTER_HTTP_CLIENT_RESPONSE: false 132 | COUNTER_HTTP_SERVER_REQUEST: false 133 | COUNTER_HTTP_SERVER_RESPONSE: false 134 | COUNTER_NET_SERVER_CONNECTION: false 135 | COUNTER_NET_SERVER_CONNECTION_CLOSE: false 136 | DTRACE_HTTP_CLIENT_REQUEST: false 137 | DTRACE_HTTP_CLIENT_RESPONSE: false 138 | DTRACE_HTTP_SERVER_REQUEST: false 139 | DTRACE_HTTP_SERVER_RESPONSE: false 140 | DTRACE_NET_SERVER_CONNECTION: false 141 | DTRACE_NET_STREAM_END: false 142 | LTTNG_HTTP_CLIENT_REQUEST: false 143 | LTTNG_HTTP_CLIENT_RESPONSE: false 144 | LTTNG_HTTP_SERVER_REQUEST: false 145 | LTTNG_HTTP_SERVER_RESPONSE: false 146 | LTTNG_NET_SERVER_CONNECTION: false 147 | LTTNG_NET_STREAM_END: false 148 | BigInt: true 149 | -------------------------------------------------------------------------------- /packages/stackvis-simplified/README.md: -------------------------------------------------------------------------------- 1 | # node-stackvis 2 | 3 | Stackvis is a command line tool and JavaScript library for visualizing call 4 | stacks. For an example, see 5 | http://us-east.manta.joyent.com/dap/public/stackvis/example.htm. This approach 6 | (and the code for the SVG-based flamegraph) is based heavily on Brendan Gregg's 7 | [FlameGraph](http://github.com/brendangregg/FlameGraph/) tools. 8 | 9 | 10 | ## Synopsis 11 | 12 | Profile a program for 30 seconds: 13 | 14 | # dtrace -n 'profile-97/pid == $YOURPID/{ @[jstack(80, 8192)] = count(); }' -c "sleep 30" > dtrace.out 15 | 16 | then translate the DTrace output into a flame graph: 17 | 18 | # stackvis < dtrace.out > flamegraph.htm 19 | 20 | Or, create the flame graph and share it on Joyent's Manta service: 21 | 22 | # stackvis < dtrace.out | stackvis share 23 | https://us-east.manta.joyent.com/dap/public/stackvis/298c9ae2-aec8-4993-8bc9-d621dcdbeb71/index.htm 24 | 25 | 26 | ## Details 27 | 28 | The default mode assumes input from a DTrace invocation like the above, and 29 | produces a D3-based visualization in a self-contained HTML file. You can 30 | explicitly specify input formats: 31 | 32 | * "dtrace" (the default) 33 | * "collapsed" (more easily grep'd through) 34 | * "perf" (from the Linux "perf" tool) 35 | * "stap" (from SystemTap) 36 | 37 | as well as output formats: 38 | 39 | * "collapsed" (see above) 40 | * "flamegraph-svg" (traditional SVG-based flame graph) 41 | * "flamegraph-d3" (the default) 42 | 43 | For example, to read "collapsed" output and produce a SVG flamegraph, use: 44 | 45 | # stackvis collapsed flamegraph-svg < collapsed.out > flamegraph.svg 46 | 47 | This module also provides the "stackcollapse" and "flamegraph" tools, which are 48 | essentially direct ports of the original FlameGraph tools. You can use them by 49 | first collecting data as above, then collapse common stacks: 50 | 51 | # stackcollapse < dtrace.out > collapsed.out 52 | 53 | then create a flame graph: 54 | 55 | # flamegraph < collapsed.out > graph.svg 56 | 57 | This approach is a little more verbose, but lets you filter out particular 58 | function names by grepping through the collapsed file. 59 | 60 | 61 | ## API 62 | 63 | The command-line tools are thin wrappers around the API, which is built upon a 64 | simple internal representation of stack traces and a bunch of Readers 65 | (lib/input-\*.json) and Writers (lib/output-\*.json) for various intermediate 66 | formats: 67 | 68 | - input-dtrace.js: reads stacks from the output of a DTrace profiling script 69 | - input-collapsed.js: reads data in the form used by the "stackcollapse" tool, 70 | where function offsets are stripped out, common stacks are collapsed, and 71 | there's one stack per line. 72 | - output-collapsed.js: writes stacks in above "collapsed" form 73 | - output-flamegraph-svg.js: writes stacks as a flame graph SVG 74 | - output-flamegraph-d3.js: writes stacks as a flame graph HTML file using D3 75 | 76 | Client code shouldn't load these directly. Instead, require 'stackvis' and use 77 | lookupReader and lookupWriter: 78 | ```javascript 79 | var mod_stackvis = require('stackvis'); 80 | var dtrace_reader = mod_stackvis.readerLookup('dtrace'); 81 | var collapsed_writer = mod_stackvis.writerLookup('collapsed'); 82 | ``` 83 | The main operation is translating from one representation to another (e.g., 84 | DTrace output to a flame graph) using pipeStacks() (which requires a Bunyan 85 | logger): 86 | ```javascript 87 | var mod_bunyan = require('bunyan'); 88 | var log = new mod_bunyan({ 'name': 'mytool', 'stream': process.stderr }); 89 | mod_stackvis.pipeStacks(log, process.stdin, dtrace_reader, collapsed_writer, 90 | process.stdout, function () { console.error('translation finished'); }); 91 | ``` 92 | This example instantiates a new dtrace_reader to read DTrace output from 93 | process.stdin and then emits the result in collapsed form to process.stdout 94 | through the collapsed_writer. 95 | 96 | ## Adding new readers and writers 97 | 98 | It's easy to add new readers (for new input sources) and writers (for new types 99 | of visualizations). See lib/stackvis.js for an overview of how these interfaces 100 | work. 101 | 102 | ## TODO 103 | 104 | - See about dealing with multiple "silos" of a single flame graph that are 105 | essentially the same, but differ in exactly one frame. 106 | - Experiment with flame graph coloring. Current options include random, 107 | gradient, and time-based. Another possibility is to use hue to denote the 108 | module and saturation to denote the size of a frame relative to others at the 109 | same level of depth. 110 | -------------------------------------------------------------------------------- /packages/diat/src/InspectorWorkerSession.ts: -------------------------------------------------------------------------------- 1 | import * as ws from 'ws' 2 | import { EventEmitter as Event } from 'events' 3 | import { IComm } from './Types' 4 | import { snippets } from './Snippets' 5 | import { TcpProxy } from './TcpProxy' 6 | import { parseInspectorUrl } from './utils' 7 | 8 | interface IConfig { 9 | comm: IComm 10 | sessionId: string 11 | host: string 12 | port: number 13 | } 14 | 15 | const kDefaultEvaluateOptions = { 16 | awaitPromise: true, 17 | includeCommandLineAPI: true, 18 | } 19 | 20 | /** 21 | * NOTE: It seems only one session availiable. 22 | */ 23 | export class InspectorWorkerSession extends Event { 24 | private config: IConfig 25 | private server: ws.Server | null = null 26 | private closed: boolean = false 27 | private tcpProxy: TcpProxy | null = null 28 | 29 | constructor(config: IConfig) { 30 | super() 31 | this.config = config 32 | 33 | this.config.comm.event.addListener( 34 | 'NodeWorker.detachedFromWorker', 35 | this.handleWorkerDetached 36 | ) 37 | } 38 | 39 | private handleWorkerDetached = info => { 40 | const { sessionId } = this.config 41 | const { sessionId: msgSesssionId } = info 42 | 43 | if (msgSesssionId === sessionId) { 44 | this.destroy() 45 | } 46 | } 47 | 48 | private openWorkerInspector = async (): Promise<{ 49 | host: string 50 | port: number 51 | }> => { 52 | const code = await snippets.getSnippet('open_inspector') 53 | const { comm, sessionId } = this.config 54 | const { event, post } = comm 55 | let id = 0 56 | 57 | const waitInspectorOpenMsg = () => 58 | new Promise(resolve => { 59 | const cleanUp = (message: any) => { 60 | event.removeListener( 61 | 'NodeWorker.receivedMessageFromWorker', 62 | handleMsg 63 | ) 64 | resolve(message) 65 | } 66 | const handleMsg = info => { 67 | if (info.sessionId === sessionId) { 68 | const message = JSON.parse(info.message) 69 | // TODO better solutions? 70 | if (message.id === 2) { 71 | cleanUp(message) 72 | } 73 | } 74 | } 75 | event.addListener('NodeWorker.receivedMessageFromWorker', handleMsg) 76 | }) 77 | 78 | const p = waitInspectorOpenMsg() 79 | 80 | await post('NodeWorker.sendMessageToWorker', { 81 | sessionId, 82 | message: JSON.stringify({ id: ++id, method: 'Runtime.enable' }), 83 | }) 84 | await post('NodeWorker.sendMessageToWorker', { 85 | sessionId, 86 | message: JSON.stringify({ 87 | method: 'Runtime.evaluate', 88 | id: ++id, 89 | params: { 90 | expression: code, 91 | ...kDefaultEvaluateOptions, 92 | }, 93 | }), 94 | }) 95 | 96 | const message: any = await p 97 | 98 | await post('NodeWorker.sendMessageToWorker', { 99 | sessionId, 100 | message: JSON.stringify({ id: ++id, method: 'Runtime.disable' }), 101 | }) 102 | 103 | if ( 104 | message.result && 105 | message.result.result && 106 | message.result.result.type === 'string' 107 | ) { 108 | const ret = JSON.parse(message.result.result.value) 109 | const parseRet = parseInspectorUrl(ret.url) 110 | if (parseRet) { 111 | return parseRet 112 | } 113 | } 114 | 115 | throw new Error('failed to open inspector inside the worker') 116 | } 117 | 118 | // TODO(oyyd): inspector.close() is missed in threads 119 | closeInspector = async () => { 120 | // 121 | } 122 | 123 | inspect = async () => { 124 | const { host, port } = await this.openWorkerInspector() 125 | 126 | this.tcpProxy = new TcpProxy({ 127 | targetHost: host, 128 | targetPort: port, 129 | }) 130 | 131 | return this.tcpProxy.listen() 132 | } 133 | 134 | destroy = async () => { 135 | if (this.closed) { 136 | return 137 | } 138 | 139 | this.closed = true 140 | 141 | const { server, tcpProxy } = this 142 | const { sessionId } = this.config 143 | this.server = null 144 | this.tcpProxy = null 145 | 146 | this.config.comm.event.removeListener( 147 | 'NodeWorker.detachedFromWorker', 148 | this.handleWorkerDetached 149 | ) 150 | 151 | this.emit('close', sessionId) 152 | 153 | await this.closeInspector() 154 | 155 | if (tcpProxy) { 156 | await tcpProxy.destroy() 157 | } 158 | 159 | if (server) { 160 | await new Promise(resolve => { 161 | server.close(() => { 162 | resolve() 163 | }) 164 | }) 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/autocomplete.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const { test } = require('tap'); 3 | 4 | const startCLI = require('./start-cli'); 5 | 6 | test('repl autocomplete', (t) => { 7 | const cli = startCLI(['examples/alive.js']); 8 | 9 | function onFatal(error) { 10 | cli.quit(); 11 | throw error; 12 | } 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => cli.command('repl')) 17 | .then(() => cli.waitForPrompt()) 18 | .then(() => cli.completer('')) 19 | .then(() => cli.waitFor(/Array/)) 20 | .then(() => cli.completer('glo')) 21 | .then(() => cli.waitFor(/globalThis/)) 22 | .then(() => { 23 | t.match( 24 | cli.output, 25 | 'global', 26 | 'could access "global" itself'); 27 | t.match( 28 | cli.output, 29 | 'globalThis', 30 | 'could access "globalThis"'); 31 | }) 32 | .then(() => cli.completer('global.')) 33 | .then(() => cli.waitFor(/global\.Array/)) 34 | .then(() => cli.completer('global.glo')) 35 | .then(() => cli.waitFor(/global\.globalThis/)) 36 | .then(() => { 37 | t.match( 38 | cli.output, 39 | 'global.global'); 40 | t.match( 41 | cli.output, 42 | 'global.globalThis'); 43 | }) 44 | .then(() => cli.completer('global.globalThis')) 45 | .then(() => cli.waitFor(/global\.globalThis/)) 46 | .then(() => { 47 | t.notMatch( 48 | cli.output, 49 | /global\.global\n/); 50 | t.match( 51 | cli.output, 52 | /global\.globalThis/); 53 | }) 54 | .then(() => cli.completer('Arr')) 55 | .then(() => cli.waitFor(/ArrayBuffer/)) 56 | .then(() => { 57 | t.match( 58 | cli.output, 59 | 'Array'); 60 | t.match( 61 | cli.output, 62 | 'ArrayBuffer'); 63 | }) 64 | .then(() => cli.completer('process.')) 65 | .then(() => cli.waitFor(/process\.versions/)) 66 | .then(() => { 67 | t.match( 68 | cli.output, 69 | 'process.version', 70 | 'could access property of "version" from "process"'); 71 | }) 72 | .then(() => cli.completer('process.version')) 73 | .then(() => cli.waitFor(/process\.versions/)) 74 | .then(() => { 75 | t.match( 76 | cli.output, 77 | 'process', 78 | '"process" should have both properties of "version" ' 79 | + 'and "versions" when search for "version"'); 80 | }) 81 | .then(() => cli.command('var myUniqueObj = { first: 1, second: 2 }')) 82 | .then(() => cli.completer('myUnique')) 83 | .then(() => cli.waitFor(/myUniqueObj/)) 84 | .then(() => cli.completer('myUniqueObj.')) 85 | .then(() => cli.waitFor(/second/)) 86 | .then(() => { 87 | t.match(cli.output, 'first', 'shoud print the property "first"'); 88 | t.match(cli.output, 'second', 'shoud print the property "second"'); 89 | }) 90 | .then(() => cli.completer('myUniqueObj.firs')) 91 | .then(() => cli.waitFor(/first/)) 92 | .then(() => { 93 | t.match(cli.output, 'myUniqueObj.first'); 94 | t.notMatch(cli.output, 'second'); 95 | }) 96 | .then(() => cli.completer('var a = myUnique')) 97 | .then(() => cli.waitFor(/myUniqueObj/)) 98 | .then(() => { 99 | t.match( 100 | cli.output, 101 | 'myUniqueObj', 102 | 'should complete for a simple sentence'); 103 | }) 104 | .then(() => cli.completer('var a = myUniqueObj.firs')) 105 | .then(() => cli.waitFor(/first/)) 106 | .then(() => { 107 | t.match( 108 | cli.output, 109 | 'myUniqueObj.first', 110 | 'should complete for a simple sentence'); 111 | }) 112 | .then(() => cli.completer('var a = myUniqueObj.')) 113 | .then(() => cli.waitFor(/second/)) 114 | .then(() => cli.ctrlC()) 115 | .then(() => cli.waitFor(/debug> $/)) 116 | .then(() => cli.quit()) 117 | .then(null, onFatal); 118 | }); 119 | 120 | test('repl autocomplete on pause', (t) => { 121 | const cli = startCLI(['examples/break.js']); 122 | 123 | function onFatal(error) { 124 | cli.quit(); 125 | throw error; 126 | } 127 | 128 | return cli.waitForInitialBreak() 129 | .then(() => cli.waitForPrompt()) 130 | .then(() => cli.stepCommand('c')) 131 | .then(() => cli.waitForPrompt()) 132 | .then(() => cli.command('repl')) 133 | .then(() => cli.waitForPrompt()) 134 | .then(() => cli.completer('')) 135 | .then(() => cli.waitFor(/name/)) 136 | .then(() => { 137 | t.match(cli.output, /name\n/, 'show scope variables'); 138 | t.match(cli.output, /sayHello\n/, 'show scope variables'); 139 | t.match(cli.output, /Number\n/, 'show properties of global'); 140 | }) 141 | .then(() => cli.quit()) 142 | .then(null, onFatal); 143 | }); 144 | -------------------------------------------------------------------------------- /packages/diat/src/Metric.ts: -------------------------------------------------------------------------------- 1 | import * as net from 'net' 2 | import { EventEmitter as Event } from 'events' 3 | import * as stream from 'stream' 4 | import * as path from 'path' 5 | import * as fs from 'fs' 6 | import * as ansi from 'ansi-escapes' 7 | import * as bytes from 'bytes' 8 | import * as util from 'util' 9 | 10 | interface IOptions { 11 | std: stream.Writable 12 | socketPath: string 13 | } 14 | 15 | interface IMessage { 16 | cpuUsage: { 17 | user: number 18 | system: number 19 | } 20 | memoryUsage: { 21 | rss: number 22 | heapTotal: number 23 | heapUsed: number 24 | external: number 25 | } 26 | uv: { 27 | handle: number 28 | request: number 29 | latency: number 30 | } 31 | } 32 | 33 | const kDefaultOptions = { 34 | std: process.stdout, 35 | } 36 | const kSocketFileName = `diat_metrics.sock` 37 | export const kMessageSeperator = '__$' 38 | const kMetricTypeNumber = 3 39 | 40 | function getAbsolutePath(filename: string): string { 41 | if (path.isAbsolute(filename)) { 42 | return filename 43 | } 44 | 45 | return path.resolve(process.cwd(), `./${filename}`) 46 | } 47 | 48 | function clearLines(std: any, line: number) { 49 | std.write(ansi.eraseLines(line) + ansi.cursorLeft) 50 | } 51 | 52 | function formatLoad(load: number): string | number { 53 | if (!load) { 54 | return load 55 | } 56 | 57 | return load.toPrecision(2) 58 | } 59 | 60 | /** 61 | * - create an unix domain socket and wait for connections, when connected: 62 | * - wait for messages, calulcate them, and output 63 | * - when disconnected or exiting: 64 | * - exit 65 | * - not connections and timeout: 66 | * - log error 67 | */ 68 | export class Metric extends Event { 69 | private options: IOptions 70 | private server: net.Server | null = null 71 | private socket: net.Socket | null = null 72 | private closed: boolean = false 73 | 74 | constructor(options: Partial) { 75 | super() 76 | this.options = Object.assign( 77 | { 78 | socketPath: kSocketFileName, 79 | }, 80 | kDefaultOptions, 81 | options 82 | ) 83 | 84 | this.options.socketPath = getAbsolutePath( 85 | this.options.socketPath || kSocketFileName 86 | ) 87 | } 88 | 89 | destroy = (err?: Error | null, getClosed: boolean = false) => { 90 | if (this.closed) { 91 | return 92 | } 93 | this.closed = true 94 | 95 | // TODO handle err 96 | const { server, socket } = this 97 | 98 | if (socket) { 99 | socket.destroy() 100 | } 101 | 102 | if (server) { 103 | server.close() 104 | this.unlinkSocketPath() 105 | } 106 | 107 | this.server = null 108 | this.socket = null 109 | if (getClosed) { 110 | this.emit('get_closed') 111 | } 112 | } 113 | 114 | private unlinkSocketPath = () => { 115 | return util 116 | .promisify(fs.unlink)(this.options.socketPath) 117 | .catch(() => {}) 118 | } 119 | 120 | private writeState = (message: IMessage) => { 121 | const { std } = this.options 122 | clearLines(std, kMetricTypeNumber) 123 | const { user, system } = message.cpuUsage 124 | const { rss, heapTotal, heapUsed, external } = message.memoryUsage 125 | const { latency, request, handle } = message.uv 126 | std.write( 127 | `[cpu] load(user): ${formatLoad(user)} load(system): ${formatLoad( 128 | system 129 | )}\n` 130 | ) 131 | std.write( 132 | `[memory] rss: ${bytes(rss)} heapTotal: ${bytes( 133 | heapTotal 134 | )} heapUsed: ${bytes(heapUsed)} external: ${bytes(external)}\n` 135 | ) 136 | std.write( 137 | `[uv] handle: ${handle}, request: ${request}, latency: ${latency}ms` 138 | ) 139 | } 140 | 141 | private calculate = (message: IMessage) => { 142 | // TODO handle invalid message structure 143 | this.writeState(message) 144 | } 145 | 146 | private handleConnection = (socket: net.Socket) => { 147 | if (this.socket) { 148 | socket.destroy() 149 | return 150 | } 151 | 152 | this.socket = socket 153 | 154 | let err: Error | null = null 155 | let message = '' 156 | 157 | const parseMessage = () => { 158 | let index = message.indexOf(kMessageSeperator) 159 | while (index >= 0) { 160 | const frame = message.slice(0, index) 161 | message = message.slice(index + kMessageSeperator.length) 162 | this.calculate(JSON.parse(frame)) 163 | index = message.indexOf(kMessageSeperator) 164 | } 165 | } 166 | 167 | socket.on('data', data => { 168 | const msg = data.toString('utf8') 169 | message += msg 170 | parseMessage() 171 | }) 172 | socket.on('error', error => { 173 | err = error 174 | }) 175 | socket.once('close', () => { 176 | this.destroy(err, true) 177 | }) 178 | } 179 | 180 | public createServer = async (): Promise => { 181 | const { socketPath } = this.options 182 | 183 | await this.unlinkSocketPath() 184 | 185 | this.server = net.createServer(this.handleConnection) 186 | 187 | const server = this.server as net.Server 188 | 189 | await new Promise(resolve => { 190 | server.listen(socketPath, () => { 191 | resolve() 192 | }) 193 | }) 194 | 195 | return socketPath 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /packages/node-inspect/test/cli/launch.test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const Path = require('path'); 3 | 4 | const { test } = require('tap'); 5 | 6 | const startCLI = require('./start-cli'); 7 | 8 | test('custom port', (t) => { 9 | const CUSTOM_PORT = '9230'; 10 | const script = Path.join('examples', 'three-lines.js'); 11 | 12 | const cli = startCLI([`--port=${CUSTOM_PORT}`, script]); 13 | 14 | return cli.waitForInitialBreak() 15 | .then(() => cli.waitForPrompt()) 16 | .then(() => { 17 | t.match(cli.output, 'debug>', 'prints a prompt'); 18 | t.match( 19 | cli.output, 20 | new RegExp(`< Debugger listening on [^\n]*${CUSTOM_PORT}`), 21 | 'forwards child output'); 22 | }) 23 | .then(() => cli.quit()) 24 | .then((code) => { 25 | t.equal(code, 0, 'exits with success'); 26 | }); 27 | }); 28 | 29 | test('random port', (t) => { 30 | const script = Path.join('examples', 'three-lines.js'); 31 | 32 | const cli = startCLI(['--port=0', script]); 33 | 34 | return cli.waitForInitialBreak() 35 | .then(() => cli.waitForPrompt()) 36 | .then(() => { 37 | t.match(cli.output, 'debug>', 'prints a prompt'); 38 | t.match( 39 | cli.output, 40 | /< Debugger listening on /, 41 | 'forwards child output'); 42 | }) 43 | .then(() => cli.quit()) 44 | .then((code) => { 45 | t.equal(code, 0, 'exits with success'); 46 | }); 47 | }); 48 | 49 | test('random port with --inspect-port=0', (t) => { 50 | const script = Path.join('examples', 'three-lines.js'); 51 | 52 | const cli = startCLI([script], ['--inspect-port=0']); 53 | 54 | return cli.waitForInitialBreak() 55 | .then(() => cli.waitForPrompt()) 56 | .then(() => { 57 | t.match(cli.output, 'debug>', 'prints a prompt'); 58 | t.match( 59 | cli.output, 60 | /< Debugger listening on /, 61 | 'forwards child output'); 62 | }) 63 | .then(() => cli.quit()) 64 | .then((code) => { 65 | t.equal(code, 0, 'exits with success'); 66 | }); 67 | }); 68 | 69 | test('examples/three-lines.js', (t) => { 70 | const script = Path.join('examples', 'three-lines.js'); 71 | const cli = startCLI([script]); 72 | 73 | return cli.waitForInitialBreak() 74 | .then(() => cli.waitForPrompt()) 75 | .then(() => { 76 | t.match(cli.output, 'debug>', 'prints a prompt'); 77 | t.match( 78 | cli.output, 79 | /< Debugger listening on [^\n]*9229/, 80 | 'forwards child output'); 81 | }) 82 | .then(() => cli.command('["hello", "world"].join(" ")')) 83 | .then(() => { 84 | t.match(cli.output, 'hello world', 'prints the result'); 85 | }) 86 | .then(() => cli.command('')) 87 | .then(() => { 88 | t.match(cli.output, 'hello world', 'repeats the last command on '); 89 | }) 90 | .then(() => cli.command('version')) 91 | .then(() => { 92 | t.match(cli.output, process.versions.v8, 'version prints the v8 version'); 93 | }) 94 | .then(() => cli.quit()) 95 | .then((code) => { 96 | t.equal(code, 0, 'exits with success'); 97 | }); 98 | }); 99 | 100 | test('run after quit / restart', (t) => { 101 | const script = Path.join('examples', 'three-lines.js'); 102 | const cli = startCLI([script]); 103 | 104 | function onFatal(error) { 105 | cli.quit(); 106 | throw error; 107 | } 108 | 109 | return cli.waitForInitialBreak() 110 | .then(() => cli.waitForPrompt()) 111 | .then(() => cli.stepCommand('n')) 112 | .then(() => { 113 | t.match( 114 | cli.output, 115 | `break in ${script}:2`, 116 | 'steps to the 2nd line'); 117 | }) 118 | .then(() => cli.command('cont')) 119 | .then(() => cli.waitFor(/disconnect/)) 120 | .then(() => { 121 | t.match( 122 | cli.output, 123 | 'Waiting for the debugger to disconnect', 124 | 'the child was done'); 125 | }) 126 | .then(() => { 127 | // On windows the socket won't close by itself 128 | return cli.command('kill'); 129 | }) 130 | .then(() => cli.command('cont')) 131 | .then(() => cli.waitFor(/start the app/)) 132 | .then(() => { 133 | t.match(cli.output, 'Use `run` to start the app again'); 134 | }) 135 | .then(() => cli.stepCommand('run')) 136 | .then(() => cli.waitForInitialBreak()) 137 | .then(() => cli.waitForPrompt()) 138 | .then(() => { 139 | t.match( 140 | cli.breakInfo, 141 | { filename: script, line: 1 }, 142 | 'is back at the beginning'); 143 | }) 144 | .then(() => cli.stepCommand('n')) 145 | .then(() => { 146 | t.match( 147 | cli.breakInfo, 148 | { filename: script, line: 2 }, 149 | 'steps to the 2nd line'); 150 | }) 151 | .then(() => cli.stepCommand('restart')) 152 | .then(() => cli.waitForInitialBreak()) 153 | .then(() => { 154 | t.match( 155 | cli.breakInfo, 156 | { filename: script, line: 1 }, 157 | 'is back at the beginning'); 158 | }) 159 | .then(() => cli.command('kill')) 160 | .then(() => cli.command('cont')) 161 | .then(() => cli.waitFor(/start the app/)) 162 | .then(() => { 163 | t.match(cli.output, 'Use `run` to start the app again'); 164 | }) 165 | .then(() => cli.stepCommand('run')) 166 | .then(() => cli.waitForInitialBreak()) 167 | .then(() => cli.waitForPrompt()) 168 | .then(() => { 169 | t.match( 170 | cli.breakInfo, 171 | { filename: script, line: 1 }, 172 | 'is back at the beginning'); 173 | }) 174 | .then(() => cli.quit()) 175 | .then(null, onFatal); 176 | }); 177 | --------------------------------------------------------------------------------