├── icon16.png ├── icon48.png ├── icon128.png ├── .gitignore ├── main.html ├── background └── background.js ├── ddp-analyzer.html ├── manifest.json ├── ddp-analyzer.js ├── main.js ├── tracker-profiler.html ├── inject.js ├── tracker-profiler.js └── _locales └── en └── messages.json /icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slava/blaze-tools/HEAD/icon16.png -------------------------------------------------------------------------------- /icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slava/blaze-tools/HEAD/icon48.png -------------------------------------------------------------------------------- /icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Slava/blaze-tools/HEAD/icon128.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .AppleDouble 3 | .LSOverride 4 | ._* 5 | .Spotlight-V100 6 | .Trashes 7 | [._]*.s[a-w][a-z] 8 | [._]s[a-w][a-z] 9 | *.un~ 10 | Session.vim 11 | .netrwhist 12 | *~ 13 | 14 | -------------------------------------------------------------------------------- /main.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | Main Page. Doesn't really contain anything. 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /background/background.js: -------------------------------------------------------------------------------- 1 | // var settings = new Store("settings", { 2 | // "sample_setting": "This is how you use Store.js to remember values" 3 | // }); 4 | 5 | 6 | //example of using a message handler from the inject scripts 7 | chrome.extension.onMessage.addListener( 8 | function(request, sender, sendResponse) { 9 | chrome.pageAction.show(sender.tab.id); 10 | sendResponse(); 11 | }); 12 | 13 | -------------------------------------------------------------------------------- /ddp-analyzer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Blaze tools", 3 | "version": "0.0.1", 4 | "manifest_version": 2, 5 | "description": "An experimental Chrome extension", 6 | "homepage_url": "https://meteor.com", 7 | "icons": { 8 | "16": "icon16.png", 9 | "48": "icon48.png", 10 | "128": "icon128.png" 11 | }, 12 | "default_locale": "en", 13 | "content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'", 14 | "background": { 15 | "scripts": [ 16 | "background/background.js" 17 | ], 18 | "persistent": true 19 | }, 20 | "devtools_page": "main.html", 21 | "permissions": [ 22 | "tabs", 23 | "https://*/*", 24 | "http://*/*" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /ddp-analyzer.js: -------------------------------------------------------------------------------- 1 | ReactiveVar = Package['reactive-var'].ReactiveVar; 2 | LocalCollection = Package['minimongo'].LocalCollection; 3 | _ = Package['underscore']._; 4 | 5 | Connections = new LocalCollection(); 6 | Messages = new LocalCollection(); 7 | 8 | var handleMessage = function (o) { 9 | if (! Connections.findOne({ name: o.name })) { 10 | Connections.insert({ name: o.name }); 11 | } 12 | 13 | Messages.insert(_.extend(o, { 14 | ts: new Date 15 | })); 16 | }; 17 | 18 | setInterval(function () { 19 | inject('DDPAnalyzer.DevTools.getBuffered()', function (err, buffer) { 20 | if (err) { 21 | console.error(err); 22 | return; 23 | } 24 | 25 | for (var i = 0; i < buffer.length; i++) { 26 | handleMessage(buffer[i]); 27 | } 28 | }); 29 | }, 500); 30 | 31 | Template.main.helpers({ 32 | connections: function () { 33 | return Connections.find(); 34 | } 35 | }); 36 | 37 | Template.ddpMessages.helpers({ 38 | messages: function () { 39 | var connection = this.connection.name; 40 | return Messages.find({ name: connection }, { sort: { ts: -1 } }); 41 | } 42 | }); 43 | 44 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | chrome.devtools.panels.create( 2 | "Tracker Profiler", 3 | "icon128.png", 4 | "tracker-profiler.html", 5 | function (panel) { 6 | } 7 | ); 8 | chrome.devtools.panels.create( 9 | "DDP Analyzer", 10 | "icon128.png", 11 | "ddp-analyzer.html", 12 | function (panel) { 13 | } 14 | ); 15 | 16 | function getBlazeTmplInstForSelection () { 17 | if (! window.Blaze) 18 | return {}; 19 | 20 | try { Blaze.getView($0) } catch (err) { 21 | return {}; 22 | } 23 | 24 | var info = { 25 | viewInst: Blaze.getView($0), 26 | data: Blaze.getData($0), 27 | template: null, 28 | view: null 29 | }; 30 | 31 | for (var v = info.viewInst; !!v; v = v.parentView) { 32 | var m = v.name.match(/^Template\.(.*)/); 33 | if (!info.template && m && m[1]) { 34 | info.template = m[1]; 35 | } 36 | if (!info.view && !m) { 37 | info.view = v.name; 38 | } 39 | 40 | if (info.view && info.template) 41 | break; 42 | } 43 | 44 | return info; 45 | } 46 | 47 | chrome.devtools.panels.elements.createSidebarPane("Blaze", function (sidebar) { 48 | function updateSidebar () { 49 | sidebar.setExpression("(" + getBlazeTmplInstForSelection.toString() + ")()"); 50 | } 51 | // TODO: make this reactive 52 | chrome.devtools.panels.elements.onSelectionChanged.addListener(updateSidebar); 53 | 54 | updateSidebar(); 55 | }); 56 | 57 | -------------------------------------------------------------------------------- /tracker-profiler.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 32 | 33 | 34 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /inject.js: -------------------------------------------------------------------------------- 1 | inject = function (script, cb) { 2 | if (! window.chrome.devtools) { 3 | if (script.match(/start/)) 4 | cb(); 5 | else if (script.match(/stop/)) 6 | cb(null, stubData); 7 | return; 8 | } 9 | chrome.devtools.inspectedWindow.eval(script, function (res, exc) { 10 | if (exc && exc.isError) { 11 | cb(exc.description, null); 12 | } else if (exc && exc.isException) { 13 | cb(exc.value, null); 14 | } else 15 | cb(null, res); 16 | }); 17 | } 18 | 19 | var stubData = [{"aveTime":158,"totalTime":158,"recomputations":1,"funcName":"DynamicTemplate:materialize","_id":"937"},{"aveTime":22,"totalTime":22,"recomputations":1,"funcName":"DynamicTemplateanonymous","_id":"936"},{"aveTime":8,"totalTime":16,"recomputations":2,"funcName":"Template.menuItem:updater","_id":"607"},{"aveTime":9,"totalTime":9,"recomputations":1,"funcName":"onLocationChange","_id":"27"},{"aveTime":3.5,"totalTime":7,"recomputations":2,"funcName":"with:setData","_id":"31"},{"aveTime":1,"totalTime":2,"recomputations":2,"funcName":"Spacebars_withanonymous","_id":"82"},{"aveTime":1,"totalTime":2,"recomputations":2,"funcName":"lookup:getSetting:materialize","_id":"68"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"if:condition","_id":"4698"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"if:updater","_id":"4687"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:name:materialize","_id":"4684"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"Spacebars_withanonymous","_id":"4664"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4653"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4649"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"Template.menuItem:updater","_id":"4591"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"with:setData","_id":"4571"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:checkContext:materialize","_id":"4548"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4537"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"with:setData","_id":"4515"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"lookup:_:materialize","_id":"4509"},{"aveTime":0.5,"totalTime":1,"recomputations":2,"funcName":"if:condition","_id":"4473"}]; 20 | 21 | 22 | -------------------------------------------------------------------------------- /tracker-profiler.js: -------------------------------------------------------------------------------- 1 | ReactiveVar = Package['reactive-var'].ReactiveVar; 2 | _ = Package['underscore']._; 3 | debug = new ReactiveVar(); 4 | started = new ReactiveVar(false); 5 | data = new ReactiveVar([]); 6 | sortProp = new ReactiveVar('totalTime'); 7 | 8 | Template.main.helpers({ 9 | debug: function () { 10 | return JSON.stringify(debug.get(), null, 2); 11 | }, 12 | started: function () { 13 | return started.get(); 14 | } 15 | }); 16 | 17 | var tp = 'Package["slava:tracker-profiler"].'; 18 | Template.main.events({ 19 | 'click #start': function () { 20 | inject(tp + 'TrackerProfiler.start()', function (err, res) { 21 | if (err) 22 | debug.set(err); 23 | else 24 | started.set(true); 25 | }); 26 | return false; 27 | }, 28 | 'click #stop': function () { 29 | inject(tp + 'TrackerProfiler.stop()', function (err, res) { 30 | if (err) { 31 | debug.set(err) 32 | } else { 33 | data.set(res); 34 | } 35 | }); 36 | 37 | started.set(false); 38 | return false; 39 | }, 40 | 'change input': function (e) { 41 | sortProp.set(e.target.id); 42 | return false; 43 | } 44 | }); 45 | 46 | var colors = _.shuffle(randomColors(1000)); 47 | var sum = function (a) { 48 | return _.reduce(a, function(memo, num){ return memo + num; }, 0); 49 | } 50 | 51 | var getData = function () { 52 | var res = data.get(); 53 | res = _.groupBy(res, 'funcName'); 54 | var i = 0; 55 | res = _.map(res, function (group, name) { 56 | return { 57 | name: name, 58 | values: _.pluck(group, sortProp.get()) 59 | }; 60 | }); 61 | res = _.sortBy(res, function (group) { 62 | return -sum(group.values); 63 | }); 64 | // XXX this hackery should be removed once we have @index 65 | _.each(res, function (x, i) { x.i = i; }); 66 | return res; 67 | }; 68 | Template.chart.helpers({ 69 | rows: function () { 70 | return getData(); 71 | }, 72 | offset: function () { 73 | return this.i * (60 + 5); 74 | }, 75 | maxSum: function () { 76 | return _.max(_.map(getData(), function (row) { 77 | return sum(row.values); 78 | })); 79 | } 80 | }); 81 | 82 | Template.row.helpers({ 83 | orderedValues: function () { 84 | return _.map(this.data.values, function (v, i) { 85 | return { 86 | value: v, 87 | i: i 88 | }; 89 | }); 90 | }, 91 | offset: function () { 92 | var p = Template.instance().data; 93 | return sum(p.data.values.slice(0, this.i)) / p.maxUnit * p.maxWidth; 94 | }, 95 | width: function () { 96 | var p = Template.instance().data; 97 | return this.value / p.maxUnit * p.maxWidth; 98 | }, 99 | color: function () { 100 | var p = Template.instance().data; 101 | return colors[this.i % colors.length]; 102 | } 103 | }); 104 | 105 | function randomColors(total) { 106 | var r = []; 107 | for (var x=0; x