loadaboy 0.1.1 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Rakefile +6 -1
- data/VERSION +1 -1
- data/ebin/run_loadaboy +15 -15
- data/elib/load_test.erl +70 -68
- data/elib/worker.erl +65 -49
- data/lib/erlang_interface.rb +2 -1
- data/lib/loadaboy.rb +13 -11
- data/loadaboy.gemspec +2 -2
- data/test/helper.rb +1 -0
- data/test/test_loadaboy.rb +43 -2
- metadata +5 -5
data/Rakefile
CHANGED
@@ -18,6 +18,7 @@ begin
|
|
18
18
|
gem.email = "jean-louis@icehouse.se"
|
19
19
|
gem.homepage = "http://github.com/Jell/loadaboy"
|
20
20
|
gem.authors = ["Jell"]
|
21
|
+
gem.files.exclude 'ebin/test_*', 'elib/minitest.erl'
|
21
22
|
gem.files.include(["ext"])
|
22
23
|
gem.extensions << 'ext/extconf.rb'
|
23
24
|
gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
@@ -36,6 +37,10 @@ Rake::TestTask.new(:test) do |test|
|
|
36
37
|
test.verbose = true
|
37
38
|
end
|
38
39
|
|
40
|
+
task :etest do
|
41
|
+
sh "cd ebin && ./test_load_test.erl"
|
42
|
+
end
|
43
|
+
|
39
44
|
begin
|
40
45
|
require 'rcov/rcovtask'
|
41
46
|
Rcov::RcovTask.new do |test|
|
@@ -51,7 +56,7 @@ end
|
|
51
56
|
|
52
57
|
task :test => :check_dependencies
|
53
58
|
|
54
|
-
task :default => :test
|
59
|
+
task :default => [:test, :etest]
|
55
60
|
|
56
61
|
require 'rake/rdoctask'
|
57
62
|
Rake::RDocTask.new do |rdoc|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
data/ebin/run_loadaboy
CHANGED
@@ -2,22 +2,22 @@
|
|
2
2
|
%% -*- erlang -*-
|
3
3
|
|
4
4
|
main(String) ->
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
5
|
+
try
|
6
|
+
error_logger:tty(false),
|
7
|
+
[Domain, WorkerCount, RequestCount | Services ] = String,
|
8
|
+
LoadTest = load_test:new(Domain, list_to_integer(WorkerCount), list_to_integer(RequestCount), Services),
|
9
|
+
LoadTest:start(),
|
10
|
+
LoadTest:prepare(),
|
11
|
+
LoadTest:run()
|
12
|
+
catch
|
13
|
+
_:_ ->
|
14
|
+
usage()
|
15
|
+
end;
|
16
16
|
|
17
17
|
main(_) ->
|
18
|
-
|
19
|
-
|
18
|
+
io:format("no header\n"),
|
19
|
+
usage().
|
20
20
|
|
21
21
|
usage() ->
|
22
|
-
|
23
|
-
|
22
|
+
io:format("usage: loadaboy domain worker-count request-count\n"),
|
23
|
+
halt(1).
|
data/elib/load_test.erl
CHANGED
@@ -1,90 +1,92 @@
|
|
1
|
-
-module(load_test, [Domain, WorkerCount, RequestCount]).
|
1
|
+
-module(load_test, [Domain, WorkerCount, RequestCount, ServiceList]).
|
2
2
|
%-export([start/0, stop/0, run/0, generate_services/1, get_services/0, get_workers/1, get_jobs/2, get_urls/3, set_urls/4]).
|
3
3
|
-compile(export_all).
|
4
4
|
-record(load_entry, {key, urls}).
|
5
5
|
|
6
6
|
start() ->
|
7
|
-
|
8
|
-
|
7
|
+
inets:start(),
|
8
|
+
mnesia:start().
|
9
9
|
|
10
10
|
stop() ->
|
11
|
-
|
11
|
+
inets:stop(),
|
12
|
+
mnesia:stop().
|
13
|
+
|
14
|
+
prepare_mnesia() ->
|
15
|
+
mnesia:delete_table(load_entry),
|
16
|
+
mnesia:create_table(load_entry, [{attributes, record_info(fields, load_entry)}]).
|
17
|
+
|
18
|
+
open_ruby_port() ->
|
19
|
+
Cmd = "ruby ../lib/erlang_interface.rb",
|
20
|
+
open_port({spawn, Cmd}, [{packet, 4}, nouse_stdio, exit_status, binary]).
|
12
21
|
|
13
22
|
prepare() ->
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
23
|
+
prepare_mnesia(),
|
24
|
+
Port = open_ruby_port(),
|
25
|
+
Payload = case ServiceList of
|
26
|
+
[Service] ->
|
27
|
+
term_to_binary([prepare, [WorkerCount, RequestCount, list_to_binary(Service)]]);
|
28
|
+
_ ->
|
29
|
+
term_to_binary([prepare, [WorkerCount, RequestCount]])
|
30
|
+
end,
|
31
|
+
port_command(Port, Payload),
|
32
|
+
receive
|
33
|
+
{Port, {data, Data}} ->
|
34
|
+
{result, Text} = binary_to_term(Data),
|
35
|
+
[set_urls(test, WorkerName, tuple_to_list(Urls)) || {WorkerName, Urls}<- tuple_to_list(Text)]
|
36
|
+
end.
|
37
|
+
|
26
38
|
run() ->
|
27
|
-
|
28
|
-
|
29
|
-
|
39
|
+
{atomic, Services} = get_services(),
|
40
|
+
generate_services(lists:usort(Services)),
|
41
|
+
wait_for_workers(WorkerCount).
|
30
42
|
|
31
|
-
generate_services(
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
generate_workers(Service, Workers),
|
38
|
-
generate_services(Remaining)
|
39
|
-
end.
|
43
|
+
generate_services([]) ->
|
44
|
+
done;
|
45
|
+
generate_services([Service | Remaining]) ->
|
46
|
+
{atomic, Workers} = get_workers(Service),
|
47
|
+
generate_workers(Service, Workers),
|
48
|
+
generate_services(Remaining).
|
40
49
|
|
41
|
-
generate_workers(
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
Worker:run(),
|
49
|
-
generate_workers(Service, Remaining)
|
50
|
-
end.
|
50
|
+
generate_workers(_Service, []) ->
|
51
|
+
done;
|
52
|
+
generate_workers(Service, [WorkerName | Remaining]) ->
|
53
|
+
{atomic, [Urls]} = get_urls(Service, WorkerName),
|
54
|
+
gen_server:start_link({local, WorkerName}, worker, [self(), WorkerName, Domain, Urls], []),
|
55
|
+
gen_server:cast(WorkerName, run),
|
56
|
+
generate_workers(Service, Remaining).
|
51
57
|
|
58
|
+
wait_for_workers(0) ->
|
59
|
+
done;
|
52
60
|
wait_for_workers(RemainingWorkers) ->
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
done;
|
58
|
-
_ ->
|
59
|
-
wait_for_workers(RemainingWorkers - 1)
|
60
|
-
end
|
61
|
-
end.
|
61
|
+
receive
|
62
|
+
done ->
|
63
|
+
wait_for_workers(RemainingWorkers - 1)
|
64
|
+
end.
|
62
65
|
|
63
66
|
get_services() ->
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
F = fun() ->
|
68
|
+
LoadEntry = #load_entry{key = {'$1', '_'}, urls = '_'},
|
69
|
+
mnesia:select(load_entry, [{LoadEntry, [], ['$1']}])
|
70
|
+
end,
|
71
|
+
mnesia:transaction(F).
|
69
72
|
|
70
73
|
get_workers(Service) ->
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
74
|
+
F = fun() ->
|
75
|
+
LoadEntry = #load_entry{key = {Service, '$1'}, urls = '_'},
|
76
|
+
mnesia:select(load_entry, [{LoadEntry, [], ['$1']}])
|
77
|
+
end,
|
78
|
+
mnesia:transaction(F).
|
77
79
|
|
78
80
|
get_urls(Service, Worker) ->
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
81
|
+
F = fun() ->
|
82
|
+
LoadEntry = #load_entry{key = {Service, Worker}, urls = '$1'},
|
83
|
+
mnesia:select(load_entry, [{LoadEntry, [], ['$1']}])
|
84
|
+
end,
|
85
|
+
mnesia:transaction(F).
|
84
86
|
|
85
87
|
set_urls(Service, Worker, Urls) ->
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
88
|
+
LoadEntry = #load_entry{key = {Service, Worker}, urls = Urls},
|
89
|
+
F = fun() ->
|
90
|
+
mnesia:write(LoadEntry)
|
91
|
+
end,
|
92
|
+
mnesia:transaction(F).
|
data/elib/worker.erl
CHANGED
@@ -1,51 +1,67 @@
|
|
1
|
-
-module(worker
|
2
|
-
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
1
|
+
-module(worker).
|
2
|
+
-export([code_change/3, handle_cast/2, handle_call/3, handle_info/2, init/1, terminate/2]).
|
3
|
+
-behavior(gen_server).
|
4
|
+
|
5
|
+
init([Parent, ProfileName, Domain, Urls]) ->
|
6
|
+
inets:start(httpc, [{profile, ProfileName}]),
|
7
|
+
{ok, [Parent, ProfileName, Domain, Urls]}.
|
8
|
+
|
9
|
+
handle_call(_Message, _From, [Parent, ProfileName, Domain, Urls]) ->
|
10
|
+
fetch_urls([ProfileName, Domain, Urls]),
|
11
|
+
{reply, done, [Parent, ProfileName, Domain, Urls]}.
|
12
|
+
|
13
|
+
handle_cast(run, [Parent, ProfileName, Domain, Urls]) ->
|
14
|
+
fetch_urls([ProfileName, Domain, Urls]),
|
15
|
+
Parent!done,
|
16
|
+
{noreply, [Parent, ProfileName, Domain, Urls]}.
|
17
|
+
|
18
|
+
handle_info(Msg, State) ->
|
19
|
+
io:format("Unexpected message: ~p~n",[Msg]),
|
20
|
+
{noreply, State}.
|
21
|
+
|
22
|
+
code_change(_OldVsn, State, _Extra) ->
|
23
|
+
%% No change planned. The function is there for the behaviour,
|
24
|
+
%% but will not be used. Only a version on the next
|
25
|
+
{ok, State}.
|
26
|
+
|
27
|
+
terminate(_, _) -> ok.
|
28
|
+
|
29
|
+
fetch_urls([_ProfileName, _Domain, []]) ->
|
30
|
+
done;
|
31
|
+
fetch_urls([ProfileName, Domain, [{Name, Url}|Tail]]) ->
|
32
|
+
[Header, Time] = fetch_url(Domain ++ binary_to_list(Url), ProfileName),
|
33
|
+
forecast_result(Header, Name, Time, Url),
|
34
|
+
fetch_urls([ProfileName, Domain, Tail]).
|
35
|
+
|
36
|
+
forecast_result(Header, Name, Time, Url) ->
|
37
|
+
io:format("~p ~p ~p ~s~n", [Header, Name, Time, Url]).
|
38
|
+
|
39
|
+
fetch_url(Url, ProfileName) ->
|
40
|
+
try
|
41
|
+
Tic = erlang:now(),
|
42
|
+
Result = get_over_http(Url, ProfileName),
|
43
|
+
Tac = erlang:now(),
|
44
|
+
Time = time_to_ms(Tac) - time_to_ms(Tic),
|
45
|
+
case Result of
|
46
|
+
timeout ->
|
47
|
+
[{"HTTP/1.1", 408, "Request Timeout"}, Time];
|
48
|
+
{Header, _Params, _Body} ->
|
49
|
+
[Header, Time]
|
50
|
+
end
|
51
|
+
catch
|
52
|
+
_:_ ->
|
53
|
+
[{"HTTP/1.1", 400, "Bad Request"}, 0]
|
54
|
+
end.
|
39
55
|
|
40
56
|
time_to_ms({Mega, Sec, Micro})->
|
41
|
-
|
42
|
-
|
43
|
-
get_over_http(Url) ->
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
57
|
+
Mega * 1000000000 + Sec * 1000 + Micro / 1000.
|
58
|
+
|
59
|
+
get_over_http(Url, ProfileName) ->
|
60
|
+
{ok, RequestId} = httpc:request(get, {Url, []}, [], [{sync, false}], ProfileName),
|
61
|
+
receive
|
62
|
+
{http, {RequestId, Result}} ->
|
63
|
+
Result
|
64
|
+
after
|
65
|
+
10000 ->
|
66
|
+
timeout
|
67
|
+
end.
|
data/lib/erlang_interface.rb
CHANGED
@@ -9,7 +9,8 @@ require "#{LOADABOY_EXEC_DIR}/Loadafile" if File.exist? "#{LOADABOY_EXEC_DIR}/Lo
|
|
9
9
|
|
10
10
|
receive do |f|
|
11
11
|
f.when([:prepare, Array]) do |array|
|
12
|
-
|
12
|
+
workers, jobs, service = array
|
13
|
+
f.send!([:result, generate_requests(workers, jobs, service)])
|
13
14
|
f.receive_loop
|
14
15
|
end
|
15
16
|
end
|
data/lib/loadaboy.rb
CHANGED
@@ -3,29 +3,31 @@ require 'enumerator'
|
|
3
3
|
module LoadaBoy
|
4
4
|
|
5
5
|
class << self
|
6
|
-
def generator(&block)
|
7
|
-
define_method
|
6
|
+
def generator(service = 'default', &block)
|
7
|
+
define_method "generate_#{service}", &block
|
8
|
+
end
|
9
|
+
def set_generator(name)
|
10
|
+
alias_method(:generate, "generate_#{name}")
|
8
11
|
end
|
9
12
|
end
|
10
13
|
|
11
|
-
|
12
|
-
|
13
|
-
def generate
|
14
|
+
def generate_default
|
14
15
|
{ :default => "/" }
|
15
16
|
end
|
16
17
|
|
17
|
-
|
18
|
+
alias_method :generate, :generate_default
|
19
|
+
|
20
|
+
protected
|
21
|
+
|
22
|
+
def generate_requests(workers_count, jobs_count, service = nil)
|
23
|
+
methods.include?("generate_#{service}") ? LoadaBoy.set_generator(service) : LoadaBoy.set_generator(:default)
|
18
24
|
(1..workers_count).map do |i|
|
19
25
|
["worker#{i}".to_sym, generate_urls(jobs_count)]
|
20
26
|
end
|
21
27
|
end
|
22
28
|
|
23
29
|
def generate_urls(jobs_count)
|
24
|
-
|
25
|
-
(1..jobs_count).map { generate.to_a }.flatten.each_slice(2) do |slice|
|
26
|
-
urls << slice
|
27
|
-
end
|
28
|
-
urls
|
30
|
+
(1..jobs_count).inject([]){ |acc, _| acc + generate.to_a}
|
29
31
|
end
|
30
32
|
end
|
31
33
|
|
data/loadaboy.gemspec
CHANGED
@@ -5,11 +5,11 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{loadaboy}
|
8
|
-
s.version = "0.
|
8
|
+
s.version = "0.2.0"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Jell"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-11-15}
|
13
13
|
s.default_executable = %q{loadaboy}
|
14
14
|
s.description = %q{LoadaBoy is a load testing gem.}
|
15
15
|
s.email = %q{jean-louis@icehouse.se}
|
data/test/helper.rb
CHANGED
data/test/test_loadaboy.rb
CHANGED
@@ -1,7 +1,48 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
3
|
class TestLoadaboy < Test::Unit::TestCase
|
4
|
-
|
5
|
-
|
4
|
+
include LoadaBoy
|
5
|
+
|
6
|
+
context ".generator" do
|
7
|
+
should "define a generate function running the given block" do
|
8
|
+
LoadaBoy.generator do
|
9
|
+
"custom generator"
|
10
|
+
end
|
11
|
+
assert_equal "custom generator", generate_default
|
12
|
+
end
|
13
|
+
should "define a generate function running the given block with proper name" do
|
14
|
+
LoadaBoy.generator :test do
|
15
|
+
"custom generator"
|
16
|
+
end
|
17
|
+
assert_equal "custom generator", generate_test
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context "#generate" do
|
22
|
+
should "return a default url" do
|
23
|
+
assert_equal({:default => "/"}, generate)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "#generate_requests" do
|
28
|
+
should "return a list of n-workers with their url list" do
|
29
|
+
stubs(:generate_urls).returns("url_list")
|
30
|
+
assert_equal [[:worker1, "url_list"], [:worker2, "url_list"]], generate_requests(2, 1)
|
31
|
+
end
|
32
|
+
should "set proper generator" do
|
33
|
+
LoadaBoy.generator :test do
|
34
|
+
"custom generator"
|
35
|
+
end
|
36
|
+
generate_requests(2, 1, :test)
|
37
|
+
assert_equal "custom generator", generate
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context "#generate_urls" do
|
42
|
+
should "return a list of n urls" do
|
43
|
+
stubs(:generate).returns("name" => "url")
|
44
|
+
assert_equal [["name", "url"], ["name", "url"]], generate_urls(2)
|
45
|
+
end
|
6
46
|
end
|
47
|
+
|
7
48
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: loadaboy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 23
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 2
|
9
|
+
- 0
|
10
|
+
version: 0.2.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jell
|
@@ -15,7 +15,7 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2010-
|
18
|
+
date: 2010-11-15 00:00:00 +01:00
|
19
19
|
default_executable: loadaboy
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|