livingsocial/rearview-engine

View on GitHub
lib/rearview/templates/utilities.rb

Summary

Maintainability
A
2 hrs
Test Coverage
#
# Add re-usable code/functions in this module
#
class Array
  def mean
    self.sum / self.length
  end

  def median
    sorted = self.sort
    mid    = self.length / 2
    if self.length.odd?
      sorted[mid].to_f
    else
      (sorted[mid-1] + sorted[mid]).to_f / 2.0
    end
  end

  def sum
    self.inject(0) { |total, n| total + n.to_f }
  end

  def percentile(number)
    position = (number > 1) ? (number.to_f / 100) : number
    arr = self.map { |x| x || 0 }
    arr.sort[(arr.length * position) - 1]
  end
  
  def sample_variance
    return self.sum / (self.length - 1).to_f
  end

  def stdev
    return Math.sqrt(self.sample_variance)
  end
end


module MonitorUtilities
  # checks if the value is outside the limits for all of the comparison values
  def outside_limits?(value, comparison_values, limit_value, limit_type)
    diffs = comparison_values.map { |v| value - v if value && v }.compact
    diffs.length == 2 && ((limit_type == :lower && diffs.max < limit_value) || (limit_type == :upper && diffs.min > limit_value))
  end

  # percentage of minutes on this monitor that have values outside the limits
  def percentage_errors(metric, comparison_metrics, limit_value, limit_type, minutes = @minutes)
    raise "You can only define the limit type as :upper or :lower." if ![:upper, :lower].include? limit_type

    zipped_values = metric.values.zip(*(Array(comparison_metrics).map(&:values)))

    error_count = zipped_values.count do |(value, *comparison_values)|
      outside_limits?(value, comparison_values, limit_value, limit_type)
    end

    (error_count.to_f / minutes.to_f) * 100
  end

  # checks for a deployment and if found returns data before and after the deploy along with the delta
  def deploy_check(num_points, deploy, metric)
    if metric == deploy
      raise "Error: You've passed the deploy metric to be analyzed against itself, which is not a valid analysis."
    elsif metric.values.size < (num_points * 2) + 1
      raise "Error: Not enough data to evaluate. There must be #{num_points} data points before and after a deploy."
    else
      results = []
      last_deploy = deploy.values.rindex { |v| !v.nil? }

      if last_deploy
        deploy_time = deploy.entries[last_deploy].timestamp

        # If the num_points after the deploy is true then
        if metric.entries.drop_while { |entry| entry.timestamp <= deploy_time }.length == num_points
          before = metric.values.last((num_points * 2) + 1).first(num_points).sum
          after  = metric.values.last(num_points).sum
          delta  = before == 0 ? 0.0 : ((after - before) / before) * 100

          results = [metric.label, before, after, delta]
        end
      end

      results
    end
  end
  
  # determines delta in standard deviation between 2 data sets
  def collect_comparisons(metric)
    five_minute_sv = metric.values.each_slice(metric.values.length / 2).to_a.map { |pair| pair.stdev  }
    five_minute_sv.each_slice(2).to_a.map { |pair| pair.sort }.map { |pair| pair[1].to_f - pair[0].to_f }
  end

  # checks standard deviation delta for metric(s) and returns metric label delta if > deviation
  def collect_aberrations(*metrics, deviation)
    if metrics.first.values.length % 2 == 1
      raise "ERROR: collect_aberrations expects an even number of data points and you passed in #{metrics.first.values.length}"
    end
    aberrations = {}
    metrics.each do |m|
      collect_comparisons(m).inject(aberrations) { |hash, delta| hash[m.label] = delta if delta >= deviation; hash }
    end
    aberrations
  end
end # Class end