diff --git a/configure b/configure index c9d598b799204b..3c3d4c96083b92 100755 --- a/configure +++ b/configure @@ -488,6 +488,11 @@ parser.add_option('--without-npm', dest='without_npm', help='do not install the bundled npm (package manager)') +parser.add_option('--without-node-report', + action='store_true', + dest='without_node_report', + help='build without node-report'), + parser.add_option('--without-perfctr', action='store_true', dest='without_perfctr', @@ -903,6 +908,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_node_report) o['default_configuration'] = 'Debug' if options.debug else 'Release' host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc() diff --git a/doc/api/index.md b/doc/api/index.md index 5ba0da6a7fb5aa..7a22e263fb62be 100644 --- a/doc/api/index.md +++ b/doc/api/index.md @@ -36,6 +36,7 @@ * [Internationalization](intl.html) * [Modules](modules.html) * [Net](net.html) +* [Node Report](node_report.html) * [OS](os.html) * [Path](path.html) * [Performance Hooks](perf_hooks.html) diff --git a/doc/api/node_report.md b/doc/api/node_report.md new file mode 100644 index 00000000000000..7e8e7b3f732448 --- /dev/null +++ b/doc/api/node_report.md @@ -0,0 +1,116 @@ +# node-report + +Delivers a human-readable diagnostic summary, written to file +or retrieved as a text. + +The report is intended for development, test and production +use, to capture and preserve information for problem determination. +It includes JavaScript and native stack traces, heap statistics, +platform information and resource usage etc. With the report enabled, +reports can be triggered on unhandled exceptions, fatal errors, signals. + +Many capabilities are available as APIs too, for being consumed in-process. + +Iin-built node-report function is supported in Node.js versions 11.0.0 onwards. +For Node.js versions 8 and below, please use it's [npm counterpart][] instead. + +## Usage + +```bash +node --report-events fatalerror,signal,exception app.js +``` +A report will be triggered automatically on unhandled exceptions and fatal +error events (for example out of memory errors), and can also be triggered +by sending a USR2 signal to a Node.js process (not supported on Windows). + +A report can also be triggered via an API call from a JavaScript +application. + +```js +const util = require('util'); +util.triggerReport(); +``` +The content of a report can also be returned as a JavaScript string via an +API call from a JavaScript application. + +```js +const util = require('util'); +const report_str = util.getReport(); +console.log(report_str); +``` + +These APIs can be used without adding the automatic exception +and fatal error hooks and the signal handler. + +Content of the report consists of a header section containing the event +type, date, time, PID and Node version, sections containing JavaScript and +native stack traces, a section containing V8 heap information, a section +containing libuv handle information and an OS platform information section +showing CPU and memory usage and system limits. An example report can be +triggered using the Node.js REPL: + +```raw +$ node +> const util = require('util'); +> util.triggerReport(); +Writing Node.js report to file: node-report.20180820.091102.8480.001.txt +Node.js report completed +> +``` + +When a report is triggered, start and end messages are issued to stderr +and the filename of the report is returned to the caller. The default filename +includes the date, time, PID and a sequence number. Alternatively, a filename +can be specified as a parameter on the `triggerReport()` call. + +```js +require('util').triggerReport('myReportName'); +``` + +Both `triggerReport()` and `getReport()` can take an optional `Error` object +as a parameter. If an `Error` object is provided, the message and stack trace +from the object will be included in the report in the `JavaScript Exception +Details` section. +When using node-report to handle errors in a callback or an exception handler +this allows the report to include the location of the original error as well +as where it was handled. +If both a filename and `Error` object are passed to `triggerReport()` the +`Error` object should be the second parameter. + +```js +try { + process.chdir('/foo/foo'); +} catch (err) { + util.triggerReport(err); +} +// ... +``` + +## Configuration + +Additional configuration is available using the following APIs: + +```js +const util = require('util'); +util.setEvents('exception+fatalerror+signal'); +util.setSignal('SIGUSR2|SIGQUIT'); +util.setFileName('stdout|stderr|'); +util.setDirectory(''); +util.setVerbose('yes|no'); +``` + +Configuration on module initialization is also available via +environment variables: + +```bash +export NODEREPORT_EVENTS=exception+fatalerror+signal+apicall +export NODEREPORT_SIGNAL=SIGUSR2|SIGQUIT +export NODEREPORT_FILENAME=stdout|stderr| +export NODEREPORT_DIRECTORY= +export NODEREPORT_VERBOSE=yes|no +``` + +Detailed API documentation can be found under [`util`][] section. + +[npm counterpart]: https://www.npmjs.com/package/node-report +[`util`]: util.html diff --git a/doc/api/util.md b/doc/api/util.md index 2fadc6b9ef2ee7..ca7d8b6756a72f 100644 --- a/doc/api/util.md +++ b/doc/api/util.md @@ -270,6 +270,26 @@ util.formatWithOptions({ colors: true }, 'See object %O', { foo: 42 }); // when printed to a terminal. ``` +## util.getNodeReport() + + +* Returns: {string} + +Returns the nore report as a string. + +Generates a human readable diagnostic summary. The report is intended for +development, test and production use, to capture and preserve information +for problem determination. It includes JavaScript and native stack traces, +heap statistics, platform information and resource usage etc. + +```js +const util = require('util'); +const report = util.getNodeReport(); +console.log(report); +``` + ## util.getSystemErrorName(err) + +* `events` {string} + +Runtime configuration of node report data capture. The string can be a comma- +separated list of one or more of: +`exception`: auto-generate a report on unhandled exceptions +`fatalerror`: auto-generate a report on unhandled internal fault +(such as out of memory errors or native assertions) +`signal`: auto-generate a report in response to a signal raised on the process. +This is convinient for collecting snapshot information of the running process at +custom program points. + +```js +const util = require('util'); +// trigger report only on uncaught exceptions +util.setReportEvents('exception'); + +// trigger for both internal error and external signal +util.setReportEvents('fatalerror+signal'); +``` + +## util.setReportSignal(signal) + + +* `signal` {string} + +Runtime modification to the node report data capture signal (default SIGUSR2). +Convinient when the execution environment already uses SIGUSR2 for other +purposes and wants Node to use another one for report generation purpose. + +```js +const util = require('util'); +util.setReportSignal('SIGQUIT'); +``` + +Multiple signals are allowed, in which case supply them as `OR` separated: + +```js +const util = require('util'); +util.setReportSignal('SIGUSR2|SIGQUIT'); +``` +This function does not work on Windows. + ## Class: util.TextDecoder + +* `filename` {string} The file to write into. **Default:** an empty string. +* Returns: {string} + +Returns the filename of the generated report. + +Triggers and produces the node report (a human readable snapshot of the internal +state of Node runtime), writes into a file at the location from where this Node +process was launched. + +```js +const util = require('util'); +util.triggerNodeReport(); +``` + +When a report is triggered, start and end messages are issued to stderr and the +filename of the report is returned to the caller. The default filename includes +the date, time, PID and a sequence number. Alternatively, a filename can be +specified as a parameter on the triggerNodeReport() call. + +```js +util.triggerNodeReport('myReportName'); +``` + ## util.types ```js function foo() { @@ -986,8 +1079,6 @@ Returns `true` if the value is a built-in [`ArrayBuffer`][] instance. This does *not* include [`SharedArrayBuffer`][] instances. Usually, it is desirable to test for both; See [`util.types.isAnyArrayBuffer()`][] for that. -For example: - ```js util.types.isArrayBuffer(new ArrayBuffer()); // Returns true util.types.isArrayBuffer(new SharedArrayBuffer()); // Returns false @@ -1006,8 +1097,6 @@ Note that this only reports back what the JavaScript engine is seeing; in particular, the return value may not match the original source code if a transpilation tool was used. -For example: - ```js util.types.isAsyncFunction(function foo() {}); // Returns false util.types.isAsyncFunction(async function foo() {}); // Returns true @@ -1023,8 +1112,6 @@ added: v10.0.0 Returns `true` if the value is a `BigInt64Array` instance. -For example: - ```js util.types.isBigInt64Array(new BigInt64Array()); // Returns true util.types.isBigInt64Array(new BigUint64Array()); // Returns false @@ -1040,8 +1127,6 @@ added: v10.0.0 Returns `true` if the value is a `BigUint64Array` instance. -For example: - ```js util.types.isBigUint64Array(new BigInt64Array()); // Returns false util.types.isBigUint64Array(new BigUint64Array()); // Returns true @@ -1058,8 +1143,6 @@ added: v10.0.0 Returns `true` if the value is a boolean object, e.g. created by `new Boolean()`. -For example: - ```js util.types.isBooleanObject(false); // Returns false util.types.isBooleanObject(true); // Returns false @@ -1079,8 +1162,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`DataView`][] instance. -For example: - ```js const ab = new ArrayBuffer(20); util.types.isDataView(new DataView(ab)); // Returns true @@ -1099,8 +1180,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Date`][] instance. -For example: - ```js util.types.isDate(new Date()); // Returns true ``` @@ -1125,8 +1204,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Float32Array`][] instance. -For example: - ```js util.types.isFloat32Array(new ArrayBuffer()); // Returns false util.types.isFloat32Array(new Float32Array()); // Returns true @@ -1143,8 +1220,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Float64Array`][] instance. -For example: - ```js util.types.isFloat64Array(new ArrayBuffer()); // Returns false util.types.isFloat64Array(new Uint8Array()); // Returns false @@ -1164,8 +1239,6 @@ Note that this only reports back what the JavaScript engine is seeing; in particular, the return value may not match the original source code if a transpilation tool was used. -For example: - ```js util.types.isGeneratorFunction(function foo() {}); // Returns false util.types.isGeneratorFunction(function* foo() {}); // Returns true @@ -1185,8 +1258,6 @@ Note that this only reports back what the JavaScript engine is seeing; in particular, the return value may not match the original source code if a transpilation tool was used. -For example: - ```js function* foo() {} const generator = foo(); @@ -1203,8 +1274,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Int8Array`][] instance. -For example: - ```js util.types.isInt8Array(new ArrayBuffer()); // Returns false util.types.isInt8Array(new Int8Array()); // Returns true @@ -1221,8 +1290,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Int16Array`][] instance. -For example: - ```js util.types.isInt16Array(new ArrayBuffer()); // Returns false util.types.isInt16Array(new Int16Array()); // Returns true @@ -1239,8 +1306,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Int32Array`][] instance. -For example: - ```js util.types.isInt32Array(new ArrayBuffer()); // Returns false util.types.isInt32Array(new Int32Array()); // Returns true @@ -1257,8 +1322,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Map`][] instance. -For example: - ```js util.types.isMap(new Map()); // Returns true ``` @@ -1274,8 +1337,6 @@ added: v10.0.0 Returns `true` if the value is an iterator returned for a built-in [`Map`][] instance. -For example: - ```js const map = new Map(); util.types.isMapIterator(map.keys()); // Returns true @@ -1294,8 +1355,6 @@ added: v10.0.0 Returns `true` if the value is an instance of a [Module Namespace Object][]. -For example: - ```js import * as ns from './a.js'; @@ -1313,8 +1372,6 @@ added: v10.0.0 Returns `true` if the value is an instance of a built-in [`Error`][] type. -For example: - ```js util.types.isNativeError(new Error()); // Returns true util.types.isNativeError(new TypeError()); // Returns true @@ -1332,8 +1389,6 @@ added: v10.0.0 Returns `true` if the value is a number object, e.g. created by `new Number()`. -For example: - ```js util.types.isNumberObject(0); // Returns false util.types.isNumberObject(new Number(0)); // Returns true @@ -1349,8 +1404,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Promise`][]. -For example: - ```js util.types.isPromise(Promise.resolve(42)); // Returns true ``` @@ -1365,8 +1418,6 @@ added: v10.0.0 Returns `true` if the value is a [`Proxy`][] instance. -For example: - ```js const target = {}; const proxy = new Proxy(target, {}); @@ -1384,8 +1435,6 @@ added: v10.0.0 Returns `true` if the value is a regular expression object. -For example: - ```js util.types.isRegExp(/abc/); // Returns true util.types.isRegExp(new RegExp('abc')); // Returns true @@ -1401,8 +1450,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Set`][] instance. -For example: - ```js util.types.isSet(new Set()); // Returns true ``` @@ -1418,8 +1465,6 @@ added: v10.0.0 Returns `true` if the value is an iterator returned for a built-in [`Set`][] instance. -For example: - ```js const set = new Set(); util.types.isSetIterator(set.keys()); // Returns true @@ -1440,8 +1485,6 @@ Returns `true` if the value is a built-in [`SharedArrayBuffer`][] instance. This does *not* include [`ArrayBuffer`][] instances. Usually, it is desirable to test for both; See [`util.types.isAnyArrayBuffer()`][] for that. -For example: - ```js util.types.isSharedArrayBuffer(new ArrayBuffer()); // Returns false util.types.isSharedArrayBuffer(new SharedArrayBuffer()); // Returns true @@ -1458,8 +1501,6 @@ added: v10.0.0 Returns `true` if the value is a string object, e.g. created by `new String()`. -For example: - ```js util.types.isStringObject('foo'); // Returns false util.types.isStringObject(new String('foo')); // Returns true @@ -1476,8 +1517,6 @@ added: v10.0.0 Returns `true` if the value is a symbol object, created by calling `Object()` on a `Symbol` primitive. -For example: - ```js const symbol = Symbol('foo'); util.types.isSymbolObject(symbol); // Returns false @@ -1494,8 +1533,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`TypedArray`][] instance. -For example: - ```js util.types.isTypedArray(new ArrayBuffer()); // Returns false util.types.isTypedArray(new Uint8Array()); // Returns true @@ -1514,8 +1551,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Uint8Array`][] instance. -For example: - ```js util.types.isUint8Array(new ArrayBuffer()); // Returns false util.types.isUint8Array(new Uint8Array()); // Returns true @@ -1532,8 +1567,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Uint8ClampedArray`][] instance. -For example: - ```js util.types.isUint8ClampedArray(new ArrayBuffer()); // Returns false util.types.isUint8ClampedArray(new Uint8ClampedArray()); // Returns true @@ -1550,8 +1583,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Uint16Array`][] instance. -For example: - ```js util.types.isUint16Array(new ArrayBuffer()); // Returns false util.types.isUint16Array(new Uint16Array()); // Returns true @@ -1568,8 +1599,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`Uint32Array`][] instance. -For example: - ```js util.types.isUint32Array(new ArrayBuffer()); // Returns false util.types.isUint32Array(new Uint32Array()); // Returns true @@ -1586,8 +1615,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`WeakMap`][] instance. -For example: - ```js util.types.isWeakMap(new WeakMap()); // Returns true ``` @@ -1602,8 +1629,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`WeakSet`][] instance. -For example: - ```js util.types.isWeakSet(new WeakSet()); // Returns true ``` @@ -1618,8 +1643,6 @@ added: v10.0.0 Returns `true` if the value is a built-in [`WebAssembly.Module`][] instance. -For example: - ```js const module = new WebAssembly.Module(wasmBuffer); util.types.isWebAssemblyCompiledModule(module); // Returns true diff --git a/lib/util.js b/lib/util.js index fa96ade26a5954..33262989623242 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1540,3 +1540,28 @@ module.exports = exports = { 'util.puts is deprecated. Use console.log instead.', 'DEP0027') }; + +const { + triggerNodeReport, + getNodeReport, + setReportEvents, + setReportSignal, + setReportFileName, + setReportDirectory, + setReportverbose, +} = process.binding('util'); + +if (triggerNodeReport !== undefined) + exports.triggerNodeReport = triggerNodeReport; +if (getNodeReport !== undefined) + exports.getNodeReport = getNodeReport; +if (setReportEvents !== undefined) + exports.setReportEvents = setReportEvents; +if (setReportSignal !== undefined) + exports.setReportSignal = setReportSignal; +if (setReportFileName !== undefined) + exports.setReportFileName = setReportFileName; +if (setReportDirectory !== undefined) + exports.setReportDirectory = setReportDirectory; +if (setReportverbose !== undefined) + exports.setReportverbose = setReportverbose; diff --git a/node.gyp b/node.gyp index a33dde4272e994..f4756ce59511a2 100644 --- a/node.gyp +++ b/node.gyp @@ -626,6 +626,33 @@ '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', + 'NODEREPORT_VERSION="1.0.0"', + ], + 'conditions': [ + ['OS=="win"', { + 'libraries': [ + 'dbghelp.lib', + 'Netapi32.lib', + 'PsApi.lib', + 'Ws2_32.lib', + ], + 'dll_files': [ + 'dbghelp.dll', + 'Netapi32.dll', + 'PsApi.dll', + 'Ws2_32.dll', + ], + }], + ], + }], ], }, { diff --git a/src/node.cc b/src/node.cc index 33a68b37a46062..84948191e7552f 100644 --- a/src/node.cc +++ b/src/node.cc @@ -91,6 +91,10 @@ #include #endif +#if defined(NODE_REPORT) +#include "node_report.h" +#endif + #if defined(LEAK_SANITIZER) #include #endif @@ -197,6 +201,7 @@ static std::string trace_enabled_categories; // NOLINT(runtime/string) static std::string trace_file_pattern = // NOLINT(runtime/string) "node_trace.${rotation}.log"; static bool abort_on_uncaught_exception = false; +static std::string report_events; // NOLINT(runtime/string) // Bit flag used to track security reverts (see node_revert.h) unsigned int reverted = 0; @@ -2028,6 +2033,13 @@ static Local GetFeatures(Environment* env) { obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls_ocsp"), have_openssl); obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "tls"), have_openssl); +#if defined(NODE_REPORT) + Local node_report = True(env->isolate()); +#else + Local node_report = False(env->isolate()); +#endif + obj->Set(FIXED_ONE_BYTE_STRING(env->isolate(), "node_report"), node_report); + return scope.Escape(obj); } @@ -2573,6 +2585,13 @@ void LoadEnvironment(Environment* env) { return; } +#if defined(NODE_REPORT) + if (!report_events.empty()) { + nodereport::InitializeNodeReport(); + nodereport::SetEvents(env->isolate(), report_events.c_str()); + } +#endif + // Bootstrap Node.js Local bootstrapper = Object::New(env->isolate()); SetupBootstrapObject(env, bootstrapper); @@ -3039,6 +3058,40 @@ static void ParseArgs(int* argc, // Also a V8 option. Pass through as-is. new_v8_argv[new_v8_argc] = arg; new_v8_argc += 1; +#if defined(NODE_REPORT) + } else if (strcmp(arg, "--report-events") == 0) { + const char* events = argv[index + 1]; + if (events == nullptr) { + fprintf(stderr, "%s: %s requires an argument\n", argv[0], arg); + exit(9); + } + + args_consumed += 1; + + // if the --report-events flag is present but not the + // associated flags, we could be trapped to read-in + // the script name or any other token to be the flag. + // If the next token has nothing what we expected, + // insert the defaults: all three events. + if (!(strcasestr(events, "fatalerror,") || + strcasestr(events, "fatalerror")) && + (!(strcasestr(events, "exception,") || + strcasestr(events, "exception"))) && + (!(strcasestr(events, "signal,") || + strcasestr(events, "signal")))) { + events = "fatalerror,signal,exception"; + + // avoid consumption of the next valid flag + args_consumed -= 1; + } + report_events = events; + // Replace ',' with '+' separators + std::size_t c = report_events.find_first_of(","); + while (c != std::string::npos) { + report_events.replace(c, 1, "+"); + c = report_events.find_first_of(",", c + 1); + } +#endif // NODE_REPORT } else { // V8 option. Pass through as-is. new_v8_argv[new_v8_argc] = arg; diff --git a/src/node_internals.h b/src/node_internals.h index 968d229f1001a7..e9c79f9309661b 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -98,6 +98,12 @@ struct sockaddr; #define NODE_BUILTIN_ICU_MODULES(V) #endif +#if NODE_REPORT +#define NODE_BUILTIN_NODE_REPORT_MODULES(V) V(node_report) +#else +#define NODE_BUILTIN_NODE_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 node::RegisterBuiltinModules(), it calls modules' registration @@ -132,7 +138,7 @@ struct sockaddr; V(string_decoder) \ V(symbols) \ V(tcp_wrap) \ - V(timers) \ + V(timers) \ V(trace_events) \ V(tty_wrap) \ V(types) \ @@ -147,7 +153,8 @@ struct sockaddr; #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_NODE_REPORT_MODULES(V) #define NODE_MODULE_CONTEXT_AWARE_CPP(modname, regfunc, priv, flags) \ static node::node_module _module = { \ diff --git a/src/node_report.cc b/src/node_report.cc new file mode 100644 index 00000000000000..4d984cf57243b6 --- /dev/null +++ b/src/node_report.cc @@ -0,0 +1,1115 @@ +#include "node_report.h" +#include "v8.h" +#include "uv.h" + +#include +#include +#include + +#if !defined(_MSC_VER) +#include +#endif + +#ifdef _WIN32 +#include +#include +#include +#include +#include +#include +#else +#include +// Get the standard printf format macros for C99 stdint types. +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include +#include +#include +#if defined(__linux__) || defined(__sun) +#include +#endif +#ifdef _AIX +#include // ld_info structure +#endif +// Include execinfo.h for the native backtrace API. The API is +// unavailable on AIX and on some Linux distributions, e.g. Alpine Linux. +#if !defined(_AIX) && !(defined(__linux__) && !defined(__GLIBC__)) +#include +#endif +#include +#endif + +#ifdef __APPLE__ +#include // _dyld_get_image_name() +#endif + + + +#ifndef _WIN32 +extern char** environ; +#endif + +namespace nodereport { + +using v8::Exception; +using v8::HeapSpaceStatistics; +using v8::HeapStatistics; +using v8::Isolate; +using v8::Local; +using v8::Message; +using v8::RegisterState; +using v8::SampleInfo; +using v8::String; +using v8::V8; + +// Internal/static function declarations +static void WriteNodeReport(Isolate* isolate, DumpEvent event, + const char* message, const char* location, + char* filename, std::ostream &out, + MaybeLocal error, TIME_TYPE* time); +static void PrintCommandLine(std::ostream& out); +static void PrintVersionInformation(std::ostream& out); +static void PrintJavaScriptStack(std::ostream& out, Isolate* isolate, + DumpEvent event, const char* location); +static void PrintJavaScriptErrorStack(std::ostream& out, Isolate* isolate, + MaybeLocal error); +static void PrintStackFromStackTrace(std::ostream& out, Isolate* isolate, + DumpEvent event); +static void PrintStackFrame(std::ostream& out, Isolate* isolate, + Local frame, int index, void* pc); +static void PrintNativeStack(std::ostream& out); +#ifndef _WIN32 +static void PrintResourceUsage(std::ostream& out); +#endif +static void PrintGCStatistics(std::ostream& out, Isolate* isolate); +static void PrintSystemInformation(std::ostream& out, Isolate* isolate); +static void PrintLoadedLibraries(std::ostream& out, Isolate* isolate); + +// Global variables +static int seq = 0; // sequence number for report filenames +const char* v8_states[] = {"JS", "GC", "COMPILER", "OTHER", "EXTERNAL", "IDLE"}; +static bool report_active = false; // recursion protection +char report_filename[NR_MAXNAME + 1] = ""; +char report_directory[NR_MAXPATH + 1] = ""; // default: current directory +TIME_TYPE loadtime_tm_struct; // module load time +time_t load_time; // module load time absolute + +// std::string version_string = NODE_VERSION_STRING; +// std::string commandline_string = ""; +version_and_command_struct version_and_command; + + + +/******************************************************************************* + * External function to trigger a node report, writing to file. + * + * The 'name' parameter is in/out: an input filename is used if supplied, and + * the actual filename is returned. + ******************************************************************************/ +void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, + const char* location, char* name, + MaybeLocal error) { + // Recursion check for report in progress, bail out + if (report_active) return; + report_active = true; + + // Obtain the current time and the pid (platform dependent) + TIME_TYPE tm_struct; +#ifdef _WIN32 + GetLocalTime(&tm_struct); + DWORD pid = GetCurrentProcessId(); +#else // UNIX, OSX + struct timeval time_val; + gettimeofday(&time_val, nullptr); + localtime_r(&time_val.tv_sec, &tm_struct); + pid_t pid = getpid(); +#endif + + // Determine the required report filename. In order of priority: + // 1) supplied on API 2) configured on startup 3) default generated + char filename[NR_MAXNAME + 1] = ""; + if (name != nullptr && strlen(name) > 0) { + // Filename was specified as API parameter, use that + snprintf(filename, sizeof(filename), "%s", name); + } else if (strlen(report_filename) > 0) { + // File name was supplied via start-up option, use that + snprintf(filename, sizeof(filename), "%s", report_filename); + } else { + // Construct the report filename, with timestamp, pid and sequence number + snprintf(filename, sizeof(filename), "%s", "node-report"); + seq++; +#ifdef _WIN32 + snprintf(&filename[strlen(filename)], sizeof(filename) - strlen(filename), + ".%4d%02d%02d.%02d%02d%02d.%d.%03d.txt", + tm_struct.wYear, tm_struct.wMonth, tm_struct.wDay, + tm_struct.wHour, tm_struct.wMinute, tm_struct.wSecond, + pid, seq); +#else // UNIX, OSX + snprintf(&filename[strlen(filename)], sizeof(filename) - strlen(filename), + ".%4d%02d%02d.%02d%02d%02d.%d.%03d.txt", + tm_struct.tm_year+1900, tm_struct.tm_mon+1, tm_struct.tm_mday, + tm_struct.tm_hour, tm_struct.tm_min, tm_struct.tm_sec, + pid, seq); +#endif + } + + // Open the report file stream for writing. Supports stdout/err, + // user-specified or (default) generated name + std::ofstream outfile; + std::ostream* outstream = &std::cout; + if (!strncmp(filename, "stdout", sizeof("stdout") - 1)) { + outstream = &std::cout; + } else if (!strncmp(filename, "stderr", sizeof("stderr") - 1)) { + outstream = &std::cerr; + } else { + // Regular file. Append filename to directory path if one was specified + if (strlen(report_directory) > 0) { + char pathname[NR_MAXPATH + NR_MAXNAME + 1] = ""; +#ifdef _WIN32 + snprintf(pathname, sizeof(pathname), + "%s%s%s", report_directory, "\\", filename); +#else + snprintf(pathname, sizeof(pathname), + "%s%s%s", report_directory, "/", filename); +#endif + outfile.open(pathname, std::ios::out); + } else { + outfile.open(filename, std::ios::out); + } + // Check for errors on the file open + if (!outfile.is_open()) { + if (strlen(report_directory) > 0) { + std::cerr << "\nFailed to open Node.js report file: " + << filename << " directory: " << report_directory + << " (errno: " << errno << ")\n"; + } else { + std::cerr << "\nFailed to open Node.js report file: " + << filename << " (errno: " << errno << ")\n"; + } + return; + } else { + std::cerr << "\nWriting Node.js report to file: " << filename << "\n"; + } + } + + // Pass our stream about by reference, not by copying it. + std::ostream &out = outfile.is_open() ? outfile : *outstream; + + WriteNodeReport(isolate, event, message, + location, filename, out, error, &tm_struct); + + // Do not close stdout/stderr, only close files we opened. + if (outfile.is_open()) { + outfile.close(); + } + + std::cerr << "Node.js report completed\n"; + if (name != nullptr) { + snprintf(name, NR_MAXNAME + 1, "%s", filename); // return report file name + } +} + +/***************************************************************************** + * External function to trigger a node report, writing to a supplied stream. + * + *****************************************************************************/ +void GetNodeReport(Isolate* isolate, DumpEvent event, const char* message, + const char* location, MaybeLocal error, + std::ostream& out) { + // Obtain the current time and the pid (platform dependent) + TIME_TYPE tm_struct; +#ifdef _WIN32 + GetLocalTime(&tm_struct); +#else // UNIX, OSX + struct timeval time_val; + gettimeofday(&time_val, nullptr); + localtime_r(&time_val.tv_sec, &tm_struct); +#endif + WriteNodeReport(isolate, event, message, + location, nullptr, out, error, &tm_struct); +} + +/******************************************************************************* + * Internal function to coordinate and write the various sections of the node + * report to the supplied stream + *******************************************************************************/ +static void WriteNodeReport(Isolate* isolate, DumpEvent event, + const char* message, const char* location, + char* filename, std::ostream &out, + MaybeLocal error, TIME_TYPE* tm_struct) { +#ifdef _WIN32 + DWORD pid = GetCurrentProcessId(); +#else // UNIX, OSX + pid_t pid = getpid(); +#endif + + // Save formatting for output stream. + std::ios oldState(nullptr); + oldState.copyfmt(out); + + // File stream opened OK, now start printing the report content: + // the title and header information (event, filename, timestamp and pid) + out << "========================================" + "========================================\n"; + out << "==== Node Report =======================" + "========================================\n"; + out << "\nEvent: " << message << ", location: \"" << location << "\"\n"; + if ( filename != nullptr ) { + out << "Filename: " << filename << "\n"; + } + + // Print dump event and module load date/time stamps + char timebuf[64]; +#ifdef _WIN32 + snprintf(timebuf, sizeof(timebuf), "%4d/%02d/%02d %02d:%02d:%02d", + tm_struct->wYear, tm_struct->wMonth, tm_struct->wDay, + tm_struct->wHour, tm_struct->wMinute, tm_struct->wSecond); + out << "Dump event time: "<< timebuf << "\n"; + snprintf(timebuf, + sizeof(timebuf), + "%4d/%02d/%02d %02d:%02d:%02d", + loadtime_tm_struct.wYear, + loadtime_tm_struct.wMonth, + loadtime_tm_struct.wDay, + loadtime_tm_struct.wHour, + loadtime_tm_struct.wMinute, + loadtime_tm_struct.wSecond); + out << "Module load time: " << timebuf << "\n"; +#else // UNIX, OSX + snprintf(timebuf, sizeof(timebuf), "%4d/%02d/%02d %02d:%02d:%02d", + tm_struct->tm_year+1900, tm_struct->tm_mon+1, tm_struct->tm_mday, + tm_struct->tm_hour, tm_struct->tm_min, tm_struct->tm_sec); + out << "Dump event time: "<< timebuf << "\n"; + snprintf(timebuf, + sizeof(timebuf), + "%4d/%02d/%02d %02d:%02d:%02d", + loadtime_tm_struct.tm_year+1900, + loadtime_tm_struct.tm_mon+1, + loadtime_tm_struct.tm_mday, + loadtime_tm_struct.tm_hour, + loadtime_tm_struct.tm_min, + loadtime_tm_struct.tm_sec); + out << "Module load time: " << timebuf << "\n"; +#endif + // Print native process ID + out << "Process ID: " << pid << std::endl; + + + // Print out the command line. + PrintCommandLine(out); + out << std::flush; + + // Print Node.js and OS version information + PrintVersionInformation(out); + out << std::flush; + +// Print summary JavaScript stack backtrace + PrintJavaScriptStack(out, isolate, event, location); + out << std::flush; + + // Print native stack backtrace + PrintNativeStack(out); + out << std::flush; + + // Print the stack trace and message from the Error object. + // (If one was provided.) + PrintJavaScriptErrorStack(out, isolate, error); + out << std::flush; + + // Print V8 Heap and Garbage Collector information + PrintGCStatistics(out, isolate); + out << std::flush; + + // Print OS and current thread resource usage +#ifndef _WIN32 + PrintResourceUsage(out); + out << std::flush; +#endif + + // Print libuv handle summary + out << "\n=======================================" + "========================================="; + out << "\n==== Node.js libuv Handle Summary =====" + "=========================================\n"; + out << "\n(Flags: R=Ref, A=Active)\n"; + out << std::left << std::setw(7) << "Flags" << std::setw(10) << "Type" + << std::setw(4 + 2 * sizeof(void*)) << "Address" << "Details" + << std::endl; + uv_walk(uv_default_loop(), walkHandle, reinterpret_cast(&out)); + + // Print operating system information + PrintSystemInformation(out, isolate); + + out << "\n=======================================" + "=========================================\n"; + out << std::flush; + + // Restore output stream formatting. + out.copyfmt(oldState); + + report_active = false; +} + +/******************************************************************************* + * Function to print process command line. + * + ******************************************************************************/ +static void PrintCommandLine(std::ostream& out) { + if (version_and_command.commandline_string != "") { + out << "Command line: " << version_and_command.commandline_string << "\n"; + } +} + +/******************************************************************************* + * Function to print Node.js version, OS version and machine information + * + ******************************************************************************/ +static void PrintVersionInformation(std::ostream& out) { + // Print Node.js and deps component versions + out << "\n" << version_and_command.version_string; + + // Print Node version + out << std::endl << "Node.js v" << NODE_VERSION_STRING; +#if defined(__GLIBC__) + out << ", glibc " << __GLIBC__ << "." << __GLIBC_MINOR__; +#endif + // Print Process word size + out << ", " << sizeof(void *) * 8 << " bit" << ")" << std::endl; + + // Print operating system and machine information (Windows) +#ifdef _WIN32 + { + const DWORD level = 101; + LPSERVER_INFO_101 os_info = nullptr; + NET_API_STATUS nStatus = NetServerGetInfo(nullptr, level, + reinterpret_cast(&os_info)); + if (nStatus == NERR_Success) { + LPSTR os_name = "Windows"; + const DWORD major = os_info->sv101_version_major & MAJOR_VERSION_MASK; + const DWORD type = os_info->sv101_type; + const bool isServer = (type & SV_TYPE_DOMAIN_CTRL) || + (type & SV_TYPE_DOMAIN_BAKCTRL) || + (type & SV_TYPE_SERVER_NT); + switch (major) { + case 5: + switch (os_info->sv101_version_minor) { + case 0: + os_name = "Windows 2000"; + break; + default: + os_name = (isServer ? "Windows Server 2003" : "Windows XP"); + } + break; + case 6: + switch (os_info->sv101_version_minor) { + case 0: + os_name = (isServer ? "Windows Server 2008" : "Windows Vista"); + break; + case 1: + os_name = (isServer ? "Windows Server 2008 R2" : "Windows 7"); + break; + case 2: + os_name = (isServer ? "Windows Server 2012" : "Windows 8"); + break; + case 3: + os_name = (isServer ? "Windows Server 2012 R2" : "Windows 8.1"); + break; + default: + os_name = (isServer ? "Windows Server" : "Windows Client"); + } + break; + case 10: + os_name = (isServer ? "Windows Server 2016" : "Windows 10"); + break; + default: + os_name = (isServer ? "Windows Server" : "Windows Client"); + } + out << "\nOS version: " << os_name << "\n"; + + // Convert and print the machine name and comment fields + // (these are LPWSTR types) + size_t count; + char name_buf[256]; + wcstombs_s(&count, name_buf, sizeof(name_buf), + os_info->sv101_name, _TRUNCATE); + if (os_info->sv101_comment != nullptr) { + char comment_buf[256]; + wcstombs_s(&count, comment_buf, sizeof(comment_buf), + os_info->sv101_comment, _TRUNCATE); + out << "\nMachine: " << name_buf << " " << comment_buf << "\n"; + } else { + out << "\nMachine: " << name_buf << "\n"; + } + + if (os_info != nullptr) { + NetApiBufferFree(os_info); + } + } else { + // NetServerGetInfo() failed, fallback to use GetComputerName() instead + TCHAR machine_name[256]; + DWORD machine_name_size = 256; + out << "\nOS version: Windows\n"; + if (GetComputerName(machine_name, &machine_name_size)) { + out << "\nMachine: " << machine_name << "\n"; + } + } + } +#else + // Print operating system and machine information (Unix/OSX) + struct utsname os_info; + if (uname(&os_info) >= 0) { +#if defined(_AIX) + out << "\nOS version: " << os_info.sysname << " " << os_info.version << "." + << os_info.release << "\n"; +#else + out << "\nOS version: " << os_info.sysname << " " << os_info.release << " " + << os_info.version << "\n"; +#endif + const char *(*libc_version)(); + *(reinterpret_cast(&libc_version)) = dlsym(RTLD_DEFAULT, + "gnu_get_libc_version"); + if (libc_version != nullptr) { + out << "(glibc: " << (*libc_version)() << ")" << std::endl; + } + out << "\nMachine: " << os_info.nodename << " " << os_info.machine << "\n"; + } +#endif +} + +/******************************************************************************* + * Function to print the JavaScript stack, if available + * + ******************************************************************************/ +static void PrintJavaScriptStack(std::ostream& out, Isolate* isolate, + DumpEvent event, const char* location) { + out << "\n=======================================" + "========================================="; + out << "\n==== JavaScript Stack Trace ===========" + "=========================================\n\n"; + +#ifdef _WIN32 + switch (event) { + case kFatalError: + // Stack trace on fatal error not supported on Windows + out << "No stack trace available\n"; + break; + default: + // All other events, print the stack using + // StackTrace::StackTrace() and GetStackSample() APIs + PrintStackFromStackTrace(out, isolate, event); + break; + } // end switch(event) +#else // Unix, OSX + switch (event) { + case kException: + case kJavaScript: { + // Print the stack using Message::PrintCurrentStackTrace() API + std::FILE* stack_fp = std::tmpfile(); + if (stack_fp != nullptr) { + char stack_buf[64]; + Message::PrintCurrentStackTrace(isolate, stack_fp); + std::fflush(stack_fp); + std::rewind(stack_fp); + while (std::fgets(stack_buf, sizeof(stack_buf), stack_fp) != nullptr) { + out << stack_buf; + } + // Calling close on a file from tmpfile *should* delete it. + std::fclose(stack_fp); + } else { + out << "No stack trace available, unable to create temporary file\n"; + } + break; + } + case kFatalError: + out << "No stack trace available\n"; + break; + case kSignal_JS: + case kSignal_UV: + // Print the stack using StackTrace::StackTrace() and GetStackSample() APIs + PrintStackFromStackTrace(out, isolate, event); + break; + } // end switch(event) +#endif +} + +/******************************************************************************* + * Function to print a JavaScript stack from an error object + * + ******************************************************************************/ +static void PrintJavaScriptErrorStack(std::ostream& out, Isolate* isolate, + MaybeLocal error) { + if (error.IsEmpty() || !error.ToLocalChecked()->IsNativeError()) { + return; + } + + out << "\n=======================================" + "========================================="; + out << "\n==== JavaScript Exception Details =====" + "=========================================\n\n"; + Local message = Exception::CreateMessage(isolate, + error.ToLocalChecked()); + + out << *message << "\n\n"; + + Local stack = Exception::GetStackTrace(error.ToLocalChecked()); + if (stack.IsEmpty()) { + out << "No stack trace available from Exception::GetStackTrace()\n"; + return; + } + // Print the stack trace, + // samples are not available as the exception isn't from the current stack. + for (int i = 0; i < stack->GetFrameCount(); i++) { + PrintStackFrame(out, isolate, stack->GetFrame(i), i, nullptr); + } +} + +/******************************************************************************* + * Function to print stack using GetStackSample() and StackTrace::StackTrace() + * + ******************************************************************************/ +static void PrintStackFromStackTrace(std::ostream& out, + Isolate* isolate, DumpEvent event) { + RegisterState state; + SampleInfo info; + void* samples[255]; + + // Initialise the register state + state.pc = nullptr; + state.fp = &state; + state.sp = &state; + + isolate->GetStackSample(state, samples, arraysize(samples), &info); + if (static_cast(info.vm_state) < arraysize(v8_states)) { + out << "JavaScript VM state: " << v8_states[info.vm_state] << "\n\n"; + } else { + out << "JavaScript VM state: \n\n"; + } + if (event == kSignal_UV) { + out << "Signal received when event loop idle, no stack trace available\n"; + return; + } + Local stack = StackTrace::CurrentStackTrace(isolate, 255, + StackTrace::kDetailed); + if (stack.IsEmpty()) { + out << "\nNo stack trace available from StackTrace::CurrentStackTrace()\n"; + return; + } + // Print the stack trace, adding in the + // pc values from GetStackSample() if available + for (int i = 0; i < stack->GetFrameCount(); i++) { + if (static_cast(i) < info.frames_count) { + PrintStackFrame(out, isolate, stack->GetFrame(i), i, samples[i]); + } else { + PrintStackFrame(out, isolate, stack->GetFrame(i), i, nullptr); + } + } +} + +/******************************************************************************* + * Function to print a JavaScript stack frame from a V8 StackFrame object + * + ******************************************************************************/ +static void PrintStackFrame(std::ostream& out, Isolate* isolate, + Local frame, int i, void* pc) { + String::Utf8Value fn_name_s(isolate, frame->GetFunctionName()); + String::Utf8Value script_name(isolate, frame->GetScriptName()); + const int line_number = frame->GetLineNumber(); + const int column = frame->GetColumn(); + char buf[64]; + + // First print the frame index and the instruction address + if (pc != nullptr) { +#ifdef _WIN32 + snprintf(buf, sizeof(buf), "%2d: [pc=0x%p] ", i, pc); +#else + snprintf(buf, sizeof(buf), "%2d: [pc=%p] ", i, pc); +#endif + out << buf; + } + + // Now print the JavaScript function name and source information + if (frame->IsEval()) { + if (frame->GetScriptId() == Message::kNoScriptIdInfo) { + out << "at [eval]:" << line_number << ":" << column << "\n"; + } else { + out << "at [eval] (" << *script_name << ":" << line_number << ":" + << column << ")\n"; + } + return; + } + + if (fn_name_s.length() == 0) { + out << *script_name << ":" << line_number << ":" << column << "\n"; + } else { + if (frame->IsConstructor()) { + out << *fn_name_s << " [constructor] (" << *script_name << ":" + << line_number << ":" << column << ")\n"; + } else { + out << *fn_name_s << " (" << *script_name << ":" << line_number << ":" + << column << ")\n"; + } + } +} + + +#ifdef _WIN32 +/******************************************************************************* + * Function to print a native stack backtrace + * + ******************************************************************************/ +void PrintNativeStack(std::ostream& out) { + void* frames[64]; + out << "\n=======================================" + "========================================="; + out << "\n==== Native Stack Trace ===============" + "=========================================\n\n"; + + HANDLE hProcess = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); + SymInitialize(hProcess, nullptr, TRUE); + char buf[64]; + + WORD numberOfFrames = CaptureStackBackTrace(2, 64, frames, nullptr); + + // Walk the frames printing symbolic information if available + for (int i = 0; i < numberOfFrames; i++) { + DWORD64 dwOffset64 = 0; + DWORD64 dwAddress = reinterpret_cast(frames[i]); + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO pSymbol = reinterpret_cast(buffer); + pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); + pSymbol->MaxNameLen = MAX_SYM_NAME; + + if (SymFromAddr(hProcess, dwAddress, &dwOffset64, pSymbol)) { + DWORD dwOffset = 0; + IMAGEHLP_LINE64 line; + line.SizeOfStruct = sizeof(line); + snprintf(buf, sizeof(buf), "%2d: [pc=0x%p]", i, + reinterpret_cast(pSymbol->Address)); + out << buf << " " << pSymbol->Name << " [+"; + if (SymGetLineFromAddr64(hProcess, dwAddress, &dwOffset, &line)) { + out << dwOffset << "] in " << line.FileName + << ": line: " << line.LineNumber << "\n"; + } else { + out << dwOffset64 << "]\n"; + } + } else { // SymFromAddr() failed, just print the address + snprintf(buf, sizeof(buf), "%2d: [pc=0x%p]\n", i, + reinterpret_cast(dwAddress)); + out << buf; + } + } +} +#elif _AIX +/******************************************************************************* + * Function to print a native stack backtrace - AIX + * + ******************************************************************************/ +void PrintNativeStack(std::ostream& out) { + out << "\n=======================================" + "========================================="; + out << "\n==== Native Stack Trace ===============" + "=========================================\n\n"; + out << "Native stack trace not supported on AIX\n"; +} +#elif(defined(__linux__) && !defined(__GLIBC__)) +/******************************************************************************* + * Function to print a native stack backtrace - Alpine Linux etc + * + ******************************************************************************/ +void PrintNativeStack(std::ostream& out) { + out << "\n=======================================" + "========================================="; + out << "\n==== Native Stack Trace ===============" + "=========================================\n\n"; + out << "Native stack trace not supported on Linux platforms without GLIBC\n"; +} +#else +/******************************************************************************* + * Function to print a native stack backtrace - Linux/OSX + * + ******************************************************************************/ +void PrintNativeStack(std::ostream& out) { + void* frames[256]; + char buf[64]; + out << "\n=======================================" + "========================================="; + out << "\n==== Native Stack Trace ===============" + "=========================================\n\n"; + + // Get the native backtrace (array of instruction addresses) + const int size = backtrace(frames, arraysize(frames)); + if (size <= 0) { + out << "Native backtrace failed, error " << size << "\n"; + return; + } else if (size <=2) { + out << "No frames to print\n"; + return; + } + + // Print the native frames, omitting the top 3 frames as they are + // in node-report code backtrace_symbols_fd(frames, size, fileno(fp)); + for (int i = 2; i < size; i++) { + // print frame index and instruction address + snprintf(buf, sizeof(buf), "%2d: [pc=%p] ", i-2, frames[i]); + out << buf; + // If we can translate the address using dladdr() print + // additional symbolic information + Dl_info info; + if (dladdr(frames[i], &info)) { + if (info.dli_sname != nullptr) { + if (char* demangled = abi::__cxa_demangle(info.dli_sname, 0, 0, 0)) { + out << demangled; // print demangled symbol name + free(demangled); + } else { + out << info.dli_sname; // just print the symbol name + } + } + if (info.dli_fname != nullptr) { + out << " [" << info.dli_fname << "]"; // print shared object name + } + } + out << std::endl; + } +} +#endif + +/******************************************************************************* + * Function to print V8 JavaScript heap information. + * + * This uses the existing V8 HeapStatistics and HeapSpaceStatistics APIs. + * The isolate->GetGCStatistics(&heap_stats) internal V8 API could potentially + * provide some more useful information - the GC history and the handle counts + ******************************************************************************/ +static void PrintGCStatistics(std::ostream& out, Isolate* isolate) { + HeapStatistics v8_heap_stats; + isolate->GetHeapStatistics(&v8_heap_stats); + + out << "\n=======================================" + "========================================="; + out << "\n==== JavaScript Heap and Garbage Collector" + " =====================================\n"; + HeapSpaceStatistics v8_heap_space_stats; + // Loop through heap spaces + for (size_t i = 0; i < isolate->NumberOfHeapSpaces(); i++) { + isolate->GetHeapSpaceStatistics(&v8_heap_space_stats, i); + out << "\nHeap space name: " << v8_heap_space_stats.space_name(); + out << "\n Memory size: "; + WriteInteger(out, v8_heap_space_stats.space_size()); + out << " bytes, committed memory: "; + WriteInteger(out, v8_heap_space_stats.physical_space_size()); + out << " bytes\n Capacity: "; + WriteInteger(out, v8_heap_space_stats.space_used_size() + + v8_heap_space_stats.space_available_size()); + out << " bytes, used: "; + WriteInteger(out, v8_heap_space_stats.space_used_size()); + out << " bytes, available: "; + WriteInteger(out, v8_heap_space_stats.space_available_size()); + out << " bytes"; + } + + out << "\n\nTotal heap memory size: "; + WriteInteger(out, v8_heap_stats.total_heap_size()); + out << " bytes\nTotal heap committed memory: "; + WriteInteger(out, v8_heap_stats.total_physical_size()); + out << " bytes\nTotal used heap memory: "; + WriteInteger(out, v8_heap_stats.used_heap_size()); + out << " bytes\nTotal available heap memory: "; + WriteInteger(out, v8_heap_stats.total_available_size()); + out << " bytes\n\nHeap memory limit: "; + WriteInteger(out, v8_heap_stats.heap_size_limit()); + out << "\n"; +} + +#ifndef _WIN32 +/******************************************************************************* + * Function to print resource usage (Linux/OSX only). + * + ******************************************************************************/ +static void PrintResourceUsage(std::ostream& out) { + char buf[64]; + double cpu_abs; + double cpu_percentage; + time_t current_time; // current time absolute + time(¤t_time); + auto uptime = difftime(current_time, load_time); + if (uptime == 0) + uptime = 1; // avoid division by zero. + out << "\n=======================================" + "========================================="; + out << "\n==== Resource Usage ===================" + "=========================================\n"; + + // Process and current thread usage statistics + struct rusage stats; + out << "\nProcess total resource usage:"; + if (getrusage(RUSAGE_SELF, &stats) == 0) { +#if defined(__APPLE__) || defined(_AIX) + snprintf(buf, sizeof(buf), "%ld.%06d", + stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + out << "\n User mode CPU: " << buf << " secs"; + snprintf(buf, sizeof(buf), "%ld.%06d", + stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + out << "\n Kernel mode CPU: " << buf << " secs"; +#else + snprintf(buf, sizeof(buf), "%ld.%06ld", + stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + out << "\n User mode CPU: " << buf << " secs"; + snprintf(buf, sizeof(buf), "%ld.%06ld", + stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + out << "\n Kernel mode CPU: " << buf << " secs"; +#endif + cpu_abs = stats.ru_utime.tv_sec + 0.000001 * stats.ru_utime.tv_usec + + stats.ru_stime.tv_sec + 0.000001 * stats.ru_stime.tv_usec; + cpu_percentage = (cpu_abs / uptime) * 100.0; + out << "\n Average CPU Consumption : "<< cpu_percentage << "%"; + out << "\n Maximum resident set size: "; + WriteInteger(out, stats.ru_maxrss * 1024); + out << " bytes\n Page faults: " << stats.ru_majflt << " (I/O required) " + << stats.ru_minflt << " (no I/O required)"; + out << "\n Filesystem activity: " << stats.ru_inblock << " reads " + << stats.ru_oublock << " writes"; + } +#ifdef RUSAGE_THREAD + out << "\n\nEvent loop thread resource usage:"; + if (getrusage(RUSAGE_THREAD, &stats) == 0) { +#if defined(__APPLE__) || defined(_AIX) + snprintf(buf, sizeof(buf), "%ld.%06d", + stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + out << "\n User mode CPU: " << buf << " secs"; + snprintf(buf, sizeof(buf), "%ld.%06d", + stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + out << "\n Kernel mode CPU: " << buf << " secs"; +#else + snprintf(buf, sizeof(buf), "%ld.%06ld", + stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + out << "\n User mode CPU: " << buf << " secs"; + snprintf(buf, sizeof(buf), "%ld.%06ld", + stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + out << "\n Kernel mode CPU: " << buf << " secs"; +#endif + cpu_abs = stats.ru_utime.tv_sec + 0.000001 * stats.ru_utime.tv_usec + + stats.ru_stime.tv_sec + 0.000001 * stats.ru_stime.tv_usec; + cpu_percentage = (cpu_abs / uptime) * 100.0; + out << "\n Average CPU Consumption : " << cpu_percentage << "%"; + out << "\n Filesystem activity: " << stats.ru_inblock << " reads " + << stats.ru_oublock << " writes"; + } +#endif + out << std::endl; +} +#endif + +/******************************************************************************* + * Function to print operating system information. + * + ******************************************************************************/ +static void PrintSystemInformation(std::ostream& out, Isolate* isolate) { +static struct { + const char* description; + int id; +} rlimit_strings[] = { + {"core file size (blocks) ", RLIMIT_CORE}, + {"data seg size (kbytes) ", RLIMIT_DATA}, + {"file size (blocks) ", RLIMIT_FSIZE}, +#if !(defined(_AIX) || defined(__sun)) + {"max locked memory (bytes) ", RLIMIT_MEMLOCK}, +#endif +#ifndef __sun + {"max memory size (kbytes) ", RLIMIT_RSS}, +#endif + {"open files ", RLIMIT_NOFILE}, + {"stack size (bytes) ", RLIMIT_STACK}, + {"cpu time (seconds) ", RLIMIT_CPU}, +#ifndef __sun + {"max user processes ", RLIMIT_NPROC}, +#endif + {"virtual memory (kbytes) ", RLIMIT_AS} +}; + out << "\n=======================================" + "========================================="; + out << "\n==== System Information ===============" + "=========================================\n"; + +#ifdef _WIN32 + out << "\nEnvironment variables\n"; + LPTSTR lpszVariable; + LPTCH lpvEnv; + + // Get pointer to the environment block + lpvEnv = GetEnvironmentStrings(); + if (lpvEnv != nullptr) { + // Variable strings are separated by null bytes, + // and the block is terminated by a null byte. + lpszVariable = reinterpret_cast(lpvEnv); + while (*lpszVariable) { + out << " " << lpszVariable << "\n", lpszVariable; + lpszVariable += lstrlen(lpszVariable) + 1; + } + FreeEnvironmentStrings(lpvEnv); + } +#else + out << "\nEnvironment variables\n"; + int index = 1; + char* env_var = *environ; + + while (env_var != nullptr) { + out << " " << env_var << "\n"; + env_var = *(environ + index++); + } + + + out << "\nResource limits soft limit hard limit"; + out << "\n"; + struct rlimit limit; + char buf[64]; + + for (size_t i = 0; i < arraysize(rlimit_strings); i++) { + if (getrlimit(rlimit_strings[i].id, &limit) == 0) { + out << " " << rlimit_strings[i].description << " "; + if (limit.rlim_cur == RLIM_INFINITY) { + out << " unlimited"; + } else { +#if defined(_AIX) || defined(__sun) + snprintf(buf, sizeof(buf), "%16ld", limit.rlim_cur); + out << buf; +#elif(defined(__linux__) && !defined(__GLIBC__)) + snprintf(buf, sizeof(buf), "%16lld", limit.rlim_cur); + out << buf; +#else + snprintf(buf, sizeof(buf), "%16" PRIu64, limit.rlim_cur); + out << buf; +#endif + } + if (limit.rlim_max == RLIM_INFINITY) { + out << " unlimited\n"; + } else { +#if defined(_AIX) + snprintf(buf, sizeof(buf), "%16ld\n", limit.rlim_max); + out << buf; +#elif(defined(__linux__) && !defined(__GLIBC__)) + snprintf(buf, sizeof(buf), "%16lld\n", limit.rlim_max); + out << buf; +#else + snprintf(buf, sizeof(buf), "%16" PRIu64 "\n", limit.rlim_max); + out << buf; +#endif + } + } + } +#endif + + out << "\nLoaded libraries\n"; + PrintLoadedLibraries(out, isolate); +} + +/******************************************************************************* + * Functions to print a list of loaded native libraries. + * + ******************************************************************************/ +#ifdef __linux__ +static int LibraryPrintCallback(struct dl_phdr_info* info, + size_t size, void* data) { + std::ostream* out = reinterpret_cast(data); + if (info->dlpi_name != nullptr && *info->dlpi_name != '\0') { + *out << " " << info->dlpi_name << "\n"; + } + return 0; +} +#endif + +static void PrintLoadedLibraries(std::ostream& out, Isolate* isolate) { +#ifdef __linux__ + dl_iterate_phdr(LibraryPrintCallback, &out); +#elif __APPLE__ + int i = 0; + const char* name = _dyld_get_image_name(i); + while (name != nullptr) { + out << " " << name << "\n"; + i++; + name = _dyld_get_image_name(i); + } +#elif _AIX + // We can't tell in advance how large the buffer needs to be. + // Retry until we reach too large a size (1Mb). + const unsigned int buffer_inc = 4096; + unsigned int buffer_size = buffer_inc; + char* buffer = reinterpret_cast(malloc(buffer_size)); + int rc = -1; + while (buffer != nullptr && rc != 0 && buffer_size < 1024 * 1024) { + rc = loadquery(L_GETINFO, buffer, buffer_size); + if (rc == 0) { + break; + } + free(buffer); + buffer_size += buffer_inc; + buffer = reinterpret_cast(malloc(buffer_size)); + } + if (buffer == nullptr) { + return; // Don't try to free the buffer. + } + if (rc == 0) { + char* buf = buffer; + ld_info* cur_info = nullptr; + do { + cur_info = reinterpret_cast(buf); + char* member_name = cur_info->ldinfo_filename + + strlen(cur_info->ldinfo_filename) + 1; + if (*member_name != '\0') { + out << " " << cur_info->ldinfo_filename << "(" << member_name << ")\n"; + } else { + out << " " << cur_info->ldinfo_filename << "\n"; + } + buf += cur_info->ldinfo_next; + } while (cur_info->ldinfo_next != 0); + } + free(buffer); +#elif __sun + Link_map* p; + + if (dlinfo(RTLD_SELF, RTLD_DI_LINKMAP, &p) != -1) { + for (Link_map* l = p; l != nullptr; l = l->l_next) { + out << " " << l->l_name << "\n"; + } + } + +#elif _WIN32 + // Windows implementation - get a handle to the process. + HANDLE process_handle = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ, + FALSE, GetCurrentProcessId()); + if (process_handle == nullptr) { + out << "No library information available\n"; + return; + } + // Get a list of all the modules in this process + DWORD size_1 = 0, size_2 = 0; + // First call to get the size of module array needed + if (EnumProcessModules(process_handle, nullptr, 0, &size_1)) { + HMODULE* modules = reinterpret_cast(malloc(size_1)); + if (modules == null) { + return; // bail out if malloc failed + } + // Second call to populate the module array + if (EnumProcessModules(process_handle, modules, size_1, &size_2)) { + for (int i = 0; + i < (size_1 / sizeof(HMODULE)) && i < (size_2 / sizeof(HMODULE)); + i++) { + TCHAR module_name[MAX_PATH]; + // Obtain and print the full pathname for each module + if (GetModuleFileNameEx(process_handle, modules[i], module_name, + sizeof(module_name) / sizeof(TCHAR))) { + out << " " << module_name << "\n"; + } + } + } + free(modules); + } else { + out << "No library information available\n"; + } + // Release the handle to the process. + CloseHandle(process_handle); +#endif +} + +} // namespace nodereport diff --git a/src/node_report.h b/src/node_report.h new file mode 100644 index 00000000000000..660843ba9ec59a --- /dev/null +++ b/src/node_report.h @@ -0,0 +1,168 @@ +#ifndef SRC_NODE_REPORT_H_ +#define SRC_NODE_REPORT_H_ + +// #include "nan.h" +#include "v8.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(_MSC_VER) +# pragma warning(push) +# pragma warning(disable : 4530) +# include +# include +# include +# pragma warning(pop) +#else +# include +# include +# include +#endif + + + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +namespace nodereport { + +using v8::Isolate; +using v8::Local; +using v8::Message; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::Object; +using v8::Number; +using v8::String; +using v8::Value; +using v8::StackTrace; +using v8::StackFrame; +using v8::MaybeLocal; + +// Bit-flags for node-report trigger options +#define NR_EXCEPTION 0x01 +#define NR_FATALERROR 0x02 +#define NR_SIGNAL 0x04 +#define NR_APICALL 0x08 + +// Maximum file and path name lengths +#define NR_MAXNAME 64 +#define NR_MAXPATH 1024 + +enum DumpEvent {kException, kFatalError, kSignal_JS, kSignal_UV, kJavaScript}; + +#ifdef _WIN32 +typedef SYSTEMTIME TIME_TYPE; +#else // UNIX, OSX +typedef struct tm TIME_TYPE; +#endif + +// NODEREPORT_VERSION is defined in binding.gyp +#if !defined(NODEREPORT_VERSION) +#define NODEREPORT_VERSION "dev" +#endif +#define UNKNOWN_NODEVERSION_STRING "Unable to determine Node.js version\n" + +typedef struct version_and_command_struct { + std::string version_string = NODE_VERSION_STRING; + std::string commandline_string = ""; +}version_and_command_struct; +extern version_and_command_struct version_and_command; + +void InitializeNodeReport(void); +void SetEvents(Isolate* isolate, const char* args); +void SetEvents(const v8::FunctionCallbackInfo& info); + +// Function declarations - functions in src/node_report.cc +void TriggerNodeReport(Isolate* isolate, DumpEvent event, + const char* message, const char* location, + char* name, v8::MaybeLocal error); +void GetNodeReport(Isolate* isolate, DumpEvent event, + const char* message, const char* location, + v8::MaybeLocal error, std::ostream& out); + +// Function declarations - utility functions in src/utilities.cc +unsigned int ProcessNodeReportEvents(const char* args); +unsigned int ProcessNodeReportSignal(const char* args); +void ProcessNodeReportFileName(const char* args); +void ProcessNodeReportDirectory(const char* args); +unsigned int ProcessNodeReportVerboseSwitch(const char* args); +void SetLoadTime(); +void SetVersionString(Isolate* isolate); +void SetCommandLine(); +void reportEndpoints(uv_handle_t* h, std::ostringstream& out); +void reportPath(uv_handle_t* h, std::ostringstream& out); +void walkHandle(uv_handle_t* h, void* arg); +void WriteInteger(std::ostream& out, size_t value); + + +// Function declarations - export functions in src/node_report_module.cc +void TriggerReport(const FunctionCallbackInfo& info); +void GetReport(const FunctionCallbackInfo& info); +void SetEvents(const FunctionCallbackInfo& info); +void SetSignal(const FunctionCallbackInfo& info); +void SetFileName(const FunctionCallbackInfo& info); +void SetDirectory(const FunctionCallbackInfo& info); +void SetVerbose(const FunctionCallbackInfo& info); + + +// Global variable declarations - definitions are in src/node-report.c +extern char report_filename[NR_MAXNAME + 1]; +extern char report_directory[NR_MAXPATH + 1]; +extern std::string version_string; +extern std::string commandline_string; +extern TIME_TYPE loadtime_tm_struct; +extern time_t load_time; + +// Local implementation of secure_getenv() +inline const char* secure_getenv(const char* key) { +#ifndef _WIN32 + if (getuid() != geteuid() || getgid() != getegid()) + return nullptr; +#endif + return getenv(key); +} + +// Emulate arraysize() on Windows pre Visual Studio 2015 +#if defined(_MSC_VER) && _MSC_VER < 1900 +#define arraysize(a) (sizeof(a) / sizeof(*a)) +#else +template +constexpr size_t arraysize(const T(&)[N]) { return N; } +#endif // defined( _MSC_VER ) && (_MSC_VER < 1900) + +// Emulate snprintf() on Windows pre Visual Studio 2015 +#if defined( _MSC_VER ) && (_MSC_VER < 1900) +#include +inline static int snprintf(char* buffer, size_t n, const char* format, ...) { + va_list argp; + va_start(argp, format); + int ret = _vscprintf(format, argp); + vsnprintf_s(buffer, n, _TRUNCATE, format, argp); + va_end(argp); + return ret; +} + +#define __func__ __FUNCTION__ +#endif // defined( _MSC_VER ) && (_MSC_VER < 1900) + +} // namespace nodereport + +#endif // SRC_NODE_REPORT_H_ diff --git a/src/node_report_module.cc b/src/node_report_module.cc new file mode 100644 index 00000000000000..a79974518fff94 --- /dev/null +++ b/src/node_report_module.cc @@ -0,0 +1,512 @@ +#include "node_report.h" +#include "node_internals.h" + +#include +#include + +using v8::EscapableHandleScope; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Isolate; +using v8::NewStringType; +using v8::StackTrace; +using v8::String; +using v8::V8; +using v8::Value; + +namespace nodereport { + +// Internal/static function declarations +static void OnFatalError(const char* location, const char* message); +bool OnUncaughtException(Isolate* isolate); +#ifdef _WIN32 +static void PrintStackFromStackTrace(Isolate* isolate, FILE* fp); +#else // signal trigger functions for Unix platforms and OSX +static void SignalDumpAsyncCallback(uv_async_t* handle); +inline void* ReportSignalThreadMain(void* unused); +static int StartWatchdogThread(void* (*thread_main)(void* unused)); +static void RegisterSignalHandler(int signo, void (*handler)(int), + struct sigaction* saved_sa); +static void RestoreSignalHandler(int signo, struct sigaction* saved_sa); +static void SignalDump(int signo); +static void SetupSignalHandler(); +#endif + +// Default node-report option settings +static unsigned int nodereport_events = NR_APICALL; +static unsigned int nodereport_verbose = 0; +#ifdef _WIN32 // signal trigger not supported on Windows +static unsigned int nodereport_signal = 0; +#else // trigger signal supported on Unix platforms and OSX +static unsigned int nodereport_signal = SIGUSR2; // default signal is SIGUSR2 +static int report_signal = 0; // atomic for signal handling in progress +static uv_sem_t report_semaphore; // semaphore for hand-off to watchdog +static uv_async_t nodereport_trigger_async; // async handle for event loop +static uv_mutex_t node_isolate_mutex; // mutex for watchdog thread +static struct sigaction saved_sa; // saved signal action +#endif + +// State variables for v8 hooks and signal initialisation +static bool exception_hook_initialised = false; +static bool error_hook_initialised = false; +static bool signal_thread_initialised = false; + +static Isolate* node_isolate; + +/******************************************************************************* + * External JavaScript API for triggering a report + * + ******************************************************************************/ +void TriggerReport(const FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + HandleScope scope(isolate); + char filename[NR_MAXNAME + 1] = ""; + MaybeLocal error; + int err_index = 0; + + if (info[0]->IsString()) { + // Filename parameter supplied + String::Utf8Value filename_parameter(isolate, info[0]); + if (filename_parameter.length() < NR_MAXNAME) { + snprintf(filename, sizeof(filename), "%s", *filename_parameter); + } else { + isolate->ThrowException(String::NewFromUtf8( + isolate, + "node-report: filename parameter is too long", + NewStringType::kNormal).ToLocalChecked()); + } + err_index++; + } + + // We need to pass the JavaScript object so we can query it for a stack trace. + if (info[err_index]->IsNativeError()) { + error = info[err_index]; + } + + if (nodereport_events & NR_APICALL) { + TriggerNodeReport(isolate, kJavaScript, + "JavaScript API", + __func__, + filename, + error); + // Return value is the report filename + info.GetReturnValue().Set(String::NewFromUtf8(isolate, filename)); + } +} + +/******************************************************************************* + * External JavaScript API for returning a report + * + ******************************************************************************/ +void GetReport(const FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + HandleScope scope(isolate); + std::ostringstream out; + + MaybeLocal error; + if (info[0]->IsNativeError()) { + error = info[0]; + } + + GetNodeReport(isolate, kJavaScript, "JavaScript API", __func__, error, out); + // Return value is the contents of a report as a string. + info.GetReturnValue().Set(String::NewFromUtf8(isolate, out.str().c_str())); +} + +/******************************************************************************* + * External JavaScript APIs for node-report configuration + * + ******************************************************************************/ +void SetEvents(const FunctionCallbackInfo& info) { + Isolate* isolate = info.GetIsolate(); + String::Utf8Value parameter(isolate, info[0]); + SetEvents(isolate, *parameter); +} +void SetEvents(Isolate* isolate, const char* args) { + unsigned int previous_events = nodereport_events; // save previous settings + nodereport_events = ProcessNodeReportEvents(args); + + // If report newly requested for fatalerror, set up the V8 callback + if ((nodereport_events & NR_FATALERROR) && + (error_hook_initialised == false)) { + isolate->SetFatalErrorHandler(OnFatalError); + error_hook_initialised = true; + } + + // If report newly requested for exceptions, + // tell V8 to capture stack trace and set up the callback + if ((nodereport_events & NR_EXCEPTION) && + (exception_hook_initialised == false)) { + isolate->SetCaptureStackTraceForUncaughtExceptions(true, + 32, + StackTrace::kDetailed); + // The hook for uncaught exception won't get called unless the + // --abort_on_uncaught_exception option is set + V8::SetFlagsFromString("--abort_on_uncaught_exception", + sizeof("--abort_on_uncaught_exception")-1); + isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); + exception_hook_initialised = true; + } + +#ifndef _WIN32 + // If report newly requested on external user signal + // set up watchdog thread and handler + if ((nodereport_events & NR_SIGNAL) && (signal_thread_initialised == false)) { + SetupSignalHandler(); + } + // If report no longer required on external user signal, + // reset the OS signal handler + if (!(nodereport_events & NR_SIGNAL) && (previous_events & NR_SIGNAL)) { + RestoreSignalHandler(nodereport_signal, &saved_sa); + } +#endif +} +void SetSignal(const FunctionCallbackInfo& info) { +#ifndef _WIN32 + String::Utf8Value parameter(info.GetIsolate(), info[0]); + unsigned int previous_signal = nodereport_signal; // save previous setting + nodereport_signal = ProcessNodeReportSignal(*parameter); + + // If signal event active and selected signal has changed, + // switch the OS signal handler + if ((nodereport_events & NR_SIGNAL) && + (nodereport_signal != previous_signal)) { + RestoreSignalHandler(previous_signal, &saved_sa); + RegisterSignalHandler(nodereport_signal, SignalDump, &saved_sa); + } +#endif +} +void SetFileName(const FunctionCallbackInfo& info) { + String::Utf8Value parameter(info.GetIsolate(), info[0]); + ProcessNodeReportFileName(*parameter); +} +void SetDirectory(const FunctionCallbackInfo& info) { + String::Utf8Value parameter(info.GetIsolate(), info[0]); + ProcessNodeReportDirectory(*parameter); +} +void SetVerbose(const FunctionCallbackInfo& info) { + String::Utf8Value parameter(info.GetIsolate(), info[0]); + nodereport_verbose = ProcessNodeReportVerboseSwitch(*parameter); +} + +/******************************************************************************* + * Callbacks for triggering report on fatal error, uncaught exception and + * external signals + ******************************************************************************/ +static void OnFatalError(const char* location, const char* message) { + if (location) { + fprintf(stderr, "FATAL ERROR: %s %s\n", location, message); + } else { + fprintf(stderr, "FATAL ERROR: %s\n", message); + } + // Trigger report if requested + if (nodereport_events & NR_FATALERROR) { + TriggerNodeReport(Isolate::GetCurrent(), + kFatalError, + message, + location, + nullptr, + MaybeLocal()); + } + fflush(stderr); + raise(SIGABRT); +} + +bool OnUncaughtException(Isolate* isolate) { + // Trigger report if requested + if (nodereport_events & NR_EXCEPTION) { + TriggerNodeReport(isolate, + kException, + "exception", + __func__, + nullptr, + MaybeLocal()); + } + if ((version_and_command.commandline_string.find( + "abort-on-uncaught-exception") + != std::string::npos) || + (version_and_command.commandline_string.find( + "abort_on_uncaught_exception") + != std::string::npos)) { + return true; // abort required + } + return false; +} + +#ifdef _WIN32 +static void PrintStackFromStackTrace(Isolate* isolate, FILE* fp) { + Local stack = StackTrace::CurrentStackTrace( + isolate, + 255, + StackTrace::kDetailed); + // Print the JavaScript function name and source information for each frame + for (int i = 0; i < stack->GetFrameCount(); i++) { + Local frame = stack->GetFrame(i); + String::Utf8Value fn_name_s(isolate, frame->GetFunctionName()); + String::Utf8Value script_name(isolate, frame->GetScriptName()); + const int line_number = frame->GetLineNumber(); + const int column = frame->GetColumn(); + + if (frame->IsEval()) { + if (frame->GetScriptId() == Message::kNoScriptIdInfo) { + fprintf(fp, "at [eval]:%i:%i\n", line_number, column); + } else { + fprintf(fp, "at [eval] (%s:%i:%i)\n", + *script_name, line_number, column); + } + } else { + if (fn_name_s.length() == 0) { + fprintf(fp, "%s:%i:%i\n", *script_name, line_number, column); + } else { + fprintf(fp, "%s (%s:%i:%i)\n", + *fn_name_s, *script_name, line_number, column); + } + } + } +} +#else +// Signal handling functions, not supported on Windows +static void SignalDumpInterruptCallback(Isolate* isolate, void* data) { + if (report_signal != 0) { + if (nodereport_verbose) { + fprintf(stdout, + "node-report: SignalDumpInterruptCallback handling signal\n"); + } + if (nodereport_events & NR_SIGNAL) { + if (nodereport_verbose) { + fprintf(stdout, + "node-report: SignalDumpInterruptCallback triggering report\n"); + } + TriggerNodeReport(isolate, + kSignal_JS, + node::signo_string(report_signal), + __func__, + nullptr, + MaybeLocal()); + } + report_signal = 0; + } +} +static void SignalDumpAsyncCallback(uv_async_t* handle) { + if (report_signal != 0) { + if (nodereport_verbose) { + fprintf(stdout, + "node-report: SignalDumpAsyncCallback handling signal\n"); + } + if (nodereport_events & NR_SIGNAL) { + if (nodereport_verbose) { + fprintf(stdout, + "node-report: SignalDumpAsyncCallback triggering NodeReport\n"); + } + TriggerNodeReport(Isolate::GetCurrent(), + kSignal_UV, + node::signo_string(report_signal), + __func__, + nullptr, + MaybeLocal()); + } + report_signal = 0; + } +} + +/******************************************************************************* + * Utility functions for signal handling support (platforms except Windows) + * - RegisterSignalHandler() - register a raw OS signal handler + * - SignalDump() - implementation of raw OS signal handler + * - StartWatchdogThread() - create a watchdog thread + * - ReportSignalThreadMain() - implementation of watchdog thread + * - SetupSignalHandler() - initialisation of signal handlers and threads + ******************************************************************************/ +// Utility function to register an OS signal handler +static void RegisterSignalHandler(int signo, void (*handler)(int), + struct sigaction* saved_sa) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sigfillset(&sa.sa_mask); // mask all signals while in the handler + sigaction(signo, &sa, saved_sa); +} + +// Utility function to restore an OS signal handler to its previous setting +static void RestoreSignalHandler(int signo, struct sigaction* saved_sa) { + sigaction(signo, saved_sa, nullptr); +} + +// Raw signal handler for triggering a report - runs on an arbitrary thread +static void SignalDump(int signo) { + // Check atomic for report already pending, storing the signal number + if (__sync_val_compare_and_swap(&report_signal, 0, signo) == 0) { + uv_sem_post(&report_semaphore); // Hand-off to watchdog thread + } +} + +// Utility function to start a watchdog thread - used for processing signals +static int StartWatchdogThread(void* (*thread_main)(void* unused)) { + pthread_attr_t attr; + pthread_attr_init(&attr); + // Minimise the stack size, except on FreeBSD where the minimum is too low +#ifndef __FreeBSD__ + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); +#endif // __FreeBSD__ + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); + + sigset_t sigmask; + sigfillset(&sigmask); + pthread_sigmask(SIG_SETMASK, &sigmask, &sigmask); + pthread_t thread; + const int err = pthread_create(&thread, &attr, thread_main, nullptr); + pthread_sigmask(SIG_SETMASK, &sigmask, nullptr); + pthread_attr_destroy(&attr); + if (err != 0) { + fprintf(stderr, + "node-report: StartWatchdogThread pthread_create() failed: %s\n", + strerror(err)); + fflush(stderr); + return -err; + } + return 0; +} + +// Watchdog thread implementation for signal-triggered report +inline void* ReportSignalThreadMain(void* unused) { + for (;;) { + uv_sem_wait(&report_semaphore); + if (nodereport_verbose) { + fprintf(stdout, + "node-report: signal %s received\n", + node::signo_string(report_signal)); + } + uv_mutex_lock(&node_isolate_mutex); + if (auto isolate = node_isolate) { + // Request interrupt callback for running JavaScript code + isolate->RequestInterrupt(SignalDumpInterruptCallback, nullptr); + // Event loop may be idle, so also request an async callback + uv_async_send(&nodereport_trigger_async); + } + uv_mutex_unlock(&node_isolate_mutex); + } + return nullptr; +} + +// Utility function to initialise signal handlers and threads +static void SetupSignalHandler() { + Isolate* isolate = Isolate::GetCurrent(); + int rc = uv_sem_init(&report_semaphore, 0); + if (rc != 0) { + fprintf(stderr, + "node-report: initialization failed, uv_sem_init() returned %d\n", + rc); + isolate->ThrowException(String::NewFromUtf8( + isolate, + "node-report: initialization failed, " + "uv_sem_init() returned error\n", + NewStringType::kNormal).ToLocalChecked()); + } + rc = uv_mutex_init(&node_isolate_mutex); + if (rc != 0) { + fprintf(stderr, "node-report: initialization failed, " + "uv_mutex_init() returned %d\n", rc); + isolate->ThrowException(String::NewFromUtf8( + isolate, + "node-report: initialization failed, " + "uv_mutex_init() returned error\n", + NewStringType::kNormal).ToLocalChecked()); + } + + if (StartWatchdogThread(ReportSignalThreadMain) == 0) { + rc = uv_async_init(uv_default_loop(), + &nodereport_trigger_async, + SignalDumpAsyncCallback); + if (rc != 0) { + fprintf(stderr, + "node-report: initialization failed, " + "uv_async_init() returned %d\n", + rc); + isolate->ThrowException(String::NewFromUtf8( + isolate, + "node-report: initialization failed, " + "uv_async_init() returned error\n", + NewStringType::kNormal).ToLocalChecked()); + } + uv_unref(reinterpret_cast(&nodereport_trigger_async)); + RegisterSignalHandler(nodereport_signal, SignalDump, &saved_sa); + signal_thread_initialised = true; + } +} +#endif + +/******************************************************************************* + * Native module initializer function, called when the module is require'd + * + ******************************************************************************/ +void InitializeNodeReport(void) { + Isolate* isolate = Isolate::GetCurrent(); + node_isolate = isolate; + + SetLoadTime(); + SetVersionString(isolate); + SetCommandLine(); + + const char* verbose_switch = secure_getenv("NODEREPORT_VERBOSE"); + if (verbose_switch != nullptr) { + nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); + } + const char* trigger_events = secure_getenv("NODEREPORT_EVENTS"); + if (trigger_events != nullptr) { + // nodereport_events = ProcessNodeReportEvents(trigger_events); + SetEvents(isolate, trigger_events); + } + const char* trigger_signal = secure_getenv("NODEREPORT_SIGNAL"); + if (trigger_signal != nullptr) { + nodereport_signal = ProcessNodeReportSignal(trigger_signal); + } + const char* report_name = secure_getenv("NODEREPORT_FILENAME"); + if (report_name != nullptr) { + ProcessNodeReportFileName(report_name); + } + const char* directory_name = secure_getenv("NODEREPORT_DIRECTORY"); + if (directory_name != nullptr) { + ProcessNodeReportDirectory(directory_name); + } +} + +// Not called at the moment. The binding is performed +// in src/node_util.cc onto the `util` object. This +// method is maintained if someone wants to leverage +// or extend node_report directly, through +// process.binding('node_report') primitive. +void Initialize(Local exports) { + Isolate* isolate = Isolate::GetCurrent(); + InitializeNodeReport(); + NODE_SET_METHOD(exports, "triggerReport", TriggerReport); + NODE_SET_METHOD(exports, "getReport", GetReport); + NODE_SET_METHOD(exports, "setEvents", SetEvents); + NODE_SET_METHOD(exports, "setSignal", SetSignal); + NODE_SET_METHOD(exports, "setFileName", SetFileName); + NODE_SET_METHOD(exports, "setDirectory", SetDirectory); + NODE_SET_METHOD(exports, "setverbose", SetVerbose); + + Local util_prop = String::NewFromUtf8(Isolate::GetCurrent(), + "util", + NewStringType::kNormal) + .ToLocalChecked(); + if (nodereport_verbose) { +#ifdef _WIN32 + fprintf(stdout, "node-report: initialization complete, event flags: %#x\n", + nodereport_events); +#else + fprintf(stdout, + "node-report: initialization complete, " + "event flags: %#x signal flag: %#x\n", + nodereport_events, + nodereport_signal); +#endif + } +} + +} // namespace nodereport + +#if defined(NODE_REPORT) +NODE_BUILTIN_MODULE_CONTEXT_AWARE(node_report, nodereport::Initialize) +#endif // NODE_REPORT + diff --git a/src/node_report_utils.cc b/src/node_report_utils.cc new file mode 100644 index 00000000000000..172f11a6570b3d --- /dev/null +++ b/src/node_report_utils.cc @@ -0,0 +1,576 @@ + +#include "node_report.h" + +#ifdef __APPLE__ +#include // _NSGetArgv() and _NSGetArgc() +#endif +#ifdef __sun +#include // psinfo_t structure +#endif +#ifdef _AIX +#include // psinfo_t structure +#endif + +using v8::Array; +using v8::EscapableHandleScope; +using v8::Isolate; +using v8::Local; +using v8::NewStringType; +using v8::Object; +using v8::String; +using v8::TryCatch; + +namespace nodereport { + +/******************************************************************************* + * Function to process node-report config: selection of trigger events. + ******************************************************************************/ +unsigned int ProcessNodeReportEvents(const char* args) { + // Parse the supplied event types + unsigned int event_flags = 0; + const char* cursor = args; + while (*cursor != '\0') { + if (!strncmp(cursor, "exception", sizeof("exception") - 1)) { + event_flags |= NR_EXCEPTION; + cursor += sizeof("exception") - 1; + } else if (!strncmp(cursor, "fatalerror", sizeof("fatalerror") - 1)) { + event_flags |= NR_FATALERROR; + cursor += sizeof("fatalerror") - 1; + } else if (!strncmp(cursor, "signal", sizeof("signal") - 1)) { + event_flags |= NR_SIGNAL; + cursor += sizeof("signal") - 1; + } else if (!strncmp(cursor, "apicall", sizeof("apicall") - 1)) { + event_flags |= NR_APICALL; + cursor += sizeof("apicall") - 1; + } else { + std::cerr << "Unrecognised argument for node-report events option: " + << cursor + << "\n"; + return 0; + } + if (*cursor == '+') { + cursor++; // Hop over the '+' separator + } + } + return event_flags; +} + +/******************************************************************************* + * Function to process node-report config: selection of trigger signal. + ******************************************************************************/ +unsigned int ProcessNodeReportSignal(const char* args) { +#ifdef _WIN32 + return 0; // no-op on Windows +#else + if (strlen(args) == 0) { + std::cerr << "Missing argument for node-report signal option\n"; + } else { + // Parse the supplied switch + if (!strncmp(args, "SIGUSR2", sizeof("SIGUSR2") - 1)) { + return SIGUSR2; + } else if (!strncmp(args, "SIGQUIT", sizeof("SIGQUIT") - 1)) { + return SIGQUIT; + } else { + std::cerr << "Unrecognised argument for node-report signal option:" + << args + << "\n"; + } + } + return SIGUSR2; // Default signal is SIGUSR2 +#endif +} + +/******************************************************************************* + * Function to process node-report config: specification of report file name. + ******************************************************************************/ +void ProcessNodeReportFileName(const char* args) { + if (strlen(args) == 0) { + std::cerr << "Missing argument for node-report filename option\n"; + return; + } + if (strlen(args) > NR_MAXNAME) { + std::cerr << "Supplied node-report filename too long (max " + << NR_MAXNAME + << " characters)\n"; + return; + } + snprintf(report_filename, sizeof(report_filename), "%s", args); +} + +/******************************************************************************* + * Function to process node-report config: specification of report directory. + ******************************************************************************/ +void ProcessNodeReportDirectory(const char* args) { + if (strlen(args) == 0) { + std::cerr << "Missing argument for node-report directory option\n"; + return; + } + if (strlen(args) > NR_MAXPATH) { + std::cerr << "Supplied node-report directory path too long (max " + << NR_MAXPATH + << " characters)\n"; + return; + } + snprintf(report_directory, sizeof(report_directory), "%s", args); +} + +/******************************************************************************* + * Function to process node-report config: verbose mode switch. + ******************************************************************************/ +unsigned int ProcessNodeReportVerboseSwitch(const char* args) { + if (strlen(args) == 0) { + std::cerr << "Missing argument for node-report verbose switch option\n"; + return 0; + } + // Parse the supplied switch + if (!strncmp(args, "yes", sizeof("yes") - 1) || + !strncmp(args, "true", sizeof("true") - 1)) { + return 1; + } else if (!strncmp(args, "no", sizeof("no") - 1) || + !strncmp(args, "false", sizeof("false") - 1)) { + return 0; + } else { + std::cerr << "Unrecognised argument for node-report verbose switch option: " + << args + << "\n"; + } + return 0; // Default is verbose mode off +} + +/******************************************************************************* + * Function to save the node and subcomponent version strings. This is called + * during node-report module initialisation. + *******************************************************************************/ +void SetVersionString(Isolate* isolate) { + // Catch anything thrown and gracefully return + TryCatch trycatch(isolate); + version_and_command.version_string = NODE_VERSION_STRING; + + // Retrieve the process object + Local process_prop = String::NewFromUtf8( + Isolate::GetCurrent(), + "process", + NewStringType::kNormal) + .ToLocalChecked(); + if (process_prop->IsNull()) return; + + Local global_obj = isolate->GetCurrentContext()->Global(); + EscapableHandleScope scope(isolate); + Local process_value = scope.Escape( + global_obj->Get( + isolate->GetCurrentContext(), + process_prop) + .FromMaybe(Local())); + if (process_value->IsNull()) return; + + if (!process_value->IsObject()) return; + Local process_obj = process_value.As(); + + // Get process.versions + Local versions_prop = String::NewFromUtf8(isolate, + "versions", + NewStringType::kNormal) + .ToLocalChecked(); + if (versions_prop->IsNull()) return; + + Local versions_value = process_obj->Get(isolate->GetCurrentContext(), + versions_prop) + .ToLocalChecked(); + if (versions_value->IsNull()) return; + if (!versions_value->IsObject()) return; + Local versions_obj = versions_value.As(); + + // Get component names and versions from process.versions + Local components = versions_obj->GetOwnPropertyNames( + isolate->GetCurrentContext()) + .FromMaybe(Local()); + if (components->IsNull()) return; + + Local components_obj = components.As(); + std::string comp_versions = "("; + size_t wrap = 0; + uint32_t total_components = (*components)->Length(); + for (uint32_t i = 0; i < total_components; i++) { + Local name_value = components_obj->Get(isolate->GetCurrentContext(), + i) + .ToLocalChecked(); + if (name_value->IsNull()) continue; + Local version_value = versions_obj->Get(isolate->GetCurrentContext(), + name_value) + .ToLocalChecked(); + if (version_value->IsNull()) continue; + + const String::Utf8Value component_name(isolate, name_value); + String::Utf8Value component_version(isolate, version_value->ToString()); + if (*component_name == nullptr || *component_version == nullptr) continue; + + if (!strcmp("node", *component_name)) { + // Put the Node.js version on the first line, if we didn't already have it + if (version_and_command.version_string == NODE_VERSION_STRING) { + version_and_command.version_string = "Node.js version: v"; + version_and_command.version_string += *component_version; + version_and_command.version_string += "\n"; + } + } else { + // Other component versions follow, + // comma separated, wrapped at 80 characters + std::string comp_version_string = *component_name; + comp_version_string += ": "; + comp_version_string += *component_version; + if (wrap == 0) { + wrap = comp_version_string.length(); + } else { + wrap += comp_version_string.length() + 2; // includes separator + if (wrap > 80) { + comp_versions += ",\n "; + wrap = comp_version_string.length(); + } else { + comp_versions += ", "; + } + } + comp_versions += comp_version_string; + } + } + version_and_command.version_string += comp_versions + ")\n"; +} + +/******************************************************************************* + * Function to save the node-report module load time value. This is called + * during node-report module initialisation. + *******************************************************************************/ +void SetLoadTime() { +#ifdef _WIN32 + GetLocalTime(&loadtime_tm_struct); +#else // UNIX, OSX + struct timeval time_val; + gettimeofday(&time_val, nullptr); + localtime_r(&time_val.tv_sec, &loadtime_tm_struct); +#endif + time(&load_time); +} + +/******************************************************************************* + * Function to save the process command line. This is called during node-report + * module initialisation. + *******************************************************************************/ +void SetCommandLine() { +#ifdef __linux__ + // Read the command line from /proc/self/cmdline + char buf[64]; + FILE* cmdline_fd = fopen("/proc/self/cmdline", "r"); + if (cmdline_fd == nullptr) { + return; + } + version_and_command.commandline_string = ""; + int bytesread = fread(buf, 1, sizeof(buf), cmdline_fd); + while (bytesread > 0) { + for (int i = 0; i < bytesread; i++) { + // Arguments are null separated. + if (buf[i] == '\0') { + version_and_command.commandline_string += " "; + } else { + version_and_command.commandline_string += buf[i]; + } + } + bytesread = fread(buf, 1, sizeof(buf), cmdline_fd); + } + fclose(cmdline_fd); +#elif __APPLE__ + char **argv = *_NSGetArgv(); + int argc = *_NSGetArgc(); + + version_and_command.commandline_string = ""; + std::string separator = ""; + for (int i = 0; i < argc; i++) { + version_and_command.commandline_string += separator + argv[i]; + separator = " "; + } +#elif defined(_AIX) || defined(__sun) + // Read the command line from /proc/self/cmdline + char procbuf[64]; + snprintf(procbuf, sizeof(procbuf), "/proc/%d/psinfo", getpid()); + FILE* psinfo_fd = fopen(procbuf, "r"); + if (psinfo_fd == nullptr) { + return; + } + psinfo_t info; + int bytesread = fread(&info, 1, sizeof(psinfo_t), psinfo_fd); + fclose(psinfo_fd); + if (bytesread == sizeof(psinfo_t)) { + version_and_command.commandline_string = ""; + std::string separator = ""; +#ifdef _AIX + char **argv = *(reinterpret_cast(info.pr_argv)); +#else + char **argv = (reinterpret_cast(info.pr_argv)); +#endif + for (uint32_t i = 0; i < info.pr_argc && argv[i] != nullptr; i++) { + version_and_command.commandline_string += separator + argv[i]; + separator = " "; + } + } +#elif _WIN32 + version_and_command.commandline_string = GetCommandLine(); +#endif +} + +/******************************************************************************* + * Utility function to format libuv socket information. + *******************************************************************************/ +void reportEndpoints(uv_handle_t* h, std::ostringstream& out) { + struct sockaddr_storage addr_storage; + struct sockaddr* addr = reinterpret_cast(&addr_storage); + char hostbuf[NI_MAXHOST]; + char portbuf[NI_MAXSERV]; + uv_any_handle* handle = reinterpret_cast(h); + int addr_size = sizeof(addr_storage); + int rc = -1; + + switch (h->type) { + case UV_UDP: { + rc = uv_udp_getsockname(&(handle->udp), addr, &addr_size); + break; + } + case UV_TCP: { + rc = uv_tcp_getsockname(&(handle->tcp), addr, &addr_size); + break; + } + default: break; + } + if (rc == 0) { + // getnameinfo will format host and port and handle IPv4/IPv6. + rc = getnameinfo(addr, addr_size, hostbuf, sizeof(hostbuf), portbuf, + sizeof(portbuf), NI_NUMERICSERV); + if (rc == 0) { + out << std::string(hostbuf) << ":" << std::string(portbuf); + } + + if (h->type == UV_TCP) { + // Get the remote end of the connection. + rc = uv_tcp_getpeername(&(handle->tcp), addr, &addr_size); + if (rc == 0) { + rc = getnameinfo(addr, addr_size, hostbuf, sizeof(hostbuf), portbuf, + sizeof(portbuf), NI_NUMERICSERV); + if (rc == 0) { + out << " connected to "; + out << std::string(hostbuf) << ":" << std::string(portbuf); + } + } else if (rc == UV_ENOTCONN) { + out << " (not connected)"; + } + } + } +} + +/******************************************************************************* + * Utility function to format libuv path information. + *******************************************************************************/ +void reportPath(uv_handle_t* h, std::ostringstream& out) { + char* buffer = nullptr; + int rc = -1; + size_t size = 0; + uv_any_handle* handle = reinterpret_cast(h); + // First call to get required buffer size. + switch (h->type) { + case UV_FS_EVENT: { + rc = uv_fs_event_getpath(&(handle->fs_event), buffer, &size); + break; + } + case UV_FS_POLL: { + rc = uv_fs_poll_getpath(&(handle->fs_poll), buffer, &size); + break; + } + default: break; + } + if (rc == UV_ENOBUFS) { + buffer = static_cast(malloc(size)); + switch (h->type) { + case UV_FS_EVENT: { + rc = uv_fs_event_getpath(&(handle->fs_event), buffer, &size); + break; + } + case UV_FS_POLL: { + rc = uv_fs_poll_getpath(&(handle->fs_poll), buffer, &size); + break; + } + default: break; + } + if (rc == 0) { + // buffer is not null terminated. + std::string name(buffer, size); + out << "filename: " << name; + } + free(buffer); + } +} + +/******************************************************************************* + * Utility function to walk libuv handles. + *******************************************************************************/ +void walkHandle(uv_handle_t* h, void* arg) { + std::string type; + std::ostringstream data; + std::ostream* out = reinterpret_cast(arg); + uv_any_handle* handle = reinterpret_cast(h); + + // List all the types so we get a compile warning if we've missed one, + // (using default: supresses the compiler warning). + switch (h->type) { + case UV_UNKNOWN_HANDLE: type = "unknown"; break; + case UV_ASYNC: type = "async"; break; + case UV_CHECK: type = "check"; break; + case UV_FS_EVENT: { + type = "fs_event"; + reportPath(h, data); + break; + } + case UV_FS_POLL: { + type = "fs_poll"; + reportPath(h, data); + break; + } + case UV_HANDLE: type = "handle"; break; + case UV_IDLE: type = "idle"; break; + case UV_NAMED_PIPE: type = "pipe"; break; + case UV_POLL: type = "poll"; break; + case UV_PREPARE: type = "prepare"; break; + case UV_PROCESS: { + type = "process"; + data << "pid: " << handle->process.pid; + break; + } + case UV_STREAM: type = "stream"; break; + case UV_TCP: { + type = "tcp"; + reportEndpoints(h, data); + break; + } + case UV_TIMER: { + uint64_t due = handle->timer.timeout; + uint64_t now = uv_now(handle->timer.loop); + type = "timer"; + data << "repeat: " << uv_timer_get_repeat(&(handle->timer)); + if (due > now) { + data << ", timeout in: " << (due - now) << " ms"; + } else { + data << ", timeout expired: " << (now - due) << " ms ago"; + } + break; + } + case UV_TTY: { + int height, width, rc; + type = "tty"; + rc = uv_tty_get_winsize(&(handle->tty), &width, &height); + if (rc == 0) { + data << "width: " << width << ", height: " << height; + } + break; + } + case UV_UDP: { + type = "udp"; + reportEndpoints(h, data); + break; + } + case UV_SIGNAL: { + // SIGWINCH is used by libuv so always appears. + // See http://docs.libuv.org/en/v1.x/signal.html + type = "signal"; + data << "signum: " << handle->signal.signum + // node::signo_string() is not exported by Node.js on Windows. +#ifndef _WIN32 + << " (" << node::signo_string(handle->signal.signum) << ")" +#endif + << ""; + break; + } + case UV_FILE: type = "file"; break; + // We shouldn't see "max" type + case UV_HANDLE_TYPE_MAX : type = "max"; break; + } + + if (h->type == UV_TCP || h->type == UV_UDP +#ifndef _WIN32 + || h->type == UV_NAMED_PIPE +#endif + ) { + // These *must* be 0 or libuv will set the buffer sizes to the non-zero + // values they contain. + int send_size = 0; + int recv_size = 0; + if (h->type == UV_TCP || h->type == UV_UDP) { + data << ", "; + } + uv_send_buffer_size(h, &send_size); + uv_recv_buffer_size(h, &recv_size); + data << "send buffer size: " << send_size + << ", recv buffer size: " << recv_size; + } + + if (h->type == UV_TCP || h->type == UV_NAMED_PIPE || h->type == UV_TTY || + h->type == UV_UDP || h->type == UV_POLL) { + uv_os_fd_t fd_v; + uv_os_fd_t* fd = &fd_v; + int rc = uv_fileno(h, fd); + // uv_os_fd_t is an int on Unix and HANDLE on Windows. +#ifndef _WIN32 + if (rc == 0) { + switch (fd_v) { + case 0: + data << ", stdin"; break; + case 1: + data << ", stdout"; break; + case 2: + data << ", stderr"; break; + default: + data << ", file descriptor: " << static_cast(fd_v); + break; + } + } +#endif + } + + if (h->type == UV_TCP || h->type == UV_NAMED_PIPE || h->type == UV_TTY) { + data << ", write queue size: " + << handle->stream.write_queue_size; + data << (uv_is_readable(&handle->stream) ? ", readable" : "") + << (uv_is_writable(&handle->stream) ? ", writable": ""); + } + + *out << std::left << "[" << (uv_has_ref(h) ? 'R' : '-') + << (uv_is_active(h) ? 'A' : '-') << "] " << std::setw(10) << type + << std::internal << std::setw(2 + 2 * sizeof(void*)); + char prev_fill = out->fill('0'); + *out << static_cast(h) << std::left; + out->fill(prev_fill); + *out << " " << std::left << data.str() << std::endl; +} + +/******************************************************************************* + * Utility function to print out integer values with commas for readability. + ******************************************************************************/ +void WriteInteger(std::ostream& out, size_t value) { + int thousandsStack[8]; // Sufficient for max 64-bit number + int stackTop = 0; + int i; + char buf[64]; + size_t workingValue = value; + + do { + thousandsStack[stackTop++] = workingValue % 1000; + workingValue /= 1000; + } while (workingValue != 0); + + for (i = stackTop-1; i >= 0; i--) { + if (i == (stackTop-1)) { + out << thousandsStack[i]; + } else { + snprintf(buf, sizeof(buf), "%03u", thousandsStack[i]); + out << buf; + } + if (i > 0) { + out << ","; + } + } +} + +} // namespace nodereport diff --git a/src/node_util.cc b/src/node_util.cc index 591f3e3b5eb91f..4468e6ffab01b6 100644 --- a/src/node_util.cc +++ b/src/node_util.cc @@ -1,5 +1,8 @@ #include "node_internals.h" #include "node_watchdog.h" +#if defined(NODE_REPORT) +#include "node_report.h" +#endif // NODE_REPORT namespace node { namespace util { @@ -209,6 +212,17 @@ void Initialize(Local target, WatchdogHasPendingSigint); env->SetMethod(target, "safeGetenv", SafeGetenv); + +#if defined(NODE_REPORT) + nodereport::InitializeNodeReport(); + env->SetMethod(target, "triggerNodeReport", nodereport::TriggerReport); + env->SetMethod(target, "getNodeReport", nodereport::GetReport); + env->SetMethod(target, "setNodeReportEvents", nodereport::SetEvents); + env->SetMethod(target, "setNodeReportSignal", nodereport::SetSignal); + env->SetMethod(target, "setNodeReportFileName", nodereport::SetFileName); + env->SetMethod(target, "setNodeReportDirectory", nodereport::SetDirectory); + env->SetMethod(target, "setNodeReportVerbose", nodereport::SetVerbose); +#endif // NODE_REPORT } } // namespace util diff --git a/test/node-report/helper.js b/test/node-report/helper.js new file mode 100644 index 00000000000000..60de70a6cc96e9 --- /dev/null +++ b/test/node-report/helper.js @@ -0,0 +1,208 @@ +'use strict'; +require('../common'); +const child_process = require('child_process'); +const assert = require('assert'); +const fs = require('fs'); +const os = require('os'); + +const osMap = { + 'aix': 'AIX', + 'darwin': 'Darwin', + 'linux': 'Linux', + 'sunos': 'SunOS', + 'win32': 'Windows', +}; + +const REPORT_SECTIONS = [ + 'Node Report', + 'JavaScript Stack Trace', + 'JavaScript Heap', + 'System Information' +]; + +const reNewline = '(?:\\r*\\n)'; + +exports.findReports = (pid) => { + // Default filenames are of the form node-report..