-export_type(
[ t/0
, status/0
+ , ancestor/0
+ , best_known_origin/0
]).
-export(
[ of_pid/1
+ , best_known_origin/1
+ , print/1
]).
-type status() ::
| waiting
.
+-type ancestor() ::
+ {otp_ancestors , [pid() | atom()]}
+ | {otp_initial_call , mfa()}
+ | {raw_initial_call , mfa()}
+ .
+
+-type best_known_origin() ::
+ {registered_name , atom()}
+ | {ancestry , [ancestor()]}
+ .
+
-define(T, #?MODULE).
-type t() ::
?T{}.
+%% ============================================================================
+%% Public API
+%% ============================================================================
+
-spec of_pid(pid()) ->
t().
of_pid(Pid) ->
, message_queue_len = pid_info_exn(Pid, message_queue_len)
}.
+-spec print(t()) ->
+ ok.
+print(
+ ?T
+ { pid = Pid
+ , registered_name = RegisteredNameOpt
+ , raw_initial_call = InitialCallRaw
+ , otp_initial_call = InitialCallOTPOpt
+ , otp_ancestors = AncestorsOpt
+ , status = Status
+ , memory = Memory
+ , total_heap_size = TotalHeapSize
+ , stack_size = StackSize
+ , message_queue_len = MsgQueueLen
+ }=T
+) ->
+ BestKnownOrigin = best_known_origin(T),
+ io:format("--------------------------------------------------~n"),
+ io:format(
+ "Pid : ~p~n"
+ "BestKnownOrigin : ~p~n"
+ "RegisteredNameOpt : ~p~n"
+ "InitialCallRaw : ~p~n"
+ "InitialCallOTPOpt : ~p~n"
+ "AncestorsOpt : ~p~n"
+ "Status : ~p~n"
+ "Memory : ~p~n"
+ "TotalHeapSize : ~p~n"
+ "StackSize : ~p~n"
+ "MsgQueueLen : ~p~n"
+ "~n",
+ [ Pid
+ , BestKnownOrigin
+ , RegisteredNameOpt
+ , InitialCallRaw
+ , InitialCallOTPOpt
+ , AncestorsOpt
+ , Status
+ , Memory
+ , TotalHeapSize
+ , StackSize
+ , MsgQueueLen
+ ]
+ ).
+
+%% ============================================================================
+%% Private helpers
+%% ============================================================================
+
pid_info_exn(Pid, Key) ->
{some, Value} = pid_info_opt(Pid, Key),
Value.
of {registered_name, []} -> none
; {_ , {Key, Value}} -> {some, Value}
end.
+
+%% ============================================================================
+%% Process code origin approximation or naming the anonymous processes.
+%%
+%% At runtime, given a PID, how precicely can we identify the origin of the
+%% code it is running?
+%%
+%% We have these data points:
+%%
+%% - Sometimes | registered name (if so, we're done)
+%% - Sometimes | ancestor PIDs or registered names
+%% - Always | initial_call (can be too generic, such as erlang:apply)
+%% - Always | current_function (can be too far down the stack)
+%% - Always | current_location (can be too far down the stack)
+%% - Potentially | application tree, but maybe expensive to compute, need to check
+%% ============================================================================
+
+-define(TAG(Tag), fun (X) -> {Tag, X} end).
+
+-spec best_known_origin(t()) ->
+ best_known_origin().
+best_known_origin(?T{registered_name={some, RegisteredName}}) ->
+ {registered_name, RegisteredName};
+best_known_origin(
+ ?T
+ { pid = _Pid
+ , registered_name = none
+ , raw_initial_call = InitCallRaw
+ , otp_initial_call = InitCallOTPOpt1
+ , otp_ancestors = AncestorsOpt1
+ , status = _Status
+ , memory = _Memory
+ , total_heap_size = _TotalHeapSize
+ , stack_size = _StackSize
+ , message_queue_len = _MsgQueueLen
+ }
+) ->
+ ToSingleton = fun (X) -> [X] end,
+ InitCallOTPOpt2 = hope_option:map(InitCallOTPOpt1, ?TAG(otp_initial_call)),
+ AncestorsOpt2 = hope_option:map(AncestorsOpt1 , ?TAG(otp_ancestors)),
+ InitCallOTPOpt3 = hope_option:map(InitCallOTPOpt2, ToSingleton),
+ AncestorsOpt3 = hope_option:map(AncestorsOpt2 , ToSingleton),
+ MaybeInitCallOTP = hope_option:get(InitCallOTPOpt3, []),
+ MaybeAncestors = hope_option:get(AncestorsOpt3 , []),
+ Ancestry =
+ [{raw_initial_call, InitCallRaw}] ++
+ MaybeInitCallOTP ++
+ MaybeAncestors,
+ {ancestry, Ancestry}.