From 9f73800171aad22da935c799668b086713b9dbd2 Mon Sep 17 00:00:00 2001 From: Gireesh Punathil Date: Wed, 5 Sep 2018 02:50:44 -0400 Subject: [PATCH] node-report: merge into core Make node-report part of core runtime, to satisfy its tier1 status on diagnostic tooling. 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) Refs: #19661 Refs: #18760 Refs: nodejs/node-report#103 --- configure | 67 ++- lib/util.js | 24 + node.gyp | 29 +- src/node.cc | 24 + src/node_config.cc | 8 + src/node_internals.h | 9 +- src/node_options.cc | 6 + src/node_options.h | 3 + src/node_report.cc | 1115 +++++++++++++++++++++++++++++++++++++ src/node_report.h | 168 ++++++ src/node_report_module.cc | 507 +++++++++++++++++ src/node_report_utils.cc | 576 +++++++++++++++++++ src/node_util.cc | 14 + 13 files changed, 2538 insertions(+), 12 deletions(-) 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 b/configure index 332071345f4487..00714b1595b732 100755 --- a/configure +++ b/configure @@ -157,11 +157,23 @@ parser.add_option("--enable-vtune-profiling", "JavaScript code executed in nodejs. This feature is only available " "for x32, x86, and x64 architectures.") +parser.add_option("--enable-pgo-generate", + action="store_true", + dest="enable_pgo_generate", + help="Enable profiling with pgo of a binary. This feature is only available " + "on linux with gcc and g++ 5.4.1 or newer.") + +parser.add_option("--enable-pgo-use", + action="store_true", + dest="enable_pgo_use", + help="Enable use of the profile generated with --enable-pgo-generate. This " + "feature is only available on linux with gcc and g++ 5.4.1 or newer.") + parser.add_option("--enable-lto", action="store_true", dest="enable_lto", help="Enable compiling with lto of a binary. This feature is only available " - "on linux with gcc and g++.") + "on linux with gcc and g++ 5.4.1 or newer.") parser.add_option("--link-module", action="append", @@ -488,6 +500,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', @@ -898,11 +915,22 @@ def configure_mips(o): o['variables']['mips_fpu_mode'] = options.mips_fpu_mode +def gcc_version_ge(version_checked): + for compiler in [(CC, 'c'), (CXX, 'c++')]: + ok, is_clang, clang_version, compiler_version = \ + try_check_compiler(compiler[0], compiler[1]) + compiler_version_num = tuple(map(int, compiler_version)) + if is_clang or compiler_version_num < version_checked: + return False + return True + + def configure_node(o): if options.dest_os == 'android': 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() @@ -942,6 +970,29 @@ def configure_node(o): else: o['variables']['node_enable_v8_vtunejit'] = 'false' + if flavor != 'linux' and (options.enable_pgo_generate or options.enable_pgo_use): + raise Exception( + 'The pgo option is supported only on linux.') + + if flavor == 'linux': + if options.enable_pgo_generate or options.enable_pgo_use: + version_checked = (5, 4, 1) + if not gcc_version_ge(version_checked): + version_checked_str = ".".join(map(str, version_checked)) + raise Exception( + 'The options --enable-pgo-generate and --enable-pgo-use ' + 'are supported for gcc and gxx %s or newer only.' % (version_checked_str)) + + if options.enable_pgo_generate and options.enable_pgo_use: + raise Exception( + 'Only one of the --enable-pgo-generate or --enable-pgo-use options ' + 'can be specified at a time. You would like to use ' + '--enable-pgo-generate first, profile node, and then recompile ' + 'with --enable-pgo-use') + + o['variables']['enable_pgo_generate'] = b(options.enable_pgo_generate) + o['variables']['enable_pgo_use'] = b(options.enable_pgo_use) + if flavor != 'linux' and (options.enable_lto): raise Exception( 'The lto option is supported only on linux.') @@ -949,15 +1000,11 @@ def configure_node(o): if flavor == 'linux': if options.enable_lto: version_checked = (5, 4, 1) - for compiler in [(CC, 'c'), (CXX, 'c++')]: - ok, is_clang, clang_version, compiler_version = \ - try_check_compiler(compiler[0], compiler[1]) - compiler_version_num = tuple(map(int, compiler_version)) - if is_clang or compiler_version_num < version_checked: - version_checked_str = ".".join(map(str, version_checked)) - raise Exception( - 'The option --enable-lto is supported for gcc and gxx %s' - ' or newer only.' % (version_checked_str)) + if not gcc_version_ge(version_checked): + version_checked_str = ".".join(map(str, version_checked)) + raise Exception( + 'The option --enable-lto is supported for gcc and gxx %s' + ' or newer only.' % (version_checked_str)) o['variables']['enable_lto'] = b(options.enable_lto) diff --git a/lib/util.js b/lib/util.js index e8fb41a2198a99..e1d5460e8435c3 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1541,3 +1541,27 @@ 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 d94e6ff62aa0db..0fd94a258071a7 100644 --- a/node.gyp +++ b/node.gyp @@ -628,7 +628,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', + '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', + ], + }], + ], + }], + ], }, { 'target_name': 'mkssldef', diff --git a/src/node.cc b/src/node.cc index 3597571cdffd4c..a87c9f5869bb87 100644 --- a/src/node.cc +++ b/src/node.cc @@ -90,6 +90,10 @@ #include #endif +#if defined(NODE_REPORT) +#include "node_report.h" +#endif + #if defined(LEAK_SANITIZER) #include #endif @@ -2332,6 +2336,14 @@ void LoadEnvironment(Environment* env) { return; } +#if defined(NODE_REPORT) + auto env_opts = per_process_opts->per_isolate->per_env; + if (!env_opts->report_events.empty()) { + nodereport::InitializeNodeReport(); + nodereport::SetEvents(env->isolate(), env_opts->report_events.c_str()); + } +#endif // NODE_REPORT + // Bootstrap Node.js Local bootstrapper = Object::New(env->isolate()); SetupBootstrapObject(env, bootstrapper); @@ -2647,6 +2659,18 @@ void ProcessArgv(std::vector* args, exit(9); } +#if defined(NODE_REPORT) + if (!env_opts->report_events.empty()) { + size_t pos = 0; + std::string& temp = env_opts->report_events; + while ((pos = temp.find(",", pos)) != std::string::npos) { + temp.replace(pos, 1, "+"); + pos += 1; + } + env_opts->report_events = temp; + } +#endif // NODE_REPORT + #if HAVE_OPENSSL if (per_process_opts->use_openssl_ca && per_process_opts->use_bundled_ca) { fprintf(stderr, "%s: either --use-openssl-ca or --use-bundled-ca can be " diff --git a/src/node_config.cc b/src/node_config.cc index 080d8550665ad3..ff945661ad34b9 100644 --- a/src/node_config.cc +++ b/src/node_config.cc @@ -153,6 +153,14 @@ static void Initialize(Local target, v8EnvironmentFlags->Set(i, OneByteString(env->isolate(), v8_environment_flags[i])); } + +#if defined(NODE_REPORT) + const std::string& report_events = env->options()->report_events; + if (!report_events.empty()) { + READONLY_STRING_PROPERTY(target, "node_report", report_events); + } +#endif // NODE_REPORT + } // InitConfig } // namespace node diff --git a/src/node_internals.h b/src/node_internals.h index edfc00e53f13a7..6a7fb02679840d 100644 --- a/src/node_internals.h +++ b/src/node_internals.h @@ -97,6 +97,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 @@ -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_options.cc b/src/node_options.cc index c8586ec64e3bb1..c01a80a0e1462d 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -129,6 +129,12 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() { "show stack traces on process warnings", &EnvironmentOptions::trace_warnings, kAllowedInEnvironment); +#if defined(NODE_REPORT) + AddOption("--report-events", + "enable node report generation", + &EnvironmentOptions::report_events, + kAllowedInEnvironment); +#endif // NODE_REPORT AddOption("--check", "syntax check script without executing", diff --git a/src/node_options.h b/src/node_options.h index 7891d550ae3dad..0e136b896639c0 100644 --- a/src/node_options.h +++ b/src/node_options.h @@ -82,6 +82,9 @@ class EnvironmentOptions { bool syntax_check_only = false; bool has_eval_string = false; std::string eval_string; +#if defined(NODE_REPORT) + std::string report_events; +#endif // NODE_REPORT bool print_eval = false; bool force_repl = false; 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..755f14e4d64b55 --- /dev/null +++ b/src/node_report_module.cc @@ -0,0 +1,507 @@ +#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) { + 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); + + 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 5adecf4d9753c3..29c11b415a717b 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 { @@ -219,6 +222,17 @@ void Initialize(Local target, env->SetMethod(target, "safeGetenv", SafeGetenv); +#if defined(NODE_REPORT) + nodereport::InitializeNodeReport(); + 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); + env->SetMethod(target, "triggerNodeReport", nodereport::TriggerReport); +#endif // NODE_REPORT + Local constants = Object::New(env->isolate()); NODE_DEFINE_CONSTANT(constants, ALL_PROPERTIES); NODE_DEFINE_CONSTANT(constants, ONLY_WRITABLE);