Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

331 lignes
11 KiB

il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
il y a 3 ans
  1. %% Copyright (c) 2017, Loïc Hoguin <essen@ninenines.eu>
  2. %%
  3. %% Permission to use, copy, modify, and/or distribute this software for any
  4. %% purpose with or without fee is hereby granted, provided that the above
  5. %% copyright notice and this permission notice appear in all copies.
  6. %%
  7. %% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8. %% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9. %% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  10. %% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  11. %% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  12. %% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  13. %% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  14. -module(cowboy_metrics_h).
  15. -behavior(cowboy_stream).
  16. -export([init/3]).
  17. -export([data/4]).
  18. -export([info/3]).
  19. -export([terminate/3]).
  20. -export([early_error/5]).
  21. -type proc_metrics() :: #{pid() => #{
  22. %% Time at which the process spawned.
  23. spawn := integer(),
  24. %% Time at which the process exited.
  25. exit => integer(),
  26. %% Reason for the process exit.
  27. reason => any()
  28. }}.
  29. -type informational_metrics() :: #{
  30. %% Informational response status.
  31. status := cowboy:http_status(),
  32. %% Headers sent with the informational response.
  33. headers := cowboy:http_headers(),
  34. %% Time when the informational response was sent.
  35. time := integer()
  36. }.
  37. -type metrics() :: #{
  38. %% The identifier for this listener.
  39. ref := ranch:ref(),
  40. %% The pid for this connection.
  41. pid := pid(),
  42. %% The streamid also indicates the total number of requests on
  43. %% this connection (StreamID div 2 + 1).
  44. streamid := cowboy_stream:streamid(),
  45. %% The terminate reason is always useful.
  46. reason := cowboy_stream:reason(),
  47. %% A filtered Req object or a partial Req object
  48. %% depending on how far the request got to.
  49. req => cowboy_req:req(),
  50. partial_req => cowboy_stream:partial_req(),
  51. %% Response status.
  52. resp_status := cowboy:http_status(),
  53. %% Filtered response headers.
  54. resp_headers := cowboy:http_headers(),
  55. %% Start/end of the processing of the request.
  56. %%
  57. %% This represents the time from this stream handler's init
  58. %% to terminate.
  59. req_start => integer(),
  60. req_end => integer(),
  61. %% Start/end of the receiving of the request body.
  62. %% Begins when the first packet has been received.
  63. req_body_start => integer(),
  64. req_body_end => integer(),
  65. %% Start/end of the sending of the response.
  66. %% Begins when we send the headers and ends on the final
  67. %% packet of the response body. If everything is sent at
  68. %% once these values are identical.
  69. resp_start => integer(),
  70. resp_end => integer(),
  71. %% For early errors all we get is the time we received it.
  72. early_error_time => integer(),
  73. %% Start/end of spawned processes. This is where most of
  74. %% the user code lies, excluding stream handlers. On a
  75. %% default Cowboy configuration there should be only one
  76. %% process: the request process.
  77. procs => proc_metrics(),
  78. %% Informational responses sent before the final response.
  79. informational => [informational_metrics()],
  80. %% Length of the request and response bodies. This does
  81. %% not include the framing.
  82. req_body_length => non_neg_integer(),
  83. resp_body_length => non_neg_integer(),
  84. %% Additional metadata set by the user.
  85. user_data => map()
  86. }.
  87. -export_type([metrics/0]).
  88. -type metrics_callback() :: fun((metrics()) -> any()).
  89. -export_type([metrics_callback/0]).
  90. -record(state, {
  91. next :: any(),
  92. callback :: fun((metrics()) -> any()),
  93. resp_headers_filter :: undefined | fun((cowboy:http_headers()) -> cowboy:http_headers()),
  94. req :: map(),
  95. resp_status :: undefined | cowboy:http_status(),
  96. resp_headers :: undefined | cowboy:http_headers(),
  97. ref :: ranch:ref(),
  98. req_start :: integer(),
  99. req_end :: undefined | integer(),
  100. req_body_start :: undefined | integer(),
  101. req_body_end :: undefined | integer(),
  102. resp_start :: undefined | integer(),
  103. resp_end :: undefined | integer(),
  104. procs = #{} :: proc_metrics(),
  105. informational = [] :: [informational_metrics()],
  106. req_body_length = 0 :: non_neg_integer(),
  107. resp_body_length = 0 :: non_neg_integer(),
  108. user_data = #{} :: map()
  109. }).
  110. -spec init(cowboy_stream:streamid(), cowboy_req:req(), cowboy:opts())
  111. -> {[{spawn, pid(), timeout()}], #state{}}.
  112. init(StreamID, Req = #{ref := Ref}, Opts = #{metrics_callback := Fun}) ->
  113. ReqStart = erlang:monotonic_time(),
  114. {Commands, Next} = cowboy_stream:init(StreamID, Req, Opts),
  115. FilteredReq = case maps:get(metrics_req_filter, Opts, undefined) of
  116. undefined -> Req;
  117. ReqFilter -> ReqFilter(Req)
  118. end,
  119. RespHeadersFilter = maps:get(metrics_resp_headers_filter, Opts, undefined),
  120. {Commands, fold(Commands, #state{
  121. next = Next,
  122. callback = Fun,
  123. resp_headers_filter = RespHeadersFilter,
  124. req = FilteredReq,
  125. ref = Ref,
  126. req_start = ReqStart
  127. })}.
  128. -spec data(cowboy_stream:streamid(), cowboy_stream:fin(), cowboy_req:resp_body(), State)
  129. -> {cowboy_stream:commands(), State} when State :: #state{}.
  130. data(StreamID, IsFin = fin, Data, State = #state{req_body_start = undefined}) ->
  131. ReqBody = erlang:monotonic_time(),
  132. do_data(StreamID, IsFin, Data, State#state{
  133. req_body_start = ReqBody,
  134. req_body_end = ReqBody,
  135. req_body_length = byte_size(Data)
  136. });
  137. data(StreamID, IsFin = fin, Data, State = #state{req_body_length = ReqBodyLen}) ->
  138. ReqBodyEnd = erlang:monotonic_time(),
  139. do_data(StreamID, IsFin, Data, State#state{
  140. req_body_end = ReqBodyEnd,
  141. req_body_length = ReqBodyLen + byte_size(Data)
  142. });
  143. data(StreamID, IsFin, Data, State = #state{req_body_start = undefined}) ->
  144. ReqBodyStart = erlang:monotonic_time(),
  145. do_data(StreamID, IsFin, Data, State#state{
  146. req_body_start = ReqBodyStart,
  147. req_body_length = byte_size(Data)
  148. });
  149. data(StreamID, IsFin, Data, State = #state{req_body_length = ReqBodyLen}) ->
  150. do_data(StreamID, IsFin, Data, State#state{
  151. req_body_length = ReqBodyLen + byte_size(Data)
  152. }).
  153. do_data(StreamID, IsFin, Data, State0 = #state{next = Next0}) ->
  154. {Commands, Next} = cowboy_stream:data(StreamID, IsFin, Data, Next0),
  155. {Commands, fold(Commands, State0#state{next = Next})}.
  156. -spec info(cowboy_stream:streamid(), any(), State)
  157. -> {cowboy_stream:commands(), State} when State :: #state{}.
  158. info(StreamID, Info = {'EXIT', Pid, Reason}, State0 = #state{procs = Procs}) ->
  159. ProcEnd = erlang:monotonic_time(),
  160. P = maps:get(Pid, Procs),
  161. State = State0#state{procs = Procs#{Pid => P#{
  162. exit => ProcEnd,
  163. reason => Reason
  164. }}},
  165. do_info(StreamID, Info, State);
  166. info(StreamID, Info, State) ->
  167. do_info(StreamID, Info, State).
  168. do_info(StreamID, Info, State0 = #state{next = Next0}) ->
  169. {Commands, Next} = cowboy_stream:info(StreamID, Info, Next0),
  170. {Commands, fold(Commands, State0#state{next = Next})}.
  171. fold([], State) ->
  172. State;
  173. fold([{spawn, Pid, _} | Tail], State0 = #state{procs = Procs}) ->
  174. ProcStart = erlang:monotonic_time(),
  175. State = State0#state{procs = Procs#{Pid => #{spawn => ProcStart}}},
  176. fold(Tail, State);
  177. fold([{inform, Status, Headers} | Tail],
  178. State = #state{informational = Infos}) ->
  179. Time = erlang:monotonic_time(),
  180. fold(Tail, State#state{informational = [#{
  181. status => Status,
  182. headers => Headers,
  183. time => Time
  184. } | Infos]});
  185. fold([{response, Status, Headers, Body} | Tail],
  186. State = #state{resp_headers_filter = RespHeadersFilter}) ->
  187. Resp = erlang:monotonic_time(),
  188. fold(Tail, State#state{
  189. resp_status = Status,
  190. resp_headers = case RespHeadersFilter of
  191. undefined -> Headers;
  192. _ -> RespHeadersFilter(Headers)
  193. end,
  194. resp_start = Resp,
  195. resp_end = Resp,
  196. resp_body_length = resp_body_length(Body)
  197. });
  198. fold([{error_response, Status, Headers, Body} | Tail],
  199. State = #state{resp_status = RespStatus}) ->
  200. %% The error_response command only results in a response
  201. %% if no response was sent before.
  202. case RespStatus of
  203. undefined ->
  204. fold([{response, Status, Headers, Body} | Tail], State);
  205. _ ->
  206. fold(Tail, State)
  207. end;
  208. fold([{headers, Status, Headers} | Tail],
  209. State = #state{resp_headers_filter = RespHeadersFilter}) ->
  210. RespStart = erlang:monotonic_time(),
  211. fold(Tail, State#state{
  212. resp_status = Status,
  213. resp_headers = case RespHeadersFilter of
  214. undefined -> Headers;
  215. _ -> RespHeadersFilter(Headers)
  216. end,
  217. resp_start = RespStart
  218. });
  219. %% @todo It might be worthwhile to keep the sendfile information around,
  220. %% especially if these frames ultimately result in a sendfile syscall.
  221. fold([{data, nofin, Data} | Tail], State = #state{resp_body_length = RespBodyLen}) ->
  222. fold(Tail, State#state{
  223. resp_body_length = RespBodyLen + resp_body_length(Data)
  224. });
  225. fold([{data, fin, Data} | Tail], State = #state{resp_body_length = RespBodyLen}) ->
  226. RespEnd = erlang:monotonic_time(),
  227. fold(Tail, State#state{
  228. resp_end = RespEnd,
  229. resp_body_length = RespBodyLen + resp_body_length(Data)
  230. });
  231. fold([{set_options, SetOpts} | Tail], State0 = #state{user_data = OldUserData}) ->
  232. State = case SetOpts of
  233. #{metrics_user_data := NewUserData} ->
  234. State0#state{user_data = maps:merge(OldUserData, NewUserData)};
  235. _ ->
  236. State0
  237. end,
  238. fold(Tail, State);
  239. fold([_ | Tail], State) ->
  240. fold(Tail, State).
  241. -spec terminate(cowboy_stream:streamid(), cowboy_stream:reason(), #state{}) -> any().
  242. terminate(StreamID, Reason, #state{next = Next, callback = Fun,
  243. req = Req, resp_status = RespStatus, resp_headers = RespHeaders, ref = Ref,
  244. req_start = ReqStart, req_body_start = ReqBodyStart,
  245. req_body_end = ReqBodyEnd, resp_start = RespStart, resp_end = RespEnd,
  246. procs = Procs, informational = Infos, user_data = UserData,
  247. req_body_length = ReqBodyLen, resp_body_length = RespBodyLen}) ->
  248. Res = cowboy_stream:terminate(StreamID, Reason, Next),
  249. ReqEnd = erlang:monotonic_time(),
  250. Metrics = #{
  251. ref => Ref,
  252. pid => self(),
  253. streamid => StreamID,
  254. reason => Reason,
  255. req => Req,
  256. resp_status => RespStatus,
  257. resp_headers => RespHeaders,
  258. req_start => ReqStart,
  259. req_end => ReqEnd,
  260. req_body_start => ReqBodyStart,
  261. req_body_end => ReqBodyEnd,
  262. resp_start => RespStart,
  263. resp_end => RespEnd,
  264. procs => Procs,
  265. informational => lists:reverse(Infos),
  266. req_body_length => ReqBodyLen,
  267. resp_body_length => RespBodyLen,
  268. user_data => UserData
  269. },
  270. Fun(Metrics),
  271. Res.
  272. -spec early_error(cowboy_stream:streamid(), cowboy_stream:reason(),
  273. cowboy_stream:partial_req(), Resp, cowboy:opts()) -> Resp
  274. when Resp :: cowboy_stream:resp_command().
  275. early_error(StreamID, Reason, PartialReq = #{ref := Ref}, Resp0, Opts = #{metrics_callback := Fun}) ->
  276. Time = erlang:monotonic_time(),
  277. Resp = {response, RespStatus, RespHeaders, RespBody}
  278. = cowboy_stream:early_error(StreamID, Reason, PartialReq, Resp0, Opts),
  279. %% As far as metrics go we are limited in what we can provide
  280. %% in this case.
  281. Metrics = #{
  282. ref => Ref,
  283. pid => self(),
  284. streamid => StreamID,
  285. reason => Reason,
  286. partial_req => PartialReq,
  287. resp_status => RespStatus,
  288. resp_headers => RespHeaders,
  289. early_error_time => Time,
  290. resp_body_length => resp_body_length(RespBody)
  291. },
  292. Fun(Metrics),
  293. Resp.
  294. resp_body_length({sendfile, _, Len, _}) ->
  295. Len;
  296. resp_body_length(Data) ->
  297. iolist_size(Data).