zombees 0.0.1

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/.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
+