#include "erl_nif.h"
|
|
|
|
#define NIF_ATOM_DECL(a) ERL_NIF_TERM atom_ ## a;
|
|
// #define NIF_ATOM_H_DECL(a) extern ERL_NIF_TERM atom_ ## a;
|
|
#define NIF_ATOM_INIT(a) atom_ ## a = enif_make_atom(env, #a);
|
|
|
|
#define NIF_ATOMS(A) \
|
|
A(_nif_thread_ret_) \
|
|
A(call) \
|
|
A(closed) \
|
|
A(cpu_timestamp) \
|
|
A(discard) \
|
|
A(exception_from) \
|
|
A(exit) \
|
|
A(extra) \
|
|
A(gc_major_end) \
|
|
A(gc_major_start) \
|
|
A(gc_minor_end) \
|
|
A(gc_minor_start) \
|
|
A(getting_linked) \
|
|
A(getting_unlinked) \
|
|
A(in) \
|
|
A(in_exiting) \
|
|
A(link) \
|
|
A(match_spec_result) \
|
|
A(mode) \
|
|
A(monotonic) \
|
|
A(ok) \
|
|
A(open) \
|
|
A(out) \
|
|
A(out_exited) \
|
|
A(out_exiting) \
|
|
A(percent) \
|
|
A(profile) \
|
|
A(receive) \
|
|
A(register) \
|
|
A(remove) \
|
|
A(return_from) \
|
|
A(return_to) \
|
|
A(scheduler_id) \
|
|
A(send) \
|
|
A(send_to_non_existing_process) \
|
|
A(spawn) \
|
|
A(spawned) \
|
|
A(strict_monotonic) \
|
|
A(timestamp) \
|
|
A(trace) \
|
|
A(trace_status) \
|
|
A(tracers) \
|
|
A(unlink) \
|
|
A(unregister)
|
|
|
|
NIF_ATOMS(NIF_ATOM_DECL)
|
|
|
|
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info)
|
|
{
|
|
NIF_ATOMS(NIF_ATOM_INIT)
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info)
|
|
{
|
|
*priv_data = *old_priv_data;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void unload(ErlNifEnv* env, void* priv_data)
|
|
{
|
|
}
|
|
|
|
// enabled(TraceTag, TracerState, Tracee)
|
|
|
|
static ERL_NIF_TERM enabled(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
|
|
ERL_NIF_TERM tracers, value;
|
|
ErlNifPid tracer;
|
|
|
|
// @todo We can go one step further by having the one pid
|
|
// in its own value in the map, skipping a get_map_value step.
|
|
|
|
// This function will only be called for trace_status.
|
|
// We can take a few shortcuts knowing this.
|
|
|
|
// Disable the trace when the tracers option is missing.
|
|
if (!enif_get_map_value(env, argv[1], atom_tracers, &tracers))
|
|
return atom_remove;
|
|
|
|
// Because the tracers supervisor is a one_for_all, we only need
|
|
// to check one of the tracer processes to confirm all are alive.
|
|
|
|
// We know for a fact that this key exists because
|
|
// there's at least one tracer process.
|
|
enif_get_map_value(env, tracers, enif_make_int(env, 0), &value);
|
|
|
|
// Disable the trace when one of the tracers is not a local process.
|
|
if (!enif_get_local_pid(env, value, &tracer))
|
|
return atom_remove;
|
|
|
|
// Disable the trace when one of the tracers is not alive.
|
|
if (!enif_is_process_alive(env, &tracer))
|
|
return atom_remove;
|
|
|
|
return atom_discard;
|
|
}
|
|
|
|
static ERL_NIF_TERM enabled_call(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
|
|
|
|
// We always want both call and return_to.
|
|
return atom_trace;
|
|
}
|
|
|
|
static ERL_NIF_TERM enabled_procs(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
|
|
ERL_NIF_TERM mode;
|
|
|
|
// We only want the spawn and exit events when 'profile' mode
|
|
// is enabled. Technically we only care about exits for callgrind,
|
|
// but spawn is cheap to keep and useful for message profilers.
|
|
if (enif_get_map_value(env, argv[1], atom_mode, &mode)
|
|
&& enif_is_identical(atom_profile, mode)
|
|
&& !(enif_is_identical(atom_spawn, argv[0])
|
|
|| enif_is_identical(atom_exit, argv[0]))) {
|
|
return atom_discard;
|
|
}
|
|
|
|
return atom_trace;
|
|
}
|
|
|
|
static ERL_NIF_TERM enabled_running_procs(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
|
|
// We always want both in and out.
|
|
return atom_trace;
|
|
}
|
|
|
|
static ERL_NIF_TERM enabled_send(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
|
|
|
|
// We always want both send and send_to_non_existing_process.
|
|
return atom_trace;
|
|
}
|
|
|
|
// trace(TraceTag, TracerState, Tracee, TraceTerm, Opts)
|
|
static ERL_NIF_TERM trace(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) {
|
|
ERL_NIF_TERM tracers, head, ts, extra, mspec, msg;
|
|
ErlNifPid tracer;
|
|
unsigned int nth;
|
|
size_t len;
|
|
int has_extra, has_mspec;
|
|
|
|
if (!enif_get_map_value(env, argv[1], atom_tracers, &tracers))
|
|
return atom_ok;
|
|
|
|
// We know for a fact that the argument is a map. And if not,
|
|
// no problem because we will return when trying to get a value from it.
|
|
enif_get_map_size(env, tracers, &len);
|
|
|
|
#if (ERL_NIF_MAJOR_VERSION >= 2) && (ERL_NIF_MINOR_VERSION >= 12)
|
|
nth = enif_hash(ERL_NIF_INTERNAL_HASH, argv[2], 0) % len;
|
|
#else
|
|
// Select the correct tracer for this process.
|
|
//
|
|
// The pid value is detailed in:
|
|
// 5b6dd0e84cf0f1dc19ddd05f86cf04b2695d8a9e/erts/emulator/beam/erl_term.h#L498
|
|
//
|
|
// As can be seen there, the first four bits of the pid value
|
|
// are always the same. We therefore shift them out.
|
|
|
|
ErlNifPid tracee;
|
|
|
|
if (!enif_get_local_pid(env, argv[2], &tracee))
|
|
return atom_ok;
|
|
|
|
nth = (tracee.pid >> 4) % len;
|
|
#endif
|
|
|
|
if (!enif_get_map_value(env, tracers, enif_make_int(env, nth), &head))
|
|
return atom_ok;
|
|
|
|
if (!enif_get_local_pid(env, head, &tracer))
|
|
return atom_ok;
|
|
|
|
// Everything good. Generate a timestamp to include in the message.
|
|
|
|
ts = enif_make_int64(env, enif_monotonic_time(ERL_NIF_USEC));
|
|
|
|
// Build the message. There can be two different messages
|
|
// depending on whether the extra option was set:
|
|
//
|
|
// - {Tag, Tracee, Ts, Term}
|
|
// - {Tag, Tracee, Ts, Term, Extra}
|
|
//
|
|
// On top of that when match specs are enabled we may have
|
|
// one additional term at the end of the tuple containing
|
|
// the result of the match spec function.
|
|
//
|
|
// - {Tag, Tracee, Ts, Term, Result}
|
|
// - {Tag, Tracee, Ts, Term, Extra, Result}
|
|
|
|
has_extra = enif_get_map_value(env, argv[4], atom_extra, &extra);
|
|
has_mspec = enif_get_map_value(env, argv[4], atom_match_spec_result, &mspec);
|
|
|
|
if (has_extra && has_mspec)
|
|
msg = enif_make_tuple6(env, argv[0], argv[2], ts, argv[3], extra, mspec);
|
|
else if (has_extra)
|
|
msg = enif_make_tuple5(env, argv[0], argv[2], ts, argv[3], extra);
|
|
else if (has_mspec)
|
|
msg = enif_make_tuple5(env, argv[0], argv[2], ts, argv[3], mspec);
|
|
else
|
|
msg = enif_make_tuple4(env, argv[0], argv[2], ts, argv[3]);
|
|
|
|
// Send the message to the selected tracer.
|
|
|
|
enif_send(env, &tracer, NULL, msg);
|
|
|
|
return atom_ok;
|
|
}
|
|
|
|
static ErlNifFunc nifFuns[] = {
|
|
{"enabled", 3, enabled},
|
|
{"enabled_call", 3, enabled_call},
|
|
{"enabled_procs", 3, enabled_procs},
|
|
{"enabled_running_procs", 3, enabled_running_procs},
|
|
{"enabled_send", 3, enabled_send},
|
|
{"trace", 5, trace},
|
|
};
|
|
|
|
ERL_NIF_INIT(tpTracerNif, nifFuns, load, NULL, upgrade, unload)
|