├── LICENSE ├── README ├── heat_tracer.js └── public ├── heat_tracer.html └── heat_tracer_client.js /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Geoff Flarity 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | First thing's first, you're going to need a system with DTrace. This likely means Solaris (or one of its decedents), OS X, or a BSD variant. There doesn't appear to be Dtrace available for Linux :( 2 | 3 | Secondly, you'll exposing a significant security volunterability if you change the binding port away from localhost. Please only do so if you're confident of your network. 4 | 5 | Still with us? Good. 6 | 7 | For this tutorial you'll also need: 8 | 9 | node - http://nodejs.org/#download 10 | npm - https://github.com/isaacs/npm 11 | node-libdtrace - https://github.com/bcantrill/node-libdtrace 12 | Socket.IO – “npm install socket.io” 13 | express - npm install express 14 | 15 | 16 | Once these are installed simply run heat tracer as root (dtrace requires root for it's kernelspace trapping goodness): 17 | 18 | sudo node heat_tracer.js 19 | 20 | 21 | That's it, browse to http://localhost/heat_tracer.html and enjoy looking at your syscall latency. 22 | 23 | 24 | This code is released under the MIT license. See LICENSE file for details.. 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /heat_tracer.js: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var libdtrace = require('libdtrace'); 3 | var express = require('express'); 4 | 5 | /* create our express & http server and prepare to serve javascript files in ./public 6 | */ 7 | var app = express() 8 | , http = require('http') 9 | , server = http.createServer(app) 10 | , io = require('socket.io').listen(server); 11 | 12 | app.configure(function(){ 13 | app.use(express.static(__dirname + '/public')); 14 | }); 15 | 16 | /* to decreate verbosity of socket.io 17 | */ 18 | io.set('log level', 1); 19 | 20 | /* Before we go any further we must realize that each time a user connects we're going to want to 21 | them send them dtrace aggregation every second. We can do so using 'setInterval', but we must 22 | keep track of both the intervals we set and the dtrace consumers that are created as we'll need 23 | them later when the client disconnects. 24 | */ 25 | var interval_id_by_session_id = {}; 26 | var dtp_by_session_id = {}; 27 | 28 | /* In order to effecienctly send packets we're going to use the Socket.IO library which seemlessly 29 | integrates with express. 30 | */ 31 | 32 | /* Now that we have a web socket server, we need to create a handler for connection events. These 33 | events represet a client connecting to our server */ 34 | io.sockets.on('connection', function(socket) { 35 | 36 | /* Like the web server object, we must also define handlers for various socket events that 37 | will happen during the lifetime of the connection. These will define how we interact with 38 | the client. The first is a message event which occurs when the client sends something to 39 | the server. */ 40 | socket.on( 'message', function(message) { 41 | /* The only message the client ever sends will be sent right after connecting. 42 | So it will happen only once during the lifetime of a socket. This message also 43 | contains a d script which defines an agregation to walk. 44 | */ 45 | var dtp = new libdtrace.Consumer(); 46 | var dscript = message['dscript']; 47 | console.log( dscript ); 48 | dtp.strcompile(dscript); 49 | dtp.go(); 50 | dtp_by_session_id[socket.sessionId] = dtp; 51 | 52 | 53 | /* All that's left to do is send the aggration data from the dscript. */ 54 | interval_id_by_session_id[socket.sessionId] = setInterval(function () { 55 | var aggdata = {}; 56 | try { 57 | dtp.aggwalk(function (id, key, val) { 58 | for( index in val ) { 59 | /* console.log( 'key: ' + key + ', interval: ' + 60 | val[index][0][0] + '-' + val[index][0][1], ', count ' + val[index][1] ); */ 61 | 62 | aggdata[key] = val; 63 | } 64 | } ); 65 | socket.emit( 'message', aggdata ); 66 | } catch( err ) { 67 | console.log(err); 68 | } 69 | 70 | }, 1001 ); 71 | } ); 72 | 73 | 74 | /* Not so fast. If a client disconnects we don't want their respective dtrace consumer to 75 | keep collecting data any more. We also don't want to try to keep sending anything to them 76 | period. So clean up. */ 77 | socket.on('disconnect', function(){ 78 | clearInterval(clearInterval(interval_id_by_session_id[socket.sessionId])); 79 | var dtp = dtp_by_session_id[socket.sessionId]; 80 | delete dtp_by_session_id[socket.sessionId]; 81 | dtp.stop(); 82 | console.log('disconnected'); 83 | }); 84 | 85 | 86 | } ); 87 | 88 | 89 | server.listen(8000); 90 | 91 | 92 | -------------------------------------------------------------------------------- /public/heat_tracer.html: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/heat_tracer_client.js: -------------------------------------------------------------------------------- 1 | /* On load we create our web socket (or flash socket if your browser doesn't support it ) and 2 | send the d script we wish to be tracing. This extremely powerful and *insecure*. */ 3 | function heat_tracer() { 4 | 5 | //Global vars 6 | setup(); 7 | 8 | var socket = new io.connect(); //connect to localhost presently 9 | 10 | socket.on('connect', function(){ 11 | console.log('on connection'); 12 | var dscript = "syscall:::entry\n{\nself->syscall_entry_ts[probefunc] = vtimestamp;\n}\nsyscall:::return\n/self->syscall_entry_ts[probefunc]/\n{\n\n@time[probefunc] = lquantize((vtimestamp - self->syscall_entry_ts[probefunc] ) / 1000, 0, 63, 2);\nself->syscall_entry_ts[probefunc] = 0;\n}"; 13 | socket.emit( 'message', { 'dscript' : dscript } ); 14 | }); 15 | 16 | 17 | /* The only messages we recieve should contain contain the dtrace aggregation data we requested 18 | on connection. */ 19 | socket.on('message', function(message){ 20 | console.log( message ); 21 | draw(message); 22 | 23 | /* for ( key in message ) { 24 | val = message[key]; 25 | console.log( 'key: ' + key + ', interval: ' + val[0][0] + '-' + val[0][1], ', count ' + val[1] ); 26 | } 27 | */ 28 | }); 29 | 30 | socket.on('disconnect', function(){ 31 | }); 32 | 33 | } 34 | 35 | 36 | /* Take the aggregation data and update the heatmap */ 37 | function draw(message) { 38 | 39 | /* Latest data goes in the right most column, initialize it */ 40 | var syscalls_by_latency = []; 41 | for ( var index = 0; index < 32; index++ ) { 42 | syscalls_by_latency[index] = 0; 43 | } 44 | 45 | /* Presently we have the latency for each system call quantized in our message. Merge the data 46 | such that we have all the system call latency quantized together. This gives us the number 47 | of syscalls made with latencies in each particular band. */ 48 | for ( var syscall in message ) { 49 | var val = message[syscall]; 50 | for ( result_index in val ) { 51 | var latency_start = val[result_index][0][0]; 52 | var count = val[result_index][1]; 53 | /* The d script we're using lquantizes from 0 to 63 in steps of two. So dividing by 2 54 | tells us which row this result belongs in */ 55 | syscalls_by_latency[Math.floor(latency_start/2)] += count; 56 | } 57 | } 58 | 59 | 60 | /* We just created a new column, shift the console to the left and add it. */ 61 | console_columns.shift(); 62 | console_columns.push(syscalls_by_latency); 63 | drawArray(console_columns); 64 | } 65 | 66 | 67 | 68 | /* Draw the columns and rows that map up the heatmap on to the canvas element */ 69 | function drawArray(console_columns) { 70 | var canvas = document.getElementById('canvas'); 71 | if (canvas.getContext) { 72 | var ctx = canvas.getContext('2d'); 73 | for ( var column_index in console_columns ) { 74 | var column = console_columns[column_index]; 75 | for ( var entry_index in column ) { 76 | entry = column[entry_index]; 77 | 78 | /* We're using a logarithmic scale for the brightness. This was all arrived at by 79 | trial and error and found to work well on my Mac. In the future this 80 | could all be adjustable with controls */ 81 | var red_value = 0; 82 | if ( entry != 0 ) { 83 | red_value = Math.floor(Math.log(entry)/Math.log(2)); 84 | } 85 | //console.log(red_value); 86 | ctx.fillStyle = 'rgb(' + (red_value * 25) + ',0,0)'; 87 | ctx.fillRect(column_index*16, 496-(entry_index*16), 16, 16); 88 | } 89 | } 90 | } 91 | } 92 | 93 | 94 | /* The heatmap is is really a 64x32 grid. Initialize the array which contains the grid data. */ 95 | function setup() { 96 | console_columns = []; 97 | 98 | for ( var column_index = 0; column_index < 64; column_index++ ) { 99 | var column = []; 100 | for ( var entry_index = 0; entry_index < 32; entry_index++ ) { 101 | column[entry_index] = 0; 102 | } 103 | console_columns.push(column); 104 | } 105 | 106 | } 107 | 108 | --------------------------------------------------------------------------------