Skip to content

Commit 45eeea4

Browse files
committed
src: implement debug output utilities
Implement utilities for easier debugging of Node.js core code, inspired by the HTTP/2 debugging code. Debugging is, however, implemented at runtime rather than at compile time, controlled through a new `NODE_DEBUG_NATIVE=categories` environment variable. The runtime overhead in the debugging-disabled case amounts to 1 well-cachable one-byte read per debug call. PR-URL: #20987 Reviewed-By: Daniel Bevenius <daniel.bevenius@gmail.com> Reviewed-By: Joyee Cheung <joyeec9h3@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 1b8e8e9 commit 45eeea4

File tree

10 files changed

+174
-0
lines changed

10 files changed

+174
-0
lines changed

node.gyp

+1
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@
367367
'src/base_object-inl.h',
368368
'src/connection_wrap.h',
369369
'src/connect_wrap.h',
370+
'src/debug_utils.h',
370371
'src/env.h',
371372
'src/env-inl.h',
372373
'src/handle_wrap.h',

src/async_wrap.cc

+5
Original file line numberDiff line numberDiff line change
@@ -769,6 +769,11 @@ void EmitAsyncDestroy(Isolate* isolate, async_context asyncContext) {
769769
Environment::GetCurrent(isolate), asyncContext.async_id);
770770
}
771771

772+
std::string AsyncWrap::diagnostic_name() const {
773+
return std::string(provider_names[provider_type()]) +
774+
" (" + std::to_string(static_cast<int64_t>(async_id_)) + ")";
775+
}
776+
772777
} // namespace node
773778

774779
NODE_BUILTIN_MODULE_CONTEXT_AWARE(async_wrap, node::AsyncWrap::Initialize)

src/async_wrap.h

+1
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ class AsyncWrap : public BaseObject {
167167
v8::Local<v8::Value>* argv);
168168

169169
virtual size_t self_size() const = 0;
170+
virtual std::string diagnostic_name() const;
170171

171172
static void WeakCallback(const v8::WeakCallbackInfo<DestroyParam> &info);
172173

src/debug_utils.h

+92
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
#ifndef SRC_DEBUG_UTILS_H_
2+
#define SRC_DEBUG_UTILS_H_
3+
4+
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
5+
6+
#include "async_wrap.h"
7+
#include "env.h"
8+
#include <string>
9+
10+
// Use FORCE_INLINE on functions that have a debug-category-enabled check first
11+
// and then ideally only a single function call following it, to maintain
12+
// performance for the common case (no debugging used).
13+
#ifdef __GNUC__
14+
#define FORCE_INLINE __attribute__((always_inline))
15+
#define COLD_NOINLINE __attribute__((cold, noinline))
16+
#else
17+
#define FORCE_INLINE
18+
#define COLD_NOINLINE
19+
#endif
20+
21+
namespace node {
22+
23+
template <typename... Args>
24+
inline void FORCE_INLINE Debug(Environment* env,
25+
DebugCategory cat,
26+
const char* format,
27+
Args&&... args) {
28+
if (!UNLIKELY(env->debug_enabled(cat)))
29+
return;
30+
fprintf(stderr, format, std::forward<Args>(args)...);
31+
}
32+
33+
inline void FORCE_INLINE Debug(Environment* env,
34+
DebugCategory cat,
35+
const char* message) {
36+
if (!UNLIKELY(env->debug_enabled(cat)))
37+
return;
38+
fprintf(stderr, "%s", message);
39+
}
40+
41+
template <typename... Args>
42+
inline void Debug(Environment* env,
43+
DebugCategory cat,
44+
const std::string& format,
45+
Args&&... args) {
46+
Debug(env, cat, format.c_str(), std::forward<Args>(args)...);
47+
}
48+
49+
// Used internally by the 'real' Debug(AsyncWrap*, ...) functions below, so that
50+
// the FORCE_INLINE flag on them doesn't apply to the contents of this function
51+
// as well.
52+
// We apply COLD_NOINLINE to tell the compiler that it's not worth optimizing
53+
// this function for speed and it should rather focus on keeping it out of
54+
// hot code paths. In particular, we want to keep the string concatenating code
55+
// out of the function containing the original `Debug()` call.
56+
template <typename... Args>
57+
void COLD_NOINLINE UnconditionalAsyncWrapDebug(AsyncWrap* async_wrap,
58+
const char* format,
59+
Args&&... args) {
60+
Debug(async_wrap->env(),
61+
static_cast<DebugCategory>(async_wrap->provider_type()),
62+
async_wrap->diagnostic_name() + " " + format + "\n",
63+
std::forward<Args>(args)...);
64+
}
65+
66+
template <typename... Args>
67+
inline void FORCE_INLINE Debug(AsyncWrap* async_wrap,
68+
const char* format,
69+
Args&&... args) {
70+
#ifdef DEBUG
71+
CHECK_NOT_NULL(async_wrap);
72+
#endif
73+
DebugCategory cat =
74+
static_cast<DebugCategory>(async_wrap->provider_type());
75+
if (!UNLIKELY(async_wrap->env()->debug_enabled(cat)))
76+
return;
77+
UnconditionalAsyncWrapDebug(async_wrap, format, std::forward<Args>(args)...);
78+
}
79+
80+
template <typename... Args>
81+
inline void FORCE_INLINE Debug(AsyncWrap* async_wrap,
82+
const std::string& format,
83+
Args&&... args) {
84+
Debug(async_wrap, format.c_str(), std::forward<Args>(args)...);
85+
}
86+
87+
88+
} // namespace node
89+
90+
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
91+
92+
#endif // SRC_DEBUG_UTILS_H_

