[
  {
    "path": ".gitignore",
    "content": "node_modules/\r\nbower_components/\r\nbin/\r\nbin-site/\r\n*.log\r\n.DS_Store\n.idea/*\n.tmp/*\ndist/*\n_replicator/*\n_users/*\npouch__all_dbs__/*\nversion/*\nlog.txt\n"
  },
  {
    "path": ".jshintrc",
    "content": "{\r\n  \"curly\": true,\r\n  \"eqeqeq\": true,\r\n  \"immed\": true,\r\n  \"latedef\": true,\r\n  \"newcap\": true,\r\n  \"noarg\": true,\r\n  \"sub\": true,\r\n  \"undef\": true,\r\n  \"boss\": true,\r\n  \"eqnull\": true,\r\n  \"node\": true,\r\n  \"shadow\": true,\r\n  \"expr\": true,\r\n  \"globals\": {\r\n    \"angular\": false,\r\n    \"$\": false,\r\n    \"window\": false,\r\n    \"ENDPOINTS\": false,\r\n    \"describe\": false,\r\n    \"it\": false,\r\n    \"beforeEach\": false,\r\n    \"before\": false,\r\n    \"xit\": false,\r\n    \"xdescribe\": false\r\n  }\r\n}"
  },
  {
    "path": ".npmignore",
    "content": "node_modules/\r\nbower_components/\r\nGruntfile.js\r\ntest/\r\nwww/\r\nbower.json\r\n.jshintrc\r\n.gitignore\r\n"
  },
  {
    "path": ".travis.yml",
    "content": "sudo: false\nlanguage: node_js\nnode_js:\n  - \"0.12\"\n  - \"4.0\"\n  - \"4.3\"\n  - \"4\"\n  - \"5.0\"\n  - \"5\"\n  - \"6\"\n  - \"stable\"\nenv:\n  - NPM_VERSION=2\n  - NPM_VERSION=3\n\nservices: couchdb\n\nbefore_install:\n  - npm install -g npm@$NPM_VERSION\n  - npm install -g grunt-cli\nbefore_script:\n  - npm install\n  - \"export DISPLAY=:99.0\"\n  - \"sh -e /etc/init.d/xvfb start\"\n  - sleep 3 # give xvfb some time to start\nscript: npm test\n"
  },
  {
    "path": "Gruntfile.js",
    "content": "module.exports = function(grunt) {\r\n\r\n\tvar couchdb = require('./test/util').config({\r\n\t\tlog: 1\r\n\t}).couch;\r\n\tvar serveStatic = require('serve-static');\r\n\tvar path = require('path');\r\n\tvar jqplot = [\r\n\t\t'jquery.jqplot.min.js',\r\n\t\t'plugins/jqplot.categoryAxisRenderer.min.js',\r\n\t\t'plugins/jqplot.highlighter.min.js',\r\n\t\t'plugins/jqplot.canvasTextRenderer.min.js',\r\n\t\t'plugins/jqplot.canvasAxisTickRenderer.min.js',\r\n\t\t'plugins/jqplot.canvasAxisLabelRenderer.min.js',\r\n\t\t'plugins/jqplot.barRenderer.min.js',\r\n\t\t'plugins/jqplot.trendline.min.js',\r\n\t\t'plugins/jqplot.pieRenderer.min.js'\r\n\t];\r\n\r\n\tgrunt.initConfig({\r\n\t\tjshint: {\r\n\t\t\tall: [\r\n\t\t\t\t'Gruntfile.js',\r\n\t\t\t\t'lib/*.js',\r\n\t\t\t\t'test/**/*.js',\r\n\t\t\t\t'www/**/*.js'\r\n\t\t\t],\r\n\t\t\toptions: {\r\n\t\t\t\tjshintrc: '.jshintrc'\r\n\t\t\t},\r\n\t\t},\r\n\r\n\t\tmetricsgen: {\r\n\t\t\tfiles: {\r\n\t\t\t\tdest: 'bin-site/metrics.js'\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tuglify: {\r\n\t\t\toptions: {\r\n\t\t\t\tmangle: false,\r\n\t\t\t\tsourceMap: true,\r\n\t\t\t\tsourceMapName: 'bin-site/main.js.map',\r\n\t\t\t},\r\n\t\t\tjs: {\r\n\t\t\t\tfiles: {\r\n\t\t\t\t\t'bin-site/main.js': ['www/**/*.js', 'bin-site/**/*.js']\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tconcat: {\r\n\t\t\tjqplot: {\r\n\t\t\t\tsrc: jqplot.map(function(file) {\r\n\t\t\t\t\treturn 'bower_components/jqplot-bower/dist/' + file;\r\n\t\t\t\t}),\r\n\t\t\t\tdest: 'bin-site/jqplot.js'\r\n\t\t\t},\r\n\t\t\tless: {\r\n\t\t\t\tsrc: ['bower_components/jqplot-bower/dist/jquery.jqplot.min.css', 'www/app/**/*.less', 'www/assets/css/*.css'],\r\n\t\t\t\tdest: 'bin-site/main.less'\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tless: {\r\n\t\t\tdev: {\r\n\t\t\t\tfiles: {\r\n\t\t\t\t\t'bin-site/main.css': 'bin-site/main.less'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tdist: {\r\n\t\t\t\toptions: {\r\n\t\t\t\t\tcompress: true\r\n\t\t\t\t},\r\n\t\t\t\tfiles: {\r\n\t\t\t\t\t'bin-site/main.css': 'bin-site/main.less'\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tautoprefixer: {\r\n\t\t\tless: {\r\n\t\t\t\tsrc: 'bin-site/main.css',\r\n\t\t\t\tdest: 'bin-site/main.css'\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tcopy: {\r\n\t\t\tpartials: {\r\n\t\t\t\texpand: true,\r\n\t\t\t\tcwd: 'www/app',\r\n\t\t\t\tsrc: ['**/*.html'],\r\n\t\t\t\tdest: 'bin-site/app'\r\n\t\t\t},\r\n\t\t\tfonts: {\r\n\t\t\t\texpand: true,\r\n\t\t\t\tcwd: 'www/assets',\r\n\t\t\t\tsrc: ['fonts/*.*'],\r\n\t\t\t\tdest: 'bin-site/assets'\r\n\t\t\t},\r\n\t\t\tendpoints: {\r\n\t\t\t\tsrc: ['www/server/endpoints.js'],\r\n\t\t\t\tdest: 'bin-site/server/endpoints.js'\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tprocesshtml: {\r\n\t\t\tdev: {\r\n\t\t\t\toptions: {\r\n\t\t\t\t\tstrip: true,\r\n\t\t\t\t\tdata: {\r\n\t\t\t\t\t\tscripts: jqplot.map(function(file) {\r\n\t\t\t\t\t\t\treturn 'jqplot-bower/dist/' + file;\r\n\t\t\t\t\t\t}).concat(grunt.file.expand({\r\n\t\t\t\t\t\t\tcwd: 'www'\r\n\t\t\t\t\t\t}, 'app/**/*.js')),\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tfiles: {\r\n\t\t\t\t\t'bin-site/index.html': 'www/index.html'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tdist: {\r\n\t\t\t\toptions: {\r\n\t\t\t\t\tdata: {\r\n\t\t\t\t\t\tscripts: ['main.js']\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tfiles: {\r\n\t\t\t\t\t'bin-site/index.html': 'www/index.html'\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\thtmlmin: {\r\n\t\t\tdist: {\r\n\t\t\t\toptions: {\r\n\t\t\t\t\tremoveComments: true,\r\n\t\t\t\t\tcollapseWhitespace: true,\r\n\t\t\t\t\tconservativeCollapse: true,\r\n\t\t\t\t\tcollapseBooleanAttributes: true\r\n\t\t\t\t},\r\n\t\t\t\tfiles: {\r\n\t\t\t\t\t'bin-site/index.html': 'bin-site/index.html'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tconnect: {\r\n\t\t\tproxies: [{\r\n\t\t\t\tchangeOrigin: false,\r\n\t\t\t\thost: 'localhost',\r\n\t\t\t\tport: '5984',\r\n\t\t\t\tcontext: grunt.file.expand('lib/couch-views/**/*.js').map(function(file) {\r\n\t\t\t\t\treturn '/' + path.basename(file, '.js');\r\n\t\t\t\t}),\r\n\t\t\t\trewrite: (function(files) {\r\n\t\t\t\t\tvar res = {};\r\n\t\t\t\t\tfiles.forEach(function(file) {\r\n\t\t\t\t\t\tvar view = path.basename(file, '.js');\r\n\t\t\t\t\t\tres[view + '/_view'] = ['/', couchdb.database, '/_design/', view, '/_view'].join('');\r\n\t\t\t\t\t});\r\n\t\t\t\t\treturn res;\r\n\t\t\t\t}(grunt.file.expand('lib/couch-views/**/*.js')))\r\n\t\t\t}],\r\n\t\t\tdev: {\r\n\t\t\t\toptions: {\r\n\t\t\t\t\thostname: '*',\r\n\t\t\t\t\tport: 9000,\r\n\t\t\t\t\tbase: ['test/res', 'bower_components', 'bin-site', 'www'],\r\n\t\t\t\t\tlivereload: true,\r\n\t\t\t\t\tmiddleware: function(connect, options) {\r\n\t\t\t\t\t\tvar middlewares = [];\r\n\t\t\t\t\t\tif (!Array.isArray(options.base)) {\r\n\t\t\t\t\t\t\toptions.base = [options.base];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tmiddlewares.push(require('grunt-connect-proxy/lib/utils').proxyRequest);\r\n\t\t\t\t\t\toptions.base.forEach(function(base) {\r\n\t\t\t\t\t\t\tmiddlewares.push(serveStatic(base));\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\treturn middlewares;\r\n\t\t\t\t\t},\r\n\t\t\t\t\tuseAvailablePort: true,\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\toptions: {\r\n\t\t\t\tlivereload: true,\r\n\t\t\t},\r\n\t\t\tviews: {\r\n\t\t\t\tfiles: ['lib/couch-views/*.js'],\r\n\t\t\t\ttasks: ['deployViews']\r\n\t\t\t},\r\n\t\t\tless: {\r\n\t\t\t\tfiles: ['www/**/*.less'],\r\n\t\t\t\ttasks: ['concat:less', 'less:dev', 'autoprefixer']\r\n\t\t\t},\r\n\t\t\thtml: {\r\n\t\t\t\tfiles: ['www/index.html'],\r\n\t\t\t\ttasks: ['processhtml:dev']\r\n\t\t\t},\r\n\t\t\tothers: {\r\n\t\t\t\tfiles: ['www/app/**/*.html', 'www/app/**/*.js'],\r\n\t\t\t\ttasks: []\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tmochaTest: {\r\n\t\t\toptions: {\r\n\t\t\t\treporter: 'dot',\r\n\t\t\t\ttimeout: 1000 * 60 * 10\r\n\t\t\t},\r\n\t\t\tunit: {\r\n\t\t\t\tsrc: ['test/**/*.spec.js'],\r\n\t\t\t}\r\n\t\t},\r\n\t\tclean: {\r\n\t\t\tall: ['bin-site', 'test.log'],\r\n\t\t\tdist: ['bin-site/jqplot.js', 'bin-site/main.less', 'bin-site/metrics.js']\r\n\t\t}\r\n\t});\r\n\r\n\trequire('load-grunt-tasks')(grunt);\r\n\trequire('./tasks/metricsgen')(grunt);\r\n\r\n\tgrunt.registerTask('seedData', function() {\r\n\t\tvar done = this.async();\r\n\t\trequire('./test/seedData')(done, 100);\r\n\t});\r\n\r\n\tgrunt.registerTask('deployViews', function() {\r\n\t\tvar done = this.async();\r\n\t\trequire('./lib/couchViews')(require('./test/util.js').config(), function(err, res) {\r\n\t\t\tconsole.log(err, res);\r\n\t\t\tdone(!err);\r\n\t\t});\r\n\t});\r\n\r\n\tgrunt.registerTask('dev', ['metricsgen', 'concat:less', 'less:dev', 'autoprefixer', 'processhtml:dev', 'configureProxies:server', 'connect:dev', 'watch']);\r\n\tgrunt.registerTask('dist', ['jshint', 'concat', 'metricsgen', 'uglify', 'less:dist', 'autoprefixer', 'copy', 'processhtml:dist', 'htmlmin', 'clean:dist']);\r\n\tgrunt.registerTask('test', ['clean', 'dist', 'mochaTest']);\r\n\r\n\tgrunt.registerTask('default', ['dev']);\r\n};\r\n"
  },
  {
    "path": "README.md",
    "content": "# perfjankie\n\nPerfJankie is a tool to monitor smoothness and responsiveness of websites and Cordova/Hybrid apps over time. It runs performance tests using [browser-perf](http://github.com/axemclion/browser-perf) and saves the results in a CouchDB server. \nIt also has a dashboard that displays graphs of the performance metrics collected over time that you help identify performance trends, or locate a single commit that can slow down a site. \n\nAfter running the tests, navigate to the following url to see the results dashboard. \n\n> http://couchdb.serverl.url/databasename/_design/site/index.html\n\nHere is a [dashboard](http://nparashuram.com/perfslides/perfjankie) created from a [sample project](http://github.com/axemclion/perfslides). \n\n![Perfjankie sample dashboard](http://i.imgur.com/3VO8T4C.png \"A sample dashboard for perfjankie\")\n\n## Why ? \nChecking for performance regressions is hard. Though most modern browsers have excellent performance measurement tools, it is hard for a developer to check these tools for every commit. Just as unit tests check for regressions in functionality, perfjankie will help with checking regressions in browser rendering performance when integrated into systems like Travis or Jenkins. \n\nThe results dashboard \n## Setup\nPerfjankie requires Selenium as the driver to run tests and CouchDB to store the results. Since this is based on browser-perf, look at [setting up browser-perf](https://github.com/axemclion/browser-perf/wiki/Setup-Instructions) for more information. \n\n## Usage\n\nPerfjankie can be used as a node module, from the command line, or as a Grunt task and can be installed from npm using `npm install perfjankie`. \n\n### Node Module\n\nThe API call looks like the following\n\n```javascript\n\nvar perfjankie = require('perfjankie');\nperfjankie({\n  \"url\": \"http://localhost:9000/testpage.html\", // URL of the page that you would like to test.\n\n  /* The next set of values identify the test */\n  name: \"Component or Webpage Name\", // A friendly name for the URL. This is shown as component name in the dashboard\n  suite: \"optional suite name\", // Displayed as the title in the dashboard. Only 1 suite name for all components\n  time: new Date().getTime(), // Used to sort the data when displaying graph. Can be the time when a commit was made\n  run: \"commit#Hash\", // A hash for the commit, displayed in the x-axis in the dashboard\n  repeat: 3, // Run the tests 3 times. Default is 1 time\n\n  /* Identifies where the data and the dashboard are saved */\n  couch: {\n    server: 'http://localhost:5984',    \n    requestOptions : { \"proxy\" : \"http://someproxy\" }, // optional, e.g. useful for http basic auth, see Please check [request] for more information on the defaults. They support features like cookie jar, proxies, ssl, etc.\n    database: 'performance', \n    updateSite: !process.env.CI, // If true, updates the couchApp that shows the dashboard. Set to false in when running Continuous integration, run this the first time using command line. \n    onlyUpdateSite: false // No data to upload, just update the site. Recommended to do from dev box as couchDB instance may require special access to create views.\n  },\n\n  callback: function(err, res) {\n    // The callback function, err is falsy if all of the following happen\n    // 1. Browsers perf tests ran\n    // 2. Data has been saved in couchDB\n    // err is not falsy even if update site fails. \n  },\n\n  /* OPTIONS PASSED TO BROWSER-PERF  */\n  // Properties identifying the test environment */\n  browsers: [{ // This can also be a [\"chrome\", \"firefox\"] or \"chrome,firefox\"\n    browserName: \"chrome\",\n    version: 32,\n    platform: \"Windows 8.1\"\n  }], // See browser perf browser configuration for all options. \n\n  selenium: {\n    hostname: \"ondemand.saucelabs.com\", // or localhost or hub.browserstack.com\n    port: 80,\n  },\n\n  BROWSERSTACK_USERNAME: process.env.BROWSERSTACK_USERNAME, // If using browserStack\n  BROWSERSTACK_KEY: process.env.BROWSERSTACK_KEY, // If using browserStack, this is automatically added to browsers object\n\n  SAUCE_USERNAME: process.env.SAUCE_USERNAME, // If using Saucelabs\n  SAUCE_ACCESSKEY: process.env.SAUCE_ACCESSKEY, // If using Saucelabs\n\n  /* A way to log the information - can be bunyan, or grunt logs. */\n  log: { // Expects the following methods,  \n    fatal: grunt.fail.fatal.bind(grunt.fail),\n    error: grunt.fail.warn.bind(grunt.fail),\n    warn: grunt.log.error.bind(grunt.log),\n    info: grunt.log.ok.bind(grunt.log),\n    debug: grunt.verbose.writeln.bind(grunt.verbose),\n    trace: grunt.log.debug.bind(grunt.log)\n  }\n\n});\n\n```\n\nOther options that can be passed include `preScript`, `actions`, `metrics`, `preScriptFile`, etc. Note that most of these options are similar to the options passed to browser-perf. Refer to the [browser-perf options](https://github.com/axemclion/browser-perf/wiki/Node-Module---API) for a mode detailed explanation. \n\n### Grunt Task\nTo run perfjankie as a Grunt task, simple load task using `grunt.loadNpmTasks('perfjankie');`, define a `perfjankie` task and pass in all the options from above as options to the Grunt task. [Here](https://github.com/axemclion/perfslides/blob/38b4f6e246c5ab971ce2957ec78bb701dbbc3038/Gruntfile.js#L57) is an example. \n\n### Command line\nRun `perfjankie --help` to see a list of all the options. \nQuick Note - to only update site the first time, run the following from the command line. You need to quote the URL to work with parameters, e.g. https://www.google.de/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=angular\n\n```bash\n$ perfjankie --config-file=local.config.json --only-update-site 'example.com'\n```\nOr without a config file\n\n```bash\n$ perfjankie --couch-server=http://localhost:5984 --couch-database=perfjankie-test --couch-user=admin_user --couch-pwd=admin_pass --name=Google 'https://www.google.de/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=angular'\n```\n\nThe config file can contain server configuration and can look like [this](https://github.com/axemclion/perfjankie/blob/master/test/res/local.config.json). \n\n## Hosting dashboard on a different server\nYou can also host the HTML/CSS/JS for displaying the results dashboard on not on CouchDB, but a different static server, possibly behind a CDN. In such cases, \n1. Use the npm module and host the contents of the `site` folder. \n2. Open index.html and insert the following snippet in the `<head>` section\n\n```html\n<script type=\"text/javascript\">window.DB_BASE=\"http://couchdb.server.url/databasename/_design\";</script>\n```\n\nThis will ensure that all requests for data are made to the other CouchDB server. Also ensure that the CouchDB server has CORS turned on. \n\n## Login before running tests\n\nYou can login a user, or perform other kinds of page setup using the [preScript](https://github.com/axemclion/browser-perf/wiki/Node-Module---API#prescript) or the [preScriptFile](https://github.com/axemclion/browser-perf/wiki/Node-Module---API#prescriptfile) options. Here is an [example](https://github.com/axemclion/browser-perf/wiki/FAQ#how-can-i-test-a-page-that-requires-login) of a login action that can be passed in the preScript option. \n\n## Migrating data from older versions \nIf you have older data and want to move to the latest release of perfjankie, you may also have to migrate your data. You can migrate from older version of a database to a newer version using \n\n```bash\n$ perfjankie --config-file=local.config.json --migrate=newDatabaseName\n```\n\nThis simply transforms all the old data into a format that will work with the newer version of perfjankie. Your version of the database is stored under a document called `version`, and the version supported by your installed version of perfjankie is the key `dbVersion` in the `package.json`\n\n## What does it measure? \n\nPerfjankie measures page rendering times. It collects metrics like frame times, page load time, first paint time, scroll time, etc. It can be used on \n* long, scrollable web pages (like a search result page, an article page, etc). The impact of changes to CSS, sticky headers and scrolling event handlers can be seen in the results. \n* components (like bootstrap, jQuery UI components, ReactJS components, AngularJS components, etc). Component developers just have to place the component multiple times on a page and will know if they caused perf regressions as they continue developing the component. \nFor more information, see the documentation for [browser-perf](http://github.com/axemclion/browser-perf)\n\n# Development\n\n## Dev setup\n\nAny changes should be verified with unit tests, see `test`-folder.\nTo run the tests you local couchdb installed with a database, see `test/res/local.config.json` for details:\n\n  1. start couchdb\n  2. start a local selenium grd: `java -jar node_modules/selenium-server/lib/runner/selenium-server-standalone-2.53.0.jar  -Dwebdriver.chrome.driver=$(pwd)/chromedriver/lib/chromedriver/chromedriver`\n  3. run tests via `npm test`"
  },
  {
    "path": "bower.json",
    "content": "{\n  \"name\": \"perfjankie\",\n  \"main\": \"index.js\",\n  \"version\": \"0.0.0\",\n  \"homepage\": \"https://github.com/axemclion/perfjankie\",\n  \"authors\": [\n    \"Parashuram <code@r.nparashuram.com>\"\n  ],\n  \"description\": \"Website for Perfjankie\",\n  \"license\": \"MIT\",\n  \"private\": true,\n  \"ignore\": [\n    \"**/.*\",\n    \"node_modules\",\n    \"bower_components\",\n    \"test\",\n    \"tests\"\n  ],\n  \"dependencies\": {\n    \"angular\": \"~1.2.18\",\n    \"bootstrap\": \"~3.1.1\",\n    \"jqplot-bower\": \"~1.0.8\",\n    \"jquery\": \"~2.1.1\",\n    \"angular-route\": \"~1.2.25\"\n  }\n}\n"
  },
  {
    "path": "lib/cli.js",
    "content": "#!/usr/bin/env node\n\nvar program = require('commander'),\n\tfs = require('fs');\n\nprogram\n\t.version('0.0.1')\n\t.option('-c --config-file <configFile>', 'Specify a configuration file. If other options are specified, they have precedence over options in config file')\n\t.option('-s, --selenium <serverURL>', 'Specify Selenium Server, like localhost:4444 or ondemand.saucelabs.com:80', 'localhost:4444')\n\t.option('-u --username <username>', 'Sauce, BrowserStack or Selenium User Name')\n\t.option('-a --accesskey <accesskey>', 'Sauce, BrowserStack or Selenium Access Key')\n\t.option('--browsers <browsers>', 'List of browsers to run the tests on')\n\t.option('--couch-server <couchServer>', 'Location of the couchDB server')\n\t.option('--couch-database <couchDatabase>', 'Name of the couch database')\n\t.option('--couch-user <couchUser>', 'Username of the couch user that can create design documents and save data')\n\t.option('--couch-pwd <couchPwd>', 'Password of the couchDB user')\n\t.option('--name <name>', 'A friendly name for the URL. This is shown as component name in the dashboard')\n\t.option('--run <run>', 'A hash for the commit, or any identifier displayed in the x-axis in the dashboard')\n\t.option('--time <time>', 'Used to sort the data when displaying graph. Can be the time or a sequence number when a commit was made', new Date().getTime())\n\t.option('--suite <suite>', 'Displayed as the title in the dashboard.')\n\t.option('--update-site', 'Update the site in addition to running the tests', true)\n\t.option('--only-update-site', 'Only update the site, do not run tests or save data for the site', false)\n\t.option('--migrate <newDbName>', 'Migrate Database to the latest version')\n\t.parse(process.argv);\n\nvar config = {};\nif (program.configFile) {\n\ttry {\n\t\tvar config = JSON.parse(fs.readFileSync(program.configFile));\n\t} catch (e) {\n\t\tthrow e;\n\t}\n}\n\nvar extend = function(obj1, obj2) {\n\tfor (var key in obj2) {\n\t\tif (typeof obj1[key] !== 'undefined' && typeof obj2[key] === 'object') {\n\t\t\tobj1[key] = extend(obj1[key], obj2[key]);\n\t\t} else if (typeof obj2[key] !== 'undefined') {\n\t\t\tobj1[key] = obj2[key];\n\t\t}\n\t}\n\treturn obj1;\n};\n\nconfig = extend(config, {\n\turl: program.args[0],\n\tname: program.name,\n\tsuite: program.suite,\n\ttime: program.time,\n\trun: program.run,\n\n\tselenium: program.serverURL,\n\tbrowsers: program.browsers,\n\tusername: program.username,\n\taccesskey: program.accesskey,\n\n\tlog: {\n\t\t'fatal': console.error.bind(console),\n\t\t'error': console.error.bind(console),\n\t\t'warn': console.warn.bind(console),\n\t\t'info': console.info.bind(console),\n\t\t'debug': console.log.bind(console),\n\t\t'trace': console.log.bind(console)\n\t},\n\n\tcouch: {\n\t\tserver: program.couchServer,\n\t\tdatabase: program.couchDatabase,\n\t\tupdateSite: program.onlyUpdateSite ? true : program.updateSite,\n\t\tonlyUpdateSite: program.onlyUpdateSite,\n\t\tusername: program.couchUser,\n\t\tpwd: program.couchPwd\n\t},\n\n\tcallback: function(err, res) {\n\t\tconsole.log(err, res);\n\t}\n});\n\nif (program.migrate) {\n\tconsole.log('Runnung database migrations');\n\trequire('../migrations/index.js')(config, program.migrate).done();\n} else {\n\trequire('./')(config);\n}"
  },
  {
    "path": "lib/couch-views/metrics_data.js",
    "content": "{\r\n\t_id: \"_design/metrics_data\",\r\n\tlanguage: \"javascript\",\r\n\tviews: {\r\n\t\tstats: {\r\n\t\t\treduce: '_stats',\r\n\t\t\tmap: function(doc) {\r\n\t\t\t\tif (doc.type === 'perfData') {\r\n\t\t\t\t\tfor (var key in doc.data) {\r\n\t\t\t\t\t\tif (typeof doc.data[key] === 'number') {\r\n\t\t\t\t\t\t\temit([doc.browser, doc.name, key, doc.time, doc.run], doc.data[key]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "lib/couch-views/pagelist.js",
    "content": "{\r\n\t_id: \"_design/pagelist\",\r\n\tlanguage: \"javascript\",\r\n\tviews: {\r\n\t\tpages: {\r\n\t\t\treduce: \"_count\",\r\n\t\t\tmap: function(doc) {\r\n\t\t\t\temit([doc.suite, doc.name, doc.meta._browserName], null);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "lib/couch-views/runs.js",
    "content": "{\n\t_id: \"_design/runs\",\n\tlanguage: \"javascript\",\n\tviews: {\n\t\tlist: {\n\t\t\tmap: function(doc) {\n\t\t\t\temit([doc.browser, doc.name, doc.time, doc.run], null);\n\t\t\t},\n\t\t\treduce: \"_count\"\n\t\t},\n\t\tdata: {\n\t\t\tmap: function(doc) {\n\t\t\t\tif (doc.type === 'perfData') {\n\t\t\t\t\tfor (var key in doc.data) {\n\t\t\t\t\t\tif (typeof doc.data[key] === 'number') {\n\t\t\t\t\t\t\temit([doc.browser, doc.name, doc.time, doc.run, key], doc.data[key]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\treduce: \"_stats\"\n\t\t}\n\t}\n}"
  },
  {
    "path": "lib/couchData.js",
    "content": "module.exports = function (config, data) {\r\n    var server = require('./utils').getCouchDB(config.couch),\r\n        Q = require('q'),\r\n        dfd = Q.defer();\r\n\r\n    var db = null,\r\n        log = config.log;\r\n\r\n    db = server.use(config.couch.database);\r\n    log.debug('Saving data');\r\n\r\n    if (typeof data === 'undefined') {\r\n        return;\r\n    }\r\n\r\n    db.bulk({\r\n        docs: data.map(function (val) {\r\n            var res = {\r\n                url: config.url,\r\n                data: val,\r\n                meta: {},\r\n                name: config.name,\r\n                suite: config.suite,\r\n                browser: val._browser || val._browserName,\r\n                run: config.run || config.time,\r\n                time: config.time,\r\n                type: 'perfData'\r\n            };\r\n            for (var key in res.data) {\r\n                if (key.indexOf('_') === 0) {\r\n                    res.meta[key] = res.data[key];\r\n                    delete res.data[key];\r\n                }\r\n            }\r\n            return res;\r\n        })\r\n    }, {\r\n        new_edits: true\r\n    }, function (err, res) {\r\n        log.debug('Got result back after saving data');\r\n        err ? dfd.reject(err) : dfd.resolve(res);\r\n    });\r\n\r\n    return dfd.promise;\r\n};\r\n"
  },
  {
    "path": "lib/couchSite.js",
    "content": "var url = require('url');\r\nmodule.exports = function (config) {\r\n\r\n    var Q = require('q');\r\n\r\n    if (!config.couch.updateSite) {\r\n        return Q(1); // jshint ignore:line\r\n    }\r\n\r\n    var path = require('path'),\r\n        log = config.log;\r\n\r\n    var siteDest = path.join(__dirname, '../bin-site'),\r\n        server = require('./utils').getCouchDB(config.couch),\r\n        db = server.use(config.couch.database);\r\n\r\n    var mime = require('./mime');\r\n\r\n    function contentType(filename) {\r\n        var contentType = path.extname(filename);\r\n        if (contentType.length > 0) {\r\n            return mime[contentType.substring(1)];\r\n        } else {\r\n            return 'text';\r\n        }\r\n    }\r\n\r\n    function readFileContents(files, siteDest) {\r\n        var fs = require('fs'),\r\n            path = require('path');\r\n\r\n        var dfd = Q.defer();\r\n        var fileContents = [];\r\n\r\n        (function readFile(i) {\r\n            if (i < files.length) {\r\n                fs.readFile(path.join(siteDest, files[i]), function (err, data) {\r\n                    if (!err) {\r\n                        fileContents.push({\r\n                            name: files[i],\r\n                            data: data,\r\n                            contentType: contentType(files[i]),\r\n                            content_type: contentType(files[i])\r\n                        });\r\n                    }\r\n                    readFile(i + 1);\r\n                });\r\n            } else {\r\n                log.debug('Completed reading all files');\r\n                dfd.resolve(fileContents);\r\n            }\r\n        }(0));\r\n\r\n        return dfd.promise;\r\n    }\r\n\r\n    function removeSite() {\r\n        var dfd = Q.defer();\r\n        db.get('_design/site', function (err, res) {\r\n            if (!err) {\r\n                db.destroy('_design/site', res._rev, function (err, res) {\r\n                    if (err) {\r\n                        dfd.reject(err);\r\n                    } else {\r\n                        dfd.resolve();\r\n                    }\r\n                });\r\n            } else {\r\n                dfd.resolve();\r\n            }\r\n        });\r\n        return dfd.promise;\r\n    }\r\n\r\n    return removeSite().then(function () {\r\n        return Q.nfcall(require('glob'), '**/*.*', {\r\n            cwd: siteDest\r\n        });\r\n    }).then(function (files) {\r\n        return readFileContents(files, siteDest);\r\n    }).then(function (fileContents) {\r\n        return Q.ninvoke(db.multipart, 'insert', {}, fileContents, '_design/site');\r\n    }).then(function () {\r\n        var link = url.parse([config.couch.server, config.couch.database, '_design/site/index.html'].join('/'));\r\n        link.auth = null;\r\n        log.info('Site Updated. View graphs at ' + url.format(link));\r\n    });\r\n};\r\n"
  },
  {
    "path": "lib/couchViews.js",
    "content": "/* jshint evil: true*/\r\nvar Q = require('q');\r\n\r\nfunction uploadViews(db, log) {\r\n    var dfd = Q.defer();\r\n\r\n    var fs = require('fs'),\r\n        glob = require('glob').sync;\r\n\r\n    var views = glob(__dirname + '/couch-views/*.js');\r\n\r\n    log.debug('Starting to upload views');\r\n    (function uploadView(i) {\r\n        if (i < views.length) {\r\n            log.debug('Checking View ' + views[i]);\r\n            var view = JSON.parse(JSON.stringify(eval('_x_ = ' + fs.readFileSync(views[i], 'utf-8')), function (key, val) {\r\n                if (typeof val === 'function') {\r\n                    return val.toString();\r\n                }\r\n                return val;\r\n            }));\r\n            db.get(view._id, function (err, res) {\r\n                if (!err) {\r\n                    view._rev = res._rev;\r\n                }\r\n                db.insert(view, function (err, res) {\r\n                    uploadView(i + 1);\r\n                });\r\n            });\r\n        } else {\r\n            log.debug('All views updated');\r\n            dfd.resolve();\r\n        }\r\n    }(0));\r\n\r\n    return dfd.promise;\r\n}\r\n\r\nmodule.exports = function (config) {\r\n    if (!config.couch.updateSite) {\r\n        return Q(); // jshint ignore:line\r\n    }\r\n\r\n    var log = config.log,\r\n        server = require('./utils').getCouchDB(config.couch),\r\n        db = server.use(config.couch.database);\r\n\r\n    return uploadViews(db, log);\r\n};"
  },
  {
    "path": "lib/index.js",
    "content": "var Q = require('q');\r\n\r\nvar init = require('./init'),\r\n\tsite = require('./couchSite'),\r\n\tviews = require('./couchViews'),\r\n\tdata = require('./couchData'),\r\n\tperf = require('./perfTests');\r\n\r\nfunction runTests(config) {\r\n\tvar dfd = Q.defer();\r\n\r\n\t(function next(i) {\r\n\t\tif (i < config.repeat) {\r\n\t\t\tperf(config).then(function(results) {\r\n\t\t\t\treturn data(config, results);\r\n\t\t\t}).then(function() {\r\n\t\t\t\tnext(i + 1);\r\n\t\t\t}, function(err) {\r\n\t\t\t\tdfd.reject(err);\r\n\t\t\t}).done();\r\n\t\t} else {\r\n\t\t\tdfd.resolve();\r\n\t\t}\r\n\t}(0));\r\n\treturn dfd.promise;\r\n}\r\n\r\nmodule.exports = function(config) {\r\n\tvar options = require('./options')(config),\r\n\t\tlog = options.log,\r\n\t\tcb = options.callback;\r\n\r\n\tlog.info('Starting PerfJankie');\r\n\r\n\tinit(config).then(function() {\r\n\t\treturn runTests(config);\r\n\t}).then(function() {\r\n\t\treturn Q.allSettled([site(config), views(config)]);\r\n\t}).then(function(res) {\r\n\t\tlog.debug('Successfully done all tasks');\r\n\t\tcb(null, res);\r\n\t}, function(err) {\r\n\t\tlog.debug(err);\r\n\t\tcb(err, null);\r\n\t}).done();\r\n};"
  },
  {
    "path": "lib/init.js",
    "content": "module.exports = function (config) {\r\n    var Q = require('q'),\r\n        server = require('./utils').getCouchDB(config.couch),\r\n        log = config.log;\r\n\r\n    log.debug('Trying to see if the database exists');\r\n\r\n    return Q.ninvoke(server.db, 'get', config.couch.database).catch(function (err) {\r\n        log.debug('Could not find database: %s\\nCreating a new database %s', err.reason, config.couch.database);\r\n        return Q.ninvoke(server.db, 'create', config.couch.database);\r\n    }).then(function () {\r\n        var db = server.use(config.couch.database);\r\n        return Q.ninvoke(db, 'get', 'version').catch(function (err) {\r\n            return Q.ninvoke(db, 'insert', {\r\n                version: require('../package.json').dbVersion\r\n            }, 'version');\r\n        });\r\n    });\r\n};\r\n"
  },
  {
    "path": "lib/mime.js",
    "content": "// from http://github.com/felixge/node-paperboy\r\nmodule.exports = {\r\n  \"aiff\": \"audio/x-aiff\",\r\n  \"appcache\": \"text/cache-manifest\",\r\n  \"arj\": \"application/x-arj-compressed\",\r\n  \"asf\": \"video/x-ms-asf\",\r\n  \"asx\": \"video/x-ms-asx\",\r\n  \"au\": \"audio/ulaw\",\r\n  \"avi\": \"video/x-msvideo\",\r\n  \"bcpio\": \"application/x-bcpio\",\r\n  \"ccad\": \"application/clariscad\",\r\n  \"cod\": \"application/vnd.rim.cod\",\r\n  \"com\": \"application/x-msdos-program\",\r\n  \"cpio\": \"application/x-cpio\",\r\n  \"cpt\": \"application/mac-compactpro\",\r\n  \"csh\": \"application/x-csh\",\r\n  \"css\": \"text/css\",\r\n  \"deb\": \"application/x-debian-package\",\r\n  \"dl\": \"video/dl\",\r\n  \"doc\": \"application/msword\",\r\n  \"drw\": \"application/drafting\",\r\n  \"dvi\": \"application/x-dvi\",\r\n  \"dwg\": \"application/acad\",\r\n  \"dxf\": \"application/dxf\",\r\n  \"dxr\": \"application/x-director\",\r\n  \"etx\": \"text/x-setext\",\r\n  \"ez\": \"application/andrew-inset\",\r\n  \"fli\": \"video/x-fli\",\r\n  \"flv\": \"video/x-flv\",\r\n  \"gif\": \"image/gif\",\r\n  \"gl\": \"video/gl\",\r\n  \"gtar\": \"application/x-gtar\",\r\n  \"gz\": \"application/x-gzip\",\r\n  \"hdf\": \"application/x-hdf\",\r\n  \"hqx\": \"application/mac-binhex40\",\r\n  \"html\": \"text/html\",\r\n  \"ice\": \"x-conference/x-cooltalk\",\r\n  \"ico\": \"image/x-icon\",\r\n  \"ief\": \"image/ief\",\r\n  \"igs\": \"model/iges\",\r\n  \"ips\": \"application/x-ipscript\",\r\n  \"ipx\": \"application/x-ipix\",\r\n  \"jad\": \"text/vnd.sun.j2me.app-descriptor\",\r\n  \"jar\": \"application/java-archive\",\r\n  \"jpeg\": \"image/jpeg\",\r\n  \"jpg\": \"image/jpeg\",\r\n  \"js\": \"text/javascript\",\r\n  \"json\": \"application/json\",\r\n  \"latex\": \"application/x-latex\",\r\n  \"lsp\": \"application/x-lisp\",\r\n  \"lzh\": \"application/octet-stream\",\r\n  \"m\": \"text/plain\",\r\n  \"m3u\": \"audio/x-mpegurl\",\r\n  \"man\": \"application/x-troff-man\",\r\n  \"me\": \"application/x-troff-me\",\r\n  \"midi\": \"audio/midi\",\r\n  \"mif\": \"application/x-mif\",\r\n  \"mime\": \"www/mime\",\r\n  \"movie\": \"video/x-sgi-movie\",\r\n  \"mustache\": \"text/plain\",\r\n  \"mp4\": \"video/mp4\",\r\n  \"mpg\": \"video/mpeg\",\r\n  \"mpga\": \"audio/mpeg\",\r\n  \"ms\": \"application/x-troff-ms\",\r\n  \"nc\": \"application/x-netcdf\",\r\n  \"oda\": \"application/oda\",\r\n  \"ogm\": \"application/ogg\",\r\n  \"pbm\": \"image/x-portable-bitmap\",\r\n  \"pdf\": \"application/pdf\",\r\n  \"pgm\": \"image/x-portable-graymap\",\r\n  \"pgn\": \"application/x-chess-pgn\",\r\n  \"pgp\": \"application/pgp\",\r\n  \"pm\": \"application/x-perl\",\r\n  \"png\": \"image/png\",\r\n  \"pnm\": \"image/x-portable-anymap\",\r\n  \"ppm\": \"image/x-portable-pixmap\",\r\n  \"ppz\": \"application/vnd.ms-powerpoint\",\r\n  \"pre\": \"application/x-freelance\",\r\n  \"prt\": \"application/pro_eng\",\r\n  \"ps\": \"application/postscript\",\r\n  \"qt\": \"video/quicktime\",\r\n  \"ra\": \"audio/x-realaudio\",\r\n  \"rar\": \"application/x-rar-compressed\",\r\n  \"ras\": \"image/x-cmu-raster\",\r\n  \"rgb\": \"image/x-rgb\",\r\n  \"rm\": \"audio/x-pn-realaudio\",\r\n  \"rpm\": \"audio/x-pn-realaudio-plugin\",\r\n  \"rtf\": \"text/rtf\",\r\n  \"rtx\": \"text/richtext\",\r\n  \"scm\": \"application/x-lotusscreencam\",\r\n  \"set\": \"application/set\",\r\n  \"sgml\": \"text/sgml\",\r\n  \"sh\": \"application/x-sh\",\r\n  \"shar\": \"application/x-shar\",\r\n  \"silo\": \"model/mesh\",\r\n  \"sit\": \"application/x-stuffit\",\r\n  \"skt\": \"application/x-koan\",\r\n  \"smil\": \"application/smil\",\r\n  \"snd\": \"audio/basic\",\r\n  \"sol\": \"application/solids\",\r\n  \"spl\": \"application/x-futuresplash\",\r\n  \"src\": \"application/x-wais-source\",\r\n  \"stl\": \"application/SLA\",\r\n  \"stp\": \"application/STEP\",\r\n  \"sv4cpio\": \"application/x-sv4cpio\",\r\n  \"sv4crc\": \"application/x-sv4crc\",\r\n  \"svg\": \"image/svg+xml\",\r\n  \"swf\": \"application/x-shockwave-flash\",\r\n  \"tar\": \"application/x-tar\",\r\n  \"tcl\": \"application/x-tcl\",\r\n  \"tex\": \"application/x-tex\",\r\n  \"texinfo\": \"application/x-texinfo\",\r\n  \"tgz\": \"application/x-tar-gz\",\r\n  \"tiff\": \"image/tiff\",\r\n  \"tr\": \"application/x-troff\",\r\n  \"tsi\": \"audio/TSP-audio\",\r\n  \"tsp\": \"application/dsptype\",\r\n  \"tsv\": \"text/tab-separated-values\",\r\n  \"unv\": \"application/i-deas\",\r\n  \"ustar\": \"application/x-ustar\",\r\n  \"vcd\": \"application/x-cdlink\",\r\n  \"vda\": \"application/vda\",\r\n  \"vivo\": \"video/vnd.vivo\",\r\n  \"vrm\": \"x-world/x-vrml\",\r\n  \"wav\": \"audio/x-wav\",\r\n  \"wax\": \"audio/x-ms-wax\",\r\n  \"wma\": \"audio/x-ms-wma\",\r\n  \"wmv\": \"video/x-ms-wmv\",\r\n  \"wmx\": \"video/x-ms-wmx\",\r\n  \"wrl\": \"model/vrml\",\r\n  \"wvx\": \"video/x-ms-wvx\",\r\n  \"xbm\": \"image/x-xbitmap\",\r\n  \"xlw\": \"application/vnd.ms-excel\",\r\n  \"xml\": \"text/xml\",\r\n  \"xpm\": \"image/x-xpixmap\",\r\n  \"xwd\": \"image/x-xwindowdump\",\r\n  \"xyz\": \"chemical/x-pdb\",\r\n  \"zip\": \"application/zip\"\r\n};"
  },
  {
    "path": "lib/options.js",
    "content": "module.exports = function(config) {\r\n\tvar noop = function() {};\r\n\tconfig.log = config.log || config.logger || noop;\r\n\r\n\tif (typeof config.log === 'function') {\r\n\t\tconfig.log = {\r\n\t\t\t'fatal': config.log,\r\n\t\t\t'error': config.log,\r\n\t\t\t'warn': config.log,\r\n\t\t\t'info': config.log,\r\n\t\t\t'debug': config.log,\r\n\t\t\t'trace': config.log\r\n\t\t};\r\n\t}\r\n\r\n\tfunction assert(expr, msg) {\r\n\t\tif (!expr) {\r\n\t\t\tthrow new Error(msg);\r\n\t\t}\r\n\t}\r\n\tconfig.selenium = config.selenium || {\r\n\t\thostname: 'localhost',\r\n\t\tport: 4444\r\n\t};\r\n\tconfig.repeat = config.repeat || 1;\r\n\tconfig.name = config.name || config.url;\r\n\tconfig.suite = config.suite || 'Default Test Suite';\r\n\r\n\tif (typeof config.callback !== 'function') {\r\n\t\tconfig.callback = noop;\r\n\t}\r\n\r\n\tif (typeof config.couch === 'undefined') {\r\n\t\tconfig.couch = {};\r\n\t}\r\n\r\n\tif (typeof config.couch.username !== 'undefined') {\r\n\t\tvar url = require('url');\r\n\t\tvar href = url.parse(config.couch.server);\r\n\t\thref.auth = config.couch.username + ':' + config.couch.pwd;\r\n\t\tconfig.couch.server = url.format(href);\r\n\t}\r\n\tassert(typeof config.couch.server !== 'undefined', 'Location to save results is not defined. Please define a couchDB server');\r\n\r\n\tconfig.couch.database = config.couch.db || config.couch.database;\r\n\tassert(typeof config.couch.server !== 'undefined', 'Location to save results is not defined. Please define a Database to save the results');\r\n\treturn config;\r\n};"
  },
  {
    "path": "lib/perfTests.js",
    "content": "module.exports = function(config) {\r\n\tvar Q = require('q'),\r\n\t\tdfd = Q.defer();\r\n\r\n\tif (config.couch.onlyUpdateSite) {\r\n\t\tdfd.resolve();\r\n\t} else {\r\n\t\tvar browserPerf = config.browserPerf || require('browser-perf'),\r\n\t\t\tlog = config.log;\r\n\r\n\t\tlog.debug('Starting Browser Perf');\r\n\t\tbrowserPerf(config.url, function(err, results) {\r\n\t\t\tif (err) {\r\n\t\t\t\tdfd.reject(err);\r\n\t\t\t} else {\r\n\t\t\t\tlog.debug('Got Browser Perf results back, now saving the results');\r\n\t\t\t\tdfd.resolve(results);\r\n\t\t\t}\r\n\t\t}, {\r\n\t\t\tbrowsers: config.browsers,\r\n\t\t\tselenium: config.selenium,\r\n\t\t\tdebugBrowser: config.debug,\r\n\t\t\tpreScript: config.preScript,\r\n\t\t\tpreScriptFile: config.preScriptFile,\r\n\t\t\tactions: config.actions,\r\n\t\t\tmetrics: config.metrics,\r\n\t\t\tSAUCE_ACCESSKEY: config.SAUCE_ACCESSKEY || undefined,\r\n\t\t\tSAUCE_USERNAME: config.SAUCE_USERNAME || undefined,\r\n\t\t\tBROWSERSTACK_USERNAME: config.BROWSERSTACK_USERNAME || undefined,\r\n\t\t\tBROWSERSTACK_KEY: config.BROWSERSTACK_KEY || undefined\r\n\t\t});\r\n\t}\r\n\treturn dfd.promise;\r\n};\r\n"
  },
  {
    "path": "lib/utils.js",
    "content": "var nano = require('nano');\n\n\nvar getCouchDB = function (options) {\n    var serverUrl = options.server,\n        server;\n    if (options.requestOptions) {\n        server = nano({\n            \"url\": serverUrl,\n            \"parseUrl\": false,\n            \"requestDefaults\": options.requestOptions\n        });\n    } else {\n        server = nano({\n            \"url\": serverUrl,\n            \"parseUrl\": false\n        });\n    }\n    return server;\n};\n\nmodule.exports = {\n    getCouchDB: getCouchDB\n};"
  },
  {
    "path": "migrations/cli.js",
    "content": "#!/usr/bin/env node\n\nvar program = require('commander'),\n\tfs = require('fs');\n\nvar oldDB, newDB;\nprogram\n\t.version('0.0.1')\n\t.option('-c --couchServer', 'Location of the couchDB server')\n\t.option('-u --username <username>', 'Username of the couch user that can create design documents and save data')\n\t.option('-p --password <password>', 'Password of the couchDB user')\n\t.parse(process.argv);\n\nprogram.on('--help', function() {\n\tconsole.log('Usage:');\n\tconsole.log('');\n\tconsole.log('    $ perfjankie-dbmigrate old-database new-database [options]');\n\tconsole.log('');\n});\n\nprogram.parse(process.argv);\nif (program.args.length !== 2) {\n\tprogram.help();\n}\n\nvar config = {\n\tlog: {\n\t\t'fatal': console.error.bind(console),\n\t\t'error': console.error.bind(console),\n\t\t'warn': console.warn.bind(console),\n\t\t'info': console.info.bind(console),\n\t\t'debug': console.log.bind(console),\n\t\t'trace': console.log.bind(console),\n\t},\n\n\tcouch: {\n\t\tserver: program.couchServer || 'http://localhost:5984',\n\t\tusername: program.username,\n\t\tpwd: program.password\n\t},\n\n\tcallback: function(err, res) {\n\t\tconsole.log(err, res);\n\t}\n};\n\nconsole.log(program.username, program.password, oldDB, newDB)\n\nrequire('./index.js')(config, program.args[0], program.args[1]).done();"
  },
  {
    "path": "migrations/index.js",
    "content": "var semver = require('semver');\r\nvar glob = require('glob');\r\nvar nano = require('nano');\r\nvar Q = require('q');\r\n\r\nQ.longStackSupport = true;\r\n\r\nvar dbInit = require('../lib/init.js');\r\n\r\nmodule.exports = function migrate(opts, oldDBName, newDBName) {\r\n\tvar config = require('../lib/options')(opts);\r\n\tvar server = nano(config.couch.server);\r\n\tvar oldDB = server.use(oldDBName);\r\n\r\n\tconfig.couch.updateSite = true;\r\n\tconfig.couch.database = newDBName;\r\n\r\n\treturn dbInit(config).then(function() {\r\n\t\tconfig.log.info('Migrating from ', oldDBName, 'to', newDBName);\r\n\t\treturn Q.ninvoke(oldDB, 'get', 'version').then(function(version) {\r\n\t\t\treturn version[0].version;\r\n\t\t}, function() {\r\n\t\t\treturn '0.1.0';\r\n\t\t});\r\n\t}).then(function(oldDBVersion) {\r\n\t\treturn glob.sync('migrate-*.js', {\r\n\t\t\tcwd: __dirname\r\n\t\t}).filter(function(file) {\r\n\t\t\treturn semver.lt(oldDBVersion, file.slice(8, -3));\r\n\t\t}).sort(function(a, b) {\r\n\t\t\treturn semver.gt(a.slice(8, -3), b.slice(8, -3));\r\n\t\t});\r\n\t}).then(function(files) {\r\n\t\tif (files.length === 0) {\r\n\t\t\tconfig.log.info('Database is already up to date');\r\n\t\t}\r\n\t\treturn files.map(function(file) {\r\n\t\t\tvar script = require('./' + file);\r\n\t\t\tconfig.log.info('\\nRunning migration - %s', file);\r\n\t\t\treturn script(oldDB, server.use(newDBName), config);\r\n\t\t}).reduce(Q.when, Q());\r\n\t}).then(function() {\r\n\t\tconfig.log.info('Updating views');\r\n\t\treturn require('../lib/couchViews.js')(config);\r\n\t}).then(function() {\r\n\t\tconfig.log.info('Updating site');\r\n\t\treturn require('../lib/couchSite.js')(config);\r\n\t});\r\n};"
  },
  {
    "path": "migrations/migrate-0.2.0.js",
    "content": "// Migrating from 0.1.x to 0.2.0\r\n\r\nvar Q = require('q');\r\n\r\nvar utility = require('./utility');\r\n\r\nmodule.exports = function(oldDb, newDb, config) {\r\n\tvar log = config.log;\r\n\r\n\treturn utility.forEachDoc(oldDb, newDb, function(doc) {\r\n\t\tif (doc.type !== 'perfData') {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tdelete doc._id;\r\n\t\tdelete doc._rev;\r\n\t\tdoc.url = null;\r\n\t\tdoc.browser = doc.meta._browserName || null;\r\n\r\n\t\tfor (var key in doc.data) {\r\n\t\t\tdoc.data[key] = doc.data[key].value;\r\n\t\t}\r\n\r\n\t\tdoc.data[events[0]] = 0;\r\n\t\tfor (var i = 1; i < events.length; i++) {\r\n\t\t\tif (typeof doc.data[events[i]] === 'undefined') {\r\n\t\t\t\tdoc.data[events[i]] = 0;\r\n\t\t\t}\r\n\t\t\tdoc.data[events[i]] = doc.data[events[i]] + doc.data[events[i - 1]];\r\n\t\t}\r\n\r\n\t\treturn doc;\r\n\t});\r\n};\r\n\r\nvar events = [\r\n\t'navigationStart',\r\n\t'unloadEventStart',\r\n\t'unloadEventEnd',\r\n\t'redirectStart',\r\n\t'redirectEnd',\r\n\t'fetchStart',\r\n\t'domainLookupStart',\r\n\t'domainLookupEnd',\r\n\t'connectStart',\r\n\t'connectEnd',\r\n\t'secureConnectionStart',\r\n\t'requestStart',\r\n\t'responseStart',\r\n\t'domLoading',\r\n\t'domInteractive',\r\n\t'domContentLoadedEventStart',\r\n\t'domContentLoadedEventEnd',\r\n\t'domComplete',\r\n\t'loadEventStart',\r\n\t'loadEventEnd'\r\n];"
  },
  {
    "path": "migrations/migrate-0.3.0.js",
    "content": "// Migrating from 0.2.x to 1.2.x\n\nvar Q = require('q');\n\nvar utility = require('./utility');\n\nmodule.exports = function(oldDb, newDb, config) {\n\tvar log = config.log;\n\n\treturn utility.forEachDoc(oldDb, newDb, function(doc) {\n\t\tif (doc.type !== 'perfData') {\n\t\t\treturn null;\n\t\t}\n\t\tdelete doc._id;\n\t\tdelete doc._rev;\n\n\t\tfor (var key in doc.data) {\n\t\t\tif (typeof doc.data.mean_frame_time === 'number') { // From TracingMetrics\n\t\t\t\tdoc.data.frames_per_sec = 1000 / doc.data.mean_frame_time;\n\t\t\t}\n\t\t\tif (typeof doc.data.meanFrameTime === 'number') { // from RAF\n\t\t\t\tdoc.data.framesPerSec_raf = 1000 / doc.data.meanFrameTime;\n\n\t\t\t}\n\t\t}\n\n\t\treturn doc;\n\t});\n};\n\nvar getFramesPerSec = function(val, metrics) {\n\tvar mft;\n\t// Iterate over each candidate to calculate FPS\n\tfor (var i = 0; i < metrics.length; i++) {\n\t\tif (val[metrics[i]]) {\n\t\t\tmft = val[metrics[i]].sum / val[metrics[i]].count;\n\t\t}\n\t\tif (mft >= 10 && mft <= 60) {\n\t\t\tbreak;\n\t\t} else {\n\t\t\tmft = null;\n\t\t}\n\t}\n\tif (mft) {\n\t\treturn {\n\t\t\tsum: 1000 / mft,\n\t\t\tcount: 1\n\t\t};\n\t}\n};"
  },
  {
    "path": "migrations/migrate-0.4.0.js",
    "content": "// Migrating to browser-perf@1.3.0. Names of some metrics have changed\n\nvar Q = require('q');\n\nvar utility = require('./utility');\n\nmodule.exports = function(oldDb, newDb, config) {\n\tvar log = config.log;\n\n\treturn utility.forEachDoc(oldDb, newDb, function(doc) {\n\t\tif (doc.type !== 'perfData') {\n\t\t\treturn null;\n\t\t}\n\t\tdelete doc._id;\n\t\tdelete doc._rev;\n\n\t\tdoc.data.meanFrameTime_raf = doc.data.meanFrameTime;\n\n\t\tvar metrics = new Metrics(doc.data);\n\n\t\tmetrics.addMetric('loadTime', 'loadEventEnd', 'fetchStart');\n\t\tmetrics.addMetric('domReadyTime', 'domComplete', 'domInteractive');\n\t\tmetrics.addMetric('readyStart', 'fetchStart', 'navigationStart');\n\t\tmetrics.addMetric('redirectTime', 'redirectEnd', 'redirectStart');\n\t\tmetrics.addMetric('appcacheTime', 'domainLookupStart', 'fetchStart');\n\t\tmetrics.addMetric('unloadEventTime', 'unloadEventEnd', 'unloadEventStart');\n\t\tmetrics.addMetric('domainLookupTime', 'domainLookupEnd', 'domainLookupStart');\n\t\tmetrics.addMetric('connectTime', 'connectEnd', 'connectStart');\n\t\tmetrics.addMetric('requestTime', 'responseEnd', 'requestStart');\n\t\tmetrics.addMetric('initDomTreeTime', 'domInteractive', 'responseEnd');\n\t\tmetrics.addMetric('loadEventTime', 'loadEventEnd', 'loadEventStart');\n\n\t\treturn doc;\n\t});\n};\n\nfunction Metrics(metrics) {\n\tthis.timing = metrics;\n}\n\nMetrics.prototype.addMetric = function(prop, a, b) {\n\tif (typeof this.timing[a] === 'number' && typeof this.timing[b] === 'number') {\n\t\tthis.timing[prop] = this.timing[a] - this.timing[b];\n\t}\n}"
  },
  {
    "path": "migrations/utility.js",
    "content": "var Q = require('q');\r\nvar MAX_LIMIT = 53;\r\n\r\nmodule.exports = {\r\n\tforEachDoc: function(oldDb, newDb, callback) {\r\n\t\tfunction processBatch(skip) {\r\n\t\t\tskip = skip || 0;\r\n\t\t\tvar count = 0;\r\n\t\t\treturn Q.ninvoke(oldDb, 'get', '_all_docs', {\r\n\t\t\t\tlimit: MAX_LIMIT,\r\n\t\t\t\tskip: skip,\r\n\t\t\t\tinclude_docs: true\r\n\t\t\t}).then(function(docs) {\r\n\t\t\t\tcount = docs[0].rows.length;\r\n\t\t\t\treturn result = docs[0].rows.map(function(data) {\r\n\t\t\t\t\treturn callback(data.doc);\r\n\t\t\t\t});\r\n\t\t\t}).then(function(results) {\r\n\t\t\t\treturn Q.ninvoke(newDb, 'bulk', {\r\n\t\t\t\t\tdocs: results.filter(function(val) {\r\n\t\t\t\t\t\treturn val !== null;\r\n\t\t\t\t\t})\r\n\t\t\t\t}, {\r\n\t\t\t\t\tnew_edits: true\r\n\t\t\t\t});\r\n\t\t\t}).then(function() {\r\n\t\t\t\tif (count >= MAX_LIMIT) {\r\n\t\t\t\t\treturn processBatch(skip + MAX_LIMIT);\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn Q();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\treturn processBatch();\r\n\t}\r\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"perfjankie\",\n  \"version\": \"2.1.2\",\n  \"dbVersion\": \"0.4.0\",\n  \"description\": \"Browser Performance regression suite\",\n  \"main\": \"lib/index.js\",\n  \"scripts\": {\n    \"test\": \"bower install && grunt test\",\n    \"prepublish\": \"grunt clean dist\"\n  },\n  \"bin\": {\n    \"perfjankie\": \"lib/cli.js\",\n    \"perfjankie-dbmigrate\": \"migrations/cli.js\"\n  },\n  \"author\": \"Parashuram <code@nparashuram.com>\",\n  \"license\": \"BSD-2-Clause\",\n  \"dependencies\": {\n    \"browser-perf\": \"~1.4.0\",\n    \"commander\": \"~2.8.1\",\n    \"glob\": \"~5.0.14\",\n    \"nano\": \"~6.1.5\",\n    \"q\": \"~1.4.1\",\n    \"sauce-tunnel\": \"^2.2.3\",\n    \"semver\": \"^5.0.1\",\n    \"serve-static\": \"^1.10.0\"\n  },\n  \"devDependencies\": {\n    \"bunyan\": \"~1.5.1\",\n    \"bower\": \"^1.7.9\",\n    \"chai\": \"~3.2.0\",\n    \"chai-as-promised\": \"^5.1.0\",\n    \"chromedriver\": \"^2.21.2\",\n    \"dtrace-provider\": \"^0.6.0\",\n    \"grunt\": \"~0.4.5\",\n    \"grunt-autoprefixer\": \"^3.0.3\",\n    \"grunt-connect-proxy\": \"^0.2.0\",\n    \"grunt-contrib-clean\": \"~0.6.0\",\n    \"grunt-contrib-concat\": \"^0.5.1\",\n    \"grunt-contrib-connect\": \"~0.11.2\",\n    \"grunt-contrib-copy\": \"^0.8.0\",\n    \"grunt-contrib-htmlmin\": \"^0.4.0\",\n    \"grunt-contrib-jshint\": \"~0.11.2\",\n    \"grunt-contrib-less\": \"^1.0.1\",\n    \"grunt-contrib-uglify\": \"^0.9.1\",\n    \"grunt-contrib-watch\": \"~0.6.1\",\n    \"grunt-mocha-test\": \"~0.12.7\",\n    \"grunt-processhtml\": \"^0.3.8\",\n    \"load-grunt-tasks\": \"~3.2.0\",\n    \"mocha\": \"~2.2.5\",\n    \"selenium-server\": \"^2.53.0\",\n    \"sinon\": \"~1.15.4\"\n  },\n  \"keywords\": [\n    \"browser-perf\",\n    \"telemetry\",\n    \"gruntplugin\"\n  ],\n  \"directories\": {\n    \"test\": \"test\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/axemclion/perfjankie.git\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/axemclion/perfjankie/issues\"\n  }\n}\n"
  },
  {
    "path": "tasks/metricsgen.js",
    "content": "var Q = require('q');\nvar browserPerf = require('browser-perf');\n\nmodule.exports = function(grunt) {\n\tgrunt.registerMultiTask('metricsgen', 'Generates the names of metrics', function() {\n\t\tvar apiDocs = new browserPerf.docs();\n\t\tvar regex = /(_avg|_max|_count)$/;\n\t\tvar doc = {};\n\n\t\tfor (var key in apiDocs.metrics) {\n\t\t\tvar modifier = null;\n\t\t\tif (apiDocs.metrics[key].source === 'TimelineMetrics' && key.match(regex)) {\n\t\t\t\tvar idx = key.lastIndexOf('_');\n\t\t\t\tmodifier = key.substr(idx + 1);\n\t\t\t\tkey = key.substr(0, idx);\n\t\t\t}\n\t\t\tif (typeof doc[key] === 'undefined') {\n\t\t\t\tdoc[key] = apiDocs.metrics[key] || {};\n\t\t\t\tdoc[key].stats = [];\n\t\t\t}\n\t\t\tif (modifier) {\n\t\t\t\tdoc[key].stats.push(modifier);\n\t\t\t}\n\t\t}\n\n\t\tvar metrics = [];\n\t\tfor (var key in doc) {\n\t\t\tdoc[key].name = key;\n\t\t\tmetrics.push(doc[key]);\n\t\t}\n\n\t\tthis.files.forEach(function(file) {\n\t\t\tgrunt.file.write(file.dest, ['var METRICS_LIST =', JSON.stringify(metrics)].join(''));\n\t\t});\n\t});\n};"
  },
  {
    "path": "tasks/task.js",
    "content": "module.exports = function(grunt) {\n  grunt.registerMultiTask('perfjankie', 'Run rendering performance test cases', function() {\n    var done = this.async(),\n      path = require('path'),\n      options = this.options({\n        log: { // Expects the following methods,\n          fatal: grunt.fail.fatal.bind(grunt.fail),\n          error: grunt.fail.warn.bind(grunt.fail),\n          warn: grunt.log.error.bind(grunt.log),\n          info: grunt.log.ok.bind(grunt.log),\n          debug: grunt.verbose.writeln.bind(grunt.verbose),\n          trace: grunt.log.debug.bind(grunt.log)\n        },\n        time: new Date().getTime()\n      }),\n      files = options.urls;\n\n    options.time = parseFloat(options.time, 10);\n    if (options.sauceTunnel) {\n      var SauceTunnel = require('sauce-tunnel');\n      grunt.log.writeln('Starting Saucelabs Tunnel');\n      var tunnel = new SauceTunnel(options.SAUCE_USERNAME, options.SAUCE_ACCESSKEY, options.sauceTunnel, true);\n      tunnel.start(function(status) {\n        grunt.log.ok('Saucelabs Tunnel started - ' + status);\n        if (status === false) {\n          done(false);\n        } else {\n          runPerfTest(files, options, function(res) {\n            grunt.verbose.writeln('All perf tests completed');\n            tunnel.stop(function() {\n              done(res);\n            });\n          });\n        }\n      });\n    } else {\n      runPerfTest(files, options, done);\n    }\n  });\n\n  var perfjankie = require('..');\n\n  var runPerfTest = function(files, options, cb) {\n    var success = true;\n    (function runTest(i) {\n      if (i < files.length) {\n        grunt.log.writeln('Testing File ', files[i]);\n        var config = {\n          url: files[i],\n          name: files[i].replace(/(\\S)*\\/|\\.html$/gi, ''),\n          callback: function(err, res) {\n            if (err) {\n              success = false;\n              console.log(res);\n              grunt.log.warn(err);\n            } else {\n              grunt.log.ok('Saved performance metrics');\n            }\n            runTest(i + 1);\n          }\n        };\n        for (var key in options) {\n          config[key] = options[key];\n        }\n        perfjankie(config);\n      } else {\n        cb(success);\n      }\n    }(0));\n  }\n};"
  },
  {
    "path": "test/index.spec.js",
    "content": "var expect = require('chai').expect,\r\n    sinon = require('sinon'),\r\n    fs = require('fs'),\r\n    nano = require('nano');\r\n\r\nrequire('q').longStackSupport = true;\r\n\r\ndescribe('App', function () {\r\n    var browserPerfStub = sinon.stub();\r\n    var sampleData;\r\n    var util = require('./util'),\r\n        app = require('../'),\r\n        config = util.config({});\r\n\r\n    before(function (done) {\r\n        nano(config.couch.server).db.destroy(config.couch.database, function(err, res) {\r\n            done();\r\n        });\r\n    });\r\n\r\n    beforeEach(function () {\r\n        config.log.info('===========');\r\n        sampleData = JSON.parse(fs.readFileSync(__dirname + '/res/sample-perf-results.json', 'utf8'));\r\n        browserPerfStub.callsArgWith(1, null, sampleData);\r\n    });\r\n\r\n    it('should only update data', function (done) {\r\n        app(util.config({\r\n            couch: {\r\n                updateSite: false\r\n            },\r\n            callback: function (err, res) {\r\n                console.log(err);\r\n                expect(err).to.not.be.ok;\r\n                expect(res).to.be.ok;\r\n                done();\r\n            },\r\n            browserPerf: browserPerfStub\r\n        }));\r\n    });\r\n\r\n    it('should only update site', function (done) {\r\n        var couchDataStub = sinon.stub();\r\n        couchDataStub.callsArgWith(2, null, []);\r\n        app(util.config({\r\n            couch: {\r\n                onlyUpdateSite: true\r\n            },\r\n            callback: function (err, res) {\r\n                expect(couchDataStub.called).to.not.be.true;\r\n                expect(err).to.not.be.ok;\r\n                expect(res).to.be.ok;\r\n                done();\r\n            }\r\n        }));\r\n    });\r\n\r\n    it('should run performance tests and save results in a database', function (done) {\r\n        app(util.config({\r\n            callback: function (err, res) {\r\n                expect(err).to.not.be.ok;\r\n                expect(res).to.be.ok;\r\n                done();\r\n            },\r\n            browserPerf: browserPerfStub\r\n        }));\r\n    });\r\n});"
  },
  {
    "path": "test/res/local.config.json",
    "content": "{\r\n\t\"browsers\": [{\r\n\t\t\"browserName\": \"chrome\",\r\n\t\t\"version\": 32\r\n\t}],\r\n\t\"selenium\": {\r\n\t\t\"hostname\": \"localhost\",\r\n\t\t\"port\": 4444\r\n\t},\r\n\r\n\t\"couch\": {\r\n\t\t\"server\": \"http://localhost:5984\",\r\n\t\t\"username\": \"admin_user\",\r\n\t\t\"pwd\": \"admin_pass\",\r\n\t\t\"database\": \"perfjankie-test\"\r\n\t}\r\n}"
  },
  {
    "path": "test/res/sample-perf-results.json",
    "content": "[{\n\t\"numAnimationFrames\": 397,\n\t\"numFramesSentToScreen\": 397,\n\t\"droppedFrameCount\": 60,\n\t\"meanFrameTime\": 19.36006281407138,\n\t\"fetchStart\": 1411941392008,\n\t\"redirectStart\": 0,\n\t\"domComplete\": 1411941394116,\n\t\"redirectEnd\": 0,\n\t\"loadEventStart\": 1411941394116,\n\t\"navigationStart\": 1411941391221,\n\t\"requestStart\": 1411941392105,\n\t\"responseEnd\": 1411941392800,\n\t\"secureConnectionStart\": 0,\n\t\"domLoading\": 1411941392268,\n\t\"domInteractive\": 1411941393139,\n\t\"domainLookupEnd\": 1411941392026,\n\t\"domContentLoadedEventStart\": 1411941393139,\n\t\"loadEventEnd\": 1411941394141,\n\t\"connectEnd\": 1411941392105,\n\t\"responseStart\": 1411941392256,\n\t\"unloadEventStart\": 0,\n\t\"domContentLoadedEventEnd\": 1411941393423,\n\t\"connectStart\": 1411941392026,\n\t\"unloadEventEnd\": 0,\n\t\"domainLookupStart\": 1411941392009,\n\t\"Program\": 3199.1089999601245,\n\t\"Program_avg\": 0.691250864295619,\n\t\"Program_max\": 406.51899999938905,\n\t\"Program_count\": 4628,\n\t\"UpdateLayerTree\": 26.194000008516014,\n\t\"UpdateLayerTree_avg\": 0.06120093459933648,\n\t\"UpdateLayerTree_max\": 0.5839999997988343,\n\t\"UpdateLayerTree_count\": 428,\n\t\"EvaluateScript\": 430.41699999943376,\n\t\"EvaluateScript_avg\": 5.380212499992922,\n\t\"EvaluateScript_max\": 198.59800000023097,\n\t\"EvaluateScript_count\": 80,\n\t\"ParseHTML\": 420.78200000245124,\n\t\"ParseHTML_avg\": 2.963253521144023,\n\t\"ParseHTML_max\": 318.4650000007823,\n\t\"ParseHTML_count\": 142,\n\t\"RecalculateStyles\": 105.08500000182539,\n\t\"RecalculateStyles_avg\": 0.5937005649820644,\n\t\"RecalculateStyles_max\": 27.915000000037253,\n\t\"RecalculateStyles_count\": 177,\n\t\"Layout\": 133.47499999776483,\n\t\"Layout_avg\": 1.4830555555307203,\n\t\"Layout_max\": 41.8179999999702,\n\t\"Layout_count\": 90,\n\t\"EventDispatch\": 589.1679999958724,\n\t\"EventDispatch_avg\": 1.812824615371915,\n\t\"EventDispatch_max\": 283.63100000005215,\n\t\"EventDispatch_count\": 325,\n\t\"FunctionCall\": 1250.5349999787286,\n\t\"FunctionCall_avg\": 0.9762177985782424,\n\t\"FunctionCall_max\": 283.51899999938905,\n\t\"FunctionCall_count\": 1281,\n\t\"TimerFire\": 405.9339999919757,\n\t\"TimerFire_avg\": 0.9900829268096969,\n\t\"TimerFire_max\": 58.355999999679625,\n\t\"TimerFire_count\": 410,\n\t\"GCEvent\": 70.74999999906868,\n\t\"GCEvent_avg\": 7.861111111007631,\n\t\"GCEvent_max\": 32.51300000026822,\n\t\"GCEvent_count\": 9,\n\t\"FireAnimationFrame\": 67.84199998434633,\n\t\"FireAnimationFrame_avg\": 0.16751111107246008,\n\t\"FireAnimationFrame_max\": 0.7209999999031425,\n\t\"FireAnimationFrame_count\": 405,\n\t\"PaintSetup\": 7.717000005766749,\n\t\"PaintSetup_avg\": 0.11692424251161741,\n\t\"PaintSetup_max\": 0.7190000005066395,\n\t\"PaintSetup_count\": 66,\n\t\"Paint\": 217.3209999995306,\n\t\"Paint_avg\": 1.3013233532906026,\n\t\"Paint_max\": 38.62199999950826,\n\t\"Paint_count\": 167,\n\t\"DecodeImage\": 12.86499999742955,\n\t\"DecodeImage_avg\": 0.31378048774218414,\n\t\"DecodeImage_max\": 1.894000000320375,\n\t\"DecodeImage_count\": 41,\n\t\"CompositeLayers\": 103.9480000063777,\n\t\"CompositeLayers_avg\": 0.2541515892576472,\n\t\"CompositeLayers_max\": 10.35800000000745,\n\t\"CompositeLayers_count\": 409,\n\t\"XHRReadyStateChange\": 91.92799999844283,\n\t\"XHRReadyStateChange_avg\": 2.553555555512301,\n\t\"XHRReadyStateChange_max\": 75.00499999988824,\n\t\"XHRReadyStateChange_count\": 36,\n\t\"mean_frame_time\": 17.56031067961447,\n\t\"jank\": 75.3956754589968,\n\t\"mostly_smooth\": 18.0550000006333,\n\t\"Layers\": 7,\n\t\"PaintedArea_total\": 17244083,\n\t\"PaintedArea_avg\": 103257.98203592814,\n\t\"NodePerLayout_avg\": 575.2888888888889,\n\t\"ExpensivePaints\": 2,\n\t\"GCInsideAnimation\": 0,\n\t\"ExpensiveEventHandlers\": 3,\n\t\"_browserName\": \"chrome\",\n\t\"_url\": \"http://amazon.com\"\n}]"
  },
  {
    "path": "test/res/test1.html",
    "content": "<!doctype html>\r\n<html>\r\n<head>\r\n\t<title></title>\r\n</head>\r\n<body>\r\n\t<script type=\"text/javascript\">\r\n\t\tvar css = {\r\n\t\t\t'width': '300px',\r\n\t\t\t'height': '300px',\r\n\t\t\t'margin': '10px',\r\n\t\t\t'borderRadius': '400px',\r\n\t\t\t'background': 'radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.15) 30%, rgba(255,255,255,.3) 32%, rgba(255,255,255,0) 33%) 0 0, radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.3) 13%, rgba(255,255,255,0) 14%) 0 0, radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 17%, rgba(255,255,255,.43) 19%, rgba(255,255,255,0) 20%) 0 110px, radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) -130px -170px, radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.2) 11%, rgba(255,255,255,.4) 13%, rgba(255,255,255,0) 14%) 130px 370px, radial-gradient(rgba(255,255,255,0) 0, rgba(255,255,255,.1) 11%, rgba(255,255,255,.2) 13%, rgba(255,255,255,0) 14%) 0 0, linear-gradient(45deg, #343702 0%, #184500 20%, #187546 30%, #006782 40%, #0b1284 50%, #760ea1 60%, #83096e 70%, #840b2a 80%, #b13e12 90%, #e27412 100%)',\r\n\t\t\t'backgroundSize': '470px 470px, 970px 970px, 410px 410px, 610px 610px, 530px 530px, 730px 730px, 100% 100%',\r\n\t\t\t'backgroundColor': '#840b2a',\r\n\t\t\t'box-shadow': '0 0 20px 20px rgb(100, 100, 100, 0.4)'\r\n\t\t};\r\n\r\n\t\tfor (var i = 0; i < 10; i++) {\r\n\t\t\tvar x = document.createElement('div');\r\n\t\t\tfor (var key in css) {\r\n\t\t\t\tx.style[key] = css[key];\r\n\t\t\t}\r\n\t\t\tdocument.body.appendChild(x);\r\n\t\t}\r\n\r\n\t\t // Testing if Chrome was loaded with the required flags\r\n\t\tif (window.chrome) {\r\n\t\t\tconsole.log('Extension:' + typeof window.chrome.gpuBenchmarking);\r\n\t\t}\r\n\r\n\t\twindow.addEventListener('MozAfterPaint', function() {\r\n\t\t\tconsole.log('Extension:true');\r\n\t\t}, true);\r\n\t</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "test/res/test2.html",
    "content": "<!doctype html>\r\n<html>\r\n<head>\r\n</head>\r\n<body>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n\t<div style = \"font-size: 40px\">DIV</div>\r\n</body>\r\n</html>"
  },
  {
    "path": "test/seedData.js",
    "content": "module.exports = function(callback, count) {\n\tcount = count || 1000;\n\tvar path = require('path');\n\tvar sampleData = require('fs').readFileSync(path.join(__dirname, '/res/sample-perf-results.json'), 'utf8');\n\n\tvar browsers = ['firefox', 'chrome'],\n\t\tcomponents = ['component1', 'component2'],\n\t\tcommits = ['commit#1', 'commit#2', 'commit#3', 'commit#4', 'commit#5', 'commit#3'];\n\n\tvar couchData = require('./../lib/couchData'),\n\t\tconfig = require('./util').config();\n\n\tvar rand = function(arr) {\n\t\treturn arr[Math.floor(Math.random() * arr.length)];\n\t};\n\n\t(function genData(i) {\n\t\tconfig.name = rand(components);\n\t\tconfig.time = 7 + Math.floor(Math.random() * 100 % 6);\n\t\tconfig.run = commits[config.time - 7];\n\t\tconfig.suite = 'Test Suite 1';\n\t\tvar data = JSON.parse(sampleData);\n\t\tfor (var key in data[0]) {\n\t\t\tdata[0][key] = data[0][key] * Math.random() * 3;\n\t\t}\n\t\tdata[0]._browserName = rand(browsers);\n\t\tcouchData(config, data).then(function() {\n\t\t\tif (i < count) {\n\t\t\t\tgenData(i + 1);\n\t\t\t} else {\n\t\t\t\tcallback(true);\n\t\t\t}\n\t\t}, function() {\n\t\t\tcallback(false);\n\t\t}).done();\n\t}(0));\n};"
  },
  {
    "path": "test/util.js",
    "content": "module.exports = {\r\n\tconfig: function(config) {\r\n\t\tconfig = config || {};\r\n\t\tvar options = {\r\n\t\t\t\"url\": \"http://localhost:9000/test1.html\",\r\n\t\t\t//\"url\": \"https://axemclion.cloudant.com/\",\r\n\t\t\t\"name\": \"Page - Test1\",\r\n\t\t\t\"suite\": 'Suite1',\r\n\t\t\t\"time\": new Date().getTime(),\r\n\t\t\t\"run\": 'commit#' + new Date().getMilliseconds(),\r\n\t\t\t\"browsers\": ['chrome', 'firefox'],\r\n\t\t\t\"selenium\": {\r\n\t\t\t\thostname: \"localhost\",\r\n\t\t\t\tport: 4444\r\n\t\t\t},\r\n\t\t\t\"log\": config.log || require('bunyan').createLogger({\r\n\t\t\t\tname: 'test',\r\n\t\t\t\tsrc: true,\r\n\t\t\t\tlevel: 'debug',\r\n\t\t\t\t//stream: process.stdout,\r\n\t\t\t\tstreams: [{\r\n\t\t\t\t\tpath: 'test.log'\r\n\t\t\t\t}]\r\n\t\t\t}),\r\n\t\t\t\"couch\": {\r\n\t\t\t\tserver: 'http://localhost:5984',\r\n\t\t\t\tdatabase: 'perfjankie-test',\r\n\t\t\t\tupdateSite: true,\r\n\t\t\t\tonlyUpdateSite: false\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tvar extend = function(options, config) {\r\n\t\t\tfor (var key in config) {\r\n\t\t\t\tif (typeof options[key] === 'object' && typeof config[key] === 'object') {\r\n\t\t\t\t\toptions[key] = extend(options[key], config[key]);\r\n\t\t\t\t} else {\r\n\t\t\t\t\toptions[key] = config[key];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn options;\r\n\t\t};\r\n\r\n\t\treturn extend(options, config || {});\r\n\t}\r\n};"
  },
  {
    "path": "www/app/all-metrics/all-metrics.html",
    "content": "<div class=\"page all-metrics\">\r\n\t<h1 class = \"page-header\">\r\n\t\tAll Metrics\r\n\t\t<small class=\"page-details pull-right\">\r\n\t\t\t{{pagename}} on \r\n\t\t\t<em title=\"{{browser}}\">{{browser}}</em>\r\n\t\t\t<a class=\"btn btn-xs btn-primary\" href=\"#/page-select\" title=\"Select a different page or browser\">change</a>\r\n\t\t</small>\r\n\t</h1>\r\n\t<div>\r\n\t\t<div class=\"input-group input-group-lg col-md-4\">\r\n\t\t\t<input type=\"text\" class=\"form-control\" ng-model=\"query\" placeholder=\"Filter Metrics\"/>\r\n\t\t\t<span class=\"input-group-addon icon-filter\"></span>\r\n\t\t</div>\r\n\r\n\t\t<ul class=\"list-unstyled metric-names row\">\r\n\t\t\t<li ng-repeat=\"metric in metrics.metricNames | orderBy:'-importance' | metricFilter:query\" class=\"col-xs-12 col-sm-6 col-md-4\" ng-class=\"{'disabled': metric.browsers.indexOf(browser) === -1}\">\r\n\t\t\t\t<div class=\"list-group-item\">\r\n\t\t\t\t\t<h4 class=\"list-group-item-heading\">{{metric.name | formatMetric}}</h4>\r\n\t\t\t\t\t<p class=\"list-group-item-text\">{{metric.summary}}</p>\r\n\t\t\t\t\t<div class=\"pull-left browser-icons\">\r\n\t\t\t\t\t\t<span class=\"icon-{{browser}}\" title=\"{{browser}}\" ng-class=\"{'disabled': metric.browsers.indexOf(browser) === -1}\" ng-repeat=\"browser in ['chrome', 'android', 'safari', 'ie', 'firefox']\"></span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"pull-right\">\r\n\t\t\t\t\t\t<a class=\"btn btn-xs btn-info\" ng-href=\"#/detail?pagename={{pagename}}&browser={{browser}}&metric={{metric.name}}\">\r\n\t\t\t\t\t\t\tView Graph &raquo;\r\n\t\t\t\t\t\t</a>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"clearfix\"></div>\r\n\t\t\t\t</div>\r\n\t\t\t</li>\r\n\t\t</ul>\r\n\t</div>\r\n</div>"
  },
  {
    "path": "www/app/all-metrics/all-metrics.less",
    "content": ".page.all-metrics {\n\t.metric-names {\n\t\tli {\n\t\t\tmargin: 30px 0 0 0;\n\t\t\t.list-group-item{\n\t\t\t\tbackground: #fefefe;\n\t\t\t\tfloat: left;\n\t\t\t\tpadding: 10px;\n\t\t\t\tborder: SOLID 1px #ccc;\n\t\t\t\tdisplay: block;\n\t\t\t\twidth: 100%;\n\t\t\t\tborder-radius: 5px;\n\t\t\t\tp{\n\t\t\t\t\theight: 70px;\n\t\t\t\t}\n\t\t\t\t.browser-icons{\n\t\t\t\t\tfont-size: 1.1em;\n\t\t\t\t\tcolor: #333;\n\t\t\t\t\tspan.disabled{\n\t\t\t\t\t\tcolor: #ccc;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t\t&.disabled{\n\t\t\t\topacity: 0.5;\n\t\t\t\t.list-group-item{\n\t\t\t\t\tbackground: #ccc;\n\t\t\t\t\tcursor: not-allowed;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "www/app/all-metrics/allmetrics.js",
    "content": "angular\r\n\t.module('allmetrics', ['ngRoute', 'Backend', 'metricdetail'])\r\n\t.config(['$routeProvider',\r\n\t\tfunction($routeProvider) {\r\n\t\t\t$routeProvider.when('/all-metrics', {\r\n\t\t\t\ttemplateUrl: 'app/all-metrics/all-metrics.html',\r\n\t\t\t\tcontroller: 'AllMetricsCtrl',\r\n\t\t\t\tcontrollerAs: 'metrics',\r\n\t\t\t\tresolve: {\r\n\t\t\t\t\tMetricNames: ['Data',\r\n\t\t\t\t\t\tfunction(data, $routeParams) {\r\n\t\t\t\t\t\t\treturn data.getAllMetrics();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t]\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t])\r\n\t.controller('AllMetricsCtrl', ['$routeParams', 'MetricNames',\r\n\t\tfunction($routeParams, MetricNames) {\r\n\t\t\tthis.metricNames = MetricNames;\r\n\t\t}\r\n\t])\r\n\t.filter('metricFilter', ['$filter',\r\n\t\tfunction($filter) {\r\n\t\t\treturn function(input, query) {\r\n\t\t\t\tif (!query) {\r\n\t\t\t\t\treturn input;\r\n\t\t\t\t}\r\n\t\t\t\tvar result = [];\r\n\t\t\t\tvar regex = new RegExp(query, 'i');\r\n\t\t\t\tvar filter = $filter('formatMetric');\r\n\t\t\t\tfor (var i = 0; i < input.length; i++) {\r\n\t\t\t\t\tif (regex.test(filter(input[i].name))) {\r\n\t\t\t\t\t\tresult.push(input[i]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn result;\r\n\t\t\t};\r\n\t\t}\r\n\t]);"
  },
  {
    "path": "www/app/app.js",
    "content": "angular\r\n\t.module('perfjankie', ['ngRoute', 'sidebar', 'pageSelect', 'summary', 'allmetrics'])\r\n\t.config(['$routeProvider',\r\n\t\tfunction($routeProvider) {\r\n\t\t\t$routeProvider.otherwise({\r\n\t\t\t\tredirectTo: '/page-select'\r\n\t\t\t});\r\n\t\t}\r\n\t])\r\n\t.controller('MainPageCtrl', ['$scope', '$location', '$routeParams',\r\n\t\tfunction($scope, $location, $routeParams) {\r\n\t\t\t$scope.$on('$routeChangeSuccess', function(scope, next, current) {\r\n\t\t\t\t$scope.pagename = $routeParams.pagename;\r\n\t\t\t\t$scope.browser = $routeParams.browser;\r\n\t\t\t\t$scope.pageLoading = false;\r\n\t\t\t});\r\n\r\n\t\t\t$scope.$on('$routeChangeStart', function() {\r\n\t\t\t\t$scope.pageLoading = true;\r\n\t\t\t});\r\n\r\n\t\t\t$scope.$on('$routeChangeError', function(a, b, c, err) {\r\n\t\t\t\t$scope.pageError = true;\r\n\t\t\t\t$scope.pageLoading = false;\r\n\t\t\t});\r\n\r\n\t\t\t$scope.goHome = function() {\r\n\t\t\t\t$location.url('/page-select');\r\n\t\t\t\twindow.document.location.reload();\r\n\t\t\t};\r\n\r\n\t\t\tif (window.location !== window.top.location) {\r\n\t\t\t\t$scope.noPjBrand = true;\r\n\t\t\t}\r\n\t\t}\r\n\t])\r\n\t.filter('formatMetric', function() {\r\n\t\treturn function(input) {\r\n\t\t\tinput = input.replace(/_/g, \" \").replace(/([A-Z])([A-Z])([a-z])|([a-z])([A-Z])/g, \"$1$4 $2$3$5\");\r\n\t\t\treturn input.toLowerCase().replace(/([^a-z]|^)([a-z])(?=[a-z]{2})/g, function(_, g1, g2) {\r\n\t\t\t\treturn g1 + g2.toUpperCase();\r\n\t\t\t});\r\n\t\t};\r\n\t})\r\n\t.filter('formatMetricValue', ['$filter',\r\n\t\tfunction($filter) {\r\n\t\t\treturn function(value, unit) {\r\n\t\t\t\tvar fraction = 0;\r\n\t\t\t\tif (unit === 'ms' || unit === 'fps') {\r\n\t\t\t\t\tfraction = 2;\r\n\t\t\t\t}\r\n\t\t\t\tif (value > 1000) {\r\n\t\t\t\t\treturn $filter('number')(value / 1000, value > 100000 ? 0 : 2) + 'K';\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn $filter('number')(value, fraction);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\t]);"
  },
  {
    "path": "www/app/backend.js",
    "content": "angular\r\n\t.module('Backend', ['Endpoints'])\r\n\t.factory('Data', ['Resource',\r\n\t\tfunction(resource) {\r\n\t\t\treturn {\r\n\t\t\t\tpagelist: function() {\r\n\t\t\t\t\treturn resource('/pagelist');\r\n\t\t\t\t},\r\n\t\t\t\trunList: function(opts) {\r\n\t\t\t\t\treturn resource('/runList', {\r\n\t\t\t\t\t\tbrowser: opts.browser,\r\n\t\t\t\t\t\tpagename: opts.pagename\r\n\t\t\t\t\t});\r\n\t\t\t\t},\r\n\t\t\t\trunData: function(opts) {\r\n\t\t\t\t\treturn resource('/runData', {\r\n\t\t\t\t\t\tbrowser: opts.browser,\r\n\t\t\t\t\t\tpagename: opts.pagename,\r\n\t\t\t\t\t\ttime: opts.time\r\n\t\t\t\t\t});\r\n\t\t\t\t},\r\n\t\t\t\tgetAllMetrics: function() {\r\n\t\t\t\t\treturn resource('/all-metrics');\r\n\t\t\t\t},\r\n\t\t\t\tmetricsData: function(opts) {\r\n\t\t\t\t\treturn resource('/metrics-data', {\r\n\t\t\t\t\t\tbrowser: opts.browser,\r\n\t\t\t\t\t\tpagename: opts.pagename,\r\n\t\t\t\t\t\tmetric: opts.metric,\r\n\t\t\t\t\t\tlimit: opts.limit\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\t]);"
  },
  {
    "path": "www/app/font.less",
    "content": "@font-face {\r\n  font-family: 'fontello';\r\n  src: url('assets/fonts/fontello.eot?37370699');\r\n  src: url('assets/fonts/fontello.eot?37370699#iefix') format('embedded-opentype'),\r\n       url('assets/fonts/fontello.woff?37370699') format('woff'),\r\n       url('assets/fonts/fontello.ttf?37370699') format('truetype'),\r\n       url('assets/fonts/fontello.svg?37370699#fontello') format('svg');\r\n  font-weight: normal;\r\n  font-style: normal;\r\n}\r\n\r\n\r\n[class^=\"icon-\"]:before, [class*=\" icon-\"]:before {\r\n  font-family: \"fontello\";\r\n  font-style: normal;\r\n  font-weight: normal;\r\n  speak: none;\r\n \r\n  display: inline-block;\r\n  text-decoration: inherit;\r\n  width: 1em;\r\n  margin-right: .2em;\r\n  text-align: center;\r\n \r\n  /* For safety - reset parent styles, that can break glyph codes*/\r\n  font-variant: normal;\r\n  text-transform: none;\r\n     \r\n  /* fix buttons height, for twitter bootstrap */\r\n  line-height: 1em;\r\n \r\n  /* Animation center compensation - margins should be symmetric */\r\n  /* remove if not needed */\r\n  margin-left: .2em;\r\n \r\n  /* you can be more comfortable with increased icons size */\r\n  /* font-size: 120%; */\r\n \r\n  /* Uncomment for 3D effect */\r\n  /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */\r\n}"
  },
  {
    "path": "www/app/main-page/error.less",
    "content": ".error{\n\ttext-align: center;\n\tmargin-top: 10%;\n}"
  },
  {
    "path": "www/app/main-page/navbar.html",
    "content": "<div class=\"navbar navbar-default navbar-fixed-top\" role=\"navigation\">\r\n\t<div class=\"container-fluid\">\r\n\t\t<div class=\"navbar-header\">\r\n\t\t\t<button type=\"button\" class=\"navbar-toggle\" data-toggle=\"collapse\" data-target=\".navbar-collapse\">\r\n\t\t\t\t<span class=\"sr-only\">Toggle navigation</span>\r\n\t\t\t\t<span class=\"icon-bar\"></span>\r\n\t\t\t\t<span class=\"icon-bar\"></span>\r\n\t\t\t\t<span class=\"icon-bar\"></span>\r\n\t\t\t</button>\r\n\t\t\t<a class=\"navbar-brand\" href=\"#/\">\r\n\t\t\t\t<span class=\"logo-icon icon-gauge\"></span>\r\n\t\t\t\t<span class=\"logo\">perfJankie</span>\r\n\t\t\t</a>\r\n\t\t</div>\r\n\t\t<div class=\"navbar-collapse collapse\">\r\n\t\t\t<ul class=\"nav navbar-nav navbar-right\">\r\n\t\t\t\t<li>\r\n\t\t\t\t\t<a href=\"/_utils\">\r\n\t\t\t\t\t\t<i class=\"icon-database\"></i>\r\n\t\t\t\t\t\t&nbsp;Database\r\n\t\t\t\t\t</a>\r\n\t\t\t\t</li>\r\n\t\t\t\t<li class=\"dropdown\">\r\n\t\t\t\t\t<a class=\"dropdown-toggle\" data-toggle=\"dropdown\">\r\n\t\t\t\t\t\t<i class=\"icon-github\"></i>\r\n\t\t\t\t\t\tSource Code <b class=\"caret\"></b>\r\n\t\t\t\t\t</a>\r\n\t\t\t\t\t<ul class=\"dropdown-menu\">\r\n\t\t\t\t\t\t<li>\r\n\t\t\t\t\t\t\t<a href=\"http://github.com/axemclion/browser-perf\" target=\"_blank\">browser-perf</a>\r\n\t\t\t\t\t\t</li>\r\n\t\t\t\t\t\t<li>\r\n\t\t\t\t\t\t\t<a href=\"http://github.com/axemclion/perfjankie\" target=\"_blank\">perfJankie</a>\r\n\t\t\t\t\t\t</li>\r\n\t\t\t\t\t\t<li class=\"divider\"></li>\r\n\t\t\t\t\t\t<li>\r\n\t\t\t\t\t\t\t<a href=\"http://nparashuram.com/contact.html\" target=\"_blank\">Contact</a>\r\n\t\t\t\t\t\t</li>\r\n\t\t\t\t\t</ul>\r\n\t\t\t\t</li>\r\n\t\t\t</ul>\r\n\t\t</div>\r\n\t</div>\r\n</div>"
  },
  {
    "path": "www/app/main-page/navbar.less",
    "content": ".navbar-brand {\r\n\tpadding: 0 15px;\r\n\t.logo-icon {\r\n\t\tfont-size: 2.5em;\r\n\t\tcolor: #aaa;\r\n\t\tvertical-align: middle;\r\n\t}\r\n\t.logo {\r\n\t\tvertical-align: middle;\r\n\t\tfont-weight: bold;\r\n\t\tfont-size: 1.3em;\r\n\t\t&:after {\r\n\t\t\tcontent: 'perfJankie';\r\n\t\t\tposition: relative;\r\n\t\t\ttop: -33px;\r\n\t\t\tleft: 69px;\r\n\t\t\tdisplay: block;\r\n\t\t\theight: 10px;\r\n\t\t\toverflow: hidden;\r\n\t\t\tbackground: #f8f8f8;\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "www/app/main-page/no-pj-brand.less",
    "content": "body.no-pj-brand {\n\t.navbar {\n\t\tdisplay: none !important;\n\t}\n\t.sidebar {\n\t\tmargin-top: 0px;\n\t}\n\t.content-container {\n\t\tpadding-top: 0px;\n\t}\n\t.pj-brand {\n\t\tdisplay: none !important;\n\t}\n}"
  },
  {
    "path": "www/app/main-page/sidebar.html",
    "content": "<div class=\"sidebar col-md-2 nav\" ng-controller=\"SideBarCtrl as sidebar\" ng-cloak>\r\n\t<div ng-if=\"!pagename || !browser\" class=\"page-select\">\r\n\t\t<h4>Select a test case to view metrics</h4>\r\n\t\t<hr/>\r\n\t</div>\r\n\t<div ng-if=\"pagename && browser\">\r\n\t\t<div class=\"metadata\">\r\n\t\t\t<span class=\"browser icon-{{browser}}\" title=\"Metrics collected on {{browser}}\"></span>\r\n\t\t\t<h4> <strong title=\"{{pagename}}\">{{pagename}}</strong>\r\n\t\t\t</h4>\r\n\t\t\t<a href = \"#/page\" class=\"btn btn-xs btn-primary\">select</a>\r\n\t\t</div>\r\n\t\t<ul class=\"nav\">\r\n\t\t\t<li>\r\n\t\t\t\t<a href=\"#/summary?pagename={{pagename}}&browser={{browser}}\">\r\n\t\t\t\t\t<span class = \"icon-gauge\"></span>\r\n\t\t\t\t\tSummary\r\n\t\t\t\t</a>\r\n\t\t\t</li>\r\n\t\t\t<li ng-repeat = \"(category, values) in sidebar.categories\">\r\n\t\t\t\t<a ng-click=\"subMenu=(subMenu===category?'':category)\">\r\n\t\t\t\t\t<span class = \"icon-{{category}}\"></span>\r\n\t\t\t\t\t{{category}}\r\n\t\t\t\t\t<span class=\"pull-right\" ng-if=\"subMenu!==category\">+</span>\r\n\t\t\t\t\t<span class=\"pull-right\" ng-if=\"subMenu===category\">-</span>\r\n\t\t\t\t</a>\r\n\t\t\t\t<ul class=\"nav sub-menu\" ng-show=\"subMenu===category\">\r\n\t\t\t\t\t<li ng-repeat=\"metric in values\">\r\n\t\t\t\t\t\t<a ng-href=\"#/detail?pagename={{pagename}}&browser={{browser}}&metric={{metric}}\">\r\n\t\t\t\t\t\t\t<span class=\"icon-{{category}}-sub\"></span>\r\n\t\t\t\t\t\t\t{{metric | formatMetric}}\r\n\t\t\t\t\t\t</a>\r\n\t\t\t\t\t</li>\r\n\t\t\t\t</ul>\r\n\t\t\t</li>\r\n\t\t\t<li>\r\n\t\t\t\t<a href=\"#/all-metrics?pagename={{pagename}}&browser={{browser}}\">\r\n\t\t\t\t\t<span class = \"icon-sort-alphabet-outline\"></span>\r\n\t\t\t\t\tAll metrics\r\n\t\t\t\t</a>\r\n\t\t\t</li>\r\n\t\t</ul>\r\n\t</div>\r\n\t<div class=\"help pj-brand\">\r\n\t\t<!-- TODO Add help text -->\r\n\t</div>\r\n</div>"
  },
  {
    "path": "www/app/main-page/sidebar.js",
    "content": "angular\n\t.module('sidebar', ['ngRoute'])\n\t.controller('SideBarCtrl', ['$routeParams', '$scope',\n\t\tfunction($routeParams, $scope) {\n\t\t\tvar self = this;\n\t\t\t$scope.$on('$routeChangeSuccess', function(scope, next, current) {\n\t\t\t\tvar browser = $routeParams.browser;\n\t\t\t\tself.categories = {\n\t\t\t\t\t'Frame Rates': ['framesPerSec_raf', 'meanFrameTime_raf', 'droppedFrameCount'],\n\t\t\t\t};\n\t\t\t\tif (['chrome', 'safari', 'android'].indexOf(browser) !== -1) {\n\t\t\t\t\tself.categories['Paint'] = ['Paint', 'Layout', 'RecalculateStyles', 'CompositeLayers'];\n\t\t\t\t\tself.categories['Javascript'] = ['TimerInstall', 'TimerFire', 'EventDispatch', 'FunctionCall'];\n\t\t\t\t\tself.categories['Frame Rates'].unshift('frames_per_sec', 'mean_frame_time');\n\t\t\t\t}\n\t\t\t\tif (browser !== 'safari') {\n\t\t\t\t\tself.categories['Network'] = ['domReadyTime', 'loadTime', 'domainLookupTime', 'requestTime', 'loadEventTime'];\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t]);"
  },
  {
    "path": "www/app/main-page/sidebar.less",
    "content": "@media (min-width:992px) {\r\n\t.sidebar {\r\n\t\tposition: fixed;\r\n\t}\r\n}\r\n.sidebar {\r\n\tuser-select: none;\r\n\tbackground: #f8f8f8;\r\n\tborder: SOLID 1px #e7e7e7;\r\n\ttop: 0;\r\n\tbottom: 0;\r\n\tleft: 0;\r\n\tmargin-top: 50px;\r\n\tpadding: 0;\r\n\toverflow-x: hidden;\r\n\t.page-select {\r\n\t\tpadding: 10px;\r\n\t\th4 {\r\n\t\t\ttext-align: center;\r\n\t\t\tfont-size: 2.5em;\r\n\t\t\tcolor: #ccc;\r\n\t\t\tfont-weight: bold;\r\n\t\t}\r\n\t}\r\n\t.metadata {\r\n\t\tpadding: 10px 15px;\r\n\t\tstrong {\r\n\t\t\twhite-space: nowrap;\r\n\t\t\twidth: 100%;\r\n\t\t\toverflow: hidden;\r\n\t\t\ttext-overflow: ellipsis;\r\n\t\t\ttext-transform: uppercase;\r\n\t\t\tvertical-align: middle;\r\n\t\t}\r\n\t\t.browser {\r\n\t\t\tcolor: #aaa;\r\n\t\t\tvertical-align: middle;\r\n\t\t\tfont-size: 3em;\r\n\t\t\tfloat: right;\r\n\t\t\tmargin: -0.6em -0.6em 0 0;\r\n\t\t}\r\n\t}\r\n\tul.nav {\r\n\t\tborder-top: 1px solid #e7e7e7;\r\n\t\tli {\r\n\t\t\tborder-bottom: 1px solid #e7e7e7;\r\n\t\t\t&.active {\r\n\t\t\t\tbackground-color: #eee;\r\n\t\t\t}\r\n\t\t\t.sub-menu{\r\n\t\t\t\ta{\r\n\t\t\t\t\tmargin-left: 20px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t.icon-Paint:before {&:extend(.icon-brush:before);} \r\n\t\t\t.icon-Paint-sub:before{ &:extend(.icon-paintbucket:before);}\r\n\t\t\t.icon-Content:before {&:extend(.icon-code:before);}\r\n\t\t\t.icon-Content-sub:before {&:extend(.icon-file-code:before);}\r\n\t\t\t.icon-Javascript:before {&:extend(.icon-cog-alt:before);}\r\n\t\t\t.icon-Javascript-sub:before {&:extend(.icon-cog:before);} \r\n\t\t\t.icon-Frame:before {&:extend(.icon-movie:before);}\r\n\t\t\t.icon-Network:before, .icon-Network-sub:before {&:extend(.icon-signal:before);}\r\n\t\t}\r\n\t}\r\n\t.help {\r\n\t\tpadding: 10px;\r\n\t}\r\n}"
  },
  {
    "path": "www/app/main.less",
    "content": "html, body {\r\n\twidth: 100%;\r\n\theight: 100%;\r\n}\r\nbody {\r\n\tfont-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\r\n\tbackground: #fff;\r\n\t.page-loading {\r\n\t\tposition: absolute;\r\n\t\ttop: 50%;\r\n\t\tleft: 50%;\r\n\t\tmargin-top: -50px;\r\n\t\tmargin-left: -50px;\r\n\t\t.spin-container {\r\n\t\t\tfont-size: 6em;\r\n\t\t\tbackground: #fff;\r\n\t\t\tbox-shadow: 0 0 10px 0 #333;\r\n\t\t\tborder-radius: 20px;\r\n\t\t}\r\n\t}\r\n\ta {\r\n\t\tcursor: pointer;\r\n\t}\r\n\t.content-container {\r\n\t\tpadding-top: 50px;\r\n\t\tmin-height: 100%;\r\n\t\t.row {\r\n\t\t\tmin-height: 100%;\r\n\t\t}\r\n\t\t.content {\r\n\t\t\tmin-height: 100%;\r\n\t\t\t.page-details {\r\n\t\t\t\tmargin-top: 14px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t.graph {\r\n\t\tdisplay: block;\r\n\t\t&>div.jqplot-target{\r\n\t\t\theight: 100%;\r\n\t\t}\r\n\t\t.graph-error {\r\n\t\t\tem {\r\n\t\t\t\tfont-size: 2em;\r\n\t\t\t\tdisplay: block;\r\n\t\t\t\tmargin-bottom: 0.5em;\r\n\t\t\t\tfont-style: normal;\r\n\t\t\t}\r\n\t\t\tpadding-top: 2em;\r\n\t\t\tdisplay: block;\r\n\t\t\ttext-align: center;\r\n\t\t\tfont-size: 1.5em;\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "www/app/metric-details/metric-detail.html",
    "content": "<div class=\"page metric-detail\">\n\t<h1 class = \"page-header\" ng-cloak>\n\t\t{{metric.name | formatMetric}}\n\t\t<a class=\"metric-help\" title=\"{{metric.summary}}\" ng-click=\"showHelp=!showHelp\">\n\t\t\t<span ng-hide=\"showHelp\">?</span>\n\t\t\t<span ng-show=\"showHelp\">-</span>\n\t\t</a>\n\t\t<small class=\"page-details pull-right\">\n\t\t\t{{pagename}} (\n\t\t\t<span title=\"{{browser}}\" class=\"icon-{{browser}}\"></span>\n\t\t\t)\n\t\t\t<a class=\"btn btn-xs btn-primary\" href=\"#/page-select\" title=\"Select a different page or browser\">change</a>\n\t\t</small>\n\t</h1>\n\t<div class=\"explanation\" ng-show=\"showHelp\">\n\t\t<p class=\"metric-summary\"> <em>Details:</em>\n\t\t\t{{metric.metadata.summary}}\n\t\t\t<br/> <em>Unit:</em>\n\t\t\t{{metric.metadata.unit}}\n\t\t\t|\n\t\t\t<em>Source:</em>\n\t\t\t{{metric.metadata.source}}\n\t\t\t|\n\t\t\t<em>Supported Browsers:</em>\n\t\t\t<span ng-repeat=\"browser in metric.metadata.browsers\">\n\t\t\t\t{{browser}}\n\t\t\t\t<span ng-if=\"!$last\">,</span>\n\t\t\t</span>\n\t\t</p>\n\t\t<p class=\"metric-details\">{{metric.metadata.details}}</p>\n\t</div>\n\t<div class=\"graph-modifiers\" ng-cloaak>\n\t\t<div ng-show=\"1 < metric.stats.length \" class=\"pull-left\"> <strong>Stat Type :</strong>\n\t\t\t<label class=\"radio-inline\">\n\t\t\t\t<input type=\"radio\" name=\"stat\" value=\"\" ng-model=\"modifier.stat\"/>\n\t\t\t\ttotal\n\t\t\t</label>\n\t\t\t<label class=\"radio-inline\" ng-repeat=\"statval in metric.stats\" >\n\t\t\t\t<input type=\"radio\" name=\"stat\" value=\"_{{statval}}\" ng-model=\"modifier.stat\"/>\n\t\t\t\t{{statval}}\n\t\t\t</label>\n\t\t</div>\n\t\t<div class=\"pull-right\"> <strong>Show last :</strong>\n\t\t\t<label class=\"radio-inline\" ng-repeat=\"limit in [10,20,40,'all']\" >\n\t\t\t\t<input type=\"radio\" name=\"limit\" value=\"{{limit}}\" ng-model=\"modifier.limit\"/>\n\t\t\t\t{{limit}}\n\t\t\t</label>\n\t\t</div>\n\t</div>\n\t<pj-metrics-details-graph class=\"clearfix graph\" style=\"height:{{metric.height}}px\" data=\"metric.data\" unit=\"metric.metadata.unit\">\n\t\t<span class=\"graph-error\">\n\t\t\t<h1>\n\t\t\t\t{{data}}\n\t\t\t\t<span class=\"icon-attention\"></span>\n\t\t\t\tCould not plot graph\n\t\t\t</h1>\n\t\t\tThis could be either due to insufficient data or error in data.\n\t\t</span>\n\t</pj-metrics-details-graph>\n</div>"
  },
  {
    "path": "www/app/metric-details/metric-detail.less",
    "content": ".page.metric-detail {\r\n\t.metric-help {\r\n\t\tbackground: #ccc;\r\n\t\tborder-radius: 50%;\r\n\t\tfont-size: 50%;\r\n\t\tcolor: #fff;\r\n\t\tpadding: 2px 6px;\r\n\t\tfont-family: courier;\r\n\t\tvertical-align: middle;\r\n\t\t&:hover{\r\n\t\t\ttext-decoration: none;\r\n\t\t}\r\n\t}\r\n\t.graph-modifiers {\r\n\t\tmargin: -20px 0 20px 0;\r\n\t\tpadding: 10px;\r\n\t\theight: 3em;\r\n\t\tinput[type=radio] {\r\n\t\t\tmargin-left: -15px;\r\n\t\t}\r\n\t}\r\n\t.explanation {\r\n\t\tmargin: -10px 0 20px 0;\r\n\t\tborder-bottom: SOLID #eee 1px;\r\n\t\tem{\r\n\t\t\tfont-style: normal;\r\n\t\t\tcolor: #777;\r\n\t\t}\r\n\t}\r\n\t.jqplot-highlighter-tooltip{\r\n\t\theight: 20px;\r\n\t}\r\n}"
  },
  {
    "path": "www/app/metric-details/metricDetail.js",
    "content": "angular\n\t.module('metricdetail', ['ngRoute', 'metricsGraphDetails', 'Backend'])\n\t.config(['$routeProvider',\n\t\tfunction($routeProvider) {\n\t\t\t$routeProvider.when('/detail', {\n\t\t\t\ttemplateUrl: 'app/metric-details/metric-detail.html',\n\t\t\t\tcontroller: 'MetricDetailCtrl',\n\t\t\t\tcontrollerAs: 'metric',\n\t\t\t\tresolve: {\n\t\t\t\t\tMetricsList: ['Data',\n\t\t\t\t\t\tfunction(data) {\n\t\t\t\t\t\t\treturn data.getAllMetrics();\n\t\t\t\t\t\t}\n\t\t\t\t\t],\n\t\t\t\t\tData: ['Data', '$route',\n\t\t\t\t\t\tfunction(Data, $route) {\n\t\t\t\t\t\t\t$route.current.params.limit = $route.current.params.limit || 40;\n\t\t\t\t\t\t\t$route.current.params.stat = $route.current.params.stat || '';\n\n\t\t\t\t\t\t\tvar params = $route.current.params;\n\t\t\t\t\t\t\treturn Data.metricsData({\n\t\t\t\t\t\t\t\tbrowser: params.browser,\n\t\t\t\t\t\t\t\tpagename: params.pagename,\n\t\t\t\t\t\t\t\tmetric: params.metric + params.stat,\n\t\t\t\t\t\t\t\tlimit: params.limit === 'all' ? undefined : params.limit\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t])\n\t.controller('MetricDetailCtrl', ['$routeParams', '$scope', '$location', 'Data', 'MetricsList',\n\t\tfunction($routeParams, $scope, $location, data, metricsList) {\n\t\t\tthis.name = $routeParams.metric;\n\t\t\tthis.data = data;\n\t\t\tfor (var i = 0; i < metricsList.length; i++){\n\t\t\t\tif (metricsList[i].name === this.name){\n\t\t\t\t\tthis.metadata = metricsList[i];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\t$scope.modifier = {\n\t\t\t\tlimit: $routeParams.limit,\n\t\t\t\tstat: $routeParams.stat\n\t\t\t};\n\n\t\t\tvar pos = $('.graph').position();\n\n\t\t\t// Sets height of graph with a minimum of 500px\n\t\t\tthis.height = Math.max(window.innerHeight - pos.top - 100, 500);\n\n\t\t\t$scope.$watchCollection('modifier', function(val, old, scope) {\n\t\t\t\tif ($routeParams.stat !== val.stat) {\n\t\t\t\t\t$location.search('stat', val.stat);\n\t\t\t\t} else if ($routeParams.limit !== val.limit) {\n\t\t\t\t\t$location.search('limit', val.limit);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t]);"
  },
  {
    "path": "www/app/metric-details/metricDetailsGraph.js",
    "content": "angular\n\t.module('metricsGraphDetails', [])\n\t.directive('pjMetricsDetailsGraph', function() {\n\t\tfunction prepareData(val) {\n\t\t\tvar result = {\n\t\t\t\tseries: [],\n\t\t\t\tmax: [],\n\t\t\t\tmin: [],\n\t\t\t\txaxis: {}\n\t\t\t};\n\t\t\tfor (var i = val.length - 1; i >= 0; i--) {\n\t\t\t\tvar p = val[i];\n\t\t\t\tresult.series.push([p.key, p.value.sum / p.value.count, p.value.min, p.value.max]);\n\t\t\t\tresult.min.push([p.key, p.value.min]);\n\t\t\t\tresult.max.push([p.key, p.value.max]);\n\t\t\t\tresult.xaxis[p.key] = p.label;\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\n\t\tfunction drawGraph(el, data, unit) {\n\t\t\t$.jqplot(el, [data.series, data.min, data.max], {\n\t\t\t\tfillBetween: {\n\t\t\t\t\tseries1: 1,\n\t\t\t\t\tseries2: 2,\n\t\t\t\t\tcolor: \"rgba(67, 142, 185, 0.2)\",\n\t\t\t\t\tbaseSeries: 0,\n\t\t\t\t\tfill: true\n\t\t\t\t},\n\t\t\t\tseries: [{\n\t\t\t\t\tshow: true,\n\t\t\t\t\tshadow: false,\n\t\t\t\t\tbreakOnNull: true,\n\t\t\t\t\trendererOptions: {\n\t\t\t\t\t\tsmooth: false,\n\t\t\t\t\t},\n\t\t\t\t\ttrendline: {\n\t\t\t\t\t\tshow: true,\n\t\t\t\t\t\tshadow: false,\n\t\t\t\t\t\tcolor: '#666',\n\t\t\t\t\t\tlineWidth: 2,\n\t\t\t\t\t\tlinePattern: 'dashed',\n\t\t\t\t\t\tlabel: 'trend'\n\t\t\t\t\t}\n\t\t\t\t}],\n\t\t\t\tseriesDefaults: {\n\t\t\t\t\tshow: false,\n\t\t\t\t\trendererOptions: {\n\t\t\t\t\t\tsmooth: true\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\taxes: {\n\t\t\t\t\txaxis: {\n\t\t\t\t\t\trenderer: $.jqplot.CategoryAxisRenderer,\n\t\t\t\t\t\tlabel: 'Runs',\n\t\t\t\t\t\tlabelRenderer: $.jqplot.CanvasAxisLabelRenderer,\n\t\t\t\t\t\ttickRenderer: $.jqplot.CanvasAxisTickRenderer,\n\t\t\t\t\t\trendererOptions: {\n\t\t\t\t\t\t\tsortMergedLabels: false\n\t\t\t\t\t\t},\n\t\t\t\t\t\ttickOptions: {\n\t\t\t\t\t\t\tmark: 'cross',\n\t\t\t\t\t\t\tshowMark: true,\n\t\t\t\t\t\t\tshowGridline: true,\n\t\t\t\t\t\t\tmarkSize: 5,\n\t\t\t\t\t\t\tangle: -90,\n\t\t\t\t\t\t\tshow: true,\n\t\t\t\t\t\t\tshowLabel: true,\n\t\t\t\t\t\t\tformatter: function(formatString, value) {\n\t\t\t\t\t\t\t\treturn data.xaxis[value];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t\tshowTicks: true, // wether or not to show the tick labels,\n\t\t\t\t\t\tshowTickMarks: true,\n\t\t\t\t\t},\n\t\t\t\t\tyaxis: {\n\t\t\t\t\t\ttickOptions: {},\n\t\t\t\t\t\trendererOptions: {\n\t\t\t\t\t\t\tforceTickAt0: false\n\t\t\t\t\t\t},\n\t\t\t\t\t\tlabel: unit || 'Y AXIS',\n\t\t\t\t\t\tlabelRenderer: $.jqplot.CanvasAxisLabelRenderer,\n\t\t\t\t\t\ttickRenderer: $.jqplot.CanvasAxisTickRenderer\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tgrid: {\n\t\t\t\t\tshadow: false,\n\t\t\t\t\tborderWidth: 0\n\t\t\t\t},\n\t\t\t\thighlighter: {\n\t\t\t\t\tshow: true,\n\t\t\t\t\tshowLabel: true,\n\t\t\t\t\ttooltipAxes: 'y',\n\t\t\t\t\tsizeAdjust: 7.5,\n\t\t\t\t\ttooltipLocation: 'ne'\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tvar id = 'metricDetails' + Math.floor(Math.random() * 10000);\n\n\t\tfunction link(scope, element, attrs) {\n\t\t\tscope.$watch('data', function(val) {\n\t\t\t\tif (val) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdrawGraph(id, prepareData(val), scope.unit);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tscope.error = e;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tlink: link,\n\t\t\trestrict: 'E',\n\t\t\ttransclude: true,\n\t\t\tscope: {\n\t\t\t\tdata: \"=\",\n\t\t\t\tunit: \"=\"\n\t\t\t},\n\t\t\ttemplate: '<div ng-hide=\"error\" id=\"' + id + '\"></div><div ng-transclude ng-show=\"error\"></div>'\n\t\t};\n\t});"
  },
  {
    "path": "www/app/page-select/page-select.html",
    "content": "<div class=\"page page-select\">\r\n\t<h1 class=\"page-header\">Test Cases</h1>\r\n\t<p class=\"lead\">Select a test case to view metrics</p>\r\n\t<ul class=\"suites list-unstyled\">\r\n\t\t<li ng-repeat=\"(suite, pages) in pageselect.pagelist\">\r\n\t\t\t<h3>\r\n\t\t\t\t<span class=\"icon-folder-open\"></span>\r\n\t\t\t\t{{suite}}\r\n\t\t\t</h3>\r\n\t\t\t<div class=\"row\">\r\n\t\t\t\t<div class = \"col-md-4\"  ng-repeat=\"(page, browsers) in pages\">\r\n\t\t\t\t\t<div class=\"panel panel-default\">\r\n\t\t\t\t\t\t<div class=\"panel-heading\">\r\n\t\t\t\t\t\t\t<h3 class=\"panel-title\">\r\n\t\t\t\t\t\t\t\t<span class=\"icon-calendar\"></span> <strong>{{page}}</strong>\r\n\t\t\t\t\t\t\t\t<small class=\"pull-right\"># of tests</small>\r\n\t\t\t\t\t\t\t</h3>\r\n\t\t\t\t\t\t</div>\r\n\t\t\t\t\t\t<ul class=\"list-group\">\r\n\t\t\t\t\t\t\t<li class=\"list-group-item\" ng-repeat=\"val in browsers\">\r\n\t\t\t\t\t\t\t\t<a ng-href=\"#/summary?pagename={{page}}&browser={{val.browser}}\">\r\n\t\t\t\t\t\t\t\t\t<span class=\"icon-{{val.browser}}\"></span>\r\n\t\t\t\t\t\t\t\t\t{{val.browser}}\r\n\t\t\t\t\t\t\t\t\t<span class=\"badge pull-right\" title=\"Number of commits/deploys\">{{val.runCount}}</span>\r\n\t\t\t\t\t\t\t\t</a>\r\n\t\t\t\t\t\t\t</li>\r\n\t\t\t\t\t\t</ul>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</li>\r\n\t</ul>\r\n</div>"
  },
  {
    "path": "www/app/page-select/page-select.less",
    "content": ".page.page-select{\r\n\t.suites{\r\n\t\t.panel{\r\n\t\t\t.list-group{\r\n\t\t\t\t.list-group-item{\r\n\t\t\t\t\tpadding: 0;\r\n\t\t\t\t\ta{\r\n\t\t\t\t\t\tdisplay: block;\r\n\t\t\t\t\t\tpadding: 10px 15px;\r\n\t\t\t\t\t\t&:hover{\r\n\t\t\t\t\t\t\ttext-decoration: none;\r\n\t\t\t\t\t\t\tbackground: #ccc;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "www/app/page-select/pageSelect.js",
    "content": "angular\r\n\t.module('pageSelect', ['ngRoute', 'Backend'])\r\n\t.config(['$routeProvider',\r\n\t\tfunction($routeProvider) {\r\n\t\t\t$routeProvider.when('/page-select', {\r\n\t\t\t\ttemplateUrl: 'app/page-select/page-select.html',\r\n\t\t\t\tcontroller: 'PageSelectCtrl',\r\n\t\t\t\tcontrollerAs: 'pageselect',\r\n\t\t\t\tresolve: {\r\n\t\t\t\t\tPageList: ['Data',\r\n\t\t\t\t\t\tfunction(data) {\r\n\t\t\t\t\t\t\treturn data.pagelist();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t]\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t])\r\n\t.controller('PageSelectCtrl', ['PageList',\r\n\t\tfunction(PageList) {\r\n\t\t\tthis.pagelist = PageList;\r\n\t\t}\r\n\t]);"
  },
  {
    "path": "www/app/summary/networkTimingGraph.js",
    "content": "angular\n\t.module('networkTiming', [])\n\t.directive('pjNetworkTimingGraph', function() {\n\t\tvar ticks = ['onLoad', 'Processing', 'Response', 'Request', 'TCP', 'DNS', 'AppCache', 'Unload', 'Start/Redirect'];\n\t\tvar events = [\n\t\t\t['loadEventStart', 'loadEventEnd'],\n\t\t\t['domLoading', 'domComplete'],\n\t\t\t['responseStart', 'responseEnd'],\n\t\t\t['requestStart', 'responseStart'],\n\t\t\t['connectStart', 'connectEnd'],\n\t\t\t['domainLookupStart', 'domainLookupEnd'],\n\t\t\t['fetchStart', 'domainLookupStart'],\n\t\t\t['unloadStart', 'unloadEnd'],\n\t\t\t['redirectStart', 'redirectStop'],\n\t\t];\n\n\t\tfunction prepareData(val) {\n\t\t\tvar series = [\n\t\t\t\t[],\n\t\t\t\t[]\n\t\t\t];\n\t\t\tvar initial = val['navigationStart'].sum / val['navigationStart'].count;\n\t\t\tvar prev = initial;\n\t\t\tfor (var i = 0; i < events.length; i++) {\n\t\t\t\tvar start = val[events[i][0]];\n\t\t\t\tvar end = val[events[i][1]];\n\t\t\t\tif (start && start.sum > 0) {\n\t\t\t\t\tstart = start.sum / start.count;\n\t\t\t\t} else {\n\t\t\t\t\tstart = (i > 0 ? series[0][i - 1] + initial : initial);\n\t\t\t\t}\n\t\t\t\tif (end && end.sum > 0) {\n\t\t\t\t\tend = end.sum / end.count;\n\t\t\t\t} else {\n\t\t\t\t\tend = start;\n\t\t\t\t}\n\t\t\t\tseries[0].push(start - initial);\n\t\t\t\tseries[1].push(end - initial);\n\t\t\t}\n\t\t\treturn series;\n\t\t}\n\n\t\tfunction drawGraph(el, series) {\n\t\t\t$.jqplot(el, series, {\n\t\t\t\tstackSeries: true,\n\t\t\t\tseriesDefaults: {\n\t\t\t\t\trenderer: $.jqplot.BarRenderer,\n\t\t\t\t\trendererOptions: {\n\t\t\t\t\t\tbarDirection: 'horizontal',\n\t\t\t\t\t\tbarPadding: 0,\n\t\t\t\t\t\tbarMargin: 0,\n\t\t\t\t\t\tshadowDepth: 0,\n\t\t\t\t\t\tstacked: true\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tseries: [{\n\t\t\t\t\tcolor: 'rgba(0,0,0,0)'\n\t\t\t\t}],\n\t\t\t\taxes: {\n\t\t\t\t\tyaxis: {\n\t\t\t\t\t\trenderer: $.jqplot.CategoryAxisRenderer,\n\t\t\t\t\t\tticks: ticks,\n\t\t\t\t\t\ttickRenderer: $.jqplot.CanvasAxisTickRenderer\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t\tgrid: {\n\t\t\t\t\tshadow: false,\n\t\t\t\t\tborderWidth: 0\n\t\t\t\t},\n\t\t\t});\n\t\t}\n\n\t\tvar id = 'networkTimings' + Math.floor(Math.random() * 10000);\n\n\t\tfunction link(scope, element, attrs) {\n\t\t\tscope.$watch('data', function(val) {\n\t\t\t\tif (val && !angular.equals(val, {})) {\n\t\t\t\t\tdrawGraph(id, prepareData(val));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\trestrict: 'E',\n\t\t\ttransclude: true,\n\t\t\tscope: {\n\t\t\t\tdata: \"=\"\n\t\t\t},\n\t\t\tlink: link,\n\t\t\ttemplate: '<div ng-hide=\"error\" id=\"' + id + '\"></div>'\n\t\t};\n\t});"
  },
  {
    "path": "www/app/summary/paintCycleGraph.js",
    "content": "angular\n\t.module('paintCycleGraph', [])\n\t.directive('pjPaintCycleGraph', function() {\n\t\tvar id = 'paintCycle' + Math.floor(Math.random() * 10000);\n\n\t\tfunction prepareData(data) {\n\t\t\tvar paints = [];\n\t\t\tangular.forEach(['Layout', 'CompositeLayers', 'Paint', 'RecalculateStyles'], function(key) {\n\t\t\t\tpaints.push([key, data[key].sum]);\n\t\t\t});\n\t\t\treturn paints;\n\t\t}\n\n\t\tfunction drawGraph(el, data) {\n\t\t\t$.jqplot(el, [data], {\n\t\t\t\tseriesColors: ['#7AA9E5', '#EFC453', '#9A7EE6', '#71B363'],\n\t\t\t\tseriesDefaults: {\n\t\t\t\t\trenderer: $.jqplot.PieRenderer,\n\t\t\t\t\trendererOptions: {\n\t\t\t\t\t\tshowDataLabels: true,\n\t\t\t\t\t\tdataLabels: ['value'],\n\t\t\t\t\t\tdataLabelFormatString: '%d ms',\n\t\t\t\t\t\thighlightMouseOver: true\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tgrid: {\n\t\t\t\t\tshadow: false,\n\t\t\t\t\tborderWidth: 0\n\t\t\t\t},\n\t\t\t\tlegend: {\n\t\t\t\t\tshow: true,\n\t\t\t\t\tlocation: 'e'\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tfunction link(scope, element, attrs) {\n\t\t\tscope.$watch('data', function(val) {\n\t\t\t\tif (val && !angular.equals(val, {})) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tdrawGraph(id, prepareData(val));\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tscope.error = e;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn {\n\t\t\tlink: link,\n\t\t\trestrict: 'E',\n\t\t\ttransclude: true,\n\t\t\tscope: {\n\t\t\t\tdata: \"=\"\n\t\t\t},\n\t\t\ttemplate: '<div ng-hide=\"error\" id=\"' + id + '\"></div><div ng-transclude ng-show=\"error\"></div>'\n\t\t};\n\t});"
  },
  {
    "path": "www/app/summary/summary.html",
    "content": "<div class=\"page summary\">\r\n\t<h1 class = \"page-header\">\r\n\t\t<small>Summary of</small>\r\n\t\t{{pagename}}\r\n\t\t<small>on {{browser}}</small>\r\n\t\t<small class=\"page-details pull-right\">\r\n\t\t\t<a class=\"btn btn-xs btn-primary\" href=\"#/page-select\" title=\"Select a different page or browser\">change</a>\r\n\t\t</small>\r\n\t</h1>\r\n\t<div class=\"row\">\r\n\t\t<div class=\"col-lg-7 col-sm-12\">\r\n\t\t\t<div class=\"panel panel-default\">\r\n\t\t\t\t<div class=\"panel-heading clearfix\">\r\n\t\t\t\t\t<span class=\"icon-movie\"></span>\r\n\t\t\t\t\tFrame Rate Trend\r\n\t\t\t\t\t<small class=\"pull-right\">(higher is better)</small>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"panel-body\">\r\n\t\t\t\t\t<pj-metrics-details-graph class=\"clearfix graph\" style=\"height:500px\" data=\"summary.frameRateData\" unit=\"'frames per second'\">\r\n\t\t\t\t\t\t<span class=\"graph-error\">\r\n\t\t\t\t\t\t\t<span class=\"icon-attention\"></span>\r\n\t\t\t\t\t\t\tCould not plot graph due to insufficient data\r\n\t\t\t\t\t\t</span>\r\n\t\t\t\t\t</pj-metrics-details-graph>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t\t<div class=\"col-lg-5 col-sm-12\">\r\n\t\t\t<div class=\"panel panel-default test-runs\">\r\n\t\t\t\t<div class=\"panel-heading\">\r\n\t\t\t\t\t<span class=\"icon-flag\"></span>\r\n\t\t\t\t\tTest runs\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"list-group-item list-group-item-warning\">\r\n\t\t\t\t\t<strong>Deploy/Run Tag</strong>\r\n\t\t\t\t\t<span class=\"pull-right\"># times run</span>\r\n\t\t\t\t</div>\r\n\t\t\t\t<div class=\"content\">\r\n\t\t\t\t\t<a ng-repeat=\"run in summary.runList\"  href=\"#/summary?pagename={{run.pagename}}&browser={{run.browser}}&time={{run.time}}\" class=\"list-group-item\" ng-class=\"{active:run.time===summary.time}\" >\r\n\t\t\t\t\t\t{{run.run}}\r\n\t\t\t\t\t\t<span class=\"badge\">{{run.runCount}}</span>\r\n\t\t\t\t\t</a>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n\t<!--div class = \"tiles\">\r\n\t<div class=\"col-md-3 col-sm-6\" ng-repeat=\"tile in summary.tiles\">\r\n\t\t<div class=\"panel\" ng-class=\"'panel-' + $index\">\r\n\t\t\t<div class=\"panel-heading\">\r\n\t\t\t\t<div class=\"row\">\r\n\t\t\t\t\t<div class=\"col-xs-3\">\r\n\t\t\t\t\t\t<span class = \"icon\" ng-class=\"'icon-' + tile.metric\"></span>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t\t<div class=\"col-xs-9 text-right\">\r\n\t\t\t\t\t\t<div class=\"huge\">{{tile.value | formatMetricValue: tile.unit}}</div>\r\n\t\t\t\t\t\t<div class=\"unit\">{{tile.unit}}</div>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t\t<a ng-href=\"#/detail?pagename={{pagename}}&browser={{browser}}&metric={{tile.metric}}\">\r\n\t\t\t\t<div class=\"panel-footer\">\r\n\t\t\t\t\t<span class=\"pull-left\">{{tile.metric | formatMetric}}</span>\r\n\t\t\t\t\t<span class=\"pull-right\">&raquo;</span>\r\n\t\t\t\t\t<div class=\"clearfix\"></div>\r\n\t\t\t\t</div>\r\n\t\t\t</a>\r\n\t\t</div>\r\n\t</div>\r\n</div-->\r\n<div class=\"row\">\r\n\t<div class=\"col-lg-5 col-sm-12\">\r\n\t\t<div class=\"panel panel-default\">\r\n\t\t\t<div class=\"panel-heading\">\r\n\t\t\t\t<span class=\"icon-paintbucket\"></span>\r\n\t\t\t\tPaint Cycle\r\n\t\t\t\t<small> <em>for selected run</em>\r\n\t\t\t\t</small>\r\n\t\t\t</div>\r\n\t\t\t<pj-paint-cycle-graph data=\"summary.currentRunData\">\r\n\t\t\t\t<span class=\"graph-error\">\r\n\t\t\t\t\t<span class=\"icon-attention\"></span>\r\n\t\t\t\t\tCould not plot graph. This could be due to insufficient data\r\n\t\t\t\t</span>\r\n\t\t\t</pj-paint-cycle-graph>\r\n\t\t</div>\r\n\t</div>\r\n\t<div class=\"col-lg-7 col-sm-12\">\r\n\t\t<div class=\"panel panel-default\">\r\n\t\t\t<div class=\"panel-heading\">\r\n\t\t\t\t<span class=\"icon-signal\"></span>\r\n\t\t\t\tNetwork\r\n\t\t\t\t<small> <em>for selected run</em>\r\n\t\t\t\t</small>\r\n\t\t\t</div>\r\n\t\t\t<div class=\"panel-body\">\r\n\t\t\t\t<pj-network-timing-graph class=\"network-timing\" data=\"summary.currentRunData\"></pj-network-timing-graph>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n</div>\r\n</div>"
  },
  {
    "path": "www/app/summary/summary.js",
    "content": "angular\r\n\t.module('summary', ['ngRoute', 'paintCycleGraph', 'networkTiming', 'Backend'])\r\n\t.config(['$routeProvider',\r\n\t\tfunction($routeProvider) {\r\n\t\t\t$routeProvider.when('/summary', {\r\n\t\t\t\ttemplateUrl: 'app/summary/summary.html',\r\n\t\t\t\tcontroller: 'SummaryCtrl',\r\n\t\t\t\tcontrollerAs: 'summary',\r\n\t\t\t\tresolve: {\r\n\t\t\t\t\trunList: ['Data', '$route',\r\n\t\t\t\t\t\tfunction(data, $route) {\r\n\t\t\t\t\t\t\tvar res = {};\r\n\t\t\t\t\t\t\tvar params = $route.current.params;\r\n\t\t\t\t\t\t\treturn data.runList({\r\n\t\t\t\t\t\t\t\tbrowser: params.browser,\r\n\t\t\t\t\t\t\t\tpagename: params.pagename\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t],\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t])\r\n\t.controller('SummaryCtrl', ['$routeParams', '$location', 'runList', 'Data',\r\n\t\tfunction($routeParams, $location, runList, Data) {\r\n\t\t\tthis.time = $routeParams.time || runList[0].time;\r\n\t\t\tthis.runList = runList;\r\n\t\t\tvar self = this;\r\n\r\n\t\t\tthis.tiles = [];\r\n\t\t\tthis.currentRunData = {};\r\n\t\t\tvar metric = 'framesPerSec_raf';\r\n\r\n\t\t\tData.runData({\r\n\t\t\t\tbrowser: $routeParams.browser,\r\n\t\t\t\tpagename: $routeParams.pagename,\r\n\t\t\t\ttime: this.time\r\n\t\t\t}).then(function(data) {\r\n\t\t\t\tself.currentRunData = data;\r\n\t\t\t\tData.metricsData({\r\n\t\t\t\t\tbrowser: $routeParams.browser,\r\n\t\t\t\t\tpagename: $routeParams.pagename,\r\n\t\t\t\t\tmetric: data['frames_per_sec'] ? 'frames_per_sec' : 'framesPerSec_raf',\r\n\t\t\t\t\tlimit: 20\r\n\t\t\t\t}).then(function(data) {\r\n\t\t\t\t\tself.frameRateData = data;\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t}\r\n\t]);"
  },
  {
    "path": "www/app/summary/summary.less",
    "content": ".summary{\n\t.test-runs{\n\t\t.list-group-item{\n\t\t\tborder-radius: 0 !important;\n\t\t\tborder-left: none;\n\t\t\tborder-right: none;\n\t\t\t&.heading{\n\t\t\t\tborder-bottom: SOLID 1px gray;\n\t\t\t}\n\t\t}\n\t\t.content{\n\t\t\theight: 490px;\n\t\t\toverflow: scroll;\n\t\t}\n\t}\n}"
  },
  {
    "path": "www/app/summary/tiles.js",
    "content": "angular\n\t.module('summaryTiles', ['Backend'])\n\t.directive('pjSummaryTiles', ['Data', '$q',\n\t\tfunction(data, $q) {\n\t\t\tvar _metricsList;\n\n\t\t\tvar metricsList = function() {\n\t\t\t\tif (_metricsList) {\n\t\t\t\t\treturn $q.when(_metricsList);\n\t\t\t\t} else {\n\t\t\t\t\treturn data.getAllMetrics().then(function(result) {\n\t\t\t\t\t\treturn _metricsList = result;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tvar prepareData = function(val) {\n\t\t\t\tvar tiles = [];\n\t\t\t\treturn metricsList().then(function(metricsList) {\n\t\t\t\t\tfunction getMetricUnit(metric) {\n\t\t\t\t\t\tfor (var i = 0; i < metricsList.length; i++) {\n\t\t\t\t\t\t\tif (metric === metricsList[i].name) {\n\t\t\t\t\t\t\t\treturn metricsList[i].unit;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn '';\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tangular.forEach(['frames_per_sec', 'framesPerSec_raf', 'firstPaint', 'ExpensivePaints', 'NodePerLayout_avg', 'ExpensiveEventHandlers', ], function(metric) {\n\t\t\t\t\t\tif (typeof val[metric] === 'object') {\n\t\t\t\t\t\t\ttiles.push({\n\t\t\t\t\t\t\t\tmetric: metric,\n\t\t\t\t\t\t\t\tunit: getMetricUnit(metric),\n\t\t\t\t\t\t\t\tvalue: val[metric].sum / val[metric].count,\n\t\t\t\t\t\t\t\tlink: metric\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\treturn tiles;\n\t\t\t\t});\n\t\t\t};\n\n\t\t\treturn {\n\t\t\t\trestrict: 'E',\n\t\t\t\ttransclude: true,\n\t\t\t\tscope: {\n\t\t\t\t\tdata: \"=\",\n\t\t\t\t\tpagename: \"=\",\n\t\t\t\t\tbrowser: \"=\"\n\t\t\t\t},\n\t\t\t\tlink: function(scope, element, attrs) {\n\t\t\t\t\tscope.$watch('data', function(val) {\n\t\t\t\t\t\tif (!val) {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tprepareData(val).then(function(res) {\n\t\t\t\t\t\t\tscope.tiles = res.slice(0, 4);\n\t\t\t\t\t\t});\n\t\t\t\t\t});\n\t\t\t\t},\n\t\t\t\ttemplateUrl: 'app/summary/tiles.tpl.html'\n\t\t\t};\n\t\t}\n\t]);"
  },
  {
    "path": "www/app/summary/tiles.less",
    "content": ".tiles {\n\t.panel {\n\t\t&.panel-0 {\n\t\t\tbackground: #428bca;\n\t\t\tborder-color: #428bca;\n\t\t\ta {\n\t\t\t\tcolor: #2a6496;\n\t\t\t}\n\t\t}\n\t\t&.panel-1 {\n\t\t\tbackground: #5cb85c;\n\t\t\tborder-color: #5cb85c;\n\t\t\ta {\n\t\t\t\tcolor: #5cb85c;\n\t\t\t}\n\t\t}\n\t\t&.panel-2 {\n\t\t\tbackground: #f0ad4e;\n\t\t\tborder-color: #f0ad4e;\n\t\t\ta {\n\t\t\t\tcolor: #f0ad4e;\n\t\t\t}\n\t\t}\n\t\t&.panel-3 {\n\t\t\tbackground: #d9534f;\n\t\t\tborder-color: #d9534f;\n\t\t\ta {\n\t\t\t\tcolor: #d9534f;\n\t\t\t}\n\t\t}\n\t\t.panel-heading {\n\t\t\tcolor: white;\n\t\t\t.huge {\n\t\t\t\tfont-size: 3em;\n\t\t\t}\n\t\t\t.unit {\n\t\t\t\tmargin-top: -3px;\n\t\t\t}\n\t\t\t.icon {\n\t\t\t\tmargin-left: -10px;\n\t\t\t\tfont-size: 4em;\n\t\t\t\t&.icon-frames_per_sec:before {\n\t\t\t\t\tcontent: '\\e815';\n\t\t\t\t}\n\t\t\t\t&.icon-ExpensivePaints:before{\n\t\t\t\t\tcontent: '\\e804';\n\t\t\t\t}\n\t\t\t\t&.icon-ExpensiveEventHandlers:before{\n\t\t\t\t\tcontent: '\\e80b';\n\t\t\t\t}\n\t\t\t\t&.icon-NodePerLayout_avg:before{\n\t\t\t\t\tcontent: '\\e808';\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "www/app/summary/tiles.tpl.html",
    "content": "<div class=\"col-md-3 col-sm-6\" ng-repeat=\"tile in tiles\">\n\t<div class=\"panel\" ng-class=\"'panel-' + $index\">\n\t\t<div class=\"panel-heading\">\n\t\t\t<div class=\"row\">\n\t\t\t\t<div class=\"col-xs-3\">\n\t\t\t\t\t<span class = \"icon\" ng-class=\"'icon-' + tile.metric\"></span>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"col-xs-9 text-right\">\n\t\t\t\t\t<div class=\"huge\">{{tile.value | formatMetricValue: tile.unit}}</div>\n\t\t\t\t\t<div class=\"unit\">{{tile.unit}}</div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t\t<a ng-href=\"#/detail?pagename={{pagename}}&browser={{browser}}&metric={{tile.metric}}\">\n\t\t\t<div class=\"panel-footer\">\n\t\t\t\t<span class=\"pull-left\">{{tile.metric | formatMetric}}</span>\n\t\t\t\t<span class=\"pull-right\">&raquo;</span>\n\t\t\t\t<div class=\"clearfix\"></div>\n\t\t\t</div>\n\t\t</a>\n\t</div>\n</div>\n"
  },
  {
    "path": "www/assets/css/animation.css",
    "content": "/*\n   Animation example, for spinners\n*/\n.animate-spin {\n  -moz-animation: spin 2s infinite linear;\n  -o-animation: spin 2s infinite linear;\n  -webkit-animation: spin 2s infinite linear;\n  animation: spin 2s infinite linear;\n  display: inline-block;\n}\n@-moz-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@-webkit-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@-o-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@-ms-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n@keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n    -o-transform: rotate(0deg);\n    -webkit-transform: rotate(0deg);\n    transform: rotate(0deg);\n  }\n\n  100% {\n    -moz-transform: rotate(359deg);\n    -o-transform: rotate(359deg);\n    -webkit-transform: rotate(359deg);\n    transform: rotate(359deg);\n  }\n}\n"
  },
  {
    "path": "www/assets/css/config.json",
    "content": "{\n  \"name\": \"\",\n  \"css_prefix_text\": \"icon-\",\n  \"css_use_suffix\": false,\n  \"hinting\": true,\n  \"units_per_em\": 1000,\n  \"ascent\": 850,\n  \"glyphs\": [\n    {\n      \"uid\": \"5156114528976f4ffab0f04526d12e89\",\n      \"css\": \"safari\",\n      \"code\": 59394,\n      \"src\": \"custom_icons\",\n      \"selected\": true,\n      \"svg\": {\n        \"path\": \"M347.2 626.7C335.7 610.9 327.5 592.3 323.9 571.7 311.1 499 359.6 429.8 432.3 416.9 452.8 413.3 473 414.6 491.7 419.9L405.1 512.8ZM507.8 687.9C531.9 678.9 552.6 664 568.6 645.2L651.2 682.9 585.6 620.7C597.5 599.4 604.2 575.1 604.4 549.8L755.5 496.1 594.3 494.6C588.3 479.2 579.9 465.1 569.6 452.8L675.3 222.9 503.3 407.5C488.3 402.4 472.3 399.6 455.8 399.6L456.3 399.5 402.4 247.8 401 410C378.2 418.9 358.5 433.1 343.1 450.9L262.7 414.3 326.3 474.6C313.7 496.4 306.7 521.6 306.6 547.9L306 545.8 154.3 599.7 316.2 601.2C321.9 616.1 329.8 629.8 339.5 641.8L229.1 858.9 401.9 687.4C417.9 693.6 435.1 697.1 452.9 697.4L506.4 848ZM531.8 992.4C285.8 1035.8 51.2 871.5 7.9 625.5-32 399.6 103.3 183.3 316.1 115.9 303.1 107.1 294 94.5 291.4 79.4 285.3 45.2 314.9 11.3 357.5 3.8 400.1-3.7 439.5 18 445.5 52.2 448.1 67.3 443.9 82.3 434.7 95 657.8 85.5 858.9 242.5 898.7 468.4 942.1 714.4 777.8 949 531.8 992.4ZM478.7 680.1C456.2 684 434.1 682.1 413.9 675.5L511.8 578.4 562.4 468.4C574.6 484.6 583.3 503.9 587 525.3 599.9 598 551.3 667.3 478.7 680.1ZM462.8 589.9C439.9 593.9 418.2 578.6 414.1 555.8 410.1 533 425.4 511.2 448.2 507.2 471 503.1 492.8 518.4 496.8 541.2 500.9 564 485.6 585.8 462.8 589.9ZM393.5 90C404.1 82.7 410.2 71.7 408.2 60.7 405.3 44.1 385.3 33.8 363.5 37.6 341.7 41.5 326.4 58.1 329.3 74.7 331.2 85.7 340.7 93.9 353.2 97.1 353.8 88.4 360.9 80.7 370.8 79 380.6 77.3 389.9 82 393.5 90ZM393.5 90\",\n        \"width\": 908.3969465648854\n      },\n      \"search\": [\n        \"glyph\"\n      ]\n    },\n    {\n      \"uid\": \"d35c5d63e9d9056d3ba81c78c5a7fe58\",\n      \"css\": \"paintbucket\",\n      \"code\": 59406,\n      \"src\": \"custom_icons\",\n      \"selected\": true,\n      \"svg\": {\n        \"path\": \"M759.8 478.5C769.5 488.3 769.5 503.9 759.8 513.7L363.3 910.2V910.2C353.5 919.9 337.9 919.9 328.1 910.2L7.8 589.8C-2 580.1-2 562.5 7.8 552.7V552.7L279.3 281.3 168 168C158.2 158.2 152.3 146.5 152.3 132.8 152.3 105.5 175.8 82 203.1 82 216.8 82 228.5 87.9 238.3 97.7L349.6 210.9 402.3 158.2C412.1 148.4 429.7 148.4 439.5 158.2V158.2 158.2L759.8 478.5V478.5ZM562.5 570.3L636.7 496.1 421.9 281.3 130.9 570.3H562.5ZM839.8 738.3C851.6 753.9 857.4 771.5 857.4 791 857.4 841.8 816.4 884.8 765.6 884.8S671.9 841.8 671.9 791C671.9 769.5 679.7 752 691.4 736.3L750 634.8C750 632.8 752 634.8 752 632.8V630.9 630.9C755.9 627 759.8 625 765.6 625S775.4 628.9 779.3 632.8V632.8 632.8 634.8Z\",\n        \"width\": 857.421875\n      },\n      \"search\": [\n        \"glyph\"\n      ]\n    },\n    {\n      \"uid\": \"5d2d07f112b8de19f2c0dbfec3e42c05\",\n      \"css\": \"spin5\",\n      \"code\": 9676,\n      \"src\": \"fontelico\"\n    },\n    {\n      \"uid\": \"62c089cb34e74b3a1200bc7f5314eb4e\",\n      \"css\": \"firefox\",\n      \"code\": 59392,\n      \"src\": \"fontelico\"\n    },\n    {\n      \"uid\": \"9c2b737b16ae2c8d66b7bfd29ba5ecd8\",\n      \"css\": \"chrome\",\n      \"code\": 59393,\n      \"src\": \"fontelico\"\n    },\n    {\n      \"uid\": \"2a46f1d1c9bd036e17a74e46613c1636\",\n      \"css\": \"ie\",\n      \"code\": 59405,\n      \"src\": \"fontelico\"\n    },\n    {\n      \"uid\": \"7034e4d22866af82bef811f52fb1ba46\",\n      \"css\": \"code\",\n      \"code\": 59408,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"c76b7947c957c9b78b11741173c8349b\",\n      \"css\": \"attention\",\n      \"code\": 59409,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"26613a2e6bc41593c54bead46f8c8ee3\",\n      \"css\": \"file-code\",\n      \"code\": 59398,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"e99461abfef3923546da8d745372c995\",\n      \"css\": \"cog\",\n      \"code\": 59407,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"98687378abd1faf8f6af97c254eb6cd6\",\n      \"css\": \"cog-alt\",\n      \"code\": 59403,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"531bc468eecbb8867d822f1c11f1e039\",\n      \"css\": \"calendar\",\n      \"code\": 59412,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"4109c474ff99cad28fd5a2c38af2ec6f\",\n      \"css\": \"filter\",\n      \"code\": 59404,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"347c38a8b96a509270fdcabc951e7571\",\n      \"css\": \"database\",\n      \"code\": 59401,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"5e0a374728ffa8d0ae1f331a8f648231\",\n      \"css\": \"github\",\n      \"code\": 59399,\n      \"src\": \"fontawesome\"\n    },\n    {\n      \"uid\": \"1d2a6c3d9236b88b0f185c7c4530fa52\",\n      \"css\": \"flag\",\n      \"code\": 59410,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"84a7262985600b683bbab0da9298776d\",\n      \"css\": \"signal\",\n      \"code\": 59397,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"8a1d446e5555e76f82ddb1c8b526f579\",\n      \"css\": \"flow-tree\",\n      \"code\": 59400,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"3a6f0140c3a390bdb203f56d1bfdefcb\",\n      \"css\": \"gauge\",\n      \"code\": 59402,\n      \"src\": \"entypo\"\n    },\n    {\n      \"uid\": \"ef8560a06ed46a192092bf1f08c142a6\",\n      \"css\": \"sort-alphabet-outline\",\n      \"code\": 59395,\n      \"src\": \"typicons\"\n    },\n    {\n      \"uid\": \"b3a9e2dab4d19ea3b2f628242c926bfe\",\n      \"css\": \"brush\",\n      \"code\": 59396,\n      \"src\": \"iconic\"\n    },\n    {\n      \"uid\": \"d2c499942f8a7c037d5a94f123eeb478\",\n      \"css\": \"layers\",\n      \"code\": 59411,\n      \"src\": \"iconic\"\n    },\n    {\n      \"uid\": \"eea613bc40c77b7eab137b29dba0c62f\",\n      \"css\": \"movie\",\n      \"code\": 59413,\n      \"src\": \"mfglabs\"\n    },\n    {\n      \"uid\": \"aaf371ab44841e9aaffebd179d324ce4\",\n      \"css\": \"android\",\n      \"code\": 59414,\n      \"src\": \"zocial\"\n    }\n  ]\n}"
  },
  {
    "path": "www/assets/css/fontello-codes.css",
    "content": "\n.icon-spin5:before { content: '\\25cc'; } /* '◌' */\n.icon-firefox:before { content: '\\e800'; } /* '' */\n.icon-chrome:before { content: '\\e801'; } /* '' */\n.icon-safari:before { content: '\\e802'; } /* '' */\n.icon-sort-alphabet-outline:before { content: '\\e803'; } /* '' */\n.icon-brush:before { content: '\\e804'; } /* '' */\n.icon-signal:before { content: '\\e805'; } /* '' */\n.icon-file-code:before { content: '\\e806'; } /* '' */\n.icon-github:before { content: '\\e807'; } /* '' */\n.icon-flow-tree:before { content: '\\e808'; } /* '' */\n.icon-database:before { content: '\\e809'; } /* '' */\n.icon-gauge:before { content: '\\e80a'; } /* '' */\n.icon-cog-alt:before { content: '\\e80b'; } /* '' */\n.icon-filter:before { content: '\\e80c'; } /* '' */\n.icon-ie:before { content: '\\e80d'; } /* '' */\n.icon-paintbucket:before { content: '\\e80e'; } /* '' */\n.icon-cog:before { content: '\\e80f'; } /* '' */\n.icon-code:before { content: '\\e810'; } /* '' */\n.icon-attention:before { content: '\\e811'; } /* '' */\n.icon-flag:before { content: '\\e812'; } /* '' */\n.icon-layers:before { content: '\\e813'; } /* '' */\n.icon-calendar:before { content: '\\e814'; } /* '' */\n.icon-movie:before { content: '\\e815'; } /* '' */\n.icon-android:before { content: '\\e816'; } /* '' */"
  },
  {
    "path": "www/assets/fonts/fontello-codes.css",
    "content": "\n.icon-firefox:before { content: '\\e800'; } /* '' */\n.icon-chrome:before { content: '\\e801'; } /* '' */\n.icon-safari:before { content: '\\e802'; } /* '' */\n.icon-sort-alphabet-outline:before { content: '\\e803'; } /* '' */\n.icon-brush:before { content: '\\e804'; } /* '' */\n.icon-signal:before { content: '\\e805'; } /* '' */\n.icon-file-code:before { content: '\\e806'; } /* '' */\n.icon-github:before { content: '\\e807'; } /* '' */\n.icon-spin5:before { content: '\\e808'; } /* '' */\n.icon-database:before { content: '\\e809'; } /* '' */\n.icon-gauge:before { content: '\\e80a'; } /* '' */\n.icon-cog-alt:before { content: '\\e80b'; } /* '' */\n.icon-filter:before { content: '\\e80c'; } /* '' */\n.icon-ie:before { content: '\\e80d'; } /* '' */\n.icon-paintbucket:before { content: '\\e80e'; } /* '' */\n.icon-cog:before { content: '\\e80f'; } /* '' */\n.icon-code:before { content: '\\e810'; } /* '' */"
  },
  {
    "path": "www/index.html",
    "content": "<!DOCTYPE HTML>\r\n<html ng-app=\"perfjankie\">\r\n<head>\r\n\t<title>PerfJankie - Rendering Performance Analysis</title>\r\n\r\n\t<!-- build:remove:dist -->\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"bootstrap/dist/css/bootstrap.min.css\"/>\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"jqplot-bower/dist/jquery.jqplot.min.css\" />\r\n\t<!-- /build -->\r\n\r\n\t<!-- build:remove:dev -->\r\n\t<link href=\"//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css\" rel=\"stylesheet\">\r\n\t<!-- /build -->\r\n\r\n\t<link rel=\"stylesheet\" type=\"text/css\" href=\"main.css\" />\r\n</head>\r\n\r\n<body ng-controller=\"MainPageCtrl\" ng-class=\"{'no-pj-brand': noPjBrand}\">\r\n\t<!-- build:include:dist app/main-page/navbar.html -->\r\n\t<div ng-include=\"'app/main-page/navbar.html'\"></div>\r\n\t<!-- /build -->\r\n\r\n\t<!-- build:include:dist app/main-page/sidebar.html -->\r\n\t<div ng-include=\"'app/main-page/sidebar.html'\"></div>\r\n\t<!-- /build -->\r\n\r\n\t<div class=\"container-fluid content-container\">\r\n\t\t<div class=\"row\">\r\n\t\t\t<div class=\"col-md-10 col-md-offset-2\">\r\n\t\t\t\t<div class=\"content\" ng-view ng-hide=\"pageError\"></div>\r\n\t\t\t\t<div class=\"content\" style=\"display:none\" ng-class=\"{show: pageError}\">\r\n\t\t\t\t\t<div class=\"jumbotron error\">\r\n\t\t\t\t\t\t<h2>\r\n\t\t\t\t\t\t\t<span class=\"icon icon-attention\"></span>\r\n\t\t\t\t\t\t\tError loading page\r\n\t\t\t\t\t\t</h2>\r\n\t\t\t\t\t\t<p class=\"lead\">\r\n\t\t\t\t\t\t\tAn error occured when trying to load this page.\r\n\t\t\t\t\t\t\t<br/>\r\n\t\t\t\t\t\t\tPlease\r\n\t\t\t\t\t\t\t<a onclick=\"document.location.reload()\">refresh</a>\r\n\t\t\t\t\t\t\tthis page, \r\n\t\t\t\t\t\t\tor go back to the\r\n\t\t\t\t\t\t\t<a ng-click=\"goHome()\">home page</a>\r\n\t\t\t\t\t\t\t.\r\n\t\t\t\t\t\t</p>\r\n\t\t\t\t\t</div>\r\n\t\t\t\t</div>\r\n\t\t\t</div>\r\n\t\t</div>\r\n\t</div>\r\n\r\n\t<div class=\"page-loading\" ng-show=\"pageLoading\">\r\n\t\t<div class=\"spin-container\">\r\n\t\t\t<span class=\"icon-spin5 animate-spin\"></span>\r\n\t\t</div>\r\n\t</div>\r\n\r\n\t<!-- build:remove:dev -->\r\n\t<script type=\"text/javascript\" src = \"//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js\"></script>\r\n\t<script type=\"text/javascript\" src = \"//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular.min.js\"></script>\r\n\t<script type=\"text/javascript\" src = \"//ajax.googleapis.com/ajax/libs/angularjs/1.2.18/angular-route.js\"></script>\r\n\t<script type=\"text/javascript\" src = \"//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js\"></script>\r\n\t<!-- /build -->\r\n\r\n\t<!-- build:remove:dist -->\r\n\t<script type=\"text/javascript\" src=\"jquery/dist/jquery.min.js\"></script>\r\n\t<script type=\"text/javascript\" src=\"angular/angular.min.js\"></script>\r\n\t<script type=\"text/javascript\" src=\"angular-route/angular-route.min.js\"></script>\r\n\t<script type=\"text/javascript\" src=\"bootstrap/dist/js/bootstrap.min.js\"></script>\r\n\r\n\t<script type=\"text/javascript\" src=\"server/endpoints.js\"></script>\r\n\t<script type=\"text/javascript\" src=\"metrics.js\"></script>\r\n\t<!-- /build -->\r\n\r\n\t<!-- build:template\r\n\t<% _.each(scripts, function(script){ %>\r\n\t<script type=\"text/javascript\" src=\"<%= script%>\"></script>\r\n\t<% }); %>\r\n\t/build -->\r\n\t<!-- -->\r\n</body>\r\n</html>"
  },
  {
    "path": "www/server/endpoints.js",
    "content": "if (typeof window.DB_BASE !== 'string') {\r\n\twindow.DB_BASE = '..';\r\n}\r\n\r\nangular\r\n\t.module('Endpoints', [])\r\n\t.factory('Resource', ['$http', '$q',\r\n\t\tfunction($http, $q) {\r\n\t\t\tfunction rowsToObj(row, keys, val) {\r\n\t\t\t\tvar res = {};\r\n\t\t\t\tres[val] = row.value;\r\n\t\t\t\tangular.forEach(keys, function(key, i) {\r\n\t\t\t\t\tres[key] = row.key[i];\r\n\t\t\t\t});\r\n\t\t\t\treturn res;\r\n\t\t\t}\r\n\r\n\t\t\tvar server = {\r\n\t\t\t\t'/pagelist': function() {\r\n\t\t\t\t\treturn $http.get(window.DB_BASE + '/pagelist/_view/pages?group=true').then(function(resp) {\r\n\t\t\t\t\t\tvar result = {};\r\n\t\t\t\t\t\tangular.forEach(resp.data.rows, function(row) {\r\n\t\t\t\t\t\t\tvar res = rowsToObj(row, ['suite', 'pagename', 'browser'], 'runCount');\r\n\t\t\t\t\t\t\tresult[res.suite] = result[res.suite] || {};\r\n\t\t\t\t\t\t\tresult[res.suite][res.pagename] = result[res.suite][res.pagename] || [];\r\n\t\t\t\t\t\t\tresult[res.suite][res.pagename].push({\r\n\t\t\t\t\t\t\t\tbrowser: res.browser,\r\n\t\t\t\t\t\t\t\trunCount: res.runCount\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\treturn result;\r\n\t\t\t\t\t});\r\n\t\t\t\t},\r\n\t\t\t\t'/all-metrics': function() {\r\n\t\t\t\t\treturn $q.when(window.METRICS_LIST);\r\n\t\t\t\t},\r\n\t\t\t\t'/runList': function(opts) {\r\n\t\t\t\t\treturn $http.get(window.DB_BASE + '/runs/_view/list', {\r\n\t\t\t\t\t\tparams: {\r\n\t\t\t\t\t\t\tendkey: JSON.stringify([opts.browser, opts.pagename, null]),\r\n\t\t\t\t\t\t\tstartkey: JSON.stringify([opts.browser, opts.pagename, {}]),\r\n\t\t\t\t\t\t\tgroup: true,\r\n\t\t\t\t\t\t\tdescending: true\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}).then(function(resp) {\r\n\t\t\t\t\t\tvar res = [];\r\n\t\t\t\t\t\tangular.forEach(resp.data.rows, function(row) {\r\n\t\t\t\t\t\t\tres.push(rowsToObj(row, ['browser', 'pagename', 'time', 'run'], 'runCount'));\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\treturn res;\r\n\t\t\t\t\t});\r\n\t\t\t\t},\r\n\t\t\t\t'/runData': function(opts) {\r\n\t\t\t\t\treturn $http.get(window.DB_BASE + '/runs/_view/data', {\r\n\t\t\t\t\t\tparams: {\r\n\t\t\t\t\t\t\tstartkey: JSON.stringify([opts.browser, opts.pagename, opts.time, null]),\r\n\t\t\t\t\t\t\tendkey: JSON.stringify([opts.browser, opts.pagename, opts.time, {}]),\r\n\t\t\t\t\t\t\tgroup: true\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}).then(function(resp) {\r\n\t\t\t\t\t\tvar res = {};\r\n\t\t\t\t\t\tangular.forEach(resp.data.rows, function(row) {\r\n\t\t\t\t\t\t\tvar obj = rowsToObj(row, ['browser', 'pagename', 'time', 'run', 'metric'], 'value');\r\n\t\t\t\t\t\t\tres[obj.metric] = obj.value;\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\treturn res;\r\n\t\t\t\t\t});\r\n\t\t\t\t},\r\n\t\t\t\t'/metrics-data': function(opts) {\r\n\t\t\t\t\tvar config = {\r\n\t\t\t\t\t\tparams: {\r\n\t\t\t\t\t\t\tendkey: JSON.stringify([opts.browser, opts.pagename, opts.metric, null]),\r\n\t\t\t\t\t\t\tstartkey: JSON.stringify([opts.browser, opts.pagename, opts.metric, {}]),\r\n\t\t\t\t\t\t\tgroup: true,\r\n\t\t\t\t\t\t\tdescending: true\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t};\r\n\t\t\t\t\tvar limit = parseInt(opts.limit, 10);\r\n\t\t\t\t\tif (!isNaN(limit)) {\r\n\t\t\t\t\t\tconfig.params.limit = limit;\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn $http.get(window.DB_BASE + '/metrics_data/_view/stats', config).then(function(resp) {\r\n\t\t\t\t\t\tvar res = [];\r\n\t\t\t\t\t\tangular.forEach(resp.data.rows, function(obj, index) {\r\n\t\t\t\t\t\t\tobj.label = obj.key[4];\r\n\t\t\t\t\t\t\tobj.key = obj.key[3];\r\n\t\t\t\t\t\t\tres.push(obj);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\treturn res;\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\t\tvar fetch = function(url, params) {\r\n\t\t\t\treturn server[url](params);\r\n\t\t\t};\r\n\r\n\t\t\treturn fetch;\r\n\t\t}\r\n\t]);"
  }
]