aboutsummaryrefslogblamecommitdiff
path: root/monitor.rb
blob: 09f9e59cf8bb109f157d6fa5fe4fbb65fb5d4ea8 (plain) (tree)
1
2
3
4
5
6
7
8





                   

                  













                                                         



















                                                                                     





































































































































                                                              
            
































                                                                
                         



                                       
                                                                       

                       
               































































                                                  
                                  



























                                                        


















                                                        



                                   











                                                                                                       














                                         


                                       




























                                                                        



                                             









                                                                  
                                                                     

 



















                                         

                                   

































                                                     
#!/usr/bin/env ruby

require "time"
require "ncurses"
require "inifile"
require "open3"
require 'optparse'
require 'ostruct'

class IO
    def readline_nonblock
      buffer = ""
      buffer << read_nonblock(1) while buffer[-1] != "\n"
      buffer
    rescue IO::WaitReadable => blocking
      if (not buffer.empty?)
        ungetc(buffer)
      end
      raise blocking
    end
end

class OptParse
  def parse(args)
    
    options = OpenStruct.new()
    options.inifile = nil

    opt_parser = OptionParser.new() do |opts|
      
      opts.banner = "Usage: monitor [options]"
      opts.separator ""

      opts.on( '-f', "-f ini_file", "chose a different initialization file") do |ini|
        options.inifile = ini
      end
    end
    opt_parser.parse!(args)
    return options
  end
end

class List_Win
  def initialize(inifile)
    @params = inifile
    @win = Ncurses::WINDOW.new(0, Ncurses.COLS()/4, 0, 0)
    @win.border(*([0]*8))
    @win.keypad(true)
    @win.timeout(1000)
    @win.refresh()
    @entry = 0
  end

  def getch()
    @win.getch()
  end
  def clear()
    @win.clear()
  end
  def resize(x,y)
    @win.resize(x,y)
  end
  def move(x,y)
    @win.move(x,y)
  end
  def refresh()
    @win.refresh()
  end

  def print_list(entry=nil)
    if(not entry.nil?)
      @entry = entry
    end
    i = 0
    @params.each_section do |section|
      if(@entry == i)
        @win.attron(Ncurses::A_REVERSE)
      end
      @win.move(i+1,1)
      print_line(@win,@params[section]['Name'])
      if(@entry == i)
        @win.attroff(Ncurses::A_REVERSE)
      end
      i = i+1
    end
    @win.border(*([0]*8))
    @win.move(0,3)
    @win.addstr("Menu")
    @win.refresh()
  end
end

