Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ typings/
.env
build/

.ccls-cache/
tags
65 changes: 64 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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'
```
5 changes: 4 additions & 1 deletion binding.gyp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
"usdt-provider.cc",
"usdt-probe.cc",
],
"include_dirs": ["<!(node -e \"require('nan')\")"],
"include_dirs": ["<!@(node -p \"require('node-addon-api').include\")"],
"dependencies": ["<!(node -p \"require('node-addon-api').gyp\")"],
"cflags!": ["-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
"libraries": [
"-lstapsdt"
]
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
},
"homepage": "https://github.com/sthima/node-usdt#readme",
"dependencies": {
"nan": "*"
"node-addon-api": "^6.1.0"
}
}
17 changes: 17 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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...");
probe1.fire(function() {
console.log("Probe fired!");
countdown = countdown - 1;
return [countdown, "My little string"];
});
}

setInterval(waiter, 3000);
196 changes: 85 additions & 111 deletions usdt-probe.cc
Original file line number Diff line number Diff line change
@@ -1,130 +1,104 @@
#include <iostream>
#include <nan.h>
#include "usdt.h"

namespace node {
#include <napi.h>

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<USDTProbe>(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<FunctionTemplate> USDTProbe::constructor_template;
size_t cblen = info.Length() - 1;

void USDTProbe::Initialize(v8::Local<Object> target) {
Nan::HandleScope scope;
Napi::Array cbargs = Napi::Array::New(env, cblen);

Local<FunctionTemplate> t = Nan::New<FunctionTemplate>(USDTProbe::New);
t->InstanceTemplate()->SetInternalFieldCount(1);
t->SetClassName(Nan::New<String>("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::Function>();
Napi::Value probe_args = cb.Call(env.Global(), {Napi::Number::New(env, cblen), cbargs});

target->Set(Nan::New<String>("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<Napi::Array>();
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<USDTProbe>(info.Holder());
info.GetReturnValue().Set(pd->_fire(info, 0));
}

v8::Local<Value> 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<Value> *cbargs = new Local<Value>[cblen];

for (size_t i = 0; i < cblen; i++) {
cbargs[i] = argsinfo[i + fnidx + 1];
}

Local<Function> cb = Local<Function>::Cast(argsinfo[fnidx]);
Local<Value> 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<Array> a = Local<Array>::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<String::Utf8Value> (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);
}
Loading