Skip to content

Instantly share code, notes, and snippets.

@csabahenk
Last active December 2, 2016 14:48
Show Gist options
  • Save csabahenk/06e295f69b81f6a74c005aab237f3db6 to your computer and use it in GitHub Desktop.
Save csabahenk/06e295f69b81f6a74c005aab237f3db6 to your computer and use it in GitHub Desktop.
gluster volfile parser / extractor
#!/usr/bin/env ruby
module GlusterConf
extend self
class GlusterConfError < Exception
end
def assert x, *bool
bool.each { |b| raise GlusterConfError, "#{$.}: #{x}" unless bool }
end
LOGSEP = /\A\+---/
IDP = proc {|x| x}
# extract conf from logs
def preparse src
vols = []
in_vol = nil
is_log = nil
preprocess = nil
src.each { |l|
(preprocess || IDP)[l] =~ /\A\s*\Z/ and next
if in_vol
if is_log and l =~ LOGSEP
in_vol = false
else
vols.last << preprocess[l]
end
else
if l =~ /\A\s*(volume|type|option|subvolumes|end-volume)(\s|\Z)/
in_vol == false and raise "mixed source"
in_vol = true
preprocess = IDP
is_log = false
vols << [l]
else
is_log = true
preprocess ||= proc { |x| x.sub /\A\s*\d+: /, "" }
if l =~ LOGSEP
in_vol = true
vols << []
end
end
end
}
vols
end
def parse src
volh = {}
vol = {}
src.each { |l|
ls = l.split
case ls[0]
when "volume"
assert l, ls.size == 2, vol.empty?
vol["name"] = ls[1]
when "type"
assert l, ls.size == 2, vol["name"], !vol["type"]
vol["type"] = ls[1]
when "option"
assert l, ls.size == 3, vol["name"]
(vol["option"] ||= {})[ls[1]] = case ls[2]
when /\A\d+\Z/
Integer ls[2]
when /\Aon|yes|true|enable\Z/i
true
when /\Aoff|no|false|disable\Z/i
false
else
ls[2]
end
when "subvolumes"
assert l, ls.size > 1, vol["name"], !vol["subvolumes"]
vol["subvolumes"] = ls[1..-1]
when "end-volume"
assert l, ls.size == 1, vol["name"], vol["type"]
volh[vol["name"]] = vol
vol = {}
when nil
else
assert l, false
end
}
# XXX we could have more proper check to see if graph is a tree
subvolumes = []
volh.each_value { |vol| subvolumes.concat(vol["subvolumes"] || []) }
topvols = volh.keys - subvolumes
assert "ambiguous toplevel volume: #{topvols.join(", ")}", topvols.size == 1
topvol = volh[topvols[0]]
volh.each_value { |vol| (vol["subvolumes"] || []).map! { |w| volh.fetch w } }
topvol
end
def summarize vol, perf=[], cluster={"distribute"=>0, "replicate"=>0}
xclass, xtype = vol["type"].split("/")
case xclass
when "performance"
perf << xtype
when "cluster"
cluster[xtype] = vol["subvolumes"].size
end
(vol["subvolumes"]||[]).each { |sv| summarize sv, perf, cluster }
{performance: perf, cluster: cluster}
end
DEFAULTFIELDS = %w[distribute replicate write-behind readdir-ahead quick-read md-cache perf-other]
def report summary, fields=nil
fields ||= DEFAULTFIELDS
perf_hot = []
perf_other = ""
rep = fields.map { |n|
case n
when "distribute", "replicate"
summary[:cluster][n]
when "perf-other"
perf_other
else
perf_hot << n
summary[:performance].include?(n) ? '✓' : '✖'
end
}
perf_other << (summary[:performance] - perf_hot).reverse.join(" ")
rep
end
end
if __FILE__ == $0
require 'yaml'
require 'csv'
require 'optparse'
fmt = 'yaml'
cols = nil
showcols = false
op = OptionParser.new
op.banner << " [<volfile>|<logfile>]..."
op.on("-f", "--format FMT", "output format (csv or yaml)") { |v|
%w[csv yaml].include? v or raise "Unknown format #{v}"
fmt = v
}
op.on("--columns COLS", Array,
"comma-separated column names for CSV (one of #{GlusterConf::DEFAULTFIELDS.join(",")})") { |v|
cols = v
}
op.on("--show-columns[=bool]", TrueClass, "show colums used for CSV output") { |v| showcols = v }
op.parse!
showcols and puts (cols || GlusterConf::DEFAULTFIELDS).join(",")
$*.each { |f|
GlusterConf.module_eval {
preparse(f == "-" ? STDIN : IO.readlines(f)).each { |v|
vol = parse v
print case fmt
when "yaml"
vol.to_yaml
when "csv"
report(summarize(vol), cols).to_csv
end
}
}
}
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment