class Ddtop::Ddtop

Constants

PREFIX
TOP_CMD

Public Class Methods

new() click to toggle source
# File lib/ddtop/ddtop.rb, line 5
def initialize
  api_key = ENV['DD_API_KEY']
  host = %x[hostname -f 2> /dev/null].strip
  host = Socket.gethostname if host.empty?
  @dog = Dogapi::Client.new(api_key, nil, host)
end

Public Instance Methods

run() click to toggle source
# File lib/ddtop/ddtop.rb, line 12
def run
  clear_screen

  emits = get_emits(ARGV)

  summary, fields = nil
  values = []

  head_hook = proc do
    if summary and not values.empty?
      emit_points(summary, fields, values, emits)
      values.clear
    end
  end

  top_each_lien(head_hook) do |top, line|
    if head?(line)
      summary, fields = parse_header(top, line)
    elsif not line.empty?
      values << line.split(/\s+/, fields.length)
    end
  end
end

Private Instance Methods

aggregate_fields(names, values, emits) click to toggle source
# File lib/ddtop/ddtop.rb, line 145
def aggregate_fields(names, values, emits)
  aggregated = {}

  values.map {|v|
    h = Hash[*names.zip(v).flatten]
    %w(PID USER PR NI S).each {|k| h.delete(k) }
    h['TIME+'] = parse_time_plus(h['TIME+'])
    h
  }.select {|h|
    emits.empty? || emits.include?(h['COMMAND'])
  }.sort_by {|h|
    h['COMMAND']
  }.chunk {|h|
    h['COMMAND']
  }.each {|cmd, hs|
    total = {}

    hs.each do |h|
      h.delete('COMMAND')

      h.each do |k, v|
        total[k] ||= 0.0
        total[k] += v.to_f
      end
    end

    aggregated['process.' + cmd] = total
  }

  return aggregated
end
clear_screen() click to toggle source
# File lib/ddtop/ddtop.rb, line 188
def clear_screen
  print "\e[2J"
end
emit_points(summary, names, values, emits) click to toggle source
# File lib/ddtop/ddtop.rb, line 124
def emit_points(summary, names, values, emits)
  time = summary.delete(:time)
  fields = aggregate_fields(names, values, emits)
  emit_points0(time, summary)
  emit_points0(time, fields)
end
emit_points0(time, metrics) click to toggle source
# File lib/ddtop/ddtop.rb, line 131
def emit_points0(time, metrics)
  metrics.each do |category, items|
    items.each do |name, value|
      metric_name = [PREFIX, category, name].join('.')

      Thread.start do
        @dog.emit_point(metric_name, expand_value(value), :timstamp => time)
      end
    end
  end
rescue Timeout::Error
  # nothing to do
end
expand_value(value) click to toggle source
# File lib/ddtop/ddtop.rb, line 196
def expand_value(value)
  n = 0

  case value
  when /m\Z/ then n = 1
  end

  value.to_f * (1024 ** n)
end
get_emits(args) click to toggle source
# File lib/ddtop/ddtop.rb, line 38
def get_emits(args)
  i = args.index('--emits')
  emits = []

  if i and args[i + 1] and args[i + 1] !~ /\A-/
    emits = args[i + 1].split(/,/).map {|i| i.strip }.select {|i| not i.empty? }
    args.delete_at(i + 1)
    args.delete_at(i)
  end

  return emits
end
get_winsize() click to toggle source
# File lib/ddtop/ddtop.rb, line 182
def get_winsize
  `stty -a`.split("\n").first.split(';').values_at(1, 2).map do |nv|
    nv.strip.split(/\s+/, 2)[1].to_i
  end
end
gets_with_print(top, esq = nil) { || ... } click to toggle source
# File lib/ddtop/ddtop.rb, line 61
def gets_with_print(top, esq = nil)
  line = top.gets

  if head?(line)
    @win_rows, @win_cols = get_winsize
    @curr_row = 0
    yield if block_given?
    reset_cursor
  end

  @curr_row += 1

  if @curr_row <= @win_rows
    pline = line.chomp
    delta = @win_cols - pline.length
    pline += ' ' * (@win_cols - pline.length) if delta > 0
    pline = pline[0, @win_cols]
    pline = esq + pline + "\e[0m" if esq
    print pline
    puts if @curr_row != @win_rows
  end

  return line.strip
end
head?(line) click to toggle source
# File lib/ddtop/ddtop.rb, line 86
def head?(line)
  line =~ /\Atop\b/
end
line_to_items(line) { |split(/[\s,:]+/).slice(1..-1)| ... } click to toggle source
# File lib/ddtop/ddtop.rb, line 119
def line_to_items(line)
  items = yield(line.split(/[\s,:]+/).slice(1..-1))
  Hash[*items.flatten]
end
parse_header(top, line) click to toggle source
# File lib/ddtop/ddtop.rb, line 90
def parse_header(top, line)
  summary_lines = [line] + (1..4).map { gets_with_print(top) }
  gets_with_print(top)
  fields_line = gets_with_print(top, "\e[7m")

  summary = parse_summary(summary_lines)
  fields = fields_line.split(/\s+/)

  [summary, fields]
end
parse_summary(lines) click to toggle source
# File lib/ddtop/ddtop.rb, line 101
def parse_summary(lines)
  summary = {}
  first_line = lines[0].split(/[\s,]+/)
  summary[:time] = parse_time(first_line[2])
  load_avg = first_line.values_at(-3, -2, -1)
  summary[:load_avg] = Hash[*['1m', '5m', '15m'].zip(load_avg).flatten]
  summary[:tasks]    = line_to_items(lines[1]) {|i| i.each_slice(2).map {|i, j| [j, i] } }
  summary[:cpu]      = line_to_items(lines[2]) {|i| i.map {|j| j.split('%').values_at(1, 0) } }
  summary[:mem]      = line_to_items(lines[3]) {|i| i.each_slice(2).map {|i, j| [j, i] } }
  summary[:swap]     = line_to_items(lines[4]) {|i| i.each_slice(2).map {|i, j| [j, i] } }
  return summary
end
parse_time(time) click to toggle source
# File lib/ddtop/ddtop.rb, line 114
def parse_time(time)
  now = Time.now
  Time.mktime(now.year, now.mon, now.day, *time.split(':').map {|i| i.to_i })
end
parse_time_plus(time) click to toggle source
# File lib/ddtop/ddtop.rb, line 177
def parse_time_plus(time)
  t = time.split(/[.:]/).map {|i| i.to_f }
  t[0] * 60 + t[1] + t[2] / 100
end
reset_cursor() click to toggle source
# File lib/ddtop/ddtop.rb, line 192
def reset_cursor
  print "\e[0;0H"
end
top_each_lien(head_hook) { |top, line| ... } click to toggle source
# File lib/ddtop/ddtop.rb, line 51
def top_each_lien(head_hook)
  cmd = [TOP_CMD, ARGV].flatten.join(' ')

  IO.popen(cmd) do |top|
    while line = gets_with_print(top, &head_hook)
      yield(top, line)
    end
  end
end