class Visage::Collectd::JSON

Attributes

rrddir[W]

Public Class Methods

new(opts={}) click to toggle source
# File lib/visage-app/collectd/json.rb, line 59
def initialize(opts={})
  @rrddir = opts[:rrddir] || Visage::Collectd::JSON.rrddir
  @types  = opts[:types]  || Visage::Collectd::JSON.types
end

Private Class Methods

hosts() click to toggle source
# File lib/visage-app/collectd/json.rb, line 224
def hosts
  if @rrddir
    Dir.glob("#{@rrddir}/*").map {|e| e.split('/').last }.sort
  end
end
plugins(opts={}) click to toggle source
# File lib/visage-app/collectd/json.rb, line 230
def plugins(opts={})
  host = opts[:host] || '*'
  Dir.glob("#{@rrddir}/#{host}/*").map {|e| e.split('/').last }.sort
end
rrddir() click to toggle source
# File lib/visage-app/collectd/json.rb, line 216
def rrddir
  @rrddir ||= Visage::Config.rrddir
end
types() click to toggle source
# File lib/visage-app/collectd/json.rb, line 220
def types
  @types  ||= Visage::Config.types
end

Public Instance Methods

downsample_array(samples, old_resolution, new_resolution) click to toggle source
# File lib/visage-app/collectd/json.rb, line 116
def downsample_array(samples, old_resolution, new_resolution)
  return samples unless samples.length > 0
  timer_start = Time.now
  new_samples = []
  if (new_resolution > 0) and (old_resolution > 0) and (new_resolution % old_resolution == 0)
    groups_of = samples.length / (new_resolution / old_resolution)
    return samples unless groups_of > 0
    samples.in_groups(groups_of, false) {|group|
      new_samples << group.compact.mean
    }
  else
    raise "downsample_array: cowardly refusing to downsample as old_resolution (#{old_resolution.to_s}) doesn't go into new_resolution (#{new_resolution.to_s}) evenly, or new_resolution or old_resolution are zero."
  end
  timer = Time.now - timer_start

  new_samples
end
json(opts={}) click to toggle source

Entry point.

# File lib/visage-app/collectd/json.rb, line 76
def json(opts={})
  host        = opts[:host]
  plugin      = opts[:plugin]
  instances   = opts[:instances][/\w.*/]
  instances   = instances.blank? ? '*' : '{' + instances.split('/').join(',') + '}'
  percentiles = opts[:percentiles] !~ /^$|^false$/ ? true : false
  resolution  = opts[:resolution] || ""
  rrdglob     = "#{@rrddir}/#{host}/#{plugin}/#{instances}.rrd"
  finish      = parse_time(opts[:finish])
  start       = parse_time(opts[:start],  :default => (finish - 3600 || (Time.now - 3600).to_i))
  data        = []

  Dir.glob(rrdglob).map do |rrdname|
    parts         = rrdname.gsub(/#{@rrddir}\//, '').split('/')
    host_name     = parts[0]
    plugin_name   = parts[1]
    instance_name = File.basename(parts[2], '.rrd')
    rrd           = Errand.new(:filename => rrdname)

    data << {  :plugin      => plugin_name, :instance => instance_name,
               :host        => host_name,
               :start       => start,
               :finish      => finish,
               :rrd         => rrd,
               :percentiles => percentiles,
               :resolution  => resolution}

  end

  encode(data)
end
parse_time(time, opts={}) click to toggle source
# File lib/visage-app/collectd/json.rb, line 64
def parse_time(time, opts={})
  case
  when time && time.index('.')
    time.split('.').first.to_i
  when time
    time.to_i
  else
   opts[:default] || Time.now.to_i
  end
end
percentile_of_array(samples, percentage) click to toggle source
# File lib/visage-app/collectd/json.rb, line 108
def percentile_of_array(samples, percentage)
  if samples
    samples.sort[ (samples.length.to_f * ( percentage.to_f / 100.to_f ) ).to_i - 1 ]
  else
    raise "I can't work out percentiles on a nil sample set"
  end
end

Private Instance Methods

encode(datas) click to toggle source

Attempt to structure the JSON reasonably sanely, so the consumer (i.e. a browser) doesn't have to do a lot of computationally expensive work.

# File lib/visage-app/collectd/json.rb, line 137
def encode(datas)

  structure = {}
  datas.each do |data|

    start      = data[:start].to_i
    finish     = data[:finish].to_i
    resolution = data[:resolution].to_i || 0

    fetch    = data[:rrd].fetch(:function   => "AVERAGE",
                                :start      => start.to_s,
                                :finish     => finish.to_s)

    rrd_data = fetch[:data]
    percentiles = data[:percentiles]

    # A single rrd can have multiple data sets (multiple metrics within
    # the same file). Separate the metrics.
    rrd_data.each_pair do |source, metric|

      # Filter out NaNs and weirdly massive values so yajl doesn't choke
      # FIXME: does this actually do anything?
      metric = metric.map do |datapoint|
        case
        when datapoint && datapoint.nan?
          @tripped = true
          @last_valid
        when @tripped
          @last_valid
        else
          @last_valid = datapoint
        end
      end

      # Last value is always wack. Remove it.
      metric   = metric[0...metric.length-1]
      host     = data[:host]
      plugin   = data[:plugin]
      instance = data[:instance]

      # only calculate percentiles if requested
      if percentiles
        timeperiod = finish.to_f - start.to_f
        interval = (timeperiod / metric.length.to_f).round
        resolution = 300
        if (interval < resolution) and (resolution > 0)
          metric_for_percentiles = downsample_array(metric, interval, resolution)
        else
          metric_for_percentiles = metric
        end
        metric_for_percentiles.compact!
        percentiles = false unless metric_for_percentiles.length > 0
      end

      if metric.length > 2000
        metric = downsample_array(metric, 1, metric.length / 1000)
      end

      structure[host] ||= {}
      structure[host][plugin] ||= {}
      structure[host][plugin][instance] ||= {}
      structure[host][plugin][instance][source] ||= {}
      structure[host][plugin][instance][source][:start]         ||= start
      structure[host][plugin][instance][source][:finish]        ||= finish
      structure[host][plugin][instance][source][:data]          ||= metric
      structure[host][plugin][instance][source][:percentile_95] ||= percentile_of_array(metric_for_percentiles, 95).round if percentiles
      structure[host][plugin][instance][source][:percentile_50] ||= percentile_of_array(metric_for_percentiles, 50).round if percentiles
      structure[host][plugin][instance][source][:percentile_5]  ||= percentile_of_array(metric_for_percentiles,  5).round if percentiles

    end
  end

  encoder = Yajl::Encoder.new
  encoder.encode(structure)
end