@ -1,257 +0,0 @@ | |||
/* | |||
* %CopyrightBegin% | |||
* | |||
* Copyright Ericsson 2015-2016. All Rights Reserved. | |||
* | |||
* Licensed under the Apache License, Version 2.0 (the "License"); | |||
* you may not use this file except in compliance with the License. | |||
* You may obtain a copy of the License at | |||
* | |||
* http://www.apache.org/licenses/LICENSE-2.0 | |||
* | |||
* Unless required by applicable law or agreed to in writing, software | |||
* distributed under the License is distributed on an "AS IS" BASIS, | |||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |||
* See the License for the specific language governing permissions and | |||
* limitations under the License. | |||
* | |||
* %CopyrightEnd% | |||
*/ | |||
/* | |||
* Purpose: NIF library for process/port tracer | |||
* | |||
*/ | |||
#define STATIC_ERLANG_NIF 1 | |||
#include "erl_nif.h" | |||
#include "config.h" | |||
#include "sys.h" | |||
#ifdef VALGRIND | |||
# include <valgrind/memcheck.h> | |||
#endif | |||
/* NIF interface declarations */ | |||
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info); | |||
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, ERL_NIF_TERM load_info); | |||
static void unload(ErlNifEnv* env, void* priv_data); | |||
/* The NIFs: */ | |||
static ERL_NIF_TERM enabled(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); | |||
static ERL_NIF_TERM trace(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]); | |||
static ErlNifFunc nif_funcs[] = { | |||
{"enabled", 3, enabled}, | |||
{"trace", 5, trace} | |||
}; | |||
ERL_NIF_INIT(erl_tracer, nif_funcs, load, NULL, upgrade, unload) | |||
#define ATOMS \ | |||
ATOM_DECL(call); \ | |||
ATOM_DECL(command); \ | |||
ATOM_DECL(cpu_timestamp); \ | |||
ATOM_DECL(discard); \ | |||
ATOM_DECL(exception_from); \ | |||
ATOM_DECL(extra); \ | |||
ATOM_DECL(match_spec_result); \ | |||
ATOM_DECL(monotonic); \ | |||
ATOM_DECL(ok); \ | |||
ATOM_DECL(remove); \ | |||
ATOM_DECL(return_from); \ | |||
ATOM_DECL(scheduler_id); \ | |||
ATOM_DECL(send); \ | |||
ATOM_DECL(send_to_non_existing_process); \ | |||
ATOM_DECL(seq_trace); \ | |||
ATOM_DECL(spawn); \ | |||
ATOM_DECL(strict_monotonic); \ | |||
ATOM_DECL(timestamp); \ | |||
ATOM_DECL(trace); \ | |||
ATOM_DECL(trace_status); \ | |||
ATOM_DECL(trace_ts); \ | |||
ATOM_DECL(true); \ | |||
ATOM_DECL(gc_minor_start); \ | |||
ATOM_DECL(gc_minor_end); \ | |||
ATOM_DECL(gc_major_start); \ | |||
ATOM_DECL(gc_major_end); | |||
#define ATOM_DECL(A) static ERL_NIF_TERM atom_##A | |||
ATOMS | |||
#undef ATOM_DECL | |||
static int load(ErlNifEnv* env, void** priv_data, ERL_NIF_TERM load_info) | |||
{ | |||
#define ATOM_DECL(A) atom_##A = enif_make_atom(env, #A) | |||
ATOMS | |||
#undef ATOM_DECL | |||
*priv_data = NULL; | |||
return 0; | |||
} | |||
static void unload(ErlNifEnv* env, void* priv_data) | |||
{ | |||
} | |||
static int upgrade(ErlNifEnv* env, void** priv_data, void** old_priv_data, | |||
ERL_NIF_TERM load_info) | |||
{ | |||
if (*old_priv_data != NULL) { | |||
return -1; /* Don't know how to do that */ | |||
} | |||
if (*priv_data != NULL) { | |||
return -1; /* Don't know how to do that */ | |||
} | |||
if (load(env, priv_data, load_info)) { | |||
return -1; | |||
} | |||
return 0; | |||
} | |||
static ERL_NIF_TERM enabled(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||
{ | |||
ErlNifPid to_pid; | |||
ErlNifPort to_port; | |||
ERL_NIF_TERM ret = enif_is_identical(argv[0], atom_trace_status) ? | |||
atom_remove : atom_discard; | |||
ASSERT(argc == 3); | |||
if (enif_get_local_pid(env, argv[1], &to_pid)) { | |||
if (!enif_is_process_alive(env, &to_pid)) | |||
/* tracer is dead so we should remove this trace point */ | |||
return ret; | |||
} else if (enif_get_local_port(env, argv[1], &to_port)) { | |||
if (!enif_is_port_alive(env, &to_port)) | |||
/* tracer is dead so we should remove this trace point */ | |||
return ret; | |||
} else { | |||
/* The state was not a pid or a port */ | |||
return ret; | |||
} | |||
/* Only generate trace for when tracer != tracee */ | |||
if (enif_is_identical(argv[1], argv[2])) { | |||
return atom_discard; | |||
} | |||
return atom_trace; | |||
} | |||
/* | |||
-spec trace(seq_trace, TracerState :: pid() | port(), | |||
Label :: non_neg_integer(), | |||
Msg :: term(), | |||
Opts :: map()) -> ignored(); | |||
trace(Tag :: atom(), TracerState :: pid() | port(), | |||
Tracee :: pid() || port() || undefined, | |||
Msg :: term(), | |||
Opts :: map()) -> ignored(). | |||
*/ | |||
static ERL_NIF_TERM trace(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[]) | |||
{ | |||
ERL_NIF_TERM value, msg, tt[8], opts; | |||
ErlNifPid to_pid; | |||
ErlNifPort to_port; | |||
size_t tt_sz = 0; | |||
int is_port = 0; | |||
size_t opts_sz = 0; | |||
ASSERT(argc == 5); | |||
if (!enif_get_local_pid(env, argv[1], &to_pid)) { | |||
if (!enif_get_local_port(env, argv[1], &to_port)) { | |||
/* This only fails if argv[1] is a not a local port/pid | |||
which should not happen as it is checked in enabled */ | |||
ASSERT(0); | |||
return atom_ok; | |||
} | |||
is_port = 1; | |||
} | |||
opts = argv[4]; | |||
if (!enif_get_map_size(env, opts, &opts_sz)) | |||
opts_sz = 0; | |||
if (opts_sz && enif_get_map_value(env, opts, atom_extra, &value)) { | |||
tt[tt_sz++] = atom_trace; | |||
tt[tt_sz++] = argv[2]; | |||
tt[tt_sz++] = argv[0]; | |||
tt[tt_sz++] = argv[3]; | |||
tt[tt_sz++] = value; | |||
} else { | |||
if (enif_is_identical(argv[0], atom_seq_trace)) { | |||
tt[tt_sz++] = atom_seq_trace; | |||
tt[tt_sz++] = argv[2]; | |||
tt[tt_sz++] = argv[3]; | |||
} else { | |||
tt[tt_sz++] = atom_trace; | |||
tt[tt_sz++] = argv[2]; | |||
tt[tt_sz++] = argv[0]; | |||
tt[tt_sz++] = argv[3]; | |||
} | |||
} | |||
if (opts_sz && enif_get_map_value(env, opts, atom_match_spec_result, &value)) { | |||
tt[tt_sz++] = value; | |||
} | |||
if (opts_sz && enif_get_map_value(env, opts, atom_scheduler_id, &value)) { | |||
tt[tt_sz++] = value; | |||
} | |||
if (opts_sz && enif_get_map_value(env, opts, atom_timestamp, &value)) { | |||
ERL_NIF_TERM ts; | |||
if (enif_is_identical(value, atom_monotonic)) { | |||
ErlNifTime mon = enif_monotonic_time(ERL_NIF_NSEC); | |||
ts = enif_make_int64(env, mon); | |||
} else if (enif_is_identical(value, atom_strict_monotonic)) { | |||
ErlNifTime mon = enif_monotonic_time(ERL_NIF_NSEC); | |||
ERL_NIF_TERM unique = enif_make_unique_integer( | |||
env, ERL_NIF_UNIQUE_MONOTONIC); | |||
ts = enif_make_tuple2(env, enif_make_int64(env, mon), unique); | |||
} else if (enif_is_identical(value, atom_timestamp)) { | |||
ts = enif_now_time(env); | |||
} else if (enif_is_identical(value, atom_cpu_timestamp)) { | |||
ts = enif_cpu_time(env); | |||
} else { | |||
ASSERT(0); | |||
goto error; | |||
} | |||
tt[tt_sz++] = ts; | |||
if (tt[0] == atom_trace) | |||
tt[0] = atom_trace_ts; | |||
} | |||
msg = enif_make_tuple_from_array(env, tt, tt_sz); | |||
if (is_port) { | |||
ErlNifBinary bin; | |||
if (!enif_term_to_binary(env, msg, &bin)) | |||
goto error; | |||
msg = enif_make_binary(env, &bin); | |||
(void) enif_port_command(env, &to_port, NULL, msg); | |||
/* if failure: port has probably died, enabled will clean up */ | |||
enif_release_binary(&bin); | |||
} else { | |||
(void) enif_send(env, &to_pid, NULL, msg); | |||
/* if failure: process has probably died, enabled will clean up */ | |||
} | |||
error: | |||
return atom_ok; | |||
} |
@ -0,0 +1,112 @@ | |||
-module(tpTracerLog). | |||
-include("eTpf.hrl"). | |||
-export([ | |||
start_link/1 | |||
, init/2 | |||
]). | |||
%% sys callbacks | |||
-export([ | |||
system_continue/3 | |||
, system_terminate/4 | |||
, system_code_change/4 | |||
]). | |||
-record(state, { | |||
parent :: pid() | |||
, fDir :: file:filename_all() %% file base name | |||
, fBaseName :: file:filename_all() %% file base name | |||
, size = 0 :: non_neg_integer() | |||
, fMaxSize :: infinity | non_neg_integer() | |||
, ioDevice :: file:io_device() | |||
, tcmIsCut = false | |||
, fMaxLog :: pos_integer() | |||
, fLogIndex = 0 :: non_neg_integer() | |||
}). | |||
start_link(Opts) -> | |||
Pid = proc_lib:spawn_link(?MODULE, init, [self(), Opts]), | |||
{ok, Pid}. | |||
init(Parent, TracerOpts) -> | |||
process_flag(message_queue_data, off_heap), | |||
process_flag(trap_exit, true), | |||
%% No need to close the file, it'll be closed when the process exits. | |||
FDir = maps:get(fDir, TracerOpts, <<"./">>), | |||
FBaseName = maps:get(fBaseName, TracerOpts, <<"traces.log">>), | |||
Filename = fileName(FDir, FBaseName), | |||
{ok, IoDevice} = file:open(Filename, [write, raw]), | |||
TcmIsCut = maps:get(tcmIsCut, TracerOpts, maps:get(tcmDepth, ?defTcmMap)), | |||
case TcmIsCut of | |||
true -> | |||
erlang:put(tcmDepth, maps:get(tcmDepth, TracerOpts, maps:get(tcmDepth, ?defTcmMap))), | |||
erlang:put(tcmListSize, maps:get(tcmListSize, TracerOpts, maps:get(tcmListSize, ?defTcmMap))), | |||
erlang:put(tcmMapSize, maps:get(tcmMapSize, TracerOpts, maps:get(tcmMapSize, ?defTcmMap))), | |||
erlang:put(tcmTupleSize, maps:get(tcmTupleSize, TracerOpts, maps:get(tcmTupleSize, ?defTcmMap))), | |||
erlang:put(tcmBinSize, maps:get(tcmBinSize, TracerOpts, maps:get(tcmBinSize, ?defTcmMap))), | |||
erlang:put(tcmBitSize, maps:get(tcmBitSize, TracerOpts, maps:get(tcmBitSize, ?defTcmMap))), | |||
erlang:put(tcmNestStruct, maps:get(tcmNestStruct, TracerOpts, maps:get(tcmNestStruct, ?defTcmMap))); | |||
_ -> | |||
ignore | |||
end, | |||
State = #state{ | |||
parent = Parent | |||
, fDir = FDir | |||
, fBaseName = FBaseName | |||
, ioDevice = IoDevice | |||
, fMaxSize = maps:get(fMaxSize, TracerOpts, 61644800) | |||
, fMaxLog = maps:get(fMaxLog, TracerOpts, 50000) | |||
, tcmIsCut = TcmIsCut | |||
}, | |||
loop(State). | |||
loop(#state{parent = Parent, fDir = FDir, fBaseName = FBaseName, size = Size, ioDevice = IoDevice, fMaxSize = MaxSize, fMaxLog = FMaxLog, fLogIndex = FLogIndex, tcmIsCut = TcmIsCut} = State) -> | |||
receive | |||
{system, From, Request} -> | |||
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], State); | |||
{'EXIT', Parent, Reason} -> | |||
terminate(Reason, State); | |||
RMsg -> | |||
Msg = case TcmIsCut of true -> tpTermCut:cut(RMsg); _ -> RMsg end, | |||
MsgBin = eFmt:format("~w", [Msg]), | |||
ok = file:write(IoDevice, MsgBin), | |||
NewSize = Size + byte_size(MsgBin), | |||
NewFLogIndex = FLogIndex + 1, | |||
case NewFLogIndex >= FMaxLog orelse NewSize >= MaxSize of | |||
true -> | |||
ok = file:close(IoDevice), | |||
Filename = fileName(FDir, FBaseName), | |||
{ok, NewIoDevice} = file:open(Filename, [write, raw]), | |||
loop(State#state{size = 0, fLogIndex = 0, ioDevice = NewIoDevice}); | |||
_ -> | |||
loop(State#state{size = NewSize, fLogIndex = NewFLogIndex}) | |||
end | |||
end. | |||
system_continue(_, _, State) -> | |||
loop(State). | |||
-spec system_terminate(any(), _, _, #state{}) -> no_return(). | |||
system_terminate(Reason, _, _, State) -> | |||
terminate(Reason, State). | |||
system_code_change(Misc, _, _, _) -> | |||
{ok, Misc}. | |||
-spec terminate(any(), #state{}) -> no_return(). | |||
terminate(Reason, #state{ioDevice = IoDevice}) -> | |||
ok = file:close(IoDevice), | |||
exit(Reason). | |||
fileName(Dir, FBaseName) -> | |||
{{Year, Month, Day}, {Hour, Minute, _Second}} = erlang:localtime(), | |||
TimeStr = eFmt:formatBin("~B~2.10.0B~2.10.0B~B~2.10.0B_", [Year, Month, Day, Hour, Minute]), | |||
FileName = <<TimeStr/binary, FBaseName/binary>>, | |||
WholeName = filename:absname(filename:join(Dir, FileName)), | |||
ok = filelib:ensure_dir(WholeName), | |||
WholeName. | |||