#!/usr/bin/env ruby require 'sqlite3' class App DB_FILE='metrics.db' class Metric def initialize row @center_id, @metric, @last_zero_time, @last_nonzero_time, @low1, @high1, @low3, @high3, @low24, @since_low1, @since_high1, @since_low3, @since_high3, @since_low24 = row @values = [] @v1 = @v3 = @v24 = 0 end attr_reader :metric, :low24 def tdif sym, a, b d = (b-a)/3600.0 if d > 36 then sprintf("%s %ud", sym, (d/24.0).round) else sprintf("%s %uh", sym, d.round) end end def alerting? [@since_low1, @since_low3, @since_high1, @since_high3, @since_low24].any? end def alert lastts buf=[] buf << tdif('low1', @since_low1, lastts) if @since_low1 buf << tdif('low3', @since_low3, lastts) if @since_low3 buf << tdif('high1', @since_high1, lastts) if @since_high1 buf << tdif('high3', @since_high3, lastts) if @since_high3 buf << tdif('low24', @since_low24, lastts) if @since_low24 if buf.empty? then nil else "#{metric} #{buf.join(',')}" end end def addval v return if @values.size >= 24 @values.push v @v24 += v return if @values.size > 3 @v3 += v return if @values.size > 1 @v1 = v end def gt a, b if a > b then ">" else " " end end def numtab sprintf("%14s|%5u%s%5u%s%5u|%5u%s%5u%s%5u|%5u%s%5u", @metric, @low1.round, gt(@low1,@v1), @v1.round, gt(@v1,@high1), @high1.round, @low3.round, gt(@low3,@v3), @v3.round, gt(@v3,@high3), @high3.round, @low24.round, gt(@low24,@v24), @v24.round) end def self.numtab s=sprintf("%14s|%5s %5s %5s|%5s %5s %5s|%5s %5s", 'metric', 'low', '1hr', 'high', 'low', '3hr', 'high', 'low', '24hr') s.gsub(/ /,'_') end def dump sprintf("%s=%s;", @metric, @values.reverse.join(' ')) end end class Center def initialize center_id @center_id = center_id @mdb=Hash.new end def register_metric mobj @mdb[mobj.metric]=mobj end def addval row c,m,ts,v = row return unless @mdb[m] @mdb[m].addval(v) end def alert lastts buf = @mdb.values.map{|mobj| mobj.alert(lastts)}.compact if buf.empty? then nil else "ALERT(#{buf.join('/')})" end end def alerting? @mdb.values.map{|mobj| mobj.alerting?}.any? end def all_silent? @mdb.values.map{|mobj| mobj.low24 == 0}.all? end def summary lastts r = [] amsg = alert(lastts) r << amsg if amsg if all_silent? then r << "(silent centre)" else r << Metric::numtab r << @mdb['files_received'].numtab r << @mdb['errors'].numtab if @mdb['errors'].low24 > 0 r << @mdb['invalid'].numtab if @mdb['invalid'].low24 > 0 r << @mdb['bulletins'].numtab if @mdb['bulletins'] r << @mdb['stored'].numtab if @mdb['stored'].alerting? r << @mdb['not_stored'].numtab if @mdb['not_stored'].alerting? r << '' if self.alerting? %w(files_received errors invalid stored not_stored bulletins).each{|mn| r << @mdb[mn].dump if @mdb[mn] and @mdb[mn].alerting? } end r.join("\n") end end def initialize @cdb=Hash.new @db=SQLite3::Database.new(DB_FILE) @lastts=nil end def input #lasttime = @db.execute(<<~ENDSQL).first.first # SELECT MAX(ts) FROM metrics; #ENDSQL sel1 = @db.execute(<<~ENDSQL) SELECT center_id, metric, last_zero_time, last_nonzero_time, low1, high1, low3, high3, low24, since_low1, since_high1, since_low3, since_high3, since_low24 FROM last_state LEFT JOIN thresholds USING(center_id, metric) LEFT JOIN alert_state USING(center_id, metric); ENDSQL sel1.each do |row| center_id = row.first @cdb[center_id]=Center.new(center_id) unless @cdb[center_id] @cdb[center_id].register_metric(Metric.new(row)) end sel1=nil sel2 = @db.execute(<<~ENDSQL) SELECT MAX(ts) FROM metrics; ENDSQL @lastts = sel2.first.first sel3 = @db.execute(<<~ENDSQL, @lastts-86400.0) SELECT center_id, metric, ts, value FROM metrics WHERE ts > ? ORDER BY center_id, metric, ts DESC; ENDSQL sel3.each {|row| center_id=row.first @cdb[center_id].addval(row) } end def output @cdb.keys.each do |center_id| next unless @cdb[center_id].alerting? puts "=== #{center_id.upcase} ===" puts @cdb[center_id].summary(@lastts) puts "" end @cdb.keys.each do |center_id| next if @cdb[center_id].alerting? puts "=== #{center_id.upcase} ===" puts @cdb[center_id].summary(@lastts) puts "" end end def run input output end end App.new.run