Ruby:计算average(),同时从数据中排除nil值

时间:2014-10-07 20:34:43

标签: ruby null

我对Ruby很陌生,而且我对一个看似简单的问题遇到了一些困难。

代码就在这里......

https://github.com/sensu/sensu-community-plugins/blob/master/plugins/graphite/check-stats.rb

...但我在最后包含了当前源的完整副本,因为它可能会随着新版本提交给Github而改变。

这是一个Sensu插件。它通过HTTP请求从Graphite收集数据。将回复存储在body中,然后将JSON.parse()存储到数据中。

对于数据中的每个指标,它会收集数据点,并对数据点执行平均值。如果平均值高于某些阈值(选项-w或-c),则会发出警告或严重警告。

有时Graphite商店有点落后。某些指标可能缺少最新的数据点。发生这种情况时,数据点为零。

问题是,当计算平均值(数据点)时,nil被计为零。这会人为地降低平均值,有时会降低插件不会触发的效果。

从平均值的计算中消除零值的最佳方法是什么?

理想情况下,消除nils应该以这样的方式发生:如果所有数据点都是nil,那么它应该触发datapoints.empty条件。基本上,在达到“除非datapoints.empty?”之前杀死所有的nils。因为如果所有都是零,那么我们实际上没有任何数据点。

或者某种方式,metric.collect {}应该跳过零值。

我试过使用.compact,但这似乎没什么区别(可能我用错了)。


这是代码的当前版本:

#!/usr/bin/env ruby

#
# Checks metrics in graphite, averaged over a period of time.
#
# The fired sensu event will only be critical if a stat is
# above the critical threshold. Otherwise, the event will be warning,
# if a stat is above the warning threshold.
#
# Multiple stats will be checked if * are used
# in the "target" query.
#
# Author: Alan Smith (alan@asmith.me)
# Date: 08/28/2014
#

require 'rubygems' if RUBY_VERSION < '1.9.0'
require 'json'
require 'net/http'
require 'sensu-plugin/check/cli'

class CheckGraphiteStat < Sensu::Plugin::Check::CLI

  option :host,
    :short => "-h HOST",
    :long => "--host HOST",
    :description => "graphite hostname",
    :proc => proc {|p| p.to_s },
    :default => "graphite"

  option :period,
    :short => "-p PERIOD",
    :long => "--period PERIOD",
    :description => "The period back in time to extract from Graphite. Use -24hours, -2days, -15mins, etc, same format as in Graphite",
    :proc => proc {|p| p.to_s },
    :required => true

  option :target,
    :short => "-t TARGET",
    :long => "--target TARGET",
    :description => "The graphite metric name. Can include * to query multiple metrics",
    :proc => proc {|p| p.to_s },
    :required => true

  option :warn,
    :short => "-w WARN",
    :long => "--warn WARN",
    :description => "Warning level",
    :proc => proc {|p| p.to_f },
    :required => false

  option :crit,
    :short => "-c Crit",
    :long => "--crit CRIT",
    :description => "Critical level",
    :proc => proc {|p| p.to_f },
    :required => false

  def average(a)
    total = 0
    a.to_a.each {|i| total += i.to_f}

    total / a.length
  end

  def danger(metric)
    datapoints = metric['datapoints'].collect {|p| p[0].to_f}

    unless datapoints.empty?
      avg = average(datapoints)

      if !config[:crit].nil? && avg > config[:crit]
        return [2, "#{metric['target']} is #{avg}"]
      elsif !config[:warn].nil? && avg > config[:warn]
        return [1, "#{metric['target']} is #{avg}"]
      end
    end
    [0, nil]
  end

  def run
    body =
      begin
        uri = URI("http://#{config[:host]}/render?format=json&target=#{config[:target]}&from=#{config[:period]}")
        res = Net::HTTP.get_response(uri)
        res.body
      rescue Exception => e
        warning "Failed to query graphite: #{e.inspect}"
      end

    status = 0
    message = ''
    data =
      begin
        JSON.parse(body)
      rescue
        []
      end

    unknown "No data from graphite" if data.empty?

    data.each do |metric|
      s, msg = danger(metric)

      message += "#{msg} " unless s == 0
      status = s unless s < status
    end

    if status == 2
      critical message
    elsif status == 1
      warning message
    end
    ok
  end
end

2 个答案:

答案 0 :(得分:3)

好吧,如果你想在收集之前消除nils,你可以做

metric['datapoints'].reject { |p| p.nil? }.collect {|p| p[0].to_f}

而不是

metric['datapoints'].collect {|p| p[0].to_f}

顺便说一句,您average也可以改写为

def average(a)
  a.reduce(0,:+)/a.size
end

答案 1 :(得分:2)

您可以使用Array#compact完全相同的内容:

["a", nil, "b", nil, "c", nil].compact
#=> [ "a", "b", "c" ]

http://ruby-doc.org/core-2.1.3/Array.html#method-i-compact