Skip to content

Commit

Permalink
initial extension attempt; this is all derived from about:gc
Browse files Browse the repository at this point in the history
Our upstream about:gc was originally https://bitbucket.org/burg/aboutgc
  • Loading branch information
asutherland committed Feb 11, 2012
0 parents commit d34ac58
Show file tree
Hide file tree
Showing 7 changed files with 333 additions and 0 deletions.
7 changes: 7 additions & 0 deletions Makefile
@@ -0,0 +1,7 @@
FILES := bootstrap.js install.rdf stylesheet.css about-jsprobes.html about-jsprobes.js lib/*.js

about-jsprobes.xpi: $(FILES)
zip -9 $@ $+

clean:
rm -f about-jsprobes.xpi *[~#]
21 changes: 21 additions & 0 deletions README.md
@@ -0,0 +1,21 @@
This is intended to be a super simple playground for JSProbes. It is a simple
restartless add-on that adds an "about:jsprobes" URL that displays/runs
about-jsprobes.html and about-jsprobes.js with chrome privileges. The probes
will not be active until you create the page. The probes will ideally stop
once you close the page.

Your stock Firefox does not include jsprobes support. To get said support, you
need to apply the patch queue from: https://bitbucket.org/asuth/jsprobes-patches
and modify your .mozconfig to include:

ac_add_options --enable-jsprobes

You can also try and use a build spun by someone else.

This add-on is derived from about:gc by Steve Fink (and later amended by Brian
Burg to support JSProbes.)

If you want to learn about JSProbes, you want to see:

- http://brrian.tumblr.com/post/10571624125/jsprobes
- http://blog.mozilla.com/sfink/2011/09/21/js-probes/
14 changes: 14 additions & 0 deletions about-jsprobes.html
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<base href="resource://aboutjsprobes/" />
<title>About JSProbes</title>
<script type="text/javascript" src="about-jsprobes.js"></script>
<link rel="stylesheet" href="stylesheet.css" />
</head>
<body>
<h2>Probe Results:</h2>
<pre id="oot">
</pre>
</body>
</html>
189 changes: 189 additions & 0 deletions about-jsprobes.js
@@ -0,0 +1,189 @@
const Ci = Components.interfaces;
const Cc = Components.classes;

const wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);

try {
const probes = Cc["@mozilla.org/base/probes;1"]
.getService(Ci.nsIProbeService);
} catch(e) {
console.error("Could not load nsIProbeService");
throw new Error("I can't go on without my probes.");
}

var mainWindow = wm.getMostRecentWindow("navigator:browser");
var gBrowser = mainWindow.gBrowser;

var urlListener = {
QueryInterface: function(aIID) {
if (aIID.equals(Ci.nsIWebProgressListener) ||
aIID.equals(Ci.nsISupportsWeakReference) ||
aIID.equals(Ci.nsISupports))
return this;
throw Components.results.NS_NOINTERFACE;
},

onLocationChange: function(aProgress, aRequest, aURI) {
window.console.log("new URI: " + aURI.spec);
}
};

window.console.log("added listener");

window.addEventListener('unload', function() {
gBrowser.removeProgressListener(urlListener);
stopProbes();
});

gBrowser.addProgressListener(urlListener);

// If scrolling through real-time data, advance this whenever dropping off
// data points.
var firstGCIndex = 0;
var timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
const TYPE_REPEATING_SLACK = Ci.nsITimer.TYPE_REPEATING_SLACK;
var activeHandlers = [];

const BYTES_PER_KB = Math.pow(2, 10);
const BYTES_PER_MB = Math.pow(2, 20);
const BYTES_PER_GB = Math.pow(2, 30);

const MS_PER_SECOND = 1000;
const MS_PER_MINUTE = MS_PER_SECOND * 1000;
const MS_PER_HOUR = MS_PER_MINUTE * 60;

results = [];

timerCb = {};

function stopProbes() {
if (!probes) return;

while (activeHandlers.length) {
probes.removeHandler(activeHandlers.pop());
}

timer.cancel();
timer = null;
probes = null;
}

function execOnProbeThread(func, callback) {
var execStr = func.toString();
execStr = execStr.substring(execStr.indexOf("{") + 1,
execStr.lastIndexOf("}"));
probes.asyncQuery(execStr, callback);
}

function registerProbe(probepoint, captureArgs, func) {
var usingStr = "using(" + captureArgs.join(");using(") + ");";
var execStr = func.toString();
execStr = execStr.substring(execStr.indexOf("{") + 1,
execStr.lastIndexOf("}"));
var cookie = probes.addHandler(probepoint, usingStr, execStr);
activeHandlers.push(cookie);
}

function gatherDataFromProbeThreadPeriodically(intervalMS,
probeThreadFunc,
thisThreadProcessFunc) {
timerCb = {
observe: function(subject, topic, data) {
execOnProbeThread(probeThreadFunc, thisThreadProcessFunc);
}
};

timer.init(timerCb, intervalMS, TYPE_REPEATING_SLACK);
}

var outputDomNode = document.getElementById("oot");
function prettyPrint(obj) {
var s = JSON.stringify(obj, 0, 2),
tn = document.createTextNode(s);
outputDomNode.appendChild(tn);
}

/**
* These are the GC probes from about:gc, re-written to use the registerProbe
* idiom above that scrapes source out of functions and tries to look pretty.
*
* Their general goal is to produce a list of GC info where the items look like:
* [GC start timestamp, GC end timestamp, before bytes, after bytes]. There
* are also heap resize events of the form [timestamp, old bytes, new bytes].
* These all end up living in tagged objects, and compartment and global GCs
* are distinguished from each other.
*/
function activateGCProbes() {
execOnProbeThread(function() {
var pendingData = [],
HEAP_RESIZE_INTERVAL = 500.0, // minimum MS between posted events
lastRecTime = 0,
current;
});

registerProbe(
probes.COMPARTMENT_GC_DID_START,
["env.currentTimeMS", "runtime.gcBytes"],
function() {
current = {
type: 'GC_COMPARTMENT',
data: [env.currentTimeMS, 0, runtime.gcBytes, 0],
sortValue: env.currentTimeMS };
});

registerProbe(
probes.GLOBAL_GC_DID_START,
["env.currentTimeMS", "runtime.gcBytes"],
function() {
current = {
type: 'GC_GLOBAL',
data: [env.currentTimeMS, 0, runtime.gcBytes, 0],
sortValue: env.currentTimeMS
};
});

registerProbe(
probes.JS_WILL_RESIZE_HEAP,
["env.currentTimeMS", "oldSize", "newSize"],
function() {
if ((env.currentTimeMS - lastRecTime) > HEAP_RESIZE_INTERVAL) {
lastRecTime = env.currentTimeMS;
pendingData.push({
type: 'HEAP_RESIZE',
sortValue: env.currentTimeMS,
data: [env.currentTimeMS, oldSize, newSize]
});
}
});

registerProbe(
probes.COMPARTMENT_GC_WILL_END,
["env.currentTimeMS", "runtime.gcBytes"],
function() {
current.data[1] = env.currentTimeMS;
current.data[3] = runtime.gcBytes;
pendingData.push(current);
});

registerProbe(
probes.GLOBAL_GC_WILL_END,
["env.currentTimeMS", "runtime.gcBytes"],
function() {
current.data[1] = env.currentTimeMS;
current.data[3] = runtime.gcBytes;
pendingData.push(current);
});

gatherDataFromProbeThreadPeriodically(
1000,
function onProbeThread() {
postMessage(pendingData);
pendingData = [];
},
function onOurThread(e) {
prettyPrint(e.value);
});
}

