bench_take_return.erl 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262
  1. -module(bench_take_return).
  2. -export([
  3. size_5_take_return_one/1,
  4. bench_size_5_take_return_one/2,
  5. size_1000_take_return_one/1,
  6. bench_size_1000_take_return_one/2,
  7. size_1_empty_take_no_members/1,
  8. bench_size_1_empty_take_no_members/2,
  9. size_5_take_return_all/1,
  10. bench_size_5_take_return_all/2,
  11. size_500_take_return_all/1,
  12. bench_size_500_take_return_all/2,
  13. size_500_clients_50_take_return_all/1,
  14. bench_size_500_clients_50_take_return_all/2,
  15. size_0_max_500_take_return_all/1,
  16. bench_size_0_max_500_take_return_all/2,
  17. size_0_max_500_clients_50_take_return_all/1,
  18. bench_size_0_max_500_clients_50_take_return_all/2
  19. ]).
  20. %% @doc Pool of fixed size 5 - try to take just one member and instantly return
  21. size_5_take_return_one(init) ->
  22. % init is called only once in the same process where benchmark will be running
  23. % at the very beginning of benchmark.
  24. % Value returned from this callback will be passed to server({input, State}),
  25. % bench_server(_, State) and server({stop, State})
  26. start_fixed(?FUNCTION_NAME, 5),
  27. ?FUNCTION_NAME;
  28. size_5_take_return_one({input, _Server}) ->
  29. % This callback is called after `init` to generate benchmark input data.
  30. % Returned value will be passed to bench_server(Input, _)
  31. [];
  32. size_5_take_return_one({stop, PoolName}) ->
  33. % Called only once at the very end of benchmark
  34. stop(PoolName).
  35. bench_size_5_take_return_one(_Input, PoolName) ->
  36. Member = pooler:take_member(PoolName),
  37. true = is_pid(Member),
  38. pooler:return_member(PoolName, Member).
  39. %% @doc Pool of fixed size 1000 - try to take just one member and instantly return
  40. size_1000_take_return_one(init) ->
  41. start_fixed(?FUNCTION_NAME, 1000),
  42. ?FUNCTION_NAME;
  43. size_1000_take_return_one({input, _Server}) ->
  44. % This callback is called after `init` to generate benchmark input data.
  45. % Returned value will be passed to bench_server(Input, _)
  46. [];
  47. size_1000_take_return_one({stop, PoolName}) ->
  48. % Called only once at the very end of benchmark
  49. stop(PoolName).
  50. bench_size_1000_take_return_one(_Input, PoolName) ->
  51. Member = pooler:take_member(PoolName),
  52. true = is_pid(Member),
  53. pooler:return_member(PoolName, Member).
  54. %% @doc Fully satrurated pool of fixed size 1 - try to take just one member, get `error_no_members'
  55. size_1_empty_take_no_members(init) ->
  56. start_fixed(?FUNCTION_NAME, 1),
  57. Worker = pooler:take_member(?FUNCTION_NAME),
  58. {?FUNCTION_NAME, Worker};
  59. size_1_empty_take_no_members({input, _}) ->
  60. [];
  61. size_1_empty_take_no_members({stop, {PoolName, Worker}}) ->
  62. pooler:return_member(PoolName, Worker),
  63. stop(PoolName).
  64. bench_size_1_empty_take_no_members(_Input, {PoolName, _}) ->
  65. error_no_members = pooler:take_member(PoolName).
  66. %% @doc Pool of fixed size 5 - take all members sequentially and instantly return them
  67. size_5_take_return_all(init) ->
  68. start_fixed(?FUNCTION_NAME, 5),
  69. {?FUNCTION_NAME, 5};
  70. size_5_take_return_all({input, {_Pool, _Size}}) ->
  71. [];
  72. size_5_take_return_all({stop, {PoolName, _}}) ->
  73. stop(PoolName).
  74. bench_size_5_take_return_all(_Input, {PoolName, Size}) ->
  75. Members = take_n(PoolName, Size),
  76. [pooler:return_member(PoolName, Member) || Member <- Members].
  77. %% @doc Pool of fixed size 500 - take all members and instantly return them
  78. size_500_take_return_all(init) ->
  79. start_fixed(?FUNCTION_NAME, 500),
  80. {?FUNCTION_NAME, 500};
  81. size_500_take_return_all({input, {_Pool, Size}}) ->
  82. lists:seq(1, Size);
  83. size_500_take_return_all({stop, {PoolName, _}}) ->
  84. stop(PoolName).
  85. bench_size_500_take_return_all(_Input, {PoolName, Size}) ->
  86. Members = take_n(PoolName, Size),
  87. [pooler:return_member(PoolName, Member) || Member <- Members].
  88. %% @doc Pool of fixed size 500 - take all members from 50 workers and let workers instantly return them
  89. size_500_clients_50_take_return_all(init) ->
  90. PoolSize = 500,
  91. NumClients = 50,
  92. PerClient = PoolSize div NumClients,
  93. start_fixed(?FUNCTION_NAME, 500),
  94. Clients = [
  95. erlang:spawn_link(
  96. fun() ->
  97. client(?FUNCTION_NAME, 0, PerClient)
  98. end
  99. )
  100. || _ <- lists:seq(1, NumClients)
  101. ],
  102. {?FUNCTION_NAME, 500, Clients};
  103. size_500_clients_50_take_return_all({input, {_Pool, _Size, _Clients}}) ->
  104. [];
  105. size_500_clients_50_take_return_all({stop, {PoolName, _Size, Clients}}) ->
  106. [
  107. begin
  108. unlink(Pid),
  109. exit(Pid, shutdown)
  110. end
  111. || Pid <- Clients
  112. ],
  113. stop(PoolName).
  114. bench_size_500_clients_50_take_return_all(_Input, {_PoolName, _Size, Clients}) ->
  115. Ref = erlang:make_ref(),
  116. Self = self(),
  117. lists:foreach(fun(C) -> C ! {do, Self, Ref} end, Clients),
  118. lists:foreach(
  119. fun(_C) ->
  120. receive
  121. {done, RecRef} ->
  122. RecRef = Ref
  123. after 5000 ->
  124. error(timeout)
  125. end
  126. end,
  127. Clients
  128. ).
  129. %% @doc Artificial example: pool with init_count=0, max_count=500 that is culled to 0 on each iteration.
  130. %% Try to take 500 workers sequentially and instantly return them (and trigger culling).
  131. %% This benchmark, while is quite unrealistic (why have pool that instantly kills workers), but it
  132. %% helps to test worker spawn/kill routines.
  133. size_0_max_500_take_return_all(init) ->
  134. Size = 500,
  135. start([
  136. {name, ?FUNCTION_NAME},
  137. {init_count, 0},
  138. {max_count, Size},
  139. {max_age, {0, sec}}
  140. ]),
  141. {?FUNCTION_NAME, Size};
  142. size_0_max_500_take_return_all({input, {_Pool, _Size}}) ->
  143. [];
  144. size_0_max_500_take_return_all({stop, {PoolName, _}}) ->
  145. stop(PoolName).
  146. bench_size_0_max_500_take_return_all(_Input, {PoolName, Size}) ->
  147. Members = take_n(PoolName, 500, Size),
  148. [pooler:return_member(PoolName, Member) || Member <- Members],
  149. whereis(PoolName) ! cull_pool,
  150. Utilization = pooler:pool_utilization(PoolName),
  151. 0 = proplists:get_value(free_count, Utilization),
  152. 0 = proplists:get_value(in_use_count, Utilization).
  153. %% @doc Pool with init_count=0, max_count=500 that is culled to 0 on each iteration, taken by 50 workers
  154. %%
  155. %% Artificial example (because pool is instantly culled), but with parallel consumers we can test concurrent
  156. %% pool worker starter
  157. size_0_max_500_clients_50_take_return_all(init) ->
  158. PoolSize = 500,
  159. NumClients = 50,
  160. PerClient = PoolSize div NumClients,
  161. start([
  162. {name, ?FUNCTION_NAME},
  163. {init_count, 0},
  164. {max_count, PoolSize},
  165. {max_age, {0, sec}},
  166. {queue_max, 500}
  167. ]),
  168. Clients = [
  169. erlang:spawn_link(
  170. fun() ->
  171. client(?FUNCTION_NAME, 500, PerClient)
  172. end
  173. )
  174. || _ <- lists:seq(1, NumClients)
  175. ],
  176. {?FUNCTION_NAME, 500, Clients};
  177. size_0_max_500_clients_50_take_return_all({input, {_Pool, _Size, _Clients}}) ->
  178. [];
  179. size_0_max_500_clients_50_take_return_all({stop, {PoolName, _Size, Clients}}) ->
  180. [
  181. begin
  182. unlink(Pid),
  183. exit(Pid, shutdown)
  184. end
  185. || Pid <- Clients
  186. ],
  187. stop(PoolName).
  188. bench_size_0_max_500_clients_50_take_return_all(_Input, {PoolName, _Size, Clients}) ->
  189. Ref = erlang:make_ref(),
  190. Self = self(),
  191. lists:foreach(fun(C) -> C ! {do, Self, Ref} end, Clients),
  192. lists:foreach(
  193. fun(_C) ->
  194. receive
  195. {done, RecRef} ->
  196. RecRef = Ref
  197. after 5000 ->
  198. error(timeout)
  199. end
  200. end,
  201. Clients
  202. ),
  203. whereis(PoolName) ! cull_pool,
  204. Utilization = pooler:pool_utilization(PoolName),
  205. 0 = proplists:get_value(free_count, Utilization),
  206. 0 = proplists:get_value(in_use_count, Utilization).
  207. %% Internal
  208. start_fixed(Name, Size) ->
  209. Conf = [
  210. {name, Name},
  211. {init_count, Size},
  212. {max_count, Size}
  213. ],
  214. start(Conf).
  215. start(Conf0) ->
  216. Conf = [{start_mfa, {pooled_gs, start_link, [{"test"}]}} | Conf0],
  217. logger:set_handler_config(default, filters, []),
  218. {ok, _} = application:ensure_all_started(pooler),
  219. {ok, _} = pooler:new_pool(Conf).
  220. stop(Name) ->
  221. pooler:rm_pool(Name),
  222. application:stop(pooler).
  223. take_n(PoolName, N) ->
  224. take_n(PoolName, 0, N).
  225. take_n(_, _, 0) ->
  226. [];
  227. take_n(PoolName, Timeout, N) ->
  228. Member = pooler:take_member(PoolName, Timeout),
  229. true = is_pid(Member),
  230. [Member | take_n(PoolName, Timeout, N - 1)].
  231. client(Pool, Timeout, N) ->
  232. receive
  233. {do, From, Ref} ->
  234. Taken = take_n(Pool, Timeout, N),
  235. [pooler:return_member(Pool, Member) || Member <- Taken],
  236. From ! {done, Ref}
  237. end,
  238. client(Pool, Timeout, N).