172 lines
5.4 KiB
JavaScript
172 lines
5.4 KiB
JavaScript
/**
|
|
* Copyright (c) 2016-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';
|
|
/*eslint no-console-disallow: "off"*/
|
|
|
|
const spawn = require('child_process').spawn;
|
|
const fs = require('fs');
|
|
const http = require('http');
|
|
const path = require('path');
|
|
const urlLib = require('url');
|
|
const SourceMapConsumer = require('source-map').SourceMapConsumer;
|
|
|
|
|
|
// url: string
|
|
// onSuccess: function (SourceMapConsumer)
|
|
// onFailure: function (string)
|
|
function getSourceMapForUrl(url, onFailure, onSuccess) {
|
|
if (!url) {
|
|
onFailure('must provide a URL');
|
|
return;
|
|
}
|
|
|
|
if (url === 'assets://default_bundle') {
|
|
onFailure('Don\'t know how to symbolicate in-app bundle, please load from server');
|
|
return;
|
|
}
|
|
|
|
const parsedUrl = urlLib.parse(url);
|
|
const mapPath = parsedUrl.pathname.replace(/\.bundle$/, '.map');
|
|
const options = {
|
|
host: 'localhost',
|
|
port: parsedUrl.port,
|
|
path: mapPath + parsedUrl.search + '&babelSourcemap=true',
|
|
};
|
|
|
|
http.get(options, (res) => {
|
|
res.setEncoding('utf8');
|
|
let sawEnd = false;
|
|
let resBody = '';
|
|
res.on('data', (chunk) => {
|
|
resBody += chunk;
|
|
}).on('end', () => {
|
|
sawEnd = true;
|
|
onSuccess(new SourceMapConsumer(resBody));
|
|
}).on('close', (err) => {
|
|
if (!sawEnd) {
|
|
onFailure('Connection terminated prematurely because of: '
|
|
+ err.code + ' for url: ' + url);
|
|
}
|
|
});
|
|
}).on('error', (err) => {
|
|
onFailure('Could not get response from: ' + url + ', error: ' + err.message);
|
|
});
|
|
}
|
|
|
|
// capture: capture object
|
|
// onSuccess: function (Map of url -> SourceMapConsumer)
|
|
// onFailure: function (string)
|
|
function getSourceMapsForCapture(capture, onFailure, onSuccess) {
|
|
const urls = new Set();
|
|
const sourcemaps = new Map();
|
|
for (const id in capture.refs) {
|
|
const ref = capture.refs[id];
|
|
if ((ref.type === 'ScriptExecutable' ||
|
|
ref.type === 'EvalExecutable' ||
|
|
ref.type === 'ProgramExecutable' ||
|
|
ref.type === 'FunctionExecutable') && ref.value.url) {
|
|
urls.add(ref.value.url);
|
|
}
|
|
}
|
|
urls.forEach((url) => {
|
|
getSourceMapForUrl(url, onFailure, (sourcemap) => {
|
|
sourcemaps.set(url, sourcemap);
|
|
urls.delete(url);
|
|
if (urls.size === 0) {
|
|
onSuccess(sourcemaps);
|
|
}
|
|
});
|
|
});
|
|
if (urls.size === 0) {
|
|
console.warn('No source information found in capture');
|
|
onSuccess(sourcemaps);
|
|
}
|
|
}
|
|
|
|
// capture: capture object
|
|
// onSuccess: function (capture object)
|
|
// onFailure: function (string)
|
|
function symbolicateHeapCaptureFunctions(capture, onFailure, onSuccess) {
|
|
getSourceMapsForCapture(capture, onFailure, (sourcemaps) => {
|
|
for (const id in capture.refs) {
|
|
const ref = capture.refs[id];
|
|
if (ref.type === 'ScriptExecutable' ||
|
|
ref.type === 'EvalExecutable' ||
|
|
ref.type === 'ProgramExecutable' ||
|
|
ref.type === 'FunctionExecutable') {
|
|
const sourcemap = sourcemaps.get(ref.value.url);
|
|
if (sourcemap) {
|
|
const original = sourcemap.originalPositionFor({
|
|
line: ref.value.line,
|
|
column: ref.value.col,
|
|
});
|
|
if (original.name) {
|
|
ref.value.name = original.name;
|
|
} else if (!ref.value.name) {
|
|
ref.value.name = path.posix.basename(original.source || '') + ':' + original.line;
|
|
}
|
|
ref.value.url = original.source;
|
|
ref.value.line = original.line;
|
|
ref.value.col = original.column;
|
|
}
|
|
}
|
|
}
|
|
onSuccess(capture);
|
|
});
|
|
}
|
|
|
|
module.exports = function(req, res, next) {
|
|
if (req.url !== '/jscheapcaptureupload') {
|
|
next();
|
|
return;
|
|
}
|
|
|
|
console.log('symbolicating Heap Capture');
|
|
symbolicateHeapCaptureFunctions(JSON.parse(req.rawBody), (err) => {
|
|
console.error('Error when symbolicating: ' + err);
|
|
},
|
|
(capture) => {
|
|
const preload = path.join(__dirname, 'heapCapture/preLoadedCapture.js');
|
|
fs.writeFileSync(preload, 'var preLoadedCapture = ');
|
|
fs.appendFileSync(preload, JSON.stringify(capture));
|
|
fs.appendFileSync(preload, ';');
|
|
const captureDir = path.join(__dirname, 'heapCapture/captures');
|
|
if (!fs.existsSync(captureDir)) {
|
|
fs.mkdirSync(captureDir);
|
|
}
|
|
console.log('Packaging Trace');
|
|
var captureHtml = captureDir + '/capture_' + Date.now() + '.html';
|
|
var capture = fs.createWriteStream(captureHtml);
|
|
var inliner = spawn(
|
|
'inliner',
|
|
['--nocompress', 'heapCapture.html'],
|
|
{ cwd: path.join(__dirname, '/heapCapture/'),
|
|
stdio: [ process.stdin, 'pipe', process.stderr ],
|
|
});
|
|
inliner.stdout.pipe(capture);
|
|
inliner.on('error', (err) => {
|
|
console.error('Error processing heap capture: ' + err.message);
|
|
console.error('make sure you have installed inliner with \'npm install inliner -g\'');
|
|
});
|
|
inliner.on('exit', (code, signal) => {
|
|
if (code === 0) {
|
|
var response = captureHtml;
|
|
console.log('Heap capture written to: ' + response);
|
|
res.end(response);
|
|
} else {
|
|
var response = 'Error processing heap capture, inliner returned code: ' + code;
|
|
console.error(response);
|
|
res.statusCode = 500;
|
|
res.end(response);
|
|
}
|
|
});
|
|
}
|
|
);
|
|
};
|