Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: ad9c4e6005
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 159 lines (124 sloc) 3.235 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
require "English"

unless $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
  $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
end

require 'thread'
require 'curses'
require "nginx_stat/io_tail"

##
# NginxStat displays the current requests per second and average request time.
# Default interval is 10 seconds.

class NginxStat
  VERSION = "0.1.0"
  
  ##
  # NginxStat.start 'online-43things.log', 'online-43people.log', 10
  #
  # Starts a new NginxStat for +filenames+ that prints every +interval+
  # seconds.
  #
  # Stats for multiple log files requires curses.

  def self.start(*args)
    interval = 10
    interval = Float(args.pop) if Float(args.last) rescue nil

    stats = []

    if args.length > 1 and not defined? Curses then
      $stderr.puts "Multiple logfile support requires curses"
      exit 1
    end

    if defined? Curses then
      Curses.init_screen
      Curses.clear
      Curses.addstr "Collecting data...\n"
      Curses.refresh
    end

    args.each_with_index do |filename, offset|
      stat = self.new File.open(filename), interval, offset
      stat.start
      stats << stat
    end

    stats.each { |stat| stat.thread.join }
  end

  ##
  # The log reading thread

  attr_reader :thread

  ##
  # Current status line

  attr_reader :status

  ##
  # Creates a new NginxStat that will listen on +io+ and print every
  # +interval+ seconds. +offset+ is only used for multi-file support.

  def initialize(io, interval, offset = 0)
    @io = io
    @io_path = File.basename io.path rescue 'unknown'
    @interval = interval.to_f
    @offset = offset

    @mutex = Mutex.new
    @status = ''
    @last_len = 0
    @count = 0
    @time = 0.0
    @thread = nil
  end

  ##
  # Starts the NginxStat running. This method never returns.

  def start
    trap 'INT' do
      Curses.close_screen if defined? Curses
      exit
    end
    start_printer
    read_log
  end

  def print
    if defined? Curses then
      Curses.setpos @offset, 0
      Curses.addstr ' ' * @last_len
      Curses.setpos @offset, 0
      Curses.addstr "#{@io_path}\t#{@status}"
      Curses.refresh
    else
      print "\r"
      print ' ' * @last_len
      print "\r"
      print @status
      $stdout.flush
    end
  end

  private

  ##
  # Starts a thread that prints log information every +interval+ seconds.

  def start_printer
    Thread.start do
      count_sec = 0
      average_time = 0.0
      
      loop do
        sleep @interval

        @mutex.synchronize do
          count_sec = @count / @interval
          average_time = @time / @count.to_f
          @count = 0
          @time = 0.0
        end

        @status = "%5.1f req/sec, %.2f sec per req" % [count_sec, average_time]

        print

        @last_len = status.length
      end
    end
  end

  ##
  # Starts a thread that reads from +io+, updating NginxStat counters as it
  # goes.

  def read_log
    @thread = Thread.start do
      @io.tail_lines do |line|
        unless exclude?(line)
          @mutex.synchronize { @time += line.strip.split.last.to_f }
          @mutex.synchronize { @count += 1 }
        end
      end
    end
  end
  
  def exclude?(line)
    line =~ /\.(gif|jpg|jpeg|png|ico)/
  end

end

Something went wrong with that request. Please try again.