Skip to content

Instantly share code, notes, and snippets.

@dannvix
Last active August 29, 2015 14:21
Show Gist options
  • Save dannvix/3cb0ebf3043214e5561b to your computer and use it in GitHub Desktop.
Save dannvix/3cb0ebf3043214e5561b to your computer and use it in GitHub Desktop.
Generic parser and store for ETW (Event Tracing for Windows)
var fs = require("fs");
var xml2js = require("xml2js"),
Lazy = require("Lazy"),
Papa = require("papaparse"),
moment = require("moment");
var Guid = require("./Guid"); // https://gist.github.com/dannvix/e1a93a3dda87cef7a60b
var EtwEventStore = (function() {
function EventsByType(store, type) {
this._store = store;
this._type = type;
};
EventsByType.prototype.i = function(index) {
if (!this._store._events[this._type]) return null;
if (index < 0 ||
index >= this._store._events[this._type].length)
{
throw ("invalid index [" + index + "]");
}
return this._store._events[this._type][index];
};
EventsByType.prototype.g = function(guid) {
if (!this._store._events[this._type]) return null;
var indices = this._store._guidIndex[this._type].lookup(guid);
return (!indices ? null : indices.map(function(idx) {
return this.i(idx);
}.bind(this)));
};
EventsByType.prototype.rg = function(guid) {
if (!this._store._events[this._type]) return null;
var indices = this._store._relGuidIndex[this._type].lookup(guid);
return (!indices ? null : indices.map(function(idx) {
return this.i(idx);
}.bind(this)));
};
EventsByType.prototype.length = function() {
if (!this._store._events[this._type]) return 0;
return this._store._events[this._type].length;
};
EventsByType.prototype.events = function() {
return this._store._events[this._type];
};
function GuidIndex() {
this._index = [];
};
GuidIndex.prototype.push = function(guid, value) {
if (this._index.length == 0) {
this._index.push({guid: guid, values: [value]});
return;
}
var binaryInsert = function(begin, end) {
var cent = ((begin + end) / 2.0) | 0;
if (begin >= end) {
this._index.splice(begin, 0, {guid: guid, values: [value]});
return;
}
var comp = this._index[cent].guid.compare(guid);
if (0 == comp) {
this._index[cent].values.push(value);
return;
};
if (comp < 0) return binaryInsert(cent+1, end);
if (comp > 0) return binaryInsert(begin, cent);
}.bind(this);
binaryInsert(0, (this._index.length));
};
GuidIndex.prototype.lookup = function(guid) {
if (this._index.length == 0) return null;
var binarySearch = function(begin, end) {
var cent = ((begin + end) / 2) | 0;
if (begin >= end) return null;
var comp = this._index[cent].guid.compare(guid);
if (0 == comp) return this._index[cent];
if (comp < 0) return binarySearch(cent+1, end);
if (comp > 0) return binarySearch(begin, cent);
}.bind(this);
var result = binarySearch(0, (this._index.length));
return ((result) ? (result.values) : null);
};
function EtwEventStore(provider, manifestXml) {
this._providerName = provider.toLowerCase();
this._eventTypes = [];
this._eventTmpls = {};
this._events = {}; //by typeName
this._guidIndex = {}; // by typeName, sorted by GUID
this._relGuidIndex = {}; // by typeName, sorted by GUID
var p = xml2js.Parser({async:false});
p.parseString(manifestXml, function(err, root) {
if (err) throw err;
var that = this;
var provider = root.instrumentationManifest.instrumentation[0].events[0].provider[0];
if (provider.$.name.toLowerCase() !== that._providerName) {
throw ("invalid provider [" + provider.$.name + "]");
}
// event types
provider.events[0].event.forEach(function(evnt) {
var id = parseInt(evnt.$.value, 10);
that._eventTypes[id] = {
id: id,
name: evnt.$.task,
template: evnt.$.template
};
});
// event templates
provider.templates[0].template.forEach(function(tmpl) {
var name = tmpl.$.tid; //e.g. "HandleEvent"
that._eventTmpls[name] = {
name: name,
columns: tmpl.data.map(function(col) {
return ({
name: col.$.name,
inType: col.$.inType,
outType: col.$.outType
});
})
};
});
}.bind(this));
};
EtwEventStore.prototype.pushEvent = function(csvCols) {
var provider = csvCols[0].toLowerCase();
if (provider !== this._providerName) {
return;
}
var typeId = parseInt(csvCols[2], 10),
type = this._eventTypes[typeId];
// extract user-data columns
var evntData = csvCols.slice(19),
columns = {};
this._eventTmpls[type.template].columns.forEach(function(colTmpl, idx) {
columns[colTmpl.name] = evntData[idx].toString().trim();
});
// guid
var actGuid = new Guid(csvCols[14]),
relGuid = new Guid(csvCols[15]);
// timestamp conversion
// Windows SYSTEMTIME is "# of 100-ns from Jan 1, 1601"
// Unix time is "# of 1s from Jan 1, 1970"
// (Jan 1, 1970) - (Jan 1, 1601) =~ 1116444736 seconds
var timestamp = parseInt(csvCols[16], 10);
timestamp = moment.unix(Math.floor((timestamp-116444736e9)/1e7));
if (!this._events[type.name]) {
this._events[type.name] = [];
this._guidIndex[type.name] = new GuidIndex();
this._relGuidIndex[type.name] = new GuidIndex();
}
// extend event columns
columns.$ProcId = parseInt(csvCols[9], 16),
columns.$ThreadId = parseInt(csvCols[10], 16),
columns.$ActivityGuid = actGuid,
columns.$RelatedActivityGuid = relGuid,
columns.$Timestamp = timestamp
this._events[type.name].push(columns);
this._guidIndex[type.name].push(actGuid, this._events[type.name].length-1);
if (!relGuid.isNull()) {
this._relGuidIndex[type.name].push(relGuid, this._events[type.name].length-1);
}
};
EtwEventStore.prototype.t = function(typeName) {
return new EventsByType(this, typeName);
};
return EtwEventStore;
})();
if (typeof module === "object") { module.exports = EtwEventStore; }
if (typeof define === "function" && define.amd) { define(EtwEventStore); }
/*
* fs.readFile("ETWProv.man", function(error, data) {
* var data = data.toString();
* var idx, decoded = [];
* // decode UTF-16LE, firstly remove UTF-16LE BOM if necessary
* if (data.charCodeAt(0) == 0xFFFD && data.charCodeAt(1) == 0xFFFD) {
* data = data.slice(2);
* }
* for (idx = 0; idx < data.length; idx+=2) {
* decoded.push(data.charCodeAt(idx)|(data.charCodeAt(idx+1)<<8));
* }
* var manifestXml = String.fromCharCode.apply(String, decoded);
* var store = new EtwEventStore("MyCompany-MyProject", manifestXml);
*
* // tracerpt.exe MyProjectEtw.etl --import ETWProv.man -of CSV -o MyProjectEtw.csv
* new Lazy(fs.createReadStream("MyProjectEtw.csv")).lines
* .forEach(function(line) {
* var row = Papa.parse(line.toString(), {
* delimiter: ",",
* newline: "\r\n",
* header: false,
* dynamicTyping: false,
* });
* if (row.errors && row.errors.length > 0) {
* throw row.errors;
* }
* var cols = row.data[0];
* store.pushEvent(cols);
* })
* .on("pipe", function() {
* var majorEvents = store.t("MajorEvents");
* var events = [], idx;
* for (idx = 0; idx < majorEvents.length(); idx++) {
* var majorEvent = majorEvents.i(idx),
* guid = majorEvent.$ActivityGuid;
* events.push({
* $MajorEvent: majorEvent,
* $SubEventA: store.t("SubEventA").rg(guid),
* $SubEventB: store.t("SubEventB").rg(guid),
* });
* }
* console.log("#events: " + events.length);
* console.log(events[0]);
* });
* });
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment