From d34ac5844f35f109b98a95452dae70322fb549d1 Mon Sep 17 00:00:00 2001 From: Andrew Sutherland Date: Fri, 10 Feb 2012 18:32:42 -0800 Subject: [PATCH] initial extension attempt; this is all derived from about:gc Our upstream about:gc was originally https://bitbucket.org/burg/aboutgc --- Makefile | 7 ++ README.md | 21 +++++ about-jsprobes.html | 14 ++++ about-jsprobes.js | 189 ++++++++++++++++++++++++++++++++++++++++++++ bootstrap.js | 67 ++++++++++++++++ install.rdf | 35 ++++++++ stylesheet.css | 0 7 files changed, 333 insertions(+) create mode 100644 Makefile create mode 100644 README.md create mode 100644 about-jsprobes.html create mode 100644 about-jsprobes.js create mode 100644 bootstrap.js create mode 100644 install.rdf create mode 100644 stylesheet.css diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1b6224d --- /dev/null +++ b/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 *[~#] diff --git a/README.md b/README.md new file mode 100644 index 0000000..d1d8195 --- /dev/null +++ b/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/ diff --git a/about-jsprobes.html b/about-jsprobes.html new file mode 100644 index 0000000..9dc1a79 --- /dev/null +++ b/about-jsprobes.html @@ -0,0 +1,14 @@ + + + + +About JSProbes + + + + +

Probe Results:

+
+
+ + diff --git a/about-jsprobes.js b/about-jsprobes.js new file mode 100644 index 0000000..fc601b3 --- /dev/null +++ b/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(); diff --git a/bootstrap.js b/bootstrap.js new file mode 100644 index 0000000..c53a2a4 --- /dev/null +++ b/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) { } diff --git a/install.rdf b/install.rdf new file mode 100644 index 0000000..d2c8898 --- /dev/null +++ b/install.rdf @@ -0,0 +1,35 @@ + + + about-jsprobes@mozilla.org + 0.1 + 2 + true + + + + + {ec8030f7-c20a-464f-9b0e-13a3a9e97384} + 12.0 + 14.0 + + + + + + + {a23983c0-fd0e-11dc-95ff-0800200c9a66} + 12.0 + 14.0 + + + + + About JSProbes + JS Probes Experimentation Template + Team Mozilla + + + + + + diff --git a/stylesheet.css b/stylesheet.css new file mode 100644 index 0000000..e69de29