2016-08-02 18:10:52 +00:00
|
|
|
/**
|
|
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
|
|
* All rights reserved.
|
|
|
|
*
|
|
|
|
* This source code is licensed under the BSD-style license found in the
|
|
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
|
2016-08-02 20:01:43 +00:00
|
|
|
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
2016-08-02 18:10:52 +00:00
|
|
|
const fs = require('fs');
|
2016-08-02 18:10:55 +00:00
|
|
|
const http = require('http');
|
2016-08-05 13:02:20 +00:00
|
|
|
const path = require('path');
|
2016-08-02 18:10:55 +00:00
|
|
|
const urlLib = require('url');
|
|
|
|
|
|
|
|
class TreeTransformator {
|
|
|
|
constructor() {
|
|
|
|
this.urlResults = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
transform(tree, callback) {
|
|
|
|
this.afterUrlsCacheBuild(tree, () => {
|
|
|
|
callback(this.transformNode(tree));
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// private
|
|
|
|
transformNode(tree) {
|
|
|
|
if (tree.url in this.urlResults) {
|
|
|
|
const original = this.urlResults[tree.url].originalPositionFor({
|
|
|
|
line: tree.lineNumber,
|
|
|
|
column: tree.columnNumber,
|
|
|
|
});
|
2016-08-05 13:02:20 +00:00
|
|
|
tree.functionName = original.name
|
2016-09-15 13:58:38 +00:00
|
|
|
|| (path.posix.basename(original.source || '') + ':' + original.line);
|
2016-08-02 18:10:55 +00:00
|
|
|
tree.scriptId = tree.id;
|
|
|
|
tree.url = 'file://' + original.source;
|
|
|
|
tree.lineNumber = original.line;
|
|
|
|
tree.columnNumber = original.column;
|
2016-08-17 15:21:54 +00:00
|
|
|
} else if (tree.deoptReason === 'outside_vm') {
|
|
|
|
tree.functionName = 'OUTSIDE VM';
|
2016-08-02 18:10:55 +00:00
|
|
|
}
|
|
|
|
tree.children = tree.children.map((t) => this.transformNode(t));
|
|
|
|
return tree;
|
|
|
|
}
|
|
|
|
|
|
|
|
// private
|
|
|
|
afterUrlsCacheBuild(tree, callback) {
|
|
|
|
let urls = new Set();
|
|
|
|
this.gatherUrls(tree, urls);
|
|
|
|
|
|
|
|
let size = urls.size;
|
|
|
|
if (size === 0) {
|
|
|
|
callback();
|
|
|
|
} else {
|
|
|
|
urls.forEach((url) => {
|
|
|
|
this.callUrlCached(url, () => {
|
|
|
|
--size;
|
|
|
|
if (size === 0) {
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// private
|
|
|
|
gatherUrls(tree, urls) {
|
|
|
|
urls.add(tree.url);
|
|
|
|
tree.children.map((t) => this.gatherUrls(t, urls));
|
|
|
|
}
|
|
|
|
|
|
|
|
// private
|
|
|
|
callUrlCached(url, callback) {
|
|
|
|
if (url === '' || url === null || url in this.urlResults) {
|
|
|
|
callback();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const parsedUrl = urlLib.parse(url);
|
2016-09-15 13:51:24 +00:00
|
|
|
const mapPath = parsedUrl.pathname.replace(/\.bundle$/, '.map');
|
2016-08-02 18:10:55 +00:00
|
|
|
const options = {
|
2016-08-10 13:33:21 +00:00
|
|
|
host: 'localhost',
|
2016-08-02 18:10:55 +00:00
|
|
|
port: parsedUrl.port,
|
2016-09-15 13:51:24 +00:00
|
|
|
path: mapPath + parsedUrl.search + '&babelSourcemap=true',
|
2016-08-02 18:10:55 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
http.get(options, (res) => {
|
|
|
|
res.setEncoding('utf8');
|
|
|
|
let sawEnd = false;
|
|
|
|
let resBody = '';
|
|
|
|
res.on('data', (chunk) => {
|
|
|
|
resBody += chunk;
|
|
|
|
}).on('end', () => {
|
|
|
|
sawEnd = true;
|
2016-08-05 13:02:20 +00:00
|
|
|
this.urlResults[url] = new SourceMapConsumer(resBody);
|
2016-08-02 18:10:55 +00:00
|
|
|
callback();
|
|
|
|
}).on('close', (err) => {
|
|
|
|
if (!sawEnd) {
|
|
|
|
console.error('Connection terminated prematurely because of: '
|
|
|
|
+ err.code + ' for url: ' + url);
|
|
|
|
callback();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}).on('error', (err) => {
|
|
|
|
console.error('Could not get response from: ' + url);
|
|
|
|
callback();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
2016-08-02 18:10:52 +00:00
|
|
|
|
|
|
|
module.exports = function(req, res, next) {
|
|
|
|
if (req.url !== '/jsc-profile') {
|
|
|
|
next();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-08-02 18:10:55 +00:00
|
|
|
console.log('Received request from JSC profiler, post processing it...');
|
|
|
|
let profile = JSON.parse(req.rawBody);
|
|
|
|
(new TreeTransformator()).transform(profile.head, (newHead) => {
|
|
|
|
profile.head = newHead;
|
|
|
|
|
|
|
|
console.log('Dumping JSC profile information...');
|
|
|
|
const dumpName = '/tmp/jsc-profile_' + Date.now() + '.cpuprofile';
|
|
|
|
|
|
|
|
fs.writeFile(dumpName, JSON.stringify(profile), (err) => {
|
|
|
|
let response = '';
|
|
|
|
if (err) {
|
|
|
|
response =
|
|
|
|
'An error occured when trying to save the profile at ' + dumpName;
|
|
|
|
console.error(response, err);
|
|
|
|
} else {
|
|
|
|
response =
|
|
|
|
'Your profile was generated at\n\n' + dumpName + '\n\n' +
|
|
|
|
'Open `Chrome/Atom Dev Tools > Profiles > Load` '
|
|
|
|
+ 'and select the profile to visualize it.';
|
|
|
|
console.log(response);
|
|
|
|
}
|
|
|
|
res.end(response);
|
|
|
|
});
|
2016-08-02 18:10:52 +00:00
|
|
|
});
|
|
|
|
};
|