Skip to content

Commit a67ae32

Browse files
author
drewkerrigan
committed
adding an arbitrary http driver
1 parent 7a5decc commit a67ae32

File tree

2 files changed

+257
-0
lines changed

2 files changed

+257
-0
lines changed

examples/http.config

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{mode, max}.
2+
3+
{duration, 1}.
4+
5+
{concurrent, 1}.
6+
7+
{driver, basho_bench_driver_http}.
8+
9+
%% Example syntax (mykeygen_seq is not defined)
10+
%% {key_generator, {function, test, mykeygen_seq, [10000, 10, 10, 100]}}.
11+
12+
{key_generator, {int_to_str, {uniform_int, 50000}}}.
13+
14+
{operations, [
15+
{{get, {"localhost", 4567, "/"}}, 1},
16+
{{put, {"localhost", 4567, "/",
17+
"{\"this\":\"is_json_%%V\"}"}}, 1}
18+
]}.

src/basho_bench_driver_http.erl

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
%% -------------------------------------------------------------------
2+
%%
3+
%% basho_bench: Benchmarking Suite
4+
%%
5+
%% Copyright (c) 2009-2013 Basho Techonologies
6+
%%
7+
%% This file is provided to you under the Apache License,
8+
%% Version 2.0 (the "License"); you may not use this file
9+
%% except in compliance with the License. You may obtain
10+
%% a copy of the License at
11+
%%
12+
%% http://www.apache.org/licenses/LICENSE-2.0
13+
%%
14+
%% Unless required by applicable law or agreed to in writing,
15+
%% software distributed under the License is distributed on an
16+
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17+
%% KIND, either express or implied. See the License for the
18+
%% specific language governing permissions and limitations
19+
%% under the License.
20+
%%
21+
%% -------------------------------------------------------------------
22+
-module(basho_bench_driver_http).
23+
24+
-export([new/1,
25+
run/4]).
26+
27+
-include("basho_bench.hrl").
28+
29+
-record(url, {abspath, host, port, username, password, path, protocol, host_type}).
30+
31+
-record(state, {path_params}). % Params to append on the path
32+
33+
34+
%% ====================================================================
35+
%% API
36+
%% ====================================================================
37+
38+
new(Id) ->
39+
40+
?DEBUG("ID: ~p\n", [Id]),
41+
42+
%% Make sure ibrowse is available
43+
case code:which(ibrowse) of
44+
non_existing ->
45+
?FAIL_MSG("~s requires ibrowse to be installed.\n", [?MODULE]);
46+
_ ->
47+
ok
48+
end,
49+
50+
application:start(ibrowse),
51+
52+
Params = basho_bench_config:get(http_params, ""),
53+
Disconnect = basho_bench_config:get(http_disconnect_frequency, infinity),
54+
55+
case Disconnect of
56+
infinity -> ok;
57+
Seconds when is_integer(Seconds) -> ok;
58+
{ops, Ops} when is_integer(Ops) -> ok;
59+
_ -> ?FAIL_MSG("Invalid configuration for http_disconnect_frequency: ~p~n", [Disconnect])
60+
end,
61+
62+
%% Uses pdict to avoid threading state record through lots of functions
63+
erlang:put(disconnect_freq, Disconnect),
64+
65+
{ok, #state {path_params = Params}}.
66+
67+
run({get, {Host, Port, Path}}, KeyGen, _ValueGen, _State) ->
68+
69+
Path1 = re:replace(Path, "%%V", KeyGen(), [global, {return, list}]),
70+
71+
PUrl = #url{host=Host, port=Port, path=Path1},
72+
73+
case do_get(PUrl) of
74+
{not_found, _Url} ->
75+
{ok, 1};
76+
{ok, _Url, _Headers} ->
77+
{ok, 1};
78+
{error, Reason} ->
79+
{error, Reason, 1}
80+
end;
81+
run({put, {Host, Port, Path, Data}}, KeyGen, _ValueGen, _State) ->
82+
Path1 = re:replace(Path, "%%V", KeyGen(), [global, {return, list}]),
83+
84+
PUrl = #url{host=Host, port=Port, path=Path1},
85+
86+
Value = re:replace(Data, "%%V", KeyGen(), [global, {return, list}]),
87+
88+
case do_put(PUrl, [], Value) of
89+
ok ->
90+
{ok, 1};
91+
{error, Reason} ->
92+
{error, Reason, 1}
93+
end.
94+
95+
%% ====================================================================
96+
%% Internal functions
97+
%% ====================================================================
98+
99+
do_get(Url) ->
100+
do_get(Url, []).
101+
102+
do_get(Url, Opts) ->
103+
case send_request(Url, [], get, [], [{response_format, binary}]) of
104+
{ok, "404", _Headers, _Body} ->
105+
{not_found, Url};
106+
{ok, "300", Headers, _Body} ->
107+
{ok, Url, Headers};
108+
{ok, "200", Headers, Body} ->
109+
case proplists:get_bool(body_on_success, Opts) of
110+
true -> {ok, Url, Headers, Body};
111+
false -> {ok, Url, Headers}
112+
end;
113+
{ok, Code, _Headers, _Body} ->
114+
{error, {http_error, Code}};
115+
{error, Reason} ->
116+
{error, Reason}
117+
end.
118+
119+
do_put(Url, Headers, ValueGen) ->
120+
Val = if is_function(ValueGen) ->
121+
ValueGen();
122+
true ->
123+
ValueGen
124+
end,
125+
case send_request(Url, Headers ++ [{'Content-Type', 'application/octet-stream'}],
126+
put, Val, [{response_format, binary}]) of
127+
{ok, "204", _Header, _Body} ->
128+
ok;
129+
{ok, Code, _Header, _Body} ->
130+
{error, {http_error, Code}};
131+
{error, Reason} ->
132+
{error, Reason}
133+
end.
134+
135+
connect(Url) ->
136+
case erlang:get({ibrowse_pid, Url#url.host}) of
137+
undefined ->
138+
{ok, Pid} = ibrowse_http_client:start({Url#url.host, Url#url.port}),
139+
erlang:put({ibrowse_pid, Url#url.host}, Pid),
140+
Pid;
141+
Pid ->
142+
case is_process_alive(Pid) of
143+
true ->
144+
Pid;
145+
false ->
146+
erlang:erase({ibrowse_pid, Url#url.host}),
147+
connect(Url)
148+
end
149+
end.
150+
151+
152+
disconnect(Url) ->
153+
case erlang:get({ibrowse_pid, Url#url.host}) of
154+
undefined ->
155+
ok;
156+
OldPid ->
157+
catch(ibrowse_http_client:stop(OldPid))
158+
end,
159+
erlang:erase({ibrowse_pid, Url#url.host}),
160+
ok.
161+
162+
maybe_disconnect(Url) ->
163+
case erlang:get(disconnect_freq) of
164+
infinity -> ok;
165+
{ops, Count} -> should_disconnect_ops(Count,Url) andalso disconnect(Url);
166+
Seconds -> should_disconnect_secs(Seconds,Url) andalso disconnect(Url)
167+
end.
168+
169+
should_disconnect_ops(Count, Url) ->
170+
Key = {ops_since_disconnect, Url#url.host},
171+
case erlang:get(Key) of
172+
undefined ->
173+
erlang:put(Key, 1),
174+
false;
175+
Count ->
176+
erlang:put(Key, 0),
177+
true;
178+
Incr ->
179+
erlang:put(Key, Incr + 1),
180+
false
181+
end.
182+
183+
should_disconnect_secs(Seconds, Url) ->
184+
Key = {last_disconnect, Url#url.host},
185+
case erlang:get(Key) of
186+
undefined ->
187+
erlang:put(Key, erlang:now()),
188+
false;
189+
Time when is_tuple(Time) andalso size(Time) == 3 ->
190+
Diff = timer:now_diff(erlang:now(), Time),
191+
if
192+
Diff >= Seconds * 1000000 ->
193+
erlang:put(Key, erlang:now()),
194+
true;
195+
true -> false
196+
end
197+
end.
198+
199+
clear_disconnect_freq(Url) ->
200+
case erlang:get(disconnect_freq) of
201+
infinity -> ok;
202+
{ops, _Count} -> erlang:put({ops_since_disconnect, Url#url.host}, 0);
203+
_Seconds -> erlang:put({last_disconnect, Url#url.host}, erlang:now())
204+
end.
205+
206+
send_request(Url, Headers, Method, Body, Options) ->
207+
send_request(Url, Headers, Method, Body, Options, 3).
208+
209+
send_request(_Url, _Headers, _Method, _Body, _Options, 0) ->
210+
{error, max_retries};
211+
send_request(Url, Headers, Method, Body, Options, Count) ->
212+
Pid = connect(Url),
213+
case catch(ibrowse_http_client:send_req(Pid, Url, Headers ++ basho_bench_config:get(http_append_headers,[]), Method, Body, Options, basho_bench_config:get(http_request_timeout, 5000))) of
214+
{ok, Status, RespHeaders, RespBody} ->
215+
maybe_disconnect(Url),
216+
{ok, Status, RespHeaders, RespBody};
217+
218+
Error ->
219+
clear_disconnect_freq(Url),
220+
disconnect(Url),
221+
case should_retry(Error) of
222+
true ->
223+
send_request(Url, Headers, Method, Body, Options, Count-1);
224+
225+
false ->
226+
normalize_error(Method, Error)
227+
end
228+
end.
229+
230+
231+
should_retry({error, send_failed}) -> true;
232+
should_retry({error, connection_closed}) -> true;
233+
should_retry({'EXIT', {normal, _}}) -> true;
234+
should_retry({'EXIT', {noproc, _}}) -> true;
235+
should_retry(_) -> false.
236+
237+
normalize_error(Method, {'EXIT', {timeout, _}}) -> {error, {Method, timeout}};
238+
normalize_error(Method, {'EXIT', Reason}) -> {error, {Method, 'EXIT', Reason}};
239+
normalize_error(Method, {error, Reason}) -> {error, {Method, Reason}}.

0 commit comments

Comments
 (0)