elbping 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/bin/elbping +12 -0
- data/lib/elbping/cli.rb +94 -0
- data/lib/elbping/display.rb +43 -0
- data/lib/elbping/pinger.rb +31 -0
- data/lib/elbping/resolver.rb +26 -0
- metadata +70 -0
data/bin/elbping
ADDED
data/lib/elbping/cli.rb
ADDED
@@ -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: []
|