Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --view flag which will start a server and host report.html #1130

Merged
merged 12 commits into from
Dec 15, 2016

Conversation

weiwei-lin
Copy link
Contributor

This server will allow to host trace file in the future. #616

@googlebot
Copy link

Thanks for your pull request. It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

📝 Please visit https://cla.developers.google.com/ to sign.

Once you've signed, please reply here (e.g. I signed it!) and we'll verify. Thanks.


  • If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address. Check your existing CLA data and verify that your email is set on your git commits.
  • If you signed the CLA as a corporation, please let us know the company's name.

@googlebot
Copy link

CLAs look good, thanks!

});

function listen(port) {
return app.listen(port)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: ;

}

module.exports = {
listen: listen
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listen: app.listen?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I changed it to listen: app.listen.bind(app)

const fileAbsPath = `${__dirname}/server/reports/${filename}`;
Printer.write(results, 'html', fileAbsPath);
opn(`http://localhost:${_REPORT_SERVER_PORT}/reports/${filename}`);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So --view overrides output pretty?

const filename = `./${assetSaver.getFilenamePrefix({url: address})}.report.html`;
Printer.write(results, 'html', filename);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove blank line

@samuelli
Copy link

samuelli commented Dec 9, 2016

Need to fix travis errors.

@brendankenny
Copy link
Member

is it possible to post the plan for this to a public issue to give context to everyone who hasn't seen it?

@@ -8,6 +8,8 @@
"typescript": "^2.0.3"
},
"dependencies": {
"@types/node": "^6.0.45"
"@types/node": "^6.0.45",
"express": "^4.14.0",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

until we need more advanced server features, could we limit this to a more simple server a la static-server to avoid the extensive dependencies express brings in until we need them?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO express is very minimalist already. I think the trade off is writing less code or less dependencies. Express lets us do simple routing and deals with basic security issues in single lines. (current code can be cut down further).

const express = require('express');
const app = express();

app.get('/reports/:name', (request, response) => {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about using express.static() here if its all in one directory?

@samuelli
Copy link

samuelli commented Dec 9, 2016

@brendankenny: Yup, in this particular case #616 is just about viewing trace results.

@WeiweiAtGit: can you make sure you summaries the experiments ideas in a separate issue?

@weiwei-lin
Copy link
Contributor Author

weiwei-lin commented Dec 9, 2016

@samuelli Sure. I will summarize the project and put a link to the document later.

@ebidel ebidel changed the title Add --view flag which will start a sever and host report.html Add --view flag which will start a server and host report.html Dec 9, 2016
@@ -234,6 +240,14 @@ function lighthouseRun(addresses: Array<string>, config: Object, flags: Object)
Printer.write(results, 'html', filename);
}

// Generate report.html, host it and open it in the default browser
if (flags.view) {
const filename = `${assetSaver.getFilenamePrefix({url: address})}.report.html`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe dry this up with 239-240.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the suggestion

*/
'use strict';

const express = require('express');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we try to do the server without express?

it's so simple I think we can get away with not using it and instead reusing https://github.com/GoogleChrome/lighthouse/blob/master/lighthouse-cli/test/fixtures/static-server.js

It might need a feature or two, but augmenting that seems better.

if (flags.view) {
server.listen().then((port: number) => {
const fileAbsPath = `${__dirname}/server/reports/${filename}`;
Printer.write(results, 'html', fileAbsPath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are already saving the file to disk in the default pretty case. Shall we just reuse that file and host that?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently, the report page is a static page. But the new report page should allow developers to interact with that. The server will then need to host a different report page (eventually). So I think we cannot use the same html file unless we want to update the old report page to the new one as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah gotcha. now i see what you were thinking here.

one way we could handle this is append a <script>...</script> to the end of the response HTML as its being served. (that's also how livereload servers work, just injecting scripts into the end of the file)

generally this has some advantages, in that we can keep the reporting stuff really consistent between our static and interactive reports.

wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow I didn't know we could do it in that approach. Thanks for the suggestion!

Copy link
Contributor Author

@weiwei-lin weiwei-lin Dec 12, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already changed. Also, server.js now uses 'http' instead of 'express'.
Edit: It's not working properly. Will update it soon
Edit2: Updated

…ico to avoid error when loading report.html in a browser
Deleted unnecessary code in server.js.\nUpdated filename variable when file is not found.\nRearranged code to avoid using variable 'server' before defining
…generated in the past

Now use symlink to access report files which are not in the server folder.\nAlso deny access to files outside server folder.\n
Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some more comments :)

@@ -0,0 +1,94 @@
/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a more descriptive directory name we can use here since this is going to become specifically the performance experiments server?


function getPort(port) {
if (!portPromise) {
portPromise = new Promise((reslove, reject) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/reslove/resolve

const server = http.createServer(requestHandler);
server.on('error', e => console.error(e.code, e));

function getPort(port) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should this be called something more like createServer?

(after the listen() callback the port query is just a sync operation)

if (flags.view) {
server.getPort(0).then((port: number) => {
const filePath = `${server.FOLDERS.REPORTS}/${filename}`;
const htmlExist = outputMode === Printer.OutputMode[Printer.OutputMode.pretty]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be better to just check if the file exists to avoid having two places to change any logic on when a file will be printed to file?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your advice. I tried to follow your approach but I found that we still need to check the output mode because we only want to host HTML file (may change to only JSON file later to ease performance comparison). Besides, there may exist a random file named 'stdout' in CWD, considering that 'stdout' is a common name. Thus I think it's still better to check whether outputPath is 'stdout'.

// If the HTML file is already generated, create a symlink to it
if (htmlExist) {
const targetPath = outputMode === Printer.OutputMode[Printer.OutputMode.pretty] ? filename : outputPath;
fs.symlinkSync(path.resolve(targetPath), filePath);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we clear this directory on server close? On the one hand, it may be worth having older versions, but if you're deleting the original the symlink becomes useless and you probably won't know to delete the ones in the server directory.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of deleting the entire folder, now the server will delete all broken symlinks on start. So we can keep the old reports and we don't need to worry about those broken symlinks.

Copy link
Contributor Author

@weiwei-lin weiwei-lin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed server folder name from 'server' to 'performance-experiment'.
Changed function name from 'listen' to 'startServer'.
Server now deletes broken symlinks on start.

if (flags.view) {
server.getPort(0).then((port: number) => {
const filePath = `${server.FOLDERS.REPORTS}/${filename}`;
const htmlExist = outputMode === Printer.OutputMode[Printer.OutputMode.pretty]
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your advice. I tried to follow your approach but I found that we still need to check the output mode because we only want to host HTML file (may change to only JSON file later to ease performance comparison). Besides, there may exist a random file named 'stdout' in CWD, considering that 'stdout' is a common name. Thus I think it's still better to check whether outputPath is 'stdout'.

// If the HTML file is already generated, create a symlink to it
if (htmlExist) {
const targetPath = outputMode === Printer.OutputMode[Printer.OutputMode.pretty] ? filename : outputPath;
fs.symlinkSync(path.resolve(targetPath), filePath);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of deleting the entire folder, now the server will delete all broken symlinks on start. So we can keep the old reports and we don't need to worry about those broken symlinks.

Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a few more things

performanceXServer.startServer(0).then((port: number) => {
const filePath = `${performanceXServer.FOLDERS.REPORTS}/${filename}`;
const htmlExist = outputMode === Printer.OutputMode[Printer.OutputMode.pretty]
|| (outputPath !== 'stdout' && outputMode === Printer.OutputMode[Printer.OutputMode.html]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for some conflicting advice here: I'm still not a fan of the fragility of this check. It may not be worth reusing the pretty report...I'd personally prefer just re-printing (~100ms for a decent report) and longer term we could do some kind of refactor like separating generation of the report html from saving to file

@paulirish for his opinion

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'm happy to regenerate the report rather than read it from where we just printed it on disk.

if --view becomes a default, then the automatic disk-save won't really make sense.

};

const server = http.createServer(requestHandler);
server.on('error', e => console.error(e.code, e));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should use log.error here for logging

sendResponse(404, `404 - File not found. ${pathname}`);
return;
}
console.error(`Unable to read local file ${filePath}:`, err);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

log.error here as well

Added a file overview and some comments for
performance-experiment/server.js.
No longer check if report page is already created. Now always create a
new copy for report page.
Removed symlink cleanup process on server start since it’s no longer
needed.
Copy link
Member

@brendankenny brendankenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@brendankenny
Copy link
Member

over to @paulirish

Copy link
Member

@paulirish paulirish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup. This is looking ace. Thanks for kicking this awesome work off, @WeiweiAtGit :D

@paulirish paulirish merged commit dd6348a into GoogleChrome:master Dec 15, 2016
@weiwei-lin
Copy link
Contributor Author

Thanks for all the advice!

@weiwei-lin weiwei-lin deleted the add-server branch December 15, 2016 10:45
andrewrota pushed a commit to andrewrota/lighthouse that referenced this pull request Jan 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants