From a4836b925ef8fc038cf7706bd166b72c3a0b6530 Mon Sep 17 00:00:00 2001 From: RafaelGSS Date: Tue, 13 Jun 2023 09:38:49 -0300 Subject: [PATCH] lib: switch NAN to N-API Signed-off-by: RafaelGSS --- .gitignore | 2 + README.md | 65 ++++++++++++- binding.gyp | 5 +- package.json | 2 +- test.js | 17 ++++ usdt-probe.cc | 196 +++++++++++++++++---------------------- usdt-provider.cc | 235 +++++++++++++++++++---------------------------- usdt.h | 59 ++++-------- 8 files changed, 289 insertions(+), 292 deletions(-) create mode 100644 test.js diff --git a/.gitignore b/.gitignore index abed06a..7eb2904 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,5 @@ typings/ .env build/ +.ccls-cache/ +tags diff --git a/README.md b/README.md index ceeb089..b82cbf8 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,65 @@ # node-usdt -Node.js runtime USDT probes + +N-API addon to create USDT probes and instrument your application easily and without +rebuilding Node. + +# Dependencies + +At the moment this module only runs on Linux and requires +[libstapsdt](https://github.com/linux-usdt/libstapsdt) to be installed to create +runtime probes. + +## Ubuntu 16.04 + +To install libstapsdt, run: + +```bash +sudo add-apt-repository ppa:sthima/oss +sudo apt-get update +sudo apt-get install libstapsdt0 libstapsdt-dev +``` + +## Other + +Build from [libstapsdt](https://github.com/linux-usdt/libstapsdt). + +# Install + +```bash +npm install usdt +``` + +# Example + +The following code will create a probe named `firstProbe`. + +```javascript +const USDT = require("usdt"); + +const provider = new USDT.USDTProvider("nodeProvider"); +const probe1 = provider.addProbe("firstProbe", "int", "char *"); +provider.enable(); +let countdown = 10; + +function waiter() { + console.log("Firing probe..."); + if(countdown <= 0) { + console.log("Disable provider"); + provider.disable(); + } + probe1.fire(function() { + console.log("Probe fired!"); + countdown = countdown - 1; + return [countdown, "My little string"]; + }); +} + +setInterval(waiter, 750); +``` + +You can then trace this probe with any tool able to trace Systemtap's probes. +Here's an example with eBPF/bcc (assuming node test.js): + +```bash +sudo trace-bpfcc -p $(pgrep -f test.js) 'u::firstProbe "%d - %s", arg1, arg2' +``` diff --git a/binding.gyp b/binding.gyp index 46af905..08400f5 100644 --- a/binding.gyp +++ b/binding.gyp @@ -6,7 +6,10 @@ "usdt-provider.cc", "usdt-probe.cc", ], - "include_dirs": [" -#include -#include "usdt.h" - -namespace node { +#include - using namespace v8; +#include "usdt.h" - USDTProbe::USDTProbe() : Nan::ObjectWrap() { - argc = 0; - probe = NULL; +using namespace Napi; + +Napi::FunctionReference* USDTProbe::New = nullptr; + +USDTProbe::USDTProbe(const CallbackInfo& info) : ObjectWrap(info) { + argc = 0; + probe = nullptr; +} + +USDTProbe::~USDTProbe() { + probe = nullptr; +} + +Napi::Object USDTProbe::Init(Napi::Env env, Napi::Object exports) { + Function func = DefineClass(env, "USDTProbe", { + InstanceMethod("fire", &USDTProbe::Fire), + }); + + New = new FunctionReference(); + *New = Persistent(func); + env.SetInstanceData(New); + exports.Set("USDTProbe", func); + return exports; +} + +Napi::Value USDTProbe::Fire(const CallbackInfo& info) { + auto env = info.Env(); + if (!info[0].IsFunction()) { + Napi::TypeError::New(env, "Must give probe value callback as first argument") + .ThrowAsJavaScriptException(); + return env.Undefined(); } - USDTProbe::~USDTProbe() { - // XXX (mmarchini) probe is cleaned by the provider - probe = NULL; + if (probeIsEnabled(this->probe) == 0) { + return env.Undefined(); } - Nan::Persistent USDTProbe::constructor_template; + size_t cblen = info.Length() - 1; - void USDTProbe::Initialize(v8::Local target) { - Nan::HandleScope scope; + Napi::Array cbargs = Napi::Array::New(env, cblen); - Local t = Nan::New(USDTProbe::New); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(Nan::New("USDTProbe").ToLocalChecked()); - constructor_template.Reset(t); + for (size_t i = 0; i < cblen; i++) { + cbargs[i] = info[i + 1]; + } - Nan::SetPrototypeMethod(t, "fire", USDTProbe::Fire); + Napi::Function cb = info[0].As(); + Napi::Value probe_args = cb.Call(env.Global(), {Napi::Number::New(env, cblen), cbargs}); - target->Set(Nan::New("USDTProbe").ToLocalChecked(), t->GetFunction()); + // exception in args callback? + if (env.IsExceptionPending()) { + Napi::Error::Fatal("USDTProbe::Fire", "Exception in callback"); + return env.Undefined(); } - NAN_METHOD(USDTProbe::New) { - Nan::HandleScope scope; - USDTProbe *probe = new USDTProbe(); - probe->Wrap(info.This()); - info.GetReturnValue().Set(info.This()); + if (argc > 0 && !probe_args.IsArray()) { + return env.Undefined(); } - NAN_METHOD(USDTProbe::Fire) { - Nan::HandleScope scope; - - if (!info[0]->IsFunction()) { - Nan::ThrowTypeError("Must give probe value callback as first argument"); - return; + Napi::Array a = probe_args.As(); + void* argv[MAX_ARGUMENTS]; + + // convert each argument value + for (size_t i = 0; i < argc; i++) { + if (a.Get(i).IsString()) { + std::string argValue = a.Get(i).ToString().Utf8Value(); + // FIXME: Free string + argv[i] = (void*)strdup(argValue.c_str()); + } else { + argv[i] = (void*)a.Get(i).ToNumber().Uint32Value(); } - - USDTProbe *pd = Nan::ObjectWrap::Unwrap(info.Holder()); - info.GetReturnValue().Set(pd->_fire(info, 0)); } - v8::Local USDTProbe::_fire(Nan::NAN_METHOD_ARGS_TYPE argsinfo, size_t fnidx) { - Nan::HandleScope scope; - - if (probeIsEnabled(this->probe) == 0) { - return Nan::Undefined(); - } - - // invoke fire callback - Nan::TryCatch try_catch; - - size_t cblen = argsinfo.Length() - fnidx - 1; - Local *cbargs = new Local[cblen]; - - for (size_t i = 0; i < cblen; i++) { - cbargs[i] = argsinfo[i + fnidx + 1]; - } - - Local cb = Local::Cast(argsinfo[fnidx]); - Local probe_args = cb->Call(this->handle(), cblen, cbargs); - - delete [] cbargs; - - // exception in args callback? - if (try_catch.HasCaught()) { - Nan::FatalException(try_catch); - return Nan::Undefined(); - } - - // check return - if (!probe_args->IsArray()) { - return Nan::Undefined(); - } - - // TODO (mmarchini) check if array size and probe args size is equal - - Local a = Local::Cast(probe_args); - void *argv[MAX_ARGUMENTS]; - - // convert each argument value - for (size_t i = 0; i < argc; i++) { - if(a->Get(i)->IsString() ){ - // FIXME (mmarchini) free string - argv[i] = (void *) strdup(*(static_cast (a->Get(i)->ToString()))); - } else { - argv[i] = (void *)((**(a->Get(i))).Uint32Value()); - } - } - - // finally fire the probe - switch(argc) { - case 6: - probeFire(this->probe, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); - break; - case 5: - probeFire(this->probe, argv[0], argv[1], argv[2], argv[3], argv[4]); - break; - case 4: - probeFire(this->probe, argv[0], argv[1], argv[2], argv[3]); - break; - case 3: - probeFire(this->probe, argv[0], argv[1], argv[2]); - break; - case 2: - probeFire(this->probe, argv[0], argv[1]); - break; - case 1: - probeFire(this->probe, argv[0]); - break; - case 0: - default: - probeFire(this->probe); - break; - } - - return Nan::True(); + // finally fire the probe + switch (argc) { + case 6: + probeFire(this->probe, argv[0], argv[1], argv[2], argv[3], argv[4], argv[5]); + break; + case 5: + probeFire(this->probe, argv[0], argv[1], argv[2], argv[3], argv[4]); + break; + case 4: + probeFire(this->probe, argv[0], argv[1], argv[2], argv[3]); + break; + case 3: + probeFire(this->probe, argv[0], argv[1], argv[2]); + break; + case 2: + probeFire(this->probe, argv[0], argv[1]); + break; + case 1: + probeFire(this->probe, argv[0]); + break; + case 0: + default: + probeFire(this->probe); + break; } -} // namespace node + return Napi::Boolean::New(env, true); +} diff --git a/usdt-provider.cc b/usdt-provider.cc index daf58d3..c3c8b86 100644 --- a/usdt-provider.cc +++ b/usdt-provider.cc @@ -1,186 +1,143 @@ -#include +#include -#include #include #include "usdt.h" +#include "v8.h" -namespace node { - using namespace v8; +using namespace Napi; - USDTProvider::USDTProvider() : Nan::ObjectWrap() { - provider = NULL; - } +USDTProvider::USDTProvider(const CallbackInfo& info) : ObjectWrap(info) { + Napi::Env env = info.Env(); - USDTProvider::~USDTProvider() { - providerDestroy(provider); + if (info.Length() < 1 || !info[0].IsString()) { + Napi::TypeError::New(env, "Must give provider name as argument") + .ThrowAsJavaScriptException(); + return; } - Nan::Persistent USDTProvider::constructor_template; - - void USDTProvider::Initialize(v8::Local target) { - Nan::HandleScope scope; + std::string name = info[0].As(); + provider = providerInit(name.c_str()); + // Unlikely? + if (provider == NULL) { + Error::Fatal("usdt-provider.cc:20", "providerInit failed"); + return; + } +} - Local t = Nan::New(USDTProvider::New); - t->InstanceTemplate()->SetInternalFieldCount(1); - t->SetClassName(Nan::New("USDTProvider").ToLocalChecked()); - constructor_template.Reset(t); +USDTProvider::~USDTProvider() { + providerDestroy(provider); +} - Nan::SetPrototypeMethod(t, "addProbe", USDTProvider::AddProbe); - // Nan::SetPrototypeMethod(t, "removeProbe", USDTProvider::RemoveProbe); - Nan::SetPrototypeMethod(t, "enable", USDTProvider::Enable); - Nan::SetPrototypeMethod(t, "disable", USDTProvider::Disable); - // Nan::SetPrototypeMethod(t, "fire", USDTProvider::Fire); +Napi::Object USDTProvider::Init(Napi::Env env, Napi::Object exports) { + Function func = DefineClass(env, "USDTProvider", { + InstanceMethod("addProbe", &USDTProvider::AddProbe), + InstanceMethod("enable", &USDTProvider::Enable), + }); + + FunctionReference* constructor = new FunctionReference(); + *constructor = Persistent(func); + env.SetInstanceData(constructor); + exports.Set("USDTProvider", func); + return exports; +} - target->Set(Nan::New("USDTProvider").ToLocalChecked(), t->GetFunction()); +void USDTProvider::Enable(const Napi::CallbackInfo& info) { + Napi::HandleScope scope(info.Env()); - USDTProbe::Initialize(target); + if (providerLoad(this->provider) != 0) { + Napi::Error::Fatal("USDTProvider::Enable", "Unable to load provider"); } +} - NAN_METHOD(USDTProvider::New) { - Nan::HandleScope scope; - USDTProvider *p = new USDTProvider(); - - p->Wrap(info.This()); - - if (info.Length() < 1 || !info[0]->IsString()) { - Nan::ThrowTypeError("Must give provider name as argument"); - return; - } +Napi::Value USDTProvider::AddProbe(const Napi::CallbackInfo& info) { + Napi::HandleScope scope(info.Env()); - String::Utf8Value name(info[0]->ToString()); + if (info.Length() == 0) { + Napi::TypeError::New(info.Env(), "Must give probe name as argument") + .ThrowAsJavaScriptException(); + return info.Env().Undefined(); + } - if ((p->provider = providerInit(*name)) == NULL) { - Nan::ThrowError("providerInit failed"); - return; - } + Napi::Value probeA = USDTProbe::New->New({}); + USDTProbe* probe = USDTProbe::Unwrap(probeA.As()); + probe->argc = 0; - info.GetReturnValue().Set(info.This()); - } + for (int i = 0; i < MAX_ARGUMENTS; i++) { + if (i < info.Length() - 1) { + std::string type = info[i + 1].ToString().Utf8Value(); - NAN_METHOD(USDTProvider::AddProbe) { - Nan::HandleScope scope; - - v8::Local obj = info.Holder(); - USDTProvider *provider = Nan::ObjectWrap::Unwrap(obj); - - // create a USDTProbe object - v8::Local klass = - Nan::New(USDTProbe::constructor_template)->GetFunction(); - v8::Local pd = klass->NewInstance(); - - // store in provider object - USDTProbe *probe = Nan::ObjectWrap::Unwrap(pd->ToObject()); - obj->Set(info[0]->ToString(), pd); - - // reference the provider to avoid GC'ing it when only probes remain in scope. - Nan::ForceSet(pd, Nan::New("__prov__").ToLocalChecked(), obj, - static_cast(DontEnum | ReadOnly | DontDelete)); - - // add probe to provider - probe->argc = 0; - for (int i = 0; i < MAX_ARGUMENTS; i++) { - if (i < info.Length() - 1) { - String::Utf8Value type(info[i + 1]->ToString()); - - if (strncmp("char *", *type, 6) == 0) { - probe->arguments[i] = uint64; - } - else if (strncmp("int", *type, 3) == 0) { - probe->arguments[i] = int32; - } - else { - probe->arguments[i] = uint64; - } - probe->argc++; + if (strncmp("char *", type.c_str(), 6) == 0) { + probe->arguments[i] = uint64; + } else if (strncmp("int", type.c_str(), 3) == 0) { + probe->arguments[i] = int32; + } else { + probe->arguments[i] = uint64; } + probe->argc++; } + } - String::Utf8Value name(info[0]->ToString()); + std::string name(info[0].ToString().Utf8Value()); - switch (probe->argc) { - case 6: - probe->probe = providerAddProbe(provider->provider, *name, probe->argc, + switch (probe->argc) { + case 6: + probe->probe = providerAddProbe(provider, name.c_str(), probe->argc, probe->arguments[0], probe->arguments[1], probe->arguments[2], probe->arguments[3], probe->arguments[4], probe->arguments[5] - ); - break; - case 5: - probe->probe = providerAddProbe(provider->provider, *name, probe->argc, + ); + break; + case 5: + probe->probe = providerAddProbe(provider, name.c_str(), probe->argc, probe->arguments[0], probe->arguments[1], probe->arguments[2], probe->arguments[3], probe->arguments[4] - ); - break; - case 4: - probe->probe = providerAddProbe(provider->provider, *name, probe->argc, + ); + break; + case 4: + probe->probe = providerAddProbe(provider, name.c_str(), probe->argc, probe->arguments[0], probe->arguments[1], probe->arguments[2], probe->arguments[3] - ); - break; - case 3: - probe->probe = providerAddProbe(provider->provider, *name, probe->argc, + ); + break; + case 3: + probe->probe = providerAddProbe(provider, name.c_str(), probe->argc, probe->arguments[0], probe->arguments[1], probe->arguments[2] - ); - break; - case 2: - probe->probe = providerAddProbe(provider->provider, *name, probe->argc, + ); + break; + case 2: + probe->probe = providerAddProbe(provider, name.c_str(), probe->argc, probe->arguments[0], probe->arguments[1] - ); - break; - case 1: - probe->probe = providerAddProbe(provider->provider, *name, probe->argc, + ); + break; + case 1: + probe->probe = providerAddProbe(provider, name.c_str(), probe->argc, probe->arguments[0] - ); - break; - case 0: - default: - probe->probe = providerAddProbe(provider->provider, *name, probe->argc); - break; - } - - info.GetReturnValue().Set(pd); - } - - NAN_METHOD(USDTProvider::Enable) { - Nan::HandleScope scope; - USDTProvider *provider = Nan::ObjectWrap::Unwrap(info.Holder()); - - if (providerLoad(provider->provider) != 0) { - // TODO (mmarchini) get error string from libstapsdt - Nan::ThrowError("Unable to load provider"); - return; - } - - return; - } - - NAN_METHOD(USDTProvider::Disable) { - Nan::HandleScope scope; - USDTProvider *provider = Nan::ObjectWrap::Unwrap(info.Holder()); - - if (providerUnload(provider->provider) != 0) { - Nan::ThrowError("Unable to unload provider"); - return; - } - - return; + ); + break; + case 0: + default: + probe->probe = providerAddProbe(provider, name.c_str(), probe->argc); + break; } - extern "C" void - init(v8::Local target) { - USDTProvider::Initialize(target); - } + return probeA; +} - NODE_MODULE(USDTProviderBindings, init) +Object Init(Env env, Object target) { + USDTProvider::Init(env, target); + USDTProbe::Init(env, target); + return target; } + +NODE_API_MODULE(USDTProviderBindings, Init) diff --git a/usdt.h b/usdt.h index 4d6140f..a659e2c 100644 --- a/usdt.h +++ b/usdt.h @@ -1,5 +1,4 @@ -#include -#include +#include extern "C" { #include @@ -11,49 +10,31 @@ extern "C" { #include #include -namespace node { +using Napi::CallbackInfo; -using namespace v8; +class USDTProbe : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object target); + static Napi::FunctionReference* New; -class USDTProbe : public Nan::ObjectWrap { + Napi::Value Fire(const Napi::CallbackInfo& info); -public: - static void Initialize(v8::Local target); - SDTProbe_t *probe; - ArgType_t arguments[MAX_ARGUMENTS]; - size_t argc; + USDTProbe(const Napi::CallbackInfo& info); + ~USDTProbe(); - static NAN_METHOD(New); - static NAN_METHOD(Fire); - - v8::Local _fire(Nan::NAN_METHOD_ARGS_TYPE, size_t); - - static Nan::Persistent constructor_template; - - USDTProbe(); - ~USDTProbe(); -private: + SDTProbe_t *probe; + ArgType_t arguments[MAX_ARGUMENTS]; + size_t argc; }; -class USDTProvider : public Nan::ObjectWrap { - -public: - static void Initialize(v8::Local target); - SDTProvider_t *provider; - - static NAN_METHOD(New); - static NAN_METHOD(AddProbe); - // static NAN_METHOD(RemoveProbe); - static NAN_METHOD(Enable); - static NAN_METHOD(Disable); - // static NAN_METHOD(Fire); - - USDTProvider(); - ~USDTProvider(); -private: - static Nan::Persistent constructor_template; -}; +class USDTProvider : public Napi::ObjectWrap { + public: + static Napi::Object Init(Napi::Env env, Napi::Object object); -void InitUSDTProvider(v8::Local target); + Napi::Value AddProbe(const Napi::CallbackInfo& info); + void Enable(const Napi::CallbackInfo& info); + USDTProvider(const Napi::CallbackInfo& info); + ~USDTProvider(); + SDTProvider_t *provider; };