activateGCProbes();
67 changes: 67 additions & 0 deletions bootstrap.js
@@ -0,0 +1,67 @@
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cm = Components.manager;

var testing = false;

Cm.QueryInterface(Ci.nsIComponentRegistrar);

Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
Components.utils.import('resource://gre/modules/Services.jsm');

function AboutJSProbes() {}

AboutJSProbes.prototype = {
QueryInterface: XPCOMUtils.generateQI([Ci.nsIAboutModule]),
classDescription: 'about:jsprobes',
classID: Components.ID('{ced45ce8-a0ee-4dbd-8099-1a27f60e83c6}'),
contractID: '@mozilla.org/network/protocol/about;1?what=jsprobes',

newChannel: function(uri)
{
var channel = Services.io.newChannel(
'resource://aboutjsprobes/about-jsprobes.html', null, null);
var securityManager = Cc['@mozilla.org/scriptsecuritymanager;1']
.getService(Ci.nsIScriptSecurityManager);
var principal = securityManager.getSystemPrincipal(uri);
channel.originalURI = uri;
channel.owner = principal;

// var c= Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
// c.logStringMessage("uri = " + uri.toString());

return channel;
},

getURIFlags: function(uri)
{
return Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT |
Ci.nsIAboutModule.ALLOW_SCRIPT;
}
};

const AboutJSProbesFactory =
XPCOMUtils.generateNSGetFactory([AboutJSProbes])(
AboutJSProbes.prototype.classID);

const APP_STARTUP = 1;
const ADDON_ENABLE = 3;
const ADDON_UPGRADE = 7;

function startup(aData, aReason) {
Cm.registerFactory(AboutJSProbes.prototype.classID,
AboutJSProbes.prototype.classDescription,
AboutJSProbes.prototype.contractID,
AboutJSProbesFactory);
var fileuri = Services.io.newFileURI(aData.installPath);
if (!aData.installPath.isDirectory())
fileuri = Services.io.newURI('jar:' + fileuri.spec + '!/', null, null);
Services.io.getProtocolHandler('resource').QueryInterface(Ci.nsIResProtocolHandler).setSubstitution('aboutjsprobes', fileuri);
}

function shutdown(aData, aReason) {
Services.io.getProtocolHandler('resource').QueryInterface(Ci.nsIResProtocolHandler).setSubstitution('aboutjsprobes', null);
Cm.unregisterFactory(AboutJSProbes.prototype.classID, AboutJSProbesFactory);
}
function install(aData, aReason) { }
function uninstall(aData, aReason) { }
35 changes: 35 additions & 0 deletions install.rdf
@@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?><RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#">
<Description about="urn:mozilla:install-manifest">
<em:id>about-jsprobes@mozilla.org</em:id>
<em:version>0.1</em:version>
<em:type>2</em:type>
<em:bootstrap>true</em:bootstrap>

<!-- Firefox -->
<em:targetApplication>
<Description>
<em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
<em:minVersion>12.0</em:minVersion>
<em:maxVersion>14.0</em:maxVersion>
</Description>
</em:targetApplication>

<!-- Fennec -->
<em:targetApplication>
<Description>
<em:id>{a23983c0-fd0e-11dc-95ff-0800200c9a66}</em:id>
<em:minVersion>12.0</em:minVersion>
<em:maxVersion>14.0</em:maxVersion>
</Description>
</em:targetApplication>

<!-- Front End MetaData -->
<em:name>About JSProbes</em:name>
<em:description>JS Probes Experimentation Template</em:description>
<em:creator>Team Mozilla</em:creator>
<em:iconURL/>

<em:optionsURL/>

</Description>
</RDF>
Empty file added stylesheet.css
Empty file.

0 comments on commit d34ac58

Please sign in to comment.