From 4f6797378eb6dd290dd5d8dc6417fea22d42ebad Mon Sep 17 00:00:00 2001 From: Gireesh Punathil Date: Wed, 5 Sep 2018 10:06:59 -0400 Subject: [PATCH 1/3] src: merge into core Make node-report part of core runtime because: 1. When enabled, node-report significantly helps root cause various types of problems, including support issues sent to the various repos of the Node.js organization. 2. The requirement of explicitly adding the dependency to node-report in user applications often represents a blocker to adoption. Major deviation from the module version of the node-report is that the report is generated in JSON format, as opposed to human readable text. No new functionalities have been added, changes that are required for melding it as a built-in capability has been affected on the module version of node-report (https://github.com/nodejs/node-report) Co-authored-by: Bidisha Pyne Co-authored-by: Howard Hellyer Co-authored-by: Jeremiah Senkpiel Co-authored-by: Julian Alimin Co-authored-by: Lakshmi Swetha Gopireddy Co-authored-by: Manusaporn Treerungroj Co-authored-by: Michael Dawson Co-authored-by: Richard Chamberlain Co-authored-by: Richard Lau Co-authored-by: Sam Roberts Co-authored-by: Vipin Menon PR-URL: https://github.com/nodejs/node/pull/22712 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Michael Dawson Reviewed-By: Vse Mozhet Byt --- configure.py | 6 + lib/internal/bootstrap/node.js | 4 + lib/internal/errors.js | 1 + lib/internal/process/execution.js | 25 + lib/internal/process/report.js | 163 ++++++ node.gyp | 75 +++ src/node.cc | 16 + src/node_binding.cc | 9 +- src/node_config.cc | 6 +- src/node_errors.cc | 18 + src/node_options.cc | 67 +++ src/node_options.h | 16 + src/node_report.cc | 833 ++++++++++++++++++++++++++++++ src/node_report.h | 165 ++++++ src/node_report_module.cc | 294 +++++++++++ src/node_report_utils.cc | 299 +++++++++++ 16 files changed, 1995 insertions(+), 2 deletions(-) create mode 100644 lib/internal/process/report.js create mode 100644 src/node_report.cc create mode 100644 src/node_report.h create mode 100644 src/node_report_module.cc create mode 100644 src/node_report_utils.cc diff --git a/configure.py b/configure.py index 8cb4c58c21809b..aa104b298dc63f 100755 --- a/configure.py +++ b/configure.py @@ -493,6 +493,11 @@ dest='without_npm', help='do not install the bundled npm (package manager)') +parser.add_option('--without-report', + action='store_true', + dest='without_report', + help='build without report') + # Dummy option for backwards compatibility parser.add_option('--with-snapshot', action='store_true', @@ -938,6 +943,7 @@ def configure_node(o): o['variables']['OS'] = 'android' o['variables']['node_prefix'] = options.prefix o['variables']['node_install_npm'] = b(not options.without_npm) + o['variables']['node_report'] = b(not options.without_report) o['default_configuration'] = 'Debug' if options.debug else 'Release' host_arch = host_arch_win() if os.name == 'nt' else host_arch_cc() diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js index 2112cb9ee17fe6..40ad964f688866 100644 --- a/lib/internal/bootstrap/node.js +++ b/lib/internal/bootstrap/node.js @@ -363,6 +363,10 @@ function startup() { } = perf.constants; perf.markMilestone(NODE_PERFORMANCE_MILESTONE_BOOTSTRAP_COMPLETE); + if (getOptionValue('--experimental-report')) { + NativeModule.require('internal/process/report').setup(); + } + if (isMainThread) { return startMainThreadExecution; } else { diff --git a/lib/internal/errors.js b/lib/internal/errors.js index b33cd2710960b6..d5cb86c3a5a4d8 100644 --- a/lib/internal/errors.js +++ b/lib/internal/errors.js @@ -709,6 +709,7 @@ E('ERR_INSPECTOR_CLOSED', 'Session was closed', Error); E('ERR_INSPECTOR_NOT_AVAILABLE', 'Inspector is not available', Error); E('ERR_INSPECTOR_NOT_CONNECTED', 'Session is not connected', Error); E('ERR_INVALID_ADDRESS_FAMILY', 'Invalid address family: %s', RangeError); +E('ERR_SYNTHETIC', 'JavaScript Callstack: %s', Error); E('ERR_INVALID_ARG_TYPE', (name, expected, actual) => { assert(typeof name === 'string', "'name' must be a string"); diff --git a/lib/internal/process/execution.js b/lib/internal/process/execution.js index 607829544a9115..b11f54b35d6253 100644 --- a/lib/internal/process/execution.js +++ b/lib/internal/process/execution.js @@ -98,6 +98,31 @@ function createFatalException() { // call that threw and was never cleared. So clear it now. clearDefaultTriggerAsyncId(); + // If node-report is enabled, call into its handler to see + // whether it is interested in handling the situation. + // Ignore if the error is scoped inside a domain. + // use == in the checks as we want to allow for null and undefined + if (er == null || er.domain == null) { + try { + const report = internalBinding('report'); + if (report != null) { + if (require('internal/options').getOptionValue( + '--experimental-report')) { + const config = {}; + report.syncConfig(config, false); + if (Array.isArray(config.events) && + config.events.includes('exception')) { + if (er) { + report.onUnCaughtException(er.stack); + } else { + report.onUnCaughtException(undefined); + } + } + } + } + } catch {} // NOOP, node_report unavailable. + } + if (exceptionHandlerState.captureFn !== null) { exceptionHandlerState.captureFn(er); } else if (!process.emit('uncaughtException', er)) { diff --git a/lib/internal/process/report.js b/lib/internal/process/report.js new file mode 100644 index 00000000000000..2d0d3f39214793 --- /dev/null +++ b/lib/internal/process/report.js @@ -0,0 +1,163 @@ +'use strict'; + +const { emitExperimentalWarning } = require('internal/util'); +const { + ERR_INVALID_ARG_TYPE, + ERR_SYNTHETIC } = require('internal/errors').codes; + +exports.setup = function() { + const REPORTEVENTS = 1; + const REPORTSIGNAL = 2; + const REPORTFILENAME = 3; + const REPORTPATH = 4; + const REPORTVERBOSE = 5; + if (internalBinding('config').hasReport) { + // If report is enabled, extract the binding and + // wrap the APIs with thin layers, with some error checks. + // user options can come in from CLI / ENV / API. + // CLI and ENV is intercepted in C++ and the API call here (JS). + // So sync up with both sides as appropriate - initially from + // C++ to JS and from JS to C++ whenever the API is called. + // Some events are controlled purely from JS (signal | exception) + // and some from C++ (fatalerror) so this sync-up is essential for + // correct behavior and alignment with the supplied tunables. + const nr = internalBinding('report'); + + // Keep it un-exposed; lest programs play with it + // leaving us with a lot of unwanted sanity checks. + let config = { + events: [], + signal: 'SIGUSR2', + filename: '', + path: '', + verbose: false + }; + const report = { + setDiagnosticReportOptions(options) { + emitExperimentalWarning('report'); + // Reuse the null and undefined checks. Save + // space when dealing with large number of arguments. + const list = parseOptions(options); + + // Flush the stale entries from report, as + // we are refreshing it, items that the users did not + // touch may be hanging around stale otherwise. + config = {}; + + // The parseOption method returns an array that include + // the indices at which valid params are present. + list.forEach((i) => { + switch (i) { + case REPORTEVENTS: + if (Array.isArray(options.events)) + config.events = options.events; + else + throw new ERR_INVALID_ARG_TYPE('events', + 'Array', + options.events); + break; + case REPORTSIGNAL: + if (typeof options.signal !== 'string') { + throw new ERR_INVALID_ARG_TYPE('signal', + 'String', + options.signal); + } + process.removeListener(config.signal, handleSignal); + if (config.events.includes('signal')) + process.on(options.signal, handleSignal); + config.signal = options.signal; + break; + case REPORTFILENAME: + if (typeof options.filename !== 'string') { + throw new ERR_INVALID_ARG_TYPE('filename', + 'String', + options.filename); + } + config.filename = options.filename; + break; + case REPORTPATH: + if (typeof options.path !== 'string') + throw new ERR_INVALID_ARG_TYPE('path', 'String', options.path); + config.path = options.path; + break; + case REPORTVERBOSE: + if (typeof options.verbose !== 'string' && + typeof options.verbose !== 'boolean') { + throw new ERR_INVALID_ARG_TYPE('verbose', + 'Booelan | String' + + ' (true|false|yes|no)', + options.verbose); + } + config.verbose = options.verbose; + break; + } + }); + // Upload this new config to C++ land + nr.syncConfig(config, true); + }, + + + triggerReport(file, err) { + emitExperimentalWarning('report'); + if (err == null) { + if (file == null) { + return nr.triggerReport(new ERR_SYNTHETIC( + 'JavaScript Callstack').stack); + } + if (typeof file !== 'string') + throw new ERR_INVALID_ARG_TYPE('file', 'String', file); + return nr.triggerReport(file, new ERR_SYNTHETIC( + 'JavaScript Callstack').stack); + } + if (typeof err !== 'object') + throw new ERR_INVALID_ARG_TYPE('err', 'Object', err); + if (file == null) + return nr.triggerReport(err.stack); + if (typeof file !== 'string') + throw new ERR_INVALID_ARG_TYPE('file', 'String', file); + return nr.triggerReport(file, err.stack); + }, + getReport(err) { + emitExperimentalWarning('report'); + if (err == null) { + return nr.getReport(new ERR_SYNTHETIC('JavaScript Callstack').stack); + } else if (typeof err !== 'object') { + throw new ERR_INVALID_ARG_TYPE('err', 'Objct', err); + } else { + return nr.getReport(err.stack); + } + } + }; + + // Download the CLI / ENV config into JS land. + nr.syncConfig(config, false); + + function handleSignal(signo) { + if (typeof signo !== 'string') + signo = config.signal; + nr.onUserSignal(signo); + } + + if (config.events.includes('signal')) { + process.on(config.signal, handleSignal); + } + + function parseOptions(obj) { + const list = []; + if (obj == null) + return list; + if (obj.events != null) + list.push(REPORTEVENTS); + if (obj.signal != null) + list.push(REPORTSIGNAL); + if (obj.filename != null) + list.push(REPORTFILENAME); + if (obj.path != null) + list.push(REPORTPATH); + if (obj.verbose != null) + list.push(REPORTVERBOSE); + return list; + } + process.report = report; + } +}; diff --git a/node.gyp b/node.gyp index 30285031a45b6f..307f8ae6032840 100644 --- a/node.gyp +++ b/node.gyp @@ -156,6 +156,7 @@ 'lib/internal/process/stdio.js', 'lib/internal/process/warning.js', 'lib/internal/process/worker_thread_only.js', + 'lib/internal/process/report.js', 'lib/internal/querystring.js', 'lib/internal/queue_microtask.js', 'lib/internal/readline.js', @@ -314,6 +315,29 @@ # the executable and rename it back to node.exe later 'product_name': '<(node_core_target_name)-win', }], + [ 'node_report=="true"', { + 'defines': [ + 'NODE_REPORT', + 'NODE_ARCH="<(target_arch)"', + 'NODE_PLATFORM="<(OS)"', + ], + 'conditions': [ + ['OS=="win"', { + 'libraries': [ + 'dbghelp.lib', + 'Netapi32.lib', + 'PsApi.lib', + 'Ws2_32.lib', + ], + 'dll_files': [ + 'dbghelp.dll', + 'Netapi32.dll', + 'PsApi.dll', + 'Ws2_32.dll', + ], + }], + ], + }], ], }, # node_core_target_name { @@ -623,6 +647,34 @@ 'src/tls_wrap.h' ], }], + [ 'node_report=="true"', { + 'sources': [ + 'src/node_report.cc', + 'src/node_report_module.cc', + 'src/node_report_utils.cc', + ], + 'defines': [ + 'NODE_REPORT', + 'NODE_ARCH="<(target_arch)"', + 'NODE_PLATFORM="<(OS)"', + ], + 'conditions': [ + ['OS=="win"', { + 'libraries': [ + 'dbghelp.lib', + 'Netapi32.lib', + 'PsApi.lib', + 'Ws2_32.lib', + ], + 'dll_files': [ + 'dbghelp.dll', + 'Netapi32.dll', + 'PsApi.dll', + 'Ws2_32.dll', + ], + }], + ], + }], [ 'node_use_large_pages=="true" and OS=="linux"', { 'defines': [ 'NODE_ENABLE_LARGE_CODE_PAGES=1' ], # The current implementation of Large Pages is under Linux. @@ -964,6 +1016,29 @@ 'OTHER_LDFLAGS': [ '-Wl,-rpath,@loader_path', ], }, }], + [ 'node_report=="true"', { + 'defines': [ + 'NODE_REPORT', + 'NODE_ARCH="<(target_arch)"', + 'NODE_PLATFORM="<(OS)"', + ], + 'conditions': [ + ['OS=="win"', { + 'libraries': [ + 'dbghelp.lib', + 'Netapi32.lib', + 'PsApi.lib', + 'Ws2_32.lib', + ], + 'dll_files': [ + 'dbghelp.dll', + 'Netapi32.dll', + 'PsApi.dll', + 'Ws2_32.dll', + ], + }], + ], + }], ], }, # cctest ], # end targets diff --git a/src/node.cc b/src/node.cc index d9cb4f9be768bf..2766654e9a06b9 100644 --- a/src/node.cc +++ b/src/node.cc @@ -89,6 +89,10 @@ #include #endif +#ifdef NODE_REPORT +#include "node_report.h" +#endif + #if defined(LEAK_SANITIZER) #include #endif @@ -733,6 +737,12 @@ void RunBootstrapping(Environment* env) { return; } +#ifdef NODE_REPORT + if (env->options()->experimental_report) { + report::InitializeReport(env->isolate(), env); + } +#endif // NODE_REPORT + // process, loaderExports, isMainThread std::vector> node_params = { env->process_string(), @@ -963,6 +973,12 @@ int Init(std::vector* argv, // Make inherited handles noninheritable. uv_disable_stdio_inheritance(); +#ifdef NODE_REPORT + // Cache the original command line to be + // used in diagnostic reports. + per_process::cli_options->cmdline = *argv; +#endif // NODE_REPORT + #if defined(NODE_V8_OPTIONS) // Should come before the call to V8::SetFlagsFromCommandLine() // so the user can disable a flag --foo at run-time by passing diff --git a/src/node_binding.cc b/src/node_binding.cc index cf47b3058de538..5ea8f01b41bb89 100644 --- a/src/node_binding.cc +++ b/src/node_binding.cc @@ -14,6 +14,12 @@ #define NODE_BUILTIN_ICU_MODULES(V) #endif +#if NODE_REPORT +#define NODE_BUILTIN_REPORT_MODULES(V) V(report) +#else +#define NODE_BUILTIN_REPORT_MODULES(V) +#endif + // A list of built-in modules. In order to do module registration // in node::Init(), need to add built-in modules in the following list. // Then in binding::RegisterBuiltinModules(), it calls modules' registration @@ -69,7 +75,8 @@ #define NODE_BUILTIN_MODULES(V) \ NODE_BUILTIN_STANDARD_MODULES(V) \ NODE_BUILTIN_OPENSSL_MODULES(V) \ - NODE_BUILTIN_ICU_MODULES(V) + NODE_BUILTIN_ICU_MODULES(V) \ + NODE_BUILTIN_REPORT_MODULES(V) // This is used to load built-in modules. Instead of using // __attribute__((constructor)), we call the _register_ diff --git a/src/node_config.cc b/src/node_config.cc index 555685dd457ad2..af9403c1f9f854 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -1,7 +1,7 @@ +#include "env-inl.h" #include "node.h" #include "node_i18n.h" #include "node_options-inl.h" -#include "env-inl.h" #include "util-inl.h" namespace node { @@ -69,6 +69,10 @@ static void Initialize(Local target, #endif // NODE_HAVE_I18N_SUPPORT +#if defined(NODE_REPORT) + READONLY_TRUE_PROPERTY(target, "hasReport"); +#endif // NODE_REPORT + if (env->abort_on_uncaught_exception()) READONLY_TRUE_PROPERTY(target, "shouldAbortOnUncaughtException"); diff --git a/src/node_errors.cc b/src/node_errors.cc index 9b55a0b92187ac..3d607db2a88712 100644 --- a/src/node_errors.cc +++ b/src/node_errors.cc @@ -3,6 +3,9 @@ #include "node_errors.h" #include "node_internals.h" +#ifdef NODE_REPORT +#include "node_report.h" +#endif namespace node { @@ -314,6 +317,21 @@ void OnFatalError(const char* location, const char* message) { } else { PrintErrorString("FATAL ERROR: %s\n", message); } +#ifdef NODE_REPORT + Isolate* isolate = Isolate::GetCurrent(); + std::string filename; + Environment* env = Environment::GetCurrent(isolate); + if (env != nullptr) { + std::shared_ptr options = env->isolate_data()->options(); + if (options->report_on_fatalerror) { + report::TriggerNodeReport( + isolate, env, message, __func__, filename, Local()); + } + } else { + report::TriggerNodeReport( + isolate, nullptr, message, __func__, filename, Local()); + } +#endif // NODE_REPORT fflush(stderr); ABORT(); } diff --git a/src/node_options.cc b/src/node_options.cc index 1f8d1db7ecc1cc..667d7b6b9ddc60 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -34,6 +34,31 @@ void PerProcessOptions::CheckOptions(std::vector* errors) { void PerIsolateOptions::CheckOptions(std::vector* errors) { per_env->CheckOptions(errors); +#ifdef NODE_REPORT + if (!report_directory.empty() && !per_env->experimental_report) + errors->push_back("--diagnostic-report-directory option is valid only when " + "--experimental-report is set"); + if (!report_filename.empty() && !per_env->experimental_report) + errors->push_back("--diagnostic-report-filename option is valid only when " + "--experimental-report is set"); + if (!report_signal.empty() && !per_env->experimental_report) + errors->push_back("--diagnostic-report-signal option is valid only when " + "--experimental-report is set"); + if (report_on_fatalerror && !per_env->experimental_report) + errors->push_back( + "--diagnostic-report-on-fatalerror option is valid only when " + "--experimental-report is set"); + if (report_on_signal && !per_env->experimental_report) + errors->push_back("--diagnostic-report-on-signal option is valid only when " + "--experimental-report is set"); + if (report_uncaught_exception && !per_env->experimental_report) + errors->push_back( + "--diagnostic-report-uncaught-exception option is valid only when " + "--experimental-report is set"); + if (report_verbose && !per_env->experimental_report) + errors->push_back("--diagnostic-report-verbose option is valid only when " + "--experimental-report is set"); +#endif // NODE_REPORT } void EnvironmentOptions::CheckOptions(std::vector* errors) { @@ -119,6 +144,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { &EnvironmentOptions::experimental_vm_modules, kAllowedInEnvironment); AddOption("--experimental-worker", "", NoOp{}, kAllowedInEnvironment); +#ifdef NODE_REPORT + AddOption("--experimental-report", + "enable report generation", + &EnvironmentOptions::experimental_report, + kAllowedInEnvironment); +#endif // NODE_REPORT AddOption("--expose-internals", "", &EnvironmentOptions::expose_internals); AddOption("--http-parser", "Select which HTTP parser to use; either 'legacy' or 'llhttp' " @@ -248,6 +279,42 @@ PerIsolateOptionsParser::PerIsolateOptionsParser() { AddOption("--perf-prof", "", V8Option{}, kAllowedInEnvironment); AddOption("--stack-trace-limit", "", V8Option{}, kAllowedInEnvironment); +#ifdef NODE_REPORT + AddOption("--diagnostic-report-uncaught-exception", + "generate diagnostic report on uncaught exceptions", + &PerIsolateOptions::report_uncaught_exception, + kAllowedInEnvironment); + AddOption("--diagnostic-report-on-signal", + "generate diagnostic report upon receiving signals", + &PerIsolateOptions::report_on_signal, + kAllowedInEnvironment); + AddOption("--diagnostic-report-on-fatalerror", + "generate diagnostic report on fatal (internal) errors", + &PerIsolateOptions::report_on_fatalerror, + kAllowedInEnvironment); + AddOption("--diagnostic-report-signal", + "causes diagnostic report to be produced on provided signal," + " unsupported in Windows. (default: SIGUSR2)", + &PerIsolateOptions::report_signal, + kAllowedInEnvironment); + Implies("--diagnostic-report-signal", "--diagnostic-report-on-signal"); + AddOption("--diagnostic-report-filename", + "define custom report file name." + " (default: YYYYMMDD.HHMMSS.PID.SEQUENCE#.txt)", + &PerIsolateOptions::report_filename, + kAllowedInEnvironment); + AddOption("--diagnostic-report-directory", + "define custom report pathname." + " (default: current working directory of Node.js process)", + &PerIsolateOptions::report_directory, + kAllowedInEnvironment); + AddOption("--diagnostic-report-verbose", + "verbose option for report generation(true|false)." + " (default: false)", + &PerIsolateOptions::report_verbose, + kAllowedInEnvironment); +#endif // NODE_REPORT + Insert(&EnvironmentOptionsParser::instance, &PerIsolateOptions::get_per_env_options); } diff --git a/src/node_options.h b/src/node_options.h index ead69eb61a7ed7..d77c4fb80877e6 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -116,6 +116,9 @@ class EnvironmentOptions : public Options { bool syntax_check_only = false; bool has_eval_string = false; +#ifdef NODE_REPORT + bool experimental_report = false; +#endif // NODE_REPORT std::string eval_string; bool print_eval = false; bool force_repl = false; @@ -142,6 +145,15 @@ class PerIsolateOptions : public Options { std::shared_ptr per_env { new EnvironmentOptions() }; bool track_heap_objects = false; +#ifdef NODE_REPORT + bool report_uncaught_exception = false; + bool report_on_signal = false; + bool report_on_fatalerror = false; + std::string report_signal; + std::string report_filename; + std::string report_directory; + bool report_verbose; +#endif // NODE_REPORT inline EnvironmentOptions* get_per_env_options(); void CheckOptions(std::vector* errors); }; @@ -184,6 +196,10 @@ class PerProcessOptions : public Options { #endif #endif +#ifdef NODE_REPORT + std::vector cmdline; +#endif // NODE_REPORT + inline PerIsolateOptions* get_per_isolate_options(); void CheckOptions(std::vector* errors); }; diff --git a/src/node_report.cc b/src/node_report.cc new file mode 100644 index 00000000000000..276ce93095029d --- /dev/null +++ b/src/node_report.cc @@ -0,0 +1,833 @@ + +#include "node_report.h" +#include "ares.h" +#include "debug_utils.h" +#include "http_parser.h" +#include "nghttp2/nghttp2ver.h" +#include "node_internals.h" +#include "node_metadata.h" +#include "zlib.h" + +#include +#include + +#ifdef _WIN32 +#include +#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 +#include +#endif + +#include +#include +#include +#include + +#ifndef _MSC_VER +#include +#endif + +#ifdef __APPLE__ +#include +#endif + +#ifndef _WIN32 +extern char** environ; +#endif + +namespace report { +using node::arraysize; +using node::Environment; +using node::Mutex; +using node::NativeSymbolDebuggingContext; +using node::PerIsolateOptions; +using v8::HeapSpaceStatistics; +using v8::HeapStatistics; +using v8::Isolate; +using v8::Local; +using v8::Number; +using v8::StackTrace; +using v8::String; +using v8::V8; +using v8::Value; + +// Internal/static function declarations +static void WriteNodeReport(Isolate* isolate, + Environment* env, + const char* message, + const char* location, + const std::string& filename, + std::ostream& out, + Local stackstr, + TIME_TYPE* time); +static void PrintVersionInformation(JSONWriter* writer); +static void PrintJavaScriptStack(JSONWriter* writer, + Isolate* isolate, + Local stackstr, + const char* location); +static void PrintNativeStack(JSONWriter* writer); +#ifndef _WIN32 +static void PrintResourceUsage(JSONWriter* writer); +#endif +static void PrintGCStatistics(JSONWriter* writer, Isolate* isolate); +static void PrintSystemInformation(JSONWriter* writer); +static void PrintLoadedLibraries(JSONWriter* writer); +static void PrintComponentVersions(JSONWriter* writer); +static void LocalTime(TIME_TYPE* tm_struct); + +// Global variables +static std::atomic_int seq = {0}; // sequence number for report filenames + +// External function to trigger a report, writing to file. +// The 'name' parameter is in/out: an input filename is used +// if supplied, and the actual filename is returned. +std::string TriggerNodeReport(Isolate* isolate, + Environment* env, + const char* message, + const char* location, + std::string name, + Local stackstr) { + std::ostringstream oss; + std::string filename; + std::shared_ptr options; + if (env != nullptr) options = env->isolate_data()->options(); + + // Obtain the current time and the pid (platform dependent) + TIME_TYPE tm_struct; + PID_TYPE pid; + LocalTime(&tm_struct); + pid = uv_os_getpid(); + // Determine the required report filename. In order of priority: + // 1) supplied on API 2) configured on startup 3) default generated + if (!name.empty()) { + // Filename was specified as API parameter, use that + oss << name; + } else if (env != nullptr && options->report_filename.length() > 0) { + // File name was supplied via start-up option, use that + oss << options->report_filename; + } else { + // Construct the report filename, with timestamp, pid and sequence number + oss << "report"; + seq++; +#ifdef _WIN32 + oss << "." << std::setfill('0') << std::setw(4) << tm_struct.wYear; + oss << std::setfill('0') << std::setw(2) << tm_struct.wMonth; + oss << std::setfill('0') << std::setw(2) << tm_struct.wDay; + oss << "." << std::setfill('0') << std::setw(2) << tm_struct.wHour; + oss << std::setfill('0') << std::setw(2) << tm_struct.wMinute; + oss << std::setfill('0') << std::setw(2) << tm_struct.wSecond; + oss << "." << pid; + oss << "." << std::setfill('0') << std::setw(3) << seq.load(); +#else // UNIX, OSX + oss << "." << std::setfill('0') << std::setw(4) << tm_struct.tm_year + 1900; + oss << std::setfill('0') << std::setw(2) << tm_struct.tm_mon + 1; + oss << std::setfill('0') << std::setw(2) << tm_struct.tm_mday; + oss << "." << std::setfill('0') << std::setw(2) << tm_struct.tm_hour; + oss << std::setfill('0') << std::setw(2) << tm_struct.tm_min; + oss << std::setfill('0') << std::setw(2) << tm_struct.tm_sec; + oss << "." << pid; + oss << "." << std::setfill('0') << std::setw(3) << seq.load(); +#endif + oss << ".json"; + } + + filename = oss.str(); + // 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 (filename == "stdout") { + outstream = &std::cout; + } else if (filename == "stderr") { + outstream = &std::cerr; + } else { + // Regular file. Append filename to directory path if one was specified + if (env != nullptr && options->report_directory.length() > 0) { + std::string pathname = options->report_directory; + pathname += PATHSEP; + pathname += filename; + outfile.open(pathname, std::ios::out | std::ios::binary); + } else { + outfile.open(filename, std::ios::out | std::ios::binary); + } + // Check for errors on the file open + if (!outfile.is_open()) { + if (env != nullptr && options->report_directory.length() > 0) { + std::cerr << std::endl + << "Failed to open Node.js report file: " << filename + << " directory: " << options->report_directory + << " (errno: " << errno << ")" << std::endl; + } else { + std::cerr << std::endl + << "Failed to open Node.js report file: " << filename + << " (errno: " << errno << ")" << std::endl; + } + return ""; + } else { + std::cerr << std::endl + << "Writing Node.js report to file: " << filename << std::endl; + } + } + + // Pass our stream about by reference, not by copying it. + std::ostream& out = outfile.is_open() ? outfile : *outstream; + + WriteNodeReport( + isolate, env, message, location, filename, out, stackstr, &tm_struct); + + // Do not close stdout/stderr, only close files we opened. + if (outfile.is_open()) { + outfile.close(); + } + + std::cerr << "Node.js report completed" << std::endl; + if (name.empty()) return filename; + return name; +} + +// External function to trigger a report, writing to a supplied stream. +void GetNodeReport(Isolate* isolate, + Environment* env, + const char* message, + const char* location, + Local stackstr, + std::ostream& out) { + // Obtain the current time and the pid (platform dependent) + TIME_TYPE tm_struct; + LocalTime(&tm_struct); + std::string str = "NA"; + WriteNodeReport( + isolate, env, message, location, str, out, stackstr, &tm_struct); +} + +// Internal function to coordinate and write the various +// sections of the report to the supplied stream +static void WriteNodeReport(Isolate* isolate, + Environment* env, + const char* message, + const char* location, + const std::string& filename, + std::ostream& out, + Local stackstr, + TIME_TYPE* tm_struct) { + std::ostringstream buf; + PID_TYPE pid = uv_os_getpid(); + + // Save formatting for output stream. + std::ios old_state(nullptr); + old_state.copyfmt(out); + + // File stream opened OK, now start printing the report content: + // the title and header information (event, filename, timestamp and pid) + + JSONWriter writer(out); + writer.json_start(); + writer.json_objectstart("header"); + + writer.json_keyvalue("event", message); + writer.json_keyvalue("location", location); + if (!filename.empty()) + writer.json_keyvalue("filename", filename); + else + writer.json_keyvalue("filename", std::string("''")); + + // Report 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); + writer.json_keyvalue("dumpEventTime", timebuf); +#else // UNIX, OSX + snprintf(timebuf, + sizeof(timebuf), + "%4d-%02d-%02dT%02d:%02d:%02dZ", + 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); + writer.json_keyvalue("dumpEventTime", timebuf); + struct timeval ts; + gettimeofday(&ts, nullptr); + writer.json_keyvalue("dumpEventTimeStamp", + std::to_string(ts.tv_sec * 1000 + ts.tv_usec / 1000)); +#endif + // Report native process ID + buf << pid; + writer.json_keyvalue("processId", buf.str()); + buf.flush(); + + // Report out the command line. + if (!node::per_process::cli_options->cmdline.empty()) { + writer.json_arraystart("commandLine"); + for (std::string arg : node::per_process::cli_options->cmdline) { + writer.json_element(arg); + } + writer.json_arrayend(); + } + + // Report Node.js and OS version information + PrintVersionInformation(&writer); + writer.json_objectend(); + + // Report summary JavaScript stack backtrace + PrintJavaScriptStack(&writer, isolate, stackstr, location); + + // Report native stack backtrace + PrintNativeStack(&writer); + + // Report V8 Heap and Garbage Collector information + PrintGCStatistics(&writer, isolate); + + // Report OS and current thread resource usage +#ifndef _WIN32 + PrintResourceUsage(&writer); +#endif + + writer.json_arraystart("libuv"); + if (env != nullptr) + uv_walk(env->event_loop(), WalkHandle, static_cast(&writer)); + else + uv_walk(uv_default_loop(), WalkHandle, static_cast(&writer)); + + writer.json_arrayend(); + + // Report operating system information + PrintSystemInformation(&writer); + + writer.json_objectend(); + + // Restore output stream formatting. + out.copyfmt(old_state); +} + +// Report Node.js version, OS version and machine information. +static void PrintVersionInformation(JSONWriter* writer) { + std::ostringstream buf; + // Report Node version + buf << "v" << NODE_VERSION_STRING; + writer->json_keyvalue("nodejsVersion", buf.str()); + buf.str(""); +#ifdef __GLIBC__ + buf << __GLIBC__ << "." << __GLIBC_MINOR__; + writer->json_keyvalue("glibcVersion", buf.str()); + buf.str(""); +#endif + // Report Process word size + buf << sizeof(void*) * 8 << " bit"; + writer->json_keyvalue("wordSize", buf.str()); + buf.str(""); + + // Report deps component versions + PrintComponentVersions(writer); + + // Report operating system and machine information (Windows) +#ifdef _WIN32 + { + // Level 101 to obtain the server name, type, and associated details. + // ref: https://docs.microsoft.com/en-us/windows/desktop/ + // api/lmserver/nf-lmserver-netservergetinfo + 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"); + } + writer->json_keyvalue("osVersion", os_name); + + // Convert and report 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); + buf << name_buf << " " << comment_buf; + writer->json_keyvalue("machine", buf.str()); + buf.flush(); + } else { + writer->json_keyvalue("machine", name_buf); + } + + if (os_info != nullptr) { + NetApiBufferFree(os_info); + } + } else { + // NetServerGetInfo() failed, fallback to use GetComputerName() instead + TCHAR machine_name[256]; + DWORD machine_name_size = 256; + writer->json_keyvalue("osVersion", "Windows"); + if (GetComputerName(machine_name, &machine_name_size)) { + writer->json_keyvalue("machine", machine_name); + } + } + } +#else + // Report operating system and machine information (Unix/OSX) + struct utsname os_info; + if (uname(&os_info) >= 0) { +#ifdef _AIX + buf << os_info.sysname << " " << os_info.version << "." << os_info.release; + writer->json_keyvalue("osVersion", buf.str()); + buf.flush(); +#else + buf << os_info.sysname << " " << os_info.release << " " << os_info.version; + writer->json_keyvalue("osVersion", buf.str()); + buf.flush(); +#endif + const char* (*libc_version)(); + *(reinterpret_cast(&libc_version)) = + dlsym(RTLD_DEFAULT, "gnu_get_libc_version"); + if (libc_version != nullptr) { + writer->json_keyvalue("glibc", (*libc_version)()); + } + buf << os_info.nodename << " " << os_info.machine; + writer->json_keyvalue("machine", buf.str()); + buf.flush(); + } +#endif +} + +// Report the JavaScript stack. +static void PrintJavaScriptStack(JSONWriter* writer, + Isolate* isolate, + Local stackstr, + const char* location) { + writer->json_objectstart("javascriptStack"); + + std::string ss; + if ((!strcmp(location, "OnFatalError")) || + (!strcmp(location, "OnUserSignal"))) { + ss = "No stack.\nUnavailable.\n"; + } else { + String::Utf8Value sv(isolate, stackstr); + ss = std::string(*sv, sv.length()); + } + int line = ss.find("\n"); + if (line == -1) { + writer->json_keyvalue("message", ss.c_str()); + writer->json_objectend(); + } else { + std::string l = ss.substr(0, line); + writer->json_keyvalue("message", l); + writer->json_arraystart("stack"); + ss = ss.substr(line + 1); + line = ss.find("\n"); + while (line != -1) { + l = ss.substr(0, line); + l.erase(l.begin(), std::find_if(l.begin(), l.end(), [](int ch) { + return !std::iswspace(ch); + })); + writer->json_element(l); + ss = ss.substr(line + 1); + line = ss.find("\n"); + } + } + writer->json_arrayend(); + writer->json_objectend(); +} + +// Report a native stack backtrace +static void PrintNativeStack(JSONWriter* writer) { + auto sym_ctx = NativeSymbolDebuggingContext::New(); + void* frames[256]; + const int size = sym_ctx->GetStackTrace(frames, arraysize(frames)); + writer->json_arraystart("nativeStack"); + int i; + std::ostringstream buf; + for (i = 1; i < size - 1; i += 1) { + void* frame = frames[i]; + buf.str(""); + buf << " [pc=" << frame << "] "; + buf << sym_ctx->LookupSymbol(frame).Display().c_str(); + writer->json_element(buf.str()); + } + buf.str(""); + buf << " [pc=" << frames[i] << "] "; + buf << sym_ctx->LookupSymbol(frames[i]).Display().c_str(); + writer->json_element(buf.str()); + writer->json_arrayend(); +} + +// Report 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(JSONWriter* writer, Isolate* isolate) { + HeapStatistics v8_heap_stats; + isolate->GetHeapStatistics(&v8_heap_stats); + HeapSpaceStatistics v8_heap_space_stats; + + writer->json_objectstart("javascriptHeap"); + writer->json_keyvalue("totalMemory", + std::to_string(v8_heap_stats.total_heap_size())); + writer->json_keyvalue("totalCommittedMemory", + std::to_string(v8_heap_stats.total_physical_size())); + writer->json_keyvalue("usedMemory", + std::to_string(v8_heap_stats.used_heap_size())); + writer->json_keyvalue("availableMemory", + std::to_string(v8_heap_stats.total_available_size())); + writer->json_keyvalue("memoryLimit", + std::to_string(v8_heap_stats.heap_size_limit())); + + writer->json_objectstart("heapSpaces"); + // Loop through heap spaces + size_t i; + for (i = 0; i < isolate->NumberOfHeapSpaces() - 1; i++) { + isolate->GetHeapSpaceStatistics(&v8_heap_space_stats, i); + writer->json_objectstart(v8_heap_space_stats.space_name()); + writer->json_keyvalue("memorySize", + std::to_string(v8_heap_space_stats.space_size())); + writer->json_keyvalue( + "committedMemory", + std::to_string(v8_heap_space_stats.physical_space_size())); + writer->json_keyvalue( + "capacity", + std::to_string(v8_heap_space_stats.space_used_size() + + v8_heap_space_stats.space_available_size())); + writer->json_keyvalue( + "used", std::to_string(v8_heap_space_stats.space_used_size())); + writer->json_keyvalue( + "available", + std::to_string(v8_heap_space_stats.space_available_size())); + writer->json_objectend(); + } + isolate->GetHeapSpaceStatistics(&v8_heap_space_stats, i); + writer->json_objectstart(v8_heap_space_stats.space_name()); + writer->json_keyvalue("memorySize", + std::to_string(v8_heap_space_stats.space_size())); + writer->json_keyvalue( + "committedMemory", + std::to_string(v8_heap_space_stats.physical_space_size())); + writer->json_keyvalue( + "capacity", + std::to_string(v8_heap_space_stats.space_used_size() + + v8_heap_space_stats.space_available_size())); + writer->json_keyvalue("used", + std::to_string(v8_heap_space_stats.space_used_size())); + writer->json_keyvalue( + "available", std::to_string(v8_heap_space_stats.space_available_size())); + writer->json_objectend(); + writer->json_objectend(); + writer->json_objectend(); +} + +#ifndef _WIN32 +// Report resource usage (Linux/OSX only). +static void PrintResourceUsage(JSONWriter* writer) { + char buf[64]; + double cpu_abs; + double cpu_percentage; + time_t current_time; // current time absolute + time(¤t_time); + size_t boot_time = static_cast(node::per_process::prog_start_time / + (1000 * 1000 * 1000)); + auto uptime = difftime(current_time, boot_time); + if (uptime == 0) uptime = 1; // avoid division by zero. + + // Process and current thread usage statistics + struct rusage stats; + writer->json_objectstart("resourceUsage"); + 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); + writer->json_keyvalue("userCpuSeconds", buf); + snprintf(buf, + sizeof(buf), + "%ld.%06d", + stats.ru_stime.tv_sec, + stats.ru_stime.tv_usec); + writer->json_keyvalue("kernelCpuSeconds", buf); +#else + snprintf(buf, + sizeof(buf), + "%ld.%06ld", + stats.ru_utime.tv_sec, + stats.ru_utime.tv_usec); + writer->json_keyvalue("userCpuSeconds", buf); + snprintf(buf, + sizeof(buf), + "%ld.%06ld", + stats.ru_stime.tv_sec, + stats.ru_stime.tv_usec); + writer->json_keyvalue("kernelCpuSeconds", buf); +#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; + writer->json_keyvalue("cpuConsumptionPercent", + std::to_string(cpu_percentage)); + writer->json_keyvalue("maxRss", std::to_string(stats.ru_maxrss * 1024)); + writer->json_objectstart("pageFaults"); + writer->json_keyvalue("IORequired", std::to_string(stats.ru_majflt)); + writer->json_keyvalue("IONotRequired", std::to_string(stats.ru_minflt)); + writer->json_objectend(); + writer->json_objectstart("fsActivity"); + writer->json_keyvalue("reads", std::to_string(stats.ru_inblock)); + writer->json_keyvalue("writes", std::to_string(stats.ru_oublock)); + writer->json_objectend(); + } + writer->json_objectend(); +#ifdef RUSAGE_THREAD + if (getrusage(RUSAGE_THREAD, &stats) == 0) { + writer->json_objectstart("uvthreadResourceUsage"); +#if defined(__APPLE__) || defined(_AIX) + snprintf(buf, + sizeof(buf), + "%ld.%06d", + stats.ru_utime.tv_sec, + stats.ru_utime.tv_usec); + writer->json_keyvalue("userCpuSeconds", buf); + snprintf(buf, + sizeof(buf), + "%ld.%06d", + stats.ru_stime.tv_sec, + stats.ru_stime.tv_usec); + writer->json_keyvalue("kernelCpuSeconds", buf); +#else + snprintf(buf, + sizeof(buf), + "%ld.%06ld", + stats.ru_utime.tv_sec, + stats.ru_utime.tv_usec); + writer->json_keyvalue("userCpuSeconds", buf); + snprintf(buf, + sizeof(buf), + "%ld.%06ld", + stats.ru_stime.tv_sec, + stats.ru_stime.tv_usec); + writer->json_keyvalue("kernelCpuSeconds", buf); +#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; + writer->json_keyvalue("cpuConsumptionPercent", + std::to_string(cpu_percentage)); + writer->json_objectstart("fsActivity"); + writer->json_keyvalue("reads", std::to_string(stats.ru_inblock)); + writer->json_keyvalue("writes", std::to_string(stats.ru_oublock)); + writer->json_objectend(); + writer->json_objectend(); + } +#endif +} +#endif + +// Report operating system information. +static void PrintSystemInformation(JSONWriter* writer) { +#ifndef _WIN32 + 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} + }; +#endif // _WIN32 + writer->json_objectstart("environmentVariables"); + Mutex::ScopedLock lock(node::per_process::env_var_mutex); +#ifdef _WIN32 + LPWSTR lpszVariable; + LPWCH lpvEnv; + + // Get pointer to the environment block + lpvEnv = GetEnvironmentStringsW(); + 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) { + DWORD size = WideCharToMultiByte( + CP_UTF8, 0, lpszVariable, -1, nullptr, 0, nullptr, nullptr); + char* str = new char[size]; + WideCharToMultiByte( + CP_UTF8, 0, lpszVariable, -1, str, size, nullptr, nullptr); + std::string env(str); + int sep = env.rfind("="); + std::string key = env.substr(0, sep); + std::string value = env.substr(sep + 1); + writer->json_keyvalue(key, value); + lpszVariable += lstrlenW(lpszVariable) + 1; + } + FreeEnvironmentStringsW(lpvEnv); + } + writer->json_objectend(); +#else + std::string pair; + for (char** env = environ; *env != nullptr; ++env) { + std::string pair(*env); + int separator = pair.find('='); + std::string key = pair.substr(0, separator); + std::string str = pair.substr(separator + 1); + writer->json_keyvalue(key, str); + } + writer->json_objectend(); + + writer->json_objectstart("userLimits"); + struct rlimit limit; + char buf[64]; + std::string soft, hard; + + for (size_t i = 0; i < arraysize(rlimit_strings); i++) { + if (getrlimit(rlimit_strings[i].id, &limit) == 0) { + if (limit.rlim_cur == RLIM_INFINITY) { + soft = std::string("unlimited"); + } else { +#if defined(_AIX) || defined(__sun) + snprintf(buf, sizeof(buf), "%ld", limit.rlim_cur); + soft = std::string(buf); +#elif defined(__linux__) && !defined(__GLIBC__) + snprintf(buf, sizeof(buf), "%ld", limit.rlim_cur); + soft = std::string(buf); +#else + snprintf(buf, sizeof(buf), "%16" PRIu64, limit.rlim_cur); + soft = std::string(soft); +#endif + } + if (limit.rlim_max == RLIM_INFINITY) { + hard = std::string("unlimited"); + } else { +#ifdef _AIX + snprintf(buf, sizeof(buf), "%lu", limit.rlim_max); + hard = std::string(buf); +#else + snprintf(buf, sizeof(buf), "%lu", limit.rlim_max); + hard = std::string(buf); +#endif + } + writer->json_objectstart(rlimit_strings[i].description); + writer->json_keyvalue("soft", soft); + writer->json_keyvalue("hard", hard); + writer->json_objectend(); + } + } + writer->json_objectend(); +#endif + + PrintLoadedLibraries(writer); +} + +// Report a list of loaded native libraries. +static void PrintLoadedLibraries(JSONWriter* writer) { + writer->json_arraystart("sharedObjects"); + std::vector modules = + NativeSymbolDebuggingContext::GetLoadedLibraries(); + for (auto const& module_name : modules) writer->json_element(module_name); + writer->json_arrayend(); +} + +// Obtain and report the node and subcomponent version strings. +static void PrintComponentVersions(JSONWriter* writer) { + std::stringstream buf; + + writer->json_objectstart("componentVersions"); + +#define V(key) \ + writer->json_keyvalue(#key, node::per_process::metadata.versions.key.c_str()); + NODE_VERSIONS_KEYS(V) +#undef V + + // Some extra information that is not present in node_metadata. + writer->json_keyvalue("arch", NODE_ARCH); + writer->json_keyvalue("platform", NODE_PLATFORM); + writer->json_keyvalue("release", NODE_RELEASE); + if (NODE_VERSION_IS_LTS != 0) + writer->json_keyvalue("lts", NODE_VERSION_LTS_CODENAME); + writer->json_objectend(); +} + +static void LocalTime(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 +} + +} // namespace report diff --git a/src/node_report.h b/src/node_report.h new file mode 100644 index 00000000000000..c64b9c9a20c116 --- /dev/null +++ b/src/node_report.h @@ -0,0 +1,165 @@ +#ifndef SRC_NODE_REPORT_H_ +#define SRC_NODE_REPORT_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "v8.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#include +#include +#endif + +namespace report { + +#ifdef _WIN32 +typedef SYSTEMTIME TIME_TYPE; +typedef DWORD PID_TYPE; +#define PATHSEP "\\" +#else // UNIX, OSX +typedef struct tm TIME_TYPE; +typedef pid_t PID_TYPE; +#define PATHSEP "/" +#endif + +void InitializeReport(v8::Isolate* isolate, node::Environment* env); + +// Function declarations - functions in src/node_report.cc +std::string TriggerNodeReport(v8::Isolate* isolate, + node::Environment* env, + const char* message, + const char* location, + std::string name, + v8::Local stackstr); +void GetNodeReport(v8::Isolate* isolate, + node::Environment* env, + const char* message, + const char* location, + v8::Local stackstr, + std::ostream& out); + +// Function declarations - utility functions in src/utilities.cc +void ReportEndpoints(uv_handle_t* h, std::ostringstream& out); +void WalkHandle(uv_handle_t* h, void* arg); +std::string EscapeJsonChars(const std::string& str); + +// Function declarations - export functions in src/node_report_module.cc +void TriggerReport(const v8::FunctionCallbackInfo& info); +void GetReport(const v8::FunctionCallbackInfo& info); + +// Node.js boot time - defined in src/node.cc +extern double prog_start_time; + +// JSON compiler definitions. +class JSONWriter { + public: + explicit JSONWriter(std::ostream& out) + : out_(out), indent_(0), state_(JSONOBJECT) {} + + inline void indent() { indent_ += 2; } + inline void deindent() { indent_ -= 2; } + inline void advance() { + for (int i = 0; i < indent_; i++) out_ << " "; + } + + inline void json_start() { + if (state_ == JSONVALUE) out_ << ","; + out_ << "\n"; + advance(); + out_ << "{"; + indent(); + state_ = JSONOBJECT; + } + + inline void json_end() { + out_ << "\n"; + deindent(); + advance(); + out_ << "}"; + state_ = JSONVALUE; + } + template + inline void json_objectstart(T key) { + if (state_ == JSONVALUE) out_ << ","; + out_ << "\n"; + advance(); + out_ << "\"" << key << "\"" + << ": {"; + indent(); + state_ = JSONOBJECT; + } + + template + inline void json_arraystart(T key) { + if (state_ == JSONVALUE) out_ << ","; + out_ << "\n"; + advance(); + out_ << "\"" << key << "\"" + << ": ["; + indent(); + state_ = JSONOBJECT; + } + inline void json_objectend() { + out_ << "\n"; + deindent(); + advance(); + out_ << "}"; + state_ = JSONVALUE; + } + + inline void json_arrayend() { + out_ << "\n"; + deindent(); + advance(); + out_ << "]"; + state_ = JSONVALUE; + } + template + inline void json_keyvalue(T key, U value) { + if (state_ == JSONVALUE) out_ << ","; + out_ << "\n"; + advance(); + out_ << "\"" << key << "\"" + << ": " + << "\""; + out_ << EscapeJsonChars(value) << "\""; + state_ = JSONVALUE; + } + + template + inline void json_element(U value) { + if (state_ == JSONVALUE) out_ << ","; + out_ << "\n"; + advance(); + out_ << "\"" << EscapeJsonChars(value) << "\""; + state_ = JSONVALUE; + } + + private: + enum JSONState { JSONOBJECT, JSONVALUE }; + std::ostream& out_; + int indent_; + int state_; +}; + +} // namespace report + +#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..522cf43a4516dd --- /dev/null +++ b/src/node_report_module.cc @@ -0,0 +1,294 @@ +#include "env.h" +#include "node_errors.h" +#include "node_internals.h" +#include "node_options.h" +#include "node_report.h" +#include "util.h" + +#include "env-inl.h" +#include "handle_wrap.h" +#include "node_buffer.h" +#include "stream_base-inl.h" +#include "stream_wrap.h" +#include "util-inl.h" + +#include +#include +#include + +namespace report { +using node::Environment; +using node::FIXED_ONE_BYTE_STRING; +using node::PerIsolateOptions; +using node::Utf8Value; +using v8::Array; +using v8::Boolean; +using v8::Context; +using v8::Function; +using v8::FunctionCallbackInfo; +using v8::HandleScope; +using v8::Isolate; +using v8::Local; +using v8::Object; +using v8::String; +using v8::V8; +using v8::Value; + +// Internal/static function declarations +void OnUncaughtException(const FunctionCallbackInfo& info); +static void Initialize(Local exports, + Local unused, + Local context); + +// External JavaScript API for triggering a report +void TriggerReport(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + std::string filename; + Local stackstr; + + if (info.Length() == 1) { + stackstr = info[0].As(); + } else { + filename = *String::Utf8Value(isolate, info[0]); + stackstr = info[1].As(); + } + + filename = TriggerNodeReport( + isolate, env, "JavaScript API", __func__, filename, stackstr); + // Return value is the report filename + info.GetReturnValue().Set( + String::NewFromUtf8(isolate, filename.c_str(), v8::NewStringType::kNormal) + .ToLocalChecked()); +} + +// External JavaScript API for returning a report +void GetReport(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + std::ostringstream out; + + GetNodeReport( + isolate, env, "JavaScript API", __func__, info[0].As(), out); + + // Return value is the contents of a report as a string. + info.GetReturnValue().Set(String::NewFromUtf8(isolate, + out.str().c_str(), + v8::NewStringType::kNormal) + .ToLocalChecked()); +} + +// Callbacks for triggering report on uncaught exception. +// Calls triggered from JS land. +void OnUncaughtException(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + std::string filename; + std::shared_ptr options = env->isolate_data()->options(); + + // Trigger report if requested + if (options->report_uncaught_exception) { + TriggerNodeReport( + isolate, env, "exception", __func__, filename, info[0].As()); + } +} + +// Signal handler for report action, called from JS land (util.js) +void OnUserSignal(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + CHECK(info[0]->IsString()); + Local str = info[0].As(); + String::Utf8Value value(isolate, str); + std::string filename; + TriggerNodeReport( + isolate, env, *value, __func__, filename, info[0].As()); +} + +// Native module initializer function, called when the module is require'd +void InitializeReport(Isolate* isolate, Environment* env) { + // Register the boot time of the process, for + // computing resource consumption average etc. + std::shared_ptr options = env->isolate_data()->options(); + + if (options->report_signal == "") options->report_signal = "SIGUSR2"; +} + +// A method to sync up data elements in the JS land with its +// corresponding elements in the C++ world. Required because +// (i) the tunables are first intercepted through the CLI but +// later modified via APIs. (ii) the report generation events +// are controlled partly from C++ and partly from JS. +void SyncConfig(const FunctionCallbackInfo& info) { + Environment* env = Environment::GetCurrent(info); + Local context = env->context(); + std::shared_ptr options = env->isolate_data()->options(); + + CHECK_EQ(info.Length(), 2); + Local obj; + if (!info[0]->ToObject(context).ToLocal(&obj)) return; + bool sync = info[1].As()->Value(); + + // Events array + Local eventskey = FIXED_ONE_BYTE_STRING(env->isolate(), "events"); + Local events_unchecked; + if (!obj->Get(context, eventskey).ToLocal(&events_unchecked)) return; + Local events; + if (events_unchecked->IsUndefined() || events_unchecked->IsNull()) { + events_unchecked = Array::New(env->isolate(), 0); + if (obj->Set(context, eventskey, events_unchecked).IsNothing()) return; + } + events = events_unchecked.As(); + + // Signal + Local signalkey = env->signal_string(); + Local signal_unchecked; + if (!obj->Get(context, signalkey).ToLocal(&signal_unchecked)) return; + Local signal; + if (signal_unchecked->IsUndefined() || signal_unchecked->IsNull()) + signal_unchecked = signalkey; + signal = signal_unchecked.As(); + + Utf8Value signalstr(env->isolate(), signal); + + // Report file + Local filekey = FIXED_ONE_BYTE_STRING(env->isolate(), "filename"); + Local file_unchecked; + if (!obj->Get(context, filekey).ToLocal(&file_unchecked)) return; + Local file; + if (file_unchecked->IsUndefined() || file_unchecked->IsNull()) + file_unchecked = filekey; + file = file_unchecked.As(); + + Utf8Value filestr(env->isolate(), file); + + // Report file path + Local pathkey = FIXED_ONE_BYTE_STRING(env->isolate(), "path"); + Local path_unchecked; + if (!obj->Get(context, pathkey).ToLocal(&path_unchecked)) return; + Local path; + if (path_unchecked->IsUndefined() || path_unchecked->IsNull()) + path_unchecked = pathkey; + path = path_unchecked.As(); + + Utf8Value pathstr(env->isolate(), path); + + // Report verbosity + Local verbosekey = FIXED_ONE_BYTE_STRING(env->isolate(), "verbose"); + Local verbose_unchecked; + if (!obj->Get(context, verbosekey).ToLocal(&verbose_unchecked)) return; + Local verbose; + if (verbose_unchecked->IsUndefined() || verbose_unchecked->IsNull()) + verbose_unchecked = Boolean::New(env->isolate(), "verbose"); + verbose = verbose_unchecked.As(); + + bool verb = verbose->BooleanValue(env->isolate()); + + if (sync) { + static const std::string e = "exception"; + static const std::string s = "signal"; + static const std::string f = "fatalerror"; + for (uint32_t i = 0; i < events->Length(); i++) { + Local v; + if (!events->Get(context, i).ToLocal(&v)) return; + Local elem; + if (!v->ToString(context).ToLocal(&elem)) return; + String::Utf8Value buf(env->isolate(), elem); + if (*buf == e) { + options->report_uncaught_exception = true; + } else if (*buf == s) { + options->report_on_signal = true; + } else if (*buf == f) { + options->report_on_fatalerror = true; + } + } + CHECK_NOT_NULL(*signalstr); + options->report_signal = *signalstr; + CHECK_NOT_NULL(*filestr); + options->report_filename = *filestr; + CHECK_NOT_NULL(*pathstr); + options->report_directory = *pathstr; + options->report_verbose = verb; + } else { + int i = 0; + if (options->report_uncaught_exception && + events + ->Set(context, + i++, + FIXED_ONE_BYTE_STRING(env->isolate(), "exception")) + .IsNothing()) + return; + if (options->report_on_signal && + events + ->Set(context, i++, FIXED_ONE_BYTE_STRING(env->isolate(), "signal")) + .IsNothing()) + return; + if (options->report_on_fatalerror && + events + ->Set( + context, i, FIXED_ONE_BYTE_STRING(env->isolate(), "fatalerror")) + .IsNothing()) + return; + + Local signal_value; + Local file_value; + Local path_value; + if (!node::ToV8Value(context, options->report_signal) + .ToLocal(&signal_value)) + return; + if (!obj->Set(context, signalkey, signal_value).FromJust()) return; + + if (!node::ToV8Value(context, options->report_filename) + .ToLocal(&file_value)) + return; + if (!obj->Set(context, filekey, file_value).FromJust()) return; + + if (!node::ToV8Value(context, options->report_directory) + .ToLocal(&path_value)) + return; + if (!obj->Set(context, pathkey, path_value).FromJust()) return; + + if (!obj->Set(context, + verbosekey, + Boolean::New(env->isolate(), options->report_verbose)) + .FromJust()) + return; + } +} + +static void Initialize(Local exports, + Local unused, + Local context) { + Environment* env = Environment::GetCurrent(context); + std::shared_ptr options = env->isolate_data()->options(); + Isolate* isolate = env->isolate(); + InitializeReport(isolate, env); + env->SetMethod(exports, "triggerReport", TriggerReport); + env->SetMethod(exports, "getReport", GetReport); + env->SetMethod(exports, "onUnCaughtException", OnUncaughtException); + env->SetMethod(exports, "onUserSignal", OnUserSignal); + env->SetMethod(exports, "syncConfig", SyncConfig); + + // TODO(gireeshpunathil) if we are retaining this flag, + // insert more verbose information at vital control flow + // points. Right now, it is only this one. + if (options->report_verbose) { + std::cerr << "report: initialization complete, event flags:" << std::endl; + std::cerr << "report_uncaught_exception: " + << options->report_uncaught_exception << std::endl; + std::cerr << "report_on_signal: " << options->report_on_signal << std::endl; + std::cerr << "report_on_fatalerror: " << options->report_on_fatalerror + << std::endl; + std::cerr << "report_signal: " << options->report_signal << std::endl; + std::cerr << "report_filename: " << options->report_filename << std::endl; + std::cerr << "report_directory: " << options->report_directory << std::endl; + std::cerr << "report_verbose: " << options->report_verbose << std::endl; + } +} + +} // namespace report + +NODE_MODULE_CONTEXT_AWARE_INTERNAL(report, report::Initialize) diff --git a/src/node_report_utils.cc b/src/node_report_utils.cc new file mode 100644 index 00000000000000..e93f230d318a6d --- /dev/null +++ b/src/node_report_utils.cc @@ -0,0 +1,299 @@ +#include +#include "env.h" +#include "node_internals.h" +#include "node_options.h" +#include "node_report.h" +#include "util.h" +#include "v8.h" + +namespace report { + +using node::MallocedBuffer; + +// 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) { + MallocedBuffer buffer(0); + 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.data, &size); + break; + } + case UV_FS_POLL: { + rc = uv_fs_poll_getpath(&(handle->fs_poll), buffer.data, &size); + break; + } + default: + break; + } + if (rc == UV_ENOBUFS) { + buffer = MallocedBuffer(size); + switch (h->type) { + case UV_FS_EVENT: { + rc = uv_fs_event_getpath(&(handle->fs_event), buffer.data, &size); + break; + } + case UV_FS_POLL: { + rc = uv_fs_poll_getpath(&(handle->fs_poll), buffer.data, &size); + break; + } + default: + break; + } + if (rc == 0) { + // buffer is not null terminated. + std::string name(buffer.data, size); + out << "filename: " << name; + } + } +} + +// Utility function to walk libuv handles. +void WalkHandle(uv_handle_t* h, void* arg) { + std::string type; + std::ostringstream data; + JSONWriter* writer = 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 +#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" : ""); + } + + writer->json_start(); + writer->json_keyvalue("type", type); + writer->json_keyvalue("is_active", std::to_string(uv_is_active(h))); + writer->json_keyvalue("is_referenced", std::to_string(uv_has_ref(h))); + writer->json_keyvalue("address", + std::to_string(reinterpret_cast(h))); + writer->json_keyvalue("details", data.str()); + writer->json_end(); +} + +static std::string findAndReplace(const std::string& str, + const std::string& old, + const std::string& neu) { + std::string ret = str; + size_t pos = 0; + while ((pos = ret.find(old, pos)) != std::string::npos) { + ret.replace(pos, old.length(), neu); + pos += neu.length(); + } + return ret; +} + +std::string EscapeJsonChars(const std::string& str) { + std::string ret = str; + ret = findAndReplace(ret, "\\", "\\\\"); + ret = findAndReplace(ret, "\\u", "\\u"); + ret = findAndReplace(ret, "\n", "\\n"); + ret = findAndReplace(ret, "\f", "\\f"); + ret = findAndReplace(ret, "\r", "\\r"); + ret = findAndReplace(ret, "\b", "\\b"); + ret = findAndReplace(ret, "\t", "\\t"); + ret = findAndReplace(ret, "\"", "\\\""); + return ret; +} + +} // namespace report From 7f4053ed1381d7001dbdaa12f064847aaf4d0ecf Mon Sep 17 00:00:00 2001 From: Vipin Menon Date: Wed, 5 Sep 2018 19:52:49 +0530 Subject: [PATCH 2/3] doc: add node-report documentation a separate section added for node-report at top level main documentation added to doc/api/report.md API documentation added to doc/api/process.md PR-URL: https://github.com/nodejs/node/pull/22712 Reviewed-By: Anna Henningsen Reviewed-By: James M Snell Reviewed-By: Michael Dawson Reviewed-By: Vse Mozhet Byt --- doc/api/cli.md | 73 +++++++ doc/api/errors.md | 6 + doc/api/index.md | 1 + doc/api/process.md | 104 ++++++++++ doc/api/report.md | 490 +++++++++++++++++++++++++++++++++++++++++++++ doc/node.1 | 46 +++++ 6 files changed, 720 insertions(+) create mode 100644 doc/api/report.md diff --git a/doc/api/cli.md b/doc/api/cli.md index 78994f428bba3c..770d98336f57f7 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -75,6 +75,64 @@ $ node --completion-bash > node_bash_completion $ source node_bash_completion ``` +### `--diagnostic-report-directory=directory` + + +Location at which the report will be generated. + +### `--diagnostic-report-filename=filename` + + +Name of the file to which the report will be written. + +### `--diagnostic-report-on-fatalerror` + + +Enables the report to be triggered on fatal errors (internal errors within +the Node.js runtime such as out of memory) that lead to termination of the +application, if `--experimental-report` is enabled. Useful to inspect various +diagnostic data elements such as heap, stack, event loop state, resource +consumption etc. to reason about the fatal error. + +### `--diagnostic-report-on-signal` + + +Enables report to be generated upon receiving the specified (or predefined) +signal to the running Node.js process, if `--experimental-report` is enabled. +The signal to trigger the report is specified through `--diagnostic-report-signal`. + +### `--diagnostic-report-signal=signal` + + +Sets or resets the signal for report generation (not supported on Windows). +Default signal is `SIGUSR2`. + +### `--diagnostic-report-uncaught-exception` + + +Enables report to be generated on un-caught exceptions, if +`--experimental-report` is enabled. Useful when inspecting JavaScript stack in +conjunction with native stack and other runtime environment data. + +### `--diagnostic-report-verbose` + + +Flag that enables additional information to be printed during report generation. + ### `--enable-fips` + +Enable experimental diagnostic report feature. + ### `--experimental-vm-modules` + +* `err` {Object} +* Returns: {Object} Returns the diagnostics report as an `Object`. + +Generates a JSON-formatted diagnostic report summary of the running process. +The report includes JavaScript and native stack traces, heap statistics, +platform information, resource usage etc. + +```js +const data = process.report.getReport(); +console.log(data); +``` + +Additional documentation on diagnostic report is available +at [report documentation][]. + +### process.report.setDiagnosticReportOptions([options]); + + +Set the runtime configuration of diagnostic report data capture. Upon invocation +of this function, the runtime is reconfigured to generate report based on +the new input. + +* `options` {Object} + * `events` {string[]} + * `signal`: generate a report in response to a signal raised on the process. + * `exception`: generate a report on unhandled exceptions. + * `fatalerror`: generate a report on internal fault + (such as out of memory errors or native assertions). + * `signal` {string} sets or resets the signal for report generation + (not supported on Windows). **Default:** `'SIGUSR2'`. + * `filename` {string} name of the file to which the report will be written. + * `path` {string} drectory at which the report will be generated. + **Default:** the current working directory of the Node.js process. + * `verbose` {boolean} flag that controls additional verbose information on + report generation. **Default:** `false`. + +```js +// Trigger a report upon uncaught exceptions or fatal erros. +process.report.setDiagnosticReportOptions( + { events: ['exception', 'fatalerror'] }); + +// Change the default path and filename of the report. +process.report.setDiagnosticReportOptions( + { filename: 'foo.json', path: '/home' }); + +// Produce the report onto stdout, when generated. Special meaning is attached +// to `stdout` and `stderr`. Usage of these will result in report being written +// to the associated standard streams. URLs are not supported. +process.report.setDiagnosticReportOptions( + { filename: 'stdout' }); + +// Enable verbose option on report generation. +process.report.setDiagnosticReportOptions( + { verbose: true }); + +``` + +Signal based report generation is not supported on Windows. + +Additional documentation on diagnostic report is available +at [report documentation][]. + +### process.report.triggerReport([filename][, err]) + + +* `filename` {string} The file to write into. The `filename` should be +a relative path, that will be appended to the directory specified by +`process.report.setDiagnosticReportOptions`, or current working directory +of the Node.js process, if unspecified. +* `err` {Object} A custom object which will be used for reporting +JavsScript stack. + +* Returns: {string} Returns the filename of the generated report. + +If both `filename` and `err` object are passed to `triggerReport()` the +`err` object must be the second parameter. + +Triggers and produces the report (a JSON-formatted file with the internal +state of Node.js runtime) synchronously, and writes into a file. + +```js +process.report.triggerReport(); +``` + +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 and error +object can be specified as parameters on the `triggerReport()` call. + +Additional documentation on diagnostic report is available +at [report documentation][]. + ## process.send(message[, sendHandle[, options]][, callback])