#!/usr/pkg/bin/ruby require 'rubygems' require 'gruff' require 'sqlite3' require 'RMagick' require 'getoptlong' require 'date' class Kunabi # attr_accessor :db def initialize @home = "/home/ober/kunabi" @dbfile = "#{@home}/kunabi.db" @db = SQLite3::Database.new(@dbfile) @date = Time.now.strftime("%Y-%m-%d") @time = Time.now.strftime("%H:%M") @mode = nil @wait = 0 @thumbnails = nil @loop = nil @index = nil @relative_time = nil @event_name = nil end def verify_table(table) schema = @db.execute("select sql from sqlite_master where type = 'table' and name = \'#{table}\'") if schema.empty? puts "#{table} does not exist. creating" @db.execute("create table #{table}(epoch bigint not null)") end rescue SQLite3::SQLException => e puts "Sqlite: #{e}" end def plot_pstat metrics = [ 'files_open','files_total','vnodes','swap_blocks_used','swap_blocks_total'] metrics.each { |m| g = Gruff::Line.new g.title = "Pstat: #{m}" values_array = Array.new epoch_array = Array.new if @event_name rows = @db.execute("select epoch,#{m} from pstat where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch,#{m} from pstat order by epoch") end rows.each { |x| epoch_array << x[0].to_i values_array << x[1].to_i } g.labels = generate_glabel(epoch_array) g.minimum_value = 0 g.y_axis_label = "1 minute rollup" g.maximum_value = values_array.max g.data(m,values_array) g.write("#{@home}/pstat_#{m}.png") make_thumbnail("#{@home}/pstat_#{m}.png","#{@home}/thumb_pstat_#{m}.png",0.10) } rescue Exception => e puts "ErrorPlotPstat: #{e}" end def make_thumbnail(source_img,thumb_img,reduction_pct) if @thumbnail img = Magick::Image.read(source_img).first thumbnail = img.thumbnail(img.columns*reduction_pct, img.rows*reduction_pct) thumbnail.write(thumb_img) end end def plot_netstat g = Gruff::Line.new g.title = "NetStat" values = Array.new if @event_name rows = @db.execute("select epoch, inpackets, outpackets from netstat where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch, inpackets, outpackets from netstat order by epoch") end epoch_array = Array.new inpackets_array = Array.new outpackets_array = Array.new rows.each { |x| epoch_array << x[0].to_i inpackets_array << x[1].to_i outpackets_array << x[2].to_i } g.labels = generate_glabel(epoch_array) outpackets_array = calculate_deltas(outpackets_array) inpackets_array = calculate_deltas(inpackets_array) g.minimum_value = 0 g.maximum_value = [inpackets_array,outpackets_array].map { |a| a.max}.max g.data("Packets Out",outpackets_array) g.data("Packets In",inpackets_array) g.y_axis_label = "1 minute rollup" g.write("#{@home}/netstat.png") make_thumbnail("#{@home}/netstat.png","#{@home}/thumb_netstat.png",0.10) rescue Exception => e puts "ErrorPlotNetStat: #{e}" end def handle_args opts = GetoptLong.new( [ "--delete", "-d", GetoptLong::OPTIONAL_ARGUMENT ], [ "--collect", "-c", GetoptLong::OPTIONAL_ARGUMENT ], [ "--event", "-e", GetoptLong::REQUIRED_ARGUMENT ], [ "--plot", "-p", GetoptLong::OPTIONAL_ARGUMENT ], [ "--relative", "-r", GetoptLong::OPTIONAL_ARGUMENT ], [ "--index", "-i", GetoptLong::OPTIONAL_ARGUMENT ], [ "--loop", "-l", GetoptLong::REQUIRED_ARGUMENT ], [ "--thumb", "-t", GetoptLong::OPTIONAL_ARGUMENT ], [ "--wait", "-w", GetoptLong::REQUIRED_ARGUMENT ] ) opts.each do |opt,arg| case opt when /-r/ @relative_time = true when /-c/ @mode = "collect" when /-p/ @mode = "plot" when /-i/ @index = true when /-l/ @loop = arg.to_i when /-t/ @thumbnail = true when /-e/ @event_name = arg when /-d/ puts "removing #{@dbfile}" File.unlink(@dbfile) when /-w/ @wait = arg.to_i end end end def run_collectors collect_vmstat_s collect_netstat_s collect_pstat end def run_plotters plot_vmstat_s plot_netstat_s #plot_pstat # plot_vmstat_procs # plot_vmstat_memory # plot_vmstat_faults # plot_netstat end def main handle_args case @mode when "collect" if !@loop.nil? i = 0 while i < @loop run_collectors sleep(@wait) i = i + 1 end end run_collectors when "plot" run_plotters else puts "hmm we got nothing" end if @index generate_index end rescue Exception => e puts "ErrorMain: #{e}" end def collect_pstat values_hash = Hash.new pstat = %x{/usr/sbin/pstat -T} pstat.each { |x| if /files/.match(x) line = x.split() values_hash['files_open'] = x.split()[0].split("/")[0] values_hash['files_total'] = x.split()[0].split("/")[1] end if /vnodes/.match(x) values_hash['vnodes'] = x.split()[0] end if /blocks swap space/.match(x) line = x.split() values_hash['swap_blocks_used'] = x.split()[0].split("/")[0] values_hash['swap_blocks_total'] = x.split()[0].split("/")[1] end # metrics = [ 'filesopen','filestotal','vnodes','swapblocksused','swapblockstotal'] } fields = "epoch," values = "'#{Time.now.to_i}'," if @event_name fields << "event_name," values << "'#{@event_name}'," end values_hash.each { |x,y| verify_field_exists('pstat',x) fields << "#{x}," values << "#{y}," } sql = "insert into pstat(#{fields}) values(#{values})".gsub("\,)",')') @db.execute(sql) end def collect_pstat values_hash = Hash.new pstat = %x{/usr/sbin/pstat -T} pstat.each { |x| if /files/.match(x) line = x.split() values_hash['files_open'] = x.split()[0].split("/")[0] values_hash['files_total'] = x.split()[0].split("/")[1] end if /vnodes/.match(x) values_hash['vnodes'] = x.split()[0] end if /blocks swap space/.match(x) line = x.split() values_hash['swap_blocks_used'] = x.split()[0].split("/")[0] values_hash['swap_blocks_total'] = x.split()[0].split("/")[1] end # metrics = [ 'filesopen','filestotal','vnodes','swapblocksused','swapblockstotal'] } fields = "epoch," values = "'#{Time.now.to_i}'," values_hash.each { |x,y| verify_field_exists('pstat',x) fields << "#{x}," values << "#{y}," } sql = "insert into pstat(#{fields}) values(#{values})".gsub("\,)",')') @db.execute(sql) end def plot_vmstat_s metrics = @db.execute("select sql from sqlite_master where type = 'table' and name = 'vmstat_s'").to_s.gsub("bigint not null default 0",'').gsub("CREATE TABLE vmstat_s(epoch bigint not null,",'').gsub(" ",'').gsub(')','').split(",") metrics.each do |m| if !/event_name/.match(m) g = Gruff::Line.new g.title = "Vmstat_s: #{m}" values_array = Array.new epoch_array = Array.new if @event_name rows = @db.execute("select epoch,#{m} from vmstat_s where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch,#{m} from vmstat_s order by epoch") end rows.each do |x| epoch_array << x[0].to_i values_array << x[1].to_i end g.labels = generate_glabel(epoch_array) values_array = calculate_deltas(values_array) g.y_axis_label = "1 minute rollup" g.title_font_size = 20 g.minimum_value = 0 g.maximum_value = values_array.max g.data(m,values_array) g.write("#{@home}/vmstat_s_#{m}.png") make_thumbnail("#{@home}/vmstat_s_#{m}.png", "#{@home}/thumb_vmstat_s_#{m}.png",0.10) end end rescue Exception => e puts "ErrorPlotVmstatS: #{e}" end def plot_netstat_s metrics = @db.execute("select sql from sqlite_master where type = 'table' and name = 'netstat_s'").to_s.gsub("bigint not null default 0",'').gsub("CREATE TABLE netstat_s(epoch bigint not null,",'').gsub(" ",'').gsub(')','').split(",") metrics.each do |m| if !/event_name/.match(m) g = Gruff::Line.new g.title = "Netstat -s: #{m}" values_array = Array.new epoch_array = Array.new if @event_name rows = @db.execute("select epoch,#{m} from netstat_s where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch,#{m} from netstat_s order by epoch") end rows.each do |x| epoch_array << x[0].to_i values_array << x[1].to_i end g.labels = generate_glabel(epoch_array) values_array = calculate_deltas(values_array) g.y_axis_label = "1 minute rollup" g.title_font_size = 20 g.minimum_value = 0 g.maximum_value = values_array.max g.data(m,values_array) g.write("#{@home}/netstat_s_#{m}.png") make_thumbnail("#{@home}/netstat_s_#{m}.png", "#{@home}/thumb_netstat_s_#{m}.png",0.10) end end rescue Exception => e puts "ErrorNetstat: #{e}" end def calculate_deltas(array) old_array = array new_array = Array.new previous_value = nil old_array.each { |x| if previous_value.nil? new_array << 0 previous_value = x.to_i else if x.to_i < previous_value.to_i new_array << 0 previous_value = x.to_i else new_array << x.to_i - previous_value.to_i previous_value = x.to_i end end } return new_array rescue Exception => e puts "ErrorCalculateDeltas: #{e}" end def print_date(epoch,format) #DateTime::parse("epoch") return Time.at(epoch).strftime(format) end def produce_relative_times(epoch_array) if epoch_array.empty? puts "produce_relative_times: Empty epoch_array passed" else relative_array = Array.new min_value = epoch_array.min epoch_array.sort.each do |x| relative_array << (x.to_i - min_value)/60 end return relative_array end end def generate_glabel(epoch_array) if !epoch_array.empty? glabel_hash = Hash.new if epoch_array.size < 6 mod_amount = 1 else mod_amount = epoch_array.size / 6 end epoch_diff = epoch_array.max - epoch_array.min if epoch_diff > 86400 epoch_format = "%M%d" else epoch_format = "%H:%M" end i = 0 if @relative_time epoch_array = produce_relative_times(epoch_array) end epoch_array.each do |x| #puts "X:#{print_date(x,"%H:%M:%S")}" if @relative_time date_output = "#{x.to_s}min" else date_output = print_date(x,epoch_format) end if i % mod_amount == 0 glabel_hash.merge!({i => date_output}) end i = i + 1 end #puts glabel_hash.inspect return glabel_hash else puts "Empty array passed to generate_glabel!" end rescue Exception => e puts "ErrorGenerateGlabel: #{e}" end def plot_vmstat_procs() g = Gruff::Line.new g.title = "Vmstat_procs" if @event_name rows = @db.execute("select epoch, r,b,w from vmstat where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch, r,b,w from vmstat order by epoch") end values = Array.new epoch_array = Array.new r_array = Array.new b_array = Array.new w_array = Array.new rows.each { |x| epoch_array << x[0].to_i r_array << x[1].to_i b_array << x[2].to_i w_array << x[3].to_i } g.minimum_value = 0 g.maximum_value = [r_array,b_array,w_array].map { |x| x.max}.max g.labels = generate_glabel(epoch_array) g.data("RunQ",r_array) g.data("Blocked",b_array) g.data("Swapped",w_array) g.write("#{@home}/vmstat_procs.png") make_thumbnail("#{@home}/vmstat_procs.png","#{@home}/thumb_vmstat_procs.png",0.10) rescue Exception => e puts "Error: #{e}" end def plot_vmstat_faults() g = Gruff::Line.new g.title = "Vmstat_faults" if @event_name rows = @db.execute("select epoch, fin, fsy, cs from vmstat where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch, fin, fsy, cs from vmstat order by epoch") end values = Array.new epoch_array = Array.new fin_array = Array.new fsy_array = Array.new cs_array = Array.new rows.each { |x| epoch_array << x[0].to_i fin_array << x[1].to_i fsy_array << x[2].to_i cs_array << x[3].to_i } g.minimum_value = 0 g.maximum_value = [fin_array,fsy_array,cs_array].map { |a| a.max}.max g.labels = generate_glabel(epoch_array) g.data("Interrupts",fin_array) g.data("Syscalls",fin_array) g.data("Context Switches",cs_array) g.y_axis_label = "1 minute rollup" g.write("#{@home}/vmstat_faults.png") make_thumbnail("#{@home}/vmstat_faults.png","#{@home}/thumb_vmstat_faults.png",0.10) rescue Exception => e puts "Error: #{e}" end def plot_vmstat_memory() g = Gruff::Line.new g.title = "Vmstat_procs" if @event_name rows = @db.execute("select epoch,avm,fre from vmstat where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch,avm,fre from vmstat order by epoch") end values = Array.new epoch_array = Array.new active_array = Array.new freelist_array = Array.new rows.each { |x| epoch_array << x[0].to_i active_array << x[1].to_i freelist_array << x[2].to_i } g.minimum_value = 0 g.maximum_value = active_array.max g.labels = generate_glabel(epoch_array) g.data("Active",active_array) g.data("Freelist",freelist_array) g.y_axis_label = "1 minute rollup" g.write("#{@home}/vmstat_memory.png") make_thumbnail("#{@home}/vmstat_memory.png","#{@home}/thumb_vmstat_memory.png",0.10) rescue Exception => e puts "Error: #{e}" end def plot_vmstat_cpu() g = Gruff::Line.new g.title = "Vmstat_procs" if @event_name rows = @db.execute("select epoch,us,csy from vmstat where event_name = '#{@event_name}' order by epoch") else rows = @db.execute("select epoch,us,csy from vmstat order by epoch") end values = Array.new epoch_array = Array.new us_array = Array.new csy_array = Array.new rows.each { |x| epoch_array << x[0].to_i us_array << x[1].to_i csy_array << x[2].to_i } g.minimum_value = 0 g.maximum_value = active_array.max g.labels = generate_glabel(epoch_array) g.data("User",us_array) g.data("System",csy_array) g.y_axis_label = "1 minute rollup" g.write("#{@home}/vmstat_cpu.png") make_thumbnail("#{@home}/vmstat_cpu.png","#{@home}/thumb_vmstat_cpu.png",0.10) rescue Exception => e puts "Error: #{e}" end def verify_field_exists(table,field) verify_table(table) schema = @db.execute("select sql from sqlite_master where type = 'table' and name = '#{table}'")[0].to_s if !/#{field}/.match(schema) puts "#{field} is MISSING in schema!" @db.execute("alter table #{table} add column #{field} bigint not null default 0") end if @event_name if !/event_name/.match(schema) @db.execute("alter table #{table} add column event_name varchar(255)") end end rescue Exception => e puts "Error: #{e}" end def sanitize_field_names(field) if /\(/.match(field.to_s) a = field.to_s.split("(")[0] b = field.to_s.split("(")[1].split(")")[1] field = "#{a} #{b}".gsub(/_$/,'') end if /^[0-9]/.match(field.to_s) field = "v" + field.to_s end if /_$/.match(field.to_s) field = field.to_s.gsub(/_$/,'') end if />/.match(field.to_s) field = field.to_s.gsub(/>/,'greater_than') end if /"}' > index.html} end def collect_netstat_s() netstat_s = %x{/usr/bin/netstat -s} header = nil fields = "epoch," values = "'#{Time.now.to_i}'," if @event_name fields << "event_name," values << "'#{@event_name}'," end netstat_s.each { |x| line = x.split value = line[0] if !value.nil? and /^[0-9]+$/.match(value) line.shift field = "" line.each { |l| field << "#{l}_"} field = field.gsub(/_$/,'') field = "#{header}_#{field}" if header field = sanitize_field_names(field) verify_field_exists('netstat_s',field) fields << "'#{field}'," values << "'#{value}'," else header = line[0].gsub(/:/,'') if line[0] end } sql = "insert into netstat_s(#{fields}) values(#{values});".gsub("\,)",')') @db.execute(sql) rescue SQLite3::SQLException => e puts "Sqlite: #{e}" end end foo = Kunabi.new foo.main