Skip to content

Commit

Permalink
new config format
Browse files Browse the repository at this point in the history
  • Loading branch information
Drew Kerrigan committed Dec 5, 2013
1 parent 9c52f1e commit 074edc6
Show file tree
Hide file tree
Showing 2 changed files with 170 additions and 53 deletions.
90 changes: 74 additions & 16 deletions examples/http.config
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,80 @@

{driver, basho_bench_driver_http}.

{key_generator, {int_to_str, {uniform_int, 50000}}}.
%% Default generators, reference by the atoms key_generator and value_generator
{key_generator, {int_to_str, {sequential_int, 50000}}}.
{value_generator, {fixed_bin, 10000}}.

{value_generator, {fixed_bin, 10}}.
%% {Name, KeyGen | ValGen}

%% Name: atom()
%% KeyGen: User or Basho Bench defined key generator
%% ValGen: User or Basho Bench defined value generator
{generators, [
{string_g, {key_generator, {int_to_str, {uniform_int, 50000}}}},
{binstring_g, {value_generator, {fixed_bin, 100}}},
]}.

%% {Name, Value}
%% {Name, {FormattedValue, Generators}}

%% Name: atom()
%% Value: string() | atom() - named generator, can be key_generator or value_generator for default
%% FormattedValue: string() - formatted with io_lib:format
%% Generators: list() - list of generators, can be key_generator or value_generator for default
{values, [
{json_v, {"{\"this\":\"is_json_~p\"}", [string_g]}},
{xml_v, {"<?xml version=\"1.0\"?><catalog><book><author>~p</author></book></catalog>", [binstring_g]}},
{plainstring_v, "hello"},
{smallbin_v, binstring_g},
{largebin_v, value_generator}
]}.

%% {Name, Headers}
%% Name: atom()

%% Headers: proplist()
{headers, [
{json_h, [{'Content-Type', 'application/json'}, {'Accept', 'application/json'}]},
{xml_h, [{'Content-Type', 'application/xml'}]},
{binary_h, [{'Content-Type', 'application/octet-stream'}]},
{empty_h, []}
]}.

%% {Name, {Host, Port, Path}}
%% {Name, {Host, Port, {FormattedPath, Generators}}}

%% Name: atom()
%% Host: string()
%% Port: integer()
%% Path: string()
%% FormattedPath: string() - formatted with io_lib:format
%% Generators: list() - list of generators, can be key_generator or value_generator for default
{targets, [
{base_uri_t, {"localhost", 4567, "/"}},
{with_key_t, {"localhost", 4567, {"/~p", key_generator}}},
{with_another_key_t, {"localhost", 4567, {"another/~p", string_g}}},
{smallbin_t, {"localhost", 4567, {"smallbin/~p", key_generator}}},
{upload_t, {"localhost", 4567, {"upload/~p", key_generator}}}
]}.

%% {{Operation,

{operations, [
%% Get without a key
{{get, {"localhost", 4567, "/"}, []}, 1},
%% Get with a key and headers
{{get_re, {"localhost", 4567, "/%%K"}, [{'Content-Type', 'application/json'}]}, 1},
%% Put with a json object and value
{{put_re, {"localhost", 4567, "/",
"{\"this\":\"is_json_%%V\"}"}, [{'Content-Type', 'application/json'}]}, 1},
%% Post with an xml object and value
{{post_re, {"localhost", 4567, "/%%K",
"<?xml version=\"1.0\"?><catalog><book><author>%%V</author></book></catalog>"},
[{'Content-Type', 'application/xml'}]}, 1},
%% Delete with a key
{{delete_re, {"localhost", 4567, "/%%K"}, []}, 1}
]}.
%% Get without a key
{{get, base_uri_t}, 1},
%% Get with a key and headers
{{get, with_key_t, json_h}, 1},
%% Put with a json object
{{put, base_uri_t, json_v, json_h}, 1},
%% Post with an xml object and value
{{post, with_key_t, xml_v, xml_h}, 1},
%% Alternate keygen with plaintext
{{post, with_another_key_t, plainstring_v, empth_h}, 1},
%% Binary value
{{post, smallbin_t, smallbin_v, binary_h}, 1},
%% Large binary value using default value gen
{{post, upload_t, largebin_v, binary_h}, 1},
%% Delete with a key
{{delete, with_key_t}, 1}
]}.
133 changes: 96 additions & 37 deletions src/basho_bench_driver_http.erl
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,28 @@

-record(url, {abspath, host, port, username, password, path, protocol, host_type}).

-record(state, {path_params}). % Params to append on the path
-record(state, {
generators = [],
values = [],
headers = [],
targets = []}).


%% ====================================================================
%% API
%% ====================================================================

new(Id) ->

build_generators([{Name, {key_generator, KeyGenSpec}}|Rest], Generators, Id) ->
KeyGen = basho_bench_keygen:new(KeyGenSpec, Id),
build_generators(Rest, [{Name, KeyGen}|Generators], Id);
build_generators([{Name, {value_generator, ValGenSpec}}|Rest], Generators, Id) ->
ValGen = basho_bench_valgen:new(ValGenSpec, Id),
build_generators(Rest, [{Name, ValGen}|Generators], Id);
build_generators([], Generators, _) ->
{ok, Generators}.

new(Id) ->
?DEBUG("ID: ~p\n", [Id]),

%% Make sure ibrowse is available
Expand All @@ -49,9 +62,8 @@ new(Id) ->

application:start(ibrowse),

Params = basho_bench_config:get(http_params, ""),
Disconnect = basho_bench_config:get(http_disconnect_frequency, infinity),

case Disconnect of
infinity -> ok;
Seconds when is_integer(Seconds) -> ok;
Expand All @@ -62,16 +74,26 @@ new(Id) ->
%% Uses pdict to avoid threading state record through lots of functions
erlang:put(disconnect_freq, Disconnect),

{ok, #state {path_params = Params}}.

run({get_re, {Host, Port, Path}, Headers}, KeyGen, _ValueGen, _State) ->
Path1 = re:replace(Path, "%%K", KeyGen(), [global, {return, list}]),
run({get, {Host, Port, Path1}, Headers}, KeyGen, _ValueGen, _State);

run({get, {Host, Port, Path}, Headers}, _KeyGen, _ValueGen, _State) ->
PUrl = #url{host=Host, port=Port, path=Path},

case do_get(PUrl, Headers) of
%% TODO: Validate these
Generators = build_generators(basho_bench_config:get(generators), [], Id),
Values = basho_bench_config:get(values),
Headers = basho_bench_config:get(headers),
Targets = basho_bench_config:get(targets),

{ok, #state {
generators = Generators,
values = Values,
headers = Headers,
targets = Targets
}}.

