autobench 0.0.1alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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,65 @@
|
|
1
|
+
/**
|
2
|
+
* Results formatter
|
3
|
+
*/
|
4
|
+
var formatter = function(results, format) {
|
5
|
+
function render() {
|
6
|
+
switch(format) {
|
7
|
+
case 'json':
|
8
|
+
return formatJson();
|
9
|
+
|
10
|
+
case 'csv':
|
11
|
+
return formatCsv();
|
12
|
+
|
13
|
+
case 'plain':
|
14
|
+
default:
|
15
|
+
return formatPlain();
|
16
|
+
}
|
17
|
+
}
|
18
|
+
|
19
|
+
function formatJson() {
|
20
|
+
return JSON.stringify(results);
|
21
|
+
}
|
22
|
+
|
23
|
+
function formatCsv() {
|
24
|
+
var obj = results.metrics,
|
25
|
+
key,
|
26
|
+
keys = [],
|
27
|
+
values = [];
|
28
|
+
|
29
|
+
for (key in obj) {
|
30
|
+
keys.push(key);
|
31
|
+
values.push(obj[key]);
|
32
|
+
}
|
33
|
+
|
34
|
+
return keys.join(',') + "\n" + values.join(',');
|
35
|
+
}
|
36
|
+
|
37
|
+
function formatPlain() {
|
38
|
+
var res = '',
|
39
|
+
obj = results.metrics,
|
40
|
+
key;
|
41
|
+
|
42
|
+
// header
|
43
|
+
res += 'phantomas metrics for <' + results.url + '>:\n\n';
|
44
|
+
|
45
|
+
// metrics
|
46
|
+
for (key in obj) {
|
47
|
+
res += '* ' + key + ': ' + obj[key]+ '\n';
|
48
|
+
}
|
49
|
+
|
50
|
+
res += '\n';
|
51
|
+
|
52
|
+
// notices
|
53
|
+
results.notices.forEach(function(msg) {
|
54
|
+
res += '> ' + msg + "\n";
|
55
|
+
});
|
56
|
+
|
57
|
+
return res.trim();
|
58
|
+
}
|
59
|
+
|
60
|
+
// public interface
|
61
|
+
this.render = render;
|
62
|
+
};
|
63
|
+
|
64
|
+
exports.formatter = formatter;
|
65
|
+
|
@@ -0,0 +1,64 @@
|
|
1
|
+
/**
|
2
|
+
* phantomas helper code
|
3
|
+
*
|
4
|
+
* Executed in page window
|
5
|
+
*/
|
6
|
+
|
7
|
+
(function(window) {
|
8
|
+
|
9
|
+
// NodeRunner
|
10
|
+
var nodeRunner = function() {
|
11
|
+
// "Beep, Beep"
|
12
|
+
};
|
13
|
+
|
14
|
+
nodeRunner.prototype = {
|
15
|
+
// call callback for each child of node
|
16
|
+
walk: function(node, callback, depth) {
|
17
|
+
if (this.isSkipped(node)) {
|
18
|
+
return;
|
19
|
+
}
|
20
|
+
|
21
|
+
var childNode,
|
22
|
+
childNodes = node.childNodes || [];
|
23
|
+
|
24
|
+
depth = (depth || 1);
|
25
|
+
|
26
|
+
for (var n=0, len = childNodes.length; n < len; n++) {
|
27
|
+
childNode = childNodes[n];
|
28
|
+
|
29
|
+
// callback can return false to stop recursive
|
30
|
+
if (callback(childNode, depth) !== false) {
|
31
|
+
this.walk(childNode, callback, depth + 1);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
},
|
35
|
+
|
36
|
+
// override this function when you create an object of class phantomas.nodeRunner
|
37
|
+
// by default only iterate over HTML elements
|
38
|
+
isSkipped: function(node) {
|
39
|
+
return !node || (node.nodeType !== Node.ELEMENT_NODE);
|
40
|
+
}
|
41
|
+
};
|
42
|
+
|
43
|
+
function getCaller() {
|
44
|
+
var caller = {};
|
45
|
+
|
46
|
+
try {
|
47
|
+
throw new Error('backtrace');
|
48
|
+
} catch(e) {
|
49
|
+
caller = (e.stackArray && e.stackArray[3]) || {};
|
50
|
+
}
|
51
|
+
|
52
|
+
return caller;
|
53
|
+
}
|
54
|
+
|
55
|
+
// create a scope
|
56
|
+
var phantomas = (window.phantomas = window.phantomas || {});
|
57
|
+
|
58
|
+
// exports
|
59
|
+
phantomas.nodeRunner = nodeRunner;
|
60
|
+
phantomas.getCaller = getCaller;
|
61
|
+
|
62
|
+
console.log('phantomas scope injected');
|
63
|
+
|
64
|
+
})(window);
|
@@ -0,0 +1,214 @@
|
|
1
|
+
/**
|
2
|
+
* Simple HTTP requests monitor and analyzer
|
3
|
+
*/
|
4
|
+
exports.version = '1.1';
|
5
|
+
|
6
|
+
exports.module = function(phantomas) {
|
7
|
+
// imports
|
8
|
+
var HTTP_STATUS_CODES = phantomas.require('http').STATUS_CODES,
|
9
|
+
parseUrl = phantomas.require('url').parse;
|
10
|
+
|
11
|
+
var requests = [];
|
12
|
+
|
13
|
+
// register metric
|
14
|
+
phantomas.setMetric('requests');
|
15
|
+
phantomas.setMetric('gzipRequests');
|
16
|
+
phantomas.setMetric('postRequests');
|
17
|
+
phantomas.setMetric('redirects');
|
18
|
+
phantomas.setMetric('notFound');
|
19
|
+
phantomas.setMetric('timeToFirstByte');
|
20
|
+
phantomas.setMetric('timeToLastByte');
|
21
|
+
phantomas.setMetric('bodySize'); // content only
|
22
|
+
phantomas.setMetric('contentLength'); // content only
|
23
|
+
|
24
|
+
// parse given URL to get protocol and domain
|
25
|
+
function parseEntryUrl(entry) {
|
26
|
+
var parsed;
|
27
|
+
|
28
|
+
if (entry.url.indexOf('data:') !== 0) {
|
29
|
+
// @see http://nodejs.org/api/url.html#url_url
|
30
|
+
parsed = parseUrl(entry.url) || {};
|
31
|
+
|
32
|
+
entry.protocol = parsed.protocol.replace(':', '');
|
33
|
+
entry.domain = parsed.hostname;
|
34
|
+
entry.query = parsed.query;
|
35
|
+
|
36
|
+
if (entry.protocol === 'https') {
|
37
|
+
entry.isSSL = true;
|
38
|
+
}
|
39
|
+
}
|
40
|
+
else {
|
41
|
+
// base64 encoded data
|
42
|
+
entry.domain = false;
|
43
|
+
entry.protocol = false;
|
44
|
+
entry.isBase64 = true;
|
45
|
+
}
|
46
|
+
}
|
47
|
+
|
48
|
+
// when the monitoring started?
|
49
|
+
var start;
|
50
|
+
phantomas.on('pageOpen', function(res) {
|
51
|
+
start = Date.now();
|
52
|
+
});
|
53
|
+
|
54
|
+
phantomas.on('onResourceRequested', function(res) {
|
55
|
+
// store current request data
|
56
|
+
var entry = requests[res.id] = {
|
57
|
+
id: res.id,
|
58
|
+
url: res.url,
|
59
|
+
method: res.method,
|
60
|
+
requestHeaders: {},
|
61
|
+
sendTime: res.time,
|
62
|
+
bodySize: 0
|
63
|
+
};
|
64
|
+
|
65
|
+
res.headers.forEach(function(header) {
|
66
|
+
entry.requestHeaders[header.name] = header.value;
|
67
|
+
});
|
68
|
+
|
69
|
+
parseEntryUrl(entry);
|
70
|
+
|
71
|
+
if (!entry.isBase64) {
|
72
|
+
phantomas.emit('send', entry, res);
|
73
|
+
}
|
74
|
+
});
|
75
|
+
|
76
|
+
phantomas.on('onResourceReceived', function(res) {
|
77
|
+
// current request data
|
78
|
+
var entry = requests[res.id];
|
79
|
+
|
80
|
+
switch(res.stage) {
|
81
|
+
// the beginning of response
|
82
|
+
case 'start':
|
83
|
+
entry.recvStartTime = res.time;
|
84
|
+
entry.timeToFirstByte = res.time - entry.sendTime;
|
85
|
+
|
86
|
+
// FIXME: buggy
|
87
|
+
// @see http://code.google.com/p/phantomjs/issues/detail?id=169
|
88
|
+
entry.bodySize += res.bodySize || 0;
|
89
|
+
break;
|
90
|
+
|
91
|
+
// the end of response
|
92
|
+
case 'end':
|
93
|
+
// timing
|
94
|
+
entry.recvEndTime = res.time;
|
95
|
+
entry.timeToLastByte = res.time - entry.sendTime;
|
96
|
+
entry.receiveTime = entry.recvEndTime - entry.recvStartTime;
|
97
|
+
|
98
|
+
// request method
|
99
|
+
switch(entry.method) {
|
100
|
+
case 'POST':
|
101
|
+
phantomas.incrMetric('postRequests');
|
102
|
+
break;
|
103
|
+
}
|
104
|
+
|
105
|
+
// HTTP code
|
106
|
+
entry.status = res.status || 200 /* for base64 data */;
|
107
|
+
entry.statusText = HTTP_STATUS_CODES[entry.status];
|
108
|
+
|
109
|
+
switch(entry.status) {
|
110
|
+
case 301:
|
111
|
+
case 302:
|
112
|
+
phantomas.incrMetric('redirects');
|
113
|
+
phantomas.addNotice(entry.url + ' is a redirect (HTTP ' + entry.status + ')');
|
114
|
+
break;
|
115
|
+
|
116
|
+
case 404:
|
117
|
+
phantomas.incrMetric('notFound');
|
118
|
+
phantomas.addNotice(entry.url + ' was not found (HTTP 404)');
|
119
|
+
break;
|
120
|
+
}
|
121
|
+
|
122
|
+
parseEntryUrl(entry);
|
123
|
+
|
124
|
+
// asset type
|
125
|
+
entry.type = 'other';
|
126
|
+
|
127
|
+
// analyze HTTP headers
|
128
|
+
entry.headers = {};
|
129
|
+
res.headers.forEach(function(header) {
|
130
|
+
entry.headers[header.name] = header.value;
|
131
|
+
|
132
|
+
switch (header.name.toLowerCase()) {
|
133
|
+
// TODO: why it's not gzipped?
|
134
|
+
// because: http://code.google.com/p/phantomjs/issues/detail?id=156
|
135
|
+
// should equal bodySize
|
136
|
+
case 'content-length':
|
137
|
+
entry.contentLength = parseInt(header.value, 10);
|
138
|
+
break;
|
139
|
+
|
140
|
+
// detect content type
|
141
|
+
case 'content-type':
|
142
|
+
// parse header value
|
143
|
+
var value = header.value.split(';').shift().toLowerCase();
|
144
|
+
|
145
|
+
switch(value) {
|
146
|
+
case 'text/html':
|
147
|
+
entry.type = 'html';
|
148
|
+
entry.isHTML = true;
|
149
|
+
break;
|
150
|
+
|
151
|
+
case 'text/css':
|
152
|
+
entry.type = 'css';
|
153
|
+
entry.isCSS = true;
|
154
|
+
break;
|
155
|
+
|
156
|
+
case 'application/x-javascript':
|
157
|
+
case 'application/javascript':
|
158
|
+
case 'text/javascript':
|
159
|
+
entry.type = 'js';
|
160
|
+
entry.isJS = true;
|
161
|
+
break;
|
162
|
+
|
163
|
+
case 'image/png':
|
164
|
+
case 'image/jpeg':
|
165
|
+
case 'image/gif':
|
166
|
+
entry.type = 'image';
|
167
|
+
entry.isImage = true;
|
168
|
+
break;
|
169
|
+
|
170
|
+
default:
|
171
|
+
phantomas.addNotice('Unknown content type found: ' + value);
|
172
|
+
}
|
173
|
+
break;
|
174
|
+
|
175
|
+
// detect content encoding
|
176
|
+
case 'content-encoding':
|
177
|
+
if (header.value === 'gzip') {
|
178
|
+
entry.gzip = true;
|
179
|
+
}
|
180
|
+
break;
|
181
|
+
}
|
182
|
+
});
|
183
|
+
|
184
|
+
// requests stats
|
185
|
+
if (!entry.isBase64) {
|
186
|
+
phantomas.incrMetric('requests');
|
187
|
+
|
188
|
+
phantomas.incrMetric('bodySize', entry.bodySize); // content only
|
189
|
+
phantomas.incrMetric('contentLength', entry.contentLength || entry.bodySize); // content only
|
190
|
+
}
|
191
|
+
|
192
|
+
if (entry.gzip) {
|
193
|
+
phantomas.incrMetric('gzipRequests');
|
194
|
+
}
|
195
|
+
|
196
|
+
// emit an event for other modules
|
197
|
+
phantomas.emit(entry.isBase64 ? 'base64recv' : 'recv' , entry, res);
|
198
|
+
//phantomas.log(entry);
|
199
|
+
break;
|
200
|
+
}
|
201
|
+
});
|
202
|
+
|
203
|
+
// TTFB / TTLB metrics
|
204
|
+
phantomas.on('recv', function(entry, res) {
|
205
|
+
// check the first request
|
206
|
+
if (entry.id === 1) {
|
207
|
+
phantomas.setMetric('timeToFirstByte', entry.timeToFirstByte);
|
208
|
+
phantomas.setMetric('timeToLastByte', entry.timeToLastByte);
|
209
|
+
}
|
210
|
+
|
211
|
+
// completion of the last HTTP request
|
212
|
+
phantomas.setMetric('httpTrafficCompleted', entry.recvEndTime - start);
|
213
|
+
});
|
214
|
+
};
|
@@ -0,0 +1,16 @@
|
|
1
|
+
/**
|
2
|
+
* Helper functions for string formatting
|
3
|
+
*/
|
4
|
+
function lpad(str, len) {
|
5
|
+
var fill = new Array( Math.max(1, len - str.toString().length + 1) ).join(' ');
|
6
|
+
return fill + str;
|
7
|
+
}
|
8
|
+
|
9
|
+
function rpad(str, len) {
|
10
|
+
var fill = new Array( Math.max(1, len - str.toString().length + 1) ).join(' ');
|
11
|
+
return str + fill;
|
12
|
+
}
|
13
|
+
|
14
|
+
exports.lpad = lpad;
|
15
|
+
exports.rpad = rpad;
|
16
|
+
|
@@ -0,0 +1,418 @@
|
|
1
|
+
/**
|
2
|
+
* phantomas main file
|
3
|
+
*/
|
4
|
+
|
5
|
+
var VERSION = '0.4.1';
|
6
|
+
|
7
|
+
var getDefaultUserAgent = function() {
|
8
|
+
var version = phantom.version,
|
9
|
+
system = require('system'),
|
10
|
+
os = system.os;
|
11
|
+
|
12
|
+
return "phantomas/" + VERSION + " (PhantomJS/" + version.major + "." + version.minor + "." + version.patch + "; " + os.name + " " + os.architecture + ")";
|
13
|
+
}
|
14
|
+
|
15
|
+
var phantomas = function(params) {
|
16
|
+
// parse script CLI parameters
|
17
|
+
this.params = params;
|
18
|
+
|
19
|
+
// --url=http://example.com
|
20
|
+
this.url = this.params.url;
|
21
|
+
|
22
|
+
// --format=[csv|json]
|
23
|
+
this.resultsFormat = params.format || 'plain';
|
24
|
+
|
25
|
+
// --viewport=1280x1024
|
26
|
+
this.viewport = params.viewport || '1280x1024';
|
27
|
+
|
28
|
+
// --verbose
|
29
|
+
this.verboseMode = params.verbose === true;
|
30
|
+
|
31
|
+
// --silent
|
32
|
+
this.silentMode = params.silent === true;
|
33
|
+
|
34
|
+
// --timeout (in seconds)
|
35
|
+
this.timeout = (params.timeout > 0 && parseInt(params.timeout, 10)) || 15;
|
36
|
+
|
37
|
+
// --modules=localStorage,cookies
|
38
|
+
this.modules = (params.modules) ? params.modules.split(',') : [];
|
39
|
+
|
40
|
+
// --user-agent=custom-agent
|
41
|
+
this.userAgent = params['user-agent'] || getDefaultUserAgent();
|
42
|
+
|
43
|
+
// setup the stuff
|
44
|
+
this.emitter = new (this.require('events').EventEmitter)();
|
45
|
+
this.emitter.setMaxListeners(200);
|
46
|
+
|
47
|
+
this.page = require('webpage').create();
|
48
|
+
|
49
|
+
// current HTTP requests counter
|
50
|
+
this.currentRequests = 0;
|
51
|
+
|
52
|
+
this.log('phantomas v' + VERSION);
|
53
|
+
|
54
|
+
// load core modules
|
55
|
+
this.addCoreModule('requestsMonitor');
|
56
|
+
|
57
|
+
// load 3rd party modules
|
58
|
+
var modules = (this.modules.length > 0) ? this.modules : this.listModules(),
|
59
|
+
self = this;
|
60
|
+
|
61
|
+
modules.forEach(function(moduleName) {
|
62
|
+
self.addModule(moduleName);
|
63
|
+
});
|
64
|
+
};
|
65
|
+
|
66
|
+
phantomas.version = VERSION;
|
67
|
+
|
68
|
+
phantomas.prototype = {
|
69
|
+
metrics: {},
|
70
|
+
notices: [],
|
71
|
+
|
72
|
+
// simple version of jQuery.proxy
|
73
|
+
proxy: function(fn, scope) {
|
74
|
+
scope = scope || this;
|
75
|
+
return function () {
|
76
|
+
return fn.apply(scope, arguments);
|
77
|
+
};
|
78
|
+
},
|
79
|
+
|
80
|
+
// emit given event
|
81
|
+
emit: function(/* eventName, arg1, arg2, ... */) {
|
82
|
+
this.log('Event ' + arguments[0] + ' emitted');
|
83
|
+
this.emitter.emit.apply(this.emitter, arguments);
|
84
|
+
},
|
85
|
+
|
86
|
+
// bind to a given event
|
87
|
+
on: function(ev, fn) {
|
88
|
+
this.emitter.on(ev, fn);
|
89
|
+
},
|
90
|
+
|
91
|
+
once: function(ev, fn) {
|
92
|
+
this.emitter.once(ev, fn);
|
93
|
+
},
|
94
|
+
|
95
|
+
// returns "wrapped" version of phantomas object with public methods / fields only
|
96
|
+
getPublicWrapper: function() {
|
97
|
+
var self = this;
|
98
|
+
|
99
|
+
// modules API
|
100
|
+
return {
|
101
|
+
url: this.params.url,
|
102
|
+
params: this.params,
|
103
|
+
|
104
|
+
// events
|
105
|
+
on: function() {self.on.apply(self, arguments);},
|
106
|
+
once: function() {self.once.apply(self, arguments);},
|
107
|
+
emit: function() {self.emit.apply(self, arguments);},
|
108
|
+
|
109
|
+
// metrics
|
110
|
+
setMetric: function() {self.setMetric.apply(self, arguments);},
|
111
|
+
setMetricEvaluate: function() {self.setMetricEvaluate.apply(self, arguments);},
|
112
|
+
incrMetric: function() {self.incrMetric.apply(self, arguments);},
|
113
|
+
|
114
|
+
// debug
|
115
|
+
addNotice: function(msg) {self.addNotice(msg);},
|
116
|
+
log: function(msg) {self.log(msg);},
|
117
|
+
echo: function(msg) {self.echo(msg);},
|
118
|
+
|
119
|
+
// phantomJS
|
120
|
+
evaluate: function(fn) {return self.page.evaluate(fn);},
|
121
|
+
injectJs: function(src) {return self.page.injectJs(src);},
|
122
|
+
require: function(module) {return self.require(module);},
|
123
|
+
getPageContent: function() {return self.page.content;}
|
124
|
+
};
|
125
|
+
},
|
126
|
+
|
127
|
+
// initialize given core phantomas module
|
128
|
+
addCoreModule: function(name) {
|
129
|
+
var pkg = require('./modules/' + name + '/' + name);
|
130
|
+
|
131
|
+
// init a module
|
132
|
+
pkg.module(this.getPublicWrapper());
|
133
|
+
|
134
|
+
this.log('Core module ' + name + (pkg.version ? ' v' + pkg.version : '') + ' initialized');
|
135
|
+
},
|
136
|
+
|
137
|
+
// initialize given phantomas module
|
138
|
+
addModule: function(name) {
|
139
|
+
var pkg;
|
140
|
+
try {
|
141
|
+
pkg = require('./../modules/' + name + '/' + name);
|
142
|
+
}
|
143
|
+
catch (e) {
|
144
|
+
this.log('Unable to load module "' + name + '"!');
|
145
|
+
return false;
|
146
|
+
}
|
147
|
+
|
148
|
+
if (pkg.skip) {
|
149
|
+
this.log('Module ' + name + ' skipped!');
|
150
|
+
return false;
|
151
|
+
}
|
152
|
+
|
153
|
+
// init a module
|
154
|
+
pkg.module(this.getPublicWrapper());
|
155
|
+
|
156
|
+
this.log('Module ' + name + (pkg.version ? ' v' + pkg.version : '') + ' initialized');
|
157
|
+
return true;
|
158
|
+
},
|
159
|
+
|
160
|
+
// returns list of 3rd party modules located in modules directory
|
161
|
+
listModules: function() {
|
162
|
+
this.log('Getting the list of all modules...');
|
163
|
+
|
164
|
+
var fs = require('fs'),
|
165
|
+
modulesDir = fs.workingDirectory + '/modules',
|
166
|
+
ls = fs.list(modulesDir) || [],
|
167
|
+
modules = [];
|
168
|
+
|
169
|
+
ls.forEach(function(entry) {
|
170
|
+
if (fs.isFile(modulesDir + '/' + entry + '/' + entry + '.js')) {
|
171
|
+
modules.push(entry);
|
172
|
+
}
|
173
|
+
});
|
174
|
+
|
175
|
+
return modules;
|
176
|
+
},
|
177
|
+
|
178
|
+
// runs phantomas
|
179
|
+
run: function(callback) {
|
180
|
+
|
181
|
+
// check required params
|
182
|
+
if (!this.url) {
|
183
|
+
throw '--url argument must be provided!';
|
184
|
+
}
|
185
|
+
|
186
|
+
// to be called by tearDown
|
187
|
+
this.onDoneCallback = callback;
|
188
|
+
|
189
|
+
this.start = Date.now();
|
190
|
+
|
191
|
+
// setup viewport
|
192
|
+
var parsedViewport = this.viewport.split('x');
|
193
|
+
|
194
|
+
if (parsedViewport.length === 2) {
|
195
|
+
this.page.viewportSize = {
|
196
|
+
height: parseInt(parsedViewport[0], 10) || 1280,
|
197
|
+
width: parseInt(parsedViewport[1], 10) || 1024
|
198
|
+
};
|
199
|
+
}
|
200
|
+
|
201
|
+
// setup user agent
|
202
|
+
if (this.userAgent) {
|
203
|
+
this.page.settings.userAgent = this.userAgent;
|
204
|
+
}
|
205
|
+
|
206
|
+
// print out debug messages
|
207
|
+
this.log('Opening <' + this.url + '>...');
|
208
|
+
this.log('Using ' + this.page.settings.userAgent + ' as user agent');
|
209
|
+
this.log('Viewport set to ' + this.page.viewportSize.height + 'x' + this.page.viewportSize.width);
|
210
|
+
|
211
|
+
// bind basic events
|
212
|
+
this.page.onInitialized = this.proxy(this.onInitialized);
|
213
|
+
this.page.onLoadStarted = this.proxy(this.onLoadStarted);
|
214
|
+
this.page.onLoadFinished = this.proxy(this.onLoadFinished);
|
215
|
+
this.page.onResourceRequested = this.proxy(this.onResourceRequested);
|
216
|
+
this.page.onResourceReceived = this.proxy(this.onResourceReceived);
|
217
|
+
|
218
|
+
// debug
|
219
|
+
this.page.onAlert = this.proxy(this.onAlert);
|
220
|
+
this.page.onConsoleMessage = this.proxy(this.onConsoleMessage);
|
221
|
+
|
222
|
+
// observe HTTP requests
|
223
|
+
// finish when the last request is completed
|
224
|
+
|
225
|
+
// update HTTP requests counter
|
226
|
+
this.on('send', this.proxy(function(entry) {
|
227
|
+
this.currentRequests++;
|
228
|
+
}));
|
229
|
+
|
230
|
+
this.on('recv', this.proxy(function(entry) {
|
231
|
+
this.currentRequests--;
|
232
|
+
|
233
|
+
this.enqueueReport();
|
234
|
+
}));
|
235
|
+
|
236
|
+
// last time changes?
|
237
|
+
this.emit('pageBeforeOpen', this.page);
|
238
|
+
|
239
|
+
// open the page
|
240
|
+
this.page.open(this.url);
|
241
|
+
|
242
|
+
this.emit('pageOpen');
|
243
|
+
|
244
|
+
// fallback - always timeout after TIMEOUT seconds
|
245
|
+
this.log('Run timeout set to ' + this.timeout + ' s');
|
246
|
+
setTimeout(this.proxy(function() {
|
247
|
+
this.log('Timeout of ' + this.timeout + ' s was reached!');
|
248
|
+
this.report();
|
249
|
+
}), this.timeout * 1000);
|
250
|
+
},
|
251
|
+
|
252
|
+
/**
|
253
|
+
* Wait a second before finishing the monitoring (i.e. report generation)
|
254
|
+
*
|
255
|
+
* This one is called when response is received. Previously scheduled reporting is removed and the new is created.
|
256
|
+
*/
|
257
|
+
enqueueReport: function() {
|
258
|
+
clearTimeout(this.lastRequestTimeout);
|
259
|
+
|
260
|
+
if (this.currentRequests < 1) {
|
261
|
+
this.lastRequestTimeout = setTimeout(this.proxy(this.report), 1000);
|
262
|
+
}
|
263
|
+
},
|
264
|
+
|
265
|
+
// called when all HTTP requests are completed
|
266
|
+
report: function() {
|
267
|
+
this.emit('report');
|
268
|
+
|
269
|
+
var time = Date.now() - this.start;
|
270
|
+
this.log('phantomas work done in ' + time + ' ms');
|
271
|
+
|
272
|
+
// format results
|
273
|
+
var results = {
|
274
|
+
url: this.url,
|
275
|
+
metrics: this.metrics,
|
276
|
+
notices: this.notices
|
277
|
+
};
|
278
|
+
|
279
|
+
this.emit('results', results);
|
280
|
+
|
281
|
+
// count all metrics
|
282
|
+
var metricsCount = 0,
|
283
|
+
i;
|
284
|
+
|
285
|
+
for (i in this.metrics) {
|
286
|
+
metricsCount++;
|
287
|
+
}
|
288
|
+
|
289
|
+
this.log('Formatting results (' + this.resultsFormat + ') with ' + metricsCount+ ' metric(s)...');
|
290
|
+
|
291
|
+
// render results
|
292
|
+
var formatter = require('./formatter').formatter,
|
293
|
+
renderer = new formatter(results, this.resultsFormat);
|
294
|
+
|
295
|
+
this.echo(renderer.render());
|
296
|
+
this.tearDown(0);
|
297
|
+
},
|
298
|
+
|
299
|
+
tearDown: function(exitCode) {
|
300
|
+
exitCode = exitCode || 0;
|
301
|
+
|
302
|
+
if (exitCode > 0) {
|
303
|
+
this.log('Exiting with code #' + exitCode);
|
304
|
+
}
|
305
|
+
|
306
|
+
this.page.release();
|
307
|
+
|
308
|
+
// call function provided to run() method
|
309
|
+
if (typeof this.onDoneCallback === 'function') {
|
310
|
+
this.onDoneCallback();
|
311
|
+
}
|
312
|
+
else {
|
313
|
+
phantom.exit(exitCode);
|
314
|
+
}
|
315
|
+
},
|
316
|
+
|
317
|
+
// core events
|
318
|
+
onInitialized: function() {
|
319
|
+
// add helper tools into window.phantomas "namespace"
|
320
|
+
this.page.injectJs('./core/helper.js');
|
321
|
+
|
322
|
+
this.log('Page object initialized');
|
323
|
+
this.emit('init');
|
324
|
+
},
|
325
|
+
|
326
|
+
onLoadStarted: function() {
|
327
|
+
this.log('Page loading started');
|
328
|
+
this.emit('loadStarted');
|
329
|
+
},
|
330
|
+
|
331
|
+
onResourceRequested: function(res) {
|
332
|
+
this.emit('onResourceRequested', res);
|
333
|
+
//this.log(JSON.stringify(res));
|
334
|
+
},
|
335
|
+
|
336
|
+
onResourceReceived: function(res) {
|
337
|
+
this.emit('onResourceReceived', res);
|
338
|
+
//this.log(JSON.stringify(res));
|
339
|
+
},
|
340
|
+
|
341
|
+
onLoadFinished: function(status) {
|
342
|
+
// trigger this only once
|
343
|
+
if (this.onLoadFinishedEmitted) {
|
344
|
+
return;
|
345
|
+
}
|
346
|
+
this.onLoadFinishedEmitted = true;
|
347
|
+
|
348
|
+
// we're done
|
349
|
+
this.log('Page loading finished ("' + status + '")');
|
350
|
+
|
351
|
+
switch(status) {
|
352
|
+
case 'success':
|
353
|
+
this.emit('loadFinished', status);
|
354
|
+
this.enqueueReport();
|
355
|
+
break;
|
356
|
+
|
357
|
+
default:
|
358
|
+
this.emit('loadFailed', status);
|
359
|
+
this.tearDown(2);
|
360
|
+
break;
|
361
|
+
}
|
362
|
+
},
|
363
|
+
|
364
|
+
// debug
|
365
|
+
onAlert: function(msg) {
|
366
|
+
this.log('Alert: ' + msg);
|
367
|
+
this.emit('alert', msg);
|
368
|
+
},
|
369
|
+
|
370
|
+
onConsoleMessage: function(msg) {
|
371
|
+
this.log('console.log: ' + msg);
|
372
|
+
this.emit('consoleLog', msg);
|
373
|
+
},
|
374
|
+
|
375
|
+
// metrics reporting
|
376
|
+
setMetric: function(name, value) {
|
377
|
+
this.metrics[name] = (typeof value !== 'undefined') ? value : 0;
|
378
|
+
},
|
379
|
+
|
380
|
+
setMetricEvaluate: function(name, fn) {
|
381
|
+
this.setMetric(name, this.page.evaluate(fn));
|
382
|
+
},
|
383
|
+
|
384
|
+
// increements given metric by given number (default is one)
|
385
|
+
incrMetric: function(name, incr /* =1 */) {
|
386
|
+
this.metrics[name] = (this.metrics[name] || 0) + (incr || 1);
|
387
|
+
},
|
388
|
+
|
389
|
+
// adds a notice that will be emitted after results
|
390
|
+
addNotice: function(msg) {
|
391
|
+
this.notices.push(msg || '');
|
392
|
+
},
|
393
|
+
|
394
|
+
// add log message
|
395
|
+
// will be printed out only when --verbose
|
396
|
+
log: function(msg) {
|
397
|
+
if (this.verboseMode) {
|
398
|
+
msg = (typeof msg === 'object') ? JSON.stringify(msg) : msg;
|
399
|
+
|
400
|
+
this.echo('> ' + msg);
|
401
|
+
}
|
402
|
+
},
|
403
|
+
|
404
|
+
// console.log wrapper obeying --silent mode
|
405
|
+
echo: function(msg) {
|
406
|
+
if (!this.silentMode) {
|
407
|
+
console.log(msg);
|
408
|
+
}
|
409
|
+
},
|
410
|
+
|
411
|
+
// require CommonJS module from lib/modules
|
412
|
+
require: function(module) {
|
413
|
+
return require('../lib/modules/' + module);
|
414
|
+
}
|
415
|
+
};
|
416
|
+
|
417
|
+
exports.phantomas = phantomas;
|
418
|
+
|