Current section

7 Versions

Jump to

Compare versions

15 files changed
+1505 additions
-233 deletions
  @@ -1,5 +1,5 @@
1 1 {<<"name">>,<<"xprof_core">>}.
2 - {<<"version">>,<<"2.0.0-rc.3">>}.
2 + {<<"version">>,<<"2.0.0-rc.4">>}.
3 3 {<<"requirements">>,
4 4 #{<<"customized_hdr_histogram">> =>
5 5 #{<<"app">> => <<"customized_hdr_histogram">>,<<"optional">> => false,
  @@ -16,11 +16,13 @@
16 16 {<<"files">>,
17 17 [<<"src/xprof_core.app.src">>,<<"rebar.config">>,<<"src/test_module.erl">>,
18 18 <<"src/xprof_core.erl">>,<<"src/xprof_core_app.erl">>,
19 - <<"src/xprof_core_cmd.erl">>,<<"src/xprof_core_elixir_syntax.erl">>,
19 + <<"src/xprof_core_cmd.erl">>,<<"src/xprof_core_cmd_argdist.erl">>,
20 + <<"src/xprof_core_cmd_funlatency.erl">>,
21 + <<"src/xprof_core_elixir_syntax.erl">>,
20 22 <<"src/xprof_core_erlang_syntax.erl">>,<<"src/xprof_core_language.erl">>,
21 23 <<"src/xprof_core_lib.erl">>,<<"src/xprof_core_ms.erl">>,
22 - <<"src/xprof_core_records.erl">>,<<"src/xprof_core_sup.erl">>,
23 - <<"src/xprof_core_trace_handler.erl">>,
24 + <<"src/xprof_core_query.erl">>,<<"src/xprof_core_records.erl">>,
25 + <<"src/xprof_core_sup.erl">>,<<"src/xprof_core_trace_handler.erl">>,
24 26 <<"src/xprof_core_trace_handler_sup.erl">>,<<"src/xprof_core_tracer.erl">>,
25 27 <<"src/xprof_core_vm_info.erl">>]}.
26 28 {<<"licenses">>,[<<"BSD-3-Clause">>]}.
  @@ -1,6 +1,6 @@
1 1 {application,xprof_core,
2 2 [{description,"Visual BEAM tracer/profiler tracer core"},
3 - {vsn,"2.0.0-rc.3"},
3 + {vsn,"2.0.0-rc.4"},
4 4 {registered,[]},
5 5 {mod,{xprof_core_app,[]}},
6 6 {applications,[kernel,stdlib,compiler,lager,
  @@ -6,10 +6,13 @@
6 6
7 7 %% Monitoring functions
8 8 monitor_pp/1,
9 + monitor_pp/2,
9 10 monitor/1,
11 + monitor/2,
10 12 demonitor/1,
11 13 get_all_monitored/0,
12 14 get_data/2,
15 + get_data_pp/2,
13 16 get_called_funs/1,
14 17 get_called_funs_pp/1,
15 18
  @@ -35,7 +38,22 @@
35 38 get_mode/0
36 39 ]).
37 40
38 - -export_type([mfa_spec/0, mfa_id/0, mfa_name/0, mode/0]).
41 + -export_type([cmd/0, param_name/0, params/0, options/0,
42 + ms/0, mfa_spec/0, mfa_id/0, mfa_name/0,
43 + mode/0
44 + ]).
45 +
46 + -type cmd() :: atom().
47 + %% Command name.
48 +
49 + -type param_name() :: atom().
50 + %% Parameter name passed to commands.
51 +
52 + -type params() :: [{param_name(), term()}].
53 + %% Parameters passed to commands.
54 +
55 + -type options() :: [{mfa, string()} | {atom(), erl_parse:abstract_expr()}].
56 + %% Internal representation of params
39 57
40 58 -type ms() :: [tuple()].
41 59 %% Match-specification.
  @@ -96,13 +114,26 @@ get_matching_mfas_pp(Query) ->
96 114 %% @doc Start monitoring based on the specified query string.
97 115 -spec monitor_pp(binary()) -> ok | {error, Reason :: already_traced | string()}.
98 116 monitor_pp(Query) ->
99 - xprof_core_tracer:monitor(unicode:characters_to_list(Query)).
117 + monitor_pp(Query, []).
118 +
119 + %% @doc Start monitoring based on the specified query string with additional
120 + %% parameters. Additional parameters have precedence overthe same keys in the
121 + %% query.
122 + -spec monitor_pp(binary(), [{binary(), binary()}])
123 + -> ok | {error, Reason :: already_traced | string()}.
124 + monitor_pp(Query, AdditionalParams) ->
125 + xprof_core_tracer:monitor_query(Query, AdditionalParams).
100 126
101 127 %% @doc Start monitoring the specified function (MFA).
102 128 -spec monitor(mfa()) -> ok | {error, Reason :: already_traced | string()}.
103 129 monitor(MFA) ->
104 130 xprof_core_tracer:monitor(MFA).
105 131
132 + %% @doc Start the specified command.
133 + -spec monitor(cmd(), params()) -> ok | {error, Reason :: term()}.
134 + monitor(Cmd, Params) ->
135 + xprof_core_tracer:monitor_cmd(Cmd, Params).
136 +
106 137 %% @doc Stop monitoring the specified function (MFA).
107 138 -spec demonitor(xprof_core:mfa_id()) -> ok.
108 139 demonitor(MFA) ->
  @@ -125,6 +156,22 @@ get_all_monitored() ->
125 156 get_data(MFA, TimeStamp) ->
126 157 xprof_core_trace_handler:data(MFA, TimeStamp).
127 158
159 + get_data_pp(MFA, TimeStamp) ->
160 + case xprof_core_trace_handler:data(MFA, TimeStamp) of
161 + {error, _} = Error ->
162 + Error;
163 + Items ->
164 + ModeCB = xprof_core_lib:get_mode_cb(),
165 + [[case is_atom(Key) of
166 + true ->
167 + {Key, Value};
168 + _ ->
169 + {ModeCB:fmt_term(Key), Value}
170 + end
171 + || {Key, Value} <- Item]
172 + || Item <- Items]
173 + end.
174 +
128 175 %% @doc Return list of called functions for given mfa tuple.
129 176 -spec get_called_funs(xprof_core:mfa_id()) -> [xprof_core:mfa_id()].
130 177 get_called_funs(MFA) ->
  @@ -1,6 +1,177 @@
1 + %%% There are 3 types of cmds
2 + %%% - tracing functions -> mfaspec present -> could use meta-tracing with trace_pattern
3 + %%% -> can be turned off by trace_pattern
4 + %%% - cmd id = mfaid
5 + %%% - tracing send or receive -> no mfa, no meta-tracing, but can use trace_pattern (to match on message/sender/receiver)
6 + %%% -> can be turned off by trace_pattern
7 + %%% - cmd id = ??? (single tracer per cmd-name or trace tag)
8 + %%% - other -> no trace_pattern -> xprof_core_tracer should find out where to send trace based on trace tag (maybe)
9 + %%% -> can only be turned off by trace/3
10 + %%% - cmd id = ??? (single tracer per cmd-name or trace tag)
1 11 -module(xprof_core_cmd).
2 12
3 - -export([expand_query/1]).
13 + -export([expand_query/1,
14 + process_query/2,
15 + process_cmd/2]).
16 +
17 + %%
18 + %% Callback functions that need to be implemented
19 + %% for a command behvaiour
20 + %%
21 +
22 + %% Return list of mandatory params
23 + -callback mandatory_params() -> [atom()].
24 +
25 + %% Convert param value from syntax-tree format to Erlang term or expression
26 + %% (Useful to implement some syntactic sugar/shorthands)
27 + -callback param_from_ast(Key :: atom(), Ast :: erl_parse:syntax_tree()) ->
28 + {ok, Value :: term()} | {error, Reason :: term()}.
29 +
30 + %% Validate param value and optionally convert to internal format
31 + %% (Called for both params parsed from query string and provided directly as
32 + %% additional params)
33 + -callback param_to_internal(Key :: atom(), Value :: term()) ->
34 + {ok, NewValue :: term()} | {error, Reason :: term()}.
35 +
36 + -record(cmd, {name, cb_mod, desc}).
37 +
38 + process_query(Query, AdditionalParams) ->
39 + case xprof_core_query:parse_query(Query) of
40 + {error, _} = Error ->
41 + Error;
42 + {ok, Cmd, ParamsAst} ->
43 + try
44 + %% * lookup cmd callback
45 + CmdCB = get_cmd_callback(Cmd),
46 +
47 + %% * convert params from AST to term
48 + %% (FIXME could error on unknown params)
49 + {ok, QueryParams} = params_from_ast(Cmd, ParamsAst, CmdCB, []),
50 +
51 + %% * merge additional params
52 + %% - if additional params have precedence then it is possible to
53 + %% modify the query from gui menu/buttons
54 + %% - if query params have precedence then we make sure what is
55 + %% displayed in query string takes effect
56 + Params = merge_params(QueryParams, AdditionalParams),
57 +
58 + %% * check all mandatory params are present
59 + MandatoryParams = CmdCB:mandatory_params(),
60 + ok = check_mandatory_params(Params, MandatoryParams),
61 +
62 + %% * check param value types as much as possible
63 + %% and maybe convert to internal format (eg mfaspec)
64 + {ok, Options} = params_to_internal(Cmd, Params, CmdCB, []),
65 +
66 + {start_cmd, Cmd, Options, CmdCB, Query}
67 + catch throw:{error, Reason} ->
68 + format_error(Reason);
69 + error:{badmatch, {error, Reason}} ->
70 + format_error(Reason)
71 + end
72 + end.
73 +
74 + process_cmd(Cmd, Params) ->
75 + try
76 + %% * lookup cmd callback
77 + CmdCB = get_cmd_callback(Cmd),
78 +
79 + %% * FIXME figure out some string represention to fake querystring
80 + %% (probably move to xprof_core_query:fmt_query(Cmd, Params)
81 + Query =
82 + case proplists:get_value(mfa, Params) of
83 + undefined ->
84 + <<"">>;
85 + MFAStr when is_list(MFAStr) ->
86 + list_to_binary(MFAStr);
87 + {Mod, Fun, Arity} ->
88 + ModeCb = xprof_core_lib:get_mode_cb(),
89 + _FormattedMFA = ModeCb:fmt_mfa(Mod, Fun, Arity)
90 + end,
91 +
92 + %% * check all mandatory params are present
93 + MandatoryParams = CmdCB:mandatory_params(),
94 + ok = check_mandatory_params(Params, MandatoryParams),
95 +
96 + %% * check param value types as much as possible
97 + %% and maybe convert to internal format (eg mfaspec)
98 + {ok, Options} = params_to_internal(Cmd, Params, CmdCB, []),
99 +
100 + {start_cmd, Cmd, Options, CmdCB, Query}
101 + catch throw:{error, Reason} ->
102 + format_error(Reason);
103 + error:{badmatch, {error, Reason}} ->
104 + format_error(Reason)
105 + end.
106 +
107 + get_cmd_callback(funlatency) ->
108 + xprof_core_cmd_funlatency;
109 + get_cmd_callback(argdist) ->
110 + xprof_core_cmd_argdist;
111 + get_cmd_callback(Cmd) ->
112 + throw({error, {unknown_command, Cmd}}).
113 +
114 + params_from_ast(Cmd, [{Key, Ast}|ParamsAst], CmdCB, Acc) ->
115 + case CmdCB:param_from_ast(Key, Ast) of
116 + {error, Reason} ->
117 + {error, {param_from_ast, Cmd, Key, Reason}};
118 + {ok, Value} ->
119 + params_from_ast(Cmd, ParamsAst, CmdCB, [{Key, Value}|Acc])
120 + end;
121 + params_from_ast(_, [], _, Acc) ->
122 + {ok, lists:reverse(Acc)}.
123 +
124 + params_to_internal(Cmd, [{Key, Value}|Params], CmdCB, Acc) ->
125 + case CmdCB:param_to_internal(Key, Value) of
126 + {error, Reason} ->
127 + {error, {param_to_internal, Cmd, Key, Reason}};
128 + {ok, InternalValue} ->
129 + params_to_internal(Cmd, Params, CmdCB, [{Key, InternalValue}|Acc])
130 + end;
131 + params_to_internal(_, [], _, Acc) ->
132 + {ok, lists:reverse(Acc)}.
133 +
134 + merge_params(P1, P2) ->
135 + lists:foldl(
136 + fun({Key, _} = Param, P) ->
137 + lists:keystore(Key, 1, P, Param)
138 + end, P2, P1).
139 +
140 + check_mandatory_params(Params, MandatoryParams) ->
141 + [case lists:keymember(Key, 1, Params) of
142 + true -> ok;
143 + false -> xprof_core_lib:err("Mandatory parameter ~p missing", [Key])
144 + end
145 + || Key <- MandatoryParams],
146 + ok.
147 +
148 + -spec format_error(any()) -> {error, string()}.
149 + format_error({unknown_command, Cmd}) ->
150 + xprof_core_lib:fmt_err("Unknown command ~p", [Cmd]);
151 + format_error({Where, Cmd, Key, unknown_param})
152 + when Where =:= param_from_ast;
153 + Where =:= param_to_internal ->
154 + xprof_core_lib:fmt_err("~p is not a valid parameter of command ~p",
155 + [Key, Cmd]);
156 + format_error({Where, _Cmd, Key, wrong_value})
157 + when Where =:= param_from_ast;
158 + Where =:= param_to_internal ->
159 + xprof_core_lib:fmt_err("Paremeter ~p has wrong value type", [Key]);
160 + format_error({param_to_internal, Cmd, Key, Reason}) ->
161 + CmdCB = get_cmd_callback(Cmd),
162 + xprof_core_lib:fmt_err("Error converting parameter ~p to internal format: ~s",
163 + [Key, CmdCB:format_error(Reason)]);
164 + format_error({param_from_ast, Cmd, Key, Reason}) ->
165 + CmdCB = get_cmd_callback(Cmd),
166 + xprof_core_lib:fmt_err("Error converting parameter ~p to internal format: ~s",
167 + [Key, CmdCB:format_error(Reason)]);
168 + format_error(Reason) ->
169 + case io_lib:deep_char_list(Reason) of
170 + true ->
171 + {error, Reason};
172 + false ->
173 + xprof_core_lib:fmt_err("Unexpected error handling query: ~p", [Reason])
174 + end.
4 175
5 176 %% @doc Get expansion suggestions for the given possibly incomplete query.
6 177 -spec expand_query(binary()) -> {CommonPrefix :: binary(), [Match]}
  @@ -9,17 +180,138 @@
9 180 Match :: {Prefix :: binary(), Label :: binary(), Hint :: binary()}
10 181 | {Prefix :: binary(), Label :: binary()}.
11 182 expand_query(Query) ->
183 + Mode = xprof_core_lib:get_mode(),
12 184 try
13 - Result = expand_match_spec(Query),
185 + Result =
186 + case {Mode, Query} of
187 + {erlang, <<"#", Q/binary>>} ->
188 + expand_extended_query(Q);
189 + {elixir, <<"%", Q/binary>>} ->
190 + expand_extended_query(Q);
191 + _ ->
192 + expand_match_spec(Query)
193 + end,
14 194 maybe_add_common_prefix(Result)
15 195 catch throw:{error, _} = Error ->
16 196 Error
17 197 end.
18 198
199 + expand_extended_query(Query) ->
200 + ModeCb = xprof_core_lib:get_mode_cb(),
201 + case ModeCb:parse_incomplete_query(unicode:characters_to_list(Query)) of
202 + {incomplete_cmd, CmdPrefix} ->
203 + _FilteredCmds = filter_cmds(unicode:characters_to_binary(CmdPrefix), ModeCb);
204 + {incomplete_key, KeyPrefix, Cmd, Params} when is_list(KeyPrefix) ->
205 + CmdInfo = get_cmd_info_or_fail(Cmd),
206 + MissingParams = missing_params(CmdInfo, Params),
207 + _FilteredParams = filter_params(unicode:characters_to_binary(KeyPrefix), MissingParams, ModeCb);
208 + {incomplete_key, {Key, RestStr}, Cmd, Params} when is_atom(Key) ->
209 + CmdInfo = get_cmd_info_or_fail(Cmd),
210 + MissingParams = missing_params(CmdInfo, Params),
211 + case lists:member(Key, MissingParams) of
212 + true ->
213 + %% FIXME this is erlang/elixir specific - move it from here
214 + case RestStr of
215 + "" ->
216 + [{<<"= ">>, ModeCb:fmt_param(Key)}];
217 + "=" ->
218 + [{<<" ">>, ModeCb:fmt_param(Key)}];
219 + ":" ->
220 + [{<<" ">>, ModeCb:fmt_param(Key)}];
221 + _ ->
222 + xprof_core_lib:err("'='/':' expected after param name: ~s",
223 + [ModeCb:fmt_param(Key)])
224 + end;
225 + false ->
226 + xprof_core_lib:err("unknown or duplicated param name: ~s",
227 + [ModeCb:fmt_param(Key)])
228 + end;
229 + {incomplete_value, Key, ValuePrefix, Cmd, Params} ->
230 + CmdInfo = get_cmd_info_or_fail(Cmd),
231 + MissingParams = missing_params(CmdInfo, Params),
232 + case lists:member(Key, MissingParams) of
233 + true ->
234 + case Key of
235 + mfa ->
236 + %% special case `mfa'
237 + ValueBin = unicode:characters_to_binary(ValuePrefix),
238 + expand_match_spec(ValueBin);
239 + _ ->
240 + [{<<>>, ModeCb:fmt_param(Key)}]
241 + end;
242 + false ->
243 + xprof_core_lib:err("unknown or duplicated param name: ~p",
244 + [ModeCb:fmt_param(Key)])
245 + end;
246 + {ok, Cmd, _Params = []} ->
247 + CmdInfo = get_cmd_info_or_fail(Cmd),
248 + MissingParams = missing_params(CmdInfo, []),
249 + _FilteredParams = filter_params(<<>>, MissingParams, ModeCb);
250 + {ok, Cmd, Params} ->
251 + CmdInfo = get_cmd_info_or_fail(Cmd),
252 + case lists:last(Params) of
253 + {mfa, _} ->
254 + %% mfa must be the last param
255 + [];
256 + _ ->
257 + case missing_params(CmdInfo, Params) of
258 + [] ->
259 + [];
260 + _ ->
261 + {<<", ">>, []}
262 + end
263 +
264 + end;
265 + {error, Reason} ->
266 + xprof_core_lib:err(Reason)
267 + end.
268 +
19 269 expand_match_spec(Query) ->
20 270 Funs = xprof_core_vm_info:get_available_funs(Query),
21 271 _FilteredFuns = [{prefix_tail(Query, Fun), Fun} || Fun <- Funs].
22 272
273 + missing_params(CmdInfo, Params) ->
274 + CmdCb = CmdInfo#cmd.cb_mod,
275 + AllParams = CmdCb:mandatory_params() ++ CmdCb:optional_params(),
276 + _MissingParams = [P || P <- AllParams,
277 + not lists:keymember(P, 1, Params)].
278 +
279 + filter_cmds(Prefix, ModeCb) ->
280 + [{<<Rest/binary, " ">>, CmdBin, Cmd#cmd.desc}
281 + || Cmd <- cmds(),
282 + begin
283 + CmdBin = ModeCb:fmt_cmd(Cmd#cmd.name),
284 + Rest = xprof_core_lib:prefix_rest(Prefix, CmdBin),
285 + Rest =/= false
286 + end].
287 +
288 + filter_params(KeyPrefix, MissingParams, ModeCb) ->
289 + [{Rest, ModeCb:fmt_param(P)}
290 + || P <- MissingParams,
291 + begin
292 + PBin = ModeCb:fmt_param_and_delim(P),
293 + Rest = xprof_core_lib:prefix_rest(KeyPrefix, PBin),
294 + Rest =/= false
295 + end].
296 +
297 +
298 + get_cmd_info_or_fail(Cmd) ->
299 + case lists:keyfind(Cmd, #cmd.name, cmds()) of
300 + false ->
301 + xprof_core_lib:err("unknown command: ~p", [Cmd]);
302 + CmdInfo ->
303 + CmdInfo
304 + end.
305 +
306 + cmds() ->
307 + [#cmd{name = funlatency,
308 + cb_mod = xprof_core_cmd_funlatency,
309 + desc = <<"Measure latency of function calls">>},
310 + #cmd{name = argdist,
311 + cb_mod = xprof_core_cmd_argdist,
312 + desc = <<"Distribution of argument values">>}
313 + ].
314 +
23 315 prefix_tail(Prefix, Bin) ->
24 316 case xprof_core_lib:prefix_rest(Prefix, Bin) of
25 317 false ->
  @@ -28,6 +320,8 @@ prefix_tail(Prefix, Bin) ->
28 320 Res
29 321 end.
30 322
323 + maybe_add_common_prefix(AlreadyAdded = {_CommonPrefix, _Matches}) ->
324 + AlreadyAdded;
31 325 maybe_add_common_prefix(Matches) when is_list(Matches) ->
32 326 CommonPrefix = common_match_prefix(Matches),
33 327 {CommonPrefix, Matches}.
  @@ -0,0 +1,104 @@
1 + -module(xprof_core_cmd_argdist).
2 +
3 + -behaviour(xprof_core_cmd).
4 +
5 + -export([mandatory_params/0,
6 + optional_params/0,
7 + param_from_ast/2,
8 + param_to_internal/2,
9 + format_error/1,
10 +
11 + get_cmd_id/1,
12 +
13 + %% tracer callbacks
14 + init/2,
15 + handle_event/3,
16 + take_snapshot/1
17 + ]).
18 +
19 + mandatory_params() ->
20 + [mfa].
21 +
22 + optional_params() ->
23 + [enum].
24 +
25 + param_from_ast(mfa, MfaStr) when is_list(MfaStr) ->
26 + {ok, MfaStr};
27 + param_from_ast(mfa, _WrongValue) ->
28 + {error, wrong_value};
29 + param_from_ast(enum, MaxEnumAst) ->
30 + xprof_core_query:param_to_term(MaxEnumAst);
31 + param_from_ast(_, _) ->
32 + {error, unknown_param}.
33 +
34 + param_to_internal(mfa, Value) ->
35 + case xprof_core_ms:fun2ms(Value) of
36 + {ok, {MFAId, {_MSOff, MSOn}}} ->
37 + {ok, {MFAId, {MSOn, MSOn}}};
38 + Error ->
39 + Error
40 + end;
41 + param_to_internal(enum, MaxEnum) ->
42 + if is_integer(MaxEnum) andalso MaxEnum >= 2 ->
43 + {ok, MaxEnum};
44 + true ->
45 + {error, wrong_value}
46 + end;
47 + param_to_internal(_, _) ->
48 + {error, unknown_param}.
49 +
50 + format_error(Str) when is_list(Str) ->
51 + %% already formatted error from `fun2ms'
52 + Str.
53 +
54 + get_cmd_id(Options) ->
55 + MFASpec = proplists:get_value(mfa, Options),
56 + MFAId = xprof_core_lib:mfaspec2id(MFASpec),
57 + MFAId.
58 +
59 + %% tracer
60 +
61 + -record(state, {freq_count = dict:new(),
62 + all_keys = [],
63 + max_enum}).
64 +
65 + init(Options, _MFASpec) ->
66 + MaxEnum = proplists:get_value(enum, Options, 10),
67 + {ok, #state{max_enum = MaxEnum}}.
68 +
69 + handle_event({trace_ts, _Pid, call, _MFA, Args, _StartTime}, _,
70 + State = #state{freq_count = FreqCount,
71 + all_keys = AllKeys,
72 + max_enum = MaxEnum}) ->
73 + {Key, NewAllKeys} =
74 + case lists:member(Args, AllKeys) of
75 + true ->
76 + {Args, AllKeys};
77 + _ ->
78 + case AllKeys of
79 + ['other'|_] ->
80 + {'other', AllKeys};
81 + _ ->
82 + case length(AllKeys) < MaxEnum of
83 + true ->
84 + {Args, [Args|AllKeys]};
85 + _ ->
86 + {'other', ['other'|AllKeys]}
87 + end
88 + end
89 + end,
90 + {ok, State#state{
91 + freq_count = dict:update_counter(Key, 1, FreqCount),
92 + all_keys = NewAllKeys}};
93 + handle_event(_, _, _) ->
94 + ok.
95 +
96 +
97 + take_snapshot(State = #state{freq_count = FreqCount,
98 + all_keys = AllKeys}) ->
99 + IntervalList = [case dict:find(Key, FreqCount) of
100 + {ok, Count} -> {Key, Count};
101 + error -> {Key, 0}
102 + end
103 + || Key <- lists:reverse(AllKeys)],
104 + {IntervalList, State#state{freq_count = dict:new()}}.
Loading more files…