Skip to content
This repository was archived by the owner on Jun 18, 2021. It is now read-only.

Initial drop of nodereport project for nodejs repo - #2

Merged
rnchamberlain merged 24 commits into
nodejs:masterfrom
rnchamberlain:initial-drop
Oct 21, 2016
Merged

rnchamberlain merged 24 commits into
nodejs:masterfrom
rnchamberlain:initial-drop

Conversation

@rnchamberlain

Copy link
Copy Markdown
Contributor

Implementation of native NPM module for diagnostic report triggered on exception and fatal error events, signals and JavaScript API. Supports Node v4 and v6, Linux, Mac and Windows. Includes testcases and demo samples.

@Fishrock123

Copy link
Copy Markdown
Contributor

I'm getting this on OS X 10.10.5 and Node.js v4/v6:

> node-gyp rebuild

  CXX(target) Release/obj.target/nodereport/src/node_report.o
../src/node_report.cc:675:76: warning: format specifies type 'long' but the argument has type '__darwin_suseconds_t' (aka 'int') [-Wformat]
    fprintf(fp,"\n  User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec);
                                       ~~~~~                               ^~~~~~~~~~~~~~~~~~~~~~
                                       %06d
../src/node_report.cc:676:78: warning: format specifies type 'long' but the argument has type '__darwin_suseconds_t' (aka 'int') [-Wformat]
    fprintf(fp,"\n  Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec);
                                         ~~~~~                               ^~~~~~~~~~~~~~~~~~~~~~
                                         %06d
../src/node_report.cc:684:17: error: use of undeclared identifier 'RUSAGE_THREAD'
  if (getrusage(RUSAGE_THREAD, &stats) == 0) {
                ^
../src/node_report.cc:685:76: warning: format specifies type 'long' but the argument has type '__darwin_suseconds_t' (aka 'int') [-Wformat]
    fprintf(fp,"\n  User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec);
                                       ~~~~~                               ^~~~~~~~~~~~~~~~~~~~~~
                                       %06d
../src/node_report.cc:686:78: warning: format specifies type 'long' but the argument has type '__darwin_suseconds_t' (aka 'int') [-Wformat]
    fprintf(fp,"\n  Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec);
                                         ~~~~~                               ^~~~~~~~~~~~~~~~~~~~~~
                                         %06d
4 warnings and 1 error generated.
make: *** [Release/obj.target/nodereport/src/node_report.o] Error 1

Also when applying this patch:

warning: squelched 20 whitespace errors
warning: 25 lines add whitespace errors.

@mhdawson

Copy link
Copy Markdown
Member

As an FYI Richard is on holiday this week , so response will be delayed.

@mhdawson

Copy link
Copy Markdown
Member

@bnoordhuis, believe you looked at PR to core, so you make be able to review this as the key issues will be the same, just as an npm versus part of core.

Comment thread binding.gyp Outdated
"conditions": [
["OS=='linux'", {
"defines": [ "_GNU_SOURCE" ],
"cflags": [ "-g -O2 -std=c++11", ],

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

These should be split, not as a single string.

@mhdawson

Copy link
Copy Markdown
Member

LGTM with a few nits.

@rnchamberlain

Copy link
Copy Markdown
Contributor Author

@mhdawson thanks for the review, I have moved the blocks of code that print version information and stack traces in node_report.cc into separate functions, and fixed the minor nits. Also added the scripts entry into package.json so uses can run "npm test", and fixed some CRLF line endings in the test code.

I had not considered line-mode options (options are currently supported via env vars and/or JS API calls). A typical node command line is:

node <node and v8 options> application.js <application options>

We could allow nodereport options to be mixed in with either the node/v8 or the application options. Suitable unique naming would be need, e.g something like "nodereport_events=exception+signal". Application options are available via process.argv, though I am not sure how to access that from C++, otherwise I should be able to get the command line from the OS . Anyway, something for a future enhancement.

@Fishrock123

Copy link
Copy Markdown
Contributor

Got it to build on OS X, is NodeReport.20161004.120508.2622.001.txt just meant to be read manually? Seems reasonably understandable to me.

@Fishrock123

Copy link
Copy Markdown
Contributor

Should this be merged so that it can be worked on more collaboratively?

@rnchamberlain

Copy link
Copy Markdown
Contributor Author

@Fishrock123 yes absolutely, the report is intended to be read manually, using any text editor/notepad/email etc, and should be readily understandable to node developers, admins, testers etc

Yes this is ready to go and for others to contribute. @mhdawson - not sure how this repo is set up, who has powers to merge this? thanks

@Fishrock123

Copy link
Copy Markdown
Contributor

I think we should probably just give you perms to merge it. cc/ @nodejs/post-mortem

@misterdjules

Copy link
Copy Markdown

This repo has @mhdawson and the @nodejs/post-mortem team as collaborators. @rnchamberlain isn't (to my surprise) a member of the @nodejs/post-mortem team, even though he's clearly active in that space, so I added him to that team.

Once @rnchamberlain accepts the invite, he'll have the credentials to push commits to this repo.

Comment thread README.md Outdated

var nodereport = require('nodereport');

By default, this will trigger a NodeReport to be written to the current

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't think this is a good idea. It means that there is no way to opt-out of auto-handling of unhandledException, which would be useful for someone who wanted to only use the manual .triggerReport(). I think it would make more sense for a require of nodereport to make no changes to global process state until it was attached. Syntax could be require('nodereport')(), or require('nodereport').attach(), or any other variant.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

also, I would want to control independently whether nodereport attached to SIGUSR2, or even configure the signal.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @sam-github, you can override the defaults and configure which events you want via the API, so
require("nodereport").setEvents("fatalerror+signal");
will switch off the exception handler, likewise
require("nodereport").setEvents("exception+fatalerror");
will switch off the signal handler.

require("nodereport").setSignal("SIGQUIT");
will configure SIGQUIT for the trigger signal instead of SIGUSR2, and we could add more choices for that (though I just found a bug in that code, fix on it's way).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Perhaps an optional options arg with key events as an array of potential triggers and key sigusr2 as a boolean to enable/disable the signal would be a nice approach.

Such an arg would be reasonably extensible for other triggers too.

Also allows for clear defaults to be set early in code.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Yes, I think that's the way to go, extra parameter(s) on the module Initialize(). There is a limitation here in the v8 APIs, in that isolate->SetFatalErrorHandler(), isolate->SetCaptureStackTraceForUncaughtExceptions() and isolate->SetAbortOnUncaughtExceptionCallback() do not have 'unset' counterparts, and they don't support chaining of the callbacks. So if the nodereport module sets those at startup, a subsequent setEvents() call to the module can only switch off what we do in our callback, the underlying change to v8/node behaviour remains in place. Using env vars gets around that in the current implementation, as they are available at module init, but they are a bit nasty. So Sam's require('nodereport')(...), suggestion, with 'at start-up' equivalents of the setEvents(), setSignal(), setDirectory etc APIs would be a better solution.

Comment thread README.md Outdated
Configuration is available via these environment variables and/or Javascript
API calls:

export NODEREPORT_EVENTS=exception+fatalerror+signal

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

that its possible to configure nodereport via env vars is great, that its necessary to do so is I think too much focus on policy instead of mechanism

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I'd expect the .setEvents() API to be the usual way to configure it, with the env vars as an alternative maybe for admins when changing the code is not convenient. I'll update the readme to reflect that.

Re the defaults, the design was for all the events to work 'out of the box' i.e. with just require("nodereport"). We could take a more cautious approach as you suggest, and just have
.triggerReport() available until .setEvents(list of selected events) is called.

@Fishrock123

Fishrock123 commented Oct 4, 2016 •

Copy link
Copy Markdown
Contributor

I don't think we should pull from core eslint (it's a legacy mess). Just use standard.

@mhdawson

mhdawson commented Oct 5, 2016

Copy link
Copy Markdown
Member

I believe I have given everybody in the post-mortem team write access to this repo. Was just waiting for 1 more L.G.T.M from one of the other reviewers before landing.

Comment thread src/module.cc Outdated
}
NAN_METHOD(SetCoreDump) {
Nan::Utf8String parameter(info[0]);
nodereport_events = ProcessNodeReportCoreSwitch(*parameter);

@joshgav joshgav Oct 6, 2016 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I believe this is intended: s/nodereport_events/nodereport_core/

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@joshgav good spot, and same error in SetSignal method below, fix in progress

@Fishrock123

Copy link
Copy Markdown
Contributor

Status? I think I'd actually like to start using this.

@rnchamberlain

Copy link
Copy Markdown
Contributor Author

Fixed as discussed with @sam-github and @joshgav. So require('nodereport') by default now just enables the .triggerReport() functionality. No changes to the Node/v8 hooks and signals until the requirer explicitly calls .setEvents("exception+signal+apicall+etc").

Status: as per @mhdawson above, just another LGTM then we land. Meantime available to try out now with
npm install rnchamberlain/nodereport-1

@sam-github

sam-github commented Oct 13, 2016 •

Copy link
Copy Markdown
Contributor

@bnoordhuis LGTY?

@rnchamberlain API LGTM. If you want to make it easy to enable all capabilities, you could implement a usage like:

require('nodereport')();

So a side-effectless require is possible, but all side-effects can be opted in easily, and the "module call" above would check for env vars

You could also extend above with an options object rather than method calls for configuration:

require('nodereport')({
  events: 'signal',
  signal: 'SIGHUP',
  });

EDIT: just suggestions, which can be taken up in a subsequent PR if you are interested, they don't have to happen now.

@rnchamberlain

Copy link
Copy Markdown
Contributor Author

@sam-github I had a look at both of your suggested syntax options, but couldn't immediately work out how to code them on the C++ side! There is a mention of a two-argument form of the Init() function here: https://nodejs.org/api/addons.html, which has a module object passed in as well as the exports object? Anyway yes, potential future enhancement - thanks.

Comment thread .gitignore Outdated

# Build directories
/build/
/node_modules/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This file uses Windows line endings for some reason.

Comment thread README.md
api.js - NodeReport triggered by Javascript API call
exception.js - NodeReport triggered by unhandled exception
fatalerror.js - NodeReport triggered by fatal error on Javascript heap out of memory
loop.js - looping application, NodeReport triggered using kill -USR2 <pid>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There is some trailing whitespace in this file.

Comment thread demo/loop.js
setTimeout(function() {
console.log('loop.js: timeout expired, exiting.');
process.exit(0);
}, 60000);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto.

Comment thread src/module.cc Outdated

if (info[0]->IsString()) {
// Filename parameter supplied
Nan::Utf8String filename_parameter(info[0]->ToString());

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You don't have to ToString() the argument to Nan::Utf8String.

Comment thread src/module.cc Outdated
if (filename_parameter.length() < NR_MAXNAME) {
snprintf(filename, sizeof(filename), "%s", *filename_parameter);
} else {
Nan::ThrowSyntaxError("nodereport: filename parameter is too long");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

SyntaxError is kind of weird to throw for a bad argument.

Comment thread src/node_report.cc Outdated
break;
case kFatalError:
#if NODE_VERSION_AT_LEAST(6, 0, 0)
if (!strncmp(location,"MarkCompactCollector", sizeof("MarkCompactCollector") - 1)) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto.

Comment thread src/node_report.cc Outdated
// Walk the frames printing symbolic information if available
for (int i = 0; i < numberOfFrames; i++) {
DWORD64 dwOffset64 = 0;
DWORD64 dwAddress = (DWORD64)(frames[i]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

C++-style casts, please!

Comment thread src/node_report.cc Outdated
if (SymFromAddr(hProcess, dwAddress, &dwOffset64, pSymbol)) {
DWORD dwOffset = 0;
IMAGEHLP_LINE64 line;
line.SizeOfStruct = sizeof(IMAGEHLP_LINE64);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

sizeof(line)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Right, but that doesn't preclude sizeof(line), does it?

@rnchamberlain rnchamberlain Oct 17, 2016 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Either sizeof(type name) or sizeof(variable name) work OK I believe, the MSDN examples uses sizeof(type name). Is sizeof(variable name) preferred?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is in the node core code base so you may want to follow that here. The reasoning is that sizeof(var) won't break when the type of var changes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK, done.

Comment thread src/node_report.cc Outdated
lpvEnv = GetEnvironmentStrings();
if (lpvEnv != NULL) {
// Variable strings are separated by NULL byte, and the block is terminated by a NULL byte.
lpszVariable = (LPTSTR) lpvEnv;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Ditto.

Comment thread src/node_report.h Outdated
#endif // !__GLIBC_PREREQ(2, 17)
#else
#define secure_getenv getenv
#endif // defined(__GLIBC__)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Potential downside is that a .node compiled against glibc 2.17 won't work with e.g. glibc 2.15. That is, you won't be able to simply copy the shared object around.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wonder if that's a common thing to do, copy binaries around from an npm install. Someone is bound to do it I suppose, and they will get an unresolved symbol at load time. Under consideration!

I think I have fixed everything else, see commits below, and thanks for the review.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Its a nice to have to be able to build on one system, and run on another slightly different one. It fails often enough that its usually necessary to build on a system of identical os/etc level and architecture as the deployment/production system.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

OK, glibc 2.17 is fairly recent: https://sourceware.org/glibc/wiki/Glibc%20Timeline. I'll do it the node.cc way and have a local implementation of secure_getenv().

@rnchamberlain

Copy link
Copy Markdown
Contributor Author

Looking back we have an LGTM and an API LGTM on this, I'll merge shortly unless anyone objects. Thanks.

@rnchamberlain
rnchamberlain merged commit 1ba381f into nodejs:master Oct 21, 2016
@rnchamberlain
rnchamberlain deleted the initial-drop branch October 21, 2016 09:22
@rnchamberlain rnchamberlain mentioned this pull request Nov 16, 2016
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants