elbping 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ require 'elbping/cli.rb'
7
+
8
+ $stderr.sync = true
9
+ $stdout.sync = true
10
+
11
+ ElbPing::CLI.main
12
+
@@ -0,0 +1,94 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'optparse'
4
+
5
+ require 'elbping/pinger.rb'
6
+ require 'elbping/resolver.rb'
7
+ require 'elbping/display.rb'
8
+
9
+ module ElbPing
10
+ module CLI
11
+
12
+ # Set up default options
13
+ OPTIONS = {}
14
+ OPTIONS[:verb_len] = ENV['PING_ELB_MAXVERBLEN'] || 128
15
+ OPTIONS[:nameserver] = ENV['PING_ELB_NS'] || 'ns-941.amazon.com'
16
+ OPTIONS[:count] = ENV['PING_ELB_PINGCOUNT'] || 4
17
+ OPTIONS[:timeout] = ENV['PING_ELB_TIMEOUT'] || 10
18
+ OPTIONS[:wait] = ENV['PING_ELB_WAIT'] || 0
19
+
20
+ # Build parser for command line options
21
+ PARSER = OptionParser.new do |opts|
22
+ opts.banner = "Usage: #{$0} [options] <elb hostname>"
23
+
24
+ opts.on("-N NAMESERVER", "--nameserver NAMESERVER", "Use NAMESERVER to perform DNS queries") do |ns|
25
+ OPTIONS[:nameserver] = ns
26
+ end
27
+ opts.on("-L LENGTH", "--verb-length LENGTH", Integer, "Use verb LENGTH characters long") do |n|
28
+ OPTIONS[:verb_len] = n
29
+ end
30
+ opts.on("-W SECONDS", "--timeout SECONDS", Integer, "Use timeout of SECONDS for HTTP requests") do |n|
31
+ OPTIONS[:timeout] = n
32
+ end
33
+ opts.on("-w SECONDS", "--wait SECONDS", Integer, "Wait SECONDS between pings (default: 0)") do |n|
34
+ OPTIONS[:wait] = n
35
+ end
36
+ opts.on("-c COUNT", "--count COUNT", Integer, "Ping each node COUNT times") do |n|
37
+ OPTIONS[:count] = n
38
+ end
39
+ end
40
+
41
+ # Parse options
42
+ def self.usage
43
+ puts PARSER.help
44
+ exit(false)
45
+ end
46
+
47
+ # Main entry point of the program
48
+ def self.main
49
+ PARSER.parse!(ARGV) rescue usage
50
+
51
+ if ARGV.size < 1
52
+ usage
53
+ end
54
+
55
+ target = ARGV[0]
56
+ nodes = ElbPing::Resolver.find_elb_nodes(target, OPTIONS[:nameserver])
57
+
58
+ # Set up summary objects
59
+ total_summary = {
60
+ :reqs_attempted => 0,
61
+ :reqs_completed => 0,
62
+ :latencies => [],
63
+ }
64
+ node_summary = {}
65
+ nodes.each { |node| node_summary[node] = total_summary.clone }
66
+
67
+ # Catch ctrl-c
68
+ trap("INT") {
69
+ ElbPing::Display.summary(total_summary, node_summary)
70
+ exit
71
+ }
72
+
73
+ (1..OPTIONS[:count]).each { |i|
74
+ sleep OPTIONS[:wait] if i > 1
75
+
76
+ nodes.map { |node|
77
+ total_summary[:reqs_attempted] += 1
78
+ node_summary[node][:reqs_attempted] += 1
79
+ status = ElbPing::HttpPinger.ping_node(node, OPTIONS[:verb_len], OPTIONS[:timeout])
80
+
81
+ unless status[:code] == :timeout
82
+ total_summary[:reqs_completed] += 1
83
+ total_summary[:latencies] += [status[:duration]]
84
+ node_summary[node][:reqs_completed] += 1
85
+ node_summary[node][:latencies] += [status[:duration]]
86
+ end
87
+
88
+ ElbPing::Display.response(status)
89
+ }
90
+ }
91
+ ElbPing::Display.summary(total_summary, node_summary)
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,43 @@
1
+
2
+ module ElbPing
3
+ module Display
4
+ # Format and display the ping data
5
+ def self.response(status)
6
+ node = status[:node]
7
+ code = status[:code]
8
+ duration = status[:duration]
9
+
10
+ puts "Response from #{node}: code=#{code.to_s} time=#{duration} ms"
11
+ end
12
+
13
+ # Display summary of results (in aggregate and per-node)
14
+ def self.summary(total_summary, node_summary)
15
+ requests = total_summary[:reqs_attempted]
16
+ responses = total_summary[:reqs_completed]
17
+ loss = (1 - (responses.to_f/requests)) * 100
18
+
19
+ latencies = total_summary[:latencies]
20
+ avg_latency = (latencies.inject { |sum, el| sum + el }.to_f / latencies.size).to_i # ms
21
+
22
+ node_summary.each { |node, summary|
23
+ requests = summary[:reqs_attempted]
24
+ responses = summary[:reqs_completed]
25
+ loss = (1 - (responses.to_f/requests)) * 100
26
+
27
+ latencies = summary[:latencies]
28
+ avg_latency = (latencies.inject { |sum, el| sum + el }.to_f / latencies.size).to_i # ms
29
+
30
+ puts "--- #{node} statistics ---"
31
+ puts "#{requests} requests, #{responses} responses, #{loss.to_i}% loss"
32
+ puts "min/avg/max = #{latencies.min}/#{avg_latency}/#{latencies.max} ms"
33
+ }
34
+
35
+ puts '--- total statistics ---'
36
+ puts "#{requests} requests, #{responses} responses, #{loss.to_i}% loss"
37
+ puts "min/avg/max = #{latencies.min}/#{avg_latency}/#{latencies.max} ms"
38
+
39
+ end
40
+
41
+ end
42
+ end
43
+
@@ -0,0 +1,31 @@
1
+
2
+ require "net/http"
3
+
4
+ module ElbPing
5
+ module HttpPinger
6
+ # Make HTTP request to given node using custom request method
7
+ def self.ping_node(node, verb_len, timeout, port=80, path="/")
8
+
9
+ ping_request = Class.new(Net::HTTPRequest) do
10
+ const_set :METHOD, "A" * verb_len
11
+ const_set :REQUEST_HAS_BODY, false
12
+ const_set :RESPONSE_HAS_BODY, false
13
+ end
14
+
15
+ start = Time.now.getutc
16
+ http = Net::HTTP.new(node, port.to_s)
17
+ http.open_timeout = timeout
18
+ http.read_timeout = timeout
19
+ http.continue_timeout = timeout
20
+ http.ssl_timeout = timeout # untested
21
+
22
+ error = nil
23
+ response = http.request(ping_request.new(path)) rescue error = :timeout
24
+
25
+ {:code => error || response.code,
26
+ :node => node,
27
+ :duration => ((Time.now.getutc - start) * 1000).to_i} # returns in ms
28
+ end
29
+ end
30
+ end
31
+
@@ -0,0 +1,26 @@
1
+
2
+ require 'net/dns'
3
+
4
+ module ElbPing
5
+ module Resolver
6
+ # Resolve an ELB address to a list of node IPs
7
+ def self.find_elb_nodes(target, nameserver)
8
+
9
+ # First resolve our nameserver IP
10
+ ns_addrs = Resolver(nameserver).answer.map { |rr| rr.address.to_s }
11
+
12
+ # Now resolve our ELB nodes
13
+ resolver = Net::DNS::Resolver.new(
14
+ :use_tcp => true,
15
+ :nameservers => ns_addrs,
16
+ :retry => 5)
17
+
18
+ resp = resolver.query(target, Net::DNS::ANY)
19
+
20
+ nodes = []
21
+ resp.each_address { |a| nodes += [a.to_s] }
22
+ nodes
23
+ end
24
+ end
25
+ end
26
+
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: elbping
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Charles Hooper
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: net-dns
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 0.8.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 0.8.0
30
+ description: elbping is a tool to ping all of the nodes behind an Amazon Elastic Load
31
+ Balancer. It only works for ELBs in HTTP mode and works by triggering an HTTP 405
32
+ (METHOD NOT ALLOWED) error caused when the ELB receives a HTTP verb that is too
33
+ long.
34
+ email: chooper@plumata.com
35
+ executables:
36
+ - elbping
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - lib/elbping/cli.rb
41
+ - lib/elbping/display.rb
42
+ - lib/elbping/pinger.rb
43
+ - lib/elbping/resolver.rb
44
+ - bin/elbping
45
+ homepage: https://github.com/chooper/elbping
46
+ licenses:
47
+ - MIT
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.25
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Small tool to 'ping' the nodes that make up an Amazon Elastic Load Balancer
70
+ test_files: []