run({get, Target}, KeyGen, ValueGen, State) ->
run({get, Target, undefined}, KeyGen, ValueGen, State);
run({get, Target, HeaderName}, KeyGen, ValueGen, State) ->
Url = build_url(Target, KeyGen, ValueGen, State),
Headers = proplists:get(HeaderName, State#state.headers, []),

case do_get(Url, Headers) of
{not_found, _Url} ->
{ok, 1};
{ok, _Url, _Header} ->
Expand All @@ -80,44 +102,41 @@ run({get, {Host, Port, Path}, Headers}, _KeyGen, _ValueGen, _State) ->
{error, Reason, 1}
end;

run({put_re, {Host, Port, Path, Data}, Headers}, KeyGen, ValueGen, _State) ->
Path1 = re:replace(Path, "%%K", KeyGen(), [global, {return, list}]),
Value = re:replace(Data, "%%V", ValueGen(), [global, {return, list}]),
run({put, {Host, Port, Path1, Value}, Headers}, KeyGen, ValueGen, _State);

run({put, {Host, Port, Path, Data}, Headers}, _KeyGen, _ValueGen, _State) ->
PUrl = #url{host=Host, port=Port, path=Path},
run({put, Target, ValueName}, KeyGen, ValueGen, State) ->
run({put, Target, ValueName, undefined}, KeyGen, ValueGen, State);
run({put, Target, ValueName, HeaderName}, KeyGen, ValueGen, State) ->
Url = build_url(Target, KeyGen, ValueGen, State),
Headers = proplists:get(HeaderName, State#state.headers, []),
Data = build_value(ValueName, KeyGen, ValueGen, State),

case do_put(PUrl, Headers, Data) of
case do_put(Url, Headers, Data) of
ok ->
{ok, 1};
{error, Reason} ->
{error, Reason, 1}
end;

run({post_re, {Host, Port, Path, Data}, Headers}, KeyGen, ValueGen, _State) ->
Path1 = re:replace(Path, "%%K", KeyGen(), [global, {return, list}]),
Value = re:replace(Data, "%%V", ValueGen(), [global, {return, list}]),
run({post, {Host, Port, Path1, Value}, Headers}, KeyGen, ValueGen, _State);
run({post, Target, ValueName}, KeyGen, ValueGen, State) ->
run({post, Target, ValueName, undefined}, KeyGen, ValueGen, State);
run({post, Target, ValueName, HeaderName}, KeyGen, ValueGen, State) ->
Url = build_url(Target, KeyGen, ValueGen, State),
Headers = proplists:get(HeaderName, State#state.headers, []),
Data = build_value(ValueName, KeyGen, ValueGen, State),

run({post, {Host, Port, Path, Data}, Headers}, _KeyGen, _ValueGen, _State) ->
PUrl = #url{host=Host, port=Port, path=Path},

case do_post(PUrl, Headers, Data) of
case do_post(Url, Headers, Data) of
ok ->
{ok, 1};
{error, Reason} ->
{error, Reason, 1}
end;

run({delete_re, {Host, Port, Path}, Headers}, KeyGen, _ValueGen, _State) ->
Path1 = re:replace(Path, "%%K", KeyGen(), [global, {return, list}]),
run({delete, {Host, Port, Path1}, Headers}, KeyGen, _ValueGen, _State);

run({delete, {Host, Port, Path}, Headers}, _KeyGen, _ValueGen, _State) ->
PUrl = #url{host=Host, port=Port, path=Path},
run({delete, Target}, KeyGen, ValueGen, State) ->
run({delete, Target, undefined}, KeyGen, ValueGen, State);
run({delete, Target, HeaderName}, KeyGen, ValueGen, State) ->
Url = build_url(Target, KeyGen, ValueGen, State),
Headers = proplists:get(HeaderName, State#state.headers, []),

case do_delete(PUrl, Headers) of
case do_delete(Url, Headers) of
ok ->
{ok, 1};
{error, Reason} ->
Expand All @@ -128,6 +147,39 @@ run({delete, {Host, Port, Path}, Headers}, _KeyGen, _ValueGen, _State) ->
%% Internal functions
%% ====================================================================

evaluate_generator(Name, Generators, KeyGen, ValueGen) ->
case Name of
key_generator -> KeyGen();
value_generator -> ValueGen();
N when is_atom(N) -> proplists:get(N, Generators);
Value -> Value
end.

build_formatted_value(String, GeneratorNames, Generators, KeyGen, ValueGen) ->

F = fun(Name) ->
evaluate_generator(Name, Generators, KeyGen, ValueGen)
end,

Values = lists:map(F, GeneratorNames),

io_lib:format(String, Values).

build_url({Host, Port, {FormattedPath, GeneratorNames}}, Generators, KeyGen, ValueGen) ->
Path = build_formatted_value(FormattedPath, GeneratorNames, Generators, KeyGen, ValueGen),
#url{host=Host, port=Port, path=Path};
build_url({Host, Port, Path}, _, _, _) ->
#url{host=Host, port=Port, path=Path};
build_url(Target, KeyGen, ValueGen, State) ->
build_url(proplists:get_value(Target, State#state.targets), State#state.generators, KeyGen, ValueGen).

build_value(ValueName, KeyGen, ValueGen, State) ->
case proplists:get_value(ValueName, State#state.values) of
{FormattedValue, GeneratorNames} ->
build_formatted_value(FormattedValue, GeneratorNames, State#state.generators, KeyGen, ValueGen);
V -> evaluate_generator(V, State#state.generators, KeyGen, ValueGen)
end.

do_get(Url, Headers) ->
%%case send_request(Url, [], get, [], [{response_format, binary}]) of
case send_request(Url, Headers, get, [], [{response_format, binary}]) of
Expand Down Expand Up @@ -312,3 +364,10 @@ should_retry(_) -> false.
normalize_error(Method, {'EXIT', {timeout, _}}) -> {error, {Method, timeout}};
normalize_error(Method, {'EXIT', Reason}) -> {error, {Method, 'EXIT', Reason}};
normalize_error(Method, {error, Reason}) -> {error, {Method, Reason}}.

any_to_str(Val) when is_binary(Val) ->
binary_to_list(Val);
any_to_str(Val) when is_integer(Val) ->
integer_to_list(Val);
any_to_str(Val) ->
Val.

0 comments on commit 074edc6

Please sign in to comment.