Permalink
Browse files

Add a basic instrumentation API

  • Loading branch information...
wycats committed Oct 15, 2012
1 parent 7b054f7 commit 134fc61b764ef5b464ab3ebef968716b7df5c50c
@@ -0,0 +1,133 @@
/**
@class Ember.Instrumentation
The purpose of the Ember Instrumentation module is
to provide efficient, general-purpose instrumentation
for Ember.
Subscribe to a listener by using `Ember.subscribe`:
Ember.subscribe("render", {
before: function(name, timestamp, payload) {
},
after: function(name, timestamp, payload) {
}
});
If you return a value from the `before` callback, that same
value will be passed as a fourth parameter to the `after`
callback.
Instrument a block of code by using `Ember.instrument`:
Ember.instrument("render.handlebars", payload, function() {
// rendering logic
}, binding);
Event names passed to `Ember.instrument` are namespaced
by periods, from more general to more specific. Subscribers
can listen for events by whatever level of granularity they
are interested in.
In the above example, the event is `render.handlebars`,
and the subscriber listened for all events beginning with
`render`. It would receive callbacks for events named
`render`, `render.handlebars`, `render.container`, or
even `render.handlebars.layout`.
*/
Ember.Instrumentation = {};
var subscribers = [], cache = {};
var populateListeners = function(name) {
var listeners = [], subscriber;
for (var i=0, l=subscribers.length; i<l; i++) {
subscriber = subscribers[i];
if (subscriber.regex.test(name)) {
listeners.push(subscriber.object);
}
}
cache[name] = listeners;
return listeners;
};
Ember.Instrumentation.instrument = function(name, payload, callback, binding) {
var listeners = cache[name];
if (!listeners) {
listeners = populateListeners(name);
}
if (listeners.length === 0) { return; }
var beforeValues = [], listener, i, l;
try {
for (i=0, l=listeners.length; i<l; i++) {
listener = listeners[i];
beforeValues[i] = listener.before(name, new Date(), payload);
}
callback.call(binding);
} catch(e) {
payload = payload || {};
payload.exception = e;
} finally {
for (i=0, l=listeners.length; i<l; i++) {
listener = listeners[i];
listener.after(name, new Date(), payload, beforeValues[i]);
}
}
};
Ember.Instrumentation.subscribe = function(pattern, object) {
var paths = pattern.split("."), path, regex = "^";
for (var i=0, l=paths.length; i<l; i++) {
path = paths[i];
if (path === "*") {
regex = regex + "[^\\.]*";
} else {
regex = regex + path;
}
}
regex = regex + "(\\..*)?";
var subscriber = {
pattern: pattern,
regex: new RegExp(regex + "$"),
object: object
};
subscribers.push(subscriber);
cache = {};
return subscriber;
};
Ember.Instrumentation.unsubscribe = function(subscriber) {
var index;
for (var i=0, l=subscribers.length; i<l; i++) {
if (subscribers[i] === subscriber) {
index = i;
}
}
subscribers.splice(index, 1);
cache = {};
};
Ember.Instrumentation.reset = function() {
subscribers = [];
cache = {};
};
Ember.instrument = Ember.Instrumentation.instrument;
Ember.subscribe = Ember.Instrumentation.subscribe;
@@ -5,6 +5,7 @@ Ember Metal
@submodule ember-metal
*/
require('ember-metal/instrumentation');
require('ember-metal/core');
require('ember-metal/map');
require('ember-metal/platform');
@@ -0,0 +1,139 @@
var instrument = Ember.Instrumentation;
module("Ember Instrumentation", {
setup: function() {
},
teardown: function() {
instrument.reset();
}
});
test("subscribing to a simple path receives the listener", function() {
expect(12);
var sentPayload = {}, count = 0;
instrument.subscribe("render", {
before: function(name, timestamp, payload) {
if (count === 0) {
strictEqual(name, "render");
} else {
strictEqual(name, "render.handlebars");
}
ok(timestamp instanceof Date);
strictEqual(payload, sentPayload);
},
after: function(name, timestamp, payload) {
if (count === 0) {
strictEqual(name, "render");
} else {
strictEqual(name, "render.handlebars");
}
ok(timestamp instanceof Date);
strictEqual(payload, sentPayload);
count++;
}
});
instrument.instrument("render", sentPayload, function() {
});
instrument.instrument("render.handlebars", sentPayload, function() {
});
});
test("returning a value from the before callback passes it to the after callback", function() {
expect(2);
var passthru1 = {}, passthru2 = {};
instrument.subscribe("render", {
before: function(name, timestamp, payload) {
return passthru1;
},
after: function(name, timestamp, payload, beforeValue) {
strictEqual(beforeValue, passthru1);
}
});
instrument.subscribe("render", {
before: function(name, timestamp, payload) {
return passthru2;
},
after: function(name, timestamp, payload, beforeValue) {
strictEqual(beforeValue, passthru2);
}
});
instrument.instrument("render", null, function() {});
});
test("raising an exception in the instrumentation attaches it to the payload", function() {
expect(2);
var error = new Error("Instrumentation");
instrument.subscribe("render", {
before: function() {},
after: function(name, timestamp, payload) {
strictEqual(payload.exception, error);
}
});
instrument.subscribe("render", {
before: function() {},
after: function(name, timestamp, payload) {
strictEqual(payload.exception, error);
}
});
instrument.instrument("render.handlebars", null, function() {
throw error;
});
});
test("it is possible to add a new subscriber after the first instrument", function() {
instrument.instrument("render.handlebars", null, function() {});
instrument.subscribe("render", {
before: function() {
ok(true, "Before callback was called");
},
after: function() {
ok(true, "After callback was called");
}
});
instrument.instrument("render.handlebars", function() {});
});
test("it is possible to remove a subscriber", function() {
expect(4);
var count = 0;
var subscriber = instrument.subscribe("render", {
before: function() {
equal(count, 0);
ok(true, "Before callback was called");
},
after: function() {
equal(count, 0);
ok(true, "After callback was called");
count++;
}
});
instrument.instrument("render.handlebars", function() {});
instrument.unsubscribe(subscriber);
instrument.instrument("render.handlebars", function() {});
});

0 comments on commit 134fc61

Please sign in to comment.