#!/usr/bin/ruby require 'time' class HTLogRRD def xsystem ary STDOUT.puts(ary.inspect) if STDOUT.tty? or STDERR.tty? system(*ary) end def create_rrd btime bopt = (btime.strftime('%s').to_i - 10).to_s dspan = @span * 2 # 600 s by default cmd = [@rrdtool, 'create', @rrd, '-b', bopt, "DS:r2:GAUGE:#{dspan}:U:U", 'RRA:AVERAGE:0.5:1:512', 'RRA:AVERAGE:0.5:12:512', 'RRA:AVERAGE:0.5:288:512', "DS:r3:GAUGE:#{dspan}:U:U", 'RRA:AVERAGE:0.5:1:512', 'RRA:AVERAGE:0.5:12:512', 'RRA:AVERAGE:0.5:288:512', "DS:r4:GAUGE:#{dspan}:U:U", 'RRA:AVERAGE:0.5:1:512', 'RRA:AVERAGE:0.5:12:512', 'RRA:AVERAGE:0.5:288:512', "DS:r5:GAUGE:#{dspan}:U:U", 'RRA:AVERAGE:0.5:1:512', 'RRA:AVERAGE:0.5:12:512', 'RRA:AVERAGE:0.5:288:512', "DS:sz:GAUGE:#{dspan}:U:U", 'RRA:AVERAGE:0.5:1:512', 'RRA:AVERAGE:0.5:12:512', 'RRA:AVERAGE:0.5:288:512', "DS:u2:GAUGE:#{dspan}:U:U", 'RRA:AVERAGE:0.5:1:512', 'RRA:AVERAGE:0.5:12:512', 'RRA:AVERAGE:0.5:288:512' ] xsystem(cmd) end PAT1 = %r{^\S+ (?:\S+ )?- (\S+) \[([^\]]+)\] "([^"]+)" (\d\d\d) (\d+)} PAT2 = %r{^\S+ (?:\S+ )?- (\S+) \[([^\]]+)\] ".*" (\d\d\d) (\d+)} def register uname, rtime, reql, resp, size rtime = Time.strptime(rtime, '%d/%b/%Y:%H:%M:%S %z') htime = Time.at((rtime.to_i / @span) * @span) @result[htime] = Hash.new(0) unless @result.include?(htime) rec = @result[htime] case resp when /^2/ then rec[:r2] += 1 rec[:sz] += size.to_i rec[:u2] += 1 if uname != '-' when /^3/ then rec[:r3] += 1 when /^4/ then rec[:r4] += 1 when /^5/ then rec[:r5] += 1 end end def scanfile fp malf = 0 for line in fp line.chomp! case line when PAT1 then register($1, $2, $3, $4, $5) when PAT2 then register($1, $2, nil, $3, $4) else STDERR.puts "bad log <#{line}>" if malf.zero? malf += 1 next end end STDERR.puts "bad #{malf} lines" unless malf.zero? end def scanlog iday case iday when 0 File.open(@acclog) {|fp| scanfile(fp) } when 1 File.open("#{@acclog}.1") {|fp| scanfile(fp) } else require 'zlib' Zlib::GzipReader.open("#{@acclog}.#{iday}.gz") {|fp| scanfile(fp) } end end def rrdupdate create_rrd(@result.keys.sort.first) if @create_flag tlist = @result.keys.sort if tlist.last > (Time.now - @span) t = tlist.pop puts "incomplete span #{t} omitted" if STDOUT.tty? end tlist = tlist[-2..-1] if @last_flag for htime in tlist datastr = [htime.strftime('%s'), @result[htime][:r2], @result[htime][:r3], @result[htime][:r4], @result[htime][:r5], @result[htime][:sz], @result[htime][:u2]].join(':') cmd = [@rrdtool, 'update', @rrd, '-s', datastr] xsystem(cmd) end end def graph basename = @rrd.sub(/(\.rrd)?$/, '') cmd = [@rrdtool, 'graph', "#{basename}-c1.png", "DEF:r2=#{@rrd}:r2:AVERAGE", "DEF:r3=#{@rrd}:r3:AVERAGE", "DEF:r4=#{@rrd}:r4:AVERAGE", "DEF:r5=#{@rrd}:r5:AVERAGE", "DEF:u2=#{@rrd}:u2:AVERAGE", "LINE1:r2#FF0000:2xx", "LINE1:r3#00FF00:3xx", "LINE1:r4#0088FF:4xx", "LINE1:r5#FFFF00:5xx", "LINE1:u2#88FF00:2xx/auth", '--imginfo', ''] xsystem(cmd) cmd = [@rrdtool, 'graph', "#{basename}-s1.png", "DEF:sz=#{@rrd}:sz:AVERAGE", "LINE1:sz#FF8800:size", '--imginfo', ''] xsystem(cmd) cmd = [@rrdtool, 'graph', "#{basename}-c10.png", '-e', 'now', '-s', 'end-864000s', "DEF:r2=#{@rrd}:r2:AVERAGE", "DEF:r3=#{@rrd}:r3:AVERAGE", "DEF:r4=#{@rrd}:r4:AVERAGE", "DEF:r5=#{@rrd}:r5:AVERAGE", "DEF:u2=#{@rrd}:u2:AVERAGE", "LINE1:r2#FF0000:2xx", "LINE1:r3#00FF00:3xx", "LINE1:r4#0088FF:4xx", "LINE1:r5#FFFF00:5xx", "LINE1:u2#88FF00:2xx/auth", '--imginfo', ''] xsystem(cmd) end def run @backdays.downto(0) {|iday| scanlog(iday) } rrdupdate graph if @graph_flag end def initialize argv @rrd = "htlog.rrd" @rrdtool = "/usr/bin/rrdtool" @create_flag = false @last_flag = true @acclog = "/var/log/apache2/access.log" @backdays = 1 @span = 300 @graph_flag = nil cmds = [] for arg in argv case arg when /^rrd=/ then @rrd = $' when /^rrdtool=/ then @rrdtool = $' when /^a(ccess)?=/ then @acclog = $' when /^b(ack)?=/ then @backdays = ($').to_i @last_flag = false when 'create' then @create_flag = true @last_flag = false when 'graph' then @graph_flag = true when /^span=(\d+)$/ then @span = $1.to_i when 'help' then puts "#$0 [rrd=path] [rrdtool=path] [access=logfile] [back=days] [create] [graph] [span=secs]" exit 1 else STDERR.puts "unknown command #{arg}" end end @result = {} end end HTLogRRD.new(ARGV).run