Permalink
Browse files

add stackcollapse

  • Loading branch information...
1 parent e1b05c6 commit ac5f7ddc33927d8d19167c7ba5204fcdbecbd644 @davepacheco davepacheco committed Mar 31, 2012
Showing with 171 additions and 2 deletions.
  1. +2 −0 .gitignore
  2. +1 −1 Makefile
  3. +23 −0 cmd/stackcollapse
  4. +46 −0 lib/input-common.js
  5. +93 −0 lib/input-dtrace.js
  6. +6 −1 package.json
View
@@ -0,0 +1,2 @@
+node_modules
+*.swp
View
@@ -17,7 +17,7 @@ CATEST = tools/catest
#
# Files
#
-JS_FILES := $(shell find lib test -name '*.js')
+JS_FILES := $(shell find cmd lib test -name '*.js')
JSL_CONF_NODE = tools/jsl.node.conf
JSL_FILES_NODE = $(JS_FILES)
View
@@ -0,0 +1,23 @@
+#!/usr/bin/env node
+
+/*
+ * cmd/stackcollapse: emit collapsed stack traces from DTrace output
+ */
+
+var mod_input_common = require('../lib/input-common');
+var mod_input_dtrace = require('../lib/input-dtrace');
+var mod_bunyan = require('bunyan');
+
+var log = new mod_bunyan({
+ 'name': 'stackcollapse',
+ 'stream': process.stderr
+});
+
+var reader = new mod_input_dtrace.reader(process.stdin, log);
+mod_input_common.collapseStacks(reader, function (err, stacks) {
+ stacks.eachStackSorted(function (frames, count) {
+ process.stdout.write(frames.join(',') + ' ' + count + '\n');
+ });
+});
+
+process.stdin.resume();
View
@@ -0,0 +1,46 @@
+/*
+ * lib/input-common.js: common routines for importing stacks
+ */
+
+function StackSet()
+{
+ this.ss_counts = {}; /* maps serialized stack -> count */
+ this.ss_stacks = {}; /* maps serialized stack -> stack */
+}
+
+StackSet.prototype.addStack = function (stack, count)
+{
+ var key = stack.join(',');
+
+ if (!this.ss_counts.hasOwnProperty(key)) {
+ this.ss_counts[key] = 0;
+ this.ss_stacks[key] = stack;
+ }
+
+ this.ss_counts[key] += count;
+};
+
+StackSet.prototype.eachStackSorted = function (callback)
+{
+ var set = this;
+ var keys = Object.keys(this.ss_stacks);
+
+ keys.sort(function (a, b) {
+ return (set.ss_counts[b] - set.ss_counts[a]);
+ });
+
+ keys.forEach(function (key) {
+ callback(set.ss_stacks[key], set.ss_counts[key]);
+ });
+};
+
+exports.collapseStacks = function (reader, callback)
+{
+ var stacks = new StackSet();
+
+ reader.on('stack', function (stack, count) {
+ stacks.addStack(stack, count);
+ });
+
+ reader.on('end', function () { callback(null, stacks); });
+};
View
@@ -0,0 +1,93 @@
+/*
+ * lib/input-dtrace.js: reads output from a DTrace profiling script, which emits
+ * stanzas that look like this:
+ *
+ * prog`foo+0x8
+ * prog`main+0x21
+ * prog`_start+0x80
+ * 14
+ *
+ * This examples shows that that particular stacktrace was seen 14 times. You
+ * can generate such output with:
+ *
+ * # dtrace -o stacks.out \
+ * -n 'profile-97/execname == "myprogram"/{ @[ustack()] = count(); }'
+ */
+
+var mod_util = require('util');
+var mod_events = require('events');
+
+var mod_carrier = require('carrier');
+
+/* We always ignore the first 3 lines. */
+var NHEADERLINES = 3;
+
+function DTraceStreamReader(input, log)
+{
+ this.dsr_log = log;
+ this.dsr_linenum = 0;
+ this.dsr_stack = [];
+ this.dsr_carrier = mod_carrier.carry(input);
+ this.dsr_carrier.on('line', this.onLine.bind(this));
+ this.dsr_carrier.on('end', this.onEnd.bind(this));
+
+ mod_events.EventEmitter.call(this);
+}
+
+mod_util.inherits(DTraceStreamReader, mod_events.EventEmitter);
+
+DTraceStreamReader.prototype.onLine = function (line)
+{
+ /* The first three lines are always ignored. */
+ if (++this.dsr_linenum <= NHEADERLINES)
+ return;
+
+ var match = /^\s+(\d+)\s*$/.exec(line);
+ if (match) {
+ if (this.dsr_stack.length === 0) {
+ this.dsr_log.warn('line ' + this.dsr_linenum +
+ ': found count with no stack');
+ return;
+ }
+
+ this.emit('stack', this.dsr_stack, parseInt(match[1], 10));
+ this.dsr_stack = [];
+ return;
+ }
+
+ /*
+ * In general, lines may have leading or trailing whitespace and the
+ * following components:
+ *
+ * module`function+offset
+ *
+ * We try to avoid assuming too much about the form in order to support
+ * various annotations provided by ustack helpers, but we want to strip
+ * off the offset.
+ */
+ var frame = line;
+ frame = frame.replace(/^\s+/, '');
+ frame = frame.replace(/\s+$/, '');
+ /* JSSTYLED */
+ frame = frame.replace(/\+.*/, '');
+
+ if (line.length === 0) {
+ if (this.dsr_stack.length !== 0)
+ this.dsr_log.warn('line ' + this.dsr_lineum +
+ ': unexpected blank line');
+ return;
+ }
+
+ this.dsr_stack.unshift(frame);
+};
+
+DTraceStreamReader.prototype.onEnd = function ()
+{
+ if (this.dsr_stack.length !== 0)
+ this.dsr_log.warn('line ' + this.dsr_linenum +
+ ': unexpected end of stream');
+
+ this.emit('end');
+};
+
+exports.reader = DTraceStreamReader;
View
@@ -1,11 +1,16 @@
{
"name": "stackvis",
"version": "0.0.1",
- "description": "stack visualization tools"
+ "description": "stack visualization tools",
"main": "./lib/stackvis.js",
"repository": {
"type": "git",
"url": "git://github.com/davepacheco/stackvis.git"
+ },
+
+ "dependencies": {
+ "carrier": "0.1.7"
+ "bunyan": "0.6.8"
}
}

0 comments on commit ac5f7dd

Please sign in to comment.