jenkins-bot has submitted this change and it was merged. Change subject: Add support for reporting metrics using more than statsv ......................................................................
Add support for reporting metrics using more than statsv Reworked the structure so we can have different reporters reporting metrics. We now support statsv, json, csv, and Graphite. We can add more reporters later. There's a new parameter --reporter that needs to be configured for each run (fixed in the batch files) so this should work out of the box in Jenkins. But one change is that the authenticated part of the key has been moved to the namespace, so the graph queries need to be changed. Also documented the methods. There a new bash script that should be used to verify changes in batch files. Check out the documentation at the top of the file: test/rulethemall.sh Also cleaned up the default values to make it easier to find. Bug: T114343 Change-Id: I7bd582a4a180b07b62d6c94552555957baf34ce6 --- M README.md A bin/index.js M examples/batchExample.txt M examples/customMetrics.txt M examples/scripting.txt M lib/cli.js A lib/collectMetrics.js M lib/index.js A lib/reporter/csv.js A lib/reporter/graphite.js A lib/reporter/index.js A lib/reporter/json.js A lib/reporter/statsv.js M lib/util.js A lib/wpt.js M package.json M scripts/batch/desktop.txt M scripts/batch/login-desktop.txt M scripts/batch/login-mobile.txt M scripts/batch/mobile-wpt-org.txt M scripts/batch/mobile.txt M scripts/batch/second-view-desktop.txt M scripts/batch/second-view-mobile.txt A test/batchTest.js M test/cliTest.js A test/collectMetricsTest.js M test/files/batch.txt A test/reporterTest.js A test/rulethemall.sh M test/utilTest.js 30 files changed, 1,027 insertions(+), 481 deletions(-) Approvals: Krinkle: Looks good to me, approved jenkins-bot: Verified diff --git a/README.md b/README.md index 8692d09..f70c0fa 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,111 @@ -# wptstatsv +# wpt-reporter -Collect browser metrics using WebPageTest and send it to [statsv](https://wikitech.wikimedia.org/wiki/Graphite#statsv). You can find more info about how we use WebPageTest at [Wikitech](https://wikitech.wikimedia.org/wiki/WebPageTest). +Collect browser metrics using WebPageTest and choose how to report it. We wrap the functionality of the https://github.com/marcelduran/webpagetest-api and collect the most important metrics from the (giant) result JSON and report it as/to CSV/JSON/Graphite/statsv. + +## Install + +<pre> +npm install wpt-reporter +</pre> + +## Run +To be able to use it you need to either have an API key to the public WebPageTest instance (you can get one [here](http://www.webpagetest.org/getkey.php)) or setup your own instance (follow our instructions [here](https://wikitech.wikimedia.org/wiki/WebPageTest#WebPageTest_and_AWS) on how to setup your own instance at AWS). + +When you have the key or your own instance and cloned/installed this repository, you are ready to go. + +You can check all configuration with help: +<pre> +wpt-reporter --help +</pre> + +All parameters will also be passed on to the [webpagetest-api](https://github.com/marcelduran/webpagetest-api) so if you want to configure a specific WebPageTest configuration that isn't included in the help section, check out the [runTest](https://github.com/marcelduran/webpagetest-api#test-works-for-runtest-method-only) method and add that to the parameter list. + +## Choose server and location +When you run a test you need configure which WebPageTest server to use (default one is http://www.webpagetest.org) and a location (for WebPageTest.org it uses *Dulles:Chrome* but you can change that to one of the available in http://www.webpagetest.org/getLocations.php). + +If you run your own WebPageTest server make sure to also change the location. You change server and location with the parameters *webPageTestHost* and *location*. + +## The keys/name of the metrics +If you send the data to Graphite/statsv, the key names will be generated like this: +*namespace.location.browser.view.metric* + +Lets go through it: + * **namespace** is the start of the key default is webpagetest but you can change that with the parameter *--namespace* when you run the script. + * **location** is the location of your agent, this will be picked up automatically from your configuration. + * **browser** is the browser type in WebPageTest. If we emulate a mobile browser, the name will have *-emaulatedMobile* appended to the name. If the name isn't configured in WebPageTest, the full location string is used. + * **view** is the *firstView* or *repeatView*. + * **metrics** is the actual metric that's picked up + +A key look something like this **webpagetest.us-west-1.chrome.firstView.SpeedIndex** + + Note: If you test your pages with different connectivities or in other ways want to sepaprate the keys, just add an part of your key with the namespace like: *--namespace webpagetest.cable* + +## Choose what metrics to collect +Default these metrics are collected: + * [SpeedIndex](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics/speed-index) + * [render](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics#TOC-Start-Render) + * [TTFB](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics#TOC-First-Byte) + * [fullyLoaded](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/metrics) + * The size and number of request for html, js, css and images + +You can override the timing metrics by supplying the **--metrics** parameter with a comma separated list of metrics to collect. They will be fetched from the median run from the JSON namespace of data.median.firstView.METRIC and data.median.repeatView.METRIC. + +## How report the metrics +You can choose how to report the metrics collected by setting the **reporter**. + +### CSV +Why should you use CSV? It's nice in way if you want to test a couple URLs and compare the result. You can choose where CSV data will be stored with the **file** option. If the file doesn't exist, it will add one line with all the column names of the metrics. If the file exists, it will just append the new metrics on a new line. + +<pre> +wpt-reporter --reporter csv --file myruns.csv --webPageTestKey MY_SECRET_KEY https://www.wikipedia.org/ +</pre> + +### JSON +The JSON reporter is a nice way to just check metrics for a run. Use it like this: + +<pre> +wpt-reporter --reporter json --webPageTestKey MY_SECRET_KEY https://www.wikipedia.org/ +</pre> + +### Graphite +Do you want to graph your data? Do that by storing the metrics in Graphite. To do that you need to have Graphite up and running (hint: use a Docker container including Graphite, that's much easier than installing it yourself). + +When you run you add your configuration to the Graphite host and port: + +<pre> +wpt-reporter --reporter graphite --graphiteHost graphite.example.org --graphitePort 2003 --webPageTestKey MY_SECRET_KEY https://www.wikipedia.org/ +</pre> + +### statsv +[Statsv](https://wikitech.wikimedia.org/wiki/Graphite#statsv) what we use at Wikimedia to get metrics to Graphite, via statsd, over HTTP. You can [read more](https://wikitech.wikimedia.org/wiki/WebPageTest) about how we use it together with WebPageTest. + +## Environment variables and scripting +If you feed the script with a batch file containing multiple runs or if you use [WebPageTest script language](https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/scripting), keys will automatically be replaced with environment variables. The keys need to be of the format *<%KEY_NAME>*. + +If we have the variable <%WMF_WPT_KEY> in our script, it will be replaced by the environment variable named WMF_WPT_KEY. Running in node, the value will be replaced with *process.env.WMF_WPT_KEY*. If the environment variable is missing from the file, you will notice that in the console log. + +How can you use that? One example is that you can make generic login script and parametrize the URL you want to test like this: + +<pre> +// The login page +logData 0 +navigate https://en.m.wikipedia.org/w/index.php?title=Special:UserLogin&returnto=Main+Page + +// Log in the user +setValue name=wpName <%WPT_USER> +setValue name=wpPassword <%WPT_USER_PASSWORD> +submitForm name=userlogin + +// This is the URL that we want to measure as a logged in user +logData 1 +navigate <%WPT_MOBILE_URL> +</pre> +Here you set the login name (<%WPT_USER>) the password <%WPT_USER_PASSWORD> and the URL to test (<%WPT_MOBILE_URL>) as variables. + + +## Want to know more? + +You can find more info about how we use WebPageTest at [Wikitech](https://wikitech.wikimedia.org/wiki/WebPageTest). + This is a Github mirror of "performance/WebPageTest" - our actual code is hosted with Gerrit (please see https://www.mediawiki.org/wiki/Developer_access for contributing. diff --git a/bin/index.js b/bin/index.js new file mode 100755 index 0000000..ef295f4 --- /dev/null +++ b/bin/index.js @@ -0,0 +1,30 @@ +#!/usr/bin/env node + +/** + * @fileoverview The bin file to run. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; +var minimist = require('minimist'); +var cli = require('../lib/cli'); +var wpt = require('../lib/index'); + +var argv = cli.getMinimistArgv(process.argv.slice(2)); + +if (argv.help) { + cli.help(); + process.exit(0); +} + +if (!cli.validateArgs(argv)) { + process.exit(1); +} + +if (argv.batch) { + wpt.runBatch(argv); +} else { + wpt.runTest(argv); +} diff --git a/examples/batchExample.txt b/examples/batchExample.txt index d593924..8d9e9ba 100644 --- a/examples/batchExample.txt +++ b/examples/batchExample.txt @@ -16,7 +16,7 @@ ## Your script can look like this (where WMF_WPT_KEY need to be an environment variable) ## Test the Facebook page 15 times ---webPageTestKey <%WMF_WPT_KEY> --runs 15 --median SpeedIndex https://en.wikipedia.org/wiki/Facebook +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.example.org --runs 15 --median SpeedIndex https://en.wikipedia.org/wiki/Facebook ## And then test Barack 31 and use SpeedIndex as median ---webPageTestKey <%WMF_WPT_KEY> --runs 31 --median SpeedIndex https://en.wikipedia.org/wiki/Barack_Obama +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.example.org --runs 31 --median SpeedIndex --reporter json https://en.wikipedia.org/wiki/Barack_Obama diff --git a/examples/customMetrics.txt b/examples/customMetrics.txt index 6664c58..122f2ee 100644 --- a/examples/customMetrics.txt +++ b/examples/customMetrics.txt @@ -56,4 +56,4 @@ return n; } -return Math.round(avgDomDepth()); \ No newline at end of file +return Math.round(avgDomDepth()); diff --git a/examples/scripting.txt b/examples/scripting.txt index fbbbf4c..ec8a79c 100644 --- a/examples/scripting.txt +++ b/examples/scripting.txt @@ -3,9 +3,9 @@ // https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/scripting // put any urls you want to navigate -navigate https://www.wikipedia.org/ +navigate https://www.example.org/ logData 1 // this step will get recorded -navigate https://en.wikipedia.org/wiki/Main_Page +navigate https://www.example.org/second/ diff --git a/lib/cli.js b/lib/cli.js index 4d107e8..f8e6b99 100644 --- a/lib/cli.js +++ b/lib/cli.js @@ -1,64 +1,71 @@ -/* -wptstatsv -~~~~~~~ -A thin wrapper for the WebPageTest API that sends metrics to statsv. - -Copyright 2015 Peter Hedenskog <phedens...@wikimedia.org> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +/** + * @fileoverview Command line helper methods. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ 'use strict'; -var util = require('./util'); +var reporters = require('./reporter'); +var minimist = require('minimist'); -var AVAILIBLE_USER_STATUS = ['anonymous', 'authenticated']; +var DEFAULT_LOCATION = 'Dulles:Chrome'; +var DEFAULT_CONNECTIVITY = 'Cable'; +var DEFAULT_WEBPAGETEST_HOST = 'www.webpagetest.org'; +var DEFAULT_NAMESPACE = 'webpagetest'; +// Here are the values we collect. Want to add more? Check the JSON that is returned: +// https://sites.google.com/a/webpagetest.org/docs/advanced-features/webpagetest-restful-apis +// #TOC-Sample +// Not 100% sure it's the latest though. Test by logging the output from WebPageTest +// Note: It can differ depending on what agent that runs the tests. +var DEFAULT_METRICS = 'SpeedIndex,render,TTFB,fullyLoaded'; module.exports = { - // TODO add extra namespace (key?) + /** + * Print the options to the console. + */ help: function() { - console.log(' Thin wrapper for the WebPageTest API that sends metrics to statsv.\n'); + console.log(' Thin wrapper for the WebPageTest API that reports metrics in different' + + ' formats.\n'); console.log(' Usage: ' + process.argv[1] + ' [options] [URL/scriptFile]'); console.log(' Supply a file when you want to script WebPageTest ' + - 'https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/scripting.'); + 'https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/scripting.'); console.log(' You can test multiple URL:s (or files) using the batch option: [options]' + - '--batch ' + '[file]\n'); + '--batch ' + '[file]\n'); console.log(' Options:'); console.log(' --webPageTestKey The secret key for the WebPageTest instance ' + - '[required]'); + '[required]'); console.log(' --batch The path to a file containing multiple URLs to test.'); - console.log(' --namespace The namespace of the key sent to statsv.' + - '[webpagetest]'); console.log(' --location The location and browser to use, check ' + - ' http://wpt.wmftest.org/getLocations.php [us-east-1:Chrome]'); + ' http://webPageTestHost/getLocations.php. [' + DEFAULT_LOCATION + ']'); console.log(' --connectivity Connectivity profile ' + - '(Cable|DSL|FIOS|Dial|3G|3GFast|Native|custom) [Cable]'); + '(Cable|DSL|FIOS|Dial|3G|3GFast|Native|custom) [' + DEFAULT_CONNECTIVITY + ']'); console.log(' --runs Number of test runs [11]'); console.log(' --webPageTestHost The host of where to run your test. ' + - '[http://wpt.wmftest.org]'); + '[' + DEFAULT_WEBPAGETEST_HOST + ']'); console.log(' --timeout The timeout in seconds until we end the test [1200]'); - console.log(' --userStatus Is the user logged in or not? Used in the namespace ' + - '(anonymous|authenticated) [anonymous]'); - console.log(' --endpoint Where to send the statsv metrics ' + - '[https://www.example.com]'); - console.log(' --sendMetrics Send metrics to statsv or not. Set to send ' + - 'metrics.'); + console.log(' --reporter Choose how you want to report the metrics ' + + '[csv|json|graphite|statsv] '); console.log(' --customMetrics A file with custom WPT metrics. ' + - 'https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/custom-metrics '); - console.log(' --verbose Log the full JSON from WebPageTest to the console\n'); + 'https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/custom-metrics '); + console.log(' --verbose Log the full JSON from WebPageTest to the console'); + console.log(' --namespace The namespace of the keys ' + + '[' + DEFAULT_NAMESPACE + ']'); + console.log(' --metrics A list of metrics to collect from the median run ' + + '[' + DEFAULT_METRICS + ']'); + Object.keys(reporters.getReporters()).forEach(function(name) { + reporters.get(name).help(); + }); + console.log(' For a full list of options, check ' + - 'https://github.com/marcelduran/webpagetest-api#test-works-for-runtest-method-only\n'); + 'https://github.com/marcelduran/webpagetest-api#test-works-for-runtest-method-only\n'); }, + /** + * Validate the input arguments. + * @param {array} argv The input parameters for the run. + * @return {boolean} returns true if the argumenst are ok. + */ validateArgs: function(argv) { if (argv.batch) { // if it is batch job then test the parameters per line @@ -68,27 +75,48 @@ return false; } else if (argv._.length === 0) { console.error('Missing URL or file as an argument. Supply the URL to test or a file ' + - 'with a scripted test'); + 'with a scripted test'); return false; } else if (argv._length > 1) { console.error('There are ' + argv._length + ' arguments passed to the script (one is' + - ' enough). Could there be extra spaces in your parameter list?'); + ' enough). Could there be extra spaces in your parameter list?'); return false; } - if (argv.userStatus && AVAILIBLE_USER_STATUS.indexOf(argv.userStatus) === -1) { - console.error('Not a valid user status:' + argv.userStatus + ' Needs to be one of ' + - '[' + AVAILIBLE_USER_STATUS + ']'); - return false; + if (!argv.batch) { + if (!argv.reporter) { + console.error('Missing reporter. Needs to be one of [' + + Object.keys(reporters.getReporters()) + ']'); + return false; + } + + var reporter = reporters.get(argv.reporter); + if (!reporter) { + console.error('There\'s no matching reporter for ' + argv.reporter); + return false; + } + + var isOK = reporter.validate(argv); + if (!isOK) { + return isOK; + } } + return true; }, - getInputURLorFile: function(arg) { - // is it a file or URL we wanna test? - if (arg.indexOf('http') === -1) { - var fileContent = util.readFile(arg); - return util.replaceWithEnv(fileContent); - } else { - return arg; - } + /** + * Convert an array of data to a minimist argv. + * @param {array} arg the array to be converted. + * @return {object} the minimist argv object. + */ + getMinimistArgv: function(arg) { + return minimist(arg, { + default: { + connectivity: DEFAULT_CONNECTIVITY, + location: DEFAULT_LOCATION, + webPageTestHost: DEFAULT_WEBPAGETEST_HOST, + metrics: DEFAULT_METRICS, + namespace: DEFAULT_NAMESPACE + } + }); } }; diff --git a/lib/collectMetrics.js b/lib/collectMetrics.js new file mode 100644 index 0000000..0a603c3 --- /dev/null +++ b/lib/collectMetrics.js @@ -0,0 +1,79 @@ +/** + * @fileoverview Collect the metrics from the WebPageTest JSON. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; + + +module.exports = { + /** + * Collect the metrics we want from the giant WebPageTest JSON. We always + * collect values from the median run. + * @param {object} wptJson The JSON returned from the WebPageTest API + * @param {array} argv The input parameters for the run. + */ + collect: function(wptJson, argv) { + var self = this; + var metricsToSend = { + timings: {}, + requests: {}, + sizes: {} + }; + var namespace = argv.namespace; + + var emulateMobile = argv.emulateMobile; + var firstViewOnly = argv.first; + var metricsToCollect = argv.metrics.split(','); + + var views = ['firstView']; + if (!firstViewOnly) { + views.push('repeatView'); + } + + views.forEach(function(view) { + // if we are missing browser info from WPT (happens when using MotoG at least) + // use a normalized version of the location + // the browser/location can then look like Dulles_MotoG_Motorola_G_Chrome + // but since there are no standard of naming it should be ok to just use what we got + var browser = wptJson.data.median[view].browser_name ? + wptJson.data.median[view].browser_name.replace(/ /g, '_') : + wptJson.data.location.replace(/[^A-Za-z_0-9]/g, '_').replace(/__+/g, '_'); + + if (emulateMobile) { + browser += '-emulateMobile'; + } + + // the actual location is the first part of the location string + // separated by either a : or _ + var location = wptJson.data.location.split(/:|_/)[0]; + + var keyStart = namespace + '.' + location + '.' + browser + '.' + view + '.'; + + metricsToCollect.forEach(function(metric) { + metricsToSend.timings[keyStart + metric] = wptJson.data.median[view][metric]; + }); + + if (wptJson.data.median[view].userTimes) { + Object.keys(wptJson.data.median[view].userTimes).forEach(function(userTiming) { + metricsToSend.timings[keyStart + userTiming] = + wptJson.data.median[view].userTimes[userTiming]; + }); + } + + // collect sizes & assets + Object.keys(wptJson.data.median[view].breakdown).forEach(function(assetType) { + metricsToSend.requests[keyStart + + assetType + '.requests'] = wptJson.data.median[view].breakdown[assetType]. + requests; + metricsToSend.sizes[keyStart + + assetType + '.bytes'] = wptJson.data.median[view].breakdown[assetType].bytes; + }); + + }); + + return metricsToSend; + } +}; diff --git a/lib/index.js b/lib/index.js index 71c36ea..315f124 100644 --- a/lib/index.js +++ b/lib/index.js @@ -1,132 +1,101 @@ -#!/usr/bin/env node - -/* -wptstatsv -~~~~~~~ -A thin wrapper for the WebPageTest API that sends metrics to statsv. - -Copyright 2015 Peter Hedenskog <phedens...@wikimedia.org> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +/** + * @fileoverview Main file to test URLs at WebPageTest. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ 'use strict'; - -var WebPageTest = require('webpagetest'); -var minimist = require('minimist'); var util = require('./util'); var async = require('async'); var cli = require('./cli'); var eol = require('os').EOL; +var wpt = require('./wpt'); +var collectMetrics = require('./collectMetrics'); -var DEFAULT_USER_STATUS = 'anonymous'; -var DEFAULT_NAMESPACE = 'webpagetest'; +module.exports = { + /** + * Test multiple URLs using a batch file + * @param {array} argv The arguments for the run + */ + runBatch: function(argv, cb) { + var callback = cb || function() {}; + var self = this; + var series = []; + var tests = util.readFile(argv.batch); + var lines = tests.split(eol); + lines.forEach(function(line) { + // only run tests where we have something on that line + if (line.indexOf('#') !== 0 && line.length > 1) { -var argv = minimist(process.argv.slice(2), { - boolean: ['sendMetrics','verbose'] -}); + var myArgs = util.convertTextLineToMinimist(line); + if (!cli.validateArgs(myArgs)) { + process.exit(1); + } + series.push(function(callback) { + self.runTest(myArgs, callback); + }); + } + }); + // lets run the tests one by one after each other. in that way + // the wait time you configure (how long time you wait until a test is finished) + // is more logical, meaning the configured time is per test and not for a + // whole test suite. + async.series(series, // jshint unused:false + function(err, results) { + if (err) { + console.error('Couldn\'t execute all the runs' + err); + } else { + console.log('Succesfully run ' + series.length + ' tests.'); + } + callback(err); + }); + }, -function runBatch(argv) { - var series = []; - var tests = util.readFile(argv.batch); - var lines = tests.split(eol); - lines.forEach(function(line) { - // only run tests where we have something on that line - if (line.indexOf('#') !== 0 && line.length > 1) { + /** + * Test a single URL + * @param {array} argv The arguments for the run + * @param {function} cb The callback that will be called when the test finished + */ + runTest: function(argv, cb) { + var callback = cb || function() {}; + var webPageTestHost = argv.webPageTestHost; + var arg = argv._[0]; - var myArgs = util.convertTextLineToMinimist(line); - if (!cli.validateArgs(myArgs)) { - process.exit(1); - } - series.push(function(callback) { - runTest(myArgs, callback); - }); - } - }); - // lets run the tests one by one after each other. in that way - // the wait time you configure (how long time you wait until a test is finished) - // is more logical, meaning the configured time is per test and not for a - // whole test suite. - async.series(series, // jshint unused:false - function(err, results) { - if (err) { - console.error('Couldn\'t execute all the runs' + err); - } else { - console.log('Succesfully run ' + series.length + ' tests.'); - } - }); -} + var input = util.getInputURLorFile(arg); -function runTest(argv, cb) { - var callback = cb || function() {}; - var endpoint = argv.endpoint || 'https://www.example.com'; - var webPageTestHost = argv.webPageTestHost || 'http://wpt.wmftest.org'; - var wpt = new WebPageTest(webPageTestHost, argv.webPageTestKey); - var userStatus = argv.userStatus || DEFAULT_USER_STATUS; - var namespace = argv.namespace || DEFAULT_NAMESPACE; - - var arg = argv._[0]; - - var input = cli.getInputURLorFile(arg); - - // Note: wptOptions are changed internally when you call runTest, so - // pollResults and timeout are changed to milliseconds so if we set the poll - // to 10 it will be 10000, that's why we recreate the object per run :) - var wptOptions = util.setupWPTOptions(argv); - // read custom javascript metrics - if (argv.customMetrics) { - wptOptions.custom = util.readFile(argv.customMetrics); - } - - console.log('Running WebPageTest [' + wptOptions.location + ' ' + wptOptions.runs + - ' time(s)] for ' + arg); - - wpt.runTest(input, wptOptions, function(err, data) { - - if (argv.verbose) { - console.log(JSON.stringify(data)); + // Note: wptOptions are changed internally when you call runTest, so + // pollResults and timeout are changed to milliseconds so if we set the poll + // to 10 it will be 10000, that's why we recreate the object per run :) + var wptOptions = util.setupWPTOptions(argv); + // read custom javascript metrics + if (argv.customMetrics) { + wptOptions.custom = util.readFile(argv.customMetrics); } - if (err) { - console.error('Couldn\'t fetch data from WebPageTest:' + JSON.stringify(err)); - console.error('Configuration:' + JSON.stringify(wptOptions, null, 2)); - callback(); - return; - } else { - console.log('WebPageTest run: ' + data.data.summary); - var collectedMetrics = util.collectMetrics(data, userStatus, namespace, - argv); - console.log('Collected metrics: ' + JSON.stringify(collectedMetrics, null, 2)); - if (argv.sendMetrics) { - util.sendMetrics(collectedMetrics, endpoint); + console.log('Running WebPageTest [' + wptOptions.location + ' ' + wptOptions.runs + + ' time(s)] for ' + arg); + + wpt.run(webPageTestHost, argv.webPageTestKey, argv, input, wptOptions, function(err, data) { + if (err) { + // the errors from the WebPageTest API can be a little confusing + // but lets hope there is always an error when it doesn't work + console.error('Couldn\'t fetch data from WebPageTest' + err); + } else { + var collectedMetrics = collectMetrics.collect(data, argv); + // log browser and versin if it's availible + if (data.data.median && data.data.median.firstView) { + var browserName = data.data.median.firstView.browser_name; + var browserVersion = data.data.median.firstView.browser_version; + // for some browsers the name and version is not availible + if (browserName && browserVersion) { + console.log('Tested using ' + browserName + ' ' + browserVersion); + } + } + var reporter = require('./reporter').get(argv.reporter); + reporter.report(collectedMetrics, argv); } callback(); - } - }); -} -// ---- Main - -if (argv.help) { - cli.help(); - process.exit(0); -} - -if (!cli.validateArgs(argv)) { - process.exit(1); -} - -if (argv.batch) { - runBatch(argv); -} else { - runTest(argv); -} + }); + } +}; diff --git a/lib/reporter/csv.js b/lib/reporter/csv.js new file mode 100644 index 0000000..65dbe4a --- /dev/null +++ b/lib/reporter/csv.js @@ -0,0 +1,66 @@ +/** + * @fileoverview Report the metrics as CSV. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; + +var fs = require('fs'); +var eol = require('os').EOL; + +module.exports = { + /** + * Validate the input arguments. + * @param {array} argv The input parameters for the run. + * @return {boolean} returns true if the argumenst are ok. + */ + validate: function(argv) { + return true; + }, + /** + * Log help arguments to the console. + */ + help: function() { + console.log(' --file The file and path to the file to write the data. ' + + 'If the file exists, the data will be appended'); + }, + /** + * Report the metrics by writing them as a CSV row. + * @param {object} collectedMetrics The metrics collected from the run. + * @param {array} argv The input parameters for the run. + */ + report: function(collectedMetrics, argv) { + var keys = 'url,location,connectivity,'; + var values = argv._[0] + ',' + argv.location + ',' + + argv.connectivity + ','; + Object.keys(collectedMetrics).forEach(function(type) { + Object.keys(collectedMetrics[type]).forEach(function(metric) { + // only use the last parts of the key that mean something + var re = /firstView|repeatView/; + var sliced = metric.split(re); + // add the view, the metric and if we have unit (bytes/requests) + // add that too + keys += (metric.indexOf('firstView') > -1 ? 'firstView' : 'repeatView') + + sliced[1] + (sliced[2] ? sliced[2] : '') + ','; + values += collectedMetrics[type][metric] + ','; + }); + }); + keys = keys.slice(0, -1); + values = values.slice(0, -1); + + if (argv.file) { + try { + fs.statSync(argv.file); + fs.appendFileSync(argv.file, values + eol, 'utf8'); + } catch (error) { + // it's new file, lets add the keys as a header + fs.appendFileSync(argv.file, keys + eol + values + eol, 'utf8'); + } + console.log('Wrote metrics to ' + argv.file); + } else { + console.log(keys + eol + values); + } + } +}; diff --git a/lib/reporter/graphite.js b/lib/reporter/graphite.js new file mode 100644 index 0000000..86399ba --- /dev/null +++ b/lib/reporter/graphite.js @@ -0,0 +1,61 @@ +/** + * @fileoverview Report the metrics to Graphite. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <peterwikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; + +var net = require('net'); + +module.exports = { + /** + * Validate the input arguments. + * @param {array} argv The input parameters for the run. + * @return {boolean} returns true if the argumenst are ok. + */ + validate: function(argv) { + if (!argv.graphiteHost) { + console.error('Missing configuration for --graphiteHost'); + } + return true; + }, + /** + * Log help arguments to the console. + */ + help: function() { + console.log(' --graphiteHost The Graphite hostname'); + console.log(' --graphitePort The Graphite port [2003]'); + }, + /** + * Report the metrics by writing them as JSON. + * @param {object} collectedMetrics The metrics collected from the run. + * @param {array} argv The input parameters for the run. + */ + report: function(metrics, argv) { + var port = argv.graphitePort || 2003; + var host = argv.graphiteHost; + var server = net.createConnection(port, host); + server.addListener('error', function(error) { + console.error('Could not send data to Graphite:' + error); + }); + + var timeStamp = ' ' + Math.round(new Date().getTime() / 1000) + '\n'; + var data = ''; + Object.keys(metrics).forEach(function(type) { + Object.keys(metrics[type]).forEach(function(metric) { + data += metric + ' ' + metrics[type][metric] + timeStamp; + }); + }); + + if (argv.verbose) { + console.log(data); + } + + server.on('connect', function() { + this.write(data); + this.end(); + }); + } +}; diff --git a/lib/reporter/index.js b/lib/reporter/index.js new file mode 100644 index 0000000..445cc23 --- /dev/null +++ b/lib/reporter/index.js @@ -0,0 +1,43 @@ +/** + * @fileoverview Hold and know which reporters that exists. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; + +var csv = require('./csv'); +var json = require('./json'); +var statsv = require('./statsv'); +var graphite = require('./graphite'); + +var reporters = { + csv: csv, + json: json, + graphite: graphite, + statsv: statsv +}; + +/** + * Get all reporters that exists + * @return {object} key/value for all reporters. + */ +module.exports.getReporters = function() { + return reporters; +}; + +/** + * Get a specifc reporter. + * @param {string} the name of the reporter + * @return {object} the reporter and null if the name doesn't matcj + */ +module.exports.get = function(name) { + var reporter = reporters[name]; + + if (!reporter) { + return null; + } + + return reporter; +}; diff --git a/lib/reporter/json.js b/lib/reporter/json.js new file mode 100644 index 0000000..017c48d --- /dev/null +++ b/lib/reporter/json.js @@ -0,0 +1,32 @@ +/** + * @fileoverview Report the metrics as JSON. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; +module.exports = { + /** + * Validate the input arguments. + * @param {array} argv The input parameters for the run. + * @return {boolean} returns true if the argumenst are ok. + */ + validate: function(argv) { + return true; + }, + /** + * Log help arguments to the console. + */ + help: function() { + + }, + /** + * Report the metrics by writing them as JSON. + * @param {object} collectedMetrics The metrics collected from the run. + * @param {array} argv The input parameters for the run. + */ + report: function(metrics, argv) { + console.log(JSON.stringify(metrics, null, 2)); + } +}; diff --git a/lib/reporter/statsv.js b/lib/reporter/statsv.js new file mode 100644 index 0000000..37c9a14 --- /dev/null +++ b/lib/reporter/statsv.js @@ -0,0 +1,71 @@ +/** + * @fileoverview Report the metrics to statsv. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; +var request = require('request'); + +module.exports = { + /** + * Validate the input arguments. + * @param {array} argv The input parameters for the run. + * @return {boolean} returns true if the argumenst are ok. + */ + validate: function(argv) { + return true; + }, + /** + * Log help arguments to the console. + */ + help: function() { + console.log(' --endpoint Where to send the statsv metrics ' + + '[https://www.example.com]'); + }, + /** + * Report the metrics by sending them to statsv. + * @param {object} collectedMetrics The metrics collected from the run. + * @param {array} argv The input parameters for the run. + */ + report: function(metrics, argv) { + var endpoint = argv.endpoint || 'https://www.example.com'; + var flatten = {}; + // flatten the structure + Object.keys(metrics).forEach(function(type) { + Object.keys(metrics[type]).forEach(function(metric) { + flatten[metric] = metrics[type][metric] + ((type === 'timings') ? 'ms' : 'g'); + }); + }); + + + // Lets do something smarter in the future, now + // cut after 5 keys and send a new request + var MAX_KEYS_PER_REQUEST = 5; + var url = endpoint + '?'; + + var keys = Object.keys(flatten); + for (var i = 0; i < keys.length; i++) { + + url += keys[i] + '=' + flatten[keys[i]] + '&'; + // don't send first, and then for each MAX_KEYS_PER_REQUEST + // and the last time + if (i !== 0 && i % MAX_KEYS_PER_REQUEST === 0 || (i + 1 === flatten.length)) { + url = url.slice(0, -1); + console.log(url); + request(url, function(error, response, body) { // jshint unused:false + if (!error) { + console.log('Succesfully sent metrics.'); + } else { + // default testing to localhost, then skip error logging + if (endpoint.indexOf('http://localhost') === -1) { + console.error(error); + } + } + }); + url = endpoint + '?'; + } + } + } +}; diff --git a/lib/util.js b/lib/util.js index 2e5f77e..6101d2b 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,94 +1,48 @@ -/* -wptstatsv -~~~~~~~ -A thin wrapper for the WebPageTest API that sends metrics to statsv. - -Copyright 2015 Peter Hedenskog <phedens...@wikimedia.org> - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. -*/ +/** + * @fileoverview Utilities file holding help methods. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ 'use strict'; -var request = require('request'); var fs = require('fs'); var path = require('path'); -var minimist = require('minimist'); - -var NAMESPACE = 'webpagetest'; -var BROWSERS = { - 'Google Chrome': 'chrome', - Firefox: 'firefox', - 'Internet Explorer': 'ie', - Safari: 'safari' -}; - +var cli = require('./cli'); module.exports = { - // Here are the values we collect. Want to add more? Check the JSON that is returned: - // https://sites.google.com/a/webpagetest.org/docs/advanced-features/webpagetest-restful-apis - // #TOC-Sample - // Not 100% sure it's the latest though. Test by logging the output from WebPageTest - // Note: It can differs depending on what agent that runs the tests. - // Note 2: Make sure we don't hit the statsv limit of maximum chars in one request - METRICS: ['SpeedIndex', 'render', 'TTFB', 'fullyLoaded'], - ASSET_TYPES: ['html','js','css','image'], - sendMetrics: function(metrics, endpoint) { - - // Lets do something smarter in the future, now - // cut after 5 keys and send a new request - var MAX_KEYS_PER_REQUEST = 5; - var url = endpoint + '?'; - - var keys = Object.keys(metrics); - for (var i = 0; i < keys.length; i++) { - - url += keys[i] + '=' + metrics[keys[i]] + '&'; - // don't send first, and then for each MAX_KEYS_PER_REQUEST - // and the last time - if (i !== 0 && i % MAX_KEYS_PER_REQUEST === 0 || (i + 1 === keys.length)) { - url = url.slice(0, -1); - request(url, function(error, response, body) { // jshint unused:false - if (!error) { - console.log('Succesfully sent metrics.'); - } else { - console.error(error); - } - }); - url = endpoint + '?'; - } - } - }, + /** + * Create a WebPageTest options object from input arguments. + * @param {array} argv The arguments for the run + * @return {object} the WebPageTest options object + */ setupWPTOptions: function(argv) { - // some default options here + var wptOptions = { pollResults: 10, timeout: 1200, - location: 'us-east-1:Chrome', video: 'true', label: argv.label ? this.getTodaysDate() + '-' + argv.label : this.getTodaysDate(), runs: 11 }; Object.keys(argv).forEach(function(param) { - if (['webPageTestKey', 'webPageTestHost', '_', 'verbose', 'userStatus', - 'sendMetrics', 'customMetrics', 'namespace', 'batch', 'label'].indexOf(param) === -1) { + if (['webPageTestKey', 'webPageTestHost', '_', 'verbose', + 'customMetrics', 'namespace', 'batch', 'label', 'endpoint', + 'reporter' + ].indexOf(param) === -1) { wptOptions[param] = argv[param]; } }); return wptOptions; }, + /** + * Convert an input text line from a file to a minimist created argument object + * @param {string} line The text line + * @return {object} a minimist argument object + */ convertTextLineToMinimist: function(line) { // replace variables with env variables line = this.replaceWithEnv(line); @@ -101,11 +55,9 @@ var locationLine = line.match(/--location(.*?)(?=\s--)/g); if (locationLine) { line = line.split(locationLine[0] + ' ').join(''); - location = locationLine[0].replace('--location ',''); + location = locationLine[0].replace('--location ', ''); } - var myArgs = minimist(line.split(' '), { - boolean: ['sendMetrics','verbose'] - }); + var myArgs = cli.getMinimistArgv(line.split(' ')); // insert the location again if (locationLine) { @@ -113,11 +65,38 @@ } return myArgs; }, + /** + * Read a text file (utf-8) and retunr the content. + * @param {string} filename The file and path to the file to read + * @return {string} the text file + */ readFile: function(filename) { var fullPathToFile = (filename.charAt(0) === path.sep) ? filename : path.join(process.cwd(), - path.sep, filename); + path.sep, filename); return fs.readFileSync(fullPathToFile, 'utf-8'); }, + /** + * Get the file content or the URL for a run. The webpagetest api takes either + * a URL or a file with URLs. + * @param {string} arg The argument to checkif it's a URL or file. + * @param {string} the URL or content of the file + */ + getInputURLorFile: function(arg) { + // is it a file or URL we wanna test? + if (arg.indexOf('http') === -1) { + var fileContent = this.readFile(arg); + return this.replaceWithEnv(fileContent); + } else { + return arg; + } + }, + /** + * Replace all occurrences placeholders matching <%YOUR_KEY> + * with a matching named environment variable. Log error if we + * have placeholders but no matching variables. + * @param {string} text The text to replae + * @return {string} the text with replaced placeholders + */ replaceWithEnv: function(text) { var matches = text.match(/<(.*?)>/g); if (matches) { @@ -128,70 +107,17 @@ text = text.replace(match, process.env[env]); } else { console.error('No ENV set for ' + env + ' the expr ' + match + - ' will not be replaced'); + ' will not be replaced'); } }); } return text; }, - collectMetrics: function(wptJson, userStatus, namespace, argv) { - - var self = this; - var metricsToSend = {}; - - var emulateMobile = argv.emulateMobile; - var firstViewOnly = argv.first; - - var views = ['firstView']; - if (!firstViewOnly) { - views.push('repeatView'); - } - - views.forEach(function(view) { - // if we are missing browser info from WPT (happens when using MotoG at least) - // use a normalized version of the location - // the browser/location can then look like Dulles_MotoG_Motorola_G___Chrome - // but since there are no standard of naming it should be ok to just use what we got - var browser = BROWSERS[wptJson.data.median[view].browser_name] || - wptJson.data.location.replace(/[^A-Za-z_0-9]/g, '_'); - - if (emulateMobile) { - browser += '-emulateMobile'; - } - - // the actual location is the first part of the location string - // separated by either a : or _ - var location = wptJson.data.location.split(/:|_/)[0]; - - var keyStart = namespace + '.' + location + '.' + userStatus + '.' + browser + - '.' + view + '.'; - - self.METRICS.forEach(function(metric) { - metricsToSend[keyStart + metric ] = wptJson.data.median[view][metric] + 'ms'; - }); - - if (wptJson.data.median[view].userTimes) { - Object.keys(wptJson.data.median[view].userTimes).forEach(function(userTiming) { - metricsToSend[keyStart + userTiming ] = - wptJson.data.median[view].userTimes[userTiming] + - 'ms'; - }); - } - - // collect sizes & assets - self.ASSET_TYPES.forEach(function(assetType) { - metricsToSend[keyStart + - assetType + '.requests' ] = wptJson.data.median[view].breakdown[assetType]. - requests + 'g'; - metricsToSend[keyStart + - assetType + '.bytes' ] = wptJson.data.median[view].breakdown[assetType].bytes + 'g'; - }); - - }); - - return metricsToSend; - }, + /** + * Get the current date and time in UTC. + * @return {string} the time with the format YYYY-MM-DD HH.MM + */ getTodaysDate: function() { function addPadding(number) { @@ -204,10 +130,10 @@ var date = new Date(); var month = addPadding(date.getUTCMonth() + 1); - var day = addPadding(date.getUTCDate()); + var day = addPadding(date.getUTCDate()); var hours = addPadding(date.getUTCHours()); var minutes = addPadding(date.getUTCMinutes()); return date.getUTCFullYear() + '-' + month + '-' + day + ' ' + hours + '.' + minutes; - }, + } }; diff --git a/lib/wpt.js b/lib/wpt.js new file mode 100644 index 0000000..2d9d6d2 --- /dev/null +++ b/lib/wpt.js @@ -0,0 +1,34 @@ +/** + * @fileoverview The functionallity to fetch data from WebPageTest. + * @author Peter Hedenskog + * @copyright (c) 2015, Peter Hedenskog <pe...@wikimedia.org>. + * Released under the Apache 2.0 License. + */ + +'use strict'; + +var WebPageTest = require('webpagetest'); + +module.exports = { + run: function(host, key, argv, input, wptOptions, cb) { + var wpt = new WebPageTest(host, key); + + wpt.runTest(input, wptOptions, function(err, data) { + + if (argv.verbose) { + console.log(JSON.stringify(data, null, 1)); + } + + if (err) { + console.error('Couldn\'t fetch data from WebPageTest:' + JSON.stringify(err)); + console.error('Configuration:' + JSON.stringify(wptOptions, null, 2)); + cb(err); + return; + } else { + console.log('WebPageTest run: ' + data.data.summary); + cb(null, data); + } + }); + + } +}; diff --git a/package.json b/package.json index f2dcfb7..5345bf0 100644 --- a/package.json +++ b/package.json @@ -1,29 +1,44 @@ { - "name": "wptstatsv", - "version": "0.0.1", - "bin": "./lib/index.js", - "description": "A thin wrapper for the WebPageTest API that sends metrics to statsv", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "" - }, - "scripts": { - "test": "mocha" - }, - "engines": { - "node": ">=0.10.7" - }, - "devDependencies": { - "mocha": "^2.2.5", - "mocha-jscs": "^2.0.0", - "mocha-jshint": "^2.2.3" - }, - "main": "./lib/index.js", - "dependencies": { - "async": "1.4.2", - "minimist": "1.1.3", - "request": "2.61.0", - "webpagetest": "0.3.3" - } -} + "name": "wpt-reporter", + "version": "0.0.1", + "bin": "./bin/index.js", + "description": "A thin wrapper for the WebPageTest API that report specific metrics as csv/json or send them to Graphite/Statsv", + "keywords": [ + "webpagetest", + "performance", + "reporter", + "graphite", + "csv", + "perfmatters", + "webperf" + ], + "homepage": "https://wikitech.wikimedia.org/wiki/WebPageTest", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://gerrit.wikimedia.org/r/p/performance/WebPageTest.git" + }, + "files": [ + "bin", + "lib" + ], + "scripts": { + "test": "mocha" + }, + "engines": { + "node": ">=0.10.7" + }, + "devDependencies": { + "mocha": "^2.2.5", + "mocha-jscs": "^2.0.0", + "mocha-jshint": "^2.2.3", + "mockery": "1.4.0" + }, + "main": "./lib/index.js", + "dependencies": { + "async": "1.4.2", + "minimist": "1.1.3", + "request": "2.61.0", + "webpagetest": "0.3.3" + } +} \ No newline at end of file diff --git a/scripts/batch/desktop.txt b/scripts/batch/desktop.txt index d5b3ba1..aa6ad0e 100644 --- a/scripts/batch/desktop.txt +++ b/scripts/batch/desktop.txt @@ -10,28 +10,28 @@ # $ WMF_WPT_KEY=SECRET_KEY STATSV_ENDPOINT=http://localhost WPT_RUNS=1 node WMF_WPT_LOCATION=us-west-1 lib/index.js --batch scripts/batch/desktop.txt # Collect metrics using Chrome ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Main_Page https://en.wikipedia.org/wiki/Main_Page +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.Main_Page --reporter statsv https://en.wikipedia.org/wiki/Main_Page ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Facebook https://en.wikipedia.org/wiki/Facebook +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.Facebook --reporter statsv https://en.wikipedia.org/wiki/Facebook ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.test2wiki.signup https://test2.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.test2wiki.anonymous.signup --reporter statsv https://test2.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-base --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.BlankPage https://en.wikipedia.org/wiki/Special:BlankPage +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-base --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.BlankPage --reporter statsv https://en.wikipedia.org/wiki/Special:BlankPage # Collect metrics using Firefox ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Main_Page https://en.wikipedia.org/wiki/Main_Page +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.Main_Page --reporter statsv https://en.wikipedia.org/wiki/Main_Page ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Facebook https://en.wikipedia.org/wiki/Facebook +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.Facebook --reporter statsv https://en.wikipedia.org/wiki/Facebook ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.test2wiki.signup https://test2.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.test2wiki.anonymous.signup --reporter statsv https://test2.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff-base --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.BlankPage https://en.wikipedia.org/wiki/Special:BlankPage +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Firefox --label ff-base --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.BlankPage --reporter statsv https://en.wikipedia.org/wiki/Special:BlankPage # Collect metrics using IE <%WPT_RUNS> (running windows 7 = no SPDY) ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11 --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Main_Page https://en.wikipedia.org/wiki/Main_Page +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11 --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.Main_Page --reporter statsv https://en.wikipedia.org/wiki/Main_Page ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11 --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Facebook https://en.wikipedia.org/wiki/Facebook +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11 --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.Facebook --reporter statsv https://en.wikipedia.org/wiki/Facebook ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11 --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.test2wiki.signup https://test2.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11 --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.test2wiki.anonymous.signup --reporter statsv https://test2.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11-base --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.BlankPage https://en.wikipedia.org/wiki/Special:BlankPage +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>_IE11 --label ie11-base --runs <%WPT_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.BlankPage --reporter statsv https://en.wikipedia.org/wiki/Special:BlankPage diff --git a/scripts/batch/login-desktop.txt b/scripts/batch/login-desktop.txt index 03de767..5c8272c 100644 --- a/scripts/batch/login-desktop.txt +++ b/scripts/batch/login-desktop.txt @@ -13,4 +13,4 @@ # Example (make sure to change WMF_WPT_KEY, and WPT_USER_PASSWORD) # $ WMF_WPT_KEY=SECRET_KEY STATSV_ENDPOINT=http://localhost WPT_RUNS=1 WPT_USER=wptuser WPT_USER_PASSWORD=SECRET_PASSWORD WMF_WPT_LOCATION=us-west-1 node lib/index.js --batch ./scripts/batch/login-desktop.txt ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-authenticated --runs <%WPT_RUNS> --first true --userStatus authenticated --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Facebook ./scripts/wptscripts/login-desktop-enwiki-facebook.txt +--webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --webPageTestHost wpt.wmftest.org --location <%WMF_WPT_LOCATION>:Chrome --label chrome-authenticated --runs <%WPT_RUNS> --first true --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.authenticated.Facebook --reporter statsv ./scripts/wptscripts/login-desktop-enwiki-facebook.txt diff --git a/scripts/batch/login-mobile.txt b/scripts/batch/login-mobile.txt index b228367..119fddc 100644 --- a/scripts/batch/login-mobile.txt +++ b/scripts/batch/login-mobile.txt @@ -13,4 +13,4 @@ # Example (make sure to change WMF_WPT_KEY, and WPT_USER_PASSWORD) # $ WMF_WPT_KEY=SECRET_KEY STATSV_ENDPOINT=http://localhost WPT_RUNS=1 WPT_USER=wptuser WPT_USER_PASSWORD=SECRET_PASSWORD WMF_WPT_LOCATION=us-west-1 node lib/index.js --batch ./scripts/batch/login-mobile.txt ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-mobile-authenticated --runs <%WPT_MOBILE_RUNS> --first true --emulateMobile true --userStatus authenticated --endpoint <%STATSV_ENDPOINT> --sendMetrics --connectivity 3GFast --namespace webpagetest.enwiki-mobile.Facebook ./scripts/wptscripts/login-mobile-enwiki-facebook.txt +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-mobile-authenticated --runs <%WPT_MOBILE_RUNS> --first true --emulateMobile true --endpoint <%STATSV_ENDPOINT> --connectivity 3GFast --namespace webpagetest.enwiki-mobile.authenticated.Facebook --reporter statsv ./scripts/wptscripts/login-mobile-enwiki-facebook.txt diff --git a/scripts/batch/mobile-wpt-org.txt b/scripts/batch/mobile-wpt-org.txt index c233386..e2ae9f3 100644 --- a/scripts/batch/mobile-wpt-org.txt +++ b/scripts/batch/mobile-wpt-org.txt @@ -11,4 +11,4 @@ # nodejs installed), just make sure to change the value of the WebPageTest key: # $ WPT_ORG_WPT_KEY=SECRET_KEY STATSV_ENDPOINT=http://localhost WPT_ORG_MOBILE_RUNS=1 node lib/index.js --batch ./scripts/batch/mobile-wpt-org.txt ---webPageTestKey <%WPT_ORG_WPT_KEY> --webPageTestHost www.webpagetest.org --median SpeedIndex --location Dulles_MotoG:Motorola G - Chrome --label chrome-m --runs <%WPT_ORG_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --connectivity 3GFast --namespace webpagetest.enwiki-mobile.Facebook --timeout 2400 https://en.m.wikipedia.org/wiki/Facebook +--webPageTestKey <%WPT_ORG_WPT_KEY> --webPageTestHost www.webpagetest.org --median SpeedIndex --location Dulles_MotoG:Motorola G - Chrome --label chrome-m --runs <%WPT_ORG_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --connectivity 3GFast --namespace webpagetest.enwiki-mobile.anonymous.Facebook --timeout 2400 --reporter statsv https://en.m.wikipedia.org/wiki/Facebook diff --git a/scripts/batch/mobile.txt b/scripts/batch/mobile.txt index 5044039..3cc84b1 100644 --- a/scripts/batch/mobile.txt +++ b/scripts/batch/mobile.txt @@ -11,10 +11,10 @@ # Example (make sure to change WMF_WPT_KEY) # $ WMF_WPT_KEY=SECRET_KEY STATSV_ENDPOINT=http://localhost WPT_MOBILE_RUNS=1 WMF_WPT_LOCATION=us-west-1 node lib/index.js --batch ./scripts/batch/mobile.txt ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki-mobile.San_Francisco --emulateMobile true --connectivity 3GFast https://en.m.wikipedia.org/wiki/San_Francisco +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki-mobile.anonymous.San_Francisco --emulateMobile true --connectivity 3GFast --reporter statsv https://en.m.wikipedia.org/wiki/San_Francisco ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki-mobile.Facebook --emulateMobile true --connectivity 3GFast https://en.m.wikipedia.org/wiki/Facebook +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki-mobile.anonymous.Facebook --emulateMobile true --connectivity 3GFast --reporter statsv https://en.m.wikipedia.org/wiki/Facebook ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.test2wiki-mobile.signup --emulateMobile true --connectivity 3GFast https://test2.m.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.test2wiki-mobile.anonymous.signup --emulateMobile true --connectivity 3GFast --reporter statsv https://test2.m.wikipedia.org/w/index.php?title=Special:UserLogin&type=signup ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki-mobile.BlankPage --emulateMobile true --connectivity 3GFast https://en.m.wikipedia.org/wiki/Special:BlankPage +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-emulated-m --runs <%WPT_MOBILE_RUNS> --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki-mobile.anonymous.BlankPage --emulateMobile true --connectivity 3GFast --reporter statsv https://en.m.wikipedia.org/wiki/Special:BlankPage diff --git a/scripts/batch/second-view-desktop.txt b/scripts/batch/second-view-desktop.txt index 3822555..8a7e2a5 100644 --- a/scripts/batch/second-view-desktop.txt +++ b/scripts/batch/second-view-desktop.txt @@ -11,4 +11,4 @@ # Example (make sure to change WMF_WPT_KEY) # $ WMF_WPT_KEY=SECRET_KEY STATSV_ENDPOINT=http://localhost WPT_RUNS=1 WMF_WPT_LOCATION=us-west-1 node lib/index.js --batch ./scripts/scripts/second-view-desktop.txt ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-second --runs <%WPT_RUNS> --first true --endpoint <%STATSV_ENDPOINT> --sendMetrics --namespace webpagetest.enwiki.Facebook-second ./scripts/wptscripts/second-view-desktop-enwiki-facebook.txt +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-second --runs <%WPT_RUNS> --first true --endpoint <%STATSV_ENDPOINT> --namespace webpagetest.enwiki.anonymous.Facebook-second --reporter statsv ./scripts/wptscripts/second-view-desktop-enwiki-facebook.txt diff --git a/scripts/batch/second-view-mobile.txt b/scripts/batch/second-view-mobile.txt index 10d7fe6..d6d390f 100644 --- a/scripts/batch/second-view-mobile.txt +++ b/scripts/batch/second-view-mobile.txt @@ -9,4 +9,4 @@ # Example (make sure to change WMF_WPT_KEY) # $ WMF_WPT_KEY=SECRET_KEY STATSV_ENDPOINT=http://localhost WPT_MOBILE_RUNS=1 WMF_WPT_LOCATION=us-west-1 node lib/index.js --batch ./scripts/batch/second-view-mobile.txt ---webPageTestKey <%WMF_WPT_KEY> --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-mobile-second --runs <%WPT_MOBILE_RUNS> --first true --emulateMobile true --endpoint <%STATSV_ENDPOINT> --sendMetrics --connectivity 3GFast --namespace webpagetest.enwiki-mobile.Facebook-second ./scripts/wptscripts/second-view-mobile-enwiki-facebook.txt +--webPageTestKey <%WMF_WPT_KEY> --webPageTestHost wpt.wmftest.org --median SpeedIndex --location <%WMF_WPT_LOCATION>:Chrome --label chrome-mobile-second --runs <%WPT_MOBILE_RUNS> --first true --emulateMobile true --endpoint <%STATSV_ENDPOINT> --connectivity 3GFast --namespace webpagetest.enwiki-mobile.anonymous.Facebook-second --reporter statsv ./scripts/wptscripts/second-view-mobile-enwiki-facebook.txt diff --git a/test/batchTest.js b/test/batchTest.js new file mode 100644 index 0000000..8001214 --- /dev/null +++ b/test/batchTest.js @@ -0,0 +1,36 @@ +var underTest = require('../lib/'); +var mockery = require('mockery'); +var util = require('../lib/util'); +var desktopJson = JSON.parse(util.readFile('test/files/desktop_result.json')); + +var wptMock = { + run: function(host, key, argv, input, wptOptions, cb) { + cb(null,desktopJson); + } +}; + +// +mockery.registerAllowable(underTest); +mockery.registerMock('./wpt', wptMock); + +describe('Test the batch functionality', function() { + before(function() { + mockery.enable({ + useCleanCache: true, + warnOnReplace: false, + warnOnUnregistered: false + }); + }); + it('A batch file should run through cleanly', function() { + var argv = { + batch: 'test/files/batch.txt' + }; + var test = require('../lib/'); + test.runBatch(argv, function() {}); + + }); + + after(function() { + mockery.disable(); + }); +}); diff --git a/test/cliTest.js b/test/cliTest.js index 0b172c6..163f460 100644 --- a/test/cliTest.js +++ b/test/cliTest.js @@ -20,24 +20,10 @@ 'use strict'; -var cli = require('../lib/cli'), -assert = require('assert'); - -// var apa = require("mocha-jscs")(["./lib"]); +var cli = require('../lib/cli'); +var assert = require('assert'); describe('Test cli', function() { - - it('Adding a URL should return a URL', function() { - - var arg = 'https://www.wikipedia.org'; - var value = cli.getInputURLorFile(arg); - assert.deepEqual(value, arg); - }); - - it('Adding a file should return a file', function() { - var arg = 'test/files/scripting.txt'; - var fileContent = cli.getInputURLorFile(arg); - }); it('Missing an URL should tell us input parameters is not ok', function() { var argv = {}; @@ -52,7 +38,7 @@ }); it('Having both WebPageTestKey and a URL should be ok', function() { - var argv = { webPageTestKey: 'thisIsMySuperSecretKey' }; + var argv = { webPageTestKey: 'thisIsMySuperSecretKey', reporter: 'json' }; argv._ = ['https://www.wikipedia.org/']; assert.strictEqual(cli.validateArgs(argv),true); }); diff --git a/test/collectMetricsTest.js b/test/collectMetricsTest.js new file mode 100644 index 0000000..57a1f02 --- /dev/null +++ b/test/collectMetricsTest.js @@ -0,0 +1,74 @@ +var cm = require('../lib/collectMetrics'); +var assert = require('assert'); +var util = require('../lib/util'); +var cli = require('../lib/cli'); +var mobileJson = JSON.parse(util.readFile('test/files/mobile_result.json')); +var desktopJson = JSON.parse(util.readFile('test/files/desktop_result.json')); + +describe('Test colllect metrics', function() { + + it('We should be able to parse a JSON from WebPageTest collecting data from desktop', + function() { + var namespace = 'webpagetest'; + var metrics = cm.collect(desktopJson, cli.getMinimistArgv([])); + Object.keys(metrics).forEach(function(type) { + Object.keys(metrics[type]).forEach(function(key) { + assert.strictEqual(metrics[type][key].toString().indexOf('undefined'), -1, + 'We have an undefined value in ' + key); + }); + }); + + // verify that we collect all the metrics that we want + ['SpeedIndex','render','TTFB','fullyLoaded'].forEach(function(definedMetric) { + var metricIncluded = false; + Object.keys(metrics).forEach(function(type) { + Object.keys(metrics[type]).forEach(function(key) { + if (key.indexOf(definedMetric) > -1) { + metricIncluded = true; + } + }); + }); + assert.strictEqual(metricIncluded, true, 'We are missing metric ' + definedMetric); + }); + + // verify that we collect all the metrics that we want + ['html','js','css','font','flash','other'].forEach(function(definedMetric) { + var metricIncluded = false; + Object.keys(metrics).forEach(function(type) { + Object.keys(metrics[type]).forEach(function(key) { + if (key.indexOf(definedMetric) > -1) { + metricIncluded = true; + } + }); + }); + assert.strictEqual(metricIncluded, true, 'We are missing asset type ' + definedMetric); + }); + }); + + it('We should be able to parse a JSON from WebPageTest collecting data from mobile', + function() { + var namespace = 'webpagetest'; + var metrics = cm.collect(mobileJson, cli.getMinimistArgv([])); + Object.keys(metrics).forEach(function(type) { + Object.keys(metrics[type]).forEach(function(key) { + // verify that we aren't fetching any undefined values = + // values missing in the WPT file + assert.strictEqual(metrics[type][key].toString().indexOf('undefined'), -1, + 'We have an undefined value in ' + key); + }); + }); + + // verify that we collect all the metrics that we want + ['SpeedIndex','render','TTFB','fullyLoaded'].forEach(function(definedMetric) { + var metricIncluded = false; + Object.keys(metrics).forEach(function(type) { + Object.keys(metrics[type]).forEach(function(key) { + if (key.indexOf(definedMetric) > -1) { + metricIncluded = true; + } + }); + }); + assert.strictEqual(metricIncluded, true, 'We are missing metric ' + definedMetric); + }); + }); +}); diff --git a/test/files/batch.txt b/test/files/batch.txt index b705377..747bc50 100644 --- a/test/files/batch.txt +++ b/test/files/batch.txt @@ -1,3 +1,3 @@ ---webPageTestKey <%WMF_WPT_KEY> --runs 15 --location Dulles:Chrome --median SpeedIndex https://en.wikipedia.org/wiki/Facebook ---webPageTestKey <%WMF_WPT_KEY> --runs 31 --location Dulles_MotoG:Motorola G - Chrome --median SpeedIndex https://en.wikipedia.org/wiki/Barack_Obama ---webPageTestKey <%WMF_WPT_KEY> --runs 31 --median SpeedIndex https://en.wikipedia.org/wiki/Barack_Obama +--webPageTestKey <%WMF_WPT_KEY> --runs 15 --location Dulles:Chrome --median SpeedIndex --reporter json https://en.wikipedia.org/wiki/Facebook +--webPageTestKey <%WMF_WPT_KEY> --runs 31 --location Dulles_MotoG:Motorola G - Chrome --median SpeedIndex --reporter json https://en.wikipedia.org/wiki/Barack_Obama +--webPageTestKey <%WMF_WPT_KEY> --runs 31 --median SpeedIndex --reporter json https://en.wikipedia.org/wiki/Barack_Obama diff --git a/test/reporterTest.js b/test/reporterTest.js new file mode 100644 index 0000000..9285b71 --- /dev/null +++ b/test/reporterTest.js @@ -0,0 +1,18 @@ +var reporter = require('../lib/reporter'); +var assert = require('assert'); + +describe('Test reporter modules', function() { + + it('All reporter modules should have the necessary methods', function() { + + Object.keys(reporter.getReporters()).forEach(function(name) { + var mod = reporter.get(name); + assert.strictEqual(typeof mod.validate === 'function', true, + 'The reporter ' + name + ' is missing validate'); + assert.strictEqual(typeof mod.help === 'function', true, + 'The reporter ' + name + ' is missing verify'); + assert.strictEqual(typeof mod.report === 'function', true, + 'The reporter ' + name + ' is missing report'); + }); + }); +}); diff --git a/test/rulethemall.sh b/test/rulethemall.sh new file mode 100755 index 0000000..dc3f596 --- /dev/null +++ b/test/rulethemall.sh @@ -0,0 +1,32 @@ +#!/bin/bash + +# Use this script to test all batch scripts. +# To be able to run it you need to set a couple of env +# variables. Make sure to change the values :) +# +# export WMF_WPT_KEY=OURKEY +# export WPT_ORG_WPT_KEY=THE_KEY_FOR_WEBPAGETEST.ORG +# export WPT_USER=THE_USER_NAME_OF_OUR_USER +# export WPT_USER_PASSWORD=THE_PASSWORD_OF_OUR_USER +# +# Run me from the root folder of the project +# test/rulethemall.sh + +export STATSV_ENDPOINT=http://localhost +export WPT_MOBILE_RUNS=1 +export WPT_RUNS=1 +export WPT_ORG_MOBILE_RUNS=1 +export WMF_WPT_LOCATION=us-west-1 + +[ -z "$WMF_WPT_KEY" ] && echo "Missing the WMF_WPT_KEY" && exit 1; +[ -z "$WPT_ORG_WPT_KEY" ] && echo "Missing the WPT_ORG_WPT_KEY" && exit 1; +[ -z "$WPT_USER" ] && echo "Missing the WPT_USER" && exit 1; +[ -z "$WPT_USER_PASSWORD" ] && echo "Missing the WPT_USER_PASSWORD" && exit 1; + +node bin/index.js --batch scripts/batch/desktop.txt +node bin/index.js --batch scripts/batch/mobile.txt +node bin/index.js --batch scripts/batch/login-mobile.txt +node bin/index.js --batch scripts/batch/login-desktop.txt +node bin/index.js --batch scripts/batch/second-view-desktop.txt +node bin/index.js --batch scripts/batch/second-view-mobile.txt +node bin/index.js --batch scripts/batch/mobile-wpt-org.txt diff --git a/test/utilTest.js b/test/utilTest.js index 07aaf21..f9b442d 100644 --- a/test/utilTest.js +++ b/test/utilTest.js @@ -20,24 +20,32 @@ 'use strict'; var util = require('../lib/util'); var assert = require('assert'); -var mobileJson = JSON.parse(util.readFile('test/files/mobile_result.json')); -var desktopJson = JSON.parse(util.readFile('test/files/desktop_result.json')); var batchScript = util.readFile('test/files/batch.txt'); var minimist = require('minimist'); var eol = require('os').EOL; describe('Test util', function() { + it('Adding a URL should return a URL', function() { + var arg = 'https://www.wikipedia.org'; + var value = util.getInputURLorFile(arg); + assert.deepEqual(value, arg); + }); + + it('Adding a file should return a file', function() { + var arg = 'test/files/scripting.txt'; + var fileContent = util.getInputURLorFile(arg); + }); it('Location field should work with and without spaces and without a location', function() { - var validValues = ['Dulles:Chrome', 'Dulles_MotoG:Motorola G - Chrome', undefined]; - var lines = batchScript.split(eol); + var validValues = ['Dulles:Chrome', 'Dulles_MotoG:Motorola G - Chrome', 'Dulles:Chrome']; + var lines = batchScript.split(eol); - for (var i = 0; i < lines.length; i++) { - if (lines[i].indexOf('#') !== 0 && lines[i].length > 1) { - var myargs = util.convertTextLineToMinimist(lines[i]); - assert.strictEqual(myargs.location, validValues[i]); + for (var i = 0; i < lines.length; i++) { + if (lines[i].indexOf('#') !== 0 && lines[i].length > 1) { + var myargs = util.convertTextLineToMinimist(lines[i]); + assert.strictEqual(myargs.location, validValues[i]); + } } - } }); @@ -52,107 +60,40 @@ assert.deepEqual(wptOptions.location, 'ap-northeast-1_IE10'); assert.deepEqual(wptOptions.connectivity, '3G'); }); - }); - it('There should not be multiple spaces in the WebPageTest options', function() { - var lines = batchScript.split(eol); - for (var i = 0; i < lines.length; i++) { - if (lines[i].indexOf('#') !== 0 && lines[i].length > 1) { - // we don't want double spaces - assert.strictEqual(lines[i].indexOf(' '), -1); - var myargs = util.convertTextLineToMinimist(lines[i]); - // and make sure that the array is only having one - // item (=url or script). - assert.strictEqual(myargs._.length, 1); - } - } - }); + + it('There should not be multiple spaces in the WebPageTest options', function() { + var lines = batchScript.split(eol); + for (var i = 0; i < lines.length; i++) { + if (lines[i].indexOf('#') !== 0 && lines[i].length > 1) { + // we don't want double spaces + assert.strictEqual(lines[i].indexOf(' '), -1); + var myargs = util.convertTextLineToMinimist(lines[i]); + // and make sure that the array is only having one + // item (=url or script). + assert.strictEqual(myargs._.length, 1); + } + } +}); it('Parameters specific for wptstatsv should be cleaned out from WebPageTest options', function() { - var keysToBeRemoved = ['webPageTestKey', 'webPageTestHost', '_', 'verbose', 'userStatus', 'sendMetrics', 'customMetrics', 'namespace']; + var keysToBeRemoved = ['webPageTestKey', 'webPageTestHost', '_', 'verbose', + 'sendMetrics', 'customMetrics', 'namespace']; var args = { - webPageTestKey: 'aSupERSecrEtKey', - webPageTestHost: 'http://www.example.org', - _: ['all', 'extra', 'args'], - verbose: true, - userStatus: 'anonymous', - customMetrics: 'javascript that collects custom metrics', - namespace: 'super.special.namespace' - }; + webPageTestKey: 'aSupERSecrEtKey', + webPageTestHost: 'http://www.example.org', + _: ['all', 'extra', 'args'], + verbose: true, + customMetrics: 'javascript that collects custom metrics', + namespace: 'super.special.namespace' + }; var wptOptions = util.setupWPTOptions(args); keysToBeRemoved.forEach(function(key) { - assert.strictEqual(wptOptions[key], undefined); - }); - }); - - - it('We should be able to parse a JSON from WebPageTest collecting data from mobile', function() { - var userStatus = 'anonymous'; - var namespace = 'webpagetest'; - var metrics = util.collectMetrics(mobileJson, userStatus, namespace, []); - Object.keys(metrics).forEach(function(key) { - // verify that we aren't fetching any undefined values = values missing in the WPT file - assert.strictEqual(metrics[key].toString().indexOf('undefined'), -1, 'We have an undefined value in ' + key); - }); - - // verify that we collect all the metrics that we want - util.METRICS.forEach(function(definedMetric) { - var metricIncluded = false; - var keys = Object.keys(metrics); - for (var i = 0; i < keys.length; i++) { - if (keys[i].indexOf(definedMetric) > -1) { - metricIncluded = true; - } - } - assert.strictEqual(metricIncluded, true, 'We are missing metric ' + definedMetric); - }); - }); - - it('We should be able to parse a JSON from WebPageTest collecting data from desktop', function() { - var userStatus = 'anonymous'; - var namespace = 'webpagetest'; - var metrics = util.collectMetrics(desktopJson, userStatus, namespace, []); - Object.keys(metrics).forEach(function(metricKey) { - assert.strictEqual(metrics[metricKey].toString().indexOf('undefined'), -1, 'We have an undefined value in ' + metricKey); - }); - - // verify that we collect all the metrics that we want - util.METRICS.forEach(function(definedMetric) { - var included = false; - Object.keys(metrics).forEach(function(metric) { - if (metric.indexOf(definedMetric) > -1) { - included = true; - } - }); - - util.ASSET_TYPES.forEach(function(assetType) { - Object.keys(metrics).forEach(function(type) { - if (type.indexOf(assetType) > -1) { - included = true; - } - var userStatus = 'anonymous'; - var namespace = 'webpagetest'; - var metrics = util.collectMetrics(desktopJson, userStatus, namespace, []); - Object.keys(metrics).forEach(function(metric) { - // verify that we aren't fetching any undefined values = values missing in the WPT file - assert.strictEqual(metrics[metric].toString().indexOf('undefined'),-1,'We have an undefined value in ' + metric); - }); - - // verify that we collect all the metrics that we want - util.METRICS.forEach(function(definedMetric) { - var metricIncluded = false; - Object.keys(metrics).forEach(function(metric) { - if (metrics[metric].toString().indexOf(definedMetric) > -1) { - metricIncluded = true; - } - }); - assert.strictEqual(included, true, 'We are missing metric ' + definedMetric); - }); + assert.strictEqual(wptOptions[key], undefined); }); }); - }); it('We should be able to replace ENV variables', function() { @@ -164,6 +105,5 @@ assert.strictEqual(replacedText.match(/VAR1/g).length, 2); assert.strictEqual(replacedText.match(/VAR2/g).length, 1); }); - }); -- To view, visit https://gerrit.wikimedia.org/r/243027 To unsubscribe, visit https://gerrit.wikimedia.org/r/settings Gerrit-MessageType: merged Gerrit-Change-Id: I7bd582a4a180b07b62d6c94552555957baf34ce6 Gerrit-PatchSet: 15 Gerrit-Project: performance/WebPageTest Gerrit-Branch: master Gerrit-Owner: Phedenskog <phedens...@wikimedia.org> Gerrit-Reviewer: Krinkle <krinklem...@gmail.com> Gerrit-Reviewer: Phedenskog <phedens...@wikimedia.org> Gerrit-Reviewer: jenkins-bot <> _______________________________________________ MediaWiki-commits mailing list MediaWiki-commits@lists.wikimedia.org https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits