diff options
author | Gireesh Punathil <gpunathi@in.ibm.com> | 2018-09-05 17:06:59 +0300 |
---|---|---|
committer | Gireesh Punathil <gpunathi@in.ibm.com> | 2019-01-18 08:04:04 +0300 |
commit | 4f6797378eb6dd290dd5d8dc6417fea22d42ebad (patch) | |
tree | 88967037a1ba9aec47faea701efecf4f44007587 /src | |
parent | 01cd21973b26a2cbacbe143c5983cb4adf8e7681 (diff) |
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 <bidipyne@in.ibm.com>
Co-authored-by: Howard Hellyer <hhellyer@uk.ibm.com>
Co-authored-by: Jeremiah Senkpiel <fishrock123@rocketmail.com>
Co-authored-by: Julian Alimin <dmastag@yahoo.com>
Co-authored-by: Lakshmi Swetha Gopireddy <lakshmigopireddy@in.ibm.com>
Co-authored-by: Manusaporn Treerungroj <m.treerungroj@gmail.com>
Co-authored-by: Michael Dawson <michael_dawson@ca.ibm.com>
Co-authored-by: Richard Chamberlain <richard_chamberlain@uk.ibm.com>
Co-authored-by: Richard Lau <riclau@uk.ibm.com>
Co-authored-by: Sam Roberts <vieuxtech@gmail.com>
Co-authored-by: Vipin Menon <vipinmv1@in.ibm.com>
PR-URL: https://github.com/nodejs/node/pull/22712
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Michael Dawson <Michael_Dawson@ca.ibm.com>
Reviewed-By: Vse Mozhet Byt <vsemozhetbyt@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/node.cc | 16 | ||||
-rw-r--r-- | src/node_binding.cc | 9 | ||||
-rw-r--r-- | src/node_config.cc | 6 | ||||
-rw-r--r-- | src/node_errors.cc | 18 | ||||
-rw-r--r-- | src/node_options.cc | 67 | ||||
-rw-r--r-- | src/node_options.h | 16 | ||||
-rw-r--r-- | src/node_report.cc | 833 | ||||
-rw-r--r-- | src/node_report.h | 165 | ||||
-rw-r--r-- | src/node_report_module.cc | 294 | ||||
-rw-r--r-- | src/node_report_utils.cc | 299 |
10 files changed, 1721 insertions, 2 deletions
diff --git a/src/node.cc b/src/node.cc index d9cb4f9be76..2766654e9a0 100644 --- a/src/node.cc +++ b/src/node.cc @@ -89,6 +89,10 @@ #include <unicode/uvernum.h> #endif +#ifdef NODE_REPORT +#include "node_report.h" +#endif + #if defined(LEAK_SANITIZER) #include <sanitizer/lsan_interface.h> #endif @@ -733,6 +737,12 @@ void RunBootstrapping(Environment* env) { return; } +#ifdef NODE_REPORT + if (env->options()->experimental_report) { + report::InitializeReport(env->isolate(), env); + } +#endif // NODE_REPORT + // process, loaderExports, isMainThread std::vector<Local<String>> node_params = { env->process_string(), @@ -963,6 +973,12 @@ int Init(std::vector<std::string>* argv, // Make inherited handles noninheritable. uv_disable_stdio_inheritance(); +#ifdef NODE_REPORT + // Cache the original command line to be + // used in diagnostic reports. + per_process::cli_options->cmdline = *argv; +#endif // NODE_REPORT + #if defined(NODE_V8_OPTIONS) // Should come before the call to V8::SetFlagsFromCommandLine() // so the user can disable a flag --foo at run-time by passing diff --git a/src/node_binding.cc b/src/node_binding.cc index cf47b3058de..5ea8f01b41b 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_<modname> diff --git a/src/node_config.cc b/src/node_config.cc index 555685dd457..af9403c1f9f 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<Object> 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 9b55a0b9218..3d607db2a88 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<PerIsolateOptions> options = env->isolate_data()->options(); + if (options->report_on_fatalerror) { + report::TriggerNodeReport( + isolate, env, message, __func__, filename, Local<String>()); + } + } else { + report::TriggerNodeReport( + isolate, nullptr, message, __func__, filename, Local<String>()); + } +#endif // NODE_REPORT fflush(stderr); ABORT(); } diff --git a/src/node_options.cc b/src/node_options.cc index 1f8d1db7ecc..667d7b6b9dd 100644 --- a/src/node_options.cc +++ b/src/node_options.cc @@ -34,6 +34,31 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors) { void PerIsolateOptions::CheckOptions(std::vector<std::string>* 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<std::string>* 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 ead69eb61a7..d77c4fb8087 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<EnvironmentOptions> 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<std::string>* errors); }; @@ -184,6 +196,10 @@ class PerProcessOptions : public Options { #endif #endif +#ifdef NODE_REPORT + std::vector<std::string> cmdline; +#endif // NODE_REPORT + inline PerIsolateOptions* get_per_isolate_options(); void CheckOptions(std::vector<std::string>* errors); }; diff --git a/src/node_report.cc b/src/node_report.cc new file mode 100644 index 00000000000..276ce930950 --- /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 <atomic> +#include <fstream> + +#ifdef _WIN32 +#include <Lm.h> +#include <Windows.h> +#include <dbghelp.h> +#include <process.h> +#include <psapi.h> +#include <tchar.h> +#include <cwctype> +#else +#include <sys/resource.h> +// Get the standard printf format macros for C99 stdint types. +#ifndef __STDC_FORMAT_MACROS +#define __STDC_FORMAT_MACROS +#endif +#include <cxxabi.h> +#include <dlfcn.h> +#include <inttypes.h> +#include <sys/utsname.h> +#endif + +#include <fcntl.h> +#include <string.h> +#include <time.h> +#include <iomanip> + +#ifndef _MSC_VER +#include <strings.h> +#endif + +#ifdef __APPLE__ +#include <mach-o/dyld.h> +#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<String> stackstr, + TIME_TYPE* time); +static void PrintVersionInformation(JSONWriter* writer); +static void PrintJavaScriptStack(JSONWriter* writer, + Isolate* isolate, + Local<String> 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<String> stackstr) { + std::ostringstream oss; + std::string filename; + std::shared_ptr<PerIsolateOptions> 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<String> 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<String> 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<void*>(&writer)); + else + uv_walk(uv_default_loop(), WalkHandle, static_cast<void*>(&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<LPBYTE*>(&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<void**>(&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<String> 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<time_t>(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<LPWSTR>(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<std::string> 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 00000000000..c64b9c9a20c --- /dev/null +++ b/src/node_report.h @@ -0,0 +1,165 @@ +#ifndef SRC_NODE_REPORT_H_ +#define SRC_NODE_REPORT_H_ + +#include <node.h> +#include <node_buffer.h> +#include <uv.h> +#include <algorithm> +#include <climits> +#include <cstdlib> +#include <cstring> +#include <queue> +#include <string> +#include <utility> +#include <vector> +#include "v8.h" + +#include <stdio.h> +#include <stdlib.h> +#include <iomanip> +#include <iostream> +#include <sstream> + +#ifdef _WIN32 +#include <time.h> +#else +#include <sys/time.h> +#include <sys/types.h> +#include <unistd.h> +#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<v8::String> stackstr); +void GetNodeReport(v8::Isolate* isolate, + node::Environment* env, + const char* message, + const char* location, + v8::Local<v8::String> 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<v8::Value>& info); +void GetReport(const v8::FunctionCallbackInfo<v8::Value>& 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 <typename T> + inline void json_objectstart(T key) { + if (state_ == JSONVALUE) out_ << ","; + out_ << "\n"; + advance(); + out_ << "\"" << key << "\"" + << ": {"; + indent(); + state_ = JSONOBJECT; + } + + template <typename T> + 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 <typename T, typename U> + inline void json_keyvalue(T key, U value) { + if (state_ == JSONVALUE) out_ << ","; + out_ << "\n"; + advance(); + out_ << "\"" << key << "\"" + << ": " + << "\""; + out_ << EscapeJsonChars(value) << "\""; + state_ = JSONVALUE; + } + + template <typename U> + 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 00000000000..522cf43a451 --- /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 <v8.h> +#include <atomic> +#include <sstream> + +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<Value>& info); +static void Initialize(Local<Object> exports, + Local<Value> unused, + Local<Context> context); + +// External JavaScript API for triggering a report +void TriggerReport(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + std::string filename; + Local<String> stackstr; + + if (info.Length() == 1) { + stackstr = info[0].As<String>(); + } else { + filename = *String::Utf8Value(isolate, info[0]); + stackstr = info[1].As<String>(); + } + + 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<Value>& 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<String>(), 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<Value>& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + HandleScope scope(isolate); + std::string filename; + std::shared_ptr<PerIsolateOptions> options = env->isolate_data()->options(); + + // Trigger report if requested + if (options->report_uncaught_exception) { + TriggerNodeReport( + isolate, env, "exception", __func__, filename, info[0].As<String>()); + } +} + +// Signal handler for report action, called from JS land (util.js) +void OnUserSignal(const FunctionCallbackInfo<Value>& info) { + Environment* env = Environment::GetCurrent(info); + Isolate* isolate = env->isolate(); + CHECK(info[0]->IsString()); + Local<String> str = info[0].As<String>(); + String::Utf8Value value(isolate, str); + std::string filename; + TriggerNodeReport( + isolate, env, *value, __func__, filename, info[0].As<String>()); +} + +// 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<PerIsolateOptions> 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<Value>& info) { + Environment* env = Environment::GetCurrent(info); + Local<Context> context = env->context(); + std::shared_ptr<PerIsolateOptions> options = env->isolate_data()->options(); + + CHECK_EQ(info.Length(), 2); + Local<Object> obj; + if (!info[0]->ToObject(context).ToLocal(&obj)) return; + bool sync = info[1].As<Boolean>()->Value(); + + // Events array + Local<String> eventskey = FIXED_ONE_BYTE_STRING(env->isolate(), "events"); + Local<Value> events_unchecked; + if (!obj->Get(context, eventskey).ToLocal(&events_unchecked)) return; + Local<Array> 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<Array>(); + + // Signal + Local<String> signalkey = env->signal_string(); + Local<Value> signal_unchecked; + if (!obj->Get(context, signalkey).ToLocal(&signal_unchecked)) return; + Local<String> signal; + if (signal_unchecked->IsUndefined() || signal_unchecked->IsNull()) + signal_unchecked = signalkey; + signal = signal_unchecked.As<String>(); + + Utf8Value signalstr(env->isolate(), signal); + + // Report file + Local<String> filekey = FIXED_ONE_BYTE_STRING(env->isolate(), "filename"); + Local<Value> file_unchecked; + if (!obj->Get(context, filekey).ToLocal(&file_unchecked)) return; + Local<String> file; + if (file_unchecked->IsUndefined() || file_unchecked->IsNull()) + file_unchecked = filekey; + file = file_unchecked.As<String>(); + + Utf8Value filestr(env->isolate(), file); + + // Report file path + Local<String> pathkey = FIXED_ONE_BYTE_STRING(env->isolate(), "path"); + Local<Value> path_unchecked; + if (!obj->Get(context, pathkey).ToLocal(&path_unchecked)) return; + Local<String> path; + if (path_unchecked->IsUndefined() || path_unchecked->IsNull()) + path_unchecked = pathkey; + path = path_unchecked.As<String>(); + + Utf8Value pathstr(env->isolate(), path); + + // Report verbosity + Local<String> verbosekey = FIXED_ONE_BYTE_STRING(env->isolate(), "verbose"); + Local<Value> verbose_unchecked; + if (!obj->Get(context, verbosekey).ToLocal(&verbose_unchecked)) return; + Local<Boolean> verbose; + if (verbose_unchecked->IsUndefined() || verbose_unchecked->IsNull()) + verbose_unchecked = Boolean::New(env->isolate(), "verbose"); + verbose = verbose_unchecked.As<Boolean>(); + + 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<Value> v; + if (!events->Get(context, i).ToLocal(&v)) return; + Local<String> 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<Value> signal_value; + Local<Value> file_value; + Local<Value> 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<Object> exports, + Local<Value> unused, + Local<Context> context) { + Environment* env = Environment::GetCurrent(context); + std::shared_ptr<PerIsolateOptions> 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 00000000000..e93f230d318 --- /dev/null +++ b/src/node_report_utils.cc @@ -0,0 +1,299 @@ +#include <v8.h> +#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<sockaddr*>(&addr_storage); + char hostbuf[NI_MAXHOST]; + char portbuf[NI_MAXSERV]; + uv_any_handle* handle = reinterpret_cast<uv_any_handle*>(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<char> buffer(0); + int rc = -1; + size_t size = 0; + uv_any_handle* handle = reinterpret_cast<uv_any_handle*>(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<char>(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<JSONWriter*>(arg); + uv_any_handle* handle = reinterpret_cast<uv_any_handle*>(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<int>(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<int64_t>(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 |