andytill / erlyberly

erlang tracing for the masses
https://twitter.com/erlyberlytips
GNU General Public License v3.0
694 stars 43 forks source link

Show argument names in function traces #162

Open andytill opened 6 years ago

andytill commented 6 years ago

Erlyberly could show the names of function arguments above the values for function arguments in the term tree. Below is a screenshot of annotated records, it might look similar to this.

image

Module AST is already parsed for record metadata. Finding function argument names would be an expansion of that.

https://github.com/andytill/erlyberly/blob/master/src/main/resources/erlyberly/beam/erlyberly.erl#L172

For example, given the function in lists.

-spec sublist(List1, Start, Len) -> List2 when
      List1 :: [T],
      List2 :: [T],
      Start :: pos_integer(),
      Len :: non_neg_integer(),
      T :: term().

sublist(List, S, L) when is_integer(L), L >= 0 ->

The function may return something like.

{lists, [{sublist,["List","S","L"],...}]}

The dialyzer spec can be used but it may not exist. Functions can have multiple clauses. A binding name should be accepted over _ or a literal e.g. Index is more useful than 0 both of which may be present in different function clauses. There are many variations in function clauses, the AST parser should attempt to return something useful but could return a blank if the argument was too complex or long.

If anyone wants to attempt this, post a comment. I can help with the UI for anyone that doesn't want to code java! :D

olafura commented 6 years ago

So I have an initial results, I have yet to convert the params from their ast form. But in their lies the crux, we have complex params like process_info2 on line 79, size_to_bytes on line 118 and record fields on line 155.

Here is a diff:

diff --git a/src/main/resources/erlyberly/beam/erlyberly.erl b/src/main/resources/erlyberly/beam/erlyberly.erl
index 633d35c..bb9a4b0 100644
--- a/src/main/resources/erlyberly/beam/erlyberly.erl
+++ b/src/main/resources/erlyberly/beam/erlyberly.erl
@@ -26,6 +26,7 @@
 -export([get_source_code/1]).
 -export([load_modules_on_path/1]).
 -export([load_module_records/1]).
+-export([load_module_functions/1]).
 -export([module_functions/0]).
 -export([process_info/0]).
 -export([saleyn_fun_src/1]).
@@ -174,6 +175,13 @@ load_module_records(Mod) ->
     Records = [{Tag, record_fields(Fields)} || {attribute,_,record,{Tag,Fields}} <- Forms],
     {ok, Records}.

+load_module_functions(Mod) ->
+    {ok, Forms} = load_module_forms(Mod),
+    Functions = [
+     {Function,Params} || {function,_,Function,_,FunctionBody} <- Forms,
+                          {clause,_,Params, _Guard, _Body} <- FunctionBody],
+    {ok, Functions}.
+
 %%% ============================================================================
 %%% module function tree
 %%% ============================================================================
olafura commented 6 years ago

I can of course just extract the vars, if you want ;)

andytill commented 6 years ago

Thanks @olafura! Yes the return should be a flat list of binding names, it is very difficult to take this apart in Java, like an untyped XML DOM. The function clause combinations do get very complex. In the example case we can get the binding names from the dialyzer spec initially, and then use the function clause to fill in the blanks.

So for example, in this function.

myfunc(0, Acc) -> Acc
myfunc(Index, _) -> myfunc(Index-1, 2).

The list of returned arguments should be ["Index", "Acc"]. If there were two different names for the same argument e.g.

myfunc(0, Acc) -> Acc
myfunc(Index, X) -> myfunc(Index-1, X+1).

Then we could return ["Index", "Acc|X"]. Names in the dialyzer spec should probably override this, unless we don't trust that the spec is being kept upto date!? Maybe we should return that too if it is different to the names in the clause.