-module(dtrace).
%%% @doc The DTrace interface module
%%%
%%% This DTrace interface module, with the corresponding NIFs, should
%%% work on any operating system platform where user-space DTrace
%%% probes are supported.
%%%
%%% Use the `dtrace:init()' function to load the NIF shared library and
%%% to initialize library's private state.
%%%
%%% It is recommended that you use the `dtrace:p()' function to add
%%% DTrace probes to your Erlang code. This function can accept up to
%%% four integer arguments and four string arguments; the integer
%%% argument(s) must come before any string argument. For example:
%%% ```
%%% 1> dtrace:put_tag("GGOOOAAALL!!!!!").
%%% true
%%% 2> dtrace:init().
%%% ok
%%%
%%% % % % Enable the DTrace probe using the 'dtrace' command.
%%%
%%% 3> dtrace:p(7, 8, 9, "one", "four").
%%% true
%%% '''
%%%
%%% Output from the example D script `user-probe.d' looks like:
%%% ```
%%% <0.34.0> GGOOOAAALL!!!!! 7 8 9 0 'one' 'four' '' ''
%%% '''
%%%
%%% If the expected type of variable is not present, e.g. integer when
%%% integer() is expected, or an I/O list when iolist() is expected,
%%% then the driver will ignore the user's input and use a default
%%% value of 0 or NULL, respectively.
-define(DTRACE_UT_KEY, '_dtrace_utag_@_@'). % Match prim_file:get_dtrace_utag()!
-export([init/0, available/0,
user_trace_s1/1, % TODO: unify with pid & tag args like user_trace_i4s4
p/0, p/1, p/2, p/3, p/4, p/5, p/6, p/7, p/8]).
-export([put_utag/1, get_utag/0]).
-export([scaff/0]). % Development only
-export([user_trace_i4s4/9]). % Know what you're doing!
-type probe_arg() :: integer() | iolist().
-type int_p_arg() :: integer() | iolist() | undef.
%% The *_maybe() types use atom() instead of a stricter 'undef'
%% because user_trace_i4s4/9 is exposed to the outside world, and
%% because the driver will allow any atom to be used as a "not
%% present" indication, we'll allow any atom in the types.
-type integer_maybe() :: integer() | atom().
-type iolist_maybe() :: iolist() | atom().
-spec init() -> ok | {error, {term(), term()}}.
init() ->
PrivDir = code:priv_dir(dtrace),
Lib = filename:join([PrivDir, "lib", "dtrace"]),
erlang:load_nif(Lib, 0).
%%%
%%% NIF placeholders
%%%
-spec available() -> true | false.
available() ->
erlang:nif_error(nif_not_loaded).
-spec user_trace_s1(iolist()) -> true | false | error | badarg.
user_trace_s1(_Message) ->
erlang:nif_error(nif_not_loaded).
-spec user_trace_i4s4(iolist(),
integer_maybe(), integer_maybe(),
integer_maybe(), integer_maybe(),
iolist_maybe(), iolist_maybe(),
iolist_maybe(), iolist_maybe()) ->
true | false | error | badarg.
user_trace_i4s4(_, _, _, _, _, _, _, _, _) ->
erlang:nif_error(nif_not_loaded).
%%%
%%% Erlang support functions
%%%
-spec p() -> true | false | error | badarg.
p() ->
user_trace_int(undef, undef, undef, undef, undef, undef, undef, undef).
-spec p(probe_arg()) -> true | false | error | badarg.
p(I1) when is_integer(I1) ->
user_trace_int(I1, undef, undef, undef, undef, undef, undef, undef);
p(S1) ->
user_trace_int(undef, undef, undef, undef, S1, undef, undef, undef).
-spec p(probe_arg(), probe_arg()) -> true | false | error | badarg.
p(I1, I2) when is_integer(I1), is_integer(I2) ->
user_trace_int(I1, I2, undef, undef, undef, undef, undef, undef);
p(I1, S1) when is_integer(I1) ->
user_trace_int(I1, undef, undef, undef, S1, undef, undef, undef);
p(S1, S2) ->
user_trace_int(undef, undef, undef, undef, S1, S2, undef, undef).
-spec p(probe_arg(), probe_arg(), probe_arg()) -> true | false | error | badarg.
p(I1, I2, I3) when is_integer(I1), is_integer(I2), is_integer(I3) ->
user_trace_int(I1, I2, I3, undef, undef, undef, undef, undef);
p(I1, I2, S1) when is_integer(I1), is_integer(I2) ->
user_trace_int(I1, I2, undef, undef, S1, undef, undef, undef);
p(I1, S1, S2) when is_integer(I1) ->
user_trace_int(I1, undef, undef, undef, S1, S2, undef, undef);
p(S1, S2, S3) ->
user_trace_int(undef, undef, undef, undef, S1, S2, S3, undef).
-spec p(probe_arg(), probe_arg(), probe_arg(), probe_arg()) ->
true | false | error | badarg.
p(I1, I2, I3, I4) when is_integer(I1), is_integer(I2), is_integer(I3), is_integer(I4) ->
user_trace_int(I1, I2, I3, I4, undef, undef, undef, undef);
p(I1, I2, I3, S1) when is_integer(I1), is_integer(I2), is_integer(I3) ->
user_trace_int(I1, I2, I3, undef, S1, undef, undef, undef);
p(I1, I2, S1, S2) when is_integer(I1), is_integer(I2) ->
user_trace_int(I1, I2, undef, undef, S1, S2, undef, undef);
p(I1, S1, S2, S3) when is_integer(I1) ->
user_trace_int(I1, undef, undef, undef, S1, S2, S3, undef);
p(S1, S2, S3, S4) ->
user_trace_int(undef, undef, undef, undef, S1, S2, S3, S4).
-spec p(probe_arg(), probe_arg(), probe_arg(), probe_arg(),
probe_arg()) ->
true | false | error | badarg.
p(I1, I2, I3, I4, S1) when is_integer(I1), is_integer(I2), is_integer(I3), is_integer(I4) ->
user_trace_int(I1, I2, I3, I4, S1, undef, undef, undef);
p(I1, I2, I3, S1, S2) when is_integer(I1), is_integer(I2), is_integer(I3) ->
user_trace_int(I1, I2, I3, undef, S1, S2, undef, undef);
p(I1, I2, S1, S2, S3) when is_integer(I1), is_integer(I2) ->
user_trace_int(I1, I2, undef, undef, S1, S2, S3, undef);
p(I1, S1, S2, S3, S4) when is_integer(I1) ->
user_trace_int(I1, undef, undef, undef, S1, S2, S3, S4).
-spec p(probe_arg(), probe_arg(), probe_arg(), probe_arg(),
probe_arg(), probe_arg()) ->
true | false | error | badarg.
p(I1, I2, I3, I4, S1, S2) when is_integer(I1), is_integer(I2), is_integer(I3), is_integer(I4) ->
user_trace_int(I1, I2, I3, I4, S1, S2, undef, undef);
p(I1, I2, I3, S1, S2, S3) when is_integer(I1), is_integer(I2), is_integer(I3) ->
user_trace_int(I1, I2, I3, undef, S1, S2, S3, undef);
p(I1, I2, S1, S2, S3, S4) when is_integer(I1), is_integer(I2) ->
user_trace_int(I1, I2, undef, undef, S1, S2, S3, S4).
-spec p(probe_arg(), probe_arg(), probe_arg(), probe_arg(),
probe_arg(), probe_arg(), probe_arg()) ->
true | false | error | badarg.
p(I1, I2, I3, I4, S1, S2, S3) when is_integer(I1), is_integer(I2), is_integer(I3), is_integer(I4) ->
user_trace_int(I1, I2, I3, I4, S1, S2, S3, undef);
p(I1, I2, I3, S1, S2, S3, S4) when is_integer(I1), is_integer(I2), is_integer(I3) ->
user_trace_int(I1, I2, I3, undef, S1, S2, S3, S4).
-spec p(probe_arg(), probe_arg(), probe_arg(), probe_arg(),
probe_arg(), probe_arg(), probe_arg(), probe_arg()) ->
true | false | error | badarg.
p(I1, I2, I3, I4, S1, S2, S3, S4) when is_integer(I1), is_integer(I2), is_integer(I3), is_integer(I4) ->
user_trace_int(I1, I2, I3, I4, S1, S2, S3, S4).
-spec user_trace_int(int_p_arg(), int_p_arg(), int_p_arg(), int_p_arg(),
int_p_arg(), int_p_arg(), int_p_arg(), int_p_arg()) ->
true | false | error | badarg.
user_trace_int(I1, I2, I3, I4, S1, S2, S3, S4) ->
UTag = get_utag(),
try
user_trace_i4s4(UTag, I1, I2, I3, I4, S1, S2, S3, S4)
catch
error:nif_not_loaded ->
false
end.
-spec put_utag(undefined | iolist()) -> ok.
put_utag(undefined) ->
put_utag(<<>>);
put_utag(T) when is_binary(T) ->
put(?DTRACE_UT_KEY, T),
ok;
put_utag(T) when is_list(T) ->
put(?DTRACE_UT_KEY, list_to_binary(T)),
ok.
get_utag() ->
case get(?DTRACE_UT_KEY) of
undefined ->
<<>>;
X ->
X
end.
%% Scaffolding to write tedious code: quick brute force and not 100% correct.
scaff_int_args(N) ->
L = lists:sublist(["I1", "I2", "I3", "I4"], N),
[string:join(L, ", ")].
scaff_int_guards(N) ->
L = lists:sublist(["is_integer(I1)", "is_integer(I2)", "is_integer(I3)",
"is_integer(I4)"], N),
lists:flatten(string:join(L, ", ")).
scaff_char_args(N) ->
L = lists:sublist(["S1", "S2", "S3", "S4"], N),
[string:join(L, ", ")].
scaff_fill(N) ->
[string:join(lists:duplicate(N, "undef"), ", ")].
scaff() ->
L = [begin
IntArgs = scaff_int_args(N_int),
IntGuards = scaff_int_guards(N_int),
IntFill = scaff_fill(4 - N_int),
CharArgs = scaff_char_args(N_char),
CharFill = scaff_fill(4 - N_char),
InArgs = string:join(IntArgs ++ CharArgs, ", "),
OutArgs = string:join(IntArgs ++ IntFill ++ CharArgs ++ CharFill,
", "),
{N_int + N_char,
lists:flatten([io_lib:format("p(~s) when ~s ->\n",
[InArgs, IntGuards]),
io_lib:format(" user_trace_int(~s);\n", [OutArgs])
])}
end || N_int <- [0,1,2,3,4], N_char <- [0,1,2,3,4]],
[io:format("%%~p\n~s", [N, Str]) || {N, Str} <- lists:sort(L)].