Skip to content

Commit

Permalink
v0.4.14 - iostat-portability
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Wilhelm committed Dec 12, 2013
2 parents 845d67c + bd67e4a commit 4d3cc0e
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 48 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# 0.4.14

* collectors/iostat - portability: parsing solaris output

# 0.4.13

* collectors/load - portability: parsing `uptime`
Expand Down
12 changes: 12 additions & 0 deletions collectors/iostat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Description

Reports average disk statistics over the configured `interval`.

Omits the first output of `iostat`, which contains all-time average
(since boot.) This causes a delay in the first report. This collector
is persistent.

# Portability

On solaris, uses `iostat -xne`, Linux uses `iostat -xd`. The available
metrics vary.
186 changes: 139 additions & 47 deletions collectors/iostat/iostat
Original file line number Diff line number Diff line change
@@ -1,55 +1,147 @@
#!/usr/bin/env ruby

$stdout.sync = true # persistent process
module Panoptimon
module Collector
class IOStat

require 'json'
opt = ARGV[0] ? JSON::parse(ARGV[0]) : {'interval' => 1, 'count' => 2}
attr_reader :iostat, :flags, :opt

cmd = %w{iostat -xd}
if opt['interval']
cmd.push(opt['interval'].to_s)
cmd.push(opt['count'].to_s) if opt['count']
end
p = IO.popen(cmd, 'r')

[1,2].each { x = p.readline until x == "\n" } # header + first sample

puts '{}' # beep

def parse_header l
want = {
'kb_read/s' => 'rkB/s',
'kb_write/s' => 'wkB/s',
'rrqm/s' => 'rrqm/s',
'wrqm/s' => 'wrqm/s',
'reads/s' => 'r/s',
'writes/s' => 'w/s',
'avgrq-sz' => 'avgrq-sz',
'avgqu-sz' => 'avgqu-sz',
'await' => 'await',
'r_await' => 'r_await',
'w_await' => 'w_await',
'util' => '%util',
}
head = l.chomp.split(/\s+/)
head = Hash[*head.zip(0..(head.length-1)).flatten]
want.values.find_all{|x| not(head[x])}.tap {|missed|
warn "missing headers: "+ missed.join(', ') if missed.length > 0
}
o = {}; want.each {|k,v| o[k] = head[v] if head[v]}
return o
end
def initialize (options={})
@opt = options
@iostat = opt[:iostat] || 'iostat'
end

def cmd
cmd = [iostat, flags]

if opt[:interval]
cmd.push(opt[:interval].to_s)
cmd.push(opt[:count].to_s) if opt[:count] # just for debugging
end

return cmd
end

def parse_header (l)
head = l.chomp.split(/\s+/)
head = Hash[*head.zip(0..(head.length-1)).flatten]
want.values.find_all{|x| not(head[x])}.tap {|missed|
warn "missing headers: "+ missed.join(', ') if missed.length > 0
}
o = {}; want.each {|k,v| o[k] = head[v] if head[v]}
return o
end

def run
p = IO.popen(cmd, 'r')
if p.eof?
raise $?;
end
cue(p)

puts '{}' # beep

omap = nil
device_idx = nil

until p.eof?
l = getlines(p)

# TODO something less sprawled
omap ||= parse_header(l[0])
device_idx ||= omap.delete(:device)

omap = nil
group = {}
puts JSON::generate(Hash[*l.drop(1).map {|x|
r = x.split(/\s+/)
[r[device_idx],
Hash[*omap.keys.map {|k| [k, r[omap[k]].to_f]}.flatten]]
}.flatten])

until p.eof?
l = p.readline("\n\n").split(/\n/)
omap ||= parse_header(l[0])
puts JSON::generate(Hash[*l.drop(1).map {|x|
r = x.split(/\s+/)
[r[0], # device name
Hash[*omap.keys.map {|k| [k, r[omap[k]].to_f]}.flatten]]
}.flatten])
end
end

class OS_linux < Panoptimon::Collector::IOStat
def flags; '-xd' ; end

def cue (p)
2.times { x = p.readline until x == "\n" } # header + first sample
end

def getlines (p)
p.readline("\n\n").split(/\n/)
end

def want
{ device: 'Device:',
'kb_read/s' => 'rkB/s',
'kb_write/s' => 'wkB/s',
'rrqm/s' => 'rrqm/s',
'wrqm/s' => 'wrqm/s',
'reads/s' => 'r/s',
'writes/s' => 'w/s',
'avgrq-sz' => 'avgrq-sz',
'avgqu-sz' => 'avgqu-sz',
'await' => 'await',
'r_await' => 'r_await',
'w_await' => 'w_await',
'util' => '%util',
}
end

end
class OS_freebsd < Panoptimon::Collector::IOStat::OS_linux; end
class OS_solaris < Panoptimon::Collector::IOStat
def flags; '-xne' ; end

def cue (p)
2.times { x = p.readline until x =~ /^\s*extended / }
end

def getlines (p)
lines = []
while line = p.gets
return lines if line =~ /^\s* extended /
lines << line.chomp
end
# eof?
return lines
end

def want
{ device: 'device',
'kb_read/s' => 'kr/s',
'kb_write/s' => 'kw/s',
'reads/s' => 'r/s',
'writes/s' => 'w/s',
'wait' => 'wait',
'wsvc_t' => 'wsvc_t',
'asvc_t' => 'asvc_t',
'pct_wait' => '%w',
'pct_busy' => '%b',
'errors' => 'tot',
}
end
end
class OS_openbsd < Panoptimon::Collector::IOStat
# default output format, -w required
# TODO reimplement cmd -- will need -w before count
# TODO reimplement run -- parsing column-wise
end
end
end
end

########################################################################
require 'panoptimon/util'

$stdout.sync = true # persistent process

require 'json'
opt = ARGV[0] ?
JSON::parse(ARGV[0], {symbolize_names: true})
: {interval: 1, count: 2}

os_class = Panoptimon::Collector::IOStat.const_get(
'OS_' + Panoptimon::Util.os.to_s)
os_class.new(opt).run

2 changes: 1 addition & 1 deletion lib/panoptimon/version.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Copyright (C) 2012 Sourcefire, Inc.

module Panoptimon
VERSION = "0.4.13"
VERSION = "0.4.14"
end

0 comments on commit 4d3cc0e

Please sign in to comment.