-
-
Notifications
You must be signed in to change notification settings - Fork 135
Parsestack refactor + tests to fix memory explosion #320
Changes from all commits
c75c6bb
c77f97a
88266bc
4e584b8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
coverage/ | ||
test/instrumentation/node-* | ||
test/manual/largeModule.js |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,4 @@ node_js: | |
- "5" | ||
- "6" | ||
- "7" | ||
before_install: | ||
- "npm install --upgrade npm -g" | ||
script: npm run test-full |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,6 @@ var transports = require('./transports'); | |
var path = require('path'); | ||
var lsmod = require('lsmod'); | ||
var stacktrace = require('stack-trace'); | ||
var Promise = require('promise/domains'); | ||
|
||
var ravenVersion = require('../package.json').version; | ||
|
||
|
@@ -142,84 +141,77 @@ function getModule(filename, base) { | |
return file; | ||
} | ||
|
||
function parseLines(lines, frame) { | ||
frame.pre_context = lines.slice(Math.max(0, frame.lineno - (LINES_OF_CONTEXT + 1)), frame.lineno - 1); | ||
frame.context_line = lines[frame.lineno - 1]; | ||
frame.post_context = lines.slice(frame.lineno, frame.lineno + LINES_OF_CONTEXT); | ||
} | ||
function readSourceFiles(filenames, cb) { | ||
// we're relying on filenames being de-duped already | ||
if (filenames.length === 0) return setTimeout(cb, 0, {}); | ||
|
||
function readFilePromise(filename) { | ||
return new Promise(function (resolve, reject) { | ||
fs.readFile(filename, function (err, file) { | ||
return err ? reject(err) : resolve(file); | ||
var sourceFiles = {}; | ||
var numFilesToRead = filenames.length; | ||
return filenames.forEach(function (filename) { | ||
fs.readFile(filename, function (readErr, file) { | ||
if (!readErr) sourceFiles[filename] = file.toString().split('\n'); | ||
if (--numFilesToRead === 0) cb(sourceFiles); | ||
}); | ||
}); | ||
} | ||
|
||
function parseStack(err, cb) { | ||
var frames = [], | ||
cache = {}; | ||
|
||
if (!err) { | ||
return cb(frames); | ||
} | ||
if (!err) return cb([]); | ||
|
||
var stack = stacktrace.parse(err); | ||
|
||
// check to make sure that the stack is what we need it to be. | ||
if (!stack || !Array.isArray(stack) || !stack.length || !stack[0].getFileName) { | ||
// lol, stack is fucked | ||
return cb(frames); | ||
// the stack is not the useful thing we were expecting :/ | ||
return cb([]); | ||
} | ||
|
||
var callbacks = stack.length; | ||
|
||
// Sentry requires the stack trace to be from oldest to newest | ||
// Sentry expects the stack trace to be oldest -> newest, v8 provides newest -> oldest | ||
stack.reverse(); | ||
|
||
return stack.forEach(function (line, index) { | ||
var frames = []; | ||
var filesToRead = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. could new Object(null) here if you wanted idk There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
stack.forEach(function (line) { | ||
var frame = { | ||
filename: line.getFileName() || '', | ||
lineno: line.getLineNumber(), | ||
colno: line.getColumnNumber(), | ||
'function': getFunction(line), | ||
}, | ||
isInternal = line.isNative() || | ||
frame.filename[0] !== '/' && | ||
frame.filename[0] !== '.' && | ||
frame.filename.indexOf(':\\') !== 1; | ||
filename: line.getFileName() || '', | ||
lineno: line.getLineNumber(), | ||
colno: line.getColumnNumber(), | ||
'function': getFunction(line), | ||
}; | ||
|
||
var isInternal = line.isNative() || | ||
frame.filename[0] !== '/' && | ||
frame.filename[0] !== '.' && | ||
frame.filename.indexOf(':\\') !== 1; | ||
|
||
// in_app is all that's not an internal Node function or a module within node_modules | ||
// note that isNative appears to return true even for node core libraries | ||
// see https://github.com/getsentry/raven-node/issues/176 | ||
frame.in_app = !isInternal && frame.filename.indexOf('node_modules/') === -1; | ||
|
||
// Extract a module name based on the filename | ||
if (frame.filename) frame.module = getModule(frame.filename); | ||
|
||
// internal Node files are not full path names. Ignore them. | ||
if (isInternal) { | ||
frames[index] = frame; | ||
if (--callbacks === 0) cb(frames); | ||
return; | ||
if (frame.filename) { | ||
frame.module = getModule(frame.filename); | ||
if (!isInternal) filesToRead[frame.filename] = true; | ||
} | ||
|
||
if (!cache[frame.filename]) { | ||
cache[frame.filename] = readFilePromise(frame.filename) | ||
.then(function (file) { | ||
return file.toString().split('\n'); | ||
}, function () { | ||
return null; | ||
}); | ||
} | ||
frames.push(frame); | ||
}); | ||
|
||
cache[frame.filename].then(function (file) { | ||
if (file) { | ||
parseLines(file, frame); | ||
return readSourceFiles(Object.keys(filesToRead), function (sourceFiles) { | ||
frames.forEach(function (frame) { | ||
if (frame.filename && sourceFiles[frame.filename]) { | ||
var lines = sourceFiles[frame.filename]; | ||
try { | ||
frame.pre_context = lines.slice(Math.max(0, frame.lineno - (LINES_OF_CONTEXT + 1)), frame.lineno - 1); | ||
frame.context_line = lines[frame.lineno - 1]; | ||
frame.post_context = lines.slice(frame.lineno, frame.lineno + LINES_OF_CONTEXT); | ||
} catch (e) { | ||
// anomaly, being defensive in case | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you want to set the properties to be empty strings /arrays here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nah, i think we'd rather not send them to the server at all; not sure what behavior diff would be but not sending them at all is what we do for native frames so i'll stick to that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. agreed |
||
// unlikely to ever happen in practice but can definitely happen in theory | ||
} | ||
} | ||
frames[index] = frame; | ||
if (--callbacks === 0) cb(frames); | ||
}); | ||
|
||
cb(frames); | ||
}); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should you wrap this line in a try catch, in case toString throws due to a weird file? to ensure that the callback always fires.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!readErr)
guarantees thatfile
is a Buffer according to fs.readFile docs, and Buffers have toString (as linked) even if they're empty, so I'm not concernedThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok 👍