├── .gitignore ├── .travis.yml ├── CHANGES.md ├── LICENSE.txt ├── Makefile ├── README.md ├── examples ├── child.js ├── event-log.js ├── hello.js └── object-mode.js ├── lib └── trace-event.ts ├── package.json ├── test └── basics.test.js └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /tmp 2 | /node_modules 3 | package-lock.json 4 | yarn.lock 5 | /npm-debug.log 6 | /examples/*.log 7 | /examples/*.json 8 | 9 | *.log 10 | dist/ 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | dist: trusty 3 | language: node_js 4 | 5 | script: 6 | - yarn build 7 | - yarn test 8 | - yarn check_format 9 | branches: 10 | only: 11 | - master 12 | 13 | cache: 14 | yarn: true 15 | 16 | matrix: 17 | include: 18 | - os: linux 19 | node_js: "8" 20 | - os: linux 21 | node_js: "6" 22 | fast_finish: true 23 | -------------------------------------------------------------------------------- /CHANGES.md: -------------------------------------------------------------------------------- 1 | # node-trace-event changelog 2 | 3 | ## 1.3.1 (not yet released) 4 | 5 | (nothing yet) 6 | 7 | 8 | ## 1.3.0 9 | 10 | - Add `.child()` option to `trace_event.createBunyanTracer()` object. 11 | 12 | 13 | ## 1.2.0 14 | 15 | - Add `trace_event.createBunyanLogger()` usage for some sugar. See the 16 | README.md for details. 17 | 18 | 19 | ## 1.1.0 20 | 21 | - Rename to 'trace-event', which is a much more accurate name. 22 | 23 | 24 | ## 1.0.0 25 | 26 | First release. 27 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | # This is the MIT license 2 | 3 | Copyright (c) 2015 Joyent Inc. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a 6 | copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included 14 | in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 17 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 20 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 21 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 22 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | TAPE = ./node_modules/.bin/tape 3 | JSSTYLE_FILES := $(shell find lib test -name "*.js") 4 | 5 | 6 | all $(TAPE): 7 | npm install 8 | 9 | .PHONY: clean 10 | clean: 11 | rm -rf examples/*.json examples/*.log 12 | 13 | .PHONY: distclean 14 | distclean: clean 15 | rm -rf node_modules 16 | 17 | .PHONY: test 18 | test: | $(TAPE) 19 | $(TAPE) test/*.test.js 20 | 21 | .PHONY: check-jsstyle 22 | check-jsstyle: $(JSSTYLE_FILES) 23 | ./tools/jsstyle -o indent=4,doxygen,unparenthesized-return=0,blank-after-start-comment=0,leading-right-paren-ok $(JSSTYLE_FILES) 24 | 25 | .PHONY: check 26 | check: check-jsstyle 27 | @echo "Check ok." 28 | 29 | # Ensure CHANGES.md and package.json have the same version. 30 | .PHONY: versioncheck 31 | versioncheck: 32 | @echo version is: $(shell cat package.json | json version) 33 | [[ `cat package.json | json version` == `grep '^## ' CHANGES.md | head -1 | awk '{print $$2}'` ]] 34 | 35 | .PHONY: cutarelease 36 | cutarelease: versioncheck 37 | [[ `git status | tail -n1` == "nothing to commit, working directory clean" ]] 38 | ./tools/cutarelease.py -p trace-event -f package.json 39 | 40 | .PHONY: git-hooks 41 | git-hooks: 42 | [[ -e .git/hooks/pre-commit ]] || ln -s ../../tools/pre-commit.sh .git/hooks/pre-commit 43 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/samccone/chrome-trace-event.svg?branch=master)](https://travis-ci.org/samccone/chrome-trace-event) 2 | 3 | chrome-trace-event: A node library for creating trace event logs of program 4 | execution according to [Google's Trace Event 5 | format](https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU). 6 | These logs can then be visualized with 7 | [trace-viewer](https://github.com/google/trace-viewer) or chrome devtools to grok one's programs. 8 | 9 | # Install 10 | 11 | npm install chrome-trace-event 12 | 13 | # Usage 14 | 15 | ```javascript 16 | const Trace = require("chrome-trace-event").Tracer; 17 | const trace = new Trace({ 18 | noStream: true 19 | }); 20 | trace.pipe(fs.createWriteStream(outPath)); 21 | trace.flush(); 22 | ``` 23 | 24 | # Links 25 | 26 | * https://github.com/google/trace-viewer/wiki 27 | * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU 28 | 29 | # License 30 | 31 | MIT. See LICENSE.txt. 32 | -------------------------------------------------------------------------------- /examples/child.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Show `evt.child()` usage for more practical usage. `.child(fields)` 3 | * allows you to bind common fields to emited events. 4 | */ 5 | 6 | var fs = require("fs"); 7 | 8 | var EVT = new (require("../dist/trace-event")).Tracer(); 9 | EVT.pipe(process.stdout); 10 | 11 | function doSubTaskA(opts, cb) { 12 | var evt = EVT.child({ id: opts.id, name: "doSubTaskA" }); 13 | evt.begin(); 14 | setTimeout(function() { 15 | // ... 16 | evt.end(); 17 | cb(); 18 | }, Math.floor(Math.random() * 2000)); 19 | } 20 | 21 | function doSomething(opts, cb) { 22 | var evt = EVT.child({ id: opts.id, name: "doSomething" }); 23 | evt.begin(); 24 | setTimeout(function() { 25 | // ... 26 | doSubTaskA(opts, function() { 27 | evt.end(); 28 | cb(); 29 | }); 30 | }, Math.floor(Math.random() * 2000)); 31 | } 32 | 33 | for (var i = 0; i < 5; i++) { 34 | doSomething({ id: i }, function() {}); 35 | } 36 | -------------------------------------------------------------------------------- /examples/event-log.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A simple example showing piping event traces to a 'events.log' file. 3 | * 4 | * Usage: 5 | * $ node event-log.js 6 | * hi 7 | * bye 8 | * $ cat events.log 9 | * [{"ts":213699797444,"pid":42628,"tid":42628,"ph":"b","cat":"default","args":{},"name":"doSomething","id":"abc"}, 10 | * {"ts":213700798563,"pid":42628,"tid":42628,"ph":"e","cat":"default","args":{},"name":"doSomething","id":"abc"}, 11 | */ 12 | 13 | var fs = require("fs"); 14 | 15 | var evt = new (require("../dist/trace-event")).Tracer(); 16 | evt.pipe(fs.createWriteStream("events.log")); 17 | console.log('Streaming events to "events.log"'); 18 | 19 | // Instrument code with evt.{begin|instant|end} calls. 20 | function doSomething(cb) { 21 | evt.begin({ name: "doSomething", id: "abc" }); 22 | // Takes 1s to do all this processing for "something". 23 | setTimeout(function() { 24 | evt.end({ name: "doSomething", id: "abc" }); 25 | cb(); 26 | }, 1000); 27 | } 28 | 29 | console.log("hi"); 30 | doSomething(function() { 31 | console.log("bye"); 32 | }); 33 | -------------------------------------------------------------------------------- /examples/hello.js: -------------------------------------------------------------------------------- 1 | /* 2 | * A first example showing trace-event usage. 3 | * We emit begin/end events for a single call to `doSomething()`. 4 | */ 5 | 6 | /* 7 | * First create the tracer `evt` that we'll use for instrumenting code. 8 | * 9 | * Notes: 10 | * - More realistically we'd stream these to an event log file (see 11 | * examples/event-log.js). This just shows you the default output. 12 | * - By default the emitted 'data' events are make up a JSON array of 13 | * event objects, suitable for piping directly to stdout or a file. 14 | * This format is as expected by 15 | * [`trace2html`](https://github.com/google/trace-viewer#readme). 16 | * - See examples/object-mode.js for raw event objects. 17 | * - See examples/child.js for a larger example. 18 | */ 19 | var evt = new (require("../dist/trace-event")).Tracer(); 20 | 21 | evt.on("data", function(data) { 22 | console.log("EVENT: %j", data); 23 | }); 24 | 25 | // Instrument code with evt.{begin|instant|end} calls. 26 | function doSomething(cb) { 27 | evt.begin({ name: "doSomething", id: "1" }); 28 | // Takes 1s to do all this processing for "something". 29 | setTimeout(function() { 30 | evt.end({ name: "doSomething", id: "1" }); 31 | cb(); 32 | }, 1000); 33 | } 34 | 35 | console.log("hi"); 36 | doSomething(function() { 37 | console.log("bye"); 38 | }); 39 | -------------------------------------------------------------------------------- /examples/object-mode.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Show that `{objectMode: true}` is available on a Tracer 3 | * to get raw JSON event object output. 4 | */ 5 | 6 | var evt = new (require("../dist/trace-event")).Tracer({ objectMode: true }); 7 | evt.on("data", function(ev) { 8 | console.log('EVENT (type "%s"): %j', typeof ev, ev); 9 | }); 10 | 11 | evt.begin({ name: "doSomething", id: "abc" }); 12 | evt.end({ name: "doSomething", id: "abc" }); 13 | -------------------------------------------------------------------------------- /lib/trace-event.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * trace-event - A library to create a trace of your node app per 3 | * Google's Trace Event format: 4 | * // JSSTYLED 5 | * https://docs.google.com/document/d/1CvAClvFfyA5R-PhYUmn5OOQtYMH4h6I0nSsKchNAySU 6 | */ 7 | 8 | import { Readable as ReadableStream, ReadableOptions } from "stream"; 9 | // ---- internal support stuff 10 | 11 | export interface Event { 12 | ts: number; 13 | pid: number; 14 | tid: number; 15 | /** event phase */ 16 | ph?: string; 17 | [otherData: string]: any; 18 | } 19 | 20 | function evCommon(): Event { 21 | var hrtime = process.hrtime(); // [seconds, nanoseconds] 22 | var ts = hrtime[0] * 1000000 + Math.round(hrtime[1] / 1000); // microseconds 23 | return { 24 | ts, 25 | pid: process.pid, 26 | tid: process.pid // no meaningful tid for node.js 27 | }; 28 | } 29 | 30 | // ---- Tracer 31 | 32 | export interface Fields { 33 | cat?: any; 34 | args?: any; 35 | [filedName: string]: any; 36 | } 37 | 38 | export interface TracerOptions { 39 | parent?: Tracer | null; 40 | fields?: Fields | null; 41 | objectMode?: boolean | null; 42 | noStream?: boolean; 43 | } 44 | 45 | export class Tracer extends ReadableStream { 46 | private _objectMode!: boolean; 47 | /** Node Stream internal APIs */ 48 | private _push: any; 49 | private firstPush?: boolean; 50 | private noStream: boolean = false; 51 | private events: Event[] = []; 52 | private parent!: Tracer | null | undefined; 53 | private fields!: Fields | null | undefined; 54 | 55 | constructor(opts: TracerOptions = {}) { 56 | super(); 57 | if (typeof opts !== "object") { 58 | throw new Error("Invalid options passed (must be an object)"); 59 | } 60 | 61 | if (opts.parent != null && typeof opts.parent !== "object") { 62 | throw new Error("Invalid option (parent) passed (must be an object)"); 63 | } 64 | 65 | if (opts.fields != null && typeof opts.fields !== "object") { 66 | throw new Error("Invalid option (fields) passed (must be an object)"); 67 | } 68 | 69 | if ( 70 | opts.objectMode != null && 71 | (opts.objectMode !== true && opts.objectMode !== false) 72 | ) { 73 | throw new Error( 74 | "Invalid option (objectsMode) passed (must be a boolean)" 75 | ); 76 | } 77 | 78 | this.noStream = opts.noStream || false; 79 | this.parent = opts.parent; 80 | 81 | if (this.parent) { 82 | this.fields = Object.assign({}, opts.parent && opts.parent.fields); 83 | } else { 84 | this.fields = {}; 85 | } 86 | if (opts.fields) { 87 | Object.assign(this.fields, opts.fields); 88 | } 89 | if (!this.fields.cat) { 90 | // trace-viewer *requires* `cat`, so let's have a fallback. 91 | this.fields.cat = "default"; 92 | } else if (Array.isArray(this.fields.cat)) { 93 | this.fields.cat = this.fields.cat.join(","); 94 | } 95 | if (!this.fields.args) { 96 | // trace-viewer *requires* `args`, so let's have a fallback. 97 | this.fields.args = {}; 98 | } 99 | 100 | if (this.parent) { 101 | // TODO: Not calling Readable ctor here. Does that cause probs? 102 | // Probably if trying to pipe from the child. 103 | // Might want a serpate TracerChild class for these guys. 104 | this._push = this.parent._push.bind(this.parent); 105 | } else { 106 | this._objectMode = Boolean(opts.objectMode); 107 | var streamOpts: ReadableOptions = { objectMode: this._objectMode }; 108 | if (this._objectMode) { 109 | this._push = this.push; 110 | } else { 111 | this._push = this._pushString; 112 | streamOpts.encoding = "utf8"; 113 | } 114 | 115 | ReadableStream.call(this, streamOpts); 116 | } 117 | } 118 | 119 | /** 120 | * If in no streamMode in order to flush out the trace 121 | * you need to call flush. 122 | */ 123 | public flush() { 124 | if (this.noStream === true) { 125 | for (const evt of this.events) { 126 | this._push(evt); 127 | } 128 | this._flush(); 129 | } 130 | } 131 | 132 | _read(_: number) {} 133 | 134 | private _pushString(ev: Event) { 135 | var separator = ""; 136 | if (!this.firstPush) { 137 | this.push("["); 138 | this.firstPush = true; 139 | } else { 140 | separator = ",\n"; 141 | } 142 | this.push(separator + JSON.stringify(ev), "utf8"); 143 | } 144 | 145 | private _flush() { 146 | if (!this._objectMode) { 147 | this.push("]"); 148 | } 149 | } 150 | 151 | public child(fields: Fields) { 152 | return new Tracer({ 153 | parent: this, 154 | fields: fields 155 | }); 156 | } 157 | 158 | public begin(fields: Fields) { 159 | return this.mkEventFunc("B")(fields); 160 | } 161 | 162 | public end(fields: Fields) { 163 | return this.mkEventFunc("E")(fields); 164 | } 165 | 166 | public completeEvent(fields: Fields) { 167 | return this.mkEventFunc("X")(fields); 168 | } 169 | 170 | public instantEvent(fields: Fields) { 171 | return this.mkEventFunc("I")(fields); 172 | } 173 | 174 | public mkEventFunc(ph: string) { 175 | return (fields: Fields) => { 176 | var ev = evCommon(); 177 | // Assign the event phase. 178 | ev.ph = ph; 179 | 180 | if (fields) { 181 | if (typeof fields === "string") { 182 | ev.name = fields; 183 | } else { 184 | for (const k of Object.keys(fields)) { 185 | if (k === "cat") { 186 | ev.cat = fields.cat.join(","); 187 | } else { 188 | ev[k] = fields[k]; 189 | } 190 | } 191 | } 192 | } 193 | 194 | if (!this.noStream) { 195 | this._push(ev); 196 | } else { 197 | this.events.push(ev); 198 | } 199 | }; 200 | } 201 | } 202 | 203 | /* 204 | * These correspond to the "Async events" in the Trace Events doc. 205 | * 206 | * Required fields: 207 | * - name 208 | * - id 209 | * 210 | * Optional fields: 211 | * - cat (array) 212 | * - args (object) 213 | * - TODO: stack fields, other optional fields? 214 | * 215 | * Dev Note: We don't explicitly assert that correct fields are 216 | * used for speed (premature optimization alert!). 217 | */ 218 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chrome-trace-event", 3 | "description": "A library to create a trace of your node app per Google's Trace Event format.", 4 | "license": "MIT", 5 | "version": "1.0.4", 6 | "author": "Trent Mick, Sam Saccone", 7 | "keywords": [ 8 | "trace-event", 9 | "trace", 10 | "event", 11 | "trace-viewer", 12 | "google" 13 | ], 14 | "repository": { 15 | "url": "github:samccone/chrome-trace-event" 16 | }, 17 | "main": "./dist/trace-event.js", 18 | "typings": "./dist/trace-event.d.ts", 19 | "dependencies": {}, 20 | "devDependencies": { 21 | "@types/node": "*", 22 | "prettier": "^1.12.1", 23 | "tape": "4.8.0", 24 | "typescript": "^4.2.4" 25 | }, 26 | "engines": { 27 | "node": ">=6.0" 28 | }, 29 | "files": [ 30 | "dist", 31 | "CHANGES.md" 32 | ], 33 | "scripts": { 34 | "build": "tsc", 35 | "check_format": "prettier -l lib/** test/** examples/**", 36 | "test": "tape test/*.test.js" 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /test/basics.test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Catch all test file for trace-event. 3 | */ 4 | 5 | var test = require("tape"); 6 | var trace_event = require("../dist/trace-event"); 7 | 8 | // --- Tests 9 | test("exports", function(t) { 10 | t.ok(new trace_event.Tracer(), ""); 11 | t.end(); 12 | }); 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2015" 7 | ], 8 | "declaration": true, 9 | "sourceMap": false, 10 | "outDir": "./dist", 11 | "importHelpers": false, 12 | "strict": true, 13 | "noImplicitAny": true, 14 | "strictNullChecks": true, 15 | "strictFunctionTypes": true, 16 | "strictPropertyInitialization": true, 17 | "noImplicitThis": true, 18 | "alwaysStrict": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "noImplicitReturns": true, 22 | "noFallthroughCasesInSwitch": true, 23 | "types": [ 24 | "node" 25 | ], 26 | "esModuleInterop": true 27 | } 28 | } 29 | --------------------------------------------------------------------------------