Skip to content

Commit

Permalink
Add links to switch between different reports in left nav-bar (#1477)
Browse files Browse the repository at this point in the history
  • Loading branch information
WeiweiAtGit authored and paulirish committed Jan 31, 2017
1 parent a5416c4 commit 527bf8e
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* @license
* Copyright 2017 Google Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

'use strict';

const fs = require('fs');
const path = require('path');
const rimraf = require('rimraf');
const assetSaver = require('../../../lighthouse-core/lib/asset-saver');

class ExperimentDatabase {
constructor(url, config) {
this._url = url;
this._config = config;

this._fsRoot = fs.mkdtempSync(`${__dirname}/experiment-data-`);
this._timeStamps = {};
}

get url() {
return this._url;
}

get config() {
return this._config;
}

get timeStamps() {
return this._timeStamps;
}

/*
* Save experiment data
* @param {!Object} lhFlags
* @param {!Object} lhResults
*/
saveData(lhFlags, lhResults) {
const id = assetSaver.getFilenamePrefix(lhResults);
this._timeStamps[id] = lhResults.generatedTime;

const dirPath = path.join(this._fsRoot, id);
fs.mkdirSync(dirPath);
fs.writeFileSync(path.join(dirPath, 'flags.json'), JSON.stringify(lhFlags));
fs.writeFileSync(path.join(dirPath, 'results.json'), JSON.stringify(lhResults));
return id;
}

/*
* Get report.html
* @param {string} id
*/
getResults(id) {
return JSON.parse(fs.readFileSync(path.join(this._fsRoot, id, 'results.json'), 'utf8'));
}

/*
* Get flags.json
* @param {string} id
*/
getFlags(id) {
return JSON.parse(fs.readFileSync(path.join(this._fsRoot, id, 'flags.json'), 'utf8'));
}

/*
* Delete all the files created by this object
*/
clear() {
rimraf.sync(this._fsRoot);
}
}

module.exports = ExperimentDatabase;
16 changes: 9 additions & 7 deletions lighthouse-cli/performance-experiment/report/scripts/perf-x.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class ConfigPanel {
this._messageField = this._configPanel.querySelector('.js-message');
this._urlBlockingList = this._configPanel.querySelector('.js-url-blocking-patterns');
this._urlBlockingStatus = {};
this._reportId = new URL(window.location).searchParams.get('id');

const bodyToggle = this._configPanel.querySelector('.js-panel-toggle');
bodyToggle.addEventListener('click', () => this._toggleBody());
Expand Down Expand Up @@ -84,16 +85,18 @@ class ConfigPanel {
});

// get and recover blocked URL patterns of current run
fetch('/blocked-url-patterns').then(response => {
fetch(`/flags?id=${this._reportId}`).then(response => {
return response.json();
}).then(blockedUrlPatterns => {
}).then(flags => {
const blockedUrlPatterns = flags.blockedUrlPatterns || [];
blockedUrlPatterns.forEach(urlPattern => this.addBlockedUrlPattern(urlPattern));
this.log('');
});
}

/**
* Send POST request to rerun lighthouse with additional flags.
* @return {!Promise} resolve when rerun is completed.
*/
_rerunLighthouse() {
this.log('Start Rerunning Lighthouse');
Expand All @@ -102,11 +105,10 @@ class ConfigPanel {
blockedUrlPatterns: this.getBlockedUrlPatterns()
};

return fetch('/rerun', {method: 'POST', body: JSON.stringify(options)}).then(() => {
location.reload();
}).catch(err => {
this.log(`Lighthouse Runtime Error: ${err}`);
});
return fetch(`/rerun?id=${this._reportId}`, {method: 'POST', body: JSON.stringify(options)})
.then(response => response.text())
.then(newReportId => location.assign(`?id=${newReportId}`))
.catch(err => this.log(`Lighthouse Runtime Error: ${err}`));
}

/**
Expand Down
137 changes: 90 additions & 47 deletions lighthouse-cli/performance-experiment/server.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,96 +21,139 @@
*
* Functionality:
* Host experiment.
* Report can be access via URL http://localhost:[PORT]/
* Rerun data can be access via URL http://localhost:[PORT]/rerun.
* This will rerun lighthouse with same parameters and rerun results in JSON format
* Report can be access via URL: /?id=[REPORT_ID]
* Browser can request lighthousr rerun by sending POST request to URL: /rerun?id=[REPORT_ID]
* This will rerun lighthouse with additional cli-flags received from POST request data and
* return the new report id
* Flags can be access via URL: /flags?id=[REPORT_ID]
*/

const http = require('http');
const parse = require('url').parse;
const opn = require('opn');
const log = require('../../lighthouse-core/lib/log');
const PerfXReportGenerator = require('./report/perf-x-report-generator');
const lighthouse = require('../../lighthouse-core');
const ExperimentDatabase = require('./experiment-database/database');
const PerfXReportGenerator = require('./report/perf-x-report-generator');


let database;
let fallbackReportId;
/**
* Start the server with an arbitrary port and open report page in the default browser.
* @param {!Object} params A JSON contains lighthouse parameters
* @param {!Object} results
* @return {!Promise<string>} Promise that resolves when server is closed
*/
let lhResults;
let lhParams;
function hostExperiment(params, results) {
lhResults = results;
lhParams = params;

return new Promise(resolve => {
database = new ExperimentDatabase(params.url, params.config);
const id = database.saveData(params.flags, results);
fallbackReportId = id;

const server = http.createServer(requestHandler);
server.listen(0);
server.on('listening', () => {
opn(`http://localhost:${server.address().port}/`);
});
server.on('listening', () => opn(`http://localhost:${server.address().port}/?id=${id}`));
server.on('error', err => log.error('PerformanceXServer', err.code, err));
server.on('close', resolve);
process.on('SIGINT', () => {
database.clear();
server.close();
});
});
}

function requestHandler(request, response) {
const pathname = parse(request.url).pathname;

if (request.method === 'GET') {
if (pathname === '/') {
reportRequestHandler(request, response);
} else if (pathname === '/blocked-url-patterns') {
blockedUrlPatternsRequestHandler(request, response);
request.parsedUrl = parse(request.url, true);
const pathname = request.parsedUrl.pathname;
try {
if (request.method === 'GET') {
if (pathname === '/') {
reportRequestHandler(request, response);
} else if (pathname === '/flags') {
flagsRequestHandler(request, response);
} else {
throw new HTTPError(404);
}
} else if (request.method === 'POST') {
if (pathname === '/rerun') {
rerunRequestHandler(request, response);
} else {
throw new HTTPError(404);
}
} else {
response.writeHead(404);
response.end('404: Resource Not Found');
throw new HTTPError(405);
}
} else if (request.method === 'POST') {
if (pathname === '/rerun') {
rerunRequestHandler(request, response);
} catch (err) {
if (err instanceof HTTPError) {
response.writeHead(err.statusCode);
response.end(err.message || http.STATUS_CODES[err.statusCode]);
} else {
response.writeHead(404);
response.end('404: Resource Not Found');
response.writeHead(500);
response.end(http.STATUS_CODES[500]);
log.err('PerformanceXServer', err.code, err);
}
} else {
response.writeHead(405);
response.end('405: Method Not Supported');
}
}

function reportRequestHandler(request, response) {
const html = new PerfXReportGenerator().generateHTML(lhResults, 'perf-x');
response.writeHead(200, {'Content-Type': 'text/html'});
response.end(html);
try {
const id = request.parsedUrl.query.id || fallbackReportId;

const reportsMetadata = Object.keys(database.timeStamps).map(key => {
const generatedTime = database.timeStamps[key];
return {url: database.url, reportHref: `/?id=${key}`, generatedTime};
});
reportsMetadata.sort((metadata1, metadata2) => {
return metadata1.generatedTime - metadata2.generatedTime;
});
const reportsCatalog = {reportsMetadata, selectedReportHref: `/?id=${id}`};

const results = database.getResults(id);
const perfXReportGenerator = new PerfXReportGenerator();

response.writeHead(200, {'Content-Type': 'text/html'});
response.end(perfXReportGenerator.generateHTML(results, 'perf-x', reportsCatalog));
} catch (err) {
throw new HTTPError(404);
}
}

function blockedUrlPatternsRequestHandler(request, response) {
response.writeHead(200, {'Content-Type': 'text/json'});
response.end(JSON.stringify(lhParams.flags.blockedUrlPatterns || []));
function flagsRequestHandler(request, response) {
try {
response.writeHead(200, {'Content-Type': 'text/json'});
response.end(JSON.stringify(database.getFlags(request.parsedUrl.query.id || fallbackReportId)));
} catch (err) {
throw new HTTPError(404);
}
}

function rerunRequestHandler(request, response) {
let message = '';
request.on('data', data => message += data);
try {
const flags = database.getFlags(request.parsedUrl.query.id || fallbackReportId);
let message = '';
request.on('data', data => message += data);

request.on('end', () => {
const additionalFlags = JSON.parse(message);
const flags = Object.assign(lhParams.flags, additionalFlags);
request.on('end', () => {
const additionalFlags = JSON.parse(message);
Object.assign(flags, additionalFlags);

lighthouse(lhParams.url, flags, lhParams.config).then(results => {
results.artifacts = undefined;
lhResults = results;
response.writeHead(200);
response.end();
lighthouse(database.url, flags, database.config).then(results => {
results.artifacts = undefined;
const id = database.saveData(flags, results);
response.writeHead(200);
response.end(id);
});
});
});
} catch (err) {
throw new HTTPError(404);
}
}

class HTTPError extends Error {
constructor(statusCode, message) {
super(message);
this.statusCode = statusCode;
}
}

module.exports = {
Expand Down
Loading

0 comments on commit 527bf8e

Please sign in to comment.