class Buff_Win
  def initialize(winsize,winpos,params)
    @params = params
    @win = Ncurses::WINDOW.new(0, winsize, 0, winpos)
    @panel = Ncurses::Panel::PANEL.new(@win)
    if(@params['Type'] == 'oneshot')
      @buffer = Buffer.new(0)
    else
      @buffer = Buffer.new(@params['Buffer'].to_i)
    end
    @proc = nil
    @curr_offset = 0
    @hscroll = 0
    update_date()

    spawn_proc()
    print_buffer()
  end

  def move_resize(winsize,winpos)
    newwin = Ncurses::WINDOW.new(0, winsize, 0, winpos)
    Ncurses::Panel.replace_panel(@panel, newwin)
    Ncurses.delwin(@win)
    @win = newwin
    print_buffer()
  end
  def update_date()
    @last_update = Time.now
    @date = @last_update.strftime("%F %R:%S")
  end

  def win_border()
    @win.border(*([0]*8))
    win_header()
  end
  def win_header()
    @win.move(0,3)
    @win.addnstr(@params['Name'],@win.getmaxx-@date.length-10)
    @win.move(0,@win.getmaxx-@date.length-2)
    @win.addstr(@date)
  end
  def refresh()
    @win.refresh()
  end
  def clear()
    @win.clear()
  end

  def show_win()
    Ncurses::Panel.top_panel(@panel)
    Ncurses::Panel.update_panels
  end

  def hscroll(scroll=0)
    @hscroll += scroll
    if(@hscroll < 0)
      @hscroll = 0
    end
    if(@hscroll > @buffer.maxlen()-@win.getmaxx+3)
      @hscroll = @buffer.maxlen()-@win.getmaxx+3
    end
    print_buffer()
    refresh()
  end
  def scroll(scroll=0,goto=nil,fact=nil)
    if (not fact.nil?)
      scroll = (fact * @win.getmaxy.to_f).to_i
    elsif (not goto.nil?)
      @curr_offset = (goto * @buffer.size()).to_i
      scroll = 0
    end
    #@curr_offset -= @win.getmaxy/2
    @curr_offset += scroll
    if(@curr_offset < 0)
      @curr_offset = 0
    end
    if(@curr_offset > @buffer.size()-@win.getmaxy+2)
      @curr_offset = @buffer.size()-@win.getmaxy+2
    end
    print_buffer()
    refresh()
  end

  def print_buffer()
    #clear()
    win_border()
    j = 1
    @buffer.yield(@win.getmaxy-2,@curr_offset) { |l,type|
      @win.move(j,1)
      if(type == 1) then @win.attron(Ncurses.COLOR_PAIR(1)) end
      print_line(@win,l,hscroll=@hscroll)
      if(type == 1) then @win.attroff(Ncurses.COLOR_PAIR(1)) end
      j = j+1
    }
    if(@buffer.has_before?)
      @win.move(2,@win.getmaxx-1)
      @win.attron(Ncurses::A_REVERSE)
      @win.addstr("↑")
      @win.attroff(Ncurses::A_REVERSE)
    end
    if(@buffer.has_after?)
      @win.move(@win.getmaxy-2,@win.getmaxx-1)
      @win.attron(Ncurses::A_REVERSE)
      @win.addstr("↓")
      @win.attroff(Ncurses::A_REVERSE)
    end
  end

  def proc_readlines()
    begin
      while true
        @buffer.push(@proc.readline_nonblock)
        update_date()
      end
    rescue IO::WaitReadable
    end
  end

  def update(force=false)
    if(@params['Type'] == 'continuous')
      proc_readlines()
    end
    if(@params['Type'] == 'oneshot')
      if(force or (Time.now - @last_update > @params['Periodic'].to_i))
        @buffer.clear()
        spawn_proc()
        clear()
      end
    end
    print_buffer()
  end

  def spawn_proc()
    if(@params['Type'] == 'oneshot')
      update_date()
      Open3.popen3(@params["Command"]) { |i,o,e,t|
        while ((not o.eof?) or (not e.eof?))
          rs = IO.select([o,e],nil)[0]
          r = (rs[0].eof?)? rs[1] : rs[0]

          if r.fileno == o.fileno
            @buffer.push(r.readline)
          elsif r.fileno == e.fileno
            @buffer.push(r.readline,type=1)
          end
        end
      }
    elsif(@params['Type'] == 'continuous')
      @proc = IO.popen(@params["Command"])
      proc_readlines()
    end
  end
end

class Buffer
  def initialize(size)
    @size = size
    @buff = []
    @buff_type = []
    @current = 0
    @wrap = false
    @before = false
    @after = false
  end
  def size()
    return @buff.length
  end
  def maxlen()
    maxlen = 0
    @buff.each do |string|
      if string.length > maxlen
        maxlen = string.length
      end
    end
    return maxlen
  end

  def push(string,type=0)
    if(string.chomp.empty?) then string = " " end
    string.split( /\r?\n/ ).each do |line|
      @buff[@current] = line
      @buff_type[@current] = type
      if(@size > 0)
        @current = (1+@current) % @size
      else
        @current = 1+@current
      end
      if(@current == 0) then @wrap = true end
    end
  end
  def yield(size,offset=0,&block)
    if(size < 0) then size = 0 end
    range = Range.new(0,@current-1).to_a
    if(@wrap)
      range = Range.new(@current,@size-1).to_a + range
    end
    range = range.last(size+offset)[0,size]
    @before = (size+offset < @buff.length)
    @after = (offset != 0 and size < @buff.length)
    if(block)
      range.each do |i|
        yield [@buff[i],@buff_type[i]]
      end
    else
      return range.collect{|r| [@buff[r],@buff_type[r]]}
    end
  end
  def has_after?()
    return @after
  end
  def has_before?()
    return @before
  end
  def clear()
    @current = 0
    @buff = []
    @buff_type = []
  end
end

def find_ini(option_inifile)
  if(not option_inifile.nil?)
    inifile = option_inifile
  elsif(ENV.has_key?('MONITOR_RC'))
    inifile = ENV['MONITOR_RC']
  elsif(Process.uid == 0)
    inifile = "/etc/monitor.rc"
  else
    inifile = ENV['HOME']+"/.monitorrc"
  end
  return inifile
end

def read_ini(ini)
  inifile = IniFile.load(ini)
  if(inifile.nil?)
    puts "Initialization file not found or not readable"
    exit
  end
  return inifile
end

