├── .editorconfig ├── .gitignore ├── AUTHORS ├── README.md ├── binding.gyp ├── example.js ├── index.js ├── lib ├── console.js ├── debugger │ ├── debugger-client.js │ ├── index.js │ └── translator.js ├── helpers.js ├── index.js ├── inspector.js ├── network.js ├── page.js ├── profiler.js ├── runtime.js ├── timeline.js └── v8-profiler.js ├── package.json ├── src ├── cpu_profiler.cc ├── cpu_profiler.h ├── heap_profiler.cc ├── heap_profiler.h ├── profile.cc ├── profile.h ├── profile_node.cc ├── profile_node.h ├── profiler.cc ├── readme.md ├── snapshot.cc ├── snapshot.h └── wscript ├── webkit-devtools-agent.js └── wscript /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | ; Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | charset = utf-8 8 | 9 | ; JS 10 | [*.js] 11 | indent_style = space 12 | indent_size = 4 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | build 4 | .lock-wscript 5 | *.swo 6 | *.swp 7 | /profiler.node 8 | 9 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | 118 Camilo Aguilar 2 | 3 marco.minetti 3 | 1 Dan MacTough 4 | 1 Steven Kabbes 5 | 1 Vadim Baryshev 6 | 1 anprogrammer 7 | 1 marcominetti 8 | 1 Andrew Bradley 9 | 1 waffle.io 10 | 1 Brock Pytlik 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node Webkit Agent 2 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/c4milo/node-webkit-agent?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 3 | [![Stories in Ready](https://badge.waffle.io/c4milo/node-webkit-agent.png?label=ready)](https://waffle.io/c4milo/node-webkit-agent) 4 | 5 | 6 | This module is an implementation of 7 | [Chrome developer tools protocol](https://developer.chrome.com/devtools/docs/protocol/1.0/index). 8 | It is still pretty much a work in progress and only heap and CPU profilers are working right now. Help is wanted to finish implementing debugger, networking and console agents as well as a implementing from scratch a flamegraphs agent. 9 | 10 | ## Features 11 | This module allows you to debug and profile remotely your nodejs applications 12 | leveraging the following features by re-using the [built-in devtools front-end](https://developer.chrome.com/devtools) 13 | that comes with any webkit-based browsers such as Chrome or Safari. 14 | 15 | * Remote heap and CPU profiling 16 | * More agents are coming. 17 | 18 | ## Installation 19 | `npm install webkit-devtools-agent` 20 | 21 | ## Usage 22 | From within your Node application, just require the module as usual, and start the agent. For example: 23 | 24 | ```javascript 25 | var agent = require('webkit-devtools-agent'); 26 | agent.start() 27 | ``` 28 | 29 | Once the agent is initiated, use any of the following hosted Devtools UIs to profile your application. 30 | 31 | **Node v0.6.x:** http://c4milo.github.io/node-webkit-agent/19.0.1084.46/inspector.html?host=localhost:9999&page=0 32 | 33 | **Node v0.8.x and v0.10.x:** http://c4milo.github.io/node-webkit-agent/26.0.1410.65/inspector.html?host=localhost:9999&page=0 34 | 35 | You can also change the agent port and binding address where it listen to by setting up the following parameters: 36 | 37 | * **port:** The port for the Devtools UI to connect to using websockets. Set to `9999` by default 38 | * **bind_to:** The host or IP address where the websockets service is going to be bound to. Set to `127.0.0.1` by default 39 | * **ipc_port:** IPC port for internal use. Set to `3333` by default 40 | * **verbose:** Whether to log more information or not. Set to `false` by default 41 | 42 | See the example below to understand better how to set these parameters. 43 | 44 | ### Example 45 | A more elaborated example looks like: 46 | 47 | ```javascript 48 | 49 | var agent = require('./index'); 50 | 51 | // Assume this HTTP service is your service 52 | var http = require('http'); 53 | http.createServer(function (req, res) { 54 | console.log('boooo'); 55 | res.writeHead(200, {'Content-Type': 'text/plain'}); 56 | res.end('Hello World\n'); 57 | }).listen(9000, '127.0.0.1'); 58 | console.log('Server running at http://127.0.0.1:9000/ , pid-> ' + process.pid); 59 | 60 | // Now let's have a signal handler for SIGUSR2 that is going 61 | // to activate the devtools agent. You can use any other means to activate 62 | // the agent, not just signal handlers. 63 | process.on('SIGUSR2', function () { 64 | if (agent.server) { 65 | agent.stop(); 66 | } else { 67 | agent.start({ 68 | port: 9999, 69 | bind_to: '0.0.0.0', 70 | ipc_port: 3333, 71 | verbose: true 72 | }); 73 | } 74 | }); 75 | 76 | 77 | ``` 78 | 79 | ## ABI compatibility 80 | [ABI](http://en.wikipedia.org/wiki/Application_binary_interface) compatibility breaks between Node v0.6.x and v0.8.x. Therefore, if you switch Node versions you would have to re-install `webkit-devtools-agent` again. See issue [#11](https://github.com/c4milo/node-webkit-agent/issues/11). 81 | 82 | ## Screenshots 83 | ### CPU profiling 84 | ![Screenshot](https://i.cloudup.com/YysNMMGE3a.png) 85 | 86 | ### Heap Profiling 87 | ![Screenshot](https://i.cloudup.com/WR5MKG6i02.png) 88 | 89 | For detailed information on debugging memory leaks using devtools' heap comparisons (using this module), follow [this tutorial](https://developer.chrome.com/devtools/docs/javascript-memory-profiling). 90 | 91 | 92 | Happy Debugging! 93 | 94 | ## License 95 | (The MIT License) 96 | 97 | Copyright 2014 Camilo Aguilar. All rights reserved. 98 | 99 | Permission is hereby granted, free of charge, to any person obtaining a copy 100 | of this software and associated documentation files (the "Software"), to 101 | deal in the Software without restriction, including without limitation the 102 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 103 | sell copies of the Software, and to permit persons to whom the Software is 104 | furnished to do so, subject to the following conditions: 105 | 106 | The above copyright notice and this permission notice shall be included in 107 | all copies or substantial portions of the Software. 108 | 109 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 110 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 111 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 112 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 113 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 114 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 115 | IN THE SOFTWARE. 116 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | 'targets': [ 3 | { 4 | 'target_name': 'profiler', 5 | 'include_dirs' : [ 6 | 'node_modules/nan', 7 | ], 8 | 'sources': [ 9 | 'src/cpu_profiler.cc', 10 | 'src/heap_profiler.cc', 11 | 'src/profile.cc', 12 | 'src/profile_node.cc', 13 | 'src/profiler.cc', 14 | 'src/snapshot.cc', 15 | ], 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /example.js: -------------------------------------------------------------------------------- 1 | var agent = require('./index'); 2 | 3 | // Assume this HTTP service is your service 4 | var http = require('http'); 5 | http.createServer(function (req, res) { 6 | console.log('boooo'); 7 | res.writeHead(200, {'Content-Type': 'text/plain'}); 8 | res.end('Hello World\n'); 9 | }).listen(9000, '127.0.0.1'); 10 | console.log('Server running at http://127.0.0.1:9000/ , pid-> ' + process.pid); 11 | 12 | // Now let's have a signal handler for SIGUSR2 that is going 13 | // to activate the devtools agent. You can use any other means to activate 14 | // the agent, not just signal handlers. 15 | process.on('SIGUSR2', function () { 16 | if (agent.server) { 17 | agent.stop(); 18 | } else { 19 | agent.start({ 20 | port: 9999, 21 | bind_to: '0.0.0.0', 22 | ipc_port: 3333, 23 | verbose: true 24 | }); 25 | } 26 | }); 27 | 28 | 29 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var agents = require('./lib'); 2 | var spawn = require('child_process').spawn; 3 | var WebSocketServer = require('ws').Server; 4 | 5 | /** 6 | * DevToolsAgent 7 | * @constructor 8 | **/ 9 | var DevToolsAgent = function () { 10 | this.loadedAgents = {}; 11 | this.proxy = null; 12 | this.server = null; 13 | this.socket = null; 14 | }; 15 | 16 | (function () { 17 | /** 18 | * Spawns a new process with a websockets service proxy 19 | * to serve devtools front-end requests. 20 | * 21 | * All the messages but debugging messages 22 | * are sent to the main process. Debugger Agent lives in this proxy. 23 | * 24 | * @api private 25 | **/ 26 | this.spawnProxy = function () { 27 | var self = this; 28 | 29 | //Parent PID for the proxy to know to whom to send the SIGUSR1 signal 30 | process.env.PARENT_PID = process.pid; 31 | 32 | this.proxy = spawn('node', [__dirname + '/webkit-devtools-agent.js', 33 | this.port, this.bind_to, this.ipc_port, 34 | this.verbose], process.argv, { 35 | env: process.env, 36 | cwd: __dirname 37 | }); 38 | 39 | this.proxy.stderr.setEncoding('utf8'); 40 | this.proxy.stderr.on('data', function (data) { 41 | console.error(data); 42 | }); 43 | 44 | this.proxy.stdout.setEncoding('utf8'); 45 | this.proxy.stdout.on('data', function (data) { 46 | if (this.verbose) { 47 | console.log(data); 48 | } 49 | }); 50 | }; 51 | 52 | /** 53 | * Proxy connection handler 54 | * 55 | * @param {net.Socket} socket The just opened network socket. 56 | * @api private 57 | **/ 58 | this.onProxyConnection = function (socket) { 59 | if (this.verbose) { 60 | console.log('webkit-devtools-agent: A proxy got connected.'); 61 | console.log('webkit-devtools-agent: Waiting for commands...'); 62 | } 63 | 64 | this.socket = socket; 65 | this.socket.on('message', this.onProxyData.bind(this)); 66 | this.socket.on('error', function(error) { 67 | console.error(error); 68 | }); 69 | }; 70 | 71 | /** 72 | * Handler for data events coming from the proxy process. 73 | * 74 | * @param {String} message A message coming from the proxy process. 75 | * @api private 76 | **/ 77 | this.onProxyData = function (message) { 78 | var self = this; 79 | 80 | try { 81 | data = JSON.parse(message); 82 | } catch(e) { 83 | console.log(e.stack); 84 | return; 85 | } 86 | 87 | var id = data.id; 88 | var command = data.method.split('.'); 89 | var domain = this.loadedAgents[command[0]]; 90 | var method = command[1]; 91 | var params = data.params; 92 | 93 | if (!domain || !domain[method]) { 94 | console.warn('%s is not implemented', data.method); 95 | return; 96 | } 97 | 98 | domain[method](params, function(result) { 99 | var response = { 100 | id: id, 101 | result: result 102 | }; 103 | 104 | self.socket.send(JSON.stringify(response)); 105 | }); 106 | }; 107 | 108 | /** 109 | * Notification function in charge of sending events 110 | * to the front-end following the protocol specified 111 | * at https://developers.google.com/chrome-developer-tools/docs/protocol/1.0 112 | * 113 | * @param {Object} A notification object that follows devtools protocol 1.0 114 | * @api private 115 | **/ 116 | this.notify = function (notification) { 117 | if (!this.socket) return; 118 | this.socket.send(JSON.stringify(notification)); 119 | }; 120 | 121 | /** 122 | * Loads every agent required at the top of this file. 123 | * @private 124 | **/ 125 | this.loadAgents = function () { 126 | var runtimeAgent = new agents.Runtime(this.notify.bind(this)); 127 | 128 | for (var agent in agents) { 129 | if (typeof agents[agent] == 'function' && agent != 'Runtime') { 130 | this.loadedAgents[agent] = new agents[agent](this.notify.bind(this), runtimeAgent); 131 | } 132 | } 133 | this.loadedAgents.Runtime = runtimeAgent; 134 | }; 135 | 136 | /** 137 | * Starts node-webkit-agent 138 | * 139 | * @api public 140 | **/ 141 | this.start = function (config) { 142 | var self = this; 143 | config = config || {} 144 | this.port = config.port || 9999; 145 | this.bind_to = config.bind_to || 'localhost'; 146 | this.ipc_port = config.ipc_port || 3333; 147 | this.verbose = config.verbose || false; 148 | 149 | if (this.server) { 150 | return; 151 | } 152 | 153 | this.server = new WebSocketServer({ 154 | port: this.ipc_port, 155 | bind_to: this.bind_to 156 | }); 157 | 158 | this.server.on('listening', function() { 159 | if (this.verbose) { 160 | console.log('webkit-devtools-agent: Spawning websocket ' + 161 | 'service process...'); 162 | } 163 | 164 | //Spawns webkit devtools proxy / websockets server 165 | self.spawnProxy(); 166 | 167 | self.loadAgents(); 168 | }); 169 | 170 | this.server.on('connection', this.onProxyConnection.bind(this)); 171 | }; 172 | 173 | /** 174 | * Stops node-webkit-agent 175 | * 176 | * @api public 177 | **/ 178 | this.stop = function () { 179 | if (this.socket) { 180 | this.socket.close(); 181 | this.socket = null; 182 | } 183 | 184 | if (this.proxy && this.proxy.pid) { 185 | if (this.verbose) { 186 | console.log('webkit-devtools-agent: Terminating websockets service' + 187 | ' with PID: ' + this.proxy.pid + '...'); 188 | } 189 | 190 | process.kill(this.proxy.pid, 'SIGTERM'); 191 | } 192 | 193 | if (this.server) { 194 | this.server.close(); 195 | this.server = null; 196 | 197 | if (this.verbose) { 198 | console.log('webkit-devtools-agent: stopped'); 199 | } 200 | } 201 | }; 202 | }).call(DevToolsAgent.prototype); 203 | 204 | module.exports = new DevToolsAgent(); 205 | -------------------------------------------------------------------------------- /lib/console.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | function ConsoleAgent(notify, runtimeAgent) { 4 | var self = this; 5 | this.notify = notify; 6 | this.runtimeAgent = runtimeAgent; 7 | this.enabled = false; 8 | this.messages = []; 9 | 10 | ['log', 'warn', 'info', 'error', 'dir', 'debug'].forEach(function(level) { 11 | var ref = console[level]; 12 | if (typeof ref === 'function') { 13 | console[level] = function () { 14 | ref.apply(this, arguments); 15 | 16 | var message = { 17 | method: 'Console.messageAdded', 18 | params: { 19 | message: { 20 | text: util.format.apply(this, arguments), 21 | level: level == 'warn' ? 'warning' : level, 22 | source: 'console-api' 23 | } 24 | } 25 | }; 26 | 27 | //TODO make it aware of RemoteObjects so 28 | //that the console in the frontend can show us its shinny 29 | //dropdown 30 | /*if (level == 'dir') { 31 | message.params.message.type = level; 32 | }*/ 33 | 34 | //TODO save messages when this agent is disabled. 35 | //self.messages.push(message); 36 | notify(message); 37 | }; 38 | } 39 | }); 40 | } 41 | 42 | (function() { 43 | this.enable = function(params, sendResult) { 44 | for(var i = 0, len = this.messages.length; i < len; i++) { 45 | this.notify(this.messages[i]); 46 | } 47 | sendResult({result: this.enabled}); 48 | }; 49 | 50 | this.disable = function(params, sendResult) { 51 | this.enabled = false; 52 | sendResult({}); 53 | }; 54 | 55 | this.clearMessages = function(params, sendResult) { 56 | this.messages = []; 57 | sendResult({}); 58 | }; 59 | 60 | this.setMonitoringXHREnabled = function(params, sendResult) { 61 | sendResult({}); 62 | }; 63 | 64 | this.addInspectedHeapObject = function(params, sendResult) { 65 | sendResult({}); 66 | }; 67 | 68 | }).call(ConsoleAgent.prototype); 69 | 70 | module.exports = ConsoleAgent; 71 | 72 | 73 | -------------------------------------------------------------------------------- /lib/debugger/debugger-client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var net = require('net'); 5 | var util = require('util'); 6 | var translator = require('./translator'); 7 | var V8Protocol = require('_debugger').Protocol; 8 | 9 | /** 10 | * DebuggerClient 11 | * This file implements http://code.google.com/p/v8/wiki/DebuggerProtocol 12 | * along with the `translator` object 13 | * 14 | * @param {Function} notify Notification function to send events 15 | * straight to DevTools frontend. 16 | * @constructor 17 | */ 18 | 19 | var DebuggerClient = module.exports = function(notify) { 20 | this.connection = null; 21 | this.connected = false; 22 | this.reqSequence = 0; 23 | this.requests = {}; 24 | this.notify = notify; 25 | this.queue = []; 26 | this.port = 5858; 27 | this.protocol = null; 28 | }; 29 | 30 | (function() { 31 | /** 32 | * Callback function that gets invoked once 33 | * a connection to the debuggee process is established. 34 | * 35 | * @api private 36 | */ 37 | this.onConnect = function() { 38 | this.connected = true; 39 | this.flushQueue(); 40 | }; 41 | 42 | /** 43 | * This callback function gets invoked by 44 | * nodejs v8 protocol implementation 45 | * once it receives the entire response 46 | * from v8 debug agent. 47 | * 48 | * @param {Object} response The response serialized 49 | * by the nodejs implementation of the v8 remote debugger protocol. 50 | * @api private 51 | */ 52 | this.onV8Response = function(response) { 53 | var payload = response.body; 54 | //console.log(payload); 55 | 56 | if (payload.type == 'event') { 57 | if (!translator[payload.event]) { 58 | console.warn('webkit-devtools-agent: Translator function ' + 59 | ' not found for event: ' + payload.event); 60 | return; 61 | } 62 | 63 | this.notify(translator[payload.event](payload)); 64 | } else if (payload.type == 'response') { 65 | var callback = this.requests[payload.request_seq]; 66 | if (callback) { 67 | callback(translator[payload.command](payload)); 68 | 69 | delete this.requests[payload.request_seq]; 70 | } else { 71 | console.warn('webkit-devtools-agent: Unexpected '+ 72 | 'message was received, there is no callback function '+ 73 | ' to handle it :( :'); 74 | console.warn(payload); 75 | } 76 | } else { 77 | console.warn('webkit-devtools-agent: Unrecognized ' + 78 | 'message type received by DebuggerAgent: '); 79 | console.warn(payload); 80 | } 81 | }; 82 | /** 83 | * Callback function to received debug data coming out of 84 | * the debugee process. 85 | * 86 | * @param {Buffer} data Data sent by v8 debug agent 87 | * @api private 88 | */ 89 | 90 | this.onData = function(data) { 91 | this.protocol.execute(data); 92 | }; 93 | 94 | /** 95 | * Callback function for `close` events in the 96 | * connection to the debugee process. 97 | * 98 | * @api private 99 | */ 100 | this.onClose = function() { 101 | this.connected = false; 102 | this.connection = null; 103 | }; 104 | 105 | /** 106 | * Callback function for `error` events in the 107 | * connection to the debuggee process. 108 | * 109 | * @param {Buffer} error JSON containing the error. 110 | * @api private 111 | */ 112 | this.onError = function(error) { 113 | console.error(error); 114 | }; 115 | 116 | /** 117 | * Flushes the internal queue, sending 118 | * all the queued messages if 119 | * there is a valid connection to the 120 | * debugee process. 121 | * 122 | * @api private 123 | */ 124 | this.flushQueue = function() { 125 | if (!this.connected) { 126 | return; 127 | } 128 | 129 | var queue = this.queue; 130 | for (var i = 0, len = queue.length; i < len; i++) { 131 | var message = JSON.stringify(queue[i]); 132 | 133 | this.connection.write('Content-Length: ' + 134 | message.length + '\r\n\r\n' + message) 135 | 136 | //removes message from the queue 137 | queue.splice(i, 1); 138 | } 139 | }; 140 | 141 | /** 142 | * Sends out message to the debugee process 143 | * through an internal queue. 144 | * 145 | * @param {Object} data Object to be sent 146 | * @param {Function} callback Callback function 147 | * to invoke once the debug agent, in the debugee process, 148 | * get back to us with a response. 149 | * 150 | * @api public 151 | */ 152 | this.send = function(data, callback) { 153 | this.reqSequence++; 154 | data.seq = this.reqSequence; 155 | 156 | /** 157 | * Once the response comes back, the 158 | * `callback` function is going to be invoked and 159 | * removed from the list of pending response handlers. 160 | */ 161 | this.requests[data.seq] = callback; 162 | 163 | /** 164 | * This queue avoids some race conditions, 165 | * especially in the devtools front-end 166 | * initialization. 167 | */ 168 | this.queue.push(data); 169 | 170 | //Flushes only if this.connected is true 171 | this.flushQueue(); 172 | 173 | if (this.queue.length > 50) { 174 | console.warn('webkit-devtools-agent: Debugger client ' + 175 | 'queue is too big. It could mean that the client was unable ' + 176 | 'to establish a connection with the v8 debug agent. ' + 177 | 'Restart the agent and start your debugging session again.'); 178 | } 179 | }; 180 | 181 | /** 182 | * Establishes a connection to the 183 | * Debug Agent of the debuggee process and 184 | * sets up the callbacks to some events. 185 | * 186 | * @param {Function} callback Callback function 187 | * @api public 188 | */ 189 | this.connect = function(callback) { 190 | this.protocol = new V8Protocol(); 191 | this.protocol.onResponse = this.onV8Response.bind(this); 192 | 193 | this.connection = net.connect(this.port); 194 | this.connection.setEncoding('utf8'); 195 | 196 | this.connection.on('connect', this.onConnect.bind(this)); 197 | this.connection.on('data', this.onData.bind(this)); 198 | this.connection.on('close', this.onClose.bind(this)); 199 | this.connection.on('error', this.onError.bind(this)); 200 | 201 | callback(translator.emptyResult()); 202 | }; 203 | 204 | /** 205 | * Disconnects from the debuggee process 206 | * 207 | * @param {Function} callback Callback function 208 | * @api public 209 | */ 210 | this.disconnect = function(callback) { 211 | this.connection.end(); 212 | callback(); 213 | }; 214 | 215 | /** 216 | * Defines pause on exceptions state. 217 | * Can be set to stop on `all` exceptions, 218 | * `uncaught` exceptions or no exceptions. 219 | * Initial pause on exceptions state is none. 220 | * 221 | * Gotcha: V8 remote debugger protocol doesn't understand `none` as 222 | * break type, so we need to send `uncaught` and `enabled` equaling false 223 | * to represent `none`. 224 | * 225 | * @param {String} exceptionBreak Type of exception break 226 | * it can be `all` or `uncaught`. 227 | * @param {Function} callback Callback function to send back 228 | * the answer to this command. The response is returned 229 | * in the DevTools Remote Protocol format specified in: 230 | * https://developers.google.com/chrome-developer-tools/docs/protocol/1.0/debugger#command-setPauseOnExceptions 231 | * @api public 232 | */ 233 | this.setExceptionBreak = function(exceptionBreak, callback) { 234 | var request = { 235 | type: 'request', 236 | command: 'setexceptionbreak', 237 | arguments: { 238 | type: exceptionBreak === 'none' ? 'uncaught': exceptionBreak, 239 | enabled: exceptionBreak == 'none' ? false : true 240 | } 241 | }; 242 | 243 | this.send(request, callback); 244 | }; 245 | 246 | this.getScripts = function() { 247 | var self = this; 248 | var request = { 249 | type: 'request', 250 | command: 'scripts', 251 | arguments: { 252 | types: 4, //normal scripts 253 | includeSource: true 254 | } 255 | }; 256 | 257 | this.send(request, function(scripts) { 258 | for (var i = 0, len = scripts.length; i < len; i++) { 259 | self.notify(scripts[i]); 260 | } 261 | }); 262 | }; 263 | }).call(DebuggerClient.prototype); 264 | 265 | -------------------------------------------------------------------------------- /lib/debugger/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Module dependencies. 3 | */ 4 | var DebuggerClient = require('./debugger-client'); 5 | 6 | /** 7 | * DebuggerAgent 8 | * 9 | * This file implements 10 | * https://developers.google.com/chrome-developer-tools/docs/protocol/1.0/debugger 11 | * 12 | * @param {Number} debuggee Process ID of the debuggee nodejs process. 13 | * @constructor 14 | */ 15 | var DebuggerAgent = module.exports = function(debuggee) { 16 | this.enabled = false; 17 | this.debuggee = debuggee; //process being debugged 18 | this.pauseOnExceptions = 'none'; //possible values are: none, all, uncaught 19 | }; 20 | 21 | (function(){ 22 | /** 23 | * Initializes agent. 24 | * @api public 25 | */ 26 | this.initialize = function(notify) { 27 | this.notify = notify; 28 | this.client = new DebuggerClient(this.notify); 29 | }; 30 | 31 | this.setPauseOnExceptions = function(params, sendResult) { 32 | var self = this; 33 | this.pauseOnExceptions = params.state; 34 | 35 | self.client.setExceptionBreak(this.pauseOnExceptions, function(data) { 36 | sendResult(data); 37 | }); 38 | }; 39 | 40 | this.causesRecompilation = function(params, sendResult) { 41 | sendResult({result: false}); 42 | }; 43 | 44 | this.supportsNativeBreakpoints = function(params, sendResult) { 45 | sendResult({result: false}); 46 | }; 47 | 48 | this.canSetScriptSource = function(params, sendResult) { 49 | sendResult({result: true}); 50 | }; 51 | 52 | this.enable = function(params, sendResult) { 53 | var self = this; 54 | if (this.enabled) { 55 | return; 56 | } 57 | 58 | process.kill(this.debuggee, 'SIGUSR1'); 59 | 60 | setTimeout(function() { 61 | self.client.connect(function(data) { 62 | self.client.getScripts(); 63 | self.enabled = true; 64 | }); 65 | }, 1000); 66 | }; 67 | 68 | this.disable = function(params, sendResult) { 69 | var self = this; 70 | this.client.disconnect(function(result) { 71 | self.enabled = false; 72 | sendResult(result); 73 | }); 74 | }; 75 | 76 | this.setBreakpointByUrl = function(params, sendResult) { 77 | var self = this; 78 | this.client.setBreakpoint(params, function(breakId) { 79 | sendResult(); 80 | }); 81 | }; 82 | 83 | 84 | }).call(DebuggerAgent.prototype); 85 | 86 | -------------------------------------------------------------------------------- /lib/debugger/translator.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview 3 | * Transforms http://code.google.com/p/v8/wiki/DebuggerProtocol 4 | * to https://developers.google.com/chrome-developer-tools/docs/protocol/1.0 5 | **/ 6 | 7 | /*var formatScript = function(script) { 8 | var lineEnds = script.line_ends; 9 | var lineCount = lineEnds.length; 10 | var endLine = script.line_offset + lineCount - 1; 11 | var endColumn; 12 | // V8 will not count last line if script source ends with \n. 13 | if (script.source[script.source.length - 1] === '\n') { 14 | endLine += 1; 15 | endColumn = 0; 16 | } else { 17 | if (lineCount === 1) 18 | endColumn = script.source.length + script.column_offset; 19 | else 20 | endColumn = script.source.length - (lineEnds[lineCount - 2] + 1); 21 | } 22 | 23 | return { 24 | id: script.id, 25 | name: script.nameOrSourceURL(), 26 | source: script.source, 27 | startLine: script.line_offset, 28 | startColumn: script.column_offset, 29 | endLine: endLine, 30 | endColumn: endColumn, 31 | isContentScript: !!script.context_data && script.context_data.indexOf("injected") == 0 32 | }; 33 | };*/ 34 | 35 | var formatScript = function(script) { 36 | var endLine = script.lineOffset + script.lineCount - 1; 37 | var endColumn = 0; 38 | 39 | // V8 will not count last line if script source ends with \n. 40 | if (script.source[script.sourceLength - 1] === '\n') { 41 | endLine += 1; 42 | } else { 43 | if (script.lineCount === 1) { 44 | endColumn = script.sourceLength + script.columnOffset; 45 | } else { 46 | endColumn = script.sourceLength - (script.source[script.lineCount - 2].length); 47 | } 48 | } 49 | 50 | return { 51 | scriptId: script.id.toString(), 52 | url: script.name, 53 | startLine: script.lineOffset, 54 | startColumn: script.columnOffset, 55 | endLine: endLine, 56 | endColumn: endColumn, 57 | isContentScript: false 58 | }; 59 | }; 60 | 61 | module.exports = { 62 | emptyResult: function() { 63 | var devtoolsFormat = {}; 64 | return devtoolsFormat; 65 | }, 66 | 67 | afterCompile: function(eventData) { 68 | return { 69 | method: 'Debugger.afterCompile', 70 | params:{} 71 | }; 72 | }, 73 | 74 | scripts: function(response) { 75 | var scripts = []; 76 | var scripts_ = response.body; 77 | 78 | for (var i = 0, len = scripts_.length; i < len; i++) { 79 | var script = scripts_[i]; 80 | 81 | scripts.push({ 82 | method: 'Debugger.scriptParsed', 83 | params: formatScript(script) 84 | }); 85 | } 86 | 87 | return scripts; 88 | }, 89 | 90 | /** 91 | * Naming exceptions in order to translate the v8 protocol 92 | * dynamicaly, based on the field `command` of 93 | */ 94 | setexceptionbreak: function(response) { 95 | return {}; 96 | }, 97 | 98 | scriptCollected: function(script) { 99 | console.log(script); 100 | } 101 | }; 102 | -------------------------------------------------------------------------------- /lib/helpers.js: -------------------------------------------------------------------------------- 1 | var underscore = require('underscore'); 2 | 3 | var helpers = underscore; 4 | module.exports = helpers; 5 | 6 | var primitiveTypes = { 7 | undefined: true, 8 | boolean: true, 9 | number: true, 10 | string: true 11 | }; 12 | 13 | var isPrimitiveValue = function(object) { 14 | return primitiveTypes[typeof object]; 15 | }; 16 | 17 | helpers.mixin({ isPrimitiveValue: isPrimitiveValue }); 18 | 19 | var subtype = function(obj) { 20 | if (obj === null) return "null"; 21 | 22 | var type = typeof obj; 23 | if (helpers.isPrimitiveValue(obj)) return null; 24 | 25 | if (helpers.isArray(obj)) return "array"; 26 | if (helpers.isRegExp(obj)) return "regexp"; 27 | if (helpers.isDate(obj)) return "date"; 28 | 29 | // FireBug's array detection. 30 | try { 31 | if (Object.prototype.toString.call(obj) === "[object Arguments]" && 32 | isFinite(obj.length)) { 33 | return "array"; 34 | } 35 | } catch (e) { 36 | } 37 | return null; 38 | }; 39 | 40 | helpers.mixin({ subtype: subtype }); 41 | 42 | var describe = function(obj) { 43 | if (helpers.isPrimitiveValue(obj)) return null; 44 | 45 | var subtype = helpers.subtype(obj); 46 | 47 | if (subtype === "regexp") return '' + obj; 48 | if (subtype === "date") return '' + obj; 49 | 50 | if (subtype === "array") { 51 | var className = 'array '; 52 | if (typeof obj.length === "number") 53 | className += "[" + obj.length + "]"; 54 | return className; 55 | } 56 | 57 | if (typeof obj === "function") return "" + obj; 58 | 59 | if (helpers.isObject(obj)) { 60 | // In Chromium DOM wrapper prototypes will have Object as their constructor name, 61 | // get the real DOM wrapper name from the constructor property. 62 | var constructorName = obj.constructor && obj.constructor.name; 63 | if (constructorName) 64 | return constructorName; 65 | } 66 | return '' + obj; 67 | }; 68 | 69 | var decycle = function(object, recursive) { 70 | 'use strict'; 71 | 72 | //Taken from https://github.com/douglascrockford/JSON-js/blob/master/cycle.js 73 | 74 | // Make a deep copy of an object or array, assuring that there is at most 75 | // one instance of each object or array in the resulting structure. The 76 | // duplicate references (which might be forming cycles) are replaced with 77 | // an object of the form 78 | // {$ref: PATH} 79 | // where the PATH is a JSONPath string that locates the first occurance. 80 | // So, 81 | // var a = []; 82 | // a[0] = a; 83 | // return JSON.stringify(JSON.decycle(a)); 84 | // produces the string '[{"$ref":"$"}]'. 85 | 86 | // JSONPath is used to locate the unique object. $ indicates the top level of 87 | // the object or array. [NUMBER] or [STRING] indicates a child member or 88 | // property. 89 | 90 | var objects = [], // Keep a reference to each unique object or array 91 | paths = []; // Keep the path to each unique object or array 92 | 93 | return (function derez(value, path, deep) { 94 | 95 | // The derez recurses through the object, producing the deep copy. 96 | 97 | var i, // The loop counter 98 | name, // Property name 99 | nu; // The new object or array 100 | 101 | switch (typeof value) { 102 | case 'object': 103 | 104 | // typeof null === 'object', so get out if this value is not really an object. 105 | 106 | if (!value) { 107 | return null; 108 | } 109 | 110 | // If the value is an object or array, look to see if we have already 111 | // encountered it. If so, return a $ref/path object. This is a hard way, 112 | // linear search that will get slower as the number of unique objects grows. 113 | 114 | for (i = 0; i < objects.length; i += 1) { 115 | if (objects[i] === value) { 116 | return {$ref: paths[i]}; 117 | } 118 | } 119 | 120 | // Otherwise, accumulate the unique value and its path. 121 | 122 | objects.push(value); 123 | paths.push(path); 124 | 125 | // If it is an array, replicate the array. 126 | 127 | if (Object.prototype.toString.apply(value) === '[object Array]') { 128 | nu = []; 129 | if (deep !== undefined){ 130 | deep++; 131 | } 132 | if (deep === undefined || deep < 2) { 133 | for (i = 0; i < value.length; i += 1) { 134 | try { 135 | typeof value[i] 136 | } catch (err) { 137 | continue; 138 | } 139 | nu[i] = derez(value[i], path + '[' + i + ']',deep); 140 | } 141 | } 142 | } else { 143 | 144 | // If it is an object, replicate the object. 145 | 146 | nu = {}; 147 | if (deep !== undefined){ 148 | deep++; 149 | } 150 | 151 | if (deep === undefined || deep < 2) { 152 | for (name in value) { 153 | try { 154 | typeof value[name] 155 | } catch (err) { 156 | continue; 157 | } 158 | if (Object.prototype.hasOwnProperty.call(value, name)) { 159 | nu[name] = derez(value[name], 160 | path + '[' + JSON.stringify(name) + ']',deep); 161 | } 162 | } 163 | } 164 | } 165 | return nu; 166 | case 'number': 167 | case 'string': 168 | case 'boolean': 169 | return value; 170 | } 171 | }(object, '$', ((recursive == false) ? 0 : undefined))); 172 | }; 173 | 174 | helpers.mixin({ decycle: decycle }); 175 | helpers.mixin({ describe: describe }); 176 | 177 | 178 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Console : require('./console'), 3 | Profiler : require('./profiler'), 4 | Page : require('./page'), 5 | //FileSystem : require('./filesystem'), 6 | Network : require('./network'), 7 | Runtime : require('./runtime'), 8 | //Timeline : require('./timeline'), 9 | Inspector : require('./inspector') 10 | }; 11 | -------------------------------------------------------------------------------- /lib/inspector.js: -------------------------------------------------------------------------------- 1 | function InspectorAgent() { 2 | this.enabled = false; 3 | } 4 | 5 | (function () { 6 | this.enable = function (params, sendResult) { 7 | sendResult({result: this.enabled}); 8 | }; 9 | }).call(InspectorAgent.prototype); 10 | 11 | module.exports = InspectorAgent; 12 | -------------------------------------------------------------------------------- /lib/network.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/c4milo/node-webkit-agent/c61bc4e3952b8c853a08fcb7e2c2ce30a281eabd/lib/network.js -------------------------------------------------------------------------------- /lib/page.js: -------------------------------------------------------------------------------- 1 | function PageAgent() { 2 | this.enabled = false; 3 | } 4 | 5 | (function () { 6 | this.enable = function (params, sendResult) { 7 | sendResult({result: this.enabled}); 8 | }; 9 | 10 | this.canOverrideDeviceMetrics = function (params, sendResult) { 11 | sendResult({result: false}); 12 | }; 13 | 14 | this.canShowDebugBorders = function (params, sendResult) { 15 | sendResult({result: false}); 16 | }; 17 | 18 | this.canShowFPSCounter = function (params, sendResult) { 19 | sendResult({result: false}); 20 | }; 21 | 22 | this.canContinuouslyPaint = function (params, sendResult) { 23 | sendResult({result: false}); 24 | }; 25 | 26 | this.canOverrideGeolocation = function (params, sendResult) { 27 | sendResult({result: false}); 28 | }; 29 | 30 | this.canOverrideDeviceOrientation = function (params, sendResult) { 31 | sendResult({result: false}); 32 | }; 33 | 34 | this.setTouchEmulationEnabled = function (params, sendResult) { 35 | sendResult({result: false}); 36 | }; 37 | }).call(PageAgent.prototype); 38 | 39 | module.exports = PageAgent; 40 | -------------------------------------------------------------------------------- /lib/profiler.js: -------------------------------------------------------------------------------- 1 | var profiler = require('./v8-profiler'); 2 | var fs = require('fs'); 3 | 4 | var HeapProfileType = 'HEAP'; 5 | var CPUProfileType = 'CPU'; 6 | 7 | function ProfilerAgent(notify) { 8 | this.notify = notify; 9 | this.profiles = { 10 | HEAP: {}, 11 | CPU: {} 12 | }; 13 | 14 | this.enabled = false; 15 | this.isProfilingCPU = false; 16 | } 17 | 18 | (function(){ 19 | this.enable = function (params, sendResult) { 20 | sendResult({result: this.enabled}); 21 | }; 22 | 23 | this.causesRecompilation = function (params, sendResult) { 24 | sendResult({result: false}); 25 | }; 26 | 27 | this.isSampling = function (params, sendResult) { 28 | sendResult({result: this.isProfilingCPU}); 29 | }; 30 | 31 | this.hasHeapProfiler = function (params, sendResult) { 32 | sendResult({result: true}); 33 | }; 34 | 35 | this.getProfileHeaders = function (params, sendResult) { 36 | var headers = []; 37 | 38 | for (var type in this.profiles) { 39 | for (var profileId in this.profiles[type]) { 40 | var profile = this.profiles[type][profileId]; 41 | headers.push({ 42 | title: profile.title, 43 | uid: profile.uid, 44 | typeId: type 45 | }); 46 | } 47 | } 48 | 49 | sendResult({ 50 | headers: headers 51 | }); 52 | }; 53 | 54 | this.takeHeapSnapshot = function (params, sendResult) { 55 | var self = this; 56 | var snapshot = profiler.takeSnapshot(function(done, total) { 57 | self.notify({ 58 | method: 'Profiler.reportHeapSnapshotProgress', 59 | params:{ 60 | done: done, 61 | total: total 62 | } 63 | }); 64 | }); 65 | 66 | this.profiles[HeapProfileType][snapshot.uid] = snapshot; 67 | 68 | this.notify({ 69 | method: 'Profiler.addProfileHeader', 70 | params: { 71 | header: { 72 | title: snapshot.title, 73 | uid: snapshot.uid, 74 | typeId: HeapProfileType 75 | } 76 | } 77 | }); 78 | 79 | sendResult({}); 80 | }; 81 | 82 | this.getHeapSnapshot = function (params, sendResult) { 83 | var self = this; 84 | var snapshot = this.profiles[HeapProfileType][params.uid]; 85 | 86 | snapshot.serialize({ 87 | onData: function (chunk, size) { 88 | chunk = chunk + ''; 89 | self.notify({ 90 | method: 'Profiler.addHeapSnapshotChunk', 91 | params: { 92 | uid: snapshot.uid, 93 | chunk: chunk 94 | } 95 | }); 96 | }, 97 | 98 | onEnd: function () { 99 | self.notify({ 100 | method: 'Profiler.finishHeapSnapshot', 101 | params: {uid: snapshot.uid} 102 | }); 103 | 104 | sendResult({ 105 | profile: { 106 | title: snapshot.title, 107 | uid: snapshot.uid, 108 | typeId: HeapProfileType 109 | } 110 | }); 111 | } 112 | }); 113 | }; 114 | 115 | this.getCPUProfile = function (params, sendResult) { 116 | var self = this; 117 | var profile = this.profiles[CPUProfileType][params.uid]; 118 | profile.typeId = CPUProfileType; 119 | 120 | sendResult({ 121 | profile: { 122 | title: profile.title, 123 | uid: profile.uid, 124 | typeId: CPUProfileType, 125 | head: profile.getTopDownRoot(), 126 | bottomUpHead: profile.getBottomUpRoot() 127 | } 128 | }); 129 | }; 130 | 131 | //Backwards support for v8 versions coming in nodejs 0.6.x and 0.8.x 132 | this.getProfile = function (params, sendResult) { 133 | if (params.type === HeapProfileType) { 134 | this.getHeapSnapshot(params, sendResult); 135 | } else if (params.type === CPUProfileType) { 136 | this.getCPUProfile(params, sendResult); 137 | } 138 | }; 139 | 140 | this.clearProfiles = function (params, sendResult) { 141 | this.profiles.HEAP = {}; 142 | this.profiles.CPU = {}; 143 | profiler.deleteAllSnapshots(); 144 | profiler.deleteAllProfiles(); 145 | }; 146 | 147 | this.start = function (params, sendResult) { 148 | /* TODO 149 | { "method":"Console.messageAdded", 150 | "params":{"message":{"source":"javascript","level":"log","text":"Profile \"Profile 1" started.","type":"log","line":0,"url":"","repeatCount":1}}} 151 | */ 152 | 153 | profiler.startProfiling(); 154 | 155 | this.notify({ 156 | method: 'Profiler.setRecordingProfile', 157 | params: { 158 | isProfiling: true 159 | } 160 | }); 161 | 162 | sendResult({}); 163 | }; 164 | 165 | var fakeProfileUid = 1; 166 | 167 | this.stop = function (params, sendResult) { 168 | var profile = profiler.stopProfiling(); 169 | 170 | // forwards compatibility for newer V8 in node 0.12 which doesn't set profile.uid 171 | if (!profile.hasOwnProperty('uid')) { 172 | profile.uid = fakeProfileUid++; 173 | } 174 | 175 | this.profiles[CPUProfileType][profile.uid] = profile; 176 | 177 | this.notify({ 178 | method: 'Profiler.addProfileHeader', 179 | params: { 180 | header: { 181 | title: profile.title, 182 | uid: profile.uid, 183 | typeId: CPUProfileType 184 | } 185 | } 186 | }); 187 | 188 | this.notify({ 189 | method: 'Profiler.setRecordingProfile', 190 | params: { 191 | isProfiling: false 192 | } 193 | }); 194 | 195 | sendResult({}); 196 | }; 197 | 198 | this.collectGarbage = function (params, sendResult) { 199 | if (typeof gc === 'function') { 200 | gc(); 201 | } else { 202 | console.warn('ProfilerAgent: ' + 203 | 'you need to run your nodejs app using --expose_gc ' + 204 | 'in order to `"force`" garbage collection.'); 205 | } 206 | sendResult({}); 207 | }; 208 | }).call(ProfilerAgent.prototype); 209 | 210 | module.exports = ProfilerAgent; 211 | -------------------------------------------------------------------------------- /lib/runtime.js: -------------------------------------------------------------------------------- 1 | var helpers = require('./helpers'); 2 | 3 | //Code was based on /WebKit/Source/WebCore/inspector/InjectedScriptSource.js 4 | var _objectId = 0; 5 | var RemoteObject = function (object, forceValueType) { 6 | this.type = typeof object; 7 | 8 | if (helpers.isPrimitiveValue(object) || 9 | object === null || forceValueType) { 10 | // We don't send undefined values over JSON. 11 | if (typeof object !== "undefined") { 12 | this.value = object; 13 | } 14 | 15 | // Null object is object with 'null' subtype' 16 | if (object === null) { 17 | this.subtype = "null"; 18 | } 19 | 20 | // Provide user-friendly number values. 21 | if (typeof object === "number") { 22 | this.description = object + ""; 23 | } 24 | return; 25 | } 26 | 27 | this.objectId = JSON.stringify({ injectedScriptId: 0, id: _objectId++}); 28 | var subtype = helpers.subtype(object); 29 | if (subtype) { 30 | this.subtype = subtype; 31 | } 32 | this.className = object.constructor || object.name || ''; 33 | this.description = helpers.describe(object); 34 | this.value = helpers.decycle(object,false); 35 | }; 36 | 37 | var getPropertyDescriptors = function (object, ownProperties) { 38 | var descriptors = []; 39 | var nameProcessed = {}; 40 | nameProcessed.__proto__ = null; 41 | 42 | for (var o = object; helpers.isObject(o); o = o.__proto__) { 43 | var names = Object.getOwnPropertyNames(o); 44 | for (var i = 0; i < names.length; ++i) { 45 | var name = names[i]; 46 | if (nameProcessed[name]) { 47 | continue; 48 | } 49 | 50 | var descriptor = {}; 51 | try { 52 | nameProcessed[name] = true; 53 | descriptor = Object.getOwnPropertyDescriptor(object, name); 54 | if (!descriptor) { 55 | try { 56 | descriptors.push({ 57 | name: name, 58 | value: object[name], 59 | writable: false, 60 | configurable: false, 61 | enumerable: false 62 | }); 63 | } catch (e) { 64 | // Silent catch. 65 | } 66 | continue; 67 | } 68 | } catch (e) { 69 | descriptor = {}; 70 | descriptor.value = e; 71 | descriptor.wasThrown = true; 72 | } 73 | 74 | descriptor.name = name; 75 | descriptors.push(descriptor); 76 | } 77 | 78 | if (ownProperties) { 79 | if (object.__proto__) { 80 | descriptors.push({ 81 | name: "__proto__", 82 | value: object.__proto__, 83 | writable: true, 84 | configurable: true, 85 | enumerable: false 86 | }); 87 | } 88 | break; 89 | } 90 | } 91 | return descriptors; 92 | }; 93 | 94 | function RuntimeAgent() { 95 | this.objects = {}; 96 | } 97 | 98 | (function() { 99 | this.evaluate = function (params, sendResult) { 100 | var result = null; 101 | try { 102 | result = eval.call(global, "with ({}) {\n" + params.expression + "\n}"); 103 | } catch (e) { 104 | return sendResult(this.createThrownValue(e, params.objectGroup)); 105 | } 106 | 107 | sendResult({ 108 | result: this.wrapObject(result, params.objectGroup), 109 | wasThrown: false 110 | }); 111 | }; 112 | 113 | this.getProperties = function (params, sendResult) { 114 | var object = this.objects[params.objectId]; 115 | 116 | if (helpers.isUndefined(object)) { 117 | console.error('RuntimeAgent.getProperties: Unknown object'); 118 | return; 119 | } 120 | 121 | object = object.value; 122 | 123 | var descriptors = getPropertyDescriptors(object, params.ownProperties); 124 | var len = descriptors.length; 125 | 126 | if (len === 0 && 127 | "arguments" in object) { 128 | for (var key in object) { 129 | descriptors.push({ 130 | name: key, 131 | value: object[key], 132 | writable: false, 133 | configurable: false, 134 | enumerable: true 135 | }); 136 | } 137 | } 138 | 139 | for (var i = 0; i < len; ++i) { 140 | var descriptor = descriptors[i]; 141 | if ("get" in descriptor) { 142 | descriptor.get = this.wrapObject(descriptor.get); 143 | } 144 | 145 | if ("set" in descriptor) { 146 | descriptor.set = this.wrapObject(descriptor.set); 147 | } 148 | 149 | if ("value" in descriptor) { 150 | descriptor.value = this.wrapObject(descriptor.value); 151 | } 152 | 153 | if (!("configurable" in descriptor)) { 154 | descriptor.configurable = false; 155 | } 156 | 157 | if (!("enumerable" in descriptor)) { 158 | descriptor.enumerable = false; 159 | } 160 | } 161 | 162 | sendResult({ 163 | result: descriptors 164 | }); 165 | }; 166 | 167 | this.wrapObject = function (object, objectGroup, forceValueType) { 168 | var remoteObject; 169 | 170 | try { 171 | remoteObject = new RemoteObject(object, forceValueType); 172 | } catch (e) { 173 | var description = ""; 174 | try { 175 | description = helpers.describe(e); 176 | } catch (ex) {} 177 | remoteObject = new RemoteObject(description, forceValueType); 178 | } 179 | 180 | this.objects[remoteObject.objectId] = { 181 | objectGroup: objectGroup, 182 | value: object 183 | }; 184 | return remoteObject; 185 | }; 186 | 187 | this.createThrownValue = function (value, objectGroup) { 188 | var remoteObject = this.wrapObject(value, objectGroup); 189 | try { 190 | remoteObject.description = '' + value; 191 | } catch (e) {} 192 | 193 | return { 194 | wasThrown: true, 195 | result: remoteObject 196 | }; 197 | }; 198 | 199 | this.callFunctionOn = function (params, sendResult) { 200 | var object = this.objects[params.objectId]; 201 | 202 | if (helpers.isUndefined(object)) { 203 | console.error('RuntimeAgent.callFunctionOn: Unknown object'); 204 | return; 205 | } 206 | 207 | object = object.value; 208 | var resolvedArgs = []; 209 | 210 | var args = params.arguments; 211 | 212 | if (args) { 213 | for (var i = 0; i < args.length; ++i) { 214 | var objectId = args[i].objectId; 215 | if (objectId) { 216 | var resolvedArg = this.objects[objectId]; 217 | if (!resolvedArg) { 218 | console.error('RuntimeAgent.callFunctionOn: Unknown object'); 219 | return; 220 | } 221 | 222 | resolvedArgs.push(resolvedArg.value); 223 | } else if ("value" in args[i]) { 224 | resolvedArgs.push(args[i].value); 225 | } else { 226 | resolvedArgs.push(undefined); 227 | } 228 | } 229 | } 230 | 231 | var objectGroup = this.objects[params.objectId].objectGroup; 232 | try { 233 | var func = eval.call(global, ("(" + params.functionDeclaration + ")")); 234 | if (typeof func !== "function") { 235 | console.error('RuntimeAgent.callFunctionOn: Expression does ' + 236 | 'not evaluate to a function'); 237 | return; 238 | } 239 | 240 | return sendResult({ 241 | result: this.wrapObject(func.apply(object, resolvedArgs), objectGroup, params.returnByValue), 242 | wasThrown: false 243 | }); 244 | } catch (e) { 245 | return sendResult(this.createThrownValue(e, objectGroup)); 246 | } 247 | }; 248 | 249 | this.releaseObjectGroup = function (params, sendResult) { 250 | for (var key in this.objects) { 251 | var value = this.objects[key]; 252 | if (value.objectGroup === params.objectGroup) { 253 | delete this.objects[key]; 254 | } 255 | } 256 | sendResult({}); 257 | }; 258 | 259 | this.releaseObject = function (params, sendResult) { 260 | delete this.objects[params.objectId]; 261 | sendResult({}); 262 | }; 263 | }).call(RuntimeAgent.prototype); 264 | 265 | module.exports = RuntimeAgent; 266 | 267 | 268 | -------------------------------------------------------------------------------- /lib/timeline.js: -------------------------------------------------------------------------------- 1 | var probes = require('./probes'); 2 | 3 | function TimelineAgent(sendEvent, runtimeAgent) { 4 | this.sendEvent = sendEvent; 5 | this.runtimeAgent = runtimeAgent; 6 | this.enabled = false; 7 | this.maxCallStackDepth = 5; 8 | this.includeMemoryDetails = true; 9 | } 10 | 11 | (function() { 12 | this.enable = function (params, sendResult) { 13 | this.enabled = true; 14 | sendResult({result: this.enabled}); 15 | probes.start(); 16 | }; 17 | 18 | this.disable = function (params, sendResult) { 19 | this.enabled = false; 20 | probes.stop(); 21 | sendResult({result: this.enabled}); 22 | }; 23 | 24 | this.timeStamp = function (params, sendResult, sendEvent) { 25 | var memory = process.memoryUsage(); 26 | sendEvent({ 27 | method: 'Timeline.eventRecorded', 28 | params: { 29 | record: { 30 | startTime: Date.now(), 31 | endTime: Date.now(), 32 | data: { 'message': message || '' }, 33 | type: 'TimeStamp', 34 | usedHeapSize: memory.heapUsed, 35 | totalHeapSize: memory.heapTotal 36 | } 37 | } 38 | }); 39 | }; 40 | 41 | this.start = function (params, sendResult) { 42 | this.maxCallStackDepth = params.maxCallStackDepth || 5; 43 | sendResult({}); 44 | }; 45 | 46 | this.stop = function (params, sendResult) { 47 | sendResult({}); 48 | }; 49 | 50 | this.setIncludeMemoryDetails = function (params, sendResult) { 51 | this.includeMemoryDetails = params.enabled || true; 52 | sendResult({}); 53 | }; 54 | }).call(TimelineAgent.prototype); 55 | 56 | module.exports = TimelineAgent; 57 | 58 | -------------------------------------------------------------------------------- /lib/v8-profiler.js: -------------------------------------------------------------------------------- 1 | var binding = require("../build/Release/profiler"); 2 | 3 | function Snapshot() {} 4 | 5 | Snapshot.prototype.compare = function (other) { 6 | var my_objects = this.nodeCounts(), 7 | their_objects = other.nodeCounts(), 8 | diff = {}, i, k, my_val, their_val; 9 | all_keys = Object.keys(my_objects).concat(Object.keys(their_objects)); //has dupes, oh well 10 | for (i = 0; i < all_keys.length; i++) { 11 | k = all_keys[i]; 12 | my_val = my_objects[k] || 0; 13 | their_val = their_objects[k] || 0; 14 | diff[k] = their_val - my_val; 15 | } 16 | return diff; 17 | }; 18 | 19 | Snapshot.prototype.hotPath = function () { 20 | var path = [], node = this.root, c, i = 0; 21 | c = this.children(node); 22 | while (c.length > 0 && i < 1000) { 23 | node = c[0].to; 24 | c = this.children(node); 25 | path.push(node); 26 | i++; 27 | } 28 | return path; 29 | }; 30 | 31 | Snapshot.prototype.children = function (node) { 32 | var i, children = []; 33 | for(i = 0; i < node.childrenCount; i++) { 34 | children[i] = node.getChild(i); 35 | } 36 | children.sort(function (a, b){ 37 | return b.to.retainedSize() - a.to.retainedSize(); 38 | }); 39 | return children; 40 | }; 41 | 42 | Snapshot.prototype.topDominatorIds = function () { 43 | var doms = {}, arr; 44 | this.allNodes().forEach(function (node){ 45 | var dom = node.dominatorNode || { id: "none"}; 46 | if (doms[dom.id]) { 47 | doms[dom.id] += 1; 48 | } 49 | else { 50 | doms[dom.id] = 1; 51 | } 52 | }); 53 | arr = Object.keys(doms).map(function (d){ 54 | return {id: d, count: doms[d]}; 55 | }); 56 | arr.sort(function (a, b) { 57 | return b.count - a.count; 58 | }); 59 | return arr; 60 | }; 61 | 62 | Snapshot.prototype.topDominators = function () { 63 | var self = this; 64 | return this.topDominatorIds().map(function (d){ 65 | return self.getNodeById(+d.id); 66 | }); 67 | }; 68 | 69 | Snapshot.prototype.allNodes = function () { 70 | var n = this.nodesCount, i, nodes = []; 71 | for (i = 0; i < n; i++) { 72 | nodes[i] = this.getNode(i); 73 | } 74 | return nodes; 75 | }; 76 | 77 | Snapshot.prototype.nodeCounts = function () { 78 | var objects = {}; 79 | this.allNodes().forEach(function (n){ 80 | if(n.type === "Object") { 81 | if (objects[n.name]) { 82 | objects[n.name] += 1; 83 | } 84 | else { 85 | objects[n.name] = 1; 86 | } 87 | } 88 | else { 89 | if (objects[n.type]) { 90 | objects[n.type] += 1; 91 | } 92 | else { 93 | objects[n.type] = 1; 94 | } 95 | } 96 | }); 97 | return objects; 98 | }; 99 | 100 | //adapted from WebCore/bindings/v8/ScriptHeapSnapshot.cpp 101 | Snapshot.prototype.stringify = function () { 102 | var root = this.root, i, j, count_i, count_j, node, 103 | lowLevels = {}, entries = {}, entry, 104 | children = {}, child, edge, result = {}; 105 | for (i = 0, count_i = root.childrenCount; i < count_i; i++) { 106 | node = root.getChild(i).to; 107 | if (node.type === 'Hidden') { 108 | lowLevels[node.name] = { 109 | count: node.instancesCount, 110 | size: node.size, 111 | type: node.name 112 | }; 113 | } 114 | else if (node.instancesCount > 0) { 115 | entries[node.name] = { 116 | constructorName: node.name, 117 | count: node.instancesCount, 118 | size: node.size 119 | }; 120 | } 121 | // FIXME: the children portion is too slow and bloats the results 122 | //* 123 | else { 124 | entry = { 125 | constructorName: node.name 126 | }; 127 | for(j = 0, count_j = node.childrenCount; j < count_j; j++) { 128 | edge = node.getChild(j); 129 | child = edge.to; 130 | entry[child.ptr.toString()] = { 131 | constructorName: child.name, 132 | count: parseInt(edge.name, 10) 133 | }; 134 | } 135 | children[node.ptr.toString()] = entry; 136 | }//*/ 137 | } 138 | result.lowlevels = lowLevels; 139 | result.entries = entries; 140 | result.children = children; 141 | return JSON.stringify(result); 142 | }; 143 | 144 | function CpuProfile() {} 145 | 146 | function inspectorObjectFor(node) { 147 | var i, count, child, 148 | result = { 149 | functionName: node.functionName, 150 | url: node.scriptName, 151 | lineNumber: node.lineNumber, 152 | // NodeJS until 0.10 only 153 | totalTime: node.totalTime, 154 | selfTime: node.selfTime, 155 | // hitCount is defined by NodeJS 0.12+ only 156 | hitCount: node.hitCount, // Would the UI want to show this? v8 3.14 had this as selfSamplesCount which this UI doesn't use. 157 | numberOfCalls: 0, 158 | visible: true, 159 | callUID: node.callUid, 160 | children: [] 161 | }; 162 | for(i = 0, count = node.childrenCount; i < count; i++) { 163 | child = node.getChild(i); 164 | result.children.push(inspectorObjectFor(child)); 165 | } 166 | return result; 167 | } 168 | 169 | CpuProfile.prototype.getTopDownRoot = function () { 170 | return inspectorObjectFor(this.topRoot); 171 | }; 172 | 173 | CpuProfile.prototype.getBottomUpRoot = function () { 174 | // Newer V8 (node 0.12) doesn't have bottom-up profiling? 175 | if (!this.bottomRoot) { 176 | return null; 177 | } 178 | return inspectorObjectFor(this.bottomRoot); 179 | }; 180 | 181 | var heapCache = []; 182 | 183 | exports.takeSnapshot = function (name, control) { 184 | if (typeof name == 'function') { 185 | control = name; 186 | name = ''; 187 | } 188 | 189 | if (!name || !name.length) { 190 | name = 'org.nodejs.profiles.heap.user-initiated.' + (heapCache.length + 1); 191 | } 192 | 193 | var snapshot = binding.heapProfiler.takeSnapshot (name, control); 194 | snapshot.__proto__ = Snapshot.prototype; 195 | heapCache.push(snapshot); 196 | 197 | return snapshot; 198 | }; 199 | 200 | exports.getSnapshot = function (index) { 201 | return heapCache[index]; 202 | }; 203 | 204 | exports.findSnapshot = function (uid) { 205 | return heapCache.filter(function (s) {return s.uid === uid;})[0]; 206 | }; 207 | 208 | exports.snapshotCount = function () { 209 | return heapCache.length; 210 | }; 211 | 212 | exports.deleteAllSnapshots = function () { 213 | heapCache = []; 214 | binding.heapProfiler.deleteAllSnapshots(); 215 | }; 216 | 217 | var cpuCache = []; 218 | 219 | exports.startProfiling = function (name) { 220 | if (!name || !name.length) { 221 | name = 'org.nodejs.profiles.cpu.user-initiated.' + (cpuCache.length + 1); 222 | } 223 | 224 | binding.cpuProfiler.startProfiling(name); 225 | }; 226 | 227 | exports.stopProfiling = function (name) { 228 | name = name ? name : ''; 229 | var profile = binding.cpuProfiler.stopProfiling(name); 230 | profile.__proto__ = CpuProfile.prototype; 231 | cpuCache.push(profile); 232 | return profile; 233 | }; 234 | 235 | exports.getProfile = function (index) { 236 | return cpuCache[index]; 237 | }; 238 | 239 | exports.findProfile = function (uid) { 240 | return cpuCache.filter(function(s) {return s.uid === uid;})[0]; 241 | }; 242 | 243 | exports.profileCount = function () { 244 | return cpuCache.length; 245 | }; 246 | 247 | exports.deleteAllProfiles = function () { 248 | cpuCache = []; 249 | binding.cpuProfiler.deleteAllProfiles(); 250 | }; 251 | 252 | process.profiler = exports; 253 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "webkit-devtools-agent", 3 | "description": "Webkit devtools agent that leverages remote debugging and profiling of nodejs applications using the built-in webkit inspector.", 4 | "keywords": [ 5 | "devtools", 6 | "agent", 7 | "webkit", 8 | "debugger", 9 | "profiler", 10 | "remote", 11 | "v8" 12 | ], 13 | "homepage": "http://github.com/c4milo/node-webkit-agent", 14 | "version": "0.3.1", 15 | "author": "Camilo Aguilar", 16 | "repository": "git://github.com/c4milo/node-webkit-agent", 17 | "engines": { 18 | "node": ">= 0.6.0" 19 | }, 20 | "devDependencies": { 21 | "mocha": "1.14.0", 22 | "should": "0.6.0" 23 | }, 24 | "dependencies": { 25 | "nan": "1.6.2", 26 | "ws": "7.2.0", 27 | "underscore": "1.3.3" 28 | }, 29 | "main": "index" 30 | } 31 | -------------------------------------------------------------------------------- /src/cpu_profiler.cc: -------------------------------------------------------------------------------- 1 | #include "cpu_profiler.h" 2 | #include "profile.h" 3 | 4 | namespace nodex { 5 | Persistent CpuProfiler::cpu_profiler_template_; 6 | 7 | void CpuProfiler::Initialize(Handle target) { 8 | NanScope(); 9 | 10 | Handle cpu_profiler_template = ObjectTemplate::New(); 11 | NanAssignPersistent(cpu_profiler_template_, cpu_profiler_template); 12 | cpu_profiler_template->SetInternalFieldCount(1); 13 | 14 | Local cpuProfilerObj = cpu_profiler_template->NewInstance(); 15 | 16 | #if (NODE_MODULE_VERSION < 12) 17 | NODE_SET_METHOD(cpuProfilerObj, "getProfilesCount", CpuProfiler::GetProfilesCount); 18 | NODE_SET_METHOD(cpuProfilerObj, "getProfile", CpuProfiler::GetProfile); 19 | NODE_SET_METHOD(cpuProfilerObj, "findProfile", CpuProfiler::FindProfile); 20 | #endif // (NODE_MODULE_VERSION < 12) 21 | NODE_SET_METHOD(cpuProfilerObj, "startProfiling", CpuProfiler::StartProfiling); 22 | NODE_SET_METHOD(cpuProfilerObj, "stopProfiling", CpuProfiler::StopProfiling); 23 | #if (NODE_MODULE_VERSION < 12) 24 | NODE_SET_METHOD(cpuProfilerObj, "deleteAllProfiles", CpuProfiler::DeleteAllProfiles); 25 | #endif // (NODE_MODULE_VERSION < 12) 26 | 27 | target->Set(NanNew("cpuProfiler"), cpuProfilerObj); 28 | } 29 | 30 | CpuProfiler::CpuProfiler() {} 31 | CpuProfiler::~CpuProfiler() {} 32 | 33 | #if (NODE_MODULE_VERSION < 12) 34 | NAN_METHOD(CpuProfiler::GetProfilesCount) { 35 | NanScope(); 36 | NanReturnValue(NanNew(v8::CpuProfiler::GetProfilesCount())); 37 | } 38 | 39 | NAN_METHOD(CpuProfiler::GetProfile) { 40 | NanScope(); 41 | if (args.Length() < 1) { 42 | NanThrowError("No index specified"); 43 | } else if (!args[0]->IsInt32()) { 44 | NanThrowTypeError("Argument must be an integer"); 45 | } 46 | int32_t index = args[0]->Int32Value(); 47 | const CpuProfile* profile = v8::CpuProfiler::GetProfile(index); 48 | NanReturnValue(Profile::New(profile)); 49 | } 50 | 51 | NAN_METHOD(CpuProfiler::FindProfile) { 52 | NanScope(); 53 | if (args.Length() < 1) { 54 | NanThrowError("No index specified"); 55 | } else if (!args[0]->IsInt32()) { 56 | NanThrowTypeError("Argument must be an integer"); 57 | } 58 | uint32_t uid = args[0]->Uint32Value(); 59 | const CpuProfile* profile = v8::CpuProfiler::FindProfile(uid); 60 | NanReturnValue(Profile::New(profile)); 61 | } 62 | #endif // (NODE_MODULE_VERSION < 12) 63 | 64 | NAN_METHOD(CpuProfiler::StartProfiling) { 65 | NanScope(); 66 | Local title = args.Length() > 0 ? args[0]->ToString() : NanNew(""); 67 | #if (NODE_MODULE_VERSION < 12) 68 | v8::CpuProfiler::StartProfiling(title); 69 | #else // (NODE_MODULE_VERSION < 12) 70 | v8::Isolate::GetCurrent()->GetCpuProfiler()->StartProfiling(title, true); 71 | #endif // (NODE_MODULE_VERSION < 12) 72 | NanReturnUndefined(); 73 | } 74 | 75 | NAN_METHOD(CpuProfiler::StopProfiling) { 76 | NanScope(); 77 | Local title = args.Length() > 0 ? args[0]->ToString() : NanNew(""); 78 | #if (NODE_MODULE_VERSION < 12) 79 | const CpuProfile* profile = v8::CpuProfiler::StopProfiling(title); 80 | #else // (NODE_MODULE_VERSION < 12) 81 | const CpuProfile* profile = v8::Isolate::GetCurrent()->GetCpuProfiler()->StopProfiling(title); 82 | #endif // (NODE_MODULE_VERSION < 12) 83 | NanReturnValue(Profile::New(profile)); 84 | } 85 | 86 | #if (NODE_MODULE_VERSION < 12) 87 | NAN_METHOD(CpuProfiler::DeleteAllProfiles) { 88 | v8::CpuProfiler::DeleteAllProfiles(); 89 | NanReturnUndefined(); 90 | } 91 | #endif // (NODE_MODULE_VERSION < 12) 92 | 93 | } //namespace nodex 94 | -------------------------------------------------------------------------------- /src/cpu_profiler.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_CPU_PROFILER_ 2 | #define NODE_CPU_PROFILER_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace v8; 9 | using namespace node; 10 | 11 | namespace nodex { 12 | class CpuProfiler { 13 | public: 14 | static void Initialize(Handle target); 15 | 16 | CpuProfiler(); 17 | virtual ~CpuProfiler(); 18 | 19 | protected: 20 | static NAN_METHOD(GetProfilesCount); 21 | static NAN_METHOD(GetProfile); 22 | static NAN_METHOD(FindProfile); 23 | static NAN_METHOD(StartProfiling); 24 | static NAN_METHOD(StopProfiling); 25 | static NAN_METHOD(DeleteAllProfiles); 26 | 27 | private: 28 | static Persistent cpu_profiler_template_; 29 | }; 30 | } //namespace nodex 31 | 32 | #endif // NODE_CPU_PROFILER_H 33 | -------------------------------------------------------------------------------- /src/heap_profiler.cc: -------------------------------------------------------------------------------- 1 | #include "heap_profiler.h" 2 | #include "snapshot.h" 3 | 4 | namespace nodex { 5 | Persistent HeapProfiler::heap_profiler_template_; 6 | 7 | class ActivityControlAdapter : public ActivityControl { 8 | public: 9 | ActivityControlAdapter(Handle progress) 10 | : reportProgress(Handle::Cast(progress)), 11 | abort(NanFalse()) {} 12 | 13 | ControlOption ReportProgressValue(int done, int total) { 14 | NanScope(); 15 | 16 | Local argv[2] = { 17 | NanNew(done), 18 | NanNew(total) 19 | }; 20 | 21 | TryCatch try_catch; 22 | 23 | abort = reportProgress->Call(NanGetCurrentContext()->Global(), 2, argv); 24 | 25 | if (try_catch.HasCaught()) { 26 | FatalException(try_catch); 27 | return kAbort; 28 | } 29 | 30 | fprintf(stderr, "here!\n"); 31 | 32 | if (abort.IsEmpty() || !abort->IsBoolean()) { 33 | return kContinue; 34 | } 35 | 36 | return abort->IsTrue() ? kAbort : kContinue; 37 | } 38 | 39 | private: 40 | Handle reportProgress; 41 | Local abort; 42 | }; 43 | 44 | void HeapProfiler::Initialize(Handle target) { 45 | NanScope(); 46 | 47 | Handle heap_profiler_template = ObjectTemplate::New(); 48 | NanAssignPersistent(heap_profiler_template_, heap_profiler_template); 49 | heap_profiler_template->SetInternalFieldCount(1); 50 | 51 | Local heapProfilerObj = heap_profiler_template->NewInstance(); 52 | 53 | NODE_SET_METHOD(heapProfilerObj, "takeSnapshot", HeapProfiler::TakeSnapshot); 54 | NODE_SET_METHOD(heapProfilerObj, "getSnapshot", HeapProfiler::GetSnapshot); 55 | #if (NODE_MODULE_VERSION < 12) 56 | NODE_SET_METHOD(heapProfilerObj, "findSnapshot", HeapProfiler::FindSnapshot); 57 | #endif // (NODE_MODULE_VERSION < 12) 58 | NODE_SET_METHOD(heapProfilerObj, "getSnapshotsCount", HeapProfiler::GetSnapshotsCount); 59 | NODE_SET_METHOD(heapProfilerObj, "deleteAllSnapshots", HeapProfiler::DeleteAllSnapshots); 60 | 61 | target->Set(NanNew("heapProfiler"), heapProfilerObj); 62 | } 63 | 64 | HeapProfiler::HeapProfiler() {} 65 | HeapProfiler::~HeapProfiler() {} 66 | 67 | NAN_METHOD(HeapProfiler::GetSnapshotsCount) { 68 | NanScope(); 69 | #if (NODE_MODULE_VERSION < 12) 70 | NanReturnValue(NanNew(v8::HeapProfiler::GetSnapshotsCount())); 71 | #else // (NODE_MODULE_VERSION < 12) 72 | NanReturnValue(NanNew(v8::Isolate::GetCurrent()->GetHeapProfiler()->GetSnapshotCount())); 73 | #endif // (NODE_MODULE_VERSION < 12) 74 | } 75 | 76 | NAN_METHOD(HeapProfiler::GetSnapshot) { 77 | NanScope(); 78 | if (args.Length() < 1) { 79 | NanThrowError("No index specified"); 80 | } else if (!args[0]->IsInt32()) { 81 | NanThrowTypeError("Argument must be an integer"); 82 | } 83 | int32_t index = args[0]->Int32Value(); 84 | #if (NODE_MODULE_VERSION < 12) 85 | const v8::HeapSnapshot* snapshot = v8::HeapProfiler::GetSnapshot(index); 86 | #else // (NODE_MODULE_VERSION < 12) 87 | const v8::HeapSnapshot* snapshot = v8::Isolate::GetCurrent()->GetHeapProfiler()->GetHeapSnapshot(index); 88 | #endif // (NODE_MODULE_VERSION < 12) 89 | 90 | NanReturnValue(Snapshot::New(snapshot)); 91 | } 92 | 93 | #if (NODE_MODULE_VERSION < 12) 94 | NAN_METHOD(HeapProfiler::FindSnapshot) { 95 | NanScope(); 96 | if (args.Length() < 1) { 97 | NanThrowError("No uid specified"); 98 | } 99 | 100 | uint32_t uid = args[0]->Uint32Value(); 101 | const v8::HeapSnapshot* snapshot = v8::HeapProfiler::FindSnapshot(uid); 102 | 103 | NanReturnValue(Snapshot::New(snapshot)); 104 | } 105 | #endif // (NODE_MODULE_VERSION < 12) 106 | 107 | NAN_METHOD(HeapProfiler::TakeSnapshot) { 108 | NanScope(); 109 | Local title = NanNew(""); 110 | uint32_t len = args.Length(); 111 | 112 | ActivityControlAdapter *control = NULL; 113 | 114 | if (len == 1) { 115 | if (args[0]->IsString()) { 116 | title = args[0]->ToString(); 117 | } else if (args[0]->IsFunction()) { 118 | //control = new ActivityControlAdapter(args[0]); 119 | } 120 | } 121 | 122 | if (len == 2) { 123 | if (args[0]->IsString()) { 124 | title = args[0]->ToString(); 125 | } 126 | 127 | if (args[1]->IsFunction()) { 128 | //control = new ActivityControlAdapter(args[1]); 129 | } 130 | } 131 | 132 | #if (NODE_MODULE_VERSION < 12) 133 | const v8::HeapSnapshot* snapshot = v8::HeapProfiler::TakeSnapshot(title, HeapSnapshot::kFull, control); 134 | #else // (NODE_MODULE_VERSION < 12) 135 | const v8::HeapSnapshot* snapshot = v8::Isolate::GetCurrent()->GetHeapProfiler()->TakeHeapSnapshot(title, control); 136 | #endif // (NODE_MODULE_VERSION < 12) 137 | 138 | NanReturnValue(Snapshot::New(snapshot)); 139 | } 140 | 141 | NAN_METHOD(HeapProfiler::DeleteAllSnapshots) { 142 | NanScope(); 143 | #if (NODE_MODULE_VERSION < 12) 144 | v8::HeapProfiler::DeleteAllSnapshots(); 145 | #else // (NODE_MODULE_VERSION < 12) 146 | v8::Isolate::GetCurrent()->GetHeapProfiler()->DeleteAllHeapSnapshots(); 147 | #endif // (NODE_MODULE_VERSION < 12) 148 | NanReturnUndefined(); 149 | } 150 | } //namespace nodex 151 | -------------------------------------------------------------------------------- /src/heap_profiler.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_HEAP_PROFILER_ 2 | #define NODE_HEAP_PROFILER_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace v8; 9 | using namespace node; 10 | 11 | namespace nodex { 12 | class HeapProfiler { 13 | public: 14 | static void Initialize(Handle target); 15 | 16 | HeapProfiler(); 17 | virtual ~HeapProfiler(); 18 | 19 | protected: 20 | static NAN_METHOD(GetSnapshotsCount); 21 | static NAN_METHOD(GetSnapshot); 22 | static NAN_METHOD(FindSnapshot); 23 | static NAN_METHOD(TakeSnapshot); 24 | static NAN_METHOD(DeleteAllSnapshots); 25 | 26 | private: 27 | static Persistent heap_profiler_template_; 28 | }; 29 | } //namespace nodex 30 | 31 | #endif // NODE_HEAP_PROFILER_H 32 | -------------------------------------------------------------------------------- /src/profile.cc: -------------------------------------------------------------------------------- 1 | #include "profile.h" 2 | #include "profile_node.h" 3 | 4 | using namespace v8; 5 | 6 | namespace nodex { 7 | 8 | Persistent Profile::profile_template_; 9 | 10 | void Profile::Initialize() { 11 | Handle profile_template = ObjectTemplate::New(); 12 | NanAssignPersistent(profile_template_, profile_template); 13 | profile_template->SetInternalFieldCount(1); 14 | profile_template->SetAccessor(NanNew("title"), Profile::GetTitle); 15 | #if (NODE_MODULE_VERSION < 12) 16 | profile_template->SetAccessor(NanNew("uid"), Profile::GetUid); 17 | #endif // (NODE_MODULE_VERSION < 12) 18 | profile_template->SetAccessor(NanNew("topRoot"), Profile::GetTopRoot); 19 | #if (NODE_MODULE_VERSION < 12) 20 | profile_template->SetAccessor(NanNew("bottomRoot"), Profile::GetBottomRoot); 21 | #endif // (NODE_MODULE_VERSION < 12) 22 | profile_template->Set(NanNew("delete"), NanNew(Profile::Delete)); 23 | } 24 | 25 | #if (NODE_MODULE_VERSION < 12) 26 | NAN_PROPERTY_GETTER(Profile::GetUid) { 27 | NanScope(); 28 | Local self = args.Holder(); 29 | void* ptr = NanGetInternalFieldPointer(self, 0); 30 | uint32_t uid = static_cast(ptr)->GetUid(); 31 | NanReturnValue(NanNew(uid)); 32 | } 33 | #endif // (NODE_MODULE_VERSION < 12) 34 | 35 | 36 | NAN_PROPERTY_GETTER(Profile::GetTitle) { 37 | NanScope(); 38 | Local self = args.Holder(); 39 | void* ptr = NanGetInternalFieldPointer(self, 0); 40 | Handle title = static_cast(ptr)->GetTitle(); 41 | NanReturnValue(title); 42 | } 43 | 44 | NAN_PROPERTY_GETTER(Profile::GetTopRoot) { 45 | NanScope(); 46 | Local self = args.Holder(); 47 | void* ptr = NanGetInternalFieldPointer(self, 0); 48 | const CpuProfileNode* node = static_cast(ptr)->GetTopDownRoot(); 49 | NanReturnValue(ProfileNode::New(node)); 50 | } 51 | 52 | 53 | #if (NODE_MODULE_VERSION < 12) 54 | NAN_PROPERTY_GETTER(Profile::GetBottomRoot) { 55 | NanScope(); 56 | Local self = args.Holder(); 57 | void* ptr = NanGetInternalFieldPointer(self, 0); 58 | const CpuProfileNode* node = static_cast(ptr)->GetBottomUpRoot(); 59 | NanReturnValue(ProfileNode::New(node)); 60 | } 61 | #endif // (NODE_MODULE_VERSION < 12) 62 | 63 | NAN_METHOD(Profile::Delete) { 64 | NanScope(); 65 | Handle self = args.This(); 66 | void* ptr = NanGetInternalFieldPointer(self, 0); 67 | static_cast(ptr)->Delete(); 68 | NanReturnUndefined(); 69 | } 70 | 71 | Handle Profile::New(const CpuProfile* profile) { 72 | NanEscapableScope(); 73 | 74 | if (profile_template_.IsEmpty()) { 75 | Profile::Initialize(); 76 | } 77 | 78 | if(!profile) { 79 | return NanUndefined(); 80 | } 81 | else { 82 | Local obj = NanNew(profile_template_)->NewInstance(); 83 | NanSetInternalFieldPointer(obj, 0, const_cast(profile)); 84 | return NanEscapeScope(obj); 85 | } 86 | } 87 | 88 | } // namespace nodex 89 | -------------------------------------------------------------------------------- /src/profile.h: -------------------------------------------------------------------------------- 1 | #ifndef NODE_PROFILE_ 2 | #define NODE_PROFILE_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace v8; 9 | 10 | namespace nodex { 11 | 12 | class Profile { 13 | public: 14 | static Handle New(const CpuProfile* profile); 15 | 16 | private: 17 | static NAN_PROPERTY_GETTER(GetUid); 18 | static NAN_PROPERTY_GETTER(GetTitle); 19 | static NAN_PROPERTY_GETTER(GetTopRoot); 20 | static NAN_PROPERTY_GETTER(GetBottomRoot); 21 | static NAN_METHOD(Delete); 22 | static void Initialize(); 23 | static Persistent profile_template_; 24 | }; 25 | 26 | } //namespace nodex 27 | #endif // NODE_PROFILE_ 28 | -------------------------------------------------------------------------------- /src/profile_node.cc: -------------------------------------------------------------------------------- 1 | #include "profile_node.h" 2 | 3 | using namespace v8; 4 | 5 | namespace nodex { 6 | 7 | Persistent ProfileNode::node_template_; 8 | 9 | void ProfileNode::Initialize() { 10 | Handle node_template = ObjectTemplate::New(); 11 | NanAssignPersistent(node_template_, node_template); 12 | node_template->SetInternalFieldCount(1); 13 | node_template->SetAccessor(NanNew("functionName"), ProfileNode::GetFunctionName); 14 | node_template->SetAccessor(NanNew("scriptName"), ProfileNode::GetScriptName); 15 | node_template->SetAccessor(NanNew("lineNumber"), ProfileNode::GetLineNumber); 16 | #if (NODE_MODULE_VERSION < 12) 17 | node_template->SetAccessor(NanNew("totalTime"), ProfileNode::GetTotalTime); 18 | node_template->SetAccessor(NanNew("selfTime"), ProfileNode::GetSelfTime); 19 | node_template->SetAccessor(NanNew("totalSamplesCount"), ProfileNode::GetTotalSamplesCount); 20 | node_template->SetAccessor(NanNew("selfSamplesCount"), ProfileNode::GetSelfSamplesCount); 21 | node_template->SetAccessor(NanNew("callUid"), ProfileNode::GetCallUid); 22 | #else // (NODE_MODULE_VERSION >= 12) 23 | // New in CpuProfileNode: GetHitCount, GetScriptId, GetColumnNumber, GetBailoutReason, GetNodeId. 24 | // GetHitCount replaces GetSelfSamplesCount. 25 | // Nothing on CpuProfileNode looks like a replacement for GetSelfTime, but the CpuProfile 26 | // object has GetSamplesCount, GetSample, GetSampleTimestamp, which might be good for something? 27 | // (Nothing replaces GetTotalSamplesCount and GetTotalTime, because bottom-up profiling is gone.) 28 | // 29 | // GetScriptId, GetBailoutReason, GetColumnNumber seem straightforward but not important. 30 | // I don't know what GetNodeId is used for. 31 | node_template->SetAccessor(NanNew("hitCount"), ProfileNode::GetHitCount); 32 | #endif // (NODE_MODULE_VERSION < 12) 33 | node_template->SetAccessor(NanNew("childrenCount"), ProfileNode::GetChildrenCount); 34 | node_template->Set(NanNew("getChild"), NanNew(ProfileNode::GetChild)); 35 | } 36 | 37 | NAN_PROPERTY_GETTER(ProfileNode::GetFunctionName) { 38 | NanScope(); 39 | Local self = args.Holder(); 40 | void* ptr = NanGetInternalFieldPointer(self, 0); 41 | Handle fname = static_cast(ptr)->GetFunctionName(); 42 | NanReturnValue(fname); 43 | } 44 | 45 | NAN_PROPERTY_GETTER(ProfileNode::GetScriptName) { 46 | NanScope(); 47 | Local self = args.Holder(); 48 | void* ptr = NanGetInternalFieldPointer(self, 0); 49 | Handle sname = static_cast(ptr)->GetScriptResourceName(); 50 | NanReturnValue(sname); 51 | } 52 | 53 | NAN_PROPERTY_GETTER(ProfileNode::GetLineNumber) { 54 | NanScope(); 55 | Local self = args.Holder(); 56 | void* ptr = NanGetInternalFieldPointer(self, 0); 57 | int32_t ln = static_cast(ptr)->GetLineNumber(); 58 | NanReturnValue(NanNew(ln)); 59 | } 60 | 61 | #if (NODE_MODULE_VERSION < 12) 62 | 63 | NAN_PROPERTY_GETTER(ProfileNode::GetTotalTime) { 64 | NanScope(); 65 | Local self = args.Holder(); 66 | void* ptr = NanGetInternalFieldPointer(self, 0); 67 | double ttime = static_cast(ptr)->GetTotalTime(); 68 | NanReturnValue(NanNew(ttime)); 69 | } 70 | 71 | NAN_PROPERTY_GETTER(ProfileNode::GetSelfTime) { 72 | NanScope(); 73 | Local self = args.Holder(); 74 | void* ptr = NanGetInternalFieldPointer(self, 0); 75 | double stime = static_cast(ptr)->GetSelfTime(); 76 | NanReturnValue(NanNew(stime)); 77 | } 78 | 79 | NAN_PROPERTY_GETTER(ProfileNode::GetTotalSamplesCount) { 80 | NanScope(); 81 | Local self = args.Holder(); 82 | void* ptr = NanGetInternalFieldPointer(self, 0); 83 | double samples = static_cast(ptr)->GetTotalSamplesCount(); 84 | NanReturnValue(NanNew(samples)); 85 | } 86 | 87 | NAN_PROPERTY_GETTER(ProfileNode::GetSelfSamplesCount) { 88 | NanScope(); 89 | Local self = args.Holder(); 90 | void* ptr = NanGetInternalFieldPointer(self, 0); 91 | double samples = static_cast(ptr)->GetSelfSamplesCount(); 92 | NanReturnValue(NanNew(samples)); 93 | } 94 | 95 | NAN_PROPERTY_GETTER(ProfileNode::GetCallUid) { 96 | NanScope(); 97 | Local self = args.Holder(); 98 | void* ptr = NanGetInternalFieldPointer(self, 0); 99 | uint32_t uid = static_cast(ptr)->GetCallUid(); 100 | NanReturnValue(NanNew(uid)); 101 | } 102 | 103 | #else // (NODE_MODULE_VERSION >= 12) 104 | 105 | NAN_PROPERTY_GETTER(ProfileNode::GetHitCount) { 106 | NanScope(); 107 | Local self = args.Holder(); 108 | void* ptr = NanGetInternalFieldPointer(self, 0); 109 | uint32_t hitCount = static_cast(ptr)->GetHitCount(); 110 | printf("HitCount %u\n", hitCount); 111 | NanReturnValue(NanNew(hitCount)); 112 | } 113 | 114 | #endif // (NODE_MODULE_VERSION < 12) 115 | 116 | NAN_PROPERTY_GETTER(ProfileNode::GetChildrenCount) { 117 | NanScope(); 118 | Local self = args.Holder(); 119 | void* ptr = NanGetInternalFieldPointer(self, 0); 120 | int32_t count = static_cast(ptr)->GetChildrenCount(); 121 | NanReturnValue(NanNew(count)); 122 | } 123 | 124 | NAN_METHOD(ProfileNode::GetChild) { 125 | NanScope(); 126 | if (args.Length() < 1) { 127 | NanThrowError("No index specified"); 128 | } else if (!args[0]->IsInt32()) { 129 | NanThrowError("Argument must be integer"); 130 | } 131 | int32_t index = args[0]->Int32Value(); 132 | Handle self = args.This(); 133 | void* ptr = NanGetInternalFieldPointer(self, 0); 134 | const CpuProfileNode* node = static_cast(ptr)->GetChild(index); 135 | NanReturnValue(ProfileNode::New(node)); 136 | } 137 | 138 | Handle ProfileNode::New(const CpuProfileNode* node) { 139 | NanEscapableScope(); 140 | 141 | if (node_template_.IsEmpty()) { 142 | ProfileNode::Initialize(); 143 | } 144 | 145 | if (!node) { 146 | return NanUndefined(); 147 | } 148 | else { 149 | Local obj = NanNew(node_template_)->NewInstance(); 150 | NanSetInternalFieldPointer(obj, 0, const_cast(node)); 151 | return NanEscapeScope(obj); 152 | } 153 | } 154 | 155 | } // namespace nodex 156 | -------------------------------------------------------------------------------- /src/profile_node.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef NODE_PROFILE_NODE_ 4 | #define NODE_PROFILE_NODE_ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace v8; 11 | 12 | namespace nodex { 13 | 14 | class ProfileNode { 15 | public: 16 | static Handle New(const CpuProfileNode* node); 17 | 18 | private: 19 | static NAN_PROPERTY_GETTER(GetFunctionName); 20 | static NAN_PROPERTY_GETTER(GetScriptName); 21 | static NAN_PROPERTY_GETTER(GetLineNumber); 22 | #if (NODE_MODULE_VERSION < 12) 23 | static NAN_PROPERTY_GETTER(GetTotalTime); 24 | static NAN_PROPERTY_GETTER(GetSelfTime); 25 | static NAN_PROPERTY_GETTER(GetTotalSamplesCount); 26 | static NAN_PROPERTY_GETTER(GetSelfSamplesCount); 27 | static NAN_PROPERTY_GETTER(GetCallUid); 28 | #else 29 | static NAN_PROPERTY_GETTER(GetHitCount); 30 | #endif 31 | static NAN_PROPERTY_GETTER(GetChildrenCount); 32 | static NAN_METHOD(GetChild); 33 | 34 | static void Initialize(); 35 | static Persistent node_template_; 36 | }; 37 | 38 | } 39 | #endif // NODE_PROFILE_NODE_ 40 | -------------------------------------------------------------------------------- /src/profiler.cc: -------------------------------------------------------------------------------- 1 | #include "heap_profiler.h" 2 | #include "cpu_profiler.h" 3 | 4 | namespace nodex { 5 | void InitializeProfiler(Handle target) { 6 | NanScope(); 7 | HeapProfiler::Initialize(target); 8 | CpuProfiler::Initialize(target); 9 | } 10 | 11 | NODE_MODULE(profiler, InitializeProfiler) 12 | } 13 | -------------------------------------------------------------------------------- /src/readme.md: -------------------------------------------------------------------------------- 1 | v8-profiler provides [node](http://github.com/ry/node) bindings for the v8 2 | profiler and integration with [node-inspector](http://github.com/dannycoates/node-inspector) 3 | 4 | ## Installation 5 | 6 | npm install v8-profiler 7 | 8 | ## Usage 9 | 10 | var profiler = require('v8-profiler'); 11 | 12 | ## API 13 | 14 | var snapshot = profiler.takeSnapshot([name]) //takes a heap snapshot 15 | 16 | profiler.startProfiling([name]) //begin cpu profiling 17 | var cpuProfile = profiler.stopProfiling([name]) //finish cpu profiling 18 | 19 | ## node-inspector 20 | 21 | Cpu profiles can be viewed and heap snapshots may be taken and viewed from the 22 | profiles panel. 23 | -------------------------------------------------------------------------------- /src/snapshot.cc: -------------------------------------------------------------------------------- 1 | #include "snapshot.h" 2 | #include "node.h" 3 | #include "node_buffer.h" 4 | 5 | using namespace v8; 6 | using namespace node; 7 | 8 | namespace nodex { 9 | 10 | Persistent Snapshot::snapshot_template_; 11 | 12 | void Snapshot::Initialize() { 13 | Handle snapshot_template = ObjectTemplate::New(); 14 | NanAssignPersistent(snapshot_template_, snapshot_template); 15 | snapshot_template->SetInternalFieldCount(1); 16 | snapshot_template->SetAccessor(NanNew("title"), Snapshot::GetTitle); 17 | snapshot_template->SetAccessor(NanNew("uid"), Snapshot::GetUid); 18 | #if (NODE_MODULE_VERSION < 12) 19 | snapshot_template->SetAccessor(NanNew("type"), Snapshot::GetType); 20 | #endif // (NODE_MODULE_VERSION < 12) 21 | snapshot_template->Set(NanNew("delete"), NanNew(Snapshot::Delete)); 22 | snapshot_template->Set(NanNew("serialize"), NanNew(Snapshot::Serialize)); 23 | } 24 | 25 | NAN_PROPERTY_GETTER(Snapshot::GetTitle) { 26 | NanScope(); 27 | Local self = args.Holder(); 28 | void* ptr = NanGetInternalFieldPointer(self, 0); 29 | Handle title = static_cast(ptr)->GetTitle(); 30 | NanReturnValue(title); 31 | } 32 | 33 | NAN_PROPERTY_GETTER(Snapshot::GetUid) { 34 | NanScope(); 35 | Local self = args.Holder(); 36 | void* ptr = NanGetInternalFieldPointer(self, 0); 37 | uint32_t uid = static_cast(ptr)->GetUid(); 38 | NanReturnValue(NanNew(uid)); 39 | } 40 | 41 | #if (NODE_MODULE_VERSION < 12) 42 | NAN_PROPERTY_GETTER(Snapshot::GetType) { 43 | NanScope(); 44 | Local self = args.Holder(); 45 | void* ptr = NanGetInternalFieldPointer(self, 0); 46 | 47 | HeapSnapshot::Type type = static_cast(ptr)->GetType(); 48 | Local t; 49 | 50 | switch(type) { 51 | case HeapSnapshot::kFull: 52 | t = NanNew("Full"); 53 | break; 54 | default: 55 | t = NanNew("Unknown"); 56 | } 57 | 58 | NanReturnValue(t); 59 | } 60 | #endif // (NODE_MODULE_VERSION < 12) 61 | 62 | NAN_METHOD(Snapshot::Delete) { 63 | NanScope(); 64 | Handle self = args.This(); 65 | void* ptr = NanGetInternalFieldPointer(self, 0); 66 | static_cast(ptr)->Delete(); 67 | NanReturnUndefined(); 68 | } 69 | 70 | Handle Snapshot::New(const HeapSnapshot* snapshot) { 71 | NanEscapableScope(); 72 | 73 | if (snapshot_template_.IsEmpty()) { 74 | Snapshot::Initialize(); 75 | } 76 | 77 | if(!snapshot) { 78 | return NanUndefined(); 79 | } 80 | else { 81 | Local snapshot_template = NanNew(snapshot_template_); 82 | Local obj = snapshot_template->NewInstance(); 83 | NanSetInternalFieldPointer(obj, 0, const_cast(snapshot)); 84 | return NanEscapeScope(obj); 85 | } 86 | } 87 | 88 | class OutputStreamAdapter : public v8::OutputStream { 89 | public: 90 | OutputStreamAdapter(Handle arg) { 91 | Local onEnd = NanNew("onEnd"); 92 | Local onData = NanNew("onData"); 93 | 94 | if (!arg->IsObject()) { 95 | NanThrowTypeError("You must specify an Object as first argument"); 96 | } 97 | 98 | obj = arg->ToObject(); 99 | if (!obj->Has(onEnd) || !obj->Has(onData)) { 100 | NanThrowTypeError("You must specify properties 'onData' and 'onEnd' to invoke this function"); 101 | } 102 | 103 | if (!obj->Get(onEnd)->IsFunction() || !obj->Get(onData)->IsFunction()) { 104 | NanThrowTypeError("Properties 'onData' and 'onEnd' have to be functions"); 105 | } 106 | 107 | onEndFunction = Local::Cast(obj->Get(onEnd)); 108 | onDataFunction = Local::Cast(obj->Get(onData)); 109 | 110 | abort = NanFalse(); 111 | } 112 | 113 | void EndOfStream() { 114 | TryCatch try_catch; 115 | onEndFunction->Call(obj, 0, NULL); 116 | 117 | if (try_catch.HasCaught()) { 118 | FatalException(try_catch); 119 | } 120 | } 121 | 122 | int GetChunkSize() { 123 | return 10240; 124 | } 125 | 126 | WriteResult WriteAsciiChunk(char* data, int size) { 127 | NanScope(); 128 | 129 | Handle argv[2] = { 130 | NanNewBufferHandle(data, size), 131 | NanNew(size) 132 | }; 133 | 134 | TryCatch try_catch; 135 | abort = onDataFunction->Call(obj, 2, argv); 136 | 137 | if (try_catch.HasCaught()) { 138 | FatalException(try_catch); 139 | return kAbort; 140 | } 141 | 142 | if (abort.IsEmpty() || !abort->IsBoolean()) { 143 | return kContinue; 144 | } 145 | 146 | return abort->IsTrue() ? kAbort : kContinue; 147 | } 148 | 149 | private: 150 | Local abort; 151 | Handle obj; 152 | Handle onEndFunction; 153 | Handle onDataFunction; 154 | }; 155 | 156 | NAN_METHOD(Snapshot::Serialize) { 157 | NanScope(); 158 | Handle self = args.This(); 159 | 160 | uint32_t argslen = args.Length(); 161 | 162 | if (argslen == 0) { 163 | NanThrowTypeError("You must specify arguments to invoke this function"); 164 | } 165 | 166 | OutputStreamAdapter *stream = new OutputStreamAdapter(args[0]); 167 | 168 | void* ptr = NanGetInternalFieldPointer(self, 0); 169 | static_cast(ptr)->Serialize(stream, HeapSnapshot::kJSON); 170 | 171 | NanReturnUndefined(); 172 | } 173 | 174 | } //namespace nodex 175 | -------------------------------------------------------------------------------- /src/snapshot.h: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ifndef NODE_SNAPSHOT_ 4 | #define NODE_SNAPSHOT_ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace v8; 11 | 12 | namespace nodex { 13 | 14 | class Snapshot { 15 | public: 16 | static Handle New(const HeapSnapshot* snapshot); 17 | 18 | private: 19 | Snapshot(const v8::HeapSnapshot* snapshot) 20 | : m_snapshot(snapshot){} 21 | 22 | const v8::HeapSnapshot* m_snapshot; 23 | 24 | static NAN_PROPERTY_GETTER(GetUid); 25 | static NAN_PROPERTY_GETTER(GetTitle); 26 | static NAN_PROPERTY_GETTER(GetType); 27 | static NAN_METHOD(Delete); 28 | static NAN_METHOD(Serialize); 29 | 30 | static void Initialize(); 31 | static Persistent snapshot_template_; 32 | }; 33 | } //namespace nodex 34 | #endif // NODE_SNAPSHOT_ 35 | -------------------------------------------------------------------------------- /src/wscript: -------------------------------------------------------------------------------- 1 | srcdir = '.' 2 | blddir = 'build' 3 | VERSION = '0.1.0' 4 | 5 | def set_options(ctx): 6 | ctx.tool_options('compiler_cxx') 7 | 8 | def configure(ctx): 9 | ctx.check_tool('compiler_cxx') 10 | ctx.check_tool('node_addon') 11 | 12 | def build(ctx): 13 | t = ctx.new_task_gen('cxx', 'shlib', 'node_addon') 14 | t.target = 'profiler' 15 | t.source = ctx.path.ant_glob('**/*.cc') 16 | -------------------------------------------------------------------------------- /webkit-devtools-agent.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | //var Debugger = require('./lib/debugger'); 3 | var WebSocket = require('ws'); 4 | var WebSocketServer = WebSocket.Server; 5 | 6 | var DevToolsAgentProxy = module.exports = function () { 7 | this.wss = null; 8 | this.backend = null; 9 | this.frontend = null; 10 | this.debuggerAgent = null; 11 | this.port = process.argv[2] || 9999; 12 | this.bind_to = process.argv[3] || '0.0.0.0'; 13 | this.ipc_port = process.argv[4] || 3333; 14 | this.verbose = process.argv[5] || false; 15 | }; 16 | 17 | (function () { 18 | process.on('uncaughtException', function (err) { 19 | console.error('webkit-devtools-agent: Websockets service uncaught exception: '); 20 | console.error(err.stack); 21 | }); 22 | 23 | this.onFrontendMessage = function (message) { 24 | var self = this; 25 | var data; 26 | try { 27 | data = JSON.parse(message); 28 | } catch(e) { 29 | console.log(e.stack); 30 | } 31 | var command = data.method.split('.'); 32 | var domainName = command[0]; 33 | 34 | if (domainName !== 'Debugger') { 35 | this.backend.send(message); 36 | return; 37 | } 38 | 39 | var id = data.id; 40 | var method = command[1]; 41 | var params = data.params; 42 | 43 | /*if (!this.debuggerAgent[method]) { 44 | console.warn('%s is not implemented', data.method); 45 | return; 46 | } 47 | 48 | this.debuggerAgent[method](params, function(result) { 49 | var response = { 50 | id: id, 51 | result: result 52 | }; 53 | self.frontend.send(JSON.stringify(response)); 54 | });*/ 55 | }; 56 | this.frontends = []; 57 | this.onFrontendConnection = function(socket) { 58 | var self = this; 59 | socket.on('message', this.onFrontendMessage.bind(this)); 60 | socket.on('close', (function(){ 61 | for(var i =0; i