You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

209 lines
7.9 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. -module(ibrowse_load_test).
  2. %%-compile(export_all).
  3. -export([
  4. random_seed/0,
  5. start/3,
  6. query_state/0,
  7. shutdown/0,
  8. start_1/3,
  9. calculate_timings/0,
  10. get_mmv/2,
  11. spawn_workers/2,
  12. spawn_workers/4,
  13. wait_for_workers/1,
  14. worker_loop/2,
  15. update_unknown_counter/2
  16. ]).
  17. -ifdef(new_rand).
  18. -define(RAND, rand).
  19. random_seed() ->
  20. ok.
  21. -else.
  22. -define(RAND, random).
  23. random_seed() ->
  24. random:seed(os:timestamp()).
  25. -endif.
  26. -define(ibrowse_load_test_counters, ibrowse_load_test_counters).
  27. start(Num_workers, Num_requests, Max_sess) ->
  28. proc_lib:spawn(fun() ->
  29. start_1(Num_workers, Num_requests, Max_sess)
  30. end).
  31. query_state() ->
  32. ibrowse_load_test ! query_state.
  33. shutdown() ->
  34. ibrowse_load_test ! shutdown.
  35. start_1(Num_workers, Num_requests, Max_sess) ->
  36. register(ibrowse_load_test, self()),
  37. application:start(ibrowse),
  38. application:set_env(ibrowse, inactivity_timeout, 5000),
  39. Ulimit = os:cmd("ulimit -n"),
  40. case catch list_to_integer(string:strip(Ulimit, right, $\n)) of
  41. X when is_integer(X), X > 3000 ->
  42. ok;
  43. X ->
  44. io:format("Load test not starting. {insufficient_value_for_ulimit, ~p}~n", [X]),
  45. exit({insufficient_value_for_ulimit, X})
  46. end,
  47. ets:new(?ibrowse_load_test_counters, [named_table, public]),
  48. ets:new(ibrowse_load_timings, [named_table, public]),
  49. try
  50. ets:insert(?ibrowse_load_test_counters, [{success, 0},
  51. {failed, 0},
  52. {timeout, 0},
  53. {retry_later, 0},
  54. {one_request_only, 0}
  55. ]),
  56. ibrowse:set_max_sessions("localhost", 8081, Max_sess),
  57. Start_time = os:timestamp(),
  58. Workers = spawn_workers(Num_workers, Num_requests),
  59. erlang:send_after(1000, self(), print_diagnostics),
  60. ok = wait_for_workers(Workers),
  61. End_time = os:timestamp(),
  62. Time_in_secs = trunc(round(timer:now_diff(End_time, Start_time) / 1000000)),
  63. Req_count = Num_workers * Num_requests,
  64. [{_, Success_count}] = ets:lookup(?ibrowse_load_test_counters, success),
  65. case Success_count == Req_count of
  66. true ->
  67. io:format("Test success. All requests succeeded~n", []);
  68. false when Success_count > 0 ->
  69. io:format("Test failed. Some successes~n", []);
  70. false ->
  71. io:format("Test failed. ALL requests FAILED~n", [])
  72. end,
  73. case Time_in_secs > 0 of
  74. true ->
  75. io:format("Reqs/sec achieved : ~p~n", [trunc(round(Success_count / Time_in_secs))]);
  76. false ->
  77. ok
  78. end,
  79. io:format("Load test results:~n~p~n", [ets:tab2list(?ibrowse_load_test_counters)]),
  80. io:format("Timings: ~p~n", [calculate_timings()])
  81. catch Err ->
  82. io:format("Err: ~p~n", [Err])
  83. after
  84. ets:delete(?ibrowse_load_test_counters),
  85. ets:delete(ibrowse_load_timings),
  86. unregister(ibrowse_load_test)
  87. end.
  88. calculate_timings() ->
  89. {Max, Min, Mean} = get_mmv(ets:first(ibrowse_load_timings), {0, 9999999, 0}),
  90. Variance = trunc(round(ets:foldl(fun({_, X}, X_acc) ->
  91. (X - Mean)*(X-Mean) + X_acc
  92. end, 0, ibrowse_load_timings) / ets:info(ibrowse_load_timings, size))),
  93. Std_dev = trunc(round(math:sqrt(Variance))),
  94. {ok, [{max, Max},
  95. {min, Min},
  96. {mean, Mean},
  97. {variance, Variance},
  98. {standard_deviation, Std_dev}]}.
  99. get_mmv('$end_of_table', {Max, Min, Total}) ->
  100. Mean = trunc(round(Total / ets:info(ibrowse_load_timings, size))),
  101. {Max, Min, Mean};
  102. get_mmv(Key, {Max, Min, Total}) ->
  103. [{_, V}] = ets:lookup(ibrowse_load_timings, Key),
  104. get_mmv(ets:next(ibrowse_load_timings, Key), {max(Max, V), min(Min, V), Total + V}).
  105. spawn_workers(Num_w, Num_r) ->
  106. spawn_workers(Num_w, Num_r, self(), []).
  107. spawn_workers(0, _Num_requests, _Parent, Acc) ->
  108. lists:reverse(Acc);
  109. spawn_workers(Num_workers, Num_requests, Parent, Acc) ->
  110. Pid_ref = spawn_monitor(fun() ->
  111. random_seed(),
  112. case catch worker_loop(Parent, Num_requests) of
  113. {'EXIT', Rsn} ->
  114. io:format("Worker crashed with reason: ~p~n", [Rsn]);
  115. _ ->
  116. ok
  117. end
  118. end),
  119. spawn_workers(Num_workers - 1, Num_requests, Parent, [Pid_ref | Acc]).
  120. wait_for_workers([]) ->
  121. ok;
  122. wait_for_workers([{Pid, Pid_ref} | T] = Pids) ->
  123. receive
  124. {done, Pid} ->
  125. wait_for_workers(T);
  126. {done, Some_pid} ->
  127. wait_for_workers([{Pid, Pid_ref} | lists:keydelete(Some_pid, 1, T)]);
  128. print_diagnostics ->
  129. io:format("~1000.p~n", [ibrowse:get_metrics()]),
  130. erlang:send_after(1000, self(), print_diagnostics),
  131. wait_for_workers(Pids);
  132. query_state ->
  133. io:format("Waiting for ~p~n", [Pids]),
  134. wait_for_workers(Pids);
  135. shutdown ->
  136. io:format("Shutting down on command. Still waiting for ~p workers~n", [length(Pids)]);
  137. {'DOWN', _, process, _, normal} ->
  138. wait_for_workers(Pids);
  139. {'DOWN', _, process, Down_pid, Rsn} ->
  140. io:format("Worker ~p died. Reason: ~p~n", [Down_pid, Rsn]),
  141. wait_for_workers(lists:keydelete(Down_pid, 1, Pids));
  142. X ->
  143. io:format("Recvd unknown msg: ~p~n", [X]),
  144. wait_for_workers(Pids)
  145. end.
  146. worker_loop(Parent, 0) ->
  147. Parent ! {done, self()};
  148. worker_loop(Parent, N) ->
  149. Delay = ?RAND:uniform(100),
  150. Url = case Delay rem 10 of
  151. %% Change 10 to some number between 0-9 depending on how
  152. %% much chaos you want to introduce into the server
  153. %% side. The higher the number, the more often the
  154. %% server will close a connection after serving the
  155. %% first request, thereby forcing the client to
  156. %% retry. Any number of 10 or higher will disable this
  157. %% chaos mechanism
  158. 10 ->
  159. ets:update_counter(?ibrowse_load_test_counters, one_request_only, 1),
  160. "http://localhost:8081/ibrowse_handle_one_request_only";
  161. _ ->
  162. "http://localhost:8081/blah"
  163. end,
  164. Start_time = os:timestamp(),
  165. Res = ibrowse:send_req(Url, [], get),
  166. End_time = os:timestamp(),
  167. Time_taken = trunc(round(timer:now_diff(End_time, Start_time) / 1000)),
  168. ets:insert(ibrowse_load_timings, {os:timestamp(), Time_taken}),
  169. case Res of
  170. {ok, "200", _, _} ->
  171. ets:update_counter(?ibrowse_load_test_counters, success, 1);
  172. {error, req_timedout} ->
  173. ets:update_counter(?ibrowse_load_test_counters, timeout, 1);
  174. {error, retry_later} ->
  175. ets:update_counter(?ibrowse_load_test_counters, retry_later, 1);
  176. {error, Reason} ->
  177. update_unknown_counter(Reason, 1);
  178. _ ->
  179. io:format("~p -- Res: ~p~n", [self(), Res]),
  180. ets:update_counter(?ibrowse_load_test_counters, failed, 1)
  181. end,
  182. timer:sleep(Delay),
  183. worker_loop(Parent, N - 1).
  184. update_unknown_counter(Counter, Inc_val) ->
  185. case catch ets:update_counter(?ibrowse_load_test_counters, Counter, Inc_val) of
  186. {'EXIT', _} ->
  187. ets:insert_new(?ibrowse_load_test_counters, {Counter, 0}),
  188. update_unknown_counter(Counter, Inc_val);
  189. _ ->
  190. ok
  191. end.