From a582ea94a823465c79fef631348f99ff012673a4 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Thu, 25 Aug 2016 16:28:13 +0100 Subject: [PATCH 01/24] Initial drop of nodereport project for nodejs repo --- .gitignore | 38 +++ README.md | 58 ++++ binding.gyp | 30 ++ demo/api_call.js | 35 ++ demo/exception.js | 34 ++ demo/fatalerror.js | 42 +++ demo/loop.js | 55 ++++ package.json | 21 ++ src/module.cc | 338 +++++++++++++++++++ src/node_report.cc | 790 +++++++++++++++++++++++++++++++++++++++++++++ src/node_report.h | 38 +++ test/autorun.js | 122 +++++++ test/test_1.js | 10 + test/test_2.js | 16 + test/test_3.js | 16 + test/test_4.js | 28 ++ 16 files changed, 1671 insertions(+) create mode 100644 .gitignore create mode 100644 binding.gyp create mode 100644 demo/api_call.js create mode 100644 demo/exception.js create mode 100644 demo/fatalerror.js create mode 100644 demo/loop.js create mode 100644 package.json create mode 100644 src/module.cc create mode 100644 src/node_report.cc create mode 100644 src/node_report.h create mode 100644 test/autorun.js create mode 100644 test/test_1.js create mode 100644 test/test_2.js create mode 100644 test/test_3.js create mode 100644 test/test_4.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f1a566b --- /dev/null +++ b/.gitignore @@ -0,0 +1,38 @@ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +*.node + +# Project files +*.project +*.cproject + +# Build directories +/build/ +/node_modules/ diff --git a/README.md b/README.md index 8c77476..1312700 100644 --- a/README.md +++ b/README.md @@ -8,3 +8,61 @@ It includes Javascript and native stack traces, heap statistics, platform information and resource usage etc. With the report enabled, reports are triggered on unhandled exceptions, fatal errors, signals and calls to a Javascript API. + +Usage: + + npm install nodejs/nodereport + + var nodereport = require('nodereport'); + +By default, this will trigger a NodeReport to be written to the current +directory on unhandled exceptions, fatal errors or (Linux/OSX only) +SIGUSR2 signals. In addition, a NodeReport can be triggered from a +Javascript application via API. The filename of the NodeReport is +returned. The default filename includes the date, time, PID and a +sequence number. Alternatively a filename can be specified on the API call. + + nodereport.triggerReport(); + + var filename = nodereport.triggerReport(); + + nodereport.triggerReport("myReportName"); + +Content of the NodeReport in the initial implementation consists of a +header section containing the event type, date, time, PID and Node version, +sections containing Javascript and native stack traces, a section containing +V8 heap information, a section containing libuv handle information and an OS +platform information section showing CPU and memory usage and system limits. +The following messages are issued to stderr when a NodeReport is triggered: + + Writing Node.js error report to file: NodeReport.201605113.145311.26249.001.txt + Node.js error report completed + +Configuration is available via these environment variables and/or Javascript +API calls: + + export NODEREPORT_EVENTS=exception+fatalerror+signal + nodereport.setEvents("exception+fatalerror+signal"); + + export NODEREPORT_COREDUMP=yes/no + nodereport.setCoreDump("yes/no"); + + export NODEREPORT_SIGNAL=SIGUSR2/SIGQUIT + nodereport.setSignal("SIGUSR2/SIGQUIT"); + + export NODEREPORT_FILENAME=stdout/stderr/ + nodereport.setFileName("stdout/stderr/"); + + export NODEREPORT_DIRECTORY= + nodereport.setDirectory(""); + + export NODEREPORT_VERBOSE=yes/no + nodereport.setVerbose("yes/no"); + +Sample programs for triggering NodeReports are provided in the +node_modules/nodereport/demo directory: + + api.js - NodeReport triggered by Javascript API call + exception.js - NodeReport triggered by unhandled exception + fatalerror.js - NodeReport triggered by fatal error on Javascript heap out of memory + loop.js - looping application, NodeReport triggered using kill -USR2 diff --git a/binding.gyp b/binding.gyp new file mode 100644 index 0000000..97d7b66 --- /dev/null +++ b/binding.gyp @@ -0,0 +1,30 @@ +{ + "targets": [ + { + "target_name": "nodereport", + "sources": [ "src/node_report.cc", "src/module.cc" ], + "include_dirs": [ ':8080/ or http://localhost:8080/'); + +setTimeout(function(){ + console.log('api_call.js: test timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/demo/exception.js b/demo/exception.js new file mode 100644 index 0000000..30f19b0 --- /dev/null +++ b/demo/exception.js @@ -0,0 +1,34 @@ +// Example - generation of NodeReport on uncaught exception +require('nodereport'); +var http = require("http"); + +var count = 0; + +function my_listener(request, response) { + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport exception demo... refresh page to cause exception (application will terminate)"); + response.end(); + break; + default: + throw new UserException('*** exception.js: exception thrown from my_listener()'); + } +} + +function UserException(message) { + this.message = message; + this.name = "UserException"; +} + +var http_server = http.createServer(my_listener); +http_server.listen(8080); + +console.log('exception.js: Node running'); +console.log('exception.js: Go to http://:8080/ or http://localhost:8080/'); + +setTimeout(function(){ + console.log('exception.js: test timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/demo/fatalerror.js b/demo/fatalerror.js new file mode 100644 index 0000000..8de8060 --- /dev/null +++ b/demo/fatalerror.js @@ -0,0 +1,42 @@ +// Example - generation of Nodereport on fatal error (Javascript heap OOM) +require('nodereport'); +var http = require('http'); + +var count = 0; + +function my_listener(request, response) { + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport fatal error demo... refresh page to trigger excessive memory usage (application will terminate)"); + response.end(); + break; + case 1: + console.log('heap_oom.js: allocating excessive Javascript heap memory....'); + var list = []; + while (true) { + list.push(new MyRecord()); + } + response.end(); + break; + } +} + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} + +var http_server = http.createServer(my_listener); +http_server.listen(8080); + +console.log('fatalerror.js: Node running'); +console.log('fatalerror.js: Note: heap default is 1.4Gb, use --max-old-space-size= to change'); +console.log('fatalerror.js: Go to http://:8080/ or http://localhost:8080/'); + +setTimeout(function(){ + console.log('fatalerror.js: timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/demo/loop.js b/demo/loop.js new file mode 100644 index 0000000..613b827 --- /dev/null +++ b/demo/loop.js @@ -0,0 +1,55 @@ +// Example - geneation of Nodereport via signal for a looping application +require('nodereport'); +var http = require("http"); + +var count = 0; + +function my_listener(request, response) { + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport looping application demo. Node process ID = " + process.pid); + response.write("\n\nRefresh page to enter loop, then use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); + response.end(); + break; + case 1: + console.log("loop.js: going to loop now, use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); + + var list = []; + for (var i=0; i<10000000000; i++) { + for (var j=0; i<1000; i++) { + list.push(new MyRecord()); + } + for (var j=0; i<1000; i++) { + list[j].id += 1; + list[j].account += 2; + } + for (var j=0; i<1000; i++) { + list.pop(); + } + } + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nNodeReport demo.... finished looping"); + response.end(); + break; + default: + } +} + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} + +var http_server = http.createServer(my_listener); +http_server.listen(8080); + +console.log('loop.js: Node running'); +console.log('loop.js: Go to http://:8080/ or http://localhost:8080/'); + +setTimeout(function(){ + console.log('loop.js: timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/package.json b/package.json new file mode 100644 index 0000000..37ea512 --- /dev/null +++ b/package.json @@ -0,0 +1,21 @@ +{ + "name": "nodereport", + "main": "build/Release/nodereport.node", + "version": "0.1.0", + "keywords": [ + "" + ], + "description": "Diagnostic NodeReport", + "homepage": "https://github.com/nodejs/nodereport", + "repository": { + "type": "git", + "url": "https://github.com/nodejs/nodereport.git" + }, + "engines": { + "node": ">=4.0.0" + }, + "dependencies": { + "nan": "^2.3.5" + }, + "license": "MIT" +} diff --git a/src/module.cc b/src/module.cc new file mode 100644 index 0000000..1d5afa4 --- /dev/null +++ b/src/module.cc @@ -0,0 +1,338 @@ +#include +//#include +#include "node_report.h" + +// Internal/static function declarations +static void OnFatalError(const char* location, const char* message); +bool OnUncaughtException(v8::Isolate* isolate); +#ifndef _WIN32 +static void SignalDumpAsyncCallback(uv_async_t* handle); +inline void* ReportSignalThreadMain(void* unused); +static int StartWatchdogThread(void *(*thread_main) (void* unused)); +static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler); +static void SignalDump(int signo); +#endif + +// Default nodereport option settings +static unsigned int nodereport_events = NR_EXCEPTION + NR_FATALERROR + NR_SIGNAL + NR_JSAPICALL; +static unsigned int nodereport_core = 1; +static unsigned int nodereport_verbose = 0; + +#ifdef _WIN32 +static unsigned int nodereport_signal = 0; // no-op on Windows +#else // signal support - on Unix/OSX only +static unsigned int nodereport_signal = SIGUSR2; // default signal is SIGUSR2 +static int report_signal = 0; +static uv_sem_t report_semaphore; +static uv_async_t nodereport_trigger_async; +static uv_mutex_t node_isolate_mutex; +#endif + +static v8::Isolate* node_isolate; + +/******************************************************************************* + * External JavaScript API for triggering a NodeReport + * + ******************************************************************************/ +NAN_METHOD(TriggerReport) { + Nan::HandleScope scope; + v8::Isolate* isolate = Isolate::GetCurrent(); + char filename[48] = ""; + + if (info[0]->IsString()) { + // Filename parameter supplied + Nan::Utf8String filename_parameter(info[0]->ToString()); + if (filename_parameter.length() < 48) { + strcpy(filename, *filename_parameter); + } else { + Nan::ThrowSyntaxError("TriggerReport: filename parameter is too long (max 48 characters)"); + } + } + if (info[0]->IsFunction()) { + // Callback parameter supplied + Nan::Callback callback(info[0].As()); + // Creates a new Object on the V8 heap + Local obj = Object::New(isolate); + obj->Set(String::NewFromUtf8(isolate, "number"), Number::New(isolate, 54)); + Local argv[1]; + argv[0] = obj; + // Invoke the callback, passing the object in argv + callback.Call(1, argv); + } + + TriggerNodeReport(isolate, kJavaScript, "JavaScript API", "TriggerReport (nodereport/src/module.cc)", filename); + // Return value is the NodeReport filename + info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked()); +} + +/******************************************************************************* + * External JavaScript APIs for nodereport configuration + * + ******************************************************************************/ +NAN_METHOD(SetEvents) { + Nan::Utf8String parameter(info[0]->ToString()); + v8::Isolate* isolate = node_isolate; + unsigned int previous_events = nodereport_events; // save previous settings + nodereport_events = ProcessNodeReportEvents(*parameter); + + // If NodeReport newly requested for fatalerror, set up the V8 call-back + if ((nodereport_events & NR_FATALERROR) && !(previous_events & NR_FATALERROR)) { + isolate->SetFatalErrorHandler(OnFatalError); + } + + // If NodeReport newly requested for exceptions, tell V8 to capture stack trace and set up the callback + if ((nodereport_events & NR_EXCEPTION) && !(previous_events & NR_EXCEPTION)) { + isolate->SetCaptureStackTraceForUncaughtExceptions(true, 32, v8::StackTrace::kDetailed); + // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set + v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); + isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); + } + +#ifndef _WIN32 + // If NodeReport newly requested on external user signal set up watchdog thread and callbacks + if ((nodereport_events & NR_SIGNAL) && !(previous_events & NR_SIGNAL)) { + uv_sem_init(&report_semaphore, 0); + if (StartWatchdogThread(ReportSignalThreadMain) == 0) { + uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); + uv_unref(reinterpret_cast(&nodereport_trigger_async)); + RegisterSignalHandler(nodereport_signal, SignalDump, false); + } + } +#endif +} +NAN_METHOD(SetCoreDump) { + Nan::Utf8String parameter(info[0]->ToString()); + nodereport_events = ProcessNodeReportCoreSwitch(*parameter); +} +NAN_METHOD(SetSignal) { + Nan::Utf8String parameter(info[0]->ToString()); + nodereport_events = ProcessNodeReportSignal(*parameter); +} +NAN_METHOD(SetFileName) { + Nan::Utf8String parameter(info[0]->ToString()); + ProcessNodeReportFileName(*parameter); +} +NAN_METHOD(SetDirectory) { + Nan::Utf8String parameter(info[0]->ToString()); + ProcessNodeReportDirectory(*parameter); +} +NAN_METHOD(SetVerbose) { + Nan::Utf8String parameter(info[0]->ToString()); + nodereport_verbose = ProcessNodeReportVerboseSwitch(*parameter); +} + +/******************************************************************************* + * Callbacks for triggering NodeReport on failure events (as configured) + * - fatal error + * - uncaught exception + * - signal + ******************************************************************************/ +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 NodeReport if requested + if (nodereport_events & NR_FATALERROR) { + TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, NULL); + } + fflush(stderr); + if (nodereport_core) { + raise(SIGABRT); // core dump requested + } else { + exit(0); // no core dump requested + } +} + +bool OnUncaughtException(v8::Isolate* isolate) { + // Trigger NodeReport if required + if (nodereport_events & NR_EXCEPTION) { + TriggerNodeReport(Isolate::GetCurrent(), kException, "exception", "OnUncaughtException (nodereport/src/module.cc)", NULL); + } + if (nodereport_core) { + return true; + } else { + return false; + } +} + +#ifndef _WIN32 +static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { + if (report_signal != 0) { + fprintf(stderr,"SignalDumpInterruptCallback - handling signal\n"); + if (nodereport_events & NR_SIGNAL) { + if (nodereport_verbose) { + fprintf(stderr,"SignalDumpInterruptCallback - triggering NodeReport\n"); + } + TriggerNodeReport(Isolate::GetCurrent(), kSignal_JS, + node::signo_string(*(static_cast(data))), + "node::SignalDumpInterruptCallback()", NULL); + } + report_signal = 0; + } +} +static void SignalDumpAsyncCallback(uv_async_t* handle) { + if (report_signal != 0) { + fprintf(stderr,"SignalDumpAsyncCallback - handling signal\n"); + if (nodereport_events & NR_SIGNAL) { + if (nodereport_verbose) { + fprintf(stderr,"SignalDumpAsyncCallback - triggering NodeReport\n"); + } + size_t signo_data = reinterpret_cast(handle->data); + TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV, + node::signo_string(static_cast(signo_data)), + "node::SignalDumpAsyncCallback()", NULL); + } + report_signal = 0; + } +} + +static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler = false) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sa.sa_flags = reset_handler ? SA_RESETHAND : 0; + sigfillset(&sa.sa_mask); + sigaction(signal, &sa, nullptr); +} + +// Raw signal handler for triggering a NodeReport - runs on an arbitrary thread +static void SignalDump(int signo) { + // Check atomic for NodeReport 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 + } +} + +// Watchdog thread implementation for signal-triggered NodeReport +inline void* ReportSignalThreadMain(void* unused) { + for (;;) { + uv_sem_wait(&report_semaphore); + if (nodereport_verbose) { + fprintf(stderr, "Signal %s received by nodereport module\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, &report_signal); + // Event loop may be idle, so also request an async callback + size_t signo_data = static_cast(report_signal); + nodereport_trigger_async.data = reinterpret_cast(signo_data); + uv_async_send(&nodereport_trigger_async); + } + uv_mutex_unlock(&node_isolate_mutex); + } + return nullptr; +} + +static int StartWatchdogThread(void *(*thread_main) (void* unused)) { + pthread_attr_t attr; + pthread_attr_init(&attr); + + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); + 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, "nodereport: pthread_create: %s\n", strerror(err)); + fflush(stderr); + return -err; + } + return 0; +} +#endif + + +/******************************************************************************* + * Native module initializer function, called when the module is require'd + * + ******************************************************************************/ +void Initialize(v8::Local exports) { + v8::Isolate* isolate = Isolate::GetCurrent(); + node_isolate = isolate; + + SetLoadTime(); + + const char* trigger_events = getenv("NODEREPORT_EVENTS"); + if (trigger_events != NULL) { + nodereport_events = ProcessNodeReportEvents(trigger_events); + } + const char* core_dump_switch = getenv("NODEREPORT_COREDUMP"); + if (core_dump_switch != NULL) { + nodereport_core = ProcessNodeReportCoreSwitch(core_dump_switch); + } + const char* trigger_signal = getenv("NODEREPORT_SIGNAL"); + if (trigger_signal != NULL) { + nodereport_signal = ProcessNodeReportSignal(trigger_signal); + } + const char* report_name = getenv("NODEREPORT_FILENAME"); + if (report_name != NULL) { + ProcessNodeReportFileName(report_name); + } + const char* directory_name = getenv("NODEREPORT_DIRECTORY"); + if (directory_name != NULL) { + ProcessNodeReportDirectory(directory_name); + } + const char* verbose_switch = getenv("NODEREPORT_VERBOSE"); + if (verbose_switch != NULL) { + nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); + } + + // If NodeReport requested for fatalerror, set up the V8 call-back + if (nodereport_events & NR_FATALERROR) { + isolate->SetFatalErrorHandler(OnFatalError); + } + + // If NodeReport requested for exceptions, tell V8 to capture stack trace and set up the callback + if (nodereport_events & NR_EXCEPTION) { + isolate->SetCaptureStackTraceForUncaughtExceptions(true, 32, v8::StackTrace::kDetailed); + // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set + v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); + isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); + } + +#ifndef _WIN32 + // If NodeReport requested on external user signal set up watchdog thread and callbacks + if (nodereport_events & NR_SIGNAL) { + uv_sem_init(&report_semaphore, 0); + if (StartWatchdogThread(ReportSignalThreadMain) == 0) { + uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); + uv_unref(reinterpret_cast(&nodereport_trigger_async)); + RegisterSignalHandler(nodereport_signal, SignalDump, false); + } + } +#endif + + exports->Set(Nan::New("triggerReport").ToLocalChecked(), + Nan::New(TriggerReport)->GetFunction()); + exports->Set(Nan::New("setEvents").ToLocalChecked(), + Nan::New(SetEvents)->GetFunction()); + exports->Set(Nan::New("setCoreDump").ToLocalChecked(), + Nan::New(SetCoreDump)->GetFunction()); + exports->Set(Nan::New("setSignal").ToLocalChecked(), + Nan::New(SetSignal)->GetFunction()); + exports->Set(Nan::New("setFileName").ToLocalChecked(), + Nan::New(SetFileName)->GetFunction()); + exports->Set(Nan::New("setDirectory").ToLocalChecked(), + Nan::New(SetDirectory)->GetFunction()); + exports->Set(Nan::New("setVerbose").ToLocalChecked(), + Nan::New(SetVerbose)->GetFunction()); + + if (nodereport_verbose) { +#ifdef _WIN32 + fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x\n", + nodereport_events, nodereport_core); +#else + fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x signal flag: %#x\n", + nodereport_events, nodereport_core, nodereport_signal); +#endif + } +} + +NODE_MODULE(nodereport, Initialize) diff --git a/src/node_report.cc b/src/node_report.cc new file mode 100644 index 0000000..c55397e --- /dev/null +++ b/src/node_report.cc @@ -0,0 +1,790 @@ +#include "node_report.h" +#include "node_version.h" +#include "v8.h" +#include "time.h" +#include "zlib.h" +#include "ares.h" + +#include +#include +#include +#include + +#if !defined(_MSC_VER) +#include +#endif + +#ifdef _WIN32 +#include +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +using v8::HeapSpaceStatistics; +using v8::HeapStatistics; +using v8::Isolate; +using v8::Local; +using v8::Message; +using v8::StackFrame; +using v8::StackTrace; +using v8::String; + +using v8::V8; + +// Internal/static function declarations +static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event); +static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, int index, void *pc); +static void PrintNativeBacktrace(FILE *fp); +#ifndef _WIN32 +static void PrintResourceUsage(FILE *fp); +#endif +static void PrintGCStatistics(FILE *fp, Isolate* isolate); +static void PrintSystemInformation(FILE *fp, Isolate* isolate); +static void WriteInteger(FILE *fp, size_t value); + +// Global variables +static int seq = 0; // sequence number for NodeReport filenames +const char* v8_states[] = {"JS", "GC", "COMPILER", "OTHER", "EXTERNAL", "IDLE"}; +const char* TriggerNames[] = {"Exception", "FatalError", "SIGUSR2", "SIGQUIT", "JavaScript API"}; +static bool report_active = false; // recursion protection +static char report_filename[NR_MAXNAME + 1] = ""; +static char report_directory[NR_MAXPATH + 1] = ""; // defaults to current working directory +#ifdef _WIN32 +static SYSTEMTIME loadtime_tm_struct; // module load time +#else // UNIX, OSX +static struct tm loadtime_tm_struct; // module load time +extern char **environ; +#endif + +#if defined(_MSC_VER) && _MSC_VER < 1900 +// Workaround for arraysize() on Windows VS 2013. +#define arraysize(a) (sizeof(a) / sizeof(*a)) +#else +template +constexpr size_t arraysize(const T(&)[N]) { return N; } +#endif + +#if defined(_MSC_VER) && (_MSC_VER < 1900) +// Workaround for snprintf() on Windows VS 2013` +#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; +} +#endif + +/******************************************************************************* + * Functions to process nodereport configuration options: + * Trigger event selection + * Core dump yes/no switch + * Trigger signal selection + * NodeReport filename + * NodeReport directory + * Verbose mode + ******************************************************************************/ +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 { + fprintf(stderr, "Unrecognised argument for nodereport events option: %s\n", cursor); + return 0; + } + if (*cursor == '+') { + cursor++; // Hop over the '+' separator + } + } + return event_flags; +} + +unsigned int ProcessNodeReportCoreSwitch(const char *args) { + if (strlen(args) == 0) { + fprintf(stderr, "Missing argument for nodereport core 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 { + fprintf(stderr, "Unrecognised argument for nodereport core switch option: %s\n", args); + } + return 1; // Default is to produce core dumps +} + +unsigned int ProcessNodeReportSignal(const char *args) { +#ifdef _WIN32 + return 0; // no-op on Windows +#else + if (strlen(args) == 0) { + fprintf(stderr, "Missing argument for nodereport signal option\n"); + return 0; + } + // Parse the supplied switch + if (!strncmp(args, "SIGUSR2", sizeof("SIGUSR2") - 1)) { + return SIGUSR2; + } else if (!strncmp(args, "SIGQUIT", sizeof("SIGQUIT") - 1)) { + return SIGQUIT; + } else { + fprintf(stderr, "Unrecognised argument for nodereport signal option: %s\n", args); + } + return SIGUSR2; // Default is SIGUSR2 +#endif +} + +void ProcessNodeReportFileName(const char *args) { + if (strlen(args) == 0) { + fprintf(stderr, "Missing argument for nodereport filename option\n"); + return; + } + if (strlen(args) > NR_MAXNAME) { + fprintf(stderr, "Supplied nodereport filename too long (max 48 characters)\n"); + return; + } + strcpy(report_filename, args); +} + +void ProcessNodeReportDirectory(const char *args) { + if (strlen(args) == 0) { + fprintf(stderr, "Missing argument for nodereport directory option\n"); + return; + } + if (strlen(args) > NR_MAXPATH) { + fprintf(stderr, "Supplied nodereport directory path too long (max 2048 characters)\n"); + return; + } + strcpy(report_directory, args); +} + +unsigned int ProcessNodeReportVerboseSwitch(const char *args) { + if (strlen(args) == 0) { + fprintf(stderr, "Missing argument for nodereport 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 { + fprintf(stderr, "Unrecognised argument for nodereport verbose switch option: %s\n", args); + } + return 0; // Default is verbose mode off +} + +/******************************************************************************* + * Function to save the nodereport module load time value + *******************************************************************************/ +void SetLoadTime() { +#ifdef _WIN32 + GetLocalTime(&loadtime_tm_struct); +#else // UNIX, OSX + struct timeval time_val; + gettimeofday(&time_val, NULL); + localtime_r(&time_val.tv_sec, &loadtime_tm_struct); +#endif +} +/******************************************************************************* + * API to write a NodeReport to file. + * + * Parameters: + * Isolate* isolate + * DumpEvent event + * const char *message + * const char *location + * char *name - in/out - returns the NodeReport filename + ******************************************************************************/ +void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, const char *location, char *name) { + // Recursion check for NodeReport in progress, bail out + if (report_active) return; + report_active = true; + + // Obtain the current time and the pid (platform dependent) +#ifdef _WIN32 + SYSTEMTIME tm_struct; + GetLocalTime(&tm_struct); + DWORD pid = GetCurrentProcessId(); +#else // UNIX, OSX + struct timeval time_val; + struct tm tm_struct; + gettimeofday(&time_val, NULL); + localtime_r(&time_val.tv_sec, &tm_struct); + pid_t pid = getpid(); +#endif + + // Determine the required NodeReport filename. In order of priority: + // 1) supplied on API 2) configured on startup 3) default generated + char filename[48] = ""; + if (name != NULL && strlen(name) > 0) { + // Filename was specified as API parameter, use that + strcpy(filename, name); + } else if (strlen(report_filename) > 0) { + // File name was supplied via start-up option, use that + strcpy(filename, report_filename); + } else { + // Construct the NodeReport filename, with timestamp, pid and sequence number + strcpy(filename, "NodeReport"); + 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 NodeReport file stream for writing. Supports stdout/err, user-specified or (default) generated name + FILE *fp = NULL; + if (!strncmp(filename, "stdout", sizeof("stdout") - 1)) { + fp = stdout; + } else if (!strncmp(filename, "stderr", sizeof("stderr") - 1)) { + fp = stderr; + } else { + // Regular file. Append filename to directory path if one was specified + if (strlen(report_directory) > 0) { + char pathname[NR_MAXPATH + NR_MAXNAME + 1] = ""; + strcpy(pathname, report_directory); +#ifdef _WIN32 + strcat(pathname, "\\"); +#else + strcat(pathname, "/"); +#endif + strncat(pathname, filename, NR_MAXNAME); + fp = fopen(pathname, "w"); + } else { + fp = fopen(filename, "w"); + } + // Check for errors on the file open + if (fp == NULL) { + if (strlen(report_directory) > 0) { + fprintf(stderr, "\nFailed to open Node.js report file: %s directory: %s (errno: %d)\n", filename, report_directory, errno); + } else { + fprintf(stderr, "\nFailed to open Node.js report file: %s (errno: %d)\n", filename, errno); + } + return; + } else { + fprintf(stderr, "\nWriting Node.js report to file: %s\n", filename); + } + } + + // Print NodeReport title and event information + fprintf(fp, "================================================================================\n"); + fprintf(fp, "==== NodeReport ================================================================\n"); + + fprintf(fp, "\nEvent: %s, location: \"%s\"\n", message, location); + fprintf(fp, "Filename: %s\n", filename); + + // Print dump event and module load date/time stamps +#ifdef _WIN32 + fprintf(fp, "Dump event time: %4d/%02d/%02d %02d:%02d:%02d\n", + tm_struct.wYear, tm_struct.wMonth, tm_struct.wDay, + tm_struct.wHour, tm_struct.wMinute, tm_struct.wSecond); + fprintf(fp, "Module load time: %4d/%02d/%02d %02d:%02d:%02d\n", + loadtime_tm_struct.wYear, loadtime_tm_struct.wMonth, loadtime_tm_struct.wDay, + loadtime_tm_struct.wHour, loadtime_tm_struct.wMinute, loadtime_tm_struct.wSecond); +#else // UNIX, OSX + fprintf(fp, "Dump event time: %4d/%02d/%02d %02d:%02d:%02d\n", + 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); + fprintf(fp, "Module load time: %4d/%02d/%02d %02d:%02d:%02d\n", + 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); +#endif + + // Print Node.js and deps component versions + fprintf(fp, "\nNode.js version: %s\n", NODE_VERSION); + fprintf(fp, "(v8: %s, libuv: %s, zlib: %s, ares: %s)\n", + V8::GetVersion(), uv_version_string(), ZLIB_VERSION, ARES_VERSION_STR); + + // Print OS name and level and machine name +#ifdef _WIN32 + fprintf(fp,"\nOS version: Windows "); +#if defined(_MSC_VER) && (_MSC_VER >= 1900) + if (IsWindows1OrGreater()) { + fprintf(fp,"10 "); + } else +#endif + if (IsWindows8OrGreater()) { + fprintf(fp,"8 "); + } else if (IsWindows7OrGreater()) { + fprintf(fp,"7 "); + } else if (IsWindowsXPOrGreater()) { + fprintf(fp,"XP "); + } + if (IsWindowsServer()) { + fprintf(fp,"Server\n"); + } else { + fprintf(fp,"Client\n"); + } + TCHAR infoBuf[256]; + DWORD bufCharCount = 256; + if (GetComputerName(infoBuf, &bufCharCount)) { + fprintf(fp,"Machine name: %s %s\n", infoBuf); + } +#else + struct utsname os_info; + if (uname(&os_info) == 0) { + fprintf(fp,"\nOS version: %s %s %s",os_info.sysname, os_info.release, os_info.version); + fprintf(fp,"\nMachine: %s %s\n", os_info.nodename, os_info.machine); + } +#endif + + // Print native process ID + fprintf(fp, "\nProcess ID: %d\n", pid); + fflush(fp); + +// Print summary JavaScript stack trace + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\n==== JavaScript Stack Trace ====================================================\n\n"); + +#ifdef _WIN32 + switch (event) { + case kFatalError: + // Stack trace on fatal error not supported on Windows + fprintf(fp, "No stack trace available\n"); + break; + default: + // All other events, print the stack using StackTrace::StackTrace() and GetStackSample() APIs + PrintStackFromStackTrace(fp, isolate, event); + break; + } // end switch(event) +#else // Unix, OSX + switch (event) { + case kException: + case kJavaScript: + // Print the stack using Message::PrintCurrentStackTrace() API + Message::PrintCurrentStackTrace(isolate, fp); + break; + case kFatalError: +#if NODE_VERSION_AT_LEAST(6, 0, 0) + if (!strncmp(location,"MarkCompactCollector", sizeof("MarkCompactCollector") - 1)) { + fprintf(fp, "V8 running in GC - no stack trace available\n"); + } else { + Message::PrintCurrentStackTrace(isolate, fp); + } +#else + fprintf(fp, "No stack trace available\n"); +#endif + break; + case kSignal_JS: + case kSignal_UV: + // Print the stack using StackTrace::StackTrace() and GetStackSample() APIs + PrintStackFromStackTrace(fp, isolate, event); + break; + } // end switch(event) +#endif + fflush(fp); + + // Print native stack backtrace + PrintNativeBacktrace(fp); + fflush(fp); + + // Print V8 Heap and Garbage Collector information + PrintGCStatistics(fp, isolate); + fflush(fp); + + // Print OS and current thread resource usage +#ifndef _WIN32 + PrintResourceUsage(fp); + fflush(fp); +#endif + + // Print libuv handle summary (TODO: investigate failure on Windows) +#ifndef _WIN32 + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\n==== Node.js libuv Handle Summary ==============================================\n"); + fprintf(fp,"\n(Flags: R=Ref, A=Active, I=Internal)\n"); + fprintf(fp,"\nFlags Type Address\n"); + uv_print_all_handles(NULL, fp); + fflush(fp); +#endif + + // Print operating system information + PrintSystemInformation(fp, isolate); + + fprintf(fp, "\n================================================================================\n"); + fflush(fp); + fclose(fp); + + fprintf(stderr, "Node.js report completed\n"); + if (name != NULL) { + strcpy(name, filename); // return the NodeReport file name + } + report_active = false; +} + +/******************************************************************************* + * Function to print stack using StackTrace::StackTrace() and GetStackSample() + * + ******************************************************************************/ +static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, + DumpEvent event) { + v8::RegisterState state; + v8::SampleInfo info; + void* samples[255]; + + // Initialise the register state + state.pc = NULL; + state.fp = &state; + state.sp = &state; + + isolate->GetStackSample(state, samples, arraysize(samples), &info); + if (static_cast(info.vm_state) < arraysize(v8_states)) { + fprintf(fp, "JavaScript VM state: %s\n\n", v8_states[info.vm_state]); + } else { + fprintf(fp, "JavaScript VM state: \n\n"); + } + if (event == kSignal_UV) { + fprintf(fp, "Signal received when event loop idle, no stack trace available\n"); + return; + } + Local stack = StackTrace::CurrentStackTrace(isolate, 255, StackTrace::kDetailed); + if (stack.IsEmpty()) { + fprintf(fp, "\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 ((size_t)i < info.frames_count) { + PrintStackFrame(fp, isolate, stack->GetFrame(i), i, samples[i]); + } else { + PrintStackFrame(fp, isolate, stack->GetFrame(i), i, NULL); + } + } +} + +/******************************************************************************* + * Function to print a JavaScript stack frame from a V8 StackFrame object + * + ******************************************************************************/ +static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, int i, void *pc) { + Nan::Utf8String fn_name_s(frame->GetFunctionName()); + Nan::Utf8String script_name(frame->GetScriptName()); + const int line_number = frame->GetLineNumber(); + const int column = frame->GetColumn(); + + // First print the frame index and the instruction address +#ifdef _WIN32 + fprintf(fp, "%2d: [pc=0x%p] ", i, pc); +#else + fprintf(fp, "%2d: [pc=%p] ", i, pc); +#endif + + // Now print the JavaScript function name and source information + 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); + } + return; + } + + if (fn_name_s.length() == 0) { + fprintf(fp, "%s:%i:%i\n", *script_name, line_number, column); + } else { + if (frame->IsConstructor()) { + fprintf(fp, "%s [constructor] (%s:%i:%i)\n", *fn_name_s, *script_name, line_number, column); + } else { + fprintf(fp, "%s (%s:%i:%i)\n", *fn_name_s, *script_name, line_number, column); + } + } +} + + +#ifdef _WIN32 +/******************************************************************************* + * Function to print a native stack backtrace + * + ******************************************************************************/ +void PrintNativeBacktrace(FILE* fp) { + void *frames[64]; + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\n==== Native Stack Trace ========================================================\n\n"); + + HANDLE hProcess = GetCurrentProcess(); + SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); + SymInitialize(hProcess, NULL, TRUE); + + WORD numberOfFrames = CaptureStackBackTrace(2, 64, frames, NULL); + + // Walk the frames printing symbolic information if available + for (int i = 0; i < numberOfFrames; i++) { + DWORD64 dwOffset64 = 0; + DWORD64 dwAddress = (DWORD64)(frames[i]); + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)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(IMAGEHLP_LINE64); + if (SymGetLineFromAddr64(hProcess, dwAddress, &dwOffset, &line)) { + fprintf(fp,"%2d: [pc=0x%p] %s [+%d] in %s: line: %lu\n", i, pSymbol->Address, pSymbol->Name, dwOffset, line.FileName, line.LineNumber); + } else { + // SymGetLineFromAddr64() failed, just print the address and symbol + if (dwOffset64 <= 32) { // sanity check + fprintf(fp,"%2d: [pc=0x%p] %s [+%d]\n", i, pSymbol->Address, pSymbol->Name, dwOffset64); + } else { + fprintf(fp,"%2d: [pc=0x%p]\n", i, pSymbol->Address); + } + } + } else { // SymFromAddr() failed, just print the address + fprintf(fp,"%2d: [pc=0x%p]\n", i, pSymbol->Address); + } + } +} +#else +/******************************************************************************* + * Function to print a native stack backtrace - Linux/OSX + * + ******************************************************************************/ +void PrintNativeBacktrace(FILE* fp) { + void* frames[256]; + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\n==== Native Stack Trace ========================================================\n\n"); + + // Get the native backtrace (array of instruction addresses) + const int size = backtrace(frames, arraysize(frames)); + if (size <= 0) { + fprintf(fp,"Native backtrace failed, error %d\n", size); + return; + } else if (size <=2) { + fprintf(fp,"No frames to print\n"); + return; + } + + // Print the native frames, omitting the top 3 frames as they are in nodereport code + // backtrace_symbols_fd(frames, size, fileno(fp)); + for (int i = 2; i < size; i++) { + // print frame index and instruction address + fprintf(fp, "%2d: [pc=%p] ", i-2, frames[i]); + // 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)) { + fprintf(fp, "%s", demangled); // print demangled symbol name + free(demangled); + } else { + fprintf(fp, "%s", info.dli_sname); // just print the symbol name + } + } + if (info.dli_fname != nullptr) { + fprintf(fp, " [%s]", info.dli_fname); // print shared object name + } + } + fprintf(fp, "\n"); + } +} +#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(FILE *fp, Isolate* isolate) { + HeapStatistics v8_heap_stats; + isolate->GetHeapStatistics(&v8_heap_stats); + + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\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); + fprintf(fp, "\nHeap space name: %s", v8_heap_space_stats.space_name()); + fprintf(fp, "\n Memory size: "); + WriteInteger(fp, v8_heap_space_stats.space_size()); + fprintf(fp, " bytes, committed memory: "); + WriteInteger(fp, v8_heap_space_stats.physical_space_size()); + fprintf(fp, " bytes\n Capacity: "); + WriteInteger(fp, v8_heap_space_stats.space_used_size() + + v8_heap_space_stats.space_available_size()); + fprintf(fp, " bytes, used: "); + WriteInteger(fp, v8_heap_space_stats.space_used_size()); + fprintf(fp, " bytes, available: "); + WriteInteger(fp, v8_heap_space_stats.space_available_size()); + fprintf(fp, " bytes"); + } + + fprintf(fp, "\n\nTotal heap memory size: "); + WriteInteger(fp, v8_heap_stats.total_heap_size()); + fprintf(fp, " bytes\nTotal heap committed memory: "); + WriteInteger(fp, v8_heap_stats.total_physical_size()); + fprintf(fp, " bytes\nTotal used heap memory: "); + WriteInteger(fp, v8_heap_stats.used_heap_size()); + fprintf(fp, " bytes\nTotal available heap memory: "); + WriteInteger(fp, v8_heap_stats.total_available_size()); + fprintf(fp, " bytes\n\nHeap memory limit: "); + WriteInteger(fp, v8_heap_stats.heap_size_limit()); + fprintf(fp, "\n"); +} + +#ifndef _WIN32 +/******************************************************************************* + * Function to print resource usage (Linux/OSX only). + * + ******************************************************************************/ +static void PrintResourceUsage(FILE *fp) { + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\n==== Resource Usage ============================================================\n"); + + // Process and current thread usage statistics + struct rusage stats; + fprintf(fp, "\nProcess total resource usage:"); + if (getrusage(RUSAGE_SELF, &stats) == 0) { + fprintf(fp,"\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + fprintf(fp,"\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + fprintf(fp,"\n Maximum resident set size: "); + WriteInteger(fp,stats.ru_maxrss * 1024); + fprintf(fp," bytes\n Page faults: %ld (I/O required) %ld (no I/O required)", stats.ru_majflt, stats.ru_minflt); + fprintf(fp,"\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); + } + + fprintf(fp, "\n\nEvent loop thread resource usage:"); + if (getrusage(RUSAGE_THREAD, &stats) == 0) { + fprintf(fp,"\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + fprintf(fp,"\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + fprintf(fp,"\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); + } + fprintf(fp, "\n"); +} +#endif + +/******************************************************************************* + * Function to print operating system information. + * + ******************************************************************************/ +static void PrintSystemInformation(FILE *fp, Isolate* isolate) { + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\n==== System Information ========================================================\n"); + +#ifdef _WIN32 + fprintf(fp, "\nEnvironment variables\n"); + LPTSTR lpszVariable; + LPTCH lpvEnv; + + // Get pointer to the environment block + lpvEnv = GetEnvironmentStrings(); + if (lpvEnv != NULL) { + // Variable strings are separated by NULL byte, and the block is terminated by a NULL byte. + lpszVariable = (LPTSTR) lpvEnv; + while (*lpszVariable) { + fprintf(fp, " %s\n", lpszVariable); + lpszVariable += lstrlen(lpszVariable) + 1; + } + FreeEnvironmentStrings(lpvEnv); + } +#else + fprintf(fp, "\nEnvironment variables\n"); + int index = 1; + char *env_var = *environ; + + while (env_var != NULL) { + fprintf(fp, " %s\n", env_var); + env_var = *(environ + index++); + } + +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}, + {"max locked memory (bytes) ", RLIMIT_MEMLOCK}, + {"max memory size (kbytes) ", RLIMIT_RSS}, + {"open files ", RLIMIT_NOFILE}, + {"stack size (bytes) ", RLIMIT_STACK}, + {"cpu time (seconds) ", RLIMIT_CPU}, + {"max user processes ", RLIMIT_NPROC}, + {"virtual memory (kbytes) ", RLIMIT_AS} +}; + + fprintf(fp, "\nResource limits soft limit hard limit\n"); + struct rlimit limit; + + for (size_t i = 0; i < arraysize(rlimit_strings); i++) { + if (getrlimit(rlimit_strings[i].id, &limit) == 0) { + fprintf(fp, " %s ", rlimit_strings[i].description); + if (limit.rlim_cur == RLIM_INFINITY) { + fprintf(fp, " unlimited"); + } else { + fprintf(fp, "%16" PRIu64, limit.rlim_cur); + } + if (limit.rlim_max == RLIM_INFINITY) { + fprintf(fp, " unlimited\n"); + } else { + fprintf(fp, "%16" PRIu64 "\n", limit.rlim_max); + } + } + } +#endif +} + +/******************************************************************************* + * Utility function to print out integer values with commas for readability. + * + ******************************************************************************/ +static void WriteInteger(FILE *fp, size_t value) { + int thousandsStack[8]; // Sufficient for max 64-bit number + int stackTop = 0; + int i; + 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)) { + fprintf(fp, "%u", thousandsStack[i]); + } else { + fprintf(fp, "%03u", thousandsStack[i]); + } + if (i > 0) { + fprintf(fp, ","); + } + } +} + diff --git a/src/node_report.h b/src/node_report.h new file mode 100644 index 0000000..f7c7333 --- /dev/null +++ b/src/node_report.h @@ -0,0 +1,38 @@ +#ifndef SRC_NODE_REPORT_H_ +#define SRC_NODE_REPORT_H_ + +#include "nan.h" + +using v8::Isolate; +using v8::Local; +using v8::Message; +using v8::Function; +using v8::Object; +using v8::Number; +using v8::String; +using v8::Value; + +// Bit-flags for NodeReport trigger options +#define NR_EXCEPTION 0x01 +#define NR_FATALERROR 0x02 +#define NR_SIGNAL 0x04 +#define NR_JSAPICALL 0x08 + +// Maximum file and path name lengths +#define NR_MAXNAME 48 +#define NR_MAXPATH 2048 + +enum DumpEvent {kException, kFatalError, kSignal_JS, kSignal_UV, kJavaScript}; + +void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, const char *location, char* name); + +unsigned int ProcessNodeReportEvents(const char *args); +unsigned int ProcessNodeReportCoreSwitch(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(); + +#endif // SRC_NODE_REPORT_H_ diff --git a/test/autorun.js b/test/autorun.js new file mode 100644 index 0000000..dcc06fd --- /dev/null +++ b/test/autorun.js @@ -0,0 +1,122 @@ +// Testcase for nodereport. Triggers and validates NodeReport files +'use strict'; + +const assert = require('assert'); +const spawn = require('child_process').spawn; +const readline = require('readline'); +const fs = require('fs'); +const path = require('path'); + +var results; +if (process.platform === 'win32') { + results = ['fail','fail','fail']; +} else { + results = ['fail','fail','fail','fail']; +} + +//TODO: switch off core dumps (using NODEREPORT_COREDUMP env var) +const stdio_log = fs.openSync('./autorun.log', 'w'); + +// Test #1: Run child process to call API to generate a NodeReport +console.log('autorun.js: running child process #1 to produce NodeReport on API call'); +var child1 = spawn(process.execPath, [path.resolve(__dirname, 'test_1.js')], {stdio: ['pipe', stdio_log, stdio_log]}); +child1.on('exit', function(code, signal) { + assert.ok(code == 0); + // Locate and validate the NodeReport + var report = locate(child1.pid); + if (report) { + validate(report, 1); + fs.unlink(report); // delete the file when we are done + } +}); + +// Test #2: Run child process to produce unhandled exception and generate a NodeReport +console.log('autorun.js: running child process #2 to produce NodeReport on exception'); +var child2 = spawn(process.execPath, [path.resolve(__dirname, 'test_2.js')], {stdio: ['pipe', stdio_log, stdio_log]}); +child2.on('exit', function(code, signal) { + assert.ok(code !== 0); + // Locate and validate the NodeReport + var report = locate(child2.pid); + if (report) { + validate(report, 2); + fs.unlink(report); // delete the file when we are done + } +}); + +// Test #3: Run child process to produce fatal error (heap OOM) and generate a NodeReport +console.log('autorun.js: running child process #3 to produce NodeReport on fatal error'); +var child3 = spawn(process.execPath, ['--max-old-space-size=20', path.resolve(__dirname, 'test_3.js')], {stdio: ['pipe', stdio_log, stdio_log]}); +child3.on('exit', function(code, signal) { + assert.ok(code !== 0); + // Locate and validate the NodeReport + var report = locate(child3.pid); + if (report) { + validate(report, 3); + fs.unlink(report); // delete the file when we are done + } +}); + +// Test #4: Run child process to loop, then send SIGUSR2 to generate a NodeReport +if (process.platform === 'win32') { + // Not supported on Windows +} else { + console.log('autorun.js: running child process #4 to produce NodeReport on SIGUSR2'); + var child4 = spawn(process.execPath, [path.resolve(__dirname, 'test_4.js')], {stdio: ['pipe', stdio_log, stdio_log]}); + setTimeout(function() { + child4.kill('SIGUSR2'); + setTimeout(function() { + child4.kill('SIGTERM'); + }, 1000); + }, 1000); + child4.on('exit', function(code, signal) { + // Locate and validate the NodeReport + var report = locate(child4.pid); + if (report) { + validate(report, 4); + fs.unlink(report); // delete the file when we are done + } + }); +} + +// Print out results of all the tests on exit +process.on('exit', function() { + console.log('autorun.js: test results: '); + for (var i = 0; i < results.length; i++) { + console.log('\ttest', i+1, ': ', results[i]); + } +}); + +// Utility function - locate a NodeReport produced by a process with the given PID +function locate(pid) { + var files = fs.readdirSync("."); + for (var i = 0; i < files.length; i++) { + if (files[i].substring(0, 10) == 'NodeReport' && files[i].indexOf(pid) != -1) { + console.log('autorun.js: located NodeReport: ', files[i]); + return files[i]; + } + } + return; +} + +// Utility function - validate a NodeReport (check all the sections are there) - and save result +function validate(report, index) { + var validationCount = 0; + const reader = readline.createInterface({ + input: fs.createReadStream(report) + }); + reader.on('line', (line) => { + //console.log('Line from file:', line); + if ((line.indexOf('==== NodeReport') > -1) || + (line.indexOf('==== JavaScript Stack Trace') > -1) || + (line.indexOf('==== JavaScript Heap') > -1) || + (line.indexOf('==== System Information') > -1)) { + validationCount++; // found expected section in NodeReport + } + }); + reader.on('close', () => { + if (validationCount == 4) { + results[index-1] = 'pass'; + } + }); +} + diff --git a/test/test_1.js b/test/test_1.js new file mode 100644 index 0000000..2a9a468 --- /dev/null +++ b/test/test_1.js @@ -0,0 +1,10 @@ +// NodeReport API example +var nodereport = require('nodereport'); + +console.log('api_call.js: triggering a NodeReport via API call...'); + +function myReport() { + nodereport.triggerReport(); +} + +myReport(); diff --git a/test/test_2.js b/test/test_2.js new file mode 100644 index 0000000..24340f1 --- /dev/null +++ b/test/test_2.js @@ -0,0 +1,16 @@ +// Testcase to produce an uncaught exception +require('nodereport'); + +console.log('exception.js: throwing an uncaught user exception....'); + +function myException(request, response) { + throw new UserException('*** exception.js: testcase exception thrown from myException()'); +} + +function UserException(message) { + this.message = message; + this.name = "UserException"; +} + +myException(); + diff --git a/test/test_3.js b/test/test_3.js new file mode 100644 index 0000000..ec68eae --- /dev/null +++ b/test/test_3.js @@ -0,0 +1,16 @@ +// Testcase to produce a fatal error (javascript heap OOM) +require('nodereport'); + +console.log('fatalerror.js: allocating excessive javascript heap memory....'); +var list = []; +while (true) { + var record = new MyRecord(); + list.push(record); +} + + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} diff --git a/test/test_4.js b/test/test_4.js new file mode 100644 index 0000000..772efe3 --- /dev/null +++ b/test/test_4.js @@ -0,0 +1,28 @@ +// Testcase to loop in Javascript code +require('nodereport'); + +console.log('loop.js: going into loop now.... use kill -USR2 to trigger NodeReport'); + +function myLoop() { + var list = []; + for (var i=0; i<10000000000; i++) { + for (var j=0; i<1000; i++) { + list.push(new MyRecord()); + } + for (var j=0; i<1000; i++) { + list[j].id += 1; + list[j].account += 2; + } + for (var j=0; i<1000; i++) { + list.pop(); + } + } +} + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} + +myLoop(); From 410958ec302b7a102557076380ecc8f1f753962f Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Mon, 5 Sep 2016 15:34:51 +0100 Subject: [PATCH 02/24] Fix for RUSAGE_THREAD not supported on OSX --- src/node_report.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node_report.cc b/src/node_report.cc index c55397e..3782890 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -679,13 +679,14 @@ static void PrintResourceUsage(FILE *fp) { fprintf(fp," bytes\n Page faults: %ld (I/O required) %ld (no I/O required)", stats.ru_majflt, stats.ru_minflt); fprintf(fp,"\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); } - +#ifdef RUSAGE_THREAD fprintf(fp, "\n\nEvent loop thread resource usage:"); if (getrusage(RUSAGE_THREAD, &stats) == 0) { fprintf(fp,"\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); fprintf(fp,"\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); fprintf(fp,"\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); } +#endif fprintf(fp, "\n"); } #endif From 3af97964fb8151e4b786354384174b9ecb4f24d8 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Mon, 5 Sep 2016 16:35:06 +0100 Subject: [PATCH 03/24] Fix compiler warnings for timeval tv_usec on OSX --- src/node_report.cc | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/node_report.cc b/src/node_report.cc index 3782890..eccff73 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -672,8 +672,13 @@ static void PrintResourceUsage(FILE *fp) { struct rusage stats; fprintf(fp, "\nProcess total resource usage:"); if (getrusage(RUSAGE_SELF, &stats) == 0) { +#ifdef __APPLE__ + fprintf(fp,"\n User mode CPU: %ld.%06d secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + fprintf(fp,"\n Kernel mode CPU: %ld.%06d secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); +#else fprintf(fp,"\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); fprintf(fp,"\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); +#endif fprintf(fp,"\n Maximum resident set size: "); WriteInteger(fp,stats.ru_maxrss * 1024); fprintf(fp," bytes\n Page faults: %ld (I/O required) %ld (no I/O required)", stats.ru_majflt, stats.ru_minflt); From fd87c9652cb47b961353b4dfabe27f9f70df1974 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Tue, 6 Sep 2016 10:00:13 +0100 Subject: [PATCH 04/24] Fix whitespace and CRLF line endings --- README.md | 16 +- src/module.cc | 676 +++++++++++++++++++++++++------------------------- 2 files changed, 346 insertions(+), 346 deletions(-) diff --git a/README.md b/README.md index 1312700..8c61df8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ and calls to a Javascript API. Usage: npm install nodejs/nodereport - + var nodereport = require('nodereport'); By default, this will trigger a NodeReport to be written to the current @@ -23,9 +23,9 @@ returned. The default filename includes the date, time, PID and a sequence number. Alternatively a filename can be specified on the API call. nodereport.triggerReport(); - + var filename = nodereport.triggerReport(); - + nodereport.triggerReport("myReportName"); Content of the NodeReport in the initial implementation consists of a @@ -43,19 +43,19 @@ API calls: export NODEREPORT_EVENTS=exception+fatalerror+signal nodereport.setEvents("exception+fatalerror+signal"); - + export NODEREPORT_COREDUMP=yes/no nodereport.setCoreDump("yes/no"); - + export NODEREPORT_SIGNAL=SIGUSR2/SIGQUIT nodereport.setSignal("SIGUSR2/SIGQUIT"); - + export NODEREPORT_FILENAME=stdout/stderr/ nodereport.setFileName("stdout/stderr/"); - + export NODEREPORT_DIRECTORY= nodereport.setDirectory(""); - + export NODEREPORT_VERBOSE=yes/no nodereport.setVerbose("yes/no"); diff --git a/src/module.cc b/src/module.cc index 1d5afa4..ed835e9 100644 --- a/src/module.cc +++ b/src/module.cc @@ -1,338 +1,338 @@ -#include -//#include -#include "node_report.h" - -// Internal/static function declarations -static void OnFatalError(const char* location, const char* message); -bool OnUncaughtException(v8::Isolate* isolate); -#ifndef _WIN32 -static void SignalDumpAsyncCallback(uv_async_t* handle); -inline void* ReportSignalThreadMain(void* unused); -static int StartWatchdogThread(void *(*thread_main) (void* unused)); -static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler); -static void SignalDump(int signo); -#endif - -// Default nodereport option settings -static unsigned int nodereport_events = NR_EXCEPTION + NR_FATALERROR + NR_SIGNAL + NR_JSAPICALL; -static unsigned int nodereport_core = 1; -static unsigned int nodereport_verbose = 0; - -#ifdef _WIN32 -static unsigned int nodereport_signal = 0; // no-op on Windows -#else // signal support - on Unix/OSX only -static unsigned int nodereport_signal = SIGUSR2; // default signal is SIGUSR2 -static int report_signal = 0; -static uv_sem_t report_semaphore; -static uv_async_t nodereport_trigger_async; -static uv_mutex_t node_isolate_mutex; -#endif - -static v8::Isolate* node_isolate; - -/******************************************************************************* - * External JavaScript API for triggering a NodeReport - * - ******************************************************************************/ -NAN_METHOD(TriggerReport) { - Nan::HandleScope scope; - v8::Isolate* isolate = Isolate::GetCurrent(); - char filename[48] = ""; - - if (info[0]->IsString()) { - // Filename parameter supplied - Nan::Utf8String filename_parameter(info[0]->ToString()); - if (filename_parameter.length() < 48) { - strcpy(filename, *filename_parameter); - } else { - Nan::ThrowSyntaxError("TriggerReport: filename parameter is too long (max 48 characters)"); - } - } - if (info[0]->IsFunction()) { - // Callback parameter supplied - Nan::Callback callback(info[0].As()); - // Creates a new Object on the V8 heap - Local obj = Object::New(isolate); - obj->Set(String::NewFromUtf8(isolate, "number"), Number::New(isolate, 54)); - Local argv[1]; - argv[0] = obj; - // Invoke the callback, passing the object in argv - callback.Call(1, argv); - } - - TriggerNodeReport(isolate, kJavaScript, "JavaScript API", "TriggerReport (nodereport/src/module.cc)", filename); - // Return value is the NodeReport filename - info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked()); -} - -/******************************************************************************* - * External JavaScript APIs for nodereport configuration - * - ******************************************************************************/ -NAN_METHOD(SetEvents) { - Nan::Utf8String parameter(info[0]->ToString()); - v8::Isolate* isolate = node_isolate; - unsigned int previous_events = nodereport_events; // save previous settings - nodereport_events = ProcessNodeReportEvents(*parameter); - - // If NodeReport newly requested for fatalerror, set up the V8 call-back - if ((nodereport_events & NR_FATALERROR) && !(previous_events & NR_FATALERROR)) { - isolate->SetFatalErrorHandler(OnFatalError); - } - - // If NodeReport newly requested for exceptions, tell V8 to capture stack trace and set up the callback - if ((nodereport_events & NR_EXCEPTION) && !(previous_events & NR_EXCEPTION)) { - isolate->SetCaptureStackTraceForUncaughtExceptions(true, 32, v8::StackTrace::kDetailed); - // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set - v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); - isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); - } - -#ifndef _WIN32 - // If NodeReport newly requested on external user signal set up watchdog thread and callbacks - if ((nodereport_events & NR_SIGNAL) && !(previous_events & NR_SIGNAL)) { - uv_sem_init(&report_semaphore, 0); - if (StartWatchdogThread(ReportSignalThreadMain) == 0) { - uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); - uv_unref(reinterpret_cast(&nodereport_trigger_async)); - RegisterSignalHandler(nodereport_signal, SignalDump, false); - } - } -#endif -} -NAN_METHOD(SetCoreDump) { - Nan::Utf8String parameter(info[0]->ToString()); - nodereport_events = ProcessNodeReportCoreSwitch(*parameter); -} -NAN_METHOD(SetSignal) { - Nan::Utf8String parameter(info[0]->ToString()); - nodereport_events = ProcessNodeReportSignal(*parameter); -} -NAN_METHOD(SetFileName) { - Nan::Utf8String parameter(info[0]->ToString()); - ProcessNodeReportFileName(*parameter); -} -NAN_METHOD(SetDirectory) { - Nan::Utf8String parameter(info[0]->ToString()); - ProcessNodeReportDirectory(*parameter); -} -NAN_METHOD(SetVerbose) { - Nan::Utf8String parameter(info[0]->ToString()); - nodereport_verbose = ProcessNodeReportVerboseSwitch(*parameter); -} - -/******************************************************************************* - * Callbacks for triggering NodeReport on failure events (as configured) - * - fatal error - * - uncaught exception - * - signal - ******************************************************************************/ -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 NodeReport if requested - if (nodereport_events & NR_FATALERROR) { - TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, NULL); - } - fflush(stderr); - if (nodereport_core) { - raise(SIGABRT); // core dump requested - } else { - exit(0); // no core dump requested - } -} - -bool OnUncaughtException(v8::Isolate* isolate) { - // Trigger NodeReport if required - if (nodereport_events & NR_EXCEPTION) { - TriggerNodeReport(Isolate::GetCurrent(), kException, "exception", "OnUncaughtException (nodereport/src/module.cc)", NULL); - } - if (nodereport_core) { - return true; - } else { - return false; - } -} - -#ifndef _WIN32 -static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { - if (report_signal != 0) { - fprintf(stderr,"SignalDumpInterruptCallback - handling signal\n"); - if (nodereport_events & NR_SIGNAL) { - if (nodereport_verbose) { - fprintf(stderr,"SignalDumpInterruptCallback - triggering NodeReport\n"); - } - TriggerNodeReport(Isolate::GetCurrent(), kSignal_JS, - node::signo_string(*(static_cast(data))), - "node::SignalDumpInterruptCallback()", NULL); - } - report_signal = 0; - } -} -static void SignalDumpAsyncCallback(uv_async_t* handle) { - if (report_signal != 0) { - fprintf(stderr,"SignalDumpAsyncCallback - handling signal\n"); - if (nodereport_events & NR_SIGNAL) { - if (nodereport_verbose) { - fprintf(stderr,"SignalDumpAsyncCallback - triggering NodeReport\n"); - } - size_t signo_data = reinterpret_cast(handle->data); - TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV, - node::signo_string(static_cast(signo_data)), - "node::SignalDumpAsyncCallback()", NULL); - } - report_signal = 0; - } -} - -static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler = false) { - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = handler; - sa.sa_flags = reset_handler ? SA_RESETHAND : 0; - sigfillset(&sa.sa_mask); - sigaction(signal, &sa, nullptr); -} - -// Raw signal handler for triggering a NodeReport - runs on an arbitrary thread -static void SignalDump(int signo) { - // Check atomic for NodeReport 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 - } -} - -// Watchdog thread implementation for signal-triggered NodeReport -inline void* ReportSignalThreadMain(void* unused) { - for (;;) { - uv_sem_wait(&report_semaphore); - if (nodereport_verbose) { - fprintf(stderr, "Signal %s received by nodereport module\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, &report_signal); - // Event loop may be idle, so also request an async callback - size_t signo_data = static_cast(report_signal); - nodereport_trigger_async.data = reinterpret_cast(signo_data); - uv_async_send(&nodereport_trigger_async); - } - uv_mutex_unlock(&node_isolate_mutex); - } - return nullptr; -} - -static int StartWatchdogThread(void *(*thread_main) (void* unused)) { - pthread_attr_t attr; - pthread_attr_init(&attr); - - pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); - 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, "nodereport: pthread_create: %s\n", strerror(err)); - fflush(stderr); - return -err; - } - return 0; -} -#endif - - -/******************************************************************************* - * Native module initializer function, called when the module is require'd - * - ******************************************************************************/ -void Initialize(v8::Local exports) { - v8::Isolate* isolate = Isolate::GetCurrent(); - node_isolate = isolate; - - SetLoadTime(); - - const char* trigger_events = getenv("NODEREPORT_EVENTS"); - if (trigger_events != NULL) { - nodereport_events = ProcessNodeReportEvents(trigger_events); - } - const char* core_dump_switch = getenv("NODEREPORT_COREDUMP"); - if (core_dump_switch != NULL) { - nodereport_core = ProcessNodeReportCoreSwitch(core_dump_switch); - } - const char* trigger_signal = getenv("NODEREPORT_SIGNAL"); - if (trigger_signal != NULL) { - nodereport_signal = ProcessNodeReportSignal(trigger_signal); - } - const char* report_name = getenv("NODEREPORT_FILENAME"); - if (report_name != NULL) { - ProcessNodeReportFileName(report_name); - } - const char* directory_name = getenv("NODEREPORT_DIRECTORY"); - if (directory_name != NULL) { - ProcessNodeReportDirectory(directory_name); - } - const char* verbose_switch = getenv("NODEREPORT_VERBOSE"); - if (verbose_switch != NULL) { - nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); - } - - // If NodeReport requested for fatalerror, set up the V8 call-back - if (nodereport_events & NR_FATALERROR) { - isolate->SetFatalErrorHandler(OnFatalError); - } - - // If NodeReport requested for exceptions, tell V8 to capture stack trace and set up the callback - if (nodereport_events & NR_EXCEPTION) { - isolate->SetCaptureStackTraceForUncaughtExceptions(true, 32, v8::StackTrace::kDetailed); - // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set - v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); - isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); - } - -#ifndef _WIN32 - // If NodeReport requested on external user signal set up watchdog thread and callbacks - if (nodereport_events & NR_SIGNAL) { - uv_sem_init(&report_semaphore, 0); - if (StartWatchdogThread(ReportSignalThreadMain) == 0) { - uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); - uv_unref(reinterpret_cast(&nodereport_trigger_async)); - RegisterSignalHandler(nodereport_signal, SignalDump, false); - } - } -#endif - - exports->Set(Nan::New("triggerReport").ToLocalChecked(), - Nan::New(TriggerReport)->GetFunction()); - exports->Set(Nan::New("setEvents").ToLocalChecked(), - Nan::New(SetEvents)->GetFunction()); - exports->Set(Nan::New("setCoreDump").ToLocalChecked(), - Nan::New(SetCoreDump)->GetFunction()); - exports->Set(Nan::New("setSignal").ToLocalChecked(), - Nan::New(SetSignal)->GetFunction()); - exports->Set(Nan::New("setFileName").ToLocalChecked(), - Nan::New(SetFileName)->GetFunction()); - exports->Set(Nan::New("setDirectory").ToLocalChecked(), - Nan::New(SetDirectory)->GetFunction()); - exports->Set(Nan::New("setVerbose").ToLocalChecked(), - Nan::New(SetVerbose)->GetFunction()); - - if (nodereport_verbose) { -#ifdef _WIN32 - fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x\n", - nodereport_events, nodereport_core); -#else - fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x signal flag: %#x\n", - nodereport_events, nodereport_core, nodereport_signal); -#endif - } -} - -NODE_MODULE(nodereport, Initialize) +#include +//#include +#include "node_report.h" + +// Internal/static function declarations +static void OnFatalError(const char* location, const char* message); +bool OnUncaughtException(v8::Isolate* isolate); +#ifndef _WIN32 +static void SignalDumpAsyncCallback(uv_async_t* handle); +inline void* ReportSignalThreadMain(void* unused); +static int StartWatchdogThread(void *(*thread_main) (void* unused)); +static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler); +static void SignalDump(int signo); +#endif + +// Default nodereport option settings +static unsigned int nodereport_events = NR_EXCEPTION + NR_FATALERROR + NR_SIGNAL + NR_JSAPICALL; +static unsigned int nodereport_core = 1; +static unsigned int nodereport_verbose = 0; + +#ifdef _WIN32 +static unsigned int nodereport_signal = 0; // no-op on Windows +#else // signal support - on Unix/OSX only +static unsigned int nodereport_signal = SIGUSR2; // default signal is SIGUSR2 +static int report_signal = 0; +static uv_sem_t report_semaphore; +static uv_async_t nodereport_trigger_async; +static uv_mutex_t node_isolate_mutex; +#endif + +static v8::Isolate* node_isolate; + +/******************************************************************************* + * External JavaScript API for triggering a NodeReport + * + ******************************************************************************/ +NAN_METHOD(TriggerReport) { + Nan::HandleScope scope; + v8::Isolate* isolate = Isolate::GetCurrent(); + char filename[48] = ""; + + if (info[0]->IsString()) { + // Filename parameter supplied + Nan::Utf8String filename_parameter(info[0]->ToString()); + if (filename_parameter.length() < 48) { + strcpy(filename, *filename_parameter); + } else { + Nan::ThrowSyntaxError("TriggerReport: filename parameter is too long (max 48 characters)"); + } + } + if (info[0]->IsFunction()) { + // Callback parameter supplied + Nan::Callback callback(info[0].As()); + // Creates a new Object on the V8 heap + Local obj = Object::New(isolate); + obj->Set(String::NewFromUtf8(isolate, "number"), Number::New(isolate, 54)); + Local argv[1]; + argv[0] = obj; + // Invoke the callback, passing the object in argv + callback.Call(1, argv); + } + + TriggerNodeReport(isolate, kJavaScript, "JavaScript API", "TriggerReport (nodereport/src/module.cc)", filename); + // Return value is the NodeReport filename + info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked()); +} + +/******************************************************************************* + * External JavaScript APIs for nodereport configuration + * + ******************************************************************************/ +NAN_METHOD(SetEvents) { + Nan::Utf8String parameter(info[0]->ToString()); + v8::Isolate* isolate = node_isolate; + unsigned int previous_events = nodereport_events; // save previous settings + nodereport_events = ProcessNodeReportEvents(*parameter); + + // If NodeReport newly requested for fatalerror, set up the V8 call-back + if ((nodereport_events & NR_FATALERROR) && !(previous_events & NR_FATALERROR)) { + isolate->SetFatalErrorHandler(OnFatalError); + } + + // If NodeReport newly requested for exceptions, tell V8 to capture stack trace and set up the callback + if ((nodereport_events & NR_EXCEPTION) && !(previous_events & NR_EXCEPTION)) { + isolate->SetCaptureStackTraceForUncaughtExceptions(true, 32, v8::StackTrace::kDetailed); + // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set + v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); + isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); + } + +#ifndef _WIN32 + // If NodeReport newly requested on external user signal set up watchdog thread and callbacks + if ((nodereport_events & NR_SIGNAL) && !(previous_events & NR_SIGNAL)) { + uv_sem_init(&report_semaphore, 0); + if (StartWatchdogThread(ReportSignalThreadMain) == 0) { + uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); + uv_unref(reinterpret_cast(&nodereport_trigger_async)); + RegisterSignalHandler(nodereport_signal, SignalDump, false); + } + } +#endif +} +NAN_METHOD(SetCoreDump) { + Nan::Utf8String parameter(info[0]->ToString()); + nodereport_events = ProcessNodeReportCoreSwitch(*parameter); +} +NAN_METHOD(SetSignal) { + Nan::Utf8String parameter(info[0]->ToString()); + nodereport_events = ProcessNodeReportSignal(*parameter); +} +NAN_METHOD(SetFileName) { + Nan::Utf8String parameter(info[0]->ToString()); + ProcessNodeReportFileName(*parameter); +} +NAN_METHOD(SetDirectory) { + Nan::Utf8String parameter(info[0]->ToString()); + ProcessNodeReportDirectory(*parameter); +} +NAN_METHOD(SetVerbose) { + Nan::Utf8String parameter(info[0]->ToString()); + nodereport_verbose = ProcessNodeReportVerboseSwitch(*parameter); +} + +/******************************************************************************* + * Callbacks for triggering NodeReport on failure events (as configured) + * - fatal error + * - uncaught exception + * - signal + ******************************************************************************/ +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 NodeReport if requested + if (nodereport_events & NR_FATALERROR) { + TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, NULL); + } + fflush(stderr); + if (nodereport_core) { + raise(SIGABRT); // core dump requested + } else { + exit(0); // no core dump requested + } +} + +bool OnUncaughtException(v8::Isolate* isolate) { + // Trigger NodeReport if required + if (nodereport_events & NR_EXCEPTION) { + TriggerNodeReport(Isolate::GetCurrent(), kException, "exception", "OnUncaughtException (nodereport/src/module.cc)", NULL); + } + if (nodereport_core) { + return true; + } else { + return false; + } +} + +#ifndef _WIN32 +static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { + if (report_signal != 0) { + fprintf(stderr,"SignalDumpInterruptCallback - handling signal\n"); + if (nodereport_events & NR_SIGNAL) { + if (nodereport_verbose) { + fprintf(stderr,"SignalDumpInterruptCallback - triggering NodeReport\n"); + } + TriggerNodeReport(Isolate::GetCurrent(), kSignal_JS, + node::signo_string(*(static_cast(data))), + "node::SignalDumpInterruptCallback()", NULL); + } + report_signal = 0; + } +} +static void SignalDumpAsyncCallback(uv_async_t* handle) { + if (report_signal != 0) { + fprintf(stderr,"SignalDumpAsyncCallback - handling signal\n"); + if (nodereport_events & NR_SIGNAL) { + if (nodereport_verbose) { + fprintf(stderr,"SignalDumpAsyncCallback - triggering NodeReport\n"); + } + size_t signo_data = reinterpret_cast(handle->data); + TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV, + node::signo_string(static_cast(signo_data)), + "node::SignalDumpAsyncCallback()", NULL); + } + report_signal = 0; + } +} + +static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler = false) { + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = handler; + sa.sa_flags = reset_handler ? SA_RESETHAND : 0; + sigfillset(&sa.sa_mask); + sigaction(signal, &sa, nullptr); +} + +// Raw signal handler for triggering a NodeReport - runs on an arbitrary thread +static void SignalDump(int signo) { + // Check atomic for NodeReport 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 + } +} + +// Watchdog thread implementation for signal-triggered NodeReport +inline void* ReportSignalThreadMain(void* unused) { + for (;;) { + uv_sem_wait(&report_semaphore); + if (nodereport_verbose) { + fprintf(stderr, "Signal %s received by nodereport module\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, &report_signal); + // Event loop may be idle, so also request an async callback + size_t signo_data = static_cast(report_signal); + nodereport_trigger_async.data = reinterpret_cast(signo_data); + uv_async_send(&nodereport_trigger_async); + } + uv_mutex_unlock(&node_isolate_mutex); + } + return nullptr; +} + +static int StartWatchdogThread(void *(*thread_main) (void* unused)) { + pthread_attr_t attr; + pthread_attr_init(&attr); + + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); + 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, "nodereport: pthread_create: %s\n", strerror(err)); + fflush(stderr); + return -err; + } + return 0; +} +#endif + + +/******************************************************************************* + * Native module initializer function, called when the module is require'd + * + ******************************************************************************/ +void Initialize(v8::Local exports) { + v8::Isolate* isolate = Isolate::GetCurrent(); + node_isolate = isolate; + + SetLoadTime(); + + const char* trigger_events = getenv("NODEREPORT_EVENTS"); + if (trigger_events != NULL) { + nodereport_events = ProcessNodeReportEvents(trigger_events); + } + const char* core_dump_switch = getenv("NODEREPORT_COREDUMP"); + if (core_dump_switch != NULL) { + nodereport_core = ProcessNodeReportCoreSwitch(core_dump_switch); + } + const char* trigger_signal = getenv("NODEREPORT_SIGNAL"); + if (trigger_signal != NULL) { + nodereport_signal = ProcessNodeReportSignal(trigger_signal); + } + const char* report_name = getenv("NODEREPORT_FILENAME"); + if (report_name != NULL) { + ProcessNodeReportFileName(report_name); + } + const char* directory_name = getenv("NODEREPORT_DIRECTORY"); + if (directory_name != NULL) { + ProcessNodeReportDirectory(directory_name); + } + const char* verbose_switch = getenv("NODEREPORT_VERBOSE"); + if (verbose_switch != NULL) { + nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); + } + + // If NodeReport requested for fatalerror, set up the V8 call-back + if (nodereport_events & NR_FATALERROR) { + isolate->SetFatalErrorHandler(OnFatalError); + } + + // If NodeReport requested for exceptions, tell V8 to capture stack trace and set up the callback + if (nodereport_events & NR_EXCEPTION) { + isolate->SetCaptureStackTraceForUncaughtExceptions(true, 32, v8::StackTrace::kDetailed); + // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set + v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); + isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); + } + +#ifndef _WIN32 + // If NodeReport requested on external user signal set up watchdog thread and callbacks + if (nodereport_events & NR_SIGNAL) { + uv_sem_init(&report_semaphore, 0); + if (StartWatchdogThread(ReportSignalThreadMain) == 0) { + uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); + uv_unref(reinterpret_cast(&nodereport_trigger_async)); + RegisterSignalHandler(nodereport_signal, SignalDump, false); + } + } +#endif + + exports->Set(Nan::New("triggerReport").ToLocalChecked(), + Nan::New(TriggerReport)->GetFunction()); + exports->Set(Nan::New("setEvents").ToLocalChecked(), + Nan::New(SetEvents)->GetFunction()); + exports->Set(Nan::New("setCoreDump").ToLocalChecked(), + Nan::New(SetCoreDump)->GetFunction()); + exports->Set(Nan::New("setSignal").ToLocalChecked(), + Nan::New(SetSignal)->GetFunction()); + exports->Set(Nan::New("setFileName").ToLocalChecked(), + Nan::New(SetFileName)->GetFunction()); + exports->Set(Nan::New("setDirectory").ToLocalChecked(), + Nan::New(SetDirectory)->GetFunction()); + exports->Set(Nan::New("setVerbose").ToLocalChecked(), + Nan::New(SetVerbose)->GetFunction()); + + if (nodereport_verbose) { +#ifdef _WIN32 + fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x\n", + nodereport_events, nodereport_core); +#else + fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x signal flag: %#x\n", + nodereport_events, nodereport_core, nodereport_signal); +#endif + } +} + +NODE_MODULE(nodereport, Initialize) From 3f0ad289cbbeabb4c3c88984bccf21bf0eaa8d18 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Tue, 6 Sep 2016 11:48:57 +0100 Subject: [PATCH 05/24] Fix formatting/style issues in binding and package files --- binding.gyp | 6 +++--- package.json | 3 --- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/binding.gyp b/binding.gyp index 97d7b66..9e4cefd 100644 --- a/binding.gyp +++ b/binding.gyp @@ -7,7 +7,7 @@ "conditions": [ ["OS=='linux'", { "defines": [ "_GNU_SOURCE" ], - "cflags": [ "-g -O2 -std=c++11", ], + "cflags": [ "-g", "-O2", "-std=c++11", ], }], ["OS=='win'", { "libraries": [ "dbghelp.lib" ], @@ -21,8 +21,8 @@ "dependencies" : [ "nodereport" ], "copies": [ { - 'destination': '<(module_root_dir)', - 'files': ['<(module_root_dir)/build/Release/nodereport.node'] + "destination": "<(module_root_dir)", + "files": ["<(module_root_dir)/build/Release/nodereport.node"] }] }, ], diff --git a/package.json b/package.json index 37ea512..86852c5 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,6 @@ "name": "nodereport", "main": "build/Release/nodereport.node", "version": "0.1.0", - "keywords": [ - "" - ], "description": "Diagnostic NodeReport", "homepage": "https://github.com/nodejs/nodereport", "repository": { From e7a52667150a240fd957574d7733e76a0f8756da Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Tue, 6 Sep 2016 16:34:12 +0100 Subject: [PATCH 06/24] Remove hard-coded filename and path lengths --- src/module.cc | 19 ++++--------------- src/node_report.cc | 6 +++--- src/node_report.h | 4 ++-- 3 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/module.cc b/src/module.cc index ed835e9..037babe 100644 --- a/src/module.cc +++ b/src/module.cc @@ -36,29 +36,18 @@ static v8::Isolate* node_isolate; ******************************************************************************/ NAN_METHOD(TriggerReport) { Nan::HandleScope scope; - v8::Isolate* isolate = Isolate::GetCurrent(); - char filename[48] = ""; + v8::Isolate* isolate = info.GetIsolate(); + char filename[NR_MAXNAME + 1] = ""; if (info[0]->IsString()) { // Filename parameter supplied Nan::Utf8String filename_parameter(info[0]->ToString()); - if (filename_parameter.length() < 48) { + if (filename_parameter.length() < NR_MAXNAME) { strcpy(filename, *filename_parameter); } else { - Nan::ThrowSyntaxError("TriggerReport: filename parameter is too long (max 48 characters)"); + Nan::ThrowSyntaxError("TriggerReport: filename parameter is too long"); } } - if (info[0]->IsFunction()) { - // Callback parameter supplied - Nan::Callback callback(info[0].As()); - // Creates a new Object on the V8 heap - Local obj = Object::New(isolate); - obj->Set(String::NewFromUtf8(isolate, "number"), Number::New(isolate, 54)); - Local argv[1]; - argv[0] = obj; - // Invoke the callback, passing the object in argv - callback.Call(1, argv); - } TriggerNodeReport(isolate, kJavaScript, "JavaScript API", "TriggerReport (nodereport/src/module.cc)", filename); // Return value is the NodeReport filename diff --git a/src/node_report.cc b/src/node_report.cc index eccff73..083c548 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -163,7 +163,7 @@ void ProcessNodeReportFileName(const char *args) { return; } if (strlen(args) > NR_MAXNAME) { - fprintf(stderr, "Supplied nodereport filename too long (max 48 characters)\n"); + fprintf(stderr, "Supplied nodereport filename too long (max %d characters)\n", NR_MAXNAME); return; } strcpy(report_filename, args); @@ -175,7 +175,7 @@ void ProcessNodeReportDirectory(const char *args) { return; } if (strlen(args) > NR_MAXPATH) { - fprintf(stderr, "Supplied nodereport directory path too long (max 2048 characters)\n"); + fprintf(stderr, "Supplied nodereport directory path too long (max %d characters)\n", NR_MAXPATH); return; } strcpy(report_directory, args); @@ -239,7 +239,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c // Determine the required NodeReport filename. In order of priority: // 1) supplied on API 2) configured on startup 3) default generated - char filename[48] = ""; + char filename[NR_MAXNAME + 1] = ""; if (name != NULL && strlen(name) > 0) { // Filename was specified as API parameter, use that strcpy(filename, name); diff --git a/src/node_report.h b/src/node_report.h index f7c7333..39fa006 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -19,8 +19,8 @@ using v8::Value; #define NR_JSAPICALL 0x08 // Maximum file and path name lengths -#define NR_MAXNAME 48 -#define NR_MAXPATH 2048 +#define NR_MAXNAME 64 +#define NR_MAXPATH 1024 enum DumpEvent {kException, kFatalError, kSignal_JS, kSignal_UV, kJavaScript}; From 2d9d7bc8c437b4df405b76b11f822ac4294a4ec5 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Thu, 8 Sep 2016 14:54:08 +0100 Subject: [PATCH 07/24] Add error handling for uv calls, remove duplicate code --- src/module.cc | 106 +++++++++++++++++++++++++++++++------------------- 1 file changed, 65 insertions(+), 41 deletions(-) diff --git a/src/module.cc b/src/module.cc index 037babe..d1fde11 100644 --- a/src/module.cc +++ b/src/module.cc @@ -11,6 +11,7 @@ inline void* ReportSignalThreadMain(void* unused); static int StartWatchdogThread(void *(*thread_main) (void* unused)); static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler); static void SignalDump(int signo); +static void SetupSignalHandler(); #endif // Default nodereport option settings @@ -45,7 +46,7 @@ NAN_METHOD(TriggerReport) { if (filename_parameter.length() < NR_MAXNAME) { strcpy(filename, *filename_parameter); } else { - Nan::ThrowSyntaxError("TriggerReport: filename parameter is too long"); + Nan::ThrowSyntaxError("nodereport: filename parameter is too long"); } } @@ -80,12 +81,7 @@ NAN_METHOD(SetEvents) { #ifndef _WIN32 // If NodeReport newly requested on external user signal set up watchdog thread and callbacks if ((nodereport_events & NR_SIGNAL) && !(previous_events & NR_SIGNAL)) { - uv_sem_init(&report_semaphore, 0); - if (StartWatchdogThread(ReportSignalThreadMain) == 0) { - uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); - uv_unref(reinterpret_cast(&nodereport_trigger_async)); - RegisterSignalHandler(nodereport_signal, SignalDump, false); - } + SetupSignalHandler(); } #endif } @@ -149,10 +145,12 @@ bool OnUncaughtException(v8::Isolate* isolate) { #ifndef _WIN32 static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { if (report_signal != 0) { - fprintf(stderr,"SignalDumpInterruptCallback - handling signal\n"); + if (nodereport_verbose) { + fprintf(stdout,"nodereport: SignalDumpInterruptCallback handling signal\n"); + } if (nodereport_events & NR_SIGNAL) { if (nodereport_verbose) { - fprintf(stderr,"SignalDumpInterruptCallback - triggering NodeReport\n"); + fprintf(stdout,"nodereport: SignalDumpInterruptCallback triggering NodeReport\n"); } TriggerNodeReport(Isolate::GetCurrent(), kSignal_JS, node::signo_string(*(static_cast(data))), @@ -163,10 +161,12 @@ static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { } static void SignalDumpAsyncCallback(uv_async_t* handle) { if (report_signal != 0) { - fprintf(stderr,"SignalDumpAsyncCallback - handling signal\n"); + if (nodereport_verbose) { + fprintf(stdout,"nodereport: SignalDumpAsyncCallback handling signal\n"); + } if (nodereport_events & NR_SIGNAL) { if (nodereport_verbose) { - fprintf(stderr,"SignalDumpAsyncCallback - triggering NodeReport\n"); + fprintf(stdout,"nodereport: SignalDumpAsyncCallback triggering NodeReport\n"); } size_t signo_data = reinterpret_cast(handle->data); TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV, @@ -177,6 +177,15 @@ static void SignalDumpAsyncCallback(uv_async_t* handle) { } } +/******************************************************************************* + * 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 signal, void (*handler)(int signal), bool reset_handler = false) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); @@ -194,12 +203,34 @@ static void SignalDump(int signo) { } } +// Utility function to start the watchdog thread +static int StartWatchdogThread(void *(*thread_main) (void* unused)) { + pthread_attr_t attr; + pthread_attr_init(&attr); + + pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); + 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, "nodereport: StartWatchdogThread pthread_create() failed: %s\n", strerror(err)); + fflush(stderr); + return -err; + } + return 0; +} + // Watchdog thread implementation for signal-triggered NodeReport inline void* ReportSignalThreadMain(void* unused) { for (;;) { uv_sem_wait(&report_semaphore); if (nodereport_verbose) { - fprintf(stderr, "Signal %s received by nodereport module\n", node::signo_string(report_signal)); + fprintf(stdout, "nodereport: signal %s received\n", node::signo_string(report_signal)); } uv_mutex_lock(&node_isolate_mutex); if (auto isolate = node_isolate) { @@ -215,25 +246,23 @@ inline void* ReportSignalThreadMain(void* unused) { return nullptr; } -static int StartWatchdogThread(void *(*thread_main) (void* unused)) { - pthread_attr_t attr; - pthread_attr_init(&attr); +// Utility function to initialise signal handlers and threads +static void SetupSignalHandler() { + int rc = uv_sem_init(&report_semaphore, 0); + if (rc != 0) { + fprintf(stderr, "nodereport: initialization failed, uv_sem_init() returned %d\n", rc); + Nan::ThrowError("nodereport: initialization failed, uv_sem_init() returned error\n"); + } - pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN); - 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, "nodereport: pthread_create: %s\n", strerror(err)); - fflush(stderr); - return -err; + if (StartWatchdogThread(ReportSignalThreadMain) == 0) { + rc = uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); + if (rc != 0) { + fprintf(stderr, "nodereport: initialization failed, uv_sem_init() returned %d\n", rc); + Nan::ThrowError("nodereport: initialization failed, uv_sem_init() returned error\n"); + } + uv_unref(reinterpret_cast(&nodereport_trigger_async)); + RegisterSignalHandler(nodereport_signal, SignalDump, false); } - return 0; } #endif @@ -248,6 +277,10 @@ void Initialize(v8::Local exports) { SetLoadTime(); + const char* verbose_switch = getenv("NODEREPORT_VERBOSE"); + if (verbose_switch != NULL) { + nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); + } const char* trigger_events = getenv("NODEREPORT_EVENTS"); if (trigger_events != NULL) { nodereport_events = ProcessNodeReportEvents(trigger_events); @@ -268,10 +301,6 @@ void Initialize(v8::Local exports) { if (directory_name != NULL) { ProcessNodeReportDirectory(directory_name); } - const char* verbose_switch = getenv("NODEREPORT_VERBOSE"); - if (verbose_switch != NULL) { - nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); - } // If NodeReport requested for fatalerror, set up the V8 call-back if (nodereport_events & NR_FATALERROR) { @@ -289,12 +318,7 @@ void Initialize(v8::Local exports) { #ifndef _WIN32 // If NodeReport requested on external user signal set up watchdog thread and callbacks if (nodereport_events & NR_SIGNAL) { - uv_sem_init(&report_semaphore, 0); - if (StartWatchdogThread(ReportSignalThreadMain) == 0) { - uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); - uv_unref(reinterpret_cast(&nodereport_trigger_async)); - RegisterSignalHandler(nodereport_signal, SignalDump, false); - } + SetupSignalHandler(); } #endif @@ -315,10 +339,10 @@ void Initialize(v8::Local exports) { if (nodereport_verbose) { #ifdef _WIN32 - fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x\n", + fprintf(stdout, "nodereport: initialization complete, event flags: %#x core flag: %#x\n", nodereport_events, nodereport_core); #else - fprintf(stdout, "Initialized nodereport module, event flags: %#x core flag: %#x signal flag: %#x\n", + fprintf(stdout, "nodereport: initialization complete, event flags: %#x core flag: %#x signal flag: %#x\n", nodereport_events, nodereport_core, nodereport_signal); #endif } From 01fc6b04fdb3837d6063ddd89bc5a08234e371e5 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Fri, 9 Sep 2016 11:27:17 +0100 Subject: [PATCH 08/24] Remove unnecessary ToString call for Nan parameters --- src/module.cc | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/module.cc b/src/module.cc index d1fde11..c7679e8 100644 --- a/src/module.cc +++ b/src/module.cc @@ -60,7 +60,7 @@ NAN_METHOD(TriggerReport) { * ******************************************************************************/ NAN_METHOD(SetEvents) { - Nan::Utf8String parameter(info[0]->ToString()); + Nan::Utf8String parameter(info[0]); v8::Isolate* isolate = node_isolate; unsigned int previous_events = nodereport_events; // save previous settings nodereport_events = ProcessNodeReportEvents(*parameter); @@ -86,23 +86,23 @@ NAN_METHOD(SetEvents) { #endif } NAN_METHOD(SetCoreDump) { - Nan::Utf8String parameter(info[0]->ToString()); + Nan::Utf8String parameter(info[0]); nodereport_events = ProcessNodeReportCoreSwitch(*parameter); } NAN_METHOD(SetSignal) { - Nan::Utf8String parameter(info[0]->ToString()); + Nan::Utf8String parameter(info[0]); nodereport_events = ProcessNodeReportSignal(*parameter); } NAN_METHOD(SetFileName) { - Nan::Utf8String parameter(info[0]->ToString()); + Nan::Utf8String parameter(info[0]); ProcessNodeReportFileName(*parameter); } NAN_METHOD(SetDirectory) { - Nan::Utf8String parameter(info[0]->ToString()); + Nan::Utf8String parameter(info[0]); ProcessNodeReportDirectory(*parameter); } NAN_METHOD(SetVerbose) { - Nan::Utf8String parameter(info[0]->ToString()); + Nan::Utf8String parameter(info[0]); nodereport_verbose = ProcessNodeReportVerboseSwitch(*parameter); } From 069fa3259712ce44269eb987793d2c56911df5bb Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Fri, 9 Sep 2016 13:41:28 +0100 Subject: [PATCH 09/24] Remove redundant passing of signal nr via callbacks --- src/module.cc | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/module.cc b/src/module.cc index c7679e8..1ea8764 100644 --- a/src/module.cc +++ b/src/module.cc @@ -124,16 +124,16 @@ static void OnFatalError(const char* location, const char* message) { } fflush(stderr); if (nodereport_core) { - raise(SIGABRT); // core dump requested + raise(SIGABRT); // core dump requested (default) } else { - exit(0); // no core dump requested + exit(1); // user specified that no core dump is wanted, just exit } } bool OnUncaughtException(v8::Isolate* isolate) { // Trigger NodeReport if required if (nodereport_events & NR_EXCEPTION) { - TriggerNodeReport(Isolate::GetCurrent(), kException, "exception", "OnUncaughtException (nodereport/src/module.cc)", NULL); + TriggerNodeReport(isolate, kException, "exception", "OnUncaughtException (nodereport/src/module.cc)", NULL); } if (nodereport_core) { return true; @@ -152,8 +152,8 @@ static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { if (nodereport_verbose) { fprintf(stdout,"nodereport: SignalDumpInterruptCallback triggering NodeReport\n"); } - TriggerNodeReport(Isolate::GetCurrent(), kSignal_JS, - node::signo_string(*(static_cast(data))), + TriggerNodeReport(isolate, kSignal_JS, + node::signo_string(report_signal), "node::SignalDumpInterruptCallback()", NULL); } report_signal = 0; @@ -168,9 +168,8 @@ static void SignalDumpAsyncCallback(uv_async_t* handle) { if (nodereport_verbose) { fprintf(stdout,"nodereport: SignalDumpAsyncCallback triggering NodeReport\n"); } - size_t signo_data = reinterpret_cast(handle->data); TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV, - node::signo_string(static_cast(signo_data)), + node::signo_string(report_signal), "node::SignalDumpAsyncCallback()", NULL); } report_signal = 0; @@ -235,10 +234,8 @@ inline void* ReportSignalThreadMain(void* unused) { uv_mutex_lock(&node_isolate_mutex); if (auto isolate = node_isolate) { // Request interrupt callback for running JavaScript code - isolate->RequestInterrupt(SignalDumpInterruptCallback, &report_signal); + isolate->RequestInterrupt(SignalDumpInterruptCallback, NULL); // Event loop may be idle, so also request an async callback - size_t signo_data = static_cast(report_signal); - nodereport_trigger_async.data = reinterpret_cast(signo_data); uv_async_send(&nodereport_trigger_async); } uv_mutex_unlock(&node_isolate_mutex); From 26b25c18342977474c59954dfd4fb2b890d02560 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Fri, 9 Sep 2016 16:07:16 +0100 Subject: [PATCH 10/24] Fix FreeBSD issues, and use secure_getenv() --- src/module.cc | 33 ++++++++++++++++++++++----------- src/node_report.h | 4 ++++ 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/module.cc b/src/module.cc index 1ea8764..bf9aa92 100644 --- a/src/module.cc +++ b/src/module.cc @@ -1,6 +1,5 @@ -#include -//#include #include "node_report.h" +#include // Internal/static function declarations static void OnFatalError(const char* location, const char* message); @@ -189,26 +188,39 @@ static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handler; +#ifndef __FreeBSD__ + // Note: SA_RESETHAND doesn't work with some versions of FreeBSD's libthr. The + // workaround is to set the handler to SIG_DFL in the signal handler, see below sa.sa_flags = reset_handler ? SA_RESETHAND : 0; +#endif //__FreeBSD__ sigfillset(&sa.sa_mask); sigaction(signal, &sa, nullptr); } // Raw signal handler for triggering a NodeReport - runs on an arbitrary thread static void SignalDump(int signo) { +#ifdef __FreeBSD__ + struct sigaction sa; + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_DFL; + sigaction(signo, &sa, nullptr); +#endif // __FreeBSD__ // Check atomic for NodeReport 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 the 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); @@ -263,7 +275,6 @@ static void SetupSignalHandler() { } #endif - /******************************************************************************* * Native module initializer function, called when the module is require'd * @@ -274,27 +285,27 @@ void Initialize(v8::Local exports) { SetLoadTime(); - const char* verbose_switch = getenv("NODEREPORT_VERBOSE"); + const char* verbose_switch = secure_getenv("NODEREPORT_VERBOSE"); if (verbose_switch != NULL) { nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); } - const char* trigger_events = getenv("NODEREPORT_EVENTS"); + const char* trigger_events = secure_getenv("NODEREPORT_EVENTS"); if (trigger_events != NULL) { nodereport_events = ProcessNodeReportEvents(trigger_events); } - const char* core_dump_switch = getenv("NODEREPORT_COREDUMP"); + const char* core_dump_switch = secure_getenv("NODEREPORT_COREDUMP"); if (core_dump_switch != NULL) { nodereport_core = ProcessNodeReportCoreSwitch(core_dump_switch); } - const char* trigger_signal = getenv("NODEREPORT_SIGNAL"); + const char* trigger_signal = secure_getenv("NODEREPORT_SIGNAL"); if (trigger_signal != NULL) { nodereport_signal = ProcessNodeReportSignal(trigger_signal); } - const char* report_name = getenv("NODEREPORT_FILENAME"); + const char* report_name = secure_getenv("NODEREPORT_FILENAME"); if (report_name != NULL) { ProcessNodeReportFileName(report_name); } - const char* directory_name = getenv("NODEREPORT_DIRECTORY"); + const char* directory_name = secure_getenv("NODEREPORT_DIRECTORY"); if (directory_name != NULL) { ProcessNodeReportDirectory(directory_name); } diff --git a/src/node_report.h b/src/node_report.h index 39fa006..a3f1b00 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -35,4 +35,8 @@ unsigned int ProcessNodeReportVerboseSwitch(const char *args); void SetLoadTime(); +#ifdef _WIN32 +#define secure_getenv getenv +#endif + #endif // SRC_NODE_REPORT_H_ From 9d330b32d48816fe269d8d70b82eb51658562fd4 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Fri, 9 Sep 2016 17:11:51 +0100 Subject: [PATCH 11/24] Fix for secure_getenv() not available on OSX --- src/node_report.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node_report.h b/src/node_report.h index a3f1b00..55eb28c 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -35,7 +35,7 @@ unsigned int ProcessNodeReportVerboseSwitch(const char *args); void SetLoadTime(); -#ifdef _WIN32 +#if defined(_WIN32) || defined(__APPLE__) #define secure_getenv getenv #endif From 2e64d34bf1b48d490dc41127c934bc35b5839804 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Mon, 12 Sep 2016 16:13:34 +0100 Subject: [PATCH 12/24] Wrap code in nodereport namespace --- src/module.cc | 5 +++++ src/node_report.cc | 12 ++++++++++-- src/node_report.h | 16 ++++++++++++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/src/module.cc b/src/module.cc index bf9aa92..f90e2a7 100644 --- a/src/module.cc +++ b/src/module.cc @@ -1,6 +1,8 @@ #include "node_report.h" #include +namespace nodereport { + // Internal/static function declarations static void OnFatalError(const char* location, const char* message); bool OnUncaughtException(v8::Isolate* isolate); @@ -357,3 +359,6 @@ void Initialize(v8::Local exports) { } NODE_MODULE(nodereport, Initialize) + +} // namespace nodereport + diff --git a/src/node_report.cc b/src/node_report.cc index 083c548..285e87b 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -30,6 +30,12 @@ #include #endif +#ifndef _WIN32 +extern char **environ; +#endif + +namespace nodereport { + using v8::HeapSpaceStatistics; using v8::HeapStatistics; using v8::Isolate; @@ -55,7 +61,6 @@ static void WriteInteger(FILE *fp, size_t value); // Global variables static int seq = 0; // sequence number for NodeReport filenames const char* v8_states[] = {"JS", "GC", "COMPILER", "OTHER", "EXTERNAL", "IDLE"}; -const char* TriggerNames[] = {"Exception", "FatalError", "SIGUSR2", "SIGQUIT", "JavaScript API"}; static bool report_active = false; // recursion protection static char report_filename[NR_MAXNAME + 1] = ""; static char report_directory[NR_MAXPATH + 1] = ""; // defaults to current working directory @@ -63,7 +68,6 @@ static char report_directory[NR_MAXPATH + 1] = ""; // defaults to current workin static SYSTEMTIME loadtime_tm_struct; // module load time #else // UNIX, OSX static struct tm loadtime_tm_struct; // module load time -extern char **environ; #endif #if defined(_MSC_VER) && _MSC_VER < 1900 @@ -358,6 +362,9 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c struct utsname os_info; if (uname(&os_info) == 0) { fprintf(fp,"\nOS version: %s %s %s",os_info.sysname, os_info.release, os_info.version); +#if defined(__GLIBC__) + fprintf(fp,"\n(glibc: %d.%d)", __GLIBC__, __GLIBC_MINOR__); +#endif fprintf(fp,"\nMachine: %s %s\n", os_info.nodename, os_info.machine); } #endif @@ -794,3 +801,4 @@ static void WriteInteger(FILE *fp, size_t value) { } } +} // namespace nodereport diff --git a/src/node_report.h b/src/node_report.h index 55eb28c..0979292 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -2,6 +2,11 @@ #define SRC_NODE_REPORT_H_ #include "nan.h" +#if !defined(_WIN32) && !defined(__APPLE__) +#include +#endif + +namespace nodereport { using v8::Isolate; using v8::Local; @@ -35,8 +40,15 @@ unsigned int ProcessNodeReportVerboseSwitch(const char *args); void SetLoadTime(); -#if defined(_WIN32) || defined(__APPLE__) +// secure_getenv() only available in glibc, revert to getenv() otherwise +#if defined(__GLIBC__) +#if !__GLIBC_PREREQ(2, 17) #define secure_getenv getenv -#endif +#endif // !__GLIBC_PREREQ(2, 17) +#else +#define secure_getenv getenv +#endif // defined(__GLIBC__) + +} // namespace nodereport #endif // SRC_NODE_REPORT_H_ From c6ecd33f24f5af87018bc577f8e974b12a08fa12 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Tue, 13 Sep 2016 09:56:10 +0100 Subject: [PATCH 13/24] Replace strcpy calls with safer snprintf --- src/module.cc | 2 +- src/node_report.cc | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/module.cc b/src/module.cc index f90e2a7..923bb1b 100644 --- a/src/module.cc +++ b/src/module.cc @@ -45,7 +45,7 @@ NAN_METHOD(TriggerReport) { // Filename parameter supplied Nan::Utf8String filename_parameter(info[0]->ToString()); if (filename_parameter.length() < NR_MAXNAME) { - strcpy(filename, *filename_parameter); + snprintf(filename, sizeof(filename), "%s", *filename_parameter); } else { Nan::ThrowSyntaxError("nodereport: filename parameter is too long"); } diff --git a/src/node_report.cc b/src/node_report.cc index 285e87b..46457f9 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -170,7 +170,7 @@ void ProcessNodeReportFileName(const char *args) { fprintf(stderr, "Supplied nodereport filename too long (max %d characters)\n", NR_MAXNAME); return; } - strcpy(report_filename, args); + snprintf(report_filename, sizeof(report_filename), "%s", args); } void ProcessNodeReportDirectory(const char *args) { @@ -182,7 +182,7 @@ void ProcessNodeReportDirectory(const char *args) { fprintf(stderr, "Supplied nodereport directory path too long (max %d characters)\n", NR_MAXPATH); return; } - strcpy(report_directory, args); + snprintf(report_directory, sizeof(report_directory), "%s", args); } unsigned int ProcessNodeReportVerboseSwitch(const char *args) { @@ -246,13 +246,13 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c char filename[NR_MAXNAME + 1] = ""; if (name != NULL && strlen(name) > 0) { // Filename was specified as API parameter, use that - strcpy(filename, name); + snprintf(filename, sizeof(filename), "%s", name); } else if (strlen(report_filename) > 0) { // File name was supplied via start-up option, use that - strcpy(filename, report_filename); + snprintf(filename, sizeof(filename), "%s", report_filename); } else { // Construct the NodeReport filename, with timestamp, pid and sequence number - strcpy(filename, "NodeReport"); + snprintf(filename, sizeof(filename), "%s", "NodeReport"); seq++; #ifdef _WIN32 @@ -280,7 +280,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c // Regular file. Append filename to directory path if one was specified if (strlen(report_directory) > 0) { char pathname[NR_MAXPATH + NR_MAXNAME + 1] = ""; - strcpy(pathname, report_directory); + snprintf(pathname, sizeof(pathname), "%s", report_directory); #ifdef _WIN32 strcat(pathname, "\\"); #else @@ -356,21 +356,21 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c TCHAR infoBuf[256]; DWORD bufCharCount = 256; if (GetComputerName(infoBuf, &bufCharCount)) { - fprintf(fp,"Machine name: %s %s\n", infoBuf); + fprintf(fp,"\nMachine name: %s %s\n", infoBuf); } #else struct utsname os_info; if (uname(&os_info) == 0) { - fprintf(fp,"\nOS version: %s %s %s",os_info.sysname, os_info.release, os_info.version); + fprintf(fp,"\nOS version: %s %s %s\n",os_info.sysname, os_info.release, os_info.version); #if defined(__GLIBC__) - fprintf(fp,"\n(glibc: %d.%d)", __GLIBC__, __GLIBC_MINOR__); + fprintf(fp,"(glibc: %d.%d)\n", __GLIBC__, __GLIBC_MINOR__); #endif fprintf(fp,"\nMachine: %s %s\n", os_info.nodename, os_info.machine); } #endif // Print native process ID - fprintf(fp, "\nProcess ID: %d\n", pid); + fprintf(fp, "Process ID: %d\n", pid); fflush(fp); // Print summary JavaScript stack trace @@ -448,7 +448,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c fprintf(stderr, "Node.js report completed\n"); if (name != NULL) { - strcpy(name, filename); // return the NodeReport file name + snprintf(name, NR_MAXNAME + 1, "%s", filename); // return the NodeReport file name } report_active = false; } From dccc0e59df2df475310ff14993b199aeb326e01e Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Tue, 13 Sep 2016 10:18:46 +0100 Subject: [PATCH 14/24] Move snprintf and arraysize definitions to header file --- src/node_report.cc | 21 --------------------- src/node_report.h | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/node_report.cc b/src/node_report.cc index 46457f9..d00e19d 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -70,27 +70,6 @@ static SYSTEMTIME loadtime_tm_struct; // module load time static struct tm loadtime_tm_struct; // module load time #endif -#if defined(_MSC_VER) && _MSC_VER < 1900 -// Workaround for arraysize() on Windows VS 2013. -#define arraysize(a) (sizeof(a) / sizeof(*a)) -#else -template -constexpr size_t arraysize(const T(&)[N]) { return N; } -#endif - -#if defined(_MSC_VER) && (_MSC_VER < 1900) -// Workaround for snprintf() on Windows VS 2013` -#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; -} -#endif - /******************************************************************************* * Functions to process nodereport configuration options: * Trigger event selection diff --git a/src/node_report.h b/src/node_report.h index 0979292..8b91668 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -49,6 +49,27 @@ void SetLoadTime(); #define secure_getenv getenv #endif // defined(__GLIBC__) +// 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; +} +#endif // defined( _MSC_VER ) && (_MSC_VER < 1900) + } // namespace nodereport #endif // SRC_NODE_REPORT_H_ From f7b8a3c1f655ffef05e6e2cec40d2a1839b69302 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Tue, 13 Sep 2016 11:22:45 +0100 Subject: [PATCH 15/24] Minor changes, indents, comments, casts, variable names --- src/node_report.cc | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/node_report.cc b/src/node_report.cc index d00e19d..ee4b01b 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -44,7 +44,6 @@ using v8::Message; using v8::StackFrame; using v8::StackTrace; using v8::String; - using v8::V8; // Internal/static function declarations @@ -297,20 +296,20 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c tm_struct.wHour, tm_struct.wMinute, tm_struct.wSecond); fprintf(fp, "Module load time: %4d/%02d/%02d %02d:%02d:%02d\n", loadtime_tm_struct.wYear, loadtime_tm_struct.wMonth, loadtime_tm_struct.wDay, - loadtime_tm_struct.wHour, loadtime_tm_struct.wMinute, loadtime_tm_struct.wSecond); + loadtime_tm_struct.wHour, loadtime_tm_struct.wMinute, loadtime_tm_struct.wSecond); #else // UNIX, OSX fprintf(fp, "Dump event time: %4d/%02d/%02d %02d:%02d:%02d\n", 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); fprintf(fp, "Module load time: %4d/%02d/%02d %02d:%02d:%02d\n", - 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); + 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); #endif // Print Node.js and deps component versions fprintf(fp, "\nNode.js version: %s\n", NODE_VERSION); fprintf(fp, "(v8: %s, libuv: %s, zlib: %s, ares: %s)\n", - V8::GetVersion(), uv_version_string(), ZLIB_VERSION, ARES_VERSION_STR); + V8::GetVersion(), uv_version_string(), ZLIB_VERSION, ARES_VERSION_STR); // Print OS name and level and machine name #ifdef _WIN32 @@ -332,10 +331,10 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c } else { fprintf(fp,"Client\n"); } - TCHAR infoBuf[256]; - DWORD bufCharCount = 256; - if (GetComputerName(infoBuf, &bufCharCount)) { - fprintf(fp,"\nMachine name: %s %s\n", infoBuf); + TCHAR machine_name[256]; + DWORD machine_name_size = 256; + if (GetComputerName(machine_name, &machine_name_size)) { + fprintf(fp,"\nMachine: %s %s\n", machine_name); } #else struct utsname os_info; @@ -409,6 +408,9 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c #endif // Print libuv handle summary (TODO: investigate failure on Windows) + // Note: documentation of the uv_print_all_handles() API says "This function + // is meant for ad hoc debugging, there is no API/ABI stability guarantee" + // http://docs.libuv.org/en/v1.x/misc.html #ifndef _WIN32 fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== Node.js libuv Handle Summary ==============================================\n"); @@ -449,9 +451,9 @@ static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, isolate->GetStackSample(state, samples, arraysize(samples), &info); if (static_cast(info.vm_state) < arraysize(v8_states)) { - fprintf(fp, "JavaScript VM state: %s\n\n", v8_states[info.vm_state]); + fprintf(fp, "JavaScript VM state: %s\n\n", v8_states[info.vm_state]); } else { - fprintf(fp, "JavaScript VM state: \n\n"); + fprintf(fp, "JavaScript VM state: \n\n"); } if (event == kSignal_UV) { fprintf(fp, "Signal received when event loop idle, no stack trace available\n"); @@ -464,7 +466,7 @@ static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, } // Print the stack trace, adding in the pc values from GetStackSample() if available for (int i = 0; i < stack->GetFrameCount(); i++) { - if ((size_t)i < info.frames_count) { + if (static_cast(i) < info.frames_count) { PrintStackFrame(fp, isolate, stack->GetFrame(i), i, samples[i]); } else { PrintStackFrame(fp, isolate, stack->GetFrame(i), i, NULL); @@ -716,7 +718,7 @@ static void PrintSystemInformation(FILE *fp, Isolate* isolate) { env_var = *(environ + index++); } -static struct { +const static struct { const char* description; int id; } rlimit_strings[] = { From af7361478390f83e80bb83a5bf3dfbde6642a781 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Mon, 3 Oct 2016 11:37:58 +0100 Subject: [PATCH 16/24] Separate functions for report sections, plus fix minor nits --- demo/api_call.js | 70 ++++++++++++------------ demo/exception.js | 68 +++++++++++------------ demo/fatalerror.js | 84 ++++++++++++++--------------- demo/loop.js | 110 ++++++++++++++++++------------------- package.json | 5 +- src/node_report.cc | 132 ++++++++++++++++++++++++++------------------- test/autorun.js | 12 ++++- test/test_1.js | 20 +++---- test/test_2.js | 32 +++++------ test/test_3.js | 32 +++++------ test/test_4.js | 56 +++++++++---------- 11 files changed, 326 insertions(+), 295 deletions(-) diff --git a/demo/api_call.js b/demo/api_call.js index 0c38fe7..7674d23 100644 --- a/demo/api_call.js +++ b/demo/api_call.js @@ -1,35 +1,35 @@ -// Example - generation of NodeReport via API call -var nodereport = require('nodereport'); -var http = require("http"); - -var count = 0; - -function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport API demo... refresh page to trigger NodeReport"); - response.end(); - break; - case 1: - response.writeHead(200,{"Content-Type": "text/plain"}); - var filename = nodereport.triggerReport(); // Call the nodereport module to trigger a NodeReport - response.write("\n" + filename + " written - refresh page to close"); - response.end(); - break; - default: - process.exit(0); - } -} - -var http_server = http.createServer(my_listener); -http_server.listen(8080); - -console.log('api_call.js: Node running'); -console.log('api_call.js: Go to http://:8080/ or http://localhost:8080/'); - -setTimeout(function(){ - console.log('api_call.js: test timeout expired, exiting.'); - process.exit(0); -}, 60000); - +// Example - generation of NodeReport via API call +var nodereport = require('nodereport'); +var http = require("http"); + +var count = 0; + +function my_listener(request, response) { + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport API demo... refresh page to trigger NodeReport"); + response.end(); + break; + case 1: + response.writeHead(200,{"Content-Type": "text/plain"}); + var filename = nodereport.triggerReport(); // Call the nodereport module to trigger a NodeReport + response.write("\n" + filename + " written - refresh page to close"); + response.end(); + break; + default: + process.exit(0); + } +} + +var http_server = http.createServer(my_listener); +http_server.listen(8080); + +console.log('api_call.js: Node running'); +console.log('api_call.js: Go to http://:8080/ or http://localhost:8080/'); + +setTimeout(function(){ + console.log('api_call.js: test timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/demo/exception.js b/demo/exception.js index 30f19b0..fa91476 100644 --- a/demo/exception.js +++ b/demo/exception.js @@ -1,34 +1,34 @@ -// Example - generation of NodeReport on uncaught exception -require('nodereport'); -var http = require("http"); - -var count = 0; - -function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport exception demo... refresh page to cause exception (application will terminate)"); - response.end(); - break; - default: - throw new UserException('*** exception.js: exception thrown from my_listener()'); - } -} - -function UserException(message) { - this.message = message; - this.name = "UserException"; -} - -var http_server = http.createServer(my_listener); -http_server.listen(8080); - -console.log('exception.js: Node running'); -console.log('exception.js: Go to http://:8080/ or http://localhost:8080/'); - -setTimeout(function(){ - console.log('exception.js: test timeout expired, exiting.'); - process.exit(0); -}, 60000); - +// Example - generation of NodeReport on uncaught exception +require('nodereport'); +var http = require("http"); + +var count = 0; + +function my_listener(request, response) { + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport exception demo... refresh page to cause exception (application will terminate)"); + response.end(); + break; + default: + throw new UserException('*** exception.js: exception thrown from my_listener()'); + } +} + +function UserException(message) { + this.message = message; + this.name = "UserException"; +} + +var http_server = http.createServer(my_listener); +http_server.listen(8080); + +console.log('exception.js: Node running'); +console.log('exception.js: Go to http://:8080/ or http://localhost:8080/'); + +setTimeout(function() { + console.log('exception.js: test timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/demo/fatalerror.js b/demo/fatalerror.js index 8de8060..142177a 100644 --- a/demo/fatalerror.js +++ b/demo/fatalerror.js @@ -1,42 +1,42 @@ -// Example - generation of Nodereport on fatal error (Javascript heap OOM) -require('nodereport'); -var http = require('http'); - -var count = 0; - -function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport fatal error demo... refresh page to trigger excessive memory usage (application will terminate)"); - response.end(); - break; - case 1: - console.log('heap_oom.js: allocating excessive Javascript heap memory....'); - var list = []; - while (true) { - list.push(new MyRecord()); - } - response.end(); - break; - } -} - -function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; -} - -var http_server = http.createServer(my_listener); -http_server.listen(8080); - -console.log('fatalerror.js: Node running'); -console.log('fatalerror.js: Note: heap default is 1.4Gb, use --max-old-space-size= to change'); -console.log('fatalerror.js: Go to http://:8080/ or http://localhost:8080/'); - -setTimeout(function(){ - console.log('fatalerror.js: timeout expired, exiting.'); - process.exit(0); -}, 60000); - +// Example - generation of Nodereport on fatal error (Javascript heap OOM) +require('nodereport'); +var http = require('http'); + +var count = 0; + +function my_listener(request, response) { + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport fatal error demo... refresh page to trigger excessive memory usage (application will terminate)"); + response.end(); + break; + case 1: + console.log('heap_oom.js: allocating excessive Javascript heap memory....'); + var list = []; + while (true) { + list.push(new MyRecord()); + } + response.end(); + break; + } +} + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} + +var http_server = http.createServer(my_listener); +http_server.listen(8080); + +console.log('fatalerror.js: Node running'); +console.log('fatalerror.js: Note: heap default is 1.4Gb, use --max-old-space-size= to change'); +console.log('fatalerror.js: Go to http://:8080/ or http://localhost:8080/'); + +setTimeout(function(){ + console.log('fatalerror.js: timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/demo/loop.js b/demo/loop.js index 613b827..2692ba8 100644 --- a/demo/loop.js +++ b/demo/loop.js @@ -1,55 +1,55 @@ -// Example - geneation of Nodereport via signal for a looping application -require('nodereport'); -var http = require("http"); - -var count = 0; - -function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport looping application demo. Node process ID = " + process.pid); - response.write("\n\nRefresh page to enter loop, then use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); - response.end(); - break; - case 1: - console.log("loop.js: going to loop now, use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); - - var list = []; - for (var i=0; i<10000000000; i++) { - for (var j=0; i<1000; i++) { - list.push(new MyRecord()); - } - for (var j=0; i<1000; i++) { - list[j].id += 1; - list[j].account += 2; - } - for (var j=0; i<1000; i++) { - list.pop(); - } - } - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nNodeReport demo.... finished looping"); - response.end(); - break; - default: - } -} - -function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; -} - -var http_server = http.createServer(my_listener); -http_server.listen(8080); - -console.log('loop.js: Node running'); -console.log('loop.js: Go to http://:8080/ or http://localhost:8080/'); - -setTimeout(function(){ - console.log('loop.js: timeout expired, exiting.'); - process.exit(0); -}, 60000); - +// Example - geneation of Nodereport via signal for a looping application +require('nodereport'); +var http = require("http"); + +var count = 0; + +function my_listener(request, response) { + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport looping application demo. Node process ID = " + process.pid); + response.write("\n\nRefresh page to enter loop, then use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); + response.end(); + break; + case 1: + console.log("loop.js: going to loop now, use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); + + var list = []; + for (var i=0; i<10000000000; i++) { + for (var j=0; i<1000; i++) { + list.push(new MyRecord()); + } + for (var j=0; i<1000; i++) { + list[j].id += 1; + list[j].account += 2; + } + for (var j=0; i<1000; i++) { + list.pop(); + } + } + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nNodeReport demo.... finished looping"); + response.end(); + break; + default: + } +} + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} + +var http_server = http.createServer(my_listener); +http_server.listen(8080); + +console.log('loop.js: Node running'); +console.log('loop.js: Go to http://:8080/ or http://localhost:8080/'); + +setTimeout(function() { + console.log('loop.js: timeout expired, exiting.'); + process.exit(0); +}, 60000); + diff --git a/package.json b/package.json index 86852c5..9e33249 100644 --- a/package.json +++ b/package.json @@ -14,5 +14,8 @@ "dependencies": { "nan": "^2.3.5" }, - "license": "MIT" + "license": "MIT", + "scripts": { + "test": "node test/autorun.js" + } } diff --git a/src/node_report.cc b/src/node_report.cc index ee4b01b..621fe59 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -47,9 +47,11 @@ using v8::String; using v8::V8; // Internal/static function declarations +static void PrintVersionInformation(FILE *fp); +static void PrintJavaScriptStack(FILE *fp, Isolate* isolate, DumpEvent event, const char *location); static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event); static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, int index, void *pc); -static void PrintNativeBacktrace(FILE *fp); +static void PrintNativeStack(FILE *fp); #ifndef _WIN32 static void PrintResourceUsage(FILE *fp); #endif @@ -192,7 +194,7 @@ void SetLoadTime() { #endif } /******************************************************************************* - * API to write a NodeReport to file. + * Main API function to write a NodeReport to file. * * Parameters: * Isolate* isolate @@ -282,10 +284,10 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c } } - // Print NodeReport title and event information + // File stream opened OK, now start printing the NodeReport content, starting with the title + // and header information (event, filename, timestamp and pid) fprintf(fp, "================================================================================\n"); fprintf(fp, "==== NodeReport ================================================================\n"); - fprintf(fp, "\nEvent: %s, location: \"%s\"\n", message, location); fprintf(fp, "Filename: %s\n", filename); @@ -305,13 +307,71 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c 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); #endif + // Print native process ID + fprintf(fp, "Process ID: %d\n", pid); + fflush(fp); + + // Print Node.js and OS version information + PrintVersionInformation(fp); + fflush(fp); + +// Print summary JavaScript stack backtrace + PrintJavaScriptStack(fp, isolate, event, location); + fflush(fp); + + // Print native stack backtrace + PrintNativeStack(fp); + fflush(fp); + + // Print V8 Heap and Garbage Collector information + PrintGCStatistics(fp, isolate); + fflush(fp); + + // Print OS and current thread resource usage +#ifndef _WIN32 + PrintResourceUsage(fp); + fflush(fp); +#endif + + // Print libuv handle summary (TODO: investigate failure on Windows) + // Note: documentation of the uv_print_all_handles() API says "This function + // is meant for ad hoc debugging, there is no API/ABI stability guarantee" + // http://docs.libuv.org/en/v1.x/misc.html +#ifndef _WIN32 + fprintf(fp, "\n================================================================================"); + fprintf(fp, "\n==== Node.js libuv Handle Summary ==============================================\n"); + fprintf(fp,"\n(Flags: R=Ref, A=Active, I=Internal)\n"); + fprintf(fp,"\nFlags Type Address\n"); + uv_print_all_handles(NULL, fp); + fflush(fp); +#endif + + // Print operating system information + PrintSystemInformation(fp, isolate); + + fprintf(fp, "\n================================================================================\n"); + fflush(fp); + fclose(fp); + + fprintf(stderr, "Node.js report completed\n"); + if (name != NULL) { + snprintf(name, NR_MAXNAME + 1, "%s", filename); // return the NodeReport file name + } + report_active = false; +} + +/******************************************************************************* + * Function to print Node.js version, OS version and machine information + * + ******************************************************************************/ +static void PrintVersionInformation(FILE *fp) { // Print Node.js and deps component versions fprintf(fp, "\nNode.js version: %s\n", NODE_VERSION); fprintf(fp, "(v8: %s, libuv: %s, zlib: %s, ares: %s)\n", V8::GetVersion(), uv_version_string(), ZLIB_VERSION, ARES_VERSION_STR); - // Print OS name and level and machine name + // Print operating system and machine information (Windows) #ifdef _WIN32 fprintf(fp,"\nOS version: Windows "); #if defined(_MSC_VER) && (_MSC_VER >= 1900) @@ -337,6 +397,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c fprintf(fp,"\nMachine: %s %s\n", machine_name); } #else + // Print operating system and machine information (Unix/OSX) struct utsname os_info; if (uname(&os_info) == 0) { fprintf(fp,"\nOS version: %s %s %s\n",os_info.sysname, os_info.release, os_info.version); @@ -346,12 +407,13 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c fprintf(fp,"\nMachine: %s %s\n", os_info.nodename, os_info.machine); } #endif +} - // Print native process ID - fprintf(fp, "Process ID: %d\n", pid); - fflush(fp); - -// Print summary JavaScript stack trace +/******************************************************************************* + * Function to print the JavaScript stack, if available + * + ******************************************************************************/ +static void PrintJavaScriptStack(FILE *fp, Isolate* isolate, DumpEvent event, const char *location) { fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== JavaScript Stack Trace ====================================================\n\n"); @@ -391,55 +453,13 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c break; } // end switch(event) #endif - fflush(fp); - - // Print native stack backtrace - PrintNativeBacktrace(fp); - fflush(fp); - - // Print V8 Heap and Garbage Collector information - PrintGCStatistics(fp, isolate); - fflush(fp); - - // Print OS and current thread resource usage -#ifndef _WIN32 - PrintResourceUsage(fp); - fflush(fp); -#endif - - // Print libuv handle summary (TODO: investigate failure on Windows) - // Note: documentation of the uv_print_all_handles() API says "This function - // is meant for ad hoc debugging, there is no API/ABI stability guarantee" - // http://docs.libuv.org/en/v1.x/misc.html -#ifndef _WIN32 - fprintf(fp, "\n================================================================================"); - fprintf(fp, "\n==== Node.js libuv Handle Summary ==============================================\n"); - fprintf(fp,"\n(Flags: R=Ref, A=Active, I=Internal)\n"); - fprintf(fp,"\nFlags Type Address\n"); - uv_print_all_handles(NULL, fp); - fflush(fp); -#endif - - // Print operating system information - PrintSystemInformation(fp, isolate); - - fprintf(fp, "\n================================================================================\n"); - fflush(fp); - fclose(fp); - - fprintf(stderr, "Node.js report completed\n"); - if (name != NULL) { - snprintf(name, NR_MAXNAME + 1, "%s", filename); // return the NodeReport file name - } - report_active = false; } /******************************************************************************* - * Function to print stack using StackTrace::StackTrace() and GetStackSample() + * Function to print stack using GetStackSample() and StackTrace::StackTrace() * ******************************************************************************/ -static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, - DumpEvent event) { +static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event) { v8::RegisterState state; v8::SampleInfo info; void* samples[255]; @@ -518,7 +538,7 @@ static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, * Function to print a native stack backtrace * ******************************************************************************/ -void PrintNativeBacktrace(FILE* fp) { +void PrintNativeStack(FILE* fp) { void *frames[64]; fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== Native Stack Trace ========================================================\n\n"); @@ -562,7 +582,7 @@ void PrintNativeBacktrace(FILE* fp) { * Function to print a native stack backtrace - Linux/OSX * ******************************************************************************/ -void PrintNativeBacktrace(FILE* fp) { +void PrintNativeStack(FILE* fp) { void* frames[256]; fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== Native Stack Trace ========================================================\n\n"); diff --git a/test/autorun.js b/test/autorun.js index dcc06fd..f034e4d 100644 --- a/test/autorun.js +++ b/test/autorun.js @@ -1,4 +1,7 @@ // Testcase for nodereport. Triggers and validates NodeReport files +// +// Parameter(s): --save_log (optional) preserves verbose log file +// 'use strict'; const assert = require('assert'); @@ -14,8 +17,8 @@ if (process.platform === 'win32') { results = ['fail','fail','fail','fail']; } -//TODO: switch off core dumps (using NODEREPORT_COREDUMP env var) -const stdio_log = fs.openSync('./autorun.log', 'w'); +// Open a log file to record verbose test output +const stdio_log = fs.openSync('./nodereport_test.log', 'w'); // Test #1: Run child process to call API to generate a NodeReport console.log('autorun.js: running child process #1 to produce NodeReport on API call'); @@ -84,6 +87,11 @@ process.on('exit', function() { for (var i = 0; i < results.length; i++) { console.log('\ttest', i+1, ': ', results[i]); } + // Close the verbose output log file, and delete it unless --save_log was specified + fs.close(stdio_log); + if (!(process.argv[2] === '--save_log')) { + fs.unlink('./nodereport_test.log'); + } }); // Utility function - locate a NodeReport produced by a process with the given PID diff --git a/test/test_1.js b/test/test_1.js index 2a9a468..9551f09 100644 --- a/test/test_1.js +++ b/test/test_1.js @@ -1,10 +1,10 @@ -// NodeReport API example -var nodereport = require('nodereport'); - -console.log('api_call.js: triggering a NodeReport via API call...'); - -function myReport() { - nodereport.triggerReport(); -} - -myReport(); +// NodeReport API example +var nodereport = require('nodereport'); + +console.log('api_call.js: triggering a NodeReport via API call...'); + +function myReport() { + nodereport.triggerReport(); +} + +myReport(); diff --git a/test/test_2.js b/test/test_2.js index 24340f1..54b260d 100644 --- a/test/test_2.js +++ b/test/test_2.js @@ -1,16 +1,16 @@ -// Testcase to produce an uncaught exception -require('nodereport'); - -console.log('exception.js: throwing an uncaught user exception....'); - -function myException(request, response) { - throw new UserException('*** exception.js: testcase exception thrown from myException()'); -} - -function UserException(message) { - this.message = message; - this.name = "UserException"; -} - -myException(); - +// Testcase to produce an uncaught exception +require('nodereport'); + +console.log('exception.js: throwing an uncaught user exception....'); + +function myException(request, response) { + throw new UserException('*** exception.js: testcase exception thrown from myException()'); +} + +function UserException(message) { + this.message = message; + this.name = "UserException"; +} + +myException(); + diff --git a/test/test_3.js b/test/test_3.js index ec68eae..2faa083 100644 --- a/test/test_3.js +++ b/test/test_3.js @@ -1,16 +1,16 @@ -// Testcase to produce a fatal error (javascript heap OOM) -require('nodereport'); - -console.log('fatalerror.js: allocating excessive javascript heap memory....'); -var list = []; -while (true) { - var record = new MyRecord(); - list.push(record); -} - - -function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; -} +// Testcase to produce a fatal error (javascript heap OOM) +require('nodereport'); + +console.log('fatalerror.js: allocating excessive javascript heap memory....'); +var list = []; +while (true) { + var record = new MyRecord(); + list.push(record); +} + + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} diff --git a/test/test_4.js b/test/test_4.js index 772efe3..9728430 100644 --- a/test/test_4.js +++ b/test/test_4.js @@ -1,28 +1,28 @@ -// Testcase to loop in Javascript code -require('nodereport'); - -console.log('loop.js: going into loop now.... use kill -USR2 to trigger NodeReport'); - -function myLoop() { - var list = []; - for (var i=0; i<10000000000; i++) { - for (var j=0; i<1000; i++) { - list.push(new MyRecord()); - } - for (var j=0; i<1000; i++) { - list[j].id += 1; - list[j].account += 2; - } - for (var j=0; i<1000; i++) { - list.pop(); - } - } -} - -function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; -} - -myLoop(); +// Testcase to loop in Javascript code +require('nodereport'); + +console.log('loop.js: going into loop now.... use kill -USR2 to trigger NodeReport'); + +function myLoop() { + var list = []; + for (var i=0; i<10000000000; i++) { + for (var j=0; i<1000; i++) { + list.push(new MyRecord()); + } + for (var j=0; i<1000; i++) { + list[j].id += 1; + list[j].account += 2; + } + for (var j=0; i<1000; i++) { + list.pop(); + } + } +} + +function MyRecord() { + this.name = 'foo'; + this.id = 128; + this.account = 98454324; +} + +myLoop(); From bd8b06b06a4391db0baf9aca1404d1f392b47ba5 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Mon, 3 Oct 2016 16:32:58 +0100 Subject: [PATCH 17/24] Fix missing mutex initialization (OSX aborts on signal) --- src/module.cc | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/module.cc b/src/module.cc index 923bb1b..bfe74fc 100644 --- a/src/module.cc +++ b/src/module.cc @@ -264,12 +264,17 @@ static void SetupSignalHandler() { fprintf(stderr, "nodereport: initialization failed, uv_sem_init() returned %d\n", rc); Nan::ThrowError("nodereport: initialization failed, uv_sem_init() returned error\n"); } + rc = uv_mutex_init(&node_isolate_mutex); + if (rc != 0) { + fprintf(stderr, "nodereport: initialization failed, uv_mutex_init() returned %d\n", rc); + Nan::ThrowError("nodereport: initialization failed, uv_mutex_init() returned error\n"); + } if (StartWatchdogThread(ReportSignalThreadMain) == 0) { rc = uv_async_init(uv_default_loop(), &nodereport_trigger_async, SignalDumpAsyncCallback); if (rc != 0) { - fprintf(stderr, "nodereport: initialization failed, uv_sem_init() returned %d\n", rc); - Nan::ThrowError("nodereport: initialization failed, uv_sem_init() returned error\n"); + fprintf(stderr, "nodereport: initialization failed, uv_async_init() returned %d\n", rc); + Nan::ThrowError("nodereport: initialization failed, uv_async_init() returned error\n"); } uv_unref(reinterpret_cast(&nodereport_trigger_async)); RegisterSignalHandler(nodereport_signal, SignalDump, false); From 0f2093b6dca109e76b34f92f547ef29902f6073f Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Wed, 5 Oct 2016 16:54:33 +0100 Subject: [PATCH 18/24] Fix .js files to use 2 space indentation --- demo/api_call.js | 36 ++++++++++++------------- demo/exception.js | 27 +++++++++---------- demo/fatalerror.js | 39 +++++++++++++-------------- demo/loop.js | 66 ++++++++++++++++++++++------------------------ test/autorun.js | 9 +++---- test/test_1.js | 2 +- test/test_2.js | 7 +++-- test/test_3.js | 10 +++---- test/test_4.js | 30 ++++++++++----------- 9 files changed, 110 insertions(+), 116 deletions(-) diff --git a/demo/api_call.js b/demo/api_call.js index 7674d23..bb4e580 100644 --- a/demo/api_call.js +++ b/demo/api_call.js @@ -5,21 +5,22 @@ var http = require("http"); var count = 0; function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport API demo... refresh page to trigger NodeReport"); - response.end(); - break; - case 1: - response.writeHead(200,{"Content-Type": "text/plain"}); - var filename = nodereport.triggerReport(); // Call the nodereport module to trigger a NodeReport - response.write("\n" + filename + " written - refresh page to close"); - response.end(); - break; - default: - process.exit(0); - } + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport API demo... refresh page to trigger NodeReport"); + response.end(); + break; + case 1: + response.writeHead(200,{"Content-Type": "text/plain"}); + // Call the nodereport module to trigger a NodeReport + var filename = nodereport.triggerReport(); + response.write("\n" + filename + " written - refresh page to close"); + response.end(); + break; + default: + process.exit(0); + } } var http_server = http.createServer(my_listener); @@ -29,7 +30,6 @@ console.log('api_call.js: Node running'); console.log('api_call.js: Go to http://:8080/ or http://localhost:8080/'); setTimeout(function(){ - console.log('api_call.js: test timeout expired, exiting.'); - process.exit(0); + console.log('api_call.js: test timeout expired, exiting.'); + process.exit(0); }, 60000); - diff --git a/demo/exception.js b/demo/exception.js index fa91476..fb9597b 100644 --- a/demo/exception.js +++ b/demo/exception.js @@ -5,20 +5,20 @@ var http = require("http"); var count = 0; function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport exception demo... refresh page to cause exception (application will terminate)"); - response.end(); - break; - default: - throw new UserException('*** exception.js: exception thrown from my_listener()'); - } + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport exception demo... refresh page to cause exception (application will terminate)"); + response.end(); + break; + default: + throw new UserException('*** exception.js: exception thrown from my_listener()'); + } } function UserException(message) { - this.message = message; - this.name = "UserException"; + this.message = message; + this.name = "UserException"; } var http_server = http.createServer(my_listener); @@ -28,7 +28,6 @@ console.log('exception.js: Node running'); console.log('exception.js: Go to http://:8080/ or http://localhost:8080/'); setTimeout(function() { - console.log('exception.js: test timeout expired, exiting.'); - process.exit(0); + console.log('exception.js: test timeout expired, exiting.'); + process.exit(0); }, 60000); - diff --git a/demo/fatalerror.js b/demo/fatalerror.js index 142177a..c2f20d8 100644 --- a/demo/fatalerror.js +++ b/demo/fatalerror.js @@ -5,27 +5,27 @@ var http = require('http'); var count = 0; function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport fatal error demo... refresh page to trigger excessive memory usage (application will terminate)"); - response.end(); - break; - case 1: - console.log('heap_oom.js: allocating excessive Javascript heap memory....'); - var list = []; - while (true) { - list.push(new MyRecord()); - } - response.end(); - break; + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport fatal error demo... refresh page to trigger excessive memory usage (application will terminate)"); + response.end(); + break; + case 1: + console.log('heap_oom.js: allocating excessive Javascript heap memory....'); + var list = []; + while (true) { + list.push(new MyRecord()); } + response.end(); + break; + } } function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; + this.name = 'foo'; + this.id = 128; + this.account = 98454324; } var http_server = http.createServer(my_listener); @@ -36,7 +36,6 @@ console.log('fatalerror.js: Note: heap default is 1.4Gb, use --max-old-space-siz console.log('fatalerror.js: Go to http://:8080/ or http://localhost:8080/'); setTimeout(function(){ - console.log('fatalerror.js: timeout expired, exiting.'); - process.exit(0); + console.log('fatalerror.js: timeout expired, exiting.'); + process.exit(0); }, 60000); - diff --git a/demo/loop.js b/demo/loop.js index 2692ba8..7f1707d 100644 --- a/demo/loop.js +++ b/demo/loop.js @@ -5,41 +5,40 @@ var http = require("http"); var count = 0; function my_listener(request, response) { - switch(count++) { - case 0: - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nRunning NodeReport looping application demo. Node process ID = " + process.pid); - response.write("\n\nRefresh page to enter loop, then use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); - response.end(); - break; - case 1: - console.log("loop.js: going to loop now, use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); - - var list = []; - for (var i=0; i<10000000000; i++) { - for (var j=0; i<1000; i++) { - list.push(new MyRecord()); - } - for (var j=0; i<1000; i++) { - list[j].id += 1; - list[j].account += 2; - } - for (var j=0; i<1000; i++) { - list.pop(); - } - } - response.writeHead(200,{"Content-Type": "text/plain"}); - response.write("\nNodeReport demo.... finished looping"); - response.end(); - break; - default: + switch(count++) { + case 0: + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nRunning NodeReport looping application demo. Node process ID = " + process.pid); + response.write("\n\nRefresh page to enter loop, then use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); + response.end(); + break; + case 1: + console.log("loop.js: going to loop now, use 'kill -USR2 " + process.pid + "' to trigger NodeReport"); + var list = []; + for (var i=0; i<10000000000; i++) { + for (var j=0; i<1000; i++) { + list.push(new MyRecord()); + } + for (var j=0; i<1000; i++) { + list[j].id += 1; + list[j].account += 2; + } + for (var j=0; i<1000; i++) { + list.pop(); + } } + response.writeHead(200,{"Content-Type": "text/plain"}); + response.write("\nNodeReport demo.... finished looping"); + response.end(); + break; + default: + } } function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; + this.name = 'foo'; + this.id = 128; + this.account = 98454324; } var http_server = http.createServer(my_listener); @@ -49,7 +48,6 @@ console.log('loop.js: Node running'); console.log('loop.js: Go to http://:8080/ or http://localhost:8080/'); setTimeout(function() { - console.log('loop.js: timeout expired, exiting.'); - process.exit(0); + console.log('loop.js: timeout expired, exiting.'); + process.exit(0); }, 60000); - diff --git a/test/autorun.js b/test/autorun.js index f034e4d..63a1bea 100644 --- a/test/autorun.js +++ b/test/autorun.js @@ -66,10 +66,10 @@ if (process.platform === 'win32') { console.log('autorun.js: running child process #4 to produce NodeReport on SIGUSR2'); var child4 = spawn(process.execPath, [path.resolve(__dirname, 'test_4.js')], {stdio: ['pipe', stdio_log, stdio_log]}); setTimeout(function() { - child4.kill('SIGUSR2'); - setTimeout(function() { - child4.kill('SIGTERM'); - }, 1000); + child4.kill('SIGUSR2'); + setTimeout(function() { + child4.kill('SIGTERM'); + }, 1000); }, 1000); child4.on('exit', function(code, signal) { // Locate and validate the NodeReport @@ -127,4 +127,3 @@ function validate(report, index) { } }); } - diff --git a/test/test_1.js b/test/test_1.js index 9551f09..d69c566 100644 --- a/test/test_1.js +++ b/test/test_1.js @@ -4,7 +4,7 @@ var nodereport = require('nodereport'); console.log('api_call.js: triggering a NodeReport via API call...'); function myReport() { - nodereport.triggerReport(); + nodereport.triggerReport(); } myReport(); diff --git a/test/test_2.js b/test/test_2.js index 54b260d..df43195 100644 --- a/test/test_2.js +++ b/test/test_2.js @@ -4,13 +4,12 @@ require('nodereport'); console.log('exception.js: throwing an uncaught user exception....'); function myException(request, response) { - throw new UserException('*** exception.js: testcase exception thrown from myException()'); + throw new UserException('*** exception.js: testcase exception thrown from myException()'); } function UserException(message) { - this.message = message; - this.name = "UserException"; + this.message = message; + this.name = "UserException"; } myException(); - diff --git a/test/test_3.js b/test/test_3.js index 2faa083..20a86ca 100644 --- a/test/test_3.js +++ b/test/test_3.js @@ -4,13 +4,13 @@ require('nodereport'); console.log('fatalerror.js: allocating excessive javascript heap memory....'); var list = []; while (true) { - var record = new MyRecord(); - list.push(record); + var record = new MyRecord(); + list.push(record); } function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; + this.name = 'foo'; + this.id = 128; + this.account = 98454324; } diff --git a/test/test_4.js b/test/test_4.js index 9728430..0d6c0d2 100644 --- a/test/test_4.js +++ b/test/test_4.js @@ -4,25 +4,25 @@ require('nodereport'); console.log('loop.js: going into loop now.... use kill -USR2 to trigger NodeReport'); function myLoop() { - var list = []; - for (var i=0; i<10000000000; i++) { - for (var j=0; i<1000; i++) { - list.push(new MyRecord()); - } - for (var j=0; i<1000; i++) { - list[j].id += 1; - list[j].account += 2; - } - for (var j=0; i<1000; i++) { - list.pop(); - } + var list = []; + for (var i=0; i<10000000000; i++) { + for (var j=0; i<1000; i++) { + list.push(new MyRecord()); } + for (var j=0; i<1000; i++) { + list[j].id += 1; + list[j].account += 2; + } + for (var j=0; i<1000; i++) { + list.pop(); + } + } } function MyRecord() { - this.name = 'foo'; - this.id = 128; - this.account = 98454324; + this.name = 'foo'; + this.id = 128; + this.account = 98454324; } myLoop(); From 56e9ee1736e82f2c2a99e239c3d89eee6a2a35c7 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Mon, 10 Oct 2016 11:47:55 +0100 Subject: [PATCH 19/24] Fix API to allow live changes to trigger signal --- src/module.cc | 45 ++++++++++++++++++++++++++------------------- src/node_report.cc | 34 +++++++++++++++++----------------- 2 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/module.cc b/src/module.cc index bfe74fc..6370b21 100644 --- a/src/module.cc +++ b/src/module.cc @@ -10,7 +10,8 @@ bool OnUncaughtException(v8::Isolate* isolate); static void SignalDumpAsyncCallback(uv_async_t* handle); inline void* ReportSignalThreadMain(void* unused); static int StartWatchdogThread(void *(*thread_main) (void* unused)); -static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler); +static void RegisterSignalHandler(int signal, void (*handler)(int signal)); +static void RestoreSignalHandler(int signal); static void SignalDump(int signo); static void SetupSignalHandler(); #endif @@ -28,6 +29,7 @@ static int report_signal = 0; static uv_sem_t report_semaphore; static uv_async_t nodereport_trigger_async; static uv_mutex_t node_isolate_mutex; +static struct sigaction saved_sa; #endif static v8::Isolate* node_isolate; @@ -80,19 +82,30 @@ NAN_METHOD(SetEvents) { } #ifndef _WIN32 - // If NodeReport newly requested on external user signal set up watchdog thread and callbacks + // If NodeReport newly requested on external user signal set up watchdog thread and handler if ((nodereport_events & NR_SIGNAL) && !(previous_events & NR_SIGNAL)) { SetupSignalHandler(); } + // If NodeReport no longer required on external user signal, reset the OS signal handler + if (!(nodereport_events & NR_SIGNAL) && (previous_events & NR_SIGNAL)) { + RestoreSignalHandler(nodereport_signal); + } #endif } NAN_METHOD(SetCoreDump) { Nan::Utf8String parameter(info[0]); - nodereport_events = ProcessNodeReportCoreSwitch(*parameter); + nodereport_core = ProcessNodeReportCoreSwitch(*parameter); } NAN_METHOD(SetSignal) { Nan::Utf8String parameter(info[0]); - nodereport_events = ProcessNodeReportSignal(*parameter); + 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); + RegisterSignalHandler(nodereport_signal, SignalDump); + } } NAN_METHOD(SetFileName) { Nan::Utf8String parameter(info[0]); @@ -186,27 +199,21 @@ static void SignalDumpAsyncCallback(uv_async_t* handle) { * - SetupSignalHandler() - initialisation of signal handlers and threads ******************************************************************************/ // Utility function to register an OS signal handler -static void RegisterSignalHandler(int signal, void (*handler)(int signal), bool reset_handler = false) { +static void RegisterSignalHandler(int signal, void (*handler)(int signal)) { struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = handler; -#ifndef __FreeBSD__ - // Note: SA_RESETHAND doesn't work with some versions of FreeBSD's libthr. The - // workaround is to set the handler to SIG_DFL in the signal handler, see below - sa.sa_flags = reset_handler ? SA_RESETHAND : 0; -#endif //__FreeBSD__ - sigfillset(&sa.sa_mask); - sigaction(signal, &sa, nullptr); + sigfillset(&sa.sa_mask); // mask all signals while in the handler + sigaction(signal, &sa, &saved_sa); +} + +// Utility function to restore an OS signal handler to its previous setting +static void RestoreSignalHandler(int signal) { + sigaction(signal, &saved_sa, nullptr); } // Raw signal handler for triggering a NodeReport - runs on an arbitrary thread static void SignalDump(int signo) { -#ifdef __FreeBSD__ - struct sigaction sa; - memset(&sa, 0, sizeof(sa)); - sa.sa_handler = SIG_DFL; - sigaction(signo, &sa, nullptr); -#endif // __FreeBSD__ // Check atomic for NodeReport 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 @@ -277,7 +284,7 @@ static void SetupSignalHandler() { Nan::ThrowError("nodereport: initialization failed, uv_async_init() returned error\n"); } uv_unref(reinterpret_cast(&nodereport_trigger_async)); - RegisterSignalHandler(nodereport_signal, SignalDump, false); + RegisterSignalHandler(nodereport_signal, SignalDump); } } #endif diff --git a/src/node_report.cc b/src/node_report.cc index 621fe59..9b029dd 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -108,15 +108,15 @@ unsigned int ProcessNodeReportEvents(const char *args) { unsigned int ProcessNodeReportCoreSwitch(const char *args) { if (strlen(args) == 0) { fprintf(stderr, "Missing argument for nodereport core 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 { - fprintf(stderr, "Unrecognised argument for nodereport core switch option: %s\n", args); + // 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 { + fprintf(stderr, "Unrecognised argument for nodereport core switch option: %s\n", args); + } } return 1; // Default is to produce core dumps } @@ -127,17 +127,17 @@ unsigned int ProcessNodeReportSignal(const char *args) { #else if (strlen(args) == 0) { fprintf(stderr, "Missing argument for nodereport signal option\n"); - return 0; - } - // Parse the supplied switch - if (!strncmp(args, "SIGUSR2", sizeof("SIGUSR2") - 1)) { - return SIGUSR2; - } else if (!strncmp(args, "SIGQUIT", sizeof("SIGQUIT") - 1)) { - return SIGQUIT; } else { - fprintf(stderr, "Unrecognised argument for nodereport signal option: %s\n", args); + // Parse the supplied switch + if (!strncmp(args, "SIGUSR2", sizeof("SIGUSR2") - 1)) { + return SIGUSR2; + } else if (!strncmp(args, "SIGQUIT", sizeof("SIGQUIT") - 1)) { + return SIGQUIT; + } else { + fprintf(stderr, "Unrecognised argument for nodereport signal option: %s\n", args); + } } - return SIGUSR2; // Default is SIGUSR2 + return SIGUSR2; // Default signal is SIGUSR2 #endif } From efb729e92cfed9548107206b8baa6748715587db Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Wed, 12 Oct 2016 15:49:10 +0100 Subject: [PATCH 20/24] Trigger events other than API are off by default --- README.md | 40 +++++++++++++++++++--------------------- demo/exception.js | 2 +- demo/fatalerror.js | 2 +- demo/loop.js | 2 +- src/module.cc | 44 +++++++++++++++++++++++++++----------------- src/node_report.cc | 3 +++ src/node_report.h | 2 +- test/test_2.js | 2 +- test/test_3.js | 2 +- test/test_4.js | 2 +- 10 files changed, 56 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 8c61df8..428cca3 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # nodereport -nodereport is an add-on for Node.js, delivered as an NPM module, +nodereport is an add-on for Node.js, delivered as an NPM native module, which provides a human-readable diagnostic summary report, written to file. The report is intended for development, test and production use, to capture and preserve information for problem determination. It includes Javascript and native stack traces, heap statistics, platform information and resource usage etc. With the report enabled, -reports are triggered on unhandled exceptions, fatal errors, signals +reports can be triggered on unhandled exceptions, fatal errors, signals and calls to a Javascript API. Usage: @@ -15,10 +15,8 @@ Usage: var nodereport = require('nodereport'); -By default, this will trigger a NodeReport to be written to the current -directory on unhandled exceptions, fatal errors or (Linux/OSX only) -SIGUSR2 signals. In addition, a NodeReport can be triggered from a -Javascript application via API. The filename of the NodeReport is +By default, this will allow a NodeReport to be triggered via an API +call from a JavaScript application. The filename of the NodeReport is returned. The default filename includes the date, time, PID and a sequence number. Alternatively a filename can be specified on the API call. @@ -38,26 +36,26 @@ The following messages are issued to stderr when a NodeReport is triggered: Writing Node.js error report to file: NodeReport.201605113.145311.26249.001.txt Node.js error report completed -Configuration is available via these environment variables and/or Javascript -API calls: - export NODEREPORT_EVENTS=exception+fatalerror+signal - nodereport.setEvents("exception+fatalerror+signal"); +A NodeReport can also be triggered on unhandled exception and fatal error +events, and/or signals (Linux/OSX only). These and other options can be +enabled or disabled using the following APIs: - export NODEREPORT_COREDUMP=yes/no - nodereport.setCoreDump("yes/no"); - - export NODEREPORT_SIGNAL=SIGUSR2/SIGQUIT - nodereport.setSignal("SIGUSR2/SIGQUIT"); + nodereport.setEvents("exception+fatalerror+signal+apicall"); + nodereport.setSignal("SIGUSR2|SIGQUIT"); + nodereport.setFileName("stdout|stderr|"); + nodereport.setDirectory(""); + nodereport.setCoreDump("yes|no"); + nodereport.setVerbose("yes|no"); - export NODEREPORT_FILENAME=stdout/stderr/ - nodereport.setFileName("stdout/stderr/"); +Configuration on module initialisation is also available via environment variables: + export NODEREPORT_EVENTS=exception+fatalerror+signal+apicall + export NODEREPORT_SIGNAL=SIGUSR2|SIGQUIT + export NODEREPORT_FILENAME=stdout|stderr| export NODEREPORT_DIRECTORY= - nodereport.setDirectory(""); - - export NODEREPORT_VERBOSE=yes/no - nodereport.setVerbose("yes/no"); + export NODEREPORT_COREDUMP=yes|no + export NODEREPORT_VERBOSE=yes|no Sample programs for triggering NodeReports are provided in the node_modules/nodereport/demo directory: diff --git a/demo/exception.js b/demo/exception.js index fb9597b..e677acf 100644 --- a/demo/exception.js +++ b/demo/exception.js @@ -1,5 +1,5 @@ // Example - generation of NodeReport on uncaught exception -require('nodereport'); +require('nodereport').setEvents("exception"); var http = require("http"); var count = 0; diff --git a/demo/fatalerror.js b/demo/fatalerror.js index c2f20d8..0abba5c 100644 --- a/demo/fatalerror.js +++ b/demo/fatalerror.js @@ -1,5 +1,5 @@ // Example - generation of Nodereport on fatal error (Javascript heap OOM) -require('nodereport'); +require('nodereport').setEvents("fatalerror"); var http = require('http'); var count = 0; diff --git a/demo/loop.js b/demo/loop.js index 7f1707d..d671f53 100644 --- a/demo/loop.js +++ b/demo/loop.js @@ -1,5 +1,5 @@ // Example - geneation of Nodereport via signal for a looping application -require('nodereport'); +require('nodereport').setEvents("signal"); var http = require("http"); var count = 0; diff --git a/src/module.cc b/src/module.cc index 6370b21..8ac3d10 100644 --- a/src/module.cc +++ b/src/module.cc @@ -17,21 +17,25 @@ static void SetupSignalHandler(); #endif // Default nodereport option settings -static unsigned int nodereport_events = NR_EXCEPTION + NR_FATALERROR + NR_SIGNAL + NR_JSAPICALL; +static unsigned int nodereport_events = NR_APICALL; static unsigned int nodereport_core = 1; static unsigned int nodereport_verbose = 0; - -#ifdef _WIN32 -static unsigned int nodereport_signal = 0; // no-op on Windows -#else // signal support - on Unix/OSX only +#ifdef _WIN32 // trigger signal 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; -static uv_sem_t report_semaphore; -static uv_async_t nodereport_trigger_async; -static uv_mutex_t node_isolate_mutex; -static struct sigaction saved_sa; +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 wachdog 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 v8::Isolate* node_isolate; /******************************************************************************* @@ -52,10 +56,11 @@ NAN_METHOD(TriggerReport) { Nan::ThrowSyntaxError("nodereport: filename parameter is too long"); } } - - TriggerNodeReport(isolate, kJavaScript, "JavaScript API", "TriggerReport (nodereport/src/module.cc)", filename); - // Return value is the NodeReport filename - info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked()); + if (nodereport_events & NR_APICALL) { + TriggerNodeReport(isolate, kJavaScript, "JavaScript API", "TriggerReport (nodereport/src/module.cc)", filename); + // Return value is the NodeReport filename + info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked()); + } } /******************************************************************************* @@ -69,21 +74,23 @@ NAN_METHOD(SetEvents) { nodereport_events = ProcessNodeReportEvents(*parameter); // If NodeReport newly requested for fatalerror, set up the V8 call-back - if ((nodereport_events & NR_FATALERROR) && !(previous_events & NR_FATALERROR)) { + if ((nodereport_events & NR_FATALERROR) && (error_hook_initialised == false)) { isolate->SetFatalErrorHandler(OnFatalError); + error_hook_initialised = true; } // If NodeReport newly requested for exceptions, tell V8 to capture stack trace and set up the callback - if ((nodereport_events & NR_EXCEPTION) && !(previous_events & NR_EXCEPTION)) { + if ((nodereport_events & NR_EXCEPTION) && (exception_hook_initialised == false)) { isolate->SetCaptureStackTraceForUncaughtExceptions(true, 32, v8::StackTrace::kDetailed); // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); + exception_hook_initialised = true; } #ifndef _WIN32 // If NodeReport newly requested on external user signal set up watchdog thread and handler - if ((nodereport_events & NR_SIGNAL) && !(previous_events & NR_SIGNAL)) { + if ((nodereport_events & NR_SIGNAL) && (signal_thread_initialised == false)) { SetupSignalHandler(); } // If NodeReport no longer required on external user signal, reset the OS signal handler @@ -285,6 +292,7 @@ static void SetupSignalHandler() { } uv_unref(reinterpret_cast(&nodereport_trigger_async)); RegisterSignalHandler(nodereport_signal, SignalDump); + signal_thread_initialised = true; } } #endif @@ -327,6 +335,7 @@ void Initialize(v8::Local exports) { // If NodeReport requested for fatalerror, set up the V8 call-back if (nodereport_events & NR_FATALERROR) { isolate->SetFatalErrorHandler(OnFatalError); + error_hook_initialised = true; } // If NodeReport requested for exceptions, tell V8 to capture stack trace and set up the callback @@ -335,6 +344,7 @@ void Initialize(v8::Local exports) { // The hook for uncaught exception won't get called unless the --abort_on_uncaught_exception option is set v8::V8::SetFlagsFromString("--abort_on_uncaught_exception", sizeof("--abort_on_uncaught_exception")-1); isolate->SetAbortOnUncaughtExceptionCallback(OnUncaughtException); + exception_hook_initialised = true; } #ifndef _WIN32 diff --git a/src/node_report.cc b/src/node_report.cc index 9b029dd..ecc851b 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -94,6 +94,9 @@ unsigned int ProcessNodeReportEvents(const char *args) { } 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 { fprintf(stderr, "Unrecognised argument for nodereport events option: %s\n", cursor); return 0; diff --git a/src/node_report.h b/src/node_report.h index 8b91668..a49c39c 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -21,7 +21,7 @@ using v8::Value; #define NR_EXCEPTION 0x01 #define NR_FATALERROR 0x02 #define NR_SIGNAL 0x04 -#define NR_JSAPICALL 0x08 +#define NR_APICALL 0x08 // Maximum file and path name lengths #define NR_MAXNAME 64 diff --git a/test/test_2.js b/test/test_2.js index df43195..e182b78 100644 --- a/test/test_2.js +++ b/test/test_2.js @@ -1,5 +1,5 @@ // Testcase to produce an uncaught exception -require('nodereport'); +require('nodereport').setEvents("exception"); console.log('exception.js: throwing an uncaught user exception....'); diff --git a/test/test_3.js b/test/test_3.js index 20a86ca..486f730 100644 --- a/test/test_3.js +++ b/test/test_3.js @@ -1,5 +1,5 @@ // Testcase to produce a fatal error (javascript heap OOM) -require('nodereport'); +require('nodereport').setEvents("fatalerror"); console.log('fatalerror.js: allocating excessive javascript heap memory....'); var list = []; diff --git a/test/test_4.js b/test/test_4.js index 0d6c0d2..3858d91 100644 --- a/test/test_4.js +++ b/test/test_4.js @@ -1,5 +1,5 @@ // Testcase to loop in Javascript code -require('nodereport'); +require('nodereport').setEvents("signal"); console.log('loop.js: going into loop now.... use kill -USR2 to trigger NodeReport'); From 2b5e0a0108b830156d08dc9f6df6e46aa5e50390 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Wed, 12 Oct 2016 16:03:36 +0100 Subject: [PATCH 21/24] Missing ifdef, no signal support on Windows --- src/module.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/module.cc b/src/module.cc index 8ac3d10..0779308 100644 --- a/src/module.cc +++ b/src/module.cc @@ -104,6 +104,7 @@ NAN_METHOD(SetCoreDump) { nodereport_core = ProcessNodeReportCoreSwitch(*parameter); } NAN_METHOD(SetSignal) { +#ifndef _WIN32 Nan::Utf8String parameter(info[0]); unsigned int previous_signal = nodereport_signal; // save previous setting nodereport_signal = ProcessNodeReportSignal(*parameter); @@ -113,6 +114,7 @@ NAN_METHOD(SetSignal) { RestoreSignalHandler(previous_signal); RegisterSignalHandler(nodereport_signal, SignalDump); } +#endif } NAN_METHOD(SetFileName) { Nan::Utf8String parameter(info[0]); From 43658622140d47b4ee5879ffc0232c3419b0985f Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Fri, 14 Oct 2016 16:08:54 +0100 Subject: [PATCH 22/24] Improve signal functions, fix K&Risms, whitespace etc --- .gitignore | 76 ++++++++++---------- README.md | 3 +- demo/loop.js | 2 +- src/module.cc | 84 +++++++++++----------- src/node_report.cc | 171 ++++++++++++++++++++++----------------------- src/node_report.h | 16 ++--- 6 files changed, 171 insertions(+), 181 deletions(-) diff --git a/.gitignore b/.gitignore index f1a566b..6fa3448 100644 --- a/.gitignore +++ b/.gitignore @@ -1,38 +1,38 @@ -# Compiled Object files -*.slo -*.lo -*.o -*.obj - -# Precompiled Headers -*.gch -*.pch - -# Compiled Dynamic libraries -*.so -*.dylib -*.dll - -# Fortran module files -*.mod -*.smod - -# Compiled Static libraries -*.lai -*.la -*.a -*.lib - -# Executables -*.exe -*.out -*.app -*.node - -# Project files -*.project -*.cproject - -# Build directories -/build/ -/node_modules/ +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +*.node + +# Project files +*.project +*.cproject + +# Build directories +/build/ +/node_modules/ diff --git a/README.md b/README.md index 428cca3..9728867 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,6 @@ The following messages are issued to stderr when a NodeReport is triggered: Writing Node.js error report to file: NodeReport.201605113.145311.26249.001.txt Node.js error report completed - A NodeReport can also be triggered on unhandled exception and fatal error events, and/or signals (Linux/OSX only). These and other options can be enabled or disabled using the following APIs: @@ -58,7 +57,7 @@ Configuration on module initialisation is also available via environment variabl export NODEREPORT_VERBOSE=yes|no Sample programs for triggering NodeReports are provided in the -node_modules/nodereport/demo directory: +node_modules/nodereport/demo directory: api.js - NodeReport triggered by Javascript API call exception.js - NodeReport triggered by unhandled exception diff --git a/demo/loop.js b/demo/loop.js index d671f53..b50e44b 100644 --- a/demo/loop.js +++ b/demo/loop.js @@ -49,5 +49,5 @@ console.log('loop.js: Go to http://:8080/ or http://localhost:8080/'); setTimeout(function() { console.log('loop.js: timeout expired, exiting.'); - process.exit(0); + process.exit(0); }, 60000); diff --git a/src/module.cc b/src/module.cc index 0779308..984a86b 100644 --- a/src/module.cc +++ b/src/module.cc @@ -9,9 +9,10 @@ bool OnUncaughtException(v8::Isolate* isolate); #ifndef _WIN32 static void SignalDumpAsyncCallback(uv_async_t* handle); inline void* ReportSignalThreadMain(void* unused); -static int StartWatchdogThread(void *(*thread_main) (void* unused)); -static void RegisterSignalHandler(int signal, void (*handler)(int signal)); -static void RestoreSignalHandler(int signal); +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 @@ -20,7 +21,7 @@ static void SetupSignalHandler(); static unsigned int nodereport_events = NR_APICALL; static unsigned int nodereport_core = 1; static unsigned int nodereport_verbose = 0; -#ifdef _WIN32 // trigger signal not supported on Windows +#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 @@ -49,15 +50,15 @@ NAN_METHOD(TriggerReport) { if (info[0]->IsString()) { // Filename parameter supplied - Nan::Utf8String filename_parameter(info[0]->ToString()); + Nan::Utf8String filename_parameter(info[0]); if (filename_parameter.length() < NR_MAXNAME) { snprintf(filename, sizeof(filename), "%s", *filename_parameter); } else { - Nan::ThrowSyntaxError("nodereport: filename parameter is too long"); + Nan::ThrowError("nodereport: filename parameter is too long"); } } if (nodereport_events & NR_APICALL) { - TriggerNodeReport(isolate, kJavaScript, "JavaScript API", "TriggerReport (nodereport/src/module.cc)", filename); + TriggerNodeReport(isolate, kJavaScript, "JavaScript API", __func__, filename); // Return value is the NodeReport filename info.GetReturnValue().Set(Nan::New(filename).ToLocalChecked()); } @@ -69,7 +70,7 @@ NAN_METHOD(TriggerReport) { ******************************************************************************/ NAN_METHOD(SetEvents) { Nan::Utf8String parameter(info[0]); - v8::Isolate* isolate = node_isolate; + v8::Isolate* isolate = info.GetIsolate(); unsigned int previous_events = nodereport_events; // save previous settings nodereport_events = ProcessNodeReportEvents(*parameter); @@ -95,7 +96,7 @@ NAN_METHOD(SetEvents) { } // If NodeReport no longer required on external user signal, reset the OS signal handler if (!(nodereport_events & NR_SIGNAL) && (previous_events & NR_SIGNAL)) { - RestoreSignalHandler(nodereport_signal); + RestoreSignalHandler(nodereport_signal, &saved_sa); } #endif } @@ -111,8 +112,8 @@ NAN_METHOD(SetSignal) { // 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); - RegisterSignalHandler(nodereport_signal, SignalDump); + RestoreSignalHandler(previous_signal, &saved_sa); + RegisterSignalHandler(nodereport_signal, SignalDump, &saved_sa); } #endif } @@ -130,10 +131,8 @@ NAN_METHOD(SetVerbose) { } /******************************************************************************* - * Callbacks for triggering NodeReport on failure events (as configured) - * - fatal error - * - uncaught exception - * - signal + * Callbacks for triggering NodeReport on fatal error, uncaught exception and + * external signals ******************************************************************************/ static void OnFatalError(const char* location, const char* message) { if (location) { @@ -143,7 +142,7 @@ static void OnFatalError(const char* location, const char* message) { } // Trigger NodeReport if requested if (nodereport_events & NR_FATALERROR) { - TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, NULL); + TriggerNodeReport(Isolate::GetCurrent(), kFatalError, message, location, nullptr); } fflush(stderr); if (nodereport_core) { @@ -156,28 +155,23 @@ static void OnFatalError(const char* location, const char* message) { bool OnUncaughtException(v8::Isolate* isolate) { // Trigger NodeReport if required if (nodereport_events & NR_EXCEPTION) { - TriggerNodeReport(isolate, kException, "exception", "OnUncaughtException (nodereport/src/module.cc)", NULL); + TriggerNodeReport(isolate, kException, "exception", __func__, nullptr); } - if (nodereport_core) { - return true; - } else { - return false; - } + return nodereport_core; } #ifndef _WIN32 -static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { +static void SignalDumpInterruptCallback(Isolate* isolate, void* data) { if (report_signal != 0) { if (nodereport_verbose) { - fprintf(stdout,"nodereport: SignalDumpInterruptCallback handling signal\n"); + fprintf(stdout, "nodereport: SignalDumpInterruptCallback handling signal\n"); } if (nodereport_events & NR_SIGNAL) { if (nodereport_verbose) { - fprintf(stdout,"nodereport: SignalDumpInterruptCallback triggering NodeReport\n"); + fprintf(stdout, "nodereport: SignalDumpInterruptCallback triggering NodeReport\n"); } TriggerNodeReport(isolate, kSignal_JS, - node::signo_string(report_signal), - "node::SignalDumpInterruptCallback()", NULL); + node::signo_string(report_signal), __func__, nullptr); } report_signal = 0; } @@ -185,15 +179,14 @@ static void SignalDumpInterruptCallback(Isolate *isolate, void *data) { static void SignalDumpAsyncCallback(uv_async_t* handle) { if (report_signal != 0) { if (nodereport_verbose) { - fprintf(stdout,"nodereport: SignalDumpAsyncCallback handling signal\n"); + fprintf(stdout, "nodereport: SignalDumpAsyncCallback handling signal\n"); } if (nodereport_events & NR_SIGNAL) { if (nodereport_verbose) { - fprintf(stdout,"nodereport: SignalDumpAsyncCallback triggering NodeReport\n"); + fprintf(stdout, "nodereport: SignalDumpAsyncCallback triggering NodeReport\n"); } TriggerNodeReport(Isolate::GetCurrent(), kSignal_UV, - node::signo_string(report_signal), - "node::SignalDumpAsyncCallback()", NULL); + node::signo_string(report_signal), __func__, nullptr); } report_signal = 0; } @@ -207,18 +200,19 @@ static void SignalDumpAsyncCallback(uv_async_t* handle) { * - ReportSignalThreadMain() - implementation of watchdog thread * - SetupSignalHandler() - initialisation of signal handlers and threads ******************************************************************************/ - // Utility function to register an OS signal handler -static void RegisterSignalHandler(int signal, void (*handler)(int signal)) { +// 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(signal, &sa, &saved_sa); + sigaction(signo, &sa, saved_sa); } // Utility function to restore an OS signal handler to its previous setting -static void RestoreSignalHandler(int signal) { - sigaction(signal, &saved_sa, nullptr); +static void RestoreSignalHandler(int signo, struct sigaction* saved_sa) { + sigaction(signo, saved_sa, nullptr); } // Raw signal handler for triggering a NodeReport - runs on an arbitrary thread @@ -230,7 +224,7 @@ static void SignalDump(int signo) { } // Utility function to start a watchdog thread - used for processing signals -static int StartWatchdogThread(void *(*thread_main) (void* unused)) { +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 @@ -264,7 +258,7 @@ inline void* ReportSignalThreadMain(void* unused) { uv_mutex_lock(&node_isolate_mutex); if (auto isolate = node_isolate) { // Request interrupt callback for running JavaScript code - isolate->RequestInterrupt(SignalDumpInterruptCallback, NULL); + isolate->RequestInterrupt(SignalDumpInterruptCallback, nullptr); // Event loop may be idle, so also request an async callback uv_async_send(&nodereport_trigger_async); } @@ -293,7 +287,7 @@ static void SetupSignalHandler() { Nan::ThrowError("nodereport: initialization failed, uv_async_init() returned error\n"); } uv_unref(reinterpret_cast(&nodereport_trigger_async)); - RegisterSignalHandler(nodereport_signal, SignalDump); + RegisterSignalHandler(nodereport_signal, SignalDump, &saved_sa); signal_thread_initialised = true; } } @@ -310,27 +304,27 @@ void Initialize(v8::Local exports) { SetLoadTime(); const char* verbose_switch = secure_getenv("NODEREPORT_VERBOSE"); - if (verbose_switch != NULL) { + if (verbose_switch != nullptr) { nodereport_verbose = ProcessNodeReportVerboseSwitch(verbose_switch); } const char* trigger_events = secure_getenv("NODEREPORT_EVENTS"); - if (trigger_events != NULL) { + if (trigger_events != nullptr) { nodereport_events = ProcessNodeReportEvents(trigger_events); } const char* core_dump_switch = secure_getenv("NODEREPORT_COREDUMP"); - if (core_dump_switch != NULL) { + if (core_dump_switch != nullptr) { nodereport_core = ProcessNodeReportCoreSwitch(core_dump_switch); } const char* trigger_signal = secure_getenv("NODEREPORT_SIGNAL"); - if (trigger_signal != NULL) { + if (trigger_signal != nullptr) { nodereport_signal = ProcessNodeReportSignal(trigger_signal); } const char* report_name = secure_getenv("NODEREPORT_FILENAME"); - if (report_name != NULL) { + if (report_name != nullptr) { ProcessNodeReportFileName(report_name); } const char* directory_name = secure_getenv("NODEREPORT_DIRECTORY"); - if (directory_name != NULL) { + if (directory_name != nullptr) { ProcessNodeReportDirectory(directory_name); } diff --git a/src/node_report.cc b/src/node_report.cc index ecc851b..ec06c8f 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -31,7 +31,7 @@ #endif #ifndef _WIN32 -extern char **environ; +extern char** environ; #endif namespace nodereport { @@ -47,17 +47,17 @@ using v8::String; using v8::V8; // Internal/static function declarations -static void PrintVersionInformation(FILE *fp); -static void PrintJavaScriptStack(FILE *fp, Isolate* isolate, DumpEvent event, const char *location); +static void PrintVersionInformation(FILE* fp); +static void PrintJavaScriptStack(FILE* fp, Isolate* isolate, DumpEvent event, const char* location); static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event); -static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, int index, void *pc); -static void PrintNativeStack(FILE *fp); +static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, int index, void* pc); +static void PrintNativeStack(FILE* fp); #ifndef _WIN32 -static void PrintResourceUsage(FILE *fp); +static void PrintResourceUsage(FILE* fp); #endif -static void PrintGCStatistics(FILE *fp, Isolate* isolate); -static void PrintSystemInformation(FILE *fp, Isolate* isolate); -static void WriteInteger(FILE *fp, size_t value); +static void PrintGCStatistics(FILE* fp, Isolate* isolate); +static void PrintSystemInformation(FILE* fp, Isolate* isolate); +static void WriteInteger(FILE* fp, size_t value); // Global variables static int seq = 0; // sequence number for NodeReport filenames @@ -80,7 +80,7 @@ static struct tm loadtime_tm_struct; // module load time * NodeReport directory * Verbose mode ******************************************************************************/ -unsigned int ProcessNodeReportEvents(const char *args) { +unsigned int ProcessNodeReportEvents(const char* args) { // Parse the supplied event types unsigned int event_flags = 0; const char* cursor = args; @@ -92,14 +92,14 @@ unsigned int ProcessNodeReportEvents(const char *args) { event_flags |= NR_FATALERROR; cursor += sizeof("fatalerror") - 1; } else if (!strncmp(cursor, "signal", sizeof("signal") - 1)) { - event_flags |= NR_SIGNAL; - cursor += 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; + event_flags |= NR_APICALL; + cursor += sizeof("apicall") - 1; } else { fprintf(stderr, "Unrecognised argument for nodereport events option: %s\n", cursor); - return 0; + return 0; } if (*cursor == '+') { cursor++; // Hop over the '+' separator @@ -108,7 +108,7 @@ unsigned int ProcessNodeReportEvents(const char *args) { return event_flags; } -unsigned int ProcessNodeReportCoreSwitch(const char *args) { +unsigned int ProcessNodeReportCoreSwitch(const char* args) { if (strlen(args) == 0) { fprintf(stderr, "Missing argument for nodereport core switch option\n"); } else { @@ -124,7 +124,7 @@ unsigned int ProcessNodeReportCoreSwitch(const char *args) { return 1; // Default is to produce core dumps } -unsigned int ProcessNodeReportSignal(const char *args) { +unsigned int ProcessNodeReportSignal(const char* args) { #ifdef _WIN32 return 0; // no-op on Windows #else @@ -144,7 +144,7 @@ unsigned int ProcessNodeReportSignal(const char *args) { #endif } -void ProcessNodeReportFileName(const char *args) { +void ProcessNodeReportFileName(const char* args) { if (strlen(args) == 0) { fprintf(stderr, "Missing argument for nodereport filename option\n"); return; @@ -156,7 +156,7 @@ void ProcessNodeReportFileName(const char *args) { snprintf(report_filename, sizeof(report_filename), "%s", args); } -void ProcessNodeReportDirectory(const char *args) { +void ProcessNodeReportDirectory(const char* args) { if (strlen(args) == 0) { fprintf(stderr, "Missing argument for nodereport directory option\n"); return; @@ -168,7 +168,7 @@ void ProcessNodeReportDirectory(const char *args) { snprintf(report_directory, sizeof(report_directory), "%s", args); } -unsigned int ProcessNodeReportVerboseSwitch(const char *args) { +unsigned int ProcessNodeReportVerboseSwitch(const char* args) { if (strlen(args) == 0) { fprintf(stderr, "Missing argument for nodereport verbose switch option\n"); return 0; @@ -192,7 +192,7 @@ void SetLoadTime() { GetLocalTime(&loadtime_tm_struct); #else // UNIX, OSX struct timeval time_val; - gettimeofday(&time_val, NULL); + gettimeofday(&time_val, nullptr); localtime_r(&time_val.tv_sec, &loadtime_tm_struct); #endif } @@ -202,11 +202,11 @@ void SetLoadTime() { * Parameters: * Isolate* isolate * DumpEvent event - * const char *message - * const char *location - * char *name - in/out - returns the NodeReport filename + * const char* message + * const char* location + * char* name - in/out - returns the NodeReport filename ******************************************************************************/ -void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, const char *location, char *name) { +void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name) { // Recursion check for NodeReport in progress, bail out if (report_active) return; report_active = true; @@ -219,7 +219,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c #else // UNIX, OSX struct timeval time_val; struct tm tm_struct; - gettimeofday(&time_val, NULL); + gettimeofday(&time_val, nullptr); localtime_r(&time_val.tv_sec, &tm_struct); pid_t pid = getpid(); #endif @@ -227,7 +227,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c // Determine the required NodeReport filename. In order of priority: // 1) supplied on API 2) configured on startup 3) default generated char filename[NR_MAXNAME + 1] = ""; - if (name != NULL && strlen(name) > 0) { + 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) { @@ -237,13 +237,12 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c // Construct the NodeReport filename, with timestamp, pid and sequence number snprintf(filename, sizeof(filename), "%s", "NodeReport"); 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); + pid, seq); #else // UNIX, OSX snprintf(&filename[strlen(filename)], sizeof(filename) - strlen(filename), ".%4d%02d%02d.%02d%02d%02d.%d.%03d.txt", @@ -254,7 +253,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c } // Open the NodeReport file stream for writing. Supports stdout/err, user-specified or (default) generated name - FILE *fp = NULL; + FILE* fp = nullptr; if (!strncmp(filename, "stdout", sizeof("stdout") - 1)) { fp = stdout; } else if (!strncmp(filename, "stderr", sizeof("stderr") - 1)) { @@ -263,19 +262,17 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c // Regular file. Append filename to directory path if one was specified if (strlen(report_directory) > 0) { char pathname[NR_MAXPATH + NR_MAXNAME + 1] = ""; - snprintf(pathname, sizeof(pathname), "%s", report_directory); #ifdef _WIN32 - strcat(pathname, "\\"); + snprintf(pathname, sizeof(pathname), "%s%s%s", report_directory, "\\", filename); #else - strcat(pathname, "/"); + snprintf(pathname, sizeof(pathname), "%s%s%s", report_directory, "/", filename); #endif - strncat(pathname, filename, NR_MAXNAME); fp = fopen(pathname, "w"); } else { fp = fopen(filename, "w"); } // Check for errors on the file open - if (fp == NULL) { + if (fp == nullptr) { if (strlen(report_directory) > 0) { fprintf(stderr, "\nFailed to open Node.js report file: %s directory: %s (errno: %d)\n", filename, report_directory, errno); } else { @@ -343,9 +340,9 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c #ifndef _WIN32 fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== Node.js libuv Handle Summary ==============================================\n"); - fprintf(fp,"\n(Flags: R=Ref, A=Active, I=Internal)\n"); - fprintf(fp,"\nFlags Type Address\n"); - uv_print_all_handles(NULL, fp); + fprintf(fp, "\n(Flags: R=Ref, A=Active, I=Internal)\n"); + fprintf(fp, "\nFlags Type Address\n"); + uv_print_all_handles(nullptr, fp); fflush(fp); #endif @@ -357,7 +354,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c fclose(fp); fprintf(stderr, "Node.js report completed\n"); - if (name != NULL) { + if (name != nullptr) { snprintf(name, NR_MAXNAME + 1, "%s", filename); // return the NodeReport file name } report_active = false; @@ -367,7 +364,7 @@ void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, c * Function to print Node.js version, OS version and machine information * ******************************************************************************/ -static void PrintVersionInformation(FILE *fp) { +static void PrintVersionInformation(FILE* fp) { // Print Node.js and deps component versions fprintf(fp, "\nNode.js version: %s\n", NODE_VERSION); @@ -376,38 +373,38 @@ static void PrintVersionInformation(FILE *fp) { // Print operating system and machine information (Windows) #ifdef _WIN32 - fprintf(fp,"\nOS version: Windows "); + fprintf(fp, "\nOS version: Windows "); #if defined(_MSC_VER) && (_MSC_VER >= 1900) if (IsWindows1OrGreater()) { - fprintf(fp,"10 "); + fprintf(fp, "10 "); } else #endif if (IsWindows8OrGreater()) { - fprintf(fp,"8 "); + fprintf(fp, "8 "); } else if (IsWindows7OrGreater()) { - fprintf(fp,"7 "); + fprintf(fp, "7 "); } else if (IsWindowsXPOrGreater()) { - fprintf(fp,"XP "); + fprintf(fp, "XP "); } if (IsWindowsServer()) { - fprintf(fp,"Server\n"); + fprintf(fp, "Server\n"); } else { - fprintf(fp,"Client\n"); + fprintf(fp, "Client\n"); } TCHAR machine_name[256]; DWORD machine_name_size = 256; if (GetComputerName(machine_name, &machine_name_size)) { - fprintf(fp,"\nMachine: %s %s\n", machine_name); + fprintf(fp, "\nMachine: %s %s\n", machine_name); } #else // Print operating system and machine information (Unix/OSX) struct utsname os_info; if (uname(&os_info) == 0) { - fprintf(fp,"\nOS version: %s %s %s\n",os_info.sysname, os_info.release, os_info.version); + fprintf(fp, "\nOS version: %s %s %s\n", os_info.sysname, os_info.release, os_info.version); #if defined(__GLIBC__) - fprintf(fp,"(glibc: %d.%d)\n", __GLIBC__, __GLIBC_MINOR__); + fprintf(fp, "(glibc: %d.%d)\n", __GLIBC__, __GLIBC_MINOR__); #endif - fprintf(fp,"\nMachine: %s %s\n", os_info.nodename, os_info.machine); + fprintf(fp, "\nMachine: %s %s\n", os_info.nodename, os_info.machine); } #endif } @@ -416,7 +413,7 @@ static void PrintVersionInformation(FILE *fp) { * Function to print the JavaScript stack, if available * ******************************************************************************/ -static void PrintJavaScriptStack(FILE *fp, Isolate* isolate, DumpEvent event, const char *location) { +static void PrintJavaScriptStack(FILE* fp, Isolate* isolate, DumpEvent event, const char* location) { fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== JavaScript Stack Trace ====================================================\n\n"); @@ -440,7 +437,7 @@ static void PrintJavaScriptStack(FILE *fp, Isolate* isolate, DumpEvent event, co break; case kFatalError: #if NODE_VERSION_AT_LEAST(6, 0, 0) - if (!strncmp(location,"MarkCompactCollector", sizeof("MarkCompactCollector") - 1)) { + if (!strncmp(location, "MarkCompactCollector", sizeof("MarkCompactCollector") - 1)) { fprintf(fp, "V8 running in GC - no stack trace available\n"); } else { Message::PrintCurrentStackTrace(isolate, fp); @@ -468,7 +465,7 @@ static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event void* samples[255]; // Initialise the register state - state.pc = NULL; + state.pc = nullptr; state.fp = &state; state.sp = &state; @@ -492,7 +489,7 @@ static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event if (static_cast(i) < info.frames_count) { PrintStackFrame(fp, isolate, stack->GetFrame(i), i, samples[i]); } else { - PrintStackFrame(fp, isolate, stack->GetFrame(i), i, NULL); + PrintStackFrame(fp, isolate, stack->GetFrame(i), i, nullptr); } } } @@ -501,7 +498,7 @@ static void PrintStackFromStackTrace(FILE* fp, Isolate* isolate, DumpEvent event * Function to print a JavaScript stack frame from a V8 StackFrame object * ******************************************************************************/ -static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, int i, void *pc) { +static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, int i, void* pc) { Nan::Utf8String fn_name_s(frame->GetFunctionName()); Nan::Utf8String script_name(frame->GetScriptName()); const int line_number = frame->GetLineNumber(); @@ -542,22 +539,22 @@ static void PrintStackFrame(FILE* fp, Isolate* isolate, Local frame, * ******************************************************************************/ void PrintNativeStack(FILE* fp) { - void *frames[64]; + void* frames[64]; fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== Native Stack Trace ========================================================\n\n"); HANDLE hProcess = GetCurrentProcess(); SymSetOptions(SYMOPT_LOAD_LINES | SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); - SymInitialize(hProcess, NULL, TRUE); + SymInitialize(hProcess, nullptr, TRUE); - WORD numberOfFrames = CaptureStackBackTrace(2, 64, frames, NULL); + 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 = (DWORD64)(frames[i]); + DWORD64 dwAddress = reinterpret_cast(frames[i]); char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; - PSYMBOL_INFO pSymbol = (PSYMBOL_INFO)buffer; + PSYMBOL_INFO pSymbol = reinterpret_cast(buffer); pSymbol->SizeOfStruct = sizeof(SYMBOL_INFO); pSymbol->MaxNameLen = MAX_SYM_NAME; @@ -566,17 +563,17 @@ void PrintNativeStack(FILE* fp) { IMAGEHLP_LINE64 line; line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); if (SymGetLineFromAddr64(hProcess, dwAddress, &dwOffset, &line)) { - fprintf(fp,"%2d: [pc=0x%p] %s [+%d] in %s: line: %lu\n", i, pSymbol->Address, pSymbol->Name, dwOffset, line.FileName, line.LineNumber); + fprintf(fp, "%2d: [pc=0x%p] %s [+%d] in %s: line: %lu\n", i, pSymbol->Address, pSymbol->Name, dwOffset, line.FileName, line.LineNumber); } else { // SymGetLineFromAddr64() failed, just print the address and symbol if (dwOffset64 <= 32) { // sanity check - fprintf(fp,"%2d: [pc=0x%p] %s [+%d]\n", i, pSymbol->Address, pSymbol->Name, dwOffset64); + fprintf(fp, "%2d: [pc=0x%p] %s [+%d]\n", i, pSymbol->Address, pSymbol->Name, dwOffset64); } else { - fprintf(fp,"%2d: [pc=0x%p]\n", i, pSymbol->Address); + fprintf(fp, "%2d: [pc=0x%p]\n", i, pSymbol->Address); } } } else { // SymFromAddr() failed, just print the address - fprintf(fp,"%2d: [pc=0x%p]\n", i, pSymbol->Address); + fprintf(fp, "%2d: [pc=0x%p]\n", i, pSymbol->Address); } } } @@ -593,10 +590,10 @@ void PrintNativeStack(FILE* fp) { // Get the native backtrace (array of instruction addresses) const int size = backtrace(frames, arraysize(frames)); if (size <= 0) { - fprintf(fp,"Native backtrace failed, error %d\n", size); + fprintf(fp, "Native backtrace failed, error %d\n", size); return; } else if (size <=2) { - fprintf(fp,"No frames to print\n"); + fprintf(fp, "No frames to print\n"); return; } @@ -632,7 +629,7 @@ void PrintNativeStack(FILE* fp) { * 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(FILE *fp, Isolate* isolate) { +static void PrintGCStatistics(FILE* fp, Isolate* isolate) { HeapStatistics v8_heap_stats; isolate->GetHeapStatistics(&v8_heap_stats); @@ -675,7 +672,7 @@ static void PrintGCStatistics(FILE *fp, Isolate* isolate) { * Function to print resource usage (Linux/OSX only). * ******************************************************************************/ -static void PrintResourceUsage(FILE *fp) { +static void PrintResourceUsage(FILE* fp) { fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== Resource Usage ============================================================\n"); @@ -684,23 +681,23 @@ static void PrintResourceUsage(FILE *fp) { fprintf(fp, "\nProcess total resource usage:"); if (getrusage(RUSAGE_SELF, &stats) == 0) { #ifdef __APPLE__ - fprintf(fp,"\n User mode CPU: %ld.%06d secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); - fprintf(fp,"\n Kernel mode CPU: %ld.%06d secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + fprintf(fp, "\n User mode CPU: %ld.%06d secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + fprintf(fp, "\n Kernel mode CPU: %ld.%06d secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); #else - fprintf(fp,"\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); - fprintf(fp,"\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + fprintf(fp, "\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + fprintf(fp, "\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); #endif - fprintf(fp,"\n Maximum resident set size: "); - WriteInteger(fp,stats.ru_maxrss * 1024); - fprintf(fp," bytes\n Page faults: %ld (I/O required) %ld (no I/O required)", stats.ru_majflt, stats.ru_minflt); - fprintf(fp,"\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); + fprintf(fp, "\n Maximum resident set size: "); + WriteInteger(fp, stats.ru_maxrss * 1024); + fprintf(fp, " bytes\n Page faults: %ld (I/O required) %ld (no I/O required)", stats.ru_majflt, stats.ru_minflt); + fprintf(fp, "\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); } #ifdef RUSAGE_THREAD fprintf(fp, "\n\nEvent loop thread resource usage:"); if (getrusage(RUSAGE_THREAD, &stats) == 0) { - fprintf(fp,"\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); - fprintf(fp,"\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); - fprintf(fp,"\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); + fprintf(fp, "\n User mode CPU: %ld.%06ld secs", stats.ru_utime.tv_sec, stats.ru_utime.tv_usec); + fprintf(fp, "\n Kernel mode CPU: %ld.%06ld secs", stats.ru_stime.tv_sec, stats.ru_stime.tv_usec); + fprintf(fp, "\n Filesystem activity: %ld reads %ld writes", stats.ru_inblock, stats.ru_oublock); } #endif fprintf(fp, "\n"); @@ -711,7 +708,7 @@ static void PrintResourceUsage(FILE *fp) { * Function to print operating system information. * ******************************************************************************/ -static void PrintSystemInformation(FILE *fp, Isolate* isolate) { +static void PrintSystemInformation(FILE* fp, Isolate* isolate) { fprintf(fp, "\n================================================================================"); fprintf(fp, "\n==== System Information ========================================================\n"); @@ -722,9 +719,9 @@ static void PrintSystemInformation(FILE *fp, Isolate* isolate) { // Get pointer to the environment block lpvEnv = GetEnvironmentStrings(); - if (lpvEnv != NULL) { - // Variable strings are separated by NULL byte, and the block is terminated by a NULL byte. - lpszVariable = (LPTSTR) lpvEnv; + 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) { fprintf(fp, " %s\n", lpszVariable); lpszVariable += lstrlen(lpszVariable) + 1; @@ -734,9 +731,9 @@ static void PrintSystemInformation(FILE *fp, Isolate* isolate) { #else fprintf(fp, "\nEnvironment variables\n"); int index = 1; - char *env_var = *environ; + char* env_var = *environ; - while (env_var != NULL) { + while (env_var != nullptr) { fprintf(fp, " %s\n", env_var); env_var = *(environ + index++); } @@ -782,7 +779,7 @@ const static struct { * Utility function to print out integer values with commas for readability. * ******************************************************************************/ -static void WriteInteger(FILE *fp, size_t value) { +static void WriteInteger(FILE* fp, size_t value) { int thousandsStack[8]; // Sufficient for max 64-bit number int stackTop = 0; int i; diff --git a/src/node_report.h b/src/node_report.h index a49c39c..3ae8324 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -29,14 +29,14 @@ using v8::Value; enum DumpEvent {kException, kFatalError, kSignal_JS, kSignal_UV, kJavaScript}; -void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char *message, const char *location, char* name); +void TriggerNodeReport(Isolate* isolate, DumpEvent event, const char* message, const char* location, char* name); -unsigned int ProcessNodeReportEvents(const char *args); -unsigned int ProcessNodeReportCoreSwitch(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); +unsigned int ProcessNodeReportEvents(const char* args); +unsigned int ProcessNodeReportCoreSwitch(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(); @@ -60,7 +60,7 @@ constexpr size_t arraysize(const T(&)[N]) { return N; } // 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, ...) { +inline static int snprintf(char* buffer, size_t n, const char* format, ...) { va_list argp; va_start(argp, format); int ret = _vscprintf(format, argp); From a46a004dc00320821a949039070ea3dd061cb858 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Fri, 14 Oct 2016 16:46:46 +0100 Subject: [PATCH 23/24] Fix for __func__ not available on VS 2013 --- src/node_report.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/node_report.h b/src/node_report.h index 3ae8324..bc3b67b 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -68,6 +68,8 @@ inline static int snprintf(char* buffer, size_t n, const char* format, ...) { va_end(argp); return ret; } + +#define __func__ __FUNCTION__ #endif // defined( _MSC_VER ) && (_MSC_VER < 1900) } // namespace nodereport From 7cd3f76f7114ec369aec965c133626293f246683 Mon Sep 17 00:00:00 2001 From: Richard Chamberlain Date: Mon, 17 Oct 2016 11:43:43 +0100 Subject: [PATCH 24/24] Add local secure_getenv() and fix sizeof call --- src/node_report.cc | 3 +-- src/node_report.h | 22 +++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/node_report.cc b/src/node_report.cc index ec06c8f..35aa4ec 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -21,7 +21,6 @@ #include #else #include -#include #include #include #include @@ -561,7 +560,7 @@ void PrintNativeStack(FILE* fp) { if (SymFromAddr(hProcess, dwAddress, &dwOffset64, pSymbol)) { DWORD dwOffset = 0; IMAGEHLP_LINE64 line; - line.SizeOfStruct = sizeof(IMAGEHLP_LINE64); + line.SizeOfStruct = sizeof(line); if (SymGetLineFromAddr64(hProcess, dwAddress, &dwOffset, &line)) { fprintf(fp, "%2d: [pc=0x%p] %s [+%d] in %s: line: %lu\n", i, pSymbol->Address, pSymbol->Name, dwOffset, line.FileName, line.LineNumber); } else { diff --git a/src/node_report.h b/src/node_report.h index bc3b67b..d952a48 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -2,9 +2,13 @@ #define SRC_NODE_REPORT_H_ #include "nan.h" -#if !defined(_WIN32) && !defined(__APPLE__) +#ifndef _WIN32 +#include +#include +#ifndef __APPLE__ #include #endif +#endif namespace nodereport { @@ -40,14 +44,14 @@ unsigned int ProcessNodeReportVerboseSwitch(const char* args); void SetLoadTime(); -// secure_getenv() only available in glibc, revert to getenv() otherwise -#if defined(__GLIBC__) -#if !__GLIBC_PREREQ(2, 17) -#define secure_getenv getenv -#endif // !__GLIBC_PREREQ(2, 17) -#else -#define secure_getenv getenv -#endif // defined(__GLIBC__) +// 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