zombees 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # YARD artifacts
16
+ .yardoc
17
+ _yardoc
18
+ doc/
19
+ aws_config.yml
20
+
21
+ #VIM
22
+ *.swp
23
+ *.swo
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --format sunny
3
+ --require emoji-rspec
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ tourfleet
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 1.9.3-p392
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ script: 'bundle exec rake run_all_specs'
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,104 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ zombees (0.0.1)
5
+ celluloid (~> 0.13.0)
6
+ celluloid-pmap
7
+ colorize
8
+ fog (~> 1.8.0)
9
+ net-ssh (~> 2.5.0)
10
+ yell
11
+
12
+ GEM
13
+ specs:
14
+ builder (3.2.0)
15
+ celluloid (0.13.0)
16
+ timers (>= 1.0.0)
17
+ celluloid-pmap (0.1.0)
18
+ celluloid (~> 0.12)
19
+ coderay (1.0.9)
20
+ colorize (0.5.8)
21
+ coveralls (0.6.7)
22
+ colorize
23
+ multi_json (~> 1.3)
24
+ rest-client
25
+ simplecov (>= 0.7)
26
+ thor
27
+ diff-lcs (1.2.3)
28
+ emoji-rspec (1.0.0)
29
+ rspec (~> 2.10)
30
+ excon (0.22.1)
31
+ ffi (1.8.1)
32
+ fog (1.8.0)
33
+ builder
34
+ excon (~> 0.14)
35
+ formatador (~> 0.2.0)
36
+ mime-types
37
+ multi_json (~> 1.0)
38
+ net-scp (~> 1.0.4)
39
+ net-ssh (>= 2.1.3)
40
+ nokogiri (~> 1.5.0)
41
+ ruby-hmac
42
+ formatador (0.2.4)
43
+ guard (1.8.0)
44
+ formatador (>= 0.2.4)
45
+ listen (>= 1.0.0)
46
+ lumberjack (>= 1.0.2)
47
+ pry (>= 0.9.10)
48
+ thor (>= 0.14.6)
49
+ guard-rspec (2.6.0)
50
+ guard (>= 1.8)
51
+ rspec (~> 2.13)
52
+ listen (1.0.3)
53
+ rb-fsevent (>= 0.9.3)
54
+ rb-inotify (>= 0.9)
55
+ rb-kqueue (>= 0.2)
56
+ lumberjack (1.0.3)
57
+ method_source (0.8.1)
58
+ mime-types (1.23)
59
+ multi_json (1.7.2)
60
+ net-scp (1.0.4)
61
+ net-ssh (>= 1.99.1)
62
+ net-ssh (2.5.2)
63
+ nokogiri (1.5.9)
64
+ pry (0.9.12.1)
65
+ coderay (~> 1.0.5)
66
+ method_source (~> 0.8)
67
+ slop (~> 3.4)
68
+ rake (10.0.4)
69
+ rb-fsevent (0.9.3)
70
+ rb-inotify (0.9.0)
71
+ ffi (>= 0.5.0)
72
+ rb-kqueue (0.2.0)
73
+ ffi (>= 0.5.0)
74
+ rest-client (1.6.7)
75
+ mime-types (>= 1.16)
76
+ rspec (2.13.0)
77
+ rspec-core (~> 2.13.0)
78
+ rspec-expectations (~> 2.13.0)
79
+ rspec-mocks (~> 2.13.0)
80
+ rspec-core (2.13.1)
81
+ rspec-expectations (2.13.0)
82
+ diff-lcs (>= 1.1.3, < 2.0)
83
+ rspec-mocks (2.13.1)
84
+ ruby-hmac (0.4.0)
85
+ simplecov (0.7.1)
86
+ multi_json (~> 1.0)
87
+ simplecov-html (~> 0.7.1)
88
+ simplecov-html (0.7.1)
89
+ slop (3.4.4)
90
+ thor (0.18.1)
91
+ timers (1.1.0)
92
+ yell (1.3.0)
93
+
94
+ PLATFORMS
95
+ ruby
96
+
97
+ DEPENDENCIES
98
+ bundler (~> 1.3)
99
+ coveralls
100
+ emoji-rspec
101
+ guard-rspec
102
+ rake
103
+ rspec
104
+ zombees!
data/Guardfile ADDED
@@ -0,0 +1,24 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec' do
5
+ watch(%r{^spec/.+_spec\.rb$})
6
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
7
+ watch('spec/spec_helper.rb') { "spec" }
8
+
9
+ # Rails example
10
+ watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
11
+ watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
12
+ watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
13
+ watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
14
+ watch('config/routes.rb') { "spec/routing" }
15
+ watch('app/controllers/application_controller.rb') { "spec/controllers" }
16
+
17
+ # Capybara features specs
18
+ watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/features/#{m[1]}_spec.rb" }
19
+
20
+ # Turnip features and steps
21
+ watch(%r{^spec/acceptance/(.+)\.feature$})
22
+ watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
23
+ end
24
+
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ [![Build Status](https://travis-ci.org/zombees/zombees.png?branch=master)](https://travis-ci.org/zombees/zombees)
2
+ [![Code Climate](https://codeclimate.com/github/zombees/zombees.png)](https://codeclimate.com/github/zombees/zombees)
3
+ [![Coverage Status](https://coveralls.io/repos/zombees/zombees/badge.png?branch=master)](https://coveralls.io/r/zombees/zombees?branch=master)
4
+
5
+ ![logo](https://raw.github.com/zombees/zombees/master/zombee.png)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'zombees'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install zombees
20
+
21
+ ## Example usage
22
+
23
+ ```ruby
24
+ # ab_adapter_example.rb
25
+ require 'yaml'
26
+ require 'zombees'
27
+ require 'zombees/ab_adapter'
28
+
29
+ aws_config = YAML.load_file('aws_config.yml').symbolize_keys!
30
+
31
+ adapter = Zombees::AbAdapter.new requests: 10, concurrency: 10, url: TEST_URL
32
+ result = Zombees::Queen.new(config: aws_config, worker_count: 2, command: adapter).run
33
+ puts result.inspect
34
+ ```
35
+
36
+ ## Contributing
37
+
38
+ 1. Fork it
39
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
40
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
41
+ 4. Push to the branch (`git push origin my-new-feature`)
42
+ 5. Create new Pull Request
43
+
44
+
45
+ TODO
46
+ ------------
47
+ - Simple CLI
48
+ - Verbose mode
49
+ - Adapters for different command to run
50
+ - Siege
51
+ - Tourbus
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+ RSpec::Core::RakeTask.new(:spec)
4
+ task default: :spec
5
+
6
+ task :push => :run_all_specs do
7
+ puts "Pushing real good"
8
+ Rake.sh 'git push origin master'
9
+ end
10
+
11
+ task :run_all_specs => :spec do
12
+ Rake.sh 'bundle exec rspec --tag @integration'
13
+ end
14
+
15
+ desc 'Run all specs with code coverage'
16
+ task :coverage do
17
+ ENV['COVERAGE'] = 'true'
18
+ Rake::Task['spec'].execute
19
+ Rake::Task['run_all_specs'].execute
20
+ end
@@ -0,0 +1,10 @@
1
+ require 'yaml'
2
+ require 'zombees'
3
+ require 'zombees/ab_adapter'
4
+
5
+ aws_config = YAML.load_file('aws_config.yml').symbolize_keys!
6
+
7
+ url = URL_TO_TEST
8
+ adapter = Zombees::AbAdapter.new requests: 10, concurrency: 10, url: "#{url}/"
9
+ result = Zombees::Queen.new(config: aws_config, worker_count: 2, command: adapter).run
10
+ puts result.inspect
@@ -0,0 +1,119 @@
1
+ module Zombees
2
+ class AbAdapter
3
+ attr_reader :config
4
+ attr_writer :command_source
5
+ attr_writer :preparer_source
6
+ attr_writer :parser_source
7
+ def initialize(config={})
8
+ @config = config
9
+ end
10
+
11
+ def command_source; @command_source ||= Command.public_method(:new) end
12
+ def preparer_source; @preparer_source ||= Preparer.new end
13
+ def parser_source(output_of_commands); @parser_source ||= Parser.new(output_of_commands) end
14
+ private :command_source, :preparer_source, :parser_source
15
+
16
+ def prepare(worker)
17
+ preparer_source.prepare(worker)
18
+ end
19
+
20
+ def run(worker)
21
+ command_source.call(config).run(worker)
22
+ end
23
+
24
+ def parse(output_of_commands)
25
+ parser_source(output_of_commands).parse
26
+ end
27
+
28
+ def aggregate(output_of_commands)
29
+ parse_result = parse(output_of_commands)
30
+ Aggregator.new(parse_result).aggregate
31
+ end
32
+
33
+ class Preparer
34
+ def prepare(worker)
35
+ worker.run_command('sudo apt-get -y update && sudo apt-get -y install apache2-utils')
36
+ end
37
+ end
38
+
39
+ class Command
40
+ attr_reader :requests, :concurrency, :url, :ab_options
41
+
42
+ def initialize(options={})
43
+ options = default.merge(options)
44
+ @requests = options[:requests]
45
+ @concurrency = options[:concurrency]
46
+ @url = options[:url]
47
+ @ab_options = options[:ab_options]
48
+ end
49
+
50
+ def default
51
+ {}
52
+ end
53
+
54
+ def run(worker)
55
+ worker.run_command(command)
56
+ end
57
+
58
+ def command
59
+ "ab -r -n #{requests} -c #{concurrency} " +
60
+ %Q{-C "sessionid=fake" #{ab_options} "#{url}"}
61
+ end
62
+ end
63
+
64
+ class Aggregator
65
+ attr_reader :parsed_results
66
+ def initialize(parsed_results)
67
+ @parsed_results = parsed_results
68
+ end
69
+
70
+ def sum(key)
71
+ values = parsed_results.map { |res| res[key] }.compact
72
+ values.inject(:+)
73
+ end
74
+
75
+ def average(key)
76
+ values = parsed_results.map { |res| res[key] }.compact
77
+ if total_values = values.inject(:+)
78
+ total_values / values.count
79
+ end
80
+ end
81
+
82
+ def hash_result(key, aggregator)
83
+ value = self.send(aggregator, key)
84
+ value ? {key => value} : {}
85
+ end
86
+
87
+ def aggregate
88
+ {}.tap do |result|
89
+ result.merge! hash_result(:complete_requests, :sum)
90
+ result.merge! hash_result(:failed_requests, :sum)
91
+ result.merge! hash_result(:time_per_request, :average)
92
+ end
93
+ end
94
+ end
95
+
96
+ class Parser
97
+ attr_reader :command_results
98
+ def initialize(command_results)
99
+ @command_results = command_results
100
+ end
101
+
102
+ def parse
103
+ command_results.flatten.map do |command|
104
+ self.class.parse(command.stdout)
105
+ end
106
+ end
107
+
108
+ def self.parse(output)
109
+ output.each_line.each_with_object({}) do |line, data|
110
+ key, value = line.strip.split(/\s*:\s*/)
111
+ if key
112
+ key += " concurrent" if value =~ /concurrent/
113
+ data[key.downcase.gsub(/\s+/, '_').to_sym] = value.to_f
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,16 @@
1
+ require 'fog'
2
+ module Zombees
3
+ class Connection
4
+ extend Forwardable
5
+ def_delegator :@connection, :servers
6
+
7
+ def self.import_key_pair(config)
8
+ connection = Fog::Compute.new(config)
9
+ connection.import_key_pair(:fog_default, IO.read('tourfleet.pub')) if connection.key_pairs.get(:fog_default).nil?
10
+ end
11
+ def initialize(config)
12
+ @connection = Fog::Compute.new(config)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,22 @@
1
+ require 'zombees/worker'
2
+ require 'zombees/connection'
3
+ require 'thread'
4
+
5
+ module Zombees
6
+ class HoneyComb
7
+ attr_reader :config
8
+
9
+ def worker
10
+ Worker.new(connection)
11
+ end
12
+
13
+ def initialize(config)
14
+ @config = config
15
+ Connection.import_key_pair(config)
16
+ end
17
+
18
+ def connection
19
+ Connection.new(config)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,38 @@
1
+ require 'yaml'
2
+ require 'zombees/worker'
3
+ require 'zombees/swarm'
4
+ require 'zombees/honey_comb'
5
+ require 'yell'
6
+
7
+ module Zombees
8
+ class Queen
9
+ include Yell::Loggable
10
+ attr_reader :config, :worker_count, :command, :swarm
11
+ attr_writer :swarm_source
12
+
13
+ def initialize(options = {})
14
+ @config = options.fetch(:config)
15
+ @worker_count = options.fetch(:worker_count)
16
+ @command = options.fetch(:command)
17
+ @swarm = options[:swarm]
18
+ end
19
+
20
+ def run
21
+ logger.info "A swarm of zombie bees gathers..."
22
+ swarm.run
23
+ end
24
+
25
+ def swarm
26
+ swarm_source.call(SwarmOptions.new(
27
+ worker_count: worker_count,
28
+ command: command,
29
+ honey_comb: HoneyComb.new(config)))
30
+ end
31
+
32
+ def swarm_source
33
+ @swarm_source ||= Swarm.public_method(:new)
34
+ end
35
+
36
+ end
37
+ end
38
+
@@ -0,0 +1,59 @@
1
+ require 'celluloid'
2
+ require 'celluloid/pmap'
3
+ require 'yell'
4
+ require 'zombees/swarm_options'
5
+ require 'forwardable'
6
+
7
+ module Zombees
8
+ class Swarm
9
+ attr_reader :worker_count, :command, :honey_comb
10
+ include Yell::Loggable
11
+ extend Forwardable
12
+
13
+ def initialize(options, population=nil)
14
+ @worker_count = options.worker_count
15
+ @adapter = options.command
16
+ @honey_comb = options.honey_comb
17
+ @population = population
18
+ end
19
+
20
+ def breed
21
+ logger.info "Resurrecting the bees population of #{worker_count}"
22
+ @population ||= worker_count.downto(1).pmap do |index|
23
+ worker = honey_comb.worker
24
+ begin
25
+ logger.info "Bee #{ index } is getting ready to fight"
26
+ worker.bootstrap.tap { |w| @adapter.prepare(w) }
27
+ rescue => e
28
+ logger.error "ARGH! Bee #{ index } stayed dead: #{e.inspect}"
29
+ worker.shutdown(e)
30
+ end
31
+ worker
32
+ end
33
+
34
+ end
35
+
36
+ def population
37
+ breed
38
+ end
39
+
40
+ def run
41
+ logger.info "The swarm lurches toward the target..."
42
+ results = population.pmap do |worker|
43
+ begin
44
+ logger.info "A zombie bee is attacking the target!"
45
+ @adapter.run(worker)
46
+ rescue => e
47
+ logger.error "A zombie bee can't reach the brains due to #{e.message}"
48
+ logger.debug e.backtrace
49
+ ensure
50
+ logger.info "A zombie bee is full of brains... Going back to the grave"
51
+ #logger.debug "worker: #{ worker.object_id } server: #{ worker.server.inspect }"
52
+ worker.shutdown
53
+ end
54
+ end
55
+ logger.info "Digesting acquired brains"
56
+ @adapter.aggregate(results)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,18 @@
1
+ class SwarmOptions
2
+ attr_reader :options
3
+ def initialize(options)
4
+ @options = options
5
+ end
6
+
7
+ def worker_count
8
+ options[:worker_count]
9
+ end
10
+
11
+ def command
12
+ options[:command]
13
+ end
14
+
15
+ def honey_comb
16
+ options[:honey_comb]
17
+ end
18
+ end
@@ -0,0 +1,3 @@
1
+ module Zombees
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,45 @@
1
+ require 'forwardable'
2
+ require 'celluloid'
3
+ require 'celluloid/pmap'
4
+ require 'zombees/connection'
5
+ require 'fog'
6
+ require 'yell'
7
+
8
+ module Zombees
9
+ class Worker
10
+ include Celluloid
11
+ include Yell::Loggable
12
+ attr_reader :config
13
+ attr_reader :server
14
+ extend Forwardable
15
+ def_delegator :@server, :ready?
16
+
17
+ def initialize(connection)
18
+ @connection = connection
19
+ end
20
+
21
+ def bootstrap
22
+ @server = @connection.servers.bootstrap(
23
+ private_key_path: 'tourfleet',
24
+ key_name: :fog_default,
25
+ username: 'ubuntu'
26
+ )
27
+
28
+ @server.wait_for { ready? }
29
+ self
30
+ end
31
+
32
+ def run_command(command)
33
+ result = @server.ssh(command)
34
+ result
35
+ end
36
+
37
+ def shutdown
38
+ if @server && @server.ready?
39
+ @server.destroy
40
+ else
41
+ logger.error "zombie bee is too badly damaged to get to the graveyard"
42
+ end
43
+ end
44
+ end
45
+ end
data/lib/zombees.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'yell'
2
+ Yell.new $stdout, name: Object
3
+ require 'zombees/queen'
@@ -0,0 +1,45 @@
1
+ This is ApacheBench, Version 2.3 <$Revision: 655654 $>
2
+ Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
3
+ Licensed to The Apache Software Foundation, http://www.apache.org/
4
+
5
+ Benchmarking www.google.com (be patient)...Send request failed!
6
+ ..done
7
+
8
+
9
+ Server Software: gws
10
+ Server Hostname: www.google.com
11
+ Server Port: 80
12
+
13
+ Document Path: /
14
+ Document Length: 10740 bytes
15
+
16
+ Concurrency Level: 10
17
+ Time taken for tests: 0.083 seconds
18
+ Complete requests: 10
19
+ Failed requests: 11
20
+ (Connect: 0, Receive: 3, Length: 8, Exceptions: 0)
21
+ Write errors: 1
22
+ Total transferred: 103128 bytes
23
+ HTML transferred: 96504 bytes
24
+ Requests per second: 120.06 [#/sec] (mean)
25
+ Time per request: 83.295 [ms] (mean)
26
+ Time per request: 8.329 [ms] (mean, across all concurrent requests)
27
+ Transfer rate: 1209.09 [Kbytes/sec] received
28
+
29
+ Connection Times (ms)
30
+ min mean[+/-sd] median max
31
+ Connect: 13 16 9.2 13 42
32
+ Processing: 40 67 9.4 70 70
33
+ Waiting: 0 63 22.0 69 70
34
+ Total: 82 83 0.2 83 83
35
+
36
+ Percentage of the requests served within a certain time (ms)
37
+ 50% 83
38
+ 66% 83
39
+ 75% 83
40
+ 80% 83
41
+ 90% 83
42
+ 95% 83
43
+ 98% 83
44
+ 99% 83
45
+ 100% 83 (longest request)
@@ -0,0 +1,24 @@
1
+ require 'integration_spec_helper'
2
+ require 'zombees'
3
+ require 'zombees/ab_adapter'
4
+
5
+ describe 'Running 3 servers', integration: true do
6
+ let(:config) {{ provider: 'AWS', aws_access_key_id: 'foo', aws_secret_access_key: 'bar' }}
7
+
8
+ class Fog::SSH::Mock
9
+ alias_method :original_run, :run
10
+
11
+ def run(commands, &blk)
12
+ Array(commands).map do |command|
13
+ result = Fog::SSH::Result.new(command)
14
+ end
15
+ end
16
+ end
17
+
18
+ it 'runs a command in parallel on all servers when they are ready' do
19
+ queen = Zombees::Queen.new(worker_count: 3, command: Zombees::AbAdapter.new, config: config)
20
+ queen.run
21
+ end
22
+ end
23
+
24
+