Skip to content

Commit 22d95eb

Browse files
committed
Code and tests for contains and match (WIP)
1 parent ecabda5 commit 22d95eb

File tree

2 files changed

+77
-29
lines changed

2 files changed

+77
-29
lines changed

src/tr.erl

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
call_stat/1, call_stat/2]).
3434

3535
%% API - utilities
36-
-export([contains_data/2,
36+
-export([match_data/2, contains_data/2, contains_val/2, match_val/2,
3737
do/1,
3838
lookup/1,
3939
next/1, seq_next/1, next/2, prev/1, seq_prev/1, prev/2,
@@ -78,10 +78,10 @@
7878
%% <li>`info' - For `send' events it is a `t:recipient()' tuple; otherwise `no_info'.</li>
7979
%% </ul>
8080

81-
-type pred() :: fun((tr()) -> boolean()).
82-
%% Predicate returning `true' for matching traces.
81+
-type pred(T) :: fun((T) -> boolean()).
82+
%% Predicate returning `true' for matching terms of type `T'.
8383
%%
84-
%% For other traces it can return a different value or fail.
84+
%% For other terms, it can return a different value or fail.
8585

8686
-type selector(Data) :: fun((tr()) -> Data).
8787
%% Trace selector function.
@@ -229,7 +229,7 @@
229229
%% Function call tree with its accumulated time and number of repetitions.
230230

231231
-type prev_next_options() :: #{tab => table(),
232-
pred => pred()}.
232+
pred => pred(tr())}.
233233
%% Options for obtaining previous and next traces.
234234
%%
235235
%% `tab' is the ETS table with traces (default: `trace').
@@ -356,12 +356,12 @@ select(F, DataVal) ->
356356
select(MS, DataVal, [], SelectRes).
357357

358358
%% @doc Returns matching traces from `tab()'.
359-
-spec filter(pred()) -> [tr()].
359+
-spec filter(pred(tr())) -> [tr()].
360360
filter(F) ->
361361
filter(F, tab()).
362362

363363
%% @doc Returns matching traces from `t:tr_source()'.
364-
-spec filter(pred(), tr_source()) -> [tr()].
364+
-spec filter(pred(tr()), tr_source()) -> [tr()].
365365
filter(F, Tab) ->
366366
Traces = foldl(fun(Tr, State) -> filter_trace(F, Tr, State) end, [], Tab),
367367
lists:reverse(Traces).
@@ -372,15 +372,15 @@ filter(F, Tab) ->
372372
%% Fails if no trace is matched.
373373
%%
374374
%% @see traceback/2
375-
-spec traceback(pred() | index() | tr()) -> [tr()].
375+
-spec traceback(pred(tr()) | index() | tr()) -> [tr()].
376376
traceback(Pred) ->
377377
traceback(Pred, #{}).
378378

379379
%% @doc Returns traceback of the first matching trace from `t:tr_source()'.
380380
%%
381381
%% Fails if no trace is matched.
382382
%% The options `limit' and `format' do not apply.
383-
-spec traceback(pred() | index() | tr(), tb_options()) -> [tr()].
383+
-spec traceback(pred(tr()) | index() | tr(), tb_options()) -> [tr()].
384384
traceback(Index, Options) when is_integer(Index) ->
385385
traceback(fun(#tr{index = I}) -> Index =:= I end, Options);
386386
traceback(T = #tr{}, Options) ->
@@ -392,12 +392,12 @@ traceback(PredF, Options) when is_function(PredF, 1) ->
392392
%% @doc Returns tracebacks of all matching traces from `tab()'.
393393
%%
394394
%% @see tracebacks/2
395-
-spec tracebacks(pred()) -> [[tr()]] | [tb_tree()].
395+
-spec tracebacks(pred(tr())) -> [[tr()]] | [tb_tree()].
396396
tracebacks(PredF) ->
397397
tracebacks(PredF, #{}).
398398

399399
%% @doc Returns tracebacks of all matching traces from `t:tr_source()'.
400-
-spec tracebacks(pred(), tb_options()) -> [[tr()]] | [tb_tree()].
400+
-spec tracebacks(pred(tr()), tb_options()) -> [[tr()]] | [tb_tree()].
401401
tracebacks(PredF, Options) when is_map(Options) ->
402402
Tab = maps:get(tab, Options, tab()),
403403
Output = maps:get(output, Options, shortest),
@@ -426,14 +426,14 @@ root({#tr{} = T, _}) -> T.
426426
%% Fails if no trace is matched.
427427
%%
428428
%% @see range/2
429-
-spec range(pred() | index() | tr()) -> [tr()].
429+
-spec range(pred(tr()) | index() | tr()) -> [tr()].
430430
range(PredF) ->
431431
range(PredF, #{}).
432432

433433
%% @doc Returns a list of traces from `t:tr_source()' between the first matched call and the corresponding return.
434434
%%
435435
%% Fails if no call is matched.
436-
-spec range(pred() | index() | tr(), range_options()) -> [tr()].
436+
-spec range(pred(tr()) | index() | tr(), range_options()) -> [tr()].
437437
range(Index, Options) when is_integer(Index) ->
438438
range(fun(#tr{index = I}) -> Index =:= I end, Options);
439439
range(T = #tr{}, Options) ->
@@ -444,12 +444,12 @@ range(PredF, Options) when is_function(PredF, 1) ->
444444
%% @doc Returns lists of traces from `tab()' between matched calls and corresponding returns.
445445
%%
446446
%% @see ranges/2
447-
-spec ranges(pred()) -> [[tr()]].
447+
-spec ranges(pred(tr())) -> [[tr()]].
448448
ranges(PredF) ->
449449
ranges(PredF, #{}).
450450

451451
%% @doc Returns lists of traces from `t:tr_source()' between matched calls and corresponding returns.
452-
-spec ranges(pred(), range_options()) -> [[tr()]].
452+
-spec ranges(pred(tr()), range_options()) -> [[tr()]].
453453
ranges(PredF, Options) when is_map(Options) ->
454454
Tab = maps:get(tab, Options, tab()),
455455
Output = maps:get(output, Options, all),
@@ -501,13 +501,49 @@ call_stat(KeyF, Tab) ->
501501

502502
%% API - utilities
503503

504-
%% @doc Looks for `DataVal' in `#tr.data'.
504+
%% @doc Returns traces with `#tr.data' containing any values matching the provided predicate.
505+
%%
506+
%% The matching values can occur in (possibly nested) tuples, maps or lists.
507+
-spec match_data(pred(term()), tr()) -> boolean().
508+
match_data(Pred, #tr{data = Data}) when is_function(Pred, 1) ->
509+
match_val(Pred, Data).
510+
511+
%% @doc Returns traces containing `DataVal' in `#tr.data'.
505512
%%
506513
%% `DataVal' can occur in (possibly nested) tuples, maps or lists.
507514
-spec contains_data(term(), tr()) -> boolean().
508515
contains_data(DataVal, #tr{data = Data}) ->
509516
contains_val(DataVal, Data).
510517

518+
%% @doc Checks if `Val' contains any values matching the predicate `Pred'.
519+
%%
520+
%% The matching values can occur in (possibly nested) tuples, maps or lists.
521+
-spec match_val(pred(T), T) -> boolean().
522+
match_val(Pred, Val) ->
523+
case catch Pred(Val) of
524+
true ->
525+
true;
526+
_ ->
527+
case Val of
528+
[_|_] ->
529+
lists:any(fun(El) -> match_val(Pred, El) end, Val);
530+
_ when is_tuple(Val) ->
531+
match_val(Pred, tuple_to_list(Val));
532+
#{} when map_size(Val) > 0 ->
533+
match_val(Pred, maps:to_list(Val));
534+
_ ->
535+
false
536+
end
537+
end.
538+
539+
%% @doc Checks if the given value `DataVal' is present within `Data'.
540+
%%
541+
%% Returns `true' if `DataVal' is found, otherwise returns `false'.
542+
%% `DataVal' can occur in (possibly nested) tuples, maps or lists.
543+
-spec contains_val(T, T) -> boolean().
544+
contains_val(DataVal, Data) ->
545+
match_val(fun(Val) -> Val =:= DataVal end, Data).
546+
511547
%% @doc Executes the function call for the provided `t:tr()' record or index.
512548
-spec do(tr()) -> term().
513549
do(Index) when is_integer(Index) ->
@@ -545,7 +581,7 @@ next(Index, Options) when is_integer(Index) ->
545581
next(#tr{index = Index}, Options) ->
546582
next(Index, Options).
547583

548-
-spec next(index(), pred(), table()) -> tr().
584+
-spec next(index(), pred(tr()), table()) -> tr().
549585
next(Index, Pred, Tab) ->
550586
case ets:next_lookup(Tab, Index) of
551587
{NextIndex, [NextT]} ->
@@ -581,7 +617,7 @@ prev(Index, Options) when is_integer(Index) ->
581617
prev(#tr{index = Index}, Options) ->
582618
prev(Index, Options).
583619

584-
-spec prev(index(), pred(), table()) -> tr().
620+
-spec prev(index(), pred(tr()), table()) -> tr().
585621
prev(Index, Pred, Tab) ->
586622
case ets:prev_lookup(Tab, Index) of
587623
{PrevIndex, [PrevT]} ->
@@ -701,9 +737,7 @@ create_tab(Tab) ->
701737
select(_MS, _DataVal, DataAcc, '$end_of_table') ->
702738
lists:append(lists:reverse(DataAcc));
703739
select(MS, DataVal, DataAcc, {Matched, Cont}) ->
704-
Filtered = lists:filter(fun(#tr{data = Data}) -> contains_val(DataVal, Data);
705-
(T) -> contains_val(DataVal, T)
706-
end, Matched),
740+
Filtered = lists:filter(fun(T) -> contains_data(DataVal, T) end, Matched),
707741
SelectRes = ets:select(Cont),
708742
select(MS, DataVal, [Filtered | DataAcc], SelectRes).
709743

@@ -713,12 +747,6 @@ filter_trace(F, T, State) ->
713747
_ -> State
714748
end.
715749

716-
contains_val(DataVal, DataVal) -> true;
717-
contains_val(DataVal, L) when is_list(L) -> lists:any(fun(El) -> contains_val(DataVal, El) end, L);
718-
contains_val(DataVal, T) when is_tuple(T) -> contains_val(DataVal, tuple_to_list(T));
719-
contains_val(DataVal, M) when is_map(M) -> contains_val(DataVal, maps:to_list(M));
720-
contains_val(_, _) -> false.
721-
722750
index(Tab) ->
723751
case ets:last(Tab) of
724752
I when is_integer(I) -> I + 1;
@@ -874,7 +902,7 @@ stop_skipping_messages(Pid, PidsTab) ->
874902

875903
%% Filter tracebacks
876904

877-
-spec tb_step(pred(), tr(), map()) -> map().
905+
-spec tb_step(pred(tr()), tr(), map()) -> map().
878906
tb_step(PredF, T = #tr{pid = Pid, event = Event},
879907
State = #{tbs := TBs, call_stacks := CallStacks, output := Output, format := Format,
880908
count := Count, limit := Limit}) ->

test/tr_SUITE.erl

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,9 @@ groups() ->
5858
tb_tree,
5959
tb_tree_longest,
6060
tb_roots]},
61-
{util, [do]},
61+
{util, [contains,
62+
match,
63+
do]},
6264
{call_stat, [simple_total,
6365
tree_total,
6466
simple_total_with_messages,
@@ -288,6 +290,24 @@ incomplete_ranges(_Config) ->
288290
%% Skip ranges with missing returns
289291
[[T1]] = tr:ranges(fun(#tr{}) -> true end, #{output => incomplete}).
290292

293+
contains(_Config) ->
294+
trace_fib3(),
295+
Result = tr:filter(fun(T) -> tr:contains_data(1, T) end),
296+
?assertMatch([#tr{index = 3, event = call, data = [1]},
297+
#tr{index = 4, event = return, data = 1},
298+
#tr{index = 7, event = return, data = 1},
299+
#tr{index = 8, event = call, data = [1]},
300+
#tr{index = 9, event = return, data = 1}], Result),
301+
?assertEqual(Result, tr:filter(fun(#tr{data = Data}) -> tr:contains_val(1, Data) end)).
302+
303+
match(_Config) ->
304+
trace_fib3(),
305+
Pred = fun([N]) -> N > 1 end,
306+
Result = tr:filter(fun(T) -> tr:match_data(Pred, T) end),
307+
?assertMatch([#tr{index = 1, event = call, data = [3]},
308+
#tr{index = 2, event = call, data = [2]}], Result),
309+
?assertEqual(Result, tr:filter(fun(#tr{data = Data}) -> tr:match_val(Pred, Data) end)).
310+
291311
do(_Config) ->
292312
tr:trace([{?MODULE, fib, 1}]),
293313
?MODULE:fib(2),

0 commit comments

Comments
 (0)