From dfd47aa1e8668c981e87d79a2fc3d0be40858789 Mon Sep 17 00:00:00 2001 From: Anna Henningsen Date: Sun, 10 Feb 2019 00:06:03 +0100 Subject: [PATCH] report: make more items programmatically accessible Prefer structured output over stringified information for libuv handles and the native stack trace. PR-URL: https://github.com/nodejs/node/pull/26019 Reviewed-By: Richard Lau Reviewed-By: Colin Ihrig Reviewed-By: Gus Caplan Reviewed-By: Michael Dawson Reviewed-By: James M Snell --- doc/api/report.md | 79 +++++++++++++++++++++------- src/node_report.cc | 15 +++--- src/node_report.h | 4 +- src/node_report_utils.cc | 109 ++++++++++++++++++++++----------------- 4 files changed, 132 insertions(+), 75 deletions(-) diff --git a/doc/api/report.md b/doc/api/report.md index 89bb203691f7c9..8d6d054c0a10b9 100644 --- a/doc/api/report.md +++ b/doc/api/report.md @@ -78,12 +78,26 @@ is provided below for reference. ] }, "nativeStack": [ - " [pc=0xa7ef87] [/home/nodeuser/project/node/out/Release/node]", - " [pc=0xa81adb] report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::string, v8::Local) [/home/nodeuser/project/node/out/Release/node]", - " [pc=0xa834f2] report::OnUncaughtException(v8::FunctionCallbackInfo const&) [/home/nodeuser/project/node/out/Release/node]", - " [pc=0xbe6b78] [/home/nodeuser/project/node/out/Release/node]", - " [pc=0xbe7596] v8::internal::Builtin_HandleApiCall(int, v8::internal::Object**, v8::internal::Isolate*) [/home/nodeuser/project/node/out/Release/node]", - " [pc=0x1930cae] [/home/nodeuser/project/node/out/Release/node]" + { + "pc": "0x000055b57f07a9ef", + "symbol": "report::GetNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, v8::Local, std::ostream&) [./node]" + }, + { + "pc": "0x000055b57f07cf03", + "symbol": "report::GetReport(v8::FunctionCallbackInfo const&) [./node]" + }, + { + "pc": "0x000055b57f1bccfd", + "symbol": " [./node]" + }, + { + "pc": "0x000055b57f1be048", + "symbol": "v8::internal::Builtin_HandleApiCall(int, v8::internal::Object**, v8::internal::Isolate*) [./node]" + }, + { + "pc": "0x000055b57feeda0e", + "symbol": " [./node]" + } ], "javascriptHeap": { "totalMemory": 6127616, @@ -175,46 +189,75 @@ is provided below for reference. "details": "" }, { + "repeat": 0, + "firesInMsFromNow": 94403548320796, + "expired": true, "type": "timer", "is_active": false, "is_referenced": false, - "address": "0x00007fff5fbfeab0", - "details": "repeat: 0, timeout expired: 18075165916 ms ago" + "address": "0x00007fff5fbfeab0" }, { "type": "check", "is_active": true, "is_referenced": false, - "address": "0x00007fff5fbfeb48", - "details": "" + "address": "0x00007fff5fbfeb48" }, { "type": "idle", "is_active": false, "is_referenced": true, - "address": "0x00007fff5fbfebc0", - "details": "" + "address": "0x00007fff5fbfebc0" }, { "type": "prepare", "is_active": false, "is_referenced": false, - "address": "0x00007fff5fbfec38", - "details": "" + "address": "0x00007fff5fbfec38" }, { "type": "check", "is_active": false, "is_referenced": false, - "address": "0x00007fff5fbfecb0", - "details": "" + "address": "0x00007fff5fbfecb0" }, { "type": "async", "is_active": true, "is_referenced": false, - "address": "0x000000010188f2e0", - "details": "" + "address": "0x000000010188f2e0" + }, + { + "width": 204, + "height": 55, + "fd": 17, + "writeQueueSize": 0, + "readable": true, + "writable": true, + "type": "tty", + "is_active": false, + "is_referenced": true, + "address": "0x000055b581db0e18" + }, + { + "signum": 28, + "signal": "SIGWINCH", + "type": "signal", + "is_active": true, + "is_referenced": false, + "address": "0x000055b581d80010" + }, + { + "width": 204, + "height": 55, + "fd": 19, + "writeQueueSize": 0, + "readable": true, + "writable": true, + "type": "tty", + "is_active": true, + "is_referenced": true, + "address": "0x000055b581df59f8" }, { "type": "loop", diff --git a/src/node_report.cc b/src/node_report.cc index 7477a2fd240513..1a459a2d1c97a3 100644 --- a/src/node_report.cc +++ b/src/node_report.cc @@ -216,9 +216,8 @@ void GetNodeReport(Isolate* isolate, // Obtain the current time and the pid (platform dependent) TIME_TYPE tm_struct; LocalTime(&tm_struct); - std::string str = "NA"; WriteNodeReport( - isolate, env, message, location, str, out, stackstr, &tm_struct); + isolate, env, message, location, "", out, stackstr, &tm_struct); } // Internal function to coordinate and write the various @@ -249,7 +248,7 @@ static void WriteNodeReport(Isolate* isolate, if (!filename.empty()) writer.json_keyvalue("filename", filename); else - writer.json_keyvalue("filename", "''"); + writer.json_keyvalue("filename", JSONWriter::Null{}); // Report dump event and module load date/time stamps char timebuf[64]; @@ -431,13 +430,13 @@ static void PrintNativeStack(JSONWriter* writer) { const int size = sym_ctx->GetStackTrace(frames, arraysize(frames)); writer->json_arraystart("nativeStack"); int i; - std::ostringstream buf; for (i = 1; i < size; i++) { void* frame = frames[i]; - buf.str(""); - buf << " [pc=" << frame << "] "; - buf << sym_ctx->LookupSymbol(frame).Display().c_str(); - writer->json_element(buf.str()); + writer->json_start(); + writer->json_keyvalue("pc", + ValueToHexString(reinterpret_cast(frame))); + writer->json_keyvalue("symbol", sym_ctx->LookupSymbol(frame).Display()); + writer->json_end(); } writer->json_arrayend(); } diff --git a/src/node_report.h b/src/node_report.h index eb38bee8c697f5..c83c52eb395a58 100644 --- a/src/node_report.h +++ b/src/node_report.h @@ -54,7 +54,6 @@ void GetNodeReport(v8::Isolate* isolate, std::ostream& out); // Function declarations - utility functions in src/node_report_utils.cc -void ReportEndpoints(uv_handle_t* h, std::ostringstream& out); void WalkHandle(uv_handle_t* h, void* arg); std::string EscapeJsonChars(const std::string& str); @@ -157,6 +156,8 @@ class JSONWriter { state_ = kAfterValue; } + struct Null {}; // Usable as a JSON value. + private: template (&addr_storage); uv_any_handle* handle = reinterpret_cast(h); int addr_size = sizeof(addr_storage); int rc = -1; + bool wrote_local_endpoint = false; + bool wrote_remote_endpoint = false; switch (h->type) { case UV_UDP: - rc = uv_udp_getsockname(&(handle->udp), addr, &addr_size); + rc = uv_udp_getsockname(&handle->udp, addr, &addr_size); break; case UV_TCP: - rc = uv_tcp_getsockname(&(handle->tcp), addr, &addr_size); + rc = uv_tcp_getsockname(&handle->tcp, addr, &addr_size); break; default: break; @@ -28,31 +32,42 @@ void ReportEndpoints(uv_handle_t* h, std::ostringstream& out) { uv_getnameinfo_t local; rc = uv_getnameinfo(h->loop, &local, nullptr, addr, NI_NUMERICSERV); - if (rc == 0) - out << local.host << ":" << local.service; + if (rc == 0) { + writer->json_objectstart("localEndpoint"); + writer->json_keyvalue("host", local.host); + writer->json_keyvalue("port", local.service); + writer->json_objectend(); + wrote_local_endpoint = true; + } + } + if (!wrote_local_endpoint) writer->json_keyvalue("localEndpoint", null); - if (h->type == UV_TCP) { - // Get the remote end of the connection. - rc = uv_tcp_getpeername(&(handle->tcp), addr, &addr_size); - if (rc == 0) { - uv_getnameinfo_t remote; - rc = uv_getnameinfo(h->loop, &remote, nullptr, addr, NI_NUMERICSERV); + if (h->type == UV_TCP) { + // Get the remote end of the connection. + rc = uv_tcp_getpeername(&handle->tcp, addr, &addr_size); + if (rc == 0) { + uv_getnameinfo_t remote; + rc = uv_getnameinfo(h->loop, &remote, nullptr, addr, NI_NUMERICSERV); - if (rc == 0) - out << " connected to " << remote.host << ":" << remote.service; - } else if (rc == UV_ENOTCONN) { - out << " (not connected)"; + if (rc == 0) { + writer->json_objectstart("remoteEndpoint"); + writer->json_keyvalue("host", remote.host); + writer->json_keyvalue("port", remote.service); + writer->json_objectend(); + wrote_local_endpoint = true; } } } + if (!wrote_remote_endpoint) writer->json_keyvalue("remoteEndpoint", null); } // Utility function to format libuv path information. -void ReportPath(uv_handle_t* h, std::ostringstream& out) { +static void ReportPath(uv_handle_t* h, JSONWriter* writer) { MallocedBuffer buffer(0); int rc = -1; size_t size = 0; uv_any_handle* handle = reinterpret_cast(h); + bool wrote_filename = false; // First call to get required buffer size. switch (h->type) { case UV_FS_EVENT: @@ -65,7 +80,7 @@ void ReportPath(uv_handle_t* h, std::ostringstream& out) { break; } if (rc == UV_ENOBUFS) { - buffer = MallocedBuffer(size); + buffer = MallocedBuffer(size + 1); switch (h->type) { case UV_FS_EVENT: rc = uv_fs_event_getpath(&(handle->fs_event), buffer.data, &size); @@ -78,55 +93,58 @@ void ReportPath(uv_handle_t* h, std::ostringstream& out) { } if (rc == 0) { // buffer is not null terminated. - std::string name(buffer.data, size); - out << "filename: " << name; + buffer.data[size] = '\0'; + writer->json_keyvalue("filename", buffer.data); + wrote_filename = true; } } + if (!wrote_filename) writer->json_keyvalue("filename", null); } // Utility function to walk libuv handles. void WalkHandle(uv_handle_t* h, void* arg) { const char* type = uv_handle_type_name(h->type); - std::ostringstream data; JSONWriter* writer = static_cast(arg); uv_any_handle* handle = reinterpret_cast(h); + writer->json_start(); + switch (h->type) { case UV_FS_EVENT: case UV_FS_POLL: - ReportPath(h, data); + ReportPath(h, writer); break; case UV_PROCESS: - data << "pid: " << handle->process.pid; + writer->json_keyvalue("pid", handle->process.pid); break; case UV_TCP: case UV_UDP: - ReportEndpoints(h, data); + ReportEndpoints(h, writer); break; case UV_TIMER: { uint64_t due = handle->timer.timeout; uint64_t now = uv_now(handle->timer.loop); - data << "repeat: " << uv_timer_get_repeat(&(handle->timer)); - if (due > now) { - data << ", timeout in: " << (due - now) << " ms"; - } else { - data << ", timeout expired: " << (now - due) << " ms ago"; - } + writer->json_keyvalue("repeat", uv_timer_get_repeat(&handle->timer)); + writer->json_keyvalue("firesInMsFromNow", + static_cast(due - now)); + writer->json_keyvalue("expired", now >= due); break; } case UV_TTY: { int height, width, rc; rc = uv_tty_get_winsize(&(handle->tty), &width, &height); if (rc == 0) { - data << "width: " << width << ", height: " << height; + writer->json_keyvalue("width", width); + writer->json_keyvalue("height", height); } break; } case UV_SIGNAL: // SIGWINCH is used by libuv so always appears. // See http://docs.libuv.org/en/v1.x/signal.html - data << "signum: " << handle->signal.signum << " (" << - node::signo_string(handle->signal.signum) << ")"; + writer->json_keyvalue("signum", handle->signal.signum); + writer->json_keyvalue("signal", + node::signo_string(handle->signal.signum)); break; default: break; @@ -141,13 +159,10 @@ void WalkHandle(uv_handle_t* h, void* arg) { // values they contain. int send_size = 0; int recv_size = 0; - if (h->type == UV_TCP || h->type == UV_UDP) { - data << ", "; - } uv_send_buffer_size(h, &send_size); uv_recv_buffer_size(h, &recv_size); - data << "send buffer size: " << send_size - << ", recv buffer size: " << recv_size; + writer->json_keyvalue("sendBufferSize", send_size); + writer->json_keyvalue("recvBufferSize", recv_size); } #ifndef _WIN32 @@ -157,18 +172,16 @@ void WalkHandle(uv_handle_t* h, void* arg) { int rc = uv_fileno(h, &fd_v); if (rc == 0) { + writer->json_keyvalue("fd", static_cast(fd_v)); switch (fd_v) { case 0: - data << ", stdin"; + writer->json_keyvalue("stdio", "stdin"); break; case 1: - data << ", stdout"; + writer->json_keyvalue("stdio", "stdout"); break; case 2: - data << ", stderr"; - break; - default: - data << ", file descriptor: " << static_cast(fd_v); + writer->json_keyvalue("stdio", "stderr"); break; } } @@ -176,18 +189,18 @@ void WalkHandle(uv_handle_t* h, void* arg) { #endif if (h->type == UV_TCP || h->type == UV_NAMED_PIPE || h->type == UV_TTY) { - data << ", write queue size: " << handle->stream.write_queue_size; - data << (uv_is_readable(&handle->stream) ? ", readable" : "") - << (uv_is_writable(&handle->stream) ? ", writable" : ""); + writer->json_keyvalue("writeQueueSize", handle->stream.write_queue_size); + writer->json_keyvalue("readable", + static_cast(uv_is_readable(&handle->stream))); + writer->json_keyvalue("writable", + static_cast(uv_is_writable(&handle->stream))); } - writer->json_start(); writer->json_keyvalue("type", type); writer->json_keyvalue("is_active", static_cast(uv_is_active(h))); writer->json_keyvalue("is_referenced", static_cast(uv_has_ref(h))); writer->json_keyvalue("address", ValueToHexString(reinterpret_cast(h))); - writer->json_keyvalue("details", data.str()); writer->json_end(); }