def print_line(win, str, hscroll=0)
  revert_color = false
  str[0,5].match(/\033\[3(.)m/) { |c| #Line starts with an escape sequence. We handle that `a la xterm`
    Ncurses.init_pair(10, c[1].to_i,   Ncurses::COLOR_BLACK)
    win.attron(Ncurses.COLOR_PAIR(10))
    revert_color = true
    str = str[5,str.length]
  }
  str = str.gsub("\011","    ")
  #Any other control char is ignored and escaped
  str = str.gsub(/[[:cntrl:]]/) { |m|
    "^"+(m.ord + 64).chr
  }
  if(hscroll > 0)
    strcut = str[hscroll,str.length]
    if(strcut.nil? or strcut.empty?)
      str = ""
    else
      str = "…"+strcut
    end
  end
  strlen = str.length
  winlen = win.getmaxx-win.getcurx-1
  if(strlen <= winlen)
    win.addstr(str + " "*(winlen-strlen))
  else
    win.addstr(str[0,winlen-1]+"…")
  end
  if(revert_color)
    win.attroff(Ncurses.COLOR_PAIR(10))
  end
end

def make_bufwins(inifile)
  bufwins = []
  inifile.each_section do |section|
    bufwin = Buff_Win.new(Ncurses.COLS()-Ncurses.COLS()/4,
                           Ncurses.COLS()/4,
                           inifile[section])
    bufwins.push(bufwin)
  end
  return bufwins
end

def update_buffers(bufwins)
  bufwins.each do |bufwin|
    bufwin.update()
  end
end

def redraw_all(list,bufwins,curr_bufwin)
  bufwins.each do |bufwin|
    bufwin.move_resize(Ncurses.COLS()-Ncurses.COLS()/4,Ncurses.COLS()/4)
  end
  list.resize(Ncurses.LINES(), Ncurses.COLS()/4)
  list.clear()
  list.print_list()
  list.refresh()
  curr_bufwin.refresh()
end


options = OptParse.new().parse(ARGV)
inifile = read_ini(find_ini(options.inifile))
begin
  # initialize ncurses
  Ncurses.initscr
  Ncurses.start_color
  Ncurses.cbreak           # provide unbuffered input
  Ncurses.noecho           # turn off input echoing
  #Ncurses.nonl             # turn off newline translation
  #Ncurses.stdscr.intrflush(false) # turn off flush-on-interrupt
  Ncurses.stdscr.keypad(true)     # turn on keypad mode
  Ncurses.init_pair(1, Ncurses::COLOR_RED,   Ncurses::COLOR_BLACK)
  Ncurses.init_pair(10, Ncurses::COLOR_WHITE,   Ncurses::COLOR_BLACK)


  list = List_Win.new(inifile)
  bufwins = make_bufwins(inifile)
  entry = 0
  cur_bufwin = bufwins[entry]
  cur_bufwin.show_win()
  list.print_list()
  while(ch = list.getch()) do
    case(ch)
    when "n".ord
      entry = (entry +1) % bufwins.length
      cur_bufwin = bufwins[entry]
      cur_bufwin.show_win()
      list.print_list(entry=entry)
    when "p".ord
      entry = (entry -1) % bufwins.length
      cur_bufwin = bufwins[entry]
      cur_bufwin.show_win()
      list.print_list(entry=entry)
    when 12 #ctrl+L
      redraw_all(list,bufwins,cur_bufwin)
    when 18 #ctrl+R
      cur_bufwin.update(force=true)
    when Ncurses::KEY_RESIZE
      redraw_all(list,bufwins,cur_bufwin)
    when Ncurses::KEY_LEFT
      cur_bufwin.hscroll(scroll=-1)
    when Ncurses::KEY_RIGHT
      cur_bufwin.hscroll(scroll=1)
    when Ncurses::KEY_DOWN
      cur_bufwin.scroll(scroll=-1)
    when Ncurses::KEY_UP
      cur_bufwin.scroll(scroll=1)
    when Ncurses::KEY_NPAGE
      cur_bufwin.scroll(scroll=0,goto=nil,fact=-0.75)
    when Ncurses::KEY_PPAGE
      cur_bufwin.scroll(scroll=0,goto=nil,fact=0.75)
    when Ncurses::KEY_HOME
      cur_bufwin.scroll(scroll=0,goto=1.0)
    when Ncurses::KEY_END
      cur_bufwin.scroll(scroll=0,goto=0.0)
    when Ncurses::ERR
      update_buffers(bufwins)
      cur_bufwin.show_win()
    when "q".ord
      break
    else
      next
    end
  end

ensure
  Ncurses.echo
  Ncurses.nocbreak
  Ncurses.nl
  Ncurses.endwin
end