src/env-inl.h

+18
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,24 @@ inline void Environment::set_http2_state(
519519
http2_state_ = std::move(buffer);
520520
}
521521

522+
bool Environment::debug_enabled(DebugCategory category) const {
523+
#ifdef DEBUG
524+
CHECK_GE(static_cast<int>(category), 0);
525+
CHECK_LT(static_cast<int>(category),
526+
static_cast<int>(DebugCategory::CATEGORY_COUNT));
527+
#endif
528+
return debug_enabled_[static_cast<int>(category)];
529+
}
530+
531+
void Environment::set_debug_enabled(DebugCategory category, bool enabled) {
532+
#ifdef DEBUG
533+
CHECK_GE(static_cast<int>(category), 0);
534+
CHECK_LT(static_cast<int>(category),
535+
static_cast<int>(DebugCategory::CATEGORY_COUNT));
536+
#endif
537+
debug_enabled_[static_cast<int>(category)] = enabled;
538+
}
539+
522540
inline AliasedBuffer<double, v8::Float64Array>*
523541
Environment::fs_stats_field_array() {
524542
return &fs_stats_field_array_;

src/env.cc

+26
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,10 @@ Environment::Environment(IsolateData* isolate_data,
130130

131131
// By default, always abort when --abort-on-uncaught-exception was passed.
132132
should_abort_on_uncaught_toggle_[0] = 1;
133+
134+
std::string debug_cats;
135+
SafeGetenv("NODE_DEBUG_NATIVE", &debug_cats);
136+
set_debug_categories(debug_cats, true);
133137
}
134138

135139
Environment::~Environment() {
@@ -496,6 +500,28 @@ Local<Value> Environment::GetNow() {
496500
}
497501

498502

503+
void Environment::set_debug_categories(const std::string& cats, bool enabled) {
504+
std::string debug_categories = cats;
505+
while (!debug_categories.empty()) {
506+
std::string::size_type comma_pos = debug_categories.find(',');
507+
std::string wanted = ToLower(debug_categories.substr(0, comma_pos));
508+
509+
#define V(name) \
510+
{ \
511+
static const std::string available_category = ToLower(#name); \
512+
if (available_category.find(wanted) != std::string::npos) \
513+
set_debug_enabled(DebugCategory::name, enabled); \
514+
}
515+
516+
DEBUG_CATEGORY_NAMES(V)
517+
518+
if (comma_pos == std::string::npos)
519+
break;
520+
// Use everything after the `,` as the list for the next iteration.
521+
debug_categories = debug_categories.substr(comma_pos);
522+
}
523+
}
524+
499525
void CollectExceptionInfo(Environment* env,
500526
v8::Local<v8::Object> obj,
501527
int errorno,

src/env.h

+19
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,19 @@ struct ContextInfo {
398398
bool is_default = false;
399399
};
400400

401+
// Listing the AsyncWrap provider types first enables us to cast directly
402+
// from a provider type to a debug category. Currently no other debug
403+
// categories are available.
404+
#define DEBUG_CATEGORY_NAMES(V) \
405+
NODE_ASYNC_PROVIDER_TYPES(V)
406+
407+
enum class DebugCategory {
408+
#define V(name) name,
409+
DEBUG_CATEGORY_NAMES(V)
410+
#undef V
411+
CATEGORY_COUNT
412+
};
413+
401414
class Environment {
402415
public:
403416
class AsyncHooks {
@@ -654,6 +667,10 @@ class Environment {
654667
inline http2::Http2State* http2_state() const;
655668
inline void set_http2_state(std::unique_ptr<http2::Http2State> state);
656669

670+
inline bool debug_enabled(DebugCategory category) const;
671+
inline void set_debug_enabled(DebugCategory category, bool enabled);
672+
void set_debug_categories(const std::string& cats, bool enabled);
673+
657674
inline AliasedBuffer<double, v8::Float64Array>* fs_stats_field_array();
658675

659676
// stat fields contains twice the number of entries because `fs.StatWatcher`
@@ -853,6 +870,8 @@ class Environment {
853870
bool http_parser_buffer_in_use_ = false;
854871
std::unique_ptr<http2::Http2State> http2_state_;
855872

873+
bool debug_enabled_[static_cast<int>(DebugCategory::CATEGORY_COUNT)] = {0};
874+
856875
AliasedBuffer<double, v8::Float64Array> fs_stats_field_array_;
857876

858877
std::vector<std::unique_ptr<fs::FileHandleReadWrap>>

src/node.cc

+3
Original file line numberDiff line numberDiff line change
@@ -3302,6 +3302,9 @@ static void PrintHelp() {
33023302
"Environment variables:\n"
33033303
"NODE_DEBUG ','-separated list of core modules\n"
33043304
" that should print debug information\n"
3305+
"NODE_DEBUG_NATIVE ','-separated list of C++ core debug\n"
3306+
" categories that should print debug\n"
3307+
" output\n"
33053308
"NODE_DISABLE_COLORS set to 1 to disable colors in the REPL\n"
33063309
"NODE_EXTRA_CA_CERTS path to additional CA certificates\n"
33073310
" file\n"

src/util-inl.h

+7
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,13 @@ char ToLower(char c) {
293293
return c >= 'A' && c <= 'Z' ? c + ('a' - 'A') : c;
294294
}
295295

296+
std::string ToLower(const std::string& in) {
297+
std::string out(in.size(), 0);
298+
for (size_t i = 0; i < in.size(); ++i)
299+
out[i] = ToLower(in[i]);
300+
return out;
301+
}
302+
296303
bool StringEqualNoCase(const char* a, const char* b) {
297304
do {
298305
if (*a == '\0')

src/util.h

+2
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
#include <stdlib.h>
3535
#include <string.h>
3636

37+
#include <string>
3738
#include <functional> // std::function
3839

3940
namespace node {
@@ -250,6 +251,7 @@ inline void SwapBytes64(char* data, size_t nbytes);
250251

251252
// tolower() is locale-sensitive. Use ToLower() instead.
252253
inline char ToLower(char c);
254+
inline std::string ToLower(const std::string& in);
253255

254256
// strcasecmp() is locale-sensitive. Use StringEqualNoCase() instead.
255257
inline bool StringEqualNoCase(const char* a, const char* b);

0 commit comments

Comments
 (0)