Current section
7 Versions
Jump to
Current section
7 Versions
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…