class UI

Public Class Methods

new(config) click to toggle source
# File lib/ui.rb, line 6
def initialize(config)
  @config = config

  init_screen
  cbreak
  curs_set(0)

  # set keyboard input timeout - sneaky way to manage refresh rate
  Curses.timeout = @config[:refresh_rate]

  if can_change_color?
    start_color
    init_pair(0, COLOR_WHITE, COLOR_BLACK)
    init_pair(1, COLOR_WHITE, COLOR_BLUE)
    init_pair(2, COLOR_WHITE, COLOR_RED)
  end

  @stat_cols    = %w[ calls objsize req/sec bw(kbps) ]
  @stat_col_width = 10
  @key_col_width  = 0

  @commands = {
    'Q' => "quit",
    'C' => "sort by calls",
    'S' => "sort by size",
    'R' => "sort by req/sec",
    'B' => "sort by bandwidth",
    'T' => "toggle sort order (asc|desc)"
  }
end

Public Instance Methods

done() click to toggle source
# File lib/ui.rb, line 169
def done
  nocbreak
  close_screen
end
header() click to toggle source
# File lib/ui.rb, line 37
def header
  # pad stat columns to @stat_col_width
  @stat_cols = @stat_cols.map { |c| sprintf("%#{@stat_col_width}s", c) }

  # key column width is whatever is left over
  @key_col_width = cols - (@stat_cols.length * @stat_col_width)

  attrset(color_pair(1))
  setpos(0,0)
  addstr(sprintf "%-#{@key_col_width}s%s", "memcache key", @stat_cols.join)
end
input_handler() click to toggle source
# File lib/ui.rb, line 152
def input_handler
  # Curses.getch has a bug in 1.8.x causing non-blocking
  # calls to block reimplemented using IO.select
  if RUBY_VERSION =~ %r^1.8/
         refresh_secs = @config[:refresh_rate].to_f / 1000

    if IO.select([STDIN], nil, nil, refresh_secs)
      c = getch
      c.chr
    else
      nil
    end
  else
    getch
  end
end
render_stats(sniffer, sort_mode, sort_order = :desc) click to toggle source
# File lib/ui.rb, line 56
def render_stats(sniffer, sort_mode, sort_order = :desc)
  render_start_t = Time.now.to_f * 1000

  # subtract header + footer lines
  maxlines = lines - 3
  offset   = 1

  # calculate packet loss ratio
  if sniffer.metrics[:stats][:recv] > 0
    loss = sprintf("%5.2f", (sniffer.metrics[:stats][:drop].to_f / sniffer.metrics[:stats][:recv].to_f) * 100)
  else
    loss = 0
  end

  # construct and render footer stats line
  setpos(lines-2,0)
  attrset(color_pair(2))
  header_summary = sprintf "%-28s %-14s %-30s",
    "sort mode: #{sort_mode.to_s} (#{sort_order.to_s})",
    "keys: #{sniffer.metrics[:calls].keys.count}",
    "packets (recv/dropped): #{sniffer.metrics[:stats][:recv]} / #{sniffer.metrics[:stats][:drop]} (#{loss}%)"
  addstr(sprintf "%-#{cols}s", header_summary)

  # reset colours for main key display
  attrset(color_pair(0))

  top = []

  sniffer.semaphore.synchronize do
    # we may have seen no packets received on the sniffer thread
    return if sniffer.metrics[:start_time].nil?

    elapsed = Time.now.to_f - sniffer.metrics[:start_time]

    # iterate over all the keys in the metrics hash and calculate some values
    sniffer.metrics[:calls].each do |k,v|
        reqsec = v / elapsed

        # if req/sec is <= the discard threshold delete those keys from
        # the metrics hash - this is a hack to manage the size of the
        # metrics hash in high volume environments
        if reqsec <= @config[:discard_thresh]
          sniffer.metrics[:calls].delete(k)
          sniffer.metrics[:objsize].delete(k)
          sniffer.metrics[:reqsec].delete(k)
          sniffer.metrics[:bw].delete(k)
        else
          sniffer.metrics[:reqsec][k]  = v / elapsed
          sniffer.metrics[:bw][k]    = ((sniffer.metrics[:objsize][k] * sniffer.metrics[:reqsec][k]) * 8) / 1000
        end
    end

    top = sniffer.metrics[sort_mode].sort { |a,b| a[1] <=> b[1] }
  end

  unless sort_order == :asc
    top.reverse!
  end

  for i in 0..maxlines-1
    if i < top.length
      k = top[i][0]
      v = top[i][1]

      # if the key is too wide for the column truncate it and add an ellipsis
      if k.length > @key_col_width
        display_key = k[0..@key_col_width-4]
        display_key = "#{display_key}..."
      else
        display_key = k
      end

      # render each key
      line = sprintf "%-#{@key_col_width}s %9.d %9.d %9.2f %9.2f",
               display_key,
               sniffer.metrics[:calls][k],
               sniffer.metrics[:objsize][k],
               sniffer.metrics[:reqsec][k],
               sniffer.metrics[:bw][k]
    else
      # we're not clearing the display between renders so erase past
      # keys with blank lines if there's < maxlines of results
      line = " "*cols
    end

    setpos(1+i, 0)
    addstr(line)
  end

  # print render time in status bar
  runtime = (Time.now.to_f * 1000) - render_start_t
  attrset(color_pair(2))
  setpos(lines-2, cols-18)
  addstr(sprintf "rt: %8.3f (ms)", runtime)
end