Skip to content

Instantly share code, notes, and snippets.

@Nephos
Created April 12, 2017 23:49
Show Gist options
  • Save Nephos/0dc9cab70a6649ab9d345b98627bc716 to your computer and use it in GitHub Desktop.
Save Nephos/0dc9cab70a6649ab9d345b98627bc716 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby
# script pingtracer 0.1
# written by poulet_a
class PingError < StandardError; end
class ParseError < PingError; end
class Ping
ATTRIBUTES = [:size, :ip, :seq, :ttl, :time]
attr_reader :size, :ip, :seq, :ttl, :time
def initialize line
Ping::parse(line).each do |k, v|
instance_variable_set "@#{k}", v
end
end
def self.parse(line)
raise ArgumentError, "`line` must be a String" unless line.is_a? String
raise ParseError, "`#{line}` is not a valid ping line" unless m = line.match(/^(?<size>\d+) bytes from (?<ip>(\d{1,3}\.){3}\d{1,3}): icmp_seq=(?<seq>\d+) ttl=(?<ttl>\d+) time=(?<time>\d+(\.\d+)?) ms/)
{
size: m["size"].to_i,
ip: m["ip"].to_s,
seq: m["seq"].to_i,
ttl: m["ttl"].to_i,
time: m["time"].to_f,
}
end
def to_s
"#{@size} bytes from #{@ip}: icmp_seq=#{@seq} ttl=#{@ttl} time=#{@time} ms"
end
def to_json
{size: @size, ip: @ip, seq: @seq, ttl: @ttl, time: @time}
end
def to_csv
"\"#{@size}\",\"#{@ip}\",\"#{@seq}\",\"#{@ttl}\",\"#{@time}\""
end
def self.stats(pings, round=4)
pings.map!{|e| e.time }
pings.sort!
{
mean: (pings.inject(&:+) / pings.size.to_f).round(round),
q25: (pings[(pings.size * 0.25).ceil]),
q50: (pings[(pings.size * 0.50).ceil]),
q75: (pings[(pings.size * 0.75).ceil]),
q95: (pings[(pings.size * 0.95).ceil]),
q99: (pings[(pings.size * 0.99).ceil]),
}
end
end
# Usage:
# ping ip | pingtracer <to_csv> > ping_stats
if __FILE__ == $0
require 'optparse'
$opts = OptionParser.new do |opts|
$format = "json"
opts.banner = "Usage: ping ip.ip.ip.ip -i '1' | #{$0} <options> \n\n" \
"Example: #{$0} --to_json --stats\n\n" \
opts.on("--stats", "-s") do
$stats = true
end
opts.on("--to_json", "--json") do
$format = "json"
end
opts.on("--to_csv", "--csv") do
$format = "csv"
end
end.parse!
if $format == "csv" && $stats == true
STDERR.puts "Invalid option: --to_csv and --stats are not compatible"
end
STDOUT.sync = true
STDERR.sync = true
pings = []
i = 0
STDOUT.puts "size,ip,seq,ttl,time" if $format == "csv"
trap("SIGINT") { STDOUT.puts Ping.stats(pings) if $stats == true }
loop do
i += 1
begin
str = STDIN.gets.to_s.chomp
next if str.match(/^PING .+/) # ignore the first line
ping = Ping.new(str)
pings << ping
i += 1
STDOUT.puts ping.send("to_#{$format}")
STDERR.puts "#{i} pings for now" if ((i < 100 && (i % 10 == 0)) || (i < 10000 && (i % 100 == 0)) || (i % 10000 == 0))
rescue PingError => e
# stop if the ping failed !
STDERR.puts e.message
break
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment