Skip to content

Commit

Permalink
src: merge into core
Browse files Browse the repository at this point in the history
Make node-report part of core runtime because:

1. When enabled, node-report significantly helps root cause various
types of problems, including support issues sent to the various repos
of the Node.js organization.

2. The requirement of explicitly adding the dependency to node-report
in user applications often represents a blocker to adoption.

Major deviation from the module version of the node-report is that the
report is generated in JSON format, as opposed to human readable text.

No new functionalities have been added, changes that are required for
melding it as a built-in capability has been affected on the module
version of node-report (https://github.com/nodejs/node-report)

Co-authored-by: Bidisha Pyne <[email protected]>
Co-authored-by: Howard Hellyer <[email protected]>
Co-authored-by: Jeremiah Senkpiel <[email protected]>
Co-authored-by: Julian Alimin <[email protected]>
Co-authored-by: Lakshmi Swetha Gopireddy <[email protected]>
Co-authored-by: Manusaporn Treerungroj <[email protected]>
Co-authored-by: Michael Dawson <[email protected]>
Co-authored-by: Richard Chamberlain <[email protected]>
Co-authored-by: Richard Lau <[email protected]>
Co-authored-by: Sam Roberts <[email protected]>
Co-authored-by: Vipin Menon <[email protected]>

PR-URL: nodejs#22712
Reviewed-By: Anna Henningsen <[email protected]>
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Michael Dawson <[email protected]>
Reviewed-By: Vse Mozhet Byt <[email protected]>
  • Loading branch information
gireeshpunathil committed Jan 18, 2019
1 parent 01cd219 commit 4f67973
Show file tree
Hide file tree
Showing 16 changed files with 1,995 additions and 2 deletions.
6 changes: 6 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -493,6 +493,11 @@
dest='without_npm',
help='do not install the bundled npm (package manager)')

parser.add_option('--without-report',
action='store_true',
dest='without_report',
help='build without report')

# Dummy option for backwards compatibility
parser.add_option('--with-snapshot',
action='store_true',
Expand Down Expand Up @@ -938,6 +943,7 @@ def configure_node(o):
o['variables']['OS'] = 'android'
o['variables']['node_prefix'] = options.prefix
o['variables']['node_install_npm'] = b(not options.without_npm)
o['variables']['node_report'] = b(not options.without_report)
o['default_configuration'] = 'Debug' if options.debug else 'Release'

host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc()
Expand Down
4 changes: 4 additions & 0 deletions lib/internal/bootstrap/node.js
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,10 @@ function startup() {
} = perf.constants;
perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE);

if (getOptionValue('--experimental-report')) {
NativeModule.require('internal/process/report').setup();
}

if (isMainThread) {
return startMainThreadExecution;
} else {
Expand Down
1 change: 1 addition & 0 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -709,6 +709,7 @@ E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error);
E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error);
E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error);
E('ERR_INVALID_ADDRESS_FAMILY', 'Invalid address family: %s', RangeError);
E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error);
E('ERR_INVALID_ARG_TYPE',
(name, expected, actual) => {
assert(typeof name === 'string', "'name' must be a string");
Expand Down
25 changes: 25 additions & 0 deletions lib/internal/process/execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,31 @@ function createFatalException() {
// call that threw and was never cleared. So clear it now.
clearDefaultTriggerAsyncId();

// If node-report is enabled, call into its handler to see
// whether it is interested in handling the situation.
// Ignore if the error is scoped inside a domain.
// use == in the checks as we want to allow for null and undefined
if (er == null || er.domain == null) {
try {
const report = internalBinding('report');
if (report != null) {
if (require('internal/options').getOptionValue(
'--experimental-report')) {
const config = {};
report.syncConfig(config, false);
if (Array.isArray(config.events) &&
config.events.includes('exception')) {
if (er) {
report.onUnCaughtException(er.stack);
} else {
report.onUnCaughtException(undefined);
}
}
}
}
} catch {} // NOOP, node_report unavailable.
}

if (exceptionHandlerState.captureFn !== null) {
exceptionHandlerState.captureFn(er);
} else if (!process.emit('uncaughtException', er)) {
Expand Down
163 changes: 163 additions & 0 deletions lib/internal/process/report.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
'use strict';

const { emitExperimentalWarning } = require('internal/util');
const {
ERR_INVALID_ARG_TYPE,
ERR_SYNTHETIC } = require('internal/errors').codes;

exports.setup = function() {
const REPORTEVENTS = 1;
const REPORTSIGNAL = 2;
const REPORTFILENAME = 3;
const REPORTPATH = 4;
const REPORTVERBOSE = 5;
if (internalBinding('config').hasReport) {
// If report is enabled, extract the binding and
// wrap the APIs with thin layers, with some error checks.
// user options can come in from CLI / ENV / API.
// CLI and ENV is intercepted in C++ and the API call here (JS).
// So sync up with both sides as appropriate - initially from
// C++ to JS and from JS to C++ whenever the API is called.
// Some events are controlled purely from JS (signal | exception)
// and some from C++ (fatalerror) so this sync-up is essential for
// correct behavior and alignment with the supplied tunables.
const nr = internalBinding('report');

// Keep it un-exposed; lest programs play with it
// leaving us with a lot of unwanted sanity checks.
let config = {
events: [],
signal: 'SIGUSR2',
filename: '',
path: '',
verbose: false
};
const report = {
setDiagnosticReportOptions(options) {
emitExperimentalWarning('report');
// Reuse the null and undefined checks. Save
// space when dealing with large number of arguments.
const list = parseOptions(options);

// Flush the stale entries from report, as
// we are refreshing it, items that the users did not
// touch may be hanging around stale otherwise.
config = {};

// The parseOption method returns an array that include
// the indices at which valid params are present.
list.forEach((i) => {
switch (i) {
case REPORTEVENTS:
if (Array.isArray(options.events))
config.events = options.events;
else
throw new ERR_INVALID_ARG_TYPE('events',
'Array',
options.events);
break;
case REPORTSIGNAL:
if (typeof options.signal !== 'string') {
throw new ERR_INVALID_ARG_TYPE('signal',
'String',
options.signal);
}
process.removeListener(config.signal, handleSignal);
if (config.events.includes('signal'))
process.on(options.signal, handleSignal);
config.signal = options.signal;
break;
case REPORTFILENAME:
if (typeof options.filename !== 'string') {
throw new ERR_INVALID_ARG_TYPE('filename',
'String',
options.filename);
}
config.filename = options.filename;
break;
case REPORTPATH:
if (typeof options.path !== 'string')
throw new ERR_INVALID_ARG_TYPE('path', 'String', options.path);
config.path = options.path;
break;
case REPORTVERBOSE:
if (typeof options.verbose !== 'string' &&
typeof options.verbose !== 'boolean') {
throw new ERR_INVALID_ARG_TYPE('verbose',
'Booelan | String' +
' (true|false|yes|no)',
options.verbose);
}
config.verbose = options.verbose;
break;
}
});
// Upload this new config to C++ land
nr.syncConfig(config, true);
},


triggerReport(file, err) {
emitExperimentalWarning('report');
if (err == null) {
if (file == null) {
return nr.triggerReport(new ERR_SYNTHETIC(
'JavaScript Callstack').stack);
}
if (typeof file !== 'string')
throw new ERR_INVALID_ARG_TYPE('file', 'String', file);
return nr.triggerReport(file, new ERR_SYNTHETIC(
'JavaScript Callstack').stack);
}
if (typeof err !== 'object')
throw new ERR_INVALID_ARG_TYPE('err', 'Object', err);
if (file == null)
return nr.triggerReport(err.stack);
if (typeof file !== 'string')
throw new ERR_INVALID_ARG_TYPE('file', 'String', file);
return nr.triggerReport(file, err.stack);
},
getReport(err) {
emitExperimentalWarning('report');
if (err == null) {
return nr.getReport(new ERR_SYNTHETIC('JavaScript Callstack').stack);
} else if (typeof err !== 'object') {
throw new ERR_INVALID_ARG_TYPE('err', 'Objct', err);
} else {
return nr.getReport(err.stack);
}
}
};

// Download the CLI / ENV config into JS land.
nr.syncConfig(config, false);

function handleSignal(signo) {
if (typeof signo !== 'string')
signo = config.signal;
nr.onUserSignal(signo);
}

if (config.events.includes('signal')) {
process.on(config.signal, handleSignal);
}

function parseOptions(obj) {
const list = [];
if (obj == null)
return list;
if (obj.events != null)
list.push(REPORTEVENTS);
if (obj.signal != null)
list.push(REPORTSIGNAL);
if (obj.filename != null)
list.push(REPORTFILENAME);
if (obj.path != null)
list.push(REPORTPATH);
if (obj.verbose != null)
list.push(REPORTVERBOSE);
return list;
}
process.report = report;
}
};
75 changes: 75 additions & 0 deletions node.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@
'lib/internal/process/stdio.js',
'lib/internal/process/warning.js',
'lib/internal/process/worker_thread_only.js',
'lib/internal/process/report.js',
'lib/internal/querystring.js',
'lib/internal/queue_microtask.js',
'lib/internal/readline.js',
Expand Down Expand Up @@ -314,6 +315,29 @@
# the executable and rename it back to node.exe later
'product_name': '<(node_core_target_name)-win',
}],
[ 'node_report=="true"', {
'defines': [
'NODE_REPORT',
'NODE_ARCH="<(target_arch)"',
'NODE_PLATFORM="<(OS)"',
],
'conditions': [
['OS=="win"', {
'libraries': [
'dbghelp.lib',
'Netapi32.lib',
'PsApi.lib',
'Ws2_32.lib',
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll',
'PsApi.dll',
'Ws2_32.dll',
],
}],
],
}],
],
}, # node_core_target_name
{
Expand Down Expand Up @@ -623,6 +647,34 @@
'src/tls_wrap.h'
],
}],
[ 'node_report=="true"', {
'sources': [
'src/node_report.cc',
'src/node_report_module.cc',
'src/node_report_utils.cc',
],
'defines': [
'NODE_REPORT',
'NODE_ARCH="<(target_arch)"',
'NODE_PLATFORM="<(OS)"',
],
'conditions': [
['OS=="win"', {
'libraries': [
'dbghelp.lib',
'Netapi32.lib',
'PsApi.lib',
'Ws2_32.lib',
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll',
'PsApi.dll',
'Ws2_32.dll',
],
}],
],
}],
[ 'node_use_large_pages=="true" and OS=="linux"', {
'defines': [ 'NODE_ENABLE_LARGE_CODE_PAGES=1' ],
# The current implementation of Large Pages is under Linux.
Expand Down Expand Up @@ -964,6 +1016,29 @@
'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ],
},
}],
[ 'node_report=="true"', {
'defines': [
'NODE_REPORT',
'NODE_ARCH="<(target_arch)"',
'NODE_PLATFORM="<(OS)"',
],
'conditions': [
['OS=="win"', {
'libraries': [
'dbghelp.lib',
'Netapi32.lib',
'PsApi.lib',
'Ws2_32.lib',
],
'dll_files': [
'dbghelp.dll',
'Netapi32.dll',
'PsApi.dll',
'Ws2_32.dll',
],
}],
],
}],
],
}, # cctest
], # end targets
Expand Down
16 changes: 16 additions & 0 deletions src/node.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@
#include <unicode/uvernum.h>
#endif

