loadaboy 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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.1
1
+ 0.2.0
data/ebin/run_loadaboy CHANGED
@@ -2,22 +2,22 @@
2
2
  %% -*- erlang -*-
3
3
 
4
4
  main(String) ->
5
- try
6
- error_logger:tty(false),
7
- [Domain, WorkerCount, RequestCount] = String,
8
- LoadTest = load_test:new(Domain, list_to_integer(WorkerCount), list_to_integer(RequestCount)),
9
- LoadTest:start(),
10
- LoadTest:prepare(),
11
- LoadTest:run()
12
- catch
13
- _:_ ->
14
- usage()
15
- end;
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
- io:format("no header\n"),
19
- usage().
18
+ io:format("no header\n"),
19
+ usage().
20
20
 
21
21
  usage() ->
22
- io:format("usage: loadaboy domain worker-count request-count\n"),
23
- halt(1).
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
- inets:start(),
8
- mnesia:start().
7
+ inets:start(),
8
+ mnesia:start().
9
9
 
10
10
  stop() ->
11
- mnesia:stop().
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
- mnesia:delete_table(load_entry),
15
- mnesia:create_table(load_entry, [{attributes, record_info(fields, load_entry)}]),
16
- Cmd = "ruby ../lib/erlang_interface.rb",
17
- Port = open_port({spawn, Cmd}, [{packet, 4}, nouse_stdio, exit_status, binary]),
18
- Payload = term_to_binary([prepare, [WorkerCount, RequestCount]]),
19
- port_command(Port, Payload),
20
- receive
21
- {Port, {data, Data}} ->
22
- {result, Text} = binary_to_term(Data),
23
- [set_urls(test, WorkerName, tuple_to_list(Urls)) || {WorkerName, Urls}<- tuple_to_list(Text)]
24
- end.
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
- {atomic, Services} = get_services(),
28
- generate_services(lists:usort(Services)),
29
- wait_for_workers(WorkerCount).
39
+ {atomic, Services} = get_services(),
40
+ generate_services(lists:usort(Services)),
41
+ wait_for_workers(WorkerCount).
30
42
 
31
- generate_services(Services) ->
32
- case Services of
33
- [] ->
34
- done;
35
- [Service | Remaining] ->
36
- {atomic, Workers} = get_workers(Service),
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(Service, Workers) ->
42
- case Workers of
43
- [] ->
44
- done;
45
- [WorkerName | Remaining] ->
46
- {atomic, [Urls]} = get_urls(Service, WorkerName),
47
- Worker = worker:new(self(), WorkerName, Domain, Urls),
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
- receive
54
- done ->
55
- case RemainingWorkers - 1 of
56
- 0 ->
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
- F = fun() ->
65
- LoadEntry = #load_entry{key = {'$1', '_'}, urls = '_'},
66
- mnesia:select(load_entry, [{LoadEntry, [], ['$1']}])
67
- end,
68
- mnesia:transaction(F).
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
- F = fun() ->
72
- LoadEntry = #load_entry{key = {Service, '$1'}, urls = '_'},
73
- mnesia:select(load_entry, [{LoadEntry, [], ['$1']}])
74
- end,
75
- mnesia:transaction(F).
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
- F = fun() ->
80
- LoadEntry = #load_entry{key = {Service, Worker}, urls = '$1'},
81
- mnesia:select(load_entry, [{LoadEntry, [], ['$1']}])
82
- end,
83
- mnesia:transaction(F).
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
- LoadEntry = #load_entry{key = {Service, Worker}, urls = Urls},
87
- F = fun() ->
88
- mnesia:write(LoadEntry)
89
- end,
90
- mnesia:transaction(F).
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, [Parent, ProfileName, Domain, Urls]).
2
- -compile(export_all).
3
-
4
- run() ->
5
- spawn(
6
- fun() ->
7
- inets:start(httpc, [{profile, ProfileName}]),
8
- fetch_urls(Urls)
9
- end
10
- ).
11
-
12
- fetch_urls(UrlList) ->
13
- case UrlList of
14
- [] ->
15
- Parent!done;
16
- [Url|Tail] ->
17
- {Name, Request} = Url,
18
- [Header, Time] = fetch_url(Domain ++ binary_to_list(Request)),
19
- io:format("~p ~p ~p~n", [Header, Name, Time]),
20
- fetch_urls(Tail)
21
- end.
22
-
23
- fetch_url(Url) ->
24
- try
25
- Tic = erlang:now(),
26
- Result = get_over_http(Url),
27
- Tac = erlang:now(),
28
- Time = time_to_ms(Tac) - time_to_ms(Tic),
29
- case Result of
30
- timeout ->
31
- [{"HTTP/1.1", 408, "Request Timeout"}, Time];
32
- {Header, _Params, _Body} ->
33
- [Header, Time]
34
- end
35
- catch
36
- _:_ ->
37
- [{"HTTP/1.1", 400, "Bad Request"}, 0]
38
- end.
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
- Mega * 1000000000 + Sec * 1000 + Micro / 1000.
42
-
43
- get_over_http(Url) ->
44
- {ok, RequestId} = httpc:request(get, {Url, []}, [], [{sync, false}], ProfileName),
45
- receive
46
- {http, {RequestId, Result}} ->
47
- Result
48
- after
49
- 10000 ->
50
- timeout
51
- end.
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.
@@ -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
- f.send!([:result, generate_requests(array[0], array[1])])
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 :generate, &block
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
- protected
12
-
13
- def generate
14
+ def generate_default
14
15
  { :default => "/" }
15
16
  end
16
17
 
17
- def generate_requests(workers_count, jobs_count)
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
- urls = []
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.1.1"
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-10-26}
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
@@ -1,6 +1,7 @@
1
1
  require 'rubygems'
2
2
  require 'test/unit'
3
3
  require 'shoulda'
4
+ require 'mocha'
4
5
 
5
6
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
6
7
  $LOAD_PATH.unshift(File.dirname(__FILE__))
@@ -1,7 +1,48 @@
1
1
  require 'helper'
2
2
 
3
3
  class TestLoadaboy < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
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: 25
4
+ hash: 23
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
- - 1
9
- - 1
10
- version: 0.1.1
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-10-26 00:00:00 +02:00
18
+ date: 2010-11-15 00:00:00 +01:00
19
19
  default_executable: loadaboy
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency