autobench 0.0.1alpha1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +16 -0
- data/README.md +123 -0
- data/bin/autobench +180 -0
- data/bin/autobench-config +162 -0
- data/lib/autobench.rb +28 -0
- data/lib/autobench/client.rb +78 -0
- data/lib/autobench/common.rb +49 -0
- data/lib/autobench/config.rb +62 -0
- data/lib/autobench/render.rb +102 -0
- data/lib/autobench/version.rb +3 -0
- data/lib/autobench/yslow.rb +75 -0
- data/lib/phantomas/README.md +296 -0
- data/lib/phantomas/core/formatter.js +65 -0
- data/lib/phantomas/core/helper.js +64 -0
- data/lib/phantomas/core/modules/requestsMonitor/requestsMonitor.js +214 -0
- data/lib/phantomas/core/pads.js +16 -0
- data/lib/phantomas/core/phantomas.js +418 -0
- data/lib/phantomas/lib/args.js +27 -0
- data/lib/phantomas/lib/modules/_coffee-script.js +2 -0
- data/lib/phantomas/lib/modules/assert.js +326 -0
- data/lib/phantomas/lib/modules/events.js +216 -0
- data/lib/phantomas/lib/modules/http.js +55 -0
- data/lib/phantomas/lib/modules/path.js +441 -0
- data/lib/phantomas/lib/modules/punycode.js +510 -0
- data/lib/phantomas/lib/modules/querystring.js +214 -0
- data/lib/phantomas/lib/modules/tty.js +7 -0
- data/lib/phantomas/lib/modules/url.js +625 -0
- data/lib/phantomas/lib/modules/util.js +520 -0
- data/lib/phantomas/modules/ajaxRequests/ajaxRequests.js +15 -0
- data/lib/phantomas/modules/assetsTypes/assetsTypes.js +21 -0
- data/lib/phantomas/modules/cacheHits/cacheHits.js +28 -0
- data/lib/phantomas/modules/caching/caching.js +66 -0
- data/lib/phantomas/modules/cookies/cookies.js +54 -0
- data/lib/phantomas/modules/domComplexity/domComplexity.js +130 -0
- data/lib/phantomas/modules/domQueries/domQueries.js +148 -0
- data/lib/phantomas/modules/domains/domains.js +49 -0
- data/lib/phantomas/modules/globalVariables/globalVariables.js +44 -0
- data/lib/phantomas/modules/headers/headers.js +48 -0
- data/lib/phantomas/modules/localStorage/localStorage.js +14 -0
- data/lib/phantomas/modules/requestsStats/requestsStats.js +71 -0
- data/lib/phantomas/modules/staticAssets/staticAssets.js +40 -0
- data/lib/phantomas/modules/waterfall/waterfall.js +62 -0
- data/lib/phantomas/modules/windowPerformance/windowPerformance.js +36 -0
- data/lib/phantomas/package.json +27 -0
- data/lib/phantomas/phantomas.js +35 -0
- data/lib/phantomas/run-multiple.js +177 -0
- data/lib/yslow.js +5 -0
- metadata +135 -0
@@ -0,0 +1,15 @@
|
|
1
|
+
/**
|
2
|
+
* Analyzes AJAX requests
|
3
|
+
*/
|
4
|
+
exports.version = '0.1';
|
5
|
+
|
6
|
+
exports.module = function(phantomas) {
|
7
|
+
phantomas.setMetric('ajaxRequests');
|
8
|
+
|
9
|
+
phantomas.on('send', function(entry, res) {
|
10
|
+
if (entry.requestHeaders['X-Requested-With'] === 'XMLHttpRequest') {
|
11
|
+
phantomas.addNotice('AJAX request: ' + entry.url);
|
12
|
+
phantomas.incrMetric('ajaxRequests');
|
13
|
+
}
|
14
|
+
});
|
15
|
+
};
|
@@ -0,0 +1,21 @@
|
|
1
|
+
/**
|
2
|
+
* Analyze number of requests and sizes of differet assets types
|
3
|
+
*/
|
4
|
+
exports.version = '0.1';
|
5
|
+
|
6
|
+
exports.module = function(phantomas) {
|
7
|
+
['html', 'css', 'js', 'image', 'base64', 'other'].forEach(function(key) {
|
8
|
+
phantomas.setMetric(key + 'Count');
|
9
|
+
phantomas.setMetric(key + 'Size');
|
10
|
+
});
|
11
|
+
|
12
|
+
phantomas.on('recv', function(entry, res) {
|
13
|
+
phantomas.incrMetric(entry.type + 'Count');
|
14
|
+
phantomas.incrMetric(entry.type + 'Size', entry.bodySize);
|
15
|
+
});
|
16
|
+
|
17
|
+
phantomas.on('base64recv', function(entry, res) {
|
18
|
+
phantomas.incrMetric('base64Count');
|
19
|
+
phantomas.incrMetric('base64Size', entry.bodySize);
|
20
|
+
});
|
21
|
+
};
|
@@ -0,0 +1,28 @@
|
|
1
|
+
/**
|
2
|
+
* Analyzes X-Cache headers from caching servers like Squid or Varnish
|
3
|
+
*/
|
4
|
+
exports.version = '0.1';
|
5
|
+
|
6
|
+
exports.module = function(phantomas) {
|
7
|
+
phantomas.setMetric('cacheHits');
|
8
|
+
phantomas.setMetric('cacheMisses');
|
9
|
+
|
10
|
+
var re = /miss|hit/i;
|
11
|
+
|
12
|
+
// examples:
|
13
|
+
// X-Cache:HIT, HIT
|
14
|
+
// X-Cache:arsenic miss (0)
|
15
|
+
phantomas.on('recv', function(entry,res) {
|
16
|
+
var header = entry.headers['X-Cache'] || '',
|
17
|
+
isHit;
|
18
|
+
|
19
|
+
if (re.test(header)) {
|
20
|
+
isHit = header.toLowerCase().indexOf('hit') > -1;
|
21
|
+
phantomas.incrMetric(isHit ? 'cacheHits' : 'cacheMisses');
|
22
|
+
|
23
|
+
if (!isHit) {
|
24
|
+
phantomas.addNotice('Cache miss on ' + entry.url + ' (X-Cache: ' + header + ')');
|
25
|
+
}
|
26
|
+
}
|
27
|
+
});
|
28
|
+
};
|
@@ -0,0 +1,66 @@
|
|
1
|
+
/**
|
2
|
+
* Analyzes HTTP caching headers
|
3
|
+
*
|
4
|
+
* @see https://developers.google.com/speed/docs/best-practices/caching
|
5
|
+
*/
|
6
|
+
exports.version = '0.1';
|
7
|
+
|
8
|
+
exports.module = function(phantomas) {
|
9
|
+
var cacheControlRegExp = /max-age=(\d+)/;
|
10
|
+
|
11
|
+
function getCachingTime(headers) {
|
12
|
+
// false means "no caching"
|
13
|
+
var ttl = false,
|
14
|
+
headerName;
|
15
|
+
|
16
|
+
for (headerName in headers) {
|
17
|
+
var value = headers[headerName];
|
18
|
+
|
19
|
+
switch(headerName.toLowerCase()) {
|
20
|
+
// parse max-age=...
|
21
|
+
//
|
22
|
+
// max-age=2592000
|
23
|
+
// public, max-age=300, must-revalidate
|
24
|
+
case 'cache-control':
|
25
|
+
var matches = value.match(cacheControlRegExp);
|
26
|
+
|
27
|
+
if (matches) {
|
28
|
+
ttl = parseInt(matches[1], 10);
|
29
|
+
}
|
30
|
+
break;
|
31
|
+
|
32
|
+
// TODO: parse date
|
33
|
+
case 'expires':
|
34
|
+
break;
|
35
|
+
}
|
36
|
+
}
|
37
|
+
|
38
|
+
//console.log(JSON.stringify(headers)); console.log("TTL: " + ttl + ' s');
|
39
|
+
|
40
|
+
return ttl;
|
41
|
+
}
|
42
|
+
|
43
|
+
phantomas.setMetric('cachingNotSpecified');
|
44
|
+
phantomas.setMetric('cachingTooShort');
|
45
|
+
phantomas.setMetric('cachingDisabled');
|
46
|
+
|
47
|
+
phantomas.on('recv', function(entry, res) {
|
48
|
+
var ttl = getCachingTime(entry.headers);
|
49
|
+
|
50
|
+
// static assets
|
51
|
+
if (entry.isImage || entry.isJS || entry.isCSS) {
|
52
|
+
if (ttl === false) {
|
53
|
+
phantomas.addNotice("No caching specified for <" + entry.url + ">");
|
54
|
+
phantomas.incrMetric('cachingNotSpecified');
|
55
|
+
}
|
56
|
+
else if (ttl === 0) {
|
57
|
+
phantomas.addNotice("Caching disabled for <" + entry.url + ">");
|
58
|
+
phantomas.incrMetric('cachingDisabled');
|
59
|
+
}
|
60
|
+
else if (ttl < 7 * 86400) {
|
61
|
+
phantomas.addNotice("Caching period is less than a week for <" + entry.url + "> (set to " + ttl + " s)");
|
62
|
+
phantomas.incrMetric('cachingTooShort');
|
63
|
+
}
|
64
|
+
}
|
65
|
+
});
|
66
|
+
};
|
@@ -0,0 +1,54 @@
|
|
1
|
+
/**
|
2
|
+
* cookies metrics
|
3
|
+
*/
|
4
|
+
exports.version = '0.1';
|
5
|
+
|
6
|
+
exports.module = function(phantomas) {
|
7
|
+
// monitor cookies in HTTP headers
|
8
|
+
var cookiesSent = 0,
|
9
|
+
cookiesRecv = 0;
|
10
|
+
cookiesDomains = {};
|
11
|
+
|
12
|
+
phantomas.on('send', function(entry, res) {
|
13
|
+
res.headers.forEach(function(header) {
|
14
|
+
switch (header.name) {
|
15
|
+
case 'Cookie':
|
16
|
+
cookiesSent += header.value.length;
|
17
|
+
cookiesDomains[entry.domain] = true;
|
18
|
+
break;
|
19
|
+
}
|
20
|
+
});
|
21
|
+
});
|
22
|
+
|
23
|
+
phantomas.on('recv', function(entry, res) {
|
24
|
+
res.headers.forEach(function(header) {
|
25
|
+
switch (header.name) {
|
26
|
+
case 'Set-Cookie':
|
27
|
+
cookiesRecv += header.value.length;
|
28
|
+
cookiesDomains[entry.domain] = true;
|
29
|
+
break;
|
30
|
+
}
|
31
|
+
});
|
32
|
+
});
|
33
|
+
|
34
|
+
// domain cookies (accessible by the browser)
|
35
|
+
phantomas.on('report', function() {
|
36
|
+
phantomas.setMetric('cookiesSent', cookiesSent);
|
37
|
+
phantomas.setMetric('cookiesRecv', cookiesRecv);
|
38
|
+
|
39
|
+
// domains with cookies
|
40
|
+
var domainsWithCookies = 0;
|
41
|
+
for (var domain in cookiesDomains) {
|
42
|
+
domainsWithCookies++;
|
43
|
+
}
|
44
|
+
phantomas.setMetric('domainsWithCookies', domainsWithCookies);
|
45
|
+
|
46
|
+
phantomas.setMetricEvaluate('documentCookiesLength', function() {
|
47
|
+
return document.cookie.length;
|
48
|
+
});
|
49
|
+
|
50
|
+
phantomas.setMetricEvaluate('documentCookiesCount', function() {
|
51
|
+
return document.cookie.split(';').length;
|
52
|
+
});
|
53
|
+
});
|
54
|
+
};
|
@@ -0,0 +1,130 @@
|
|
1
|
+
/**
|
2
|
+
* Analyzes DOM complexity
|
3
|
+
*/
|
4
|
+
exports.version = '0.2';
|
5
|
+
|
6
|
+
exports.module = function(phantomas) {
|
7
|
+
|
8
|
+
// HTML size
|
9
|
+
phantomas.on('report', function() {
|
10
|
+
phantomas.setMetricEvaluate('bodyHTMLSize', function() {
|
11
|
+
return document.body.innerHTML.length;
|
12
|
+
});
|
13
|
+
|
14
|
+
phantomas.evaluate(function() {
|
15
|
+
(function(phantomas) {
|
16
|
+
var runner = new phantomas.nodeRunner(),
|
17
|
+
whitespacesRegExp = /^\s+$/;
|
18
|
+
|
19
|
+
var metrics = {
|
20
|
+
nodes: 0,
|
21
|
+
comments: 0,
|
22
|
+
hiddenContent: 0,
|
23
|
+
whitespaces: 0,
|
24
|
+
maxDepth: 0,
|
25
|
+
nodesWithCSS: 0
|
26
|
+
};
|
27
|
+
|
28
|
+
// include all nodes
|
29
|
+
runner.isSkipped = function(node) {
|
30
|
+
return false;
|
31
|
+
};
|
32
|
+
|
33
|
+
runner.walk(document.body, function(node, depth) {
|
34
|
+
switch (node.nodeType) {
|
35
|
+
case Node.COMMENT_NODE:
|
36
|
+
metrics.comments += node.textContent.length + 7; // '<!--' + '-->'.length
|
37
|
+
break;
|
38
|
+
|
39
|
+
case Node.ELEMENT_NODE:
|
40
|
+
metrics.nodes++;
|
41
|
+
metrics.maxDepth = Math.max(metrics.maxDepth, depth);
|
42
|
+
|
43
|
+
// ignore inline <script> tags
|
44
|
+
if (node.nodeName === 'SCRIPT') {
|
45
|
+
return false;
|
46
|
+
}
|
47
|
+
|
48
|
+
// @see https://developer.mozilla.org/en/DOM%3awindow.getComputedStyle
|
49
|
+
var styles = window.getComputedStyle(node);
|
50
|
+
|
51
|
+
if (styles && styles.getPropertyValue('display') === 'none') {
|
52
|
+
//console.log(node.innerHTML);
|
53
|
+
metrics.hiddenContent += node.innerHTML.length;
|
54
|
+
|
55
|
+
// don't run for child nodes as they're hidden as well
|
56
|
+
return false;
|
57
|
+
}
|
58
|
+
|
59
|
+
// count nodes with inline CSS
|
60
|
+
if (node.hasAttribute('style')) {
|
61
|
+
metrics.nodesWithCSS++;
|
62
|
+
}
|
63
|
+
|
64
|
+
break;
|
65
|
+
|
66
|
+
case Node.TEXT_NODE:
|
67
|
+
if (whitespacesRegExp.test(node.textContent)) {
|
68
|
+
metrics.whitespaces += node.textContent.length;
|
69
|
+
}
|
70
|
+
break;
|
71
|
+
}
|
72
|
+
});
|
73
|
+
|
74
|
+
// store metrics
|
75
|
+
phantomas.DOMmetrics = metrics;
|
76
|
+
|
77
|
+
}(window.phantomas));
|
78
|
+
});
|
79
|
+
|
80
|
+
// total length of HTML comments (including <!-- --> brackets)
|
81
|
+
phantomas.setMetricEvaluate('commentsSize', function() {
|
82
|
+
return window.phantomas.DOMmetrics.comments;
|
83
|
+
});
|
84
|
+
|
85
|
+
// total length of HTML of hidden elements (i.e. display: none)
|
86
|
+
phantomas.setMetricEvaluate('hiddenContentSize', function() {
|
87
|
+
return window.phantomas.DOMmetrics.hiddenContent;
|
88
|
+
});
|
89
|
+
|
90
|
+
// total length of text nodes with whitespaces only (i.e. pretty formatting of HTML)
|
91
|
+
phantomas.setMetricEvaluate('whiteSpacesSize', function() {
|
92
|
+
return window.phantomas.DOMmetrics.whitespaces;
|
93
|
+
});
|
94
|
+
|
95
|
+
// count all tags
|
96
|
+
phantomas.setMetricEvaluate('DOMelementsCount', function() {
|
97
|
+
return window.phantomas.DOMmetrics.nodes;
|
98
|
+
});
|
99
|
+
|
100
|
+
phantomas.setMetricEvaluate('DOMelementMaxDepth', function() {
|
101
|
+
return window.phantomas.DOMmetrics.maxDepth;
|
102
|
+
});
|
103
|
+
|
104
|
+
// count <iframe> tags
|
105
|
+
phantomas.setMetricEvaluate('iframesCount', function() {
|
106
|
+
return document.querySelectorAll('iframe').length;
|
107
|
+
});
|
108
|
+
|
109
|
+
// nodes with inlines CSS (style attribute)
|
110
|
+
phantomas.setMetricEvaluate('nodesWithInlineCSS', function() {
|
111
|
+
return window.phantomas.DOMmetrics.nodesWithCSS;
|
112
|
+
});
|
113
|
+
|
114
|
+
// <img> nodes without dimensions (one of width / height missing)
|
115
|
+
phantomas.setMetricEvaluate('imagesWithoutDimensions', function() {
|
116
|
+
var imgNodes = document.body.querySelectorAll('img'),
|
117
|
+
node,
|
118
|
+
imagesWithoutDimensions = 0;
|
119
|
+
|
120
|
+
for (i=0, len=imgNodes.length; i<len; i++) {
|
121
|
+
node = imgNodes[i];
|
122
|
+
if (!node.hasAttribute('width') || !node.hasAttribute('height')) {
|
123
|
+
imagesWithoutDimensions++;
|
124
|
+
}
|
125
|
+
};
|
126
|
+
|
127
|
+
return imagesWithoutDimensions;
|
128
|
+
});
|
129
|
+
});
|
130
|
+
};
|
@@ -0,0 +1,148 @@
|
|
1
|
+
/**
|
2
|
+
* Analyzes DOM queries done via native DOM methods & jQuery
|
3
|
+
*
|
4
|
+
*/
|
5
|
+
exports.version = '0.2';
|
6
|
+
|
7
|
+
exports.module = function(phantomas) {
|
8
|
+
|
9
|
+
// fake native DOM functions
|
10
|
+
phantomas.once('init', function() {
|
11
|
+
phantomas.evaluate(function() {
|
12
|
+
(function() {
|
13
|
+
var originalGetElementById = window.document.getElementById,
|
14
|
+
originalGetElementsByClassName = window.document.getElementsByClassName,
|
15
|
+
originalAppendChild = Node.prototype.appendChild,
|
16
|
+
originalInsertBefore = Node.prototype.insertBefore;
|
17
|
+
|
18
|
+
// metrics storage
|
19
|
+
window.phantomas.domQueries = 0;
|
20
|
+
window.phantomas.domInserts = 0;
|
21
|
+
window.phantomas.domInsertsBacktrace = [];
|
22
|
+
|
23
|
+
window.phantomas.jQueryOnDOMReadyFunctions = 0;
|
24
|
+
window.phantomas.jQueryOnDOMReadyFunctionsBacktrace = [];
|
25
|
+
|
26
|
+
window.phantomas.jQuerySelectors = 0;
|
27
|
+
window.phantomas.jQuerySelectorsBacktrace = [];
|
28
|
+
|
29
|
+
// hook into DOM methods
|
30
|
+
document.getElementById = function(id) {
|
31
|
+
// log calls
|
32
|
+
console.log('document.getElementById("' + id + '")');
|
33
|
+
window.phantomas.domQueries++;
|
34
|
+
|
35
|
+
return originalGetElementById.call(document, id);
|
36
|
+
};
|
37
|
+
|
38
|
+
// count DOM inserts
|
39
|
+
Node.prototype.appendChild = function(child) {
|
40
|
+
var hasParent = typeof this.parentNode !== 'undefined';
|
41
|
+
|
42
|
+
// ignore appending to the node that's not yet added to DOM tree
|
43
|
+
if (!hasParent) {
|
44
|
+
return;
|
45
|
+
}
|
46
|
+
|
47
|
+
var caller = window.phantomas.getCaller();
|
48
|
+
|
49
|
+
window.phantomas.domInserts++;
|
50
|
+
window.phantomas.domInsertsBacktrace.push({
|
51
|
+
url: caller.sourceURL,
|
52
|
+
line: caller.line
|
53
|
+
});
|
54
|
+
|
55
|
+
return originalAppendChild.call(this, child);
|
56
|
+
};
|
57
|
+
|
58
|
+
Node.prototype.insertBefore = function(child) {
|
59
|
+
var hasParent = typeof this.parentNode !== 'undefined';
|
60
|
+
|
61
|
+
// ignore appending to the node that's not yet added to DOM tree
|
62
|
+
if (!hasParent) {
|
63
|
+
return;
|
64
|
+
}
|
65
|
+
|
66
|
+
var caller = window.phantomas.getCaller();
|
67
|
+
|
68
|
+
window.phantomas.domInserts++;
|
69
|
+
window.phantomas.domInsertsBacktrace.push({
|
70
|
+
url: caller.sourceURL,
|
71
|
+
line: caller.line
|
72
|
+
});
|
73
|
+
|
74
|
+
return originalInsertBefore.call(this, child);
|
75
|
+
};
|
76
|
+
|
77
|
+
return;
|
78
|
+
|
79
|
+
// hook into $.fn.init to catch DOM queries
|
80
|
+
//
|
81
|
+
// TODO: use a better approach here:
|
82
|
+
// @see https://github.com/osteele/jquery-profile
|
83
|
+
var jQuery;
|
84
|
+
|
85
|
+
window.__defineSetter__('jQuery', function(val) {
|
86
|
+
jQuery = val;
|
87
|
+
console.log('Mocked jQuery v' + val.fn.jquery + ' object');
|
88
|
+
});
|
89
|
+
|
90
|
+
window.__defineGetter__('jQuery', function() {
|
91
|
+
return jQuery;
|
92
|
+
});
|
93
|
+
})();
|
94
|
+
});
|
95
|
+
});
|
96
|
+
|
97
|
+
phantomas.on('loadFinished', function() {
|
98
|
+
phantomas.setMetricEvaluate('DOMqueries', function() {
|
99
|
+
return window.phantomas.domQueries;
|
100
|
+
});
|
101
|
+
|
102
|
+
phantomas.setMetricEvaluate('DOMinserts', function() {
|
103
|
+
return window.phantomas.domInserts;
|
104
|
+
});
|
105
|
+
|
106
|
+
phantomas.setMetricEvaluate('jQuerySelectors', function() {
|
107
|
+
return window.phantomas.jQuerySelectors;
|
108
|
+
});
|
109
|
+
|
110
|
+
phantomas.setMetricEvaluate('jQueryOnDOMReadyFunctions', function() {
|
111
|
+
return window.phantomas.jQueryOnDOMReadyFunctions;
|
112
|
+
});
|
113
|
+
|
114
|
+
// list all selectors
|
115
|
+
var selectorsBacktrace = phantomas.evaluate(function() {
|
116
|
+
return window.phantomas.jQuerySelectorsBacktrace;
|
117
|
+
});
|
118
|
+
/**
|
119
|
+
phantomas.addNotice('jQuery selectors:');
|
120
|
+
selectorsBacktrace.forEach(function(item) {
|
121
|
+
phantomas.addNotice('* $("' + item.selector + '") called from ' + item.url + ' @ ' + item.line);
|
122
|
+
});
|
123
|
+
phantomas.addNotice();
|
124
|
+
|
125
|
+
// list all onDOMReady functions
|
126
|
+
var onDOMReadyBacktrace = phantomas.evaluate(function() {
|
127
|
+
return window.phantomas.jQueryOnDOMReadyFunctionsBacktrace;
|
128
|
+
});
|
129
|
+
|
130
|
+
phantomas.addNotice('jQuery onDOMReady functions (' + onDOMReadyBacktrace.length + '):');
|
131
|
+
onDOMReadyBacktrace.forEach(function(item) {
|
132
|
+
phantomas.addNotice('* bound from ' + item.url + ' @ ' + item.line);
|
133
|
+
});
|
134
|
+
phantomas.addNotice();
|
135
|
+
|
136
|
+
// list all DOM inserts
|
137
|
+
var domInsertsBacktrace = phantomas.evaluate(function() {
|
138
|
+
return window.phantomas.domInsertsBacktrace;
|
139
|
+
});
|
140
|
+
|
141
|
+
phantomas.addNotice('DOM inserts (' + domInsertsBacktrace.length + '):');
|
142
|
+
domInsertsBacktrace.forEach(function(item) {
|
143
|
+
phantomas.addNotice('* from ' + item.url + ' @ ' + item.line);
|
144
|
+
});
|
145
|
+
phantomas.addNotice();
|
146
|
+
**/
|
147
|
+
});
|
148
|
+
};
|