#ifdef NODE_REPORT
#include "node_report.h"
#endif

#if defined(LEAK_SANITIZER)
#include <sanitizer/lsan_interface.h>
#endif
Expand Down Expand Up @@ -733,6 +737,12 @@ void RunBootstrapping(Environment* env) {
return;
}

#ifdef NODE_REPORT
if (env->options()->experimental_report) {
report::InitializeReport(env->isolate(), env);
}
#endif // NODE_REPORT

// process, loaderExports, isMainThread
std::vector<Local<String>> node_params = {
env->process_string(),
Expand Down Expand Up @@ -963,6 +973,12 @@ int Init(std::vector<std::string>* argv,
// Make inherited handles noninheritable.
uv_disable_stdio_inheritance();

#ifdef NODE_REPORT
// Cache the original command line to be
// used in diagnostic reports.
per_process::cli_options->cmdline = *argv;
#endif // NODE_REPORT

#if defined(NODE_V8_OPTIONS)
// Should come before the call to V8::SetFlagsFromCommandLine()
// so the user can disable a flag --foo at run-time by passing
Expand Down
9 changes: 8 additions & 1 deletion src/node_binding.cc
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
#define NODE_BUILTIN_ICU_MODULES(V)
#endif

#if NODE_REPORT
#define NODE_BUILTIN_REPORT_MODULES(V) V(report)
#else
#define NODE_BUILTIN_REPORT_MODULES(V)
#endif

// A list of built-in modules. In order to do module registration
// in node::Init(), need to add built-in modules in the following list.
// Then in binding::RegisterBuiltinModules(), it calls modules' registration
Expand Down Expand Up @@ -69,7 +75,8 @@
#define NODE_BUILTIN_MODULES(V) \
NODE_BUILTIN_STANDARD_MODULES(V) \
NODE_BUILTIN_OPENSSL_MODULES(V) \
NODE_BUILTIN_ICU_MODULES(V)
NODE_BUILTIN_ICU_MODULES(V) \
NODE_BUILTIN_REPORT_MODULES(V)

// This is used to load built-in modules. Instead of using
// __attribute__((constructor)), we call the _register_<modname>
Expand Down
Loading

0 comments on commit 4f67973

Please sign in to comment.