%%% @doc HTTP server independent part of the REST API implementation %%% %%% == Autocomplete == %%% %%% === /api/funs === %%% %%% Returns: %%% %%% %%% Get expansion suggestions for the given possibly incomplete query. %%% Used for autocomplete suggestions on the GUI. %%% "commonPrefix" can be used to append to the query if no suggestion is %%% selected. %%% "prefix" can be appended to the query if the given suggestion is selected. %%% %%% === /api/get_callees === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Get list of functions (MFAs) that are called by the specified function %%% (MFA) based on static analysis (ie. not based on runtime information). %%% %%% %%% == Monitoring functions == %%% %%% === /api/mon_start === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Start monitoring based on the specified query string. %%% %%% === /api/mon_stop === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Stop monitoring the specified function (MFA). %%% %%% === /api/mon_get_all === %%% %%% Returns: %%% %%% %%% Return list of monitored functions. %%% (The values of "mod", "fun" and "arity" can be used as params to calls to eg %%% "/api/mon_stop" while "queryvalue" can be used to display the original query %%% string). %%% %%% === /api/data === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Return metrics gathered for the given function since the given %%% timestamp. Each item contains a timestamp and the corresponding histogram %%% metrics values. %%% %%% == Global trace status == %%% %%% === /api/trace_set === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Turn on or pause tracing of all processes. %%% %%% === /api/trace_status === %%% %%% Returns: %%% %%% %%% Return current tracing state. %%% (The `initialized' status is basically the same as `paused', additionally %%% meaning that no tracing was started yet since xprof was started) %%% %%% == Long call capturing == %%% %%% === /api/capture === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Start capturing arguments and return values of function calls that lasted %%% longer than the specified time threshold in ms. Stop after `limit' number of %%% captured calls. %%% %%% === /api/capture_stop === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Stop capturing long calls of the given function (MFA). %%% %%% === /api/capture_data === %%% %%% Params: %%% %%% %%% Returns: %%% %%% %%% Return captured arguments and return values. %%% %%% The `Offset' argument is the item index last seen by the caller, only items %%% newer than that will be returned. An offset of 0 will return all data. %%% %%% The returned `HasMore' indicates whether capturing is still ongoing or it has %%% been stopped either manually or by reaching the limit. %%% %%% == Syntax mode == %%% %%% === /api/mode === %%% %%% Returns: %%% %%% %%% Get syntax mode, if not set explicitely in the backend, it will be %%% autodetected. %%% %%% @end -module(xprof_gui_rest). -export([handle_req/2]). -spec handle_req(Path :: binary(), Params :: [{binary(), binary()}]) -> StatusCode | {StatusCode, Body} when StatusCode :: integer(), Body :: binary(). handle_req(What, Params) -> try do_handle_req(What, Params) catch C:E -> lager:error("Error handling REST API request \"~s\" ~p ~p:~p", [What, Params, C, E]), 500 end. do_handle_req(<<"funs">>, Params) -> Query = get_query(Params), {CommonPrefix, Funs} = xprof_core:expand_query(Query), Data = {[{expansion, CommonPrefix}, {matches, [{[{expansion, Exp},{label, Label}]}||{Exp, Label} <- Funs]} ]}, Json = jsone:encode(Data), lager:debug("Returning ~b functions matching phrase \"~s\"", [length(Funs), Query]), {200, Json}; do_handle_req(<<"get_callees">>, Req) -> MFA = get_mfa(Req), Callees = xprof_core:get_called_funs_pp(MFA), Json = jsone:encode(Callees), {200, Json}; do_handle_req(<<"mon_start">>, Params) -> Query = get_query(Params), lager:info("Starting monitoring via web on '~s'~n", [Query]), case xprof_core:monitor_pp(Query) of ok -> 204; {error, already_traced} -> Json = jsone:encode({[{message, <<"The requested function is already monitored">>}]}), {409, Json}; {error, Msg} -> Json = jsone:encode({[{message, unicode:characters_to_binary(Msg)}]}), {400, Json} end; do_handle_req(<<"mon_stop">>, Params) -> MFA = {M, F, A} = get_mfa(Params), lager:info("Stopping monitoring via web on ~w:~w/~w~n",[M, F, A]), xprof_core:demonitor(MFA), 204; do_handle_req(<<"mon_get_all">>, _Params) -> Funs = xprof_core:get_all_monitored(), FunsArr = [{[{<<"mfa">>, [Mod, Fun, Arity]}, {<<"query">>, Query}, {<<"graph_type">>, <<"percentiles">>} ]} || {{Mod, Fun, Arity}, Query} <- Funs], Json = jsone:encode(FunsArr), {200, Json}; do_handle_req(<<"data">>, Params) -> MFA = get_mfa(Params), LastTS = get_int(<<"last_ts">>, Params, 0), case xprof_core:get_data(MFA, LastTS) of {error, not_found} -> 404; Vals -> Json = jsone:encode([{Val} || Val <- Vals]), {200, Json} end; do_handle_req(<<"trace_set">>, Params) -> case proplists:get_value(<<"spec">>, Params) of <<"all">> -> xprof_core:trace(all), 204; <<"pause">> -> xprof_core:trace(pause), 204; Spec -> lager:info("Wrong spec for tracing: ~p",[Spec]), 400 end; do_handle_req(<<"trace_status">>, _Params) -> {_, Status} = xprof_core:get_trace_status(), Json = jsone:encode({[{status, Status}]}), {200, Json}; do_handle_req(<<"capture">>, Params) -> MFA = {M, F, A} = get_mfa(Params), Threshold = get_int(<<"threshold">>, Params), Limit = get_int(<<"limit">>, Params), lager:info("Capture ~b calls to ~w:~w/~w~n exceeding ~b ms", [Limit, M, F, A, Threshold]), case xprof_core:capture(MFA, Threshold, Limit) of {ok, CaptureId} -> Json = jsone:encode({[{capture_id, CaptureId}]}), {200, Json}; {error, not_found} -> 404 end; do_handle_req(<<"capture_stop">>, Params) -> MFA = get_mfa(Params), lager:info("Stopping slow calls capturing for ~p", [MFA]), case xprof_core:capture_stop(MFA) of ok -> 204; {error, not_found} -> 404; {error, not_captured} -> 404 end; do_handle_req(<<"capture_data">>, Params) -> MFA = get_mfa(Params), Offset = get_int(<<"offset">>, Params, 0), case xprof_core:get_captured_data_pp(MFA, Offset) of {error, not_found} -> 404; {ok, {Id, Threshold, Limit, HasMore}, Items} -> Json = jsone:encode({[{capture_id, Id}, {threshold, Threshold}, {limit, Limit}, {items, Items}, {has_more, HasMore}]}), {200, Json} end; do_handle_req(<<"mode">>, _Params) -> Mode = xprof_core:get_mode(), Json = jsone:encode({[{mode, Mode}]}), {200, Json}. %% Helpers -spec get_mfa([{binary(), binary() | true}]) -> xprof_core:mfa_id(). get_mfa(Params) -> {binary_to_atom(proplists:get_value(<<"mod">>, Params), latin1), binary_to_atom(proplists:get_value(<<"fun">>, Params), latin1), case proplists:get_value(<<"arity">>, Params) of <<"_">> -> '_'; Arity -> binary_to_integer(Arity) end}. -spec get_query([{binary(), binary() | true}]) -> binary(). get_query(Params) -> proplists:get_value(<<"query">>, Params, <<"">>). -spec get_int(binary(), [{binary(), binary() | true}]) -> integer(). get_int(Key, Params) -> {_, BinValue} = lists:keyfind(Key, 1, Params), binary_to_integer(BinValue). -spec get_int(binary(), [{binary(), binary() | true}], integer()) -> integer(). get_int(Key, Params, Default) -> case lists:keyfind(Key, 1, Params) of {_, BinValue} -> binary_to_integer(BinValue); _ -> Default end.