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.

181 lines
7.4 KiB

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