| // Copyright Joyent, Inc. and other Node contributors. |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a |
| // copy of this software and associated documentation files (the |
| // "Software"), to deal in the Software without restriction, including |
| // without limitation the rights to use, copy, modify, merge, publish, |
| // distribute, sublicense, and/or sell copies of the Software, and to permit |
| // persons to whom the Software is furnished to do so, subject to the |
| // following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included |
| // in all copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
| // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
| // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN |
| // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, |
| // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| // USE OR OTHER DEALINGS IN THE SOFTWARE. |
| |
| #include "node_dtrace.h" |
| |
| #ifdef HAVE_DTRACE |
| #include "node_provider.h" |
| #elif HAVE_ETW |
| #include "node_win32_etw_provider-inl.h" |
| #else |
| #define NODE_HTTP_SERVER_REQUEST(arg0, arg1) |
| #define NODE_HTTP_SERVER_REQUEST_ENABLED() (0) |
| #define NODE_HTTP_SERVER_RESPONSE(arg0) |
| #define NODE_HTTP_SERVER_RESPONSE_ENABLED() (0) |
| #define NODE_HTTP_CLIENT_REQUEST(arg0, arg1) |
| #define NODE_HTTP_CLIENT_REQUEST_ENABLED() (0) |
| #define NODE_HTTP_CLIENT_RESPONSE(arg0) |
| #define NODE_HTTP_CLIENT_RESPONSE_ENABLED() (0) |
| #define NODE_NET_SERVER_CONNECTION(arg0) |
| #define NODE_NET_SERVER_CONNECTION_ENABLED() (0) |
| #define NODE_NET_STREAM_END(arg0) |
| #define NODE_NET_STREAM_END_ENABLED() (0) |
| #define NODE_GC_START(arg0, arg1, arg2) |
| #define NODE_GC_DONE(arg0, arg1, arg2) |
| #endif |
| |
| #include "env-inl.h" |
| #include "node_errors.h" |
| |
| #include <cstring> |
| |
| namespace node { |
| |
| using v8::Context; |
| using v8::FunctionCallbackInfo; |
| using v8::GCCallbackFlags; |
| using v8::GCType; |
| using v8::HandleScope; |
| using v8::Isolate; |
| using v8::Local; |
| using v8::Object; |
| using v8::Value; |
| |
| #define SLURP_STRING(obj, member, valp) \ |
| if (!(obj)->IsObject()) { \ |
| return node::THROW_ERR_INVALID_ARG_TYPE(env, \ |
| "expected object for " #obj " to contain string member " #member); \ |
| } \ |
| node::Utf8Value _##member(env->isolate(), \ |
| obj->Get(env->context(), \ |
| OneByteString(env->isolate(), #member)).ToLocalChecked()); \ |
| if ((*(const char **)valp = *_##member) == nullptr) \ |
| *(const char **)valp = "<unknown>"; |
| |
| #define SLURP_INT(obj, member, valp) \ |
| if (!(obj)->IsObject()) { \ |
| return node::THROW_ERR_INVALID_ARG_TYPE( \ |
| env, \ |
| "expected object for " #obj " to contain integer member " #member); \ |
| } \ |
| *valp = obj->Get(env->context(), \ |
| OneByteString(env->isolate(), #member)).ToLocalChecked() \ |
| ->Int32Value(env->context()) \ |
| .FromJust(); |
| |
| #define SLURP_OBJECT(obj, member, valp) \ |
| if (!(obj)->IsObject()) { \ |
| return node::THROW_ERR_INVALID_ARG_TYPE(env, \ |
| "expected object for " #obj " to contain object member " #member); \ |
| } \ |
| *valp = Local<Object>::Cast(obj->Get(env->context(), \ |
| OneByteString(env->isolate(), #member)).ToLocalChecked()); |
| |
| #define SLURP_CONNECTION(arg, conn) \ |
| if (!(arg)->IsObject()) { \ |
| return node::THROW_ERR_INVALID_ARG_TYPE(env, \ |
| "expected argument " #arg " to be a connection object"); \ |
| } \ |
| node_dtrace_connection_t conn; \ |
| Local<Object> _##conn = Local<Object>::Cast(arg); \ |
| Local<Value> _handle = \ |
| (_##conn)->Get(env->context(), \ |
| FIXED_ONE_BYTE_STRING(env->isolate(), "_handle")) \ |
| .ToLocalChecked(); \ |
| if (_handle->IsObject()) { \ |
| SLURP_INT(_handle.As<Object>(), fd, &conn.fd); \ |
| } else { \ |
| conn.fd = -1; \ |
| } \ |
| SLURP_STRING(_##conn, remoteAddress, &conn.remote); \ |
| SLURP_INT(_##conn, remotePort, &conn.port); \ |
| SLURP_INT(_##conn, bufferSize, &conn.buffered); |
| |
| #define SLURP_CONNECTION_HTTP_CLIENT(arg, conn) \ |
| if (!(arg)->IsObject()) { \ |
| return node::THROW_ERR_INVALID_ARG_TYPE(env, \ |
| "expected argument " #arg " to be a connection object"); \ |
| } \ |
| node_dtrace_connection_t conn; \ |
| Local<Object> _##conn = Local<Object>::Cast(arg); \ |
| SLURP_INT(_##conn, fd, &conn.fd); \ |
| SLURP_STRING(_##conn, host, &conn.remote); \ |
| SLURP_INT(_##conn, port, &conn.port); \ |
| SLURP_INT(_##conn, bufferSize, &conn.buffered); |
| |
| #define SLURP_CONNECTION_HTTP_CLIENT_RESPONSE(arg0, arg1, conn) \ |
| if (!(arg0)->IsObject()) { \ |
| return node::THROW_ERR_INVALID_ARG_TYPE(env, \ |
| "expected argument " #arg0 " to be a connection object"); \ |
| } \ |
| if (!(arg1)->IsObject()) { \ |
| return node::THROW_ERR_INVALID_ARG_TYPE(env, \ |
| "expected argument " #arg1 " to be a connection object"); \ |
| } \ |
| node_dtrace_connection_t conn; \ |
| Local<Object> _##conn = Local<Object>::Cast(arg0); \ |
| SLURP_INT(_##conn, fd, &conn.fd); \ |
| SLURP_INT(_##conn, bufferSize, &conn.buffered); \ |
| _##conn = Local<Object>::Cast(arg1); \ |
| SLURP_STRING(_##conn, host, &conn.remote); \ |
| SLURP_INT(_##conn, port, &conn.port); |
| |
| |
| void DTRACE_NET_SERVER_CONNECTION(const FunctionCallbackInfo<Value>& args) { |
| if (!NODE_NET_SERVER_CONNECTION_ENABLED()) |
| return; |
| Environment* env = Environment::GetCurrent(args); |
| SLURP_CONNECTION(args[0], conn); |
| NODE_NET_SERVER_CONNECTION(&conn, conn.remote, conn.port, conn.fd); |
| } |
| |
| |
| void DTRACE_NET_STREAM_END(const FunctionCallbackInfo<Value>& args) { |
| if (!NODE_NET_STREAM_END_ENABLED()) |
| return; |
| Environment* env = Environment::GetCurrent(args); |
| SLURP_CONNECTION(args[0], conn); |
| NODE_NET_STREAM_END(&conn, conn.remote, conn.port, conn.fd); |
| } |
| |
| void DTRACE_HTTP_SERVER_REQUEST(const FunctionCallbackInfo<Value>& args) { |
| node_dtrace_http_server_request_t req; |
| |
| if (!NODE_HTTP_SERVER_REQUEST_ENABLED()) |
| return; |
| |
| Environment* env = Environment::GetCurrent(args); |
| HandleScope scope(env->isolate()); |
| Local<Object> arg0 = Local<Object>::Cast(args[0]); |
| Local<Object> headers; |
| |
| memset(&req, 0, sizeof(req)); |
| req._un.version = 1; |
| SLURP_STRING(arg0, url, &req.url); |
| SLURP_STRING(arg0, method, &req.method); |
| SLURP_OBJECT(arg0, headers, &headers); |
| |
| if (!(headers)->IsObject()) { |
| return node::THROW_ERR_INVALID_ARG_TYPE(env, |
| "expected object for request to contain string member headers"); |
| } |
| |
| Local<Value> strfwdfor = headers->Get( |
| env->context(), env->x_forwarded_string()).ToLocalChecked(); |
| node::Utf8Value fwdfor(env->isolate(), strfwdfor); |
| |
| if (!strfwdfor->IsString() || (req.forwardedFor = *fwdfor) == nullptr) |
| req.forwardedFor = const_cast<char*>(""); |
| |
| SLURP_CONNECTION(args[1], conn); |
| NODE_HTTP_SERVER_REQUEST(&req, &conn, conn.remote, conn.port, req.method, \ |
| req.url, conn.fd); |
| } |
| |
| |
| void DTRACE_HTTP_SERVER_RESPONSE(const FunctionCallbackInfo<Value>& args) { |
| if (!NODE_HTTP_SERVER_RESPONSE_ENABLED()) |
| return; |
| Environment* env = Environment::GetCurrent(args); |
| SLURP_CONNECTION(args[0], conn); |
| NODE_HTTP_SERVER_RESPONSE(&conn, conn.remote, conn.port, conn.fd); |
| } |
| |
| |
| void DTRACE_HTTP_CLIENT_REQUEST(const FunctionCallbackInfo<Value>& args) { |
| node_dtrace_http_client_request_t req; |
| char* header; |
| |
| if (!NODE_HTTP_CLIENT_REQUEST_ENABLED()) |
| return; |
| |
| Environment* env = Environment::GetCurrent(args); |
| HandleScope scope(env->isolate()); |
| |
| /* |
| * For the method and URL, we're going to dig them out of the header. This |
| * is not as efficient as it could be, but we would rather not force the |
| * caller here to retain their method and URL until the time at which |
| * DTRACE_HTTP_CLIENT_REQUEST can be called. |
| */ |
| Local<Object> arg0 = Local<Object>::Cast(args[0]); |
| SLURP_STRING(arg0, _header, &header); |
| |
| req.method = header; |
| |
| while (*header != '\0' && *header != ' ') |
| header++; |
| |
| if (*header != '\0') |
| *header++ = '\0'; |
| |
| req.url = header; |
| |
| while (*header != '\0' && *header != ' ') |
| header++; |
| |
| *header = '\0'; |
| |
| SLURP_CONNECTION_HTTP_CLIENT(args[1], conn); |
| NODE_HTTP_CLIENT_REQUEST(&req, &conn, conn.remote, conn.port, req.method, \ |
| req.url, conn.fd); |
| } |
| |
| |
| void DTRACE_HTTP_CLIENT_RESPONSE(const FunctionCallbackInfo<Value>& args) { |
| if (!NODE_HTTP_CLIENT_RESPONSE_ENABLED()) |
| return; |
| Environment* env = Environment::GetCurrent(args); |
| SLURP_CONNECTION_HTTP_CLIENT_RESPONSE(args[0], args[1], conn); |
| NODE_HTTP_CLIENT_RESPONSE(&conn, conn.remote, conn.port, conn.fd); |
| } |
| |
| void dtrace_gc_start(Isolate* isolate, |
| GCType type, |
| GCCallbackFlags flags, |
| void* data) { |
| // Previous versions of this probe point only logged type and flags. |
| // That's why for reasons of backwards compatibility the isolate goes last. |
| NODE_GC_START(type, flags, isolate); |
| } |
| |
| void dtrace_gc_done(Isolate* isolate, |
| GCType type, |
| GCCallbackFlags flags, |
| void* data) { |
| // Previous versions of this probe point only logged type and flags. |
| // That's why for reasons of backwards compatibility the isolate goes last. |
| NODE_GC_DONE(type, flags, isolate); |
| } |
| |
| |
| void InitDTrace(Environment* env) { |
| #ifdef HAVE_ETW |
| // ETW is neither thread-safe nor does it clean up resources on exit, |
| // so we can use it only on the main thread. |
| if (env->is_main_thread()) { |
| init_etw(); |
| } |
| #endif |
| |
| // We need to use the variant of GC callbacks that takes data to |
| // avoid running into DCHECKs when multiple Environments try to add |
| // the same callback to the same isolate multiple times. |
| env->isolate()->AddGCPrologueCallback(dtrace_gc_start, env); |
| env->isolate()->AddGCEpilogueCallback(dtrace_gc_done, env); |
| env->AddCleanupHook([](void* data) { |
| Environment* env = static_cast<Environment*>(data); |
| env->isolate()->RemoveGCPrologueCallback(dtrace_gc_start, env); |
| env->isolate()->RemoveGCEpilogueCallback(dtrace_gc_done, env); |
| }, env); |
| } |
| |
| void InitializeDTrace(Local<Object> target, |
| Local<Value> unused, |
| Local<Context> context, |
| void* priv) { |
| Environment* env = Environment::GetCurrent(context); |
| |
| #if defined HAVE_DTRACE || defined HAVE_ETW |
| # define NODE_PROBE(name) env->SetMethod(target, #name, name); |
| NODE_PROBE(DTRACE_NET_SERVER_CONNECTION) |
| NODE_PROBE(DTRACE_NET_STREAM_END) |
| NODE_PROBE(DTRACE_HTTP_SERVER_REQUEST) |
| NODE_PROBE(DTRACE_HTTP_SERVER_RESPONSE) |
| NODE_PROBE(DTRACE_HTTP_CLIENT_REQUEST) |
| NODE_PROBE(DTRACE_HTTP_CLIENT_RESPONSE) |
| # undef NODE_PROBE |
| #endif |
| } |
| |
| } // namespace node |
| NODE_MODULE_CONTEXT_AWARE_INTERNAL(dtrace, node::InitializeDTrace) |