# A note on GAUGE_COUNTERS.
#
# The sample_rate argument allows for the parameterization
# of instruments that decide to report data as gauges, that
# would typically be reported as counters.
#
# Aggregating counters is typically done simply with the `+`
# operator, which doesn't preserve the number of unique
# reporters that contributed to the count, or allow for one
# to learn the *average* of the counts posted.
#
# A gauge is typically aggregated by simply *replacing* the
# previous value, however, some systems do *more* with gauges
# when aggregating across multiple sources of that gauge, like,
# average, or compute stdev.
#
# This is problematic, however, when a gauge is being used as
# a counter, to preserve the average / stdev computational
# properties from above, because the interval that the gauge
# is being read it, affects the derivative of the increasing
# count. Instead of the derivative over 60s, the derivative is
# taken every 10s, giving us a derivative value that's approximately
# 1/6th of the actual derivative over 60s.
#
# We compensate for this by allowing Instruments to correct for
# this, and ensure that, even though it's an estimate, the data
# is scaled appropriately to the target aggregation interval, not
# just the collection interval.

module Barnes
  module Instruments
    class RubyGC
      COUNTERS = {
        :count => :'GC.count',
        :major_gc_count => :'GC.major_count',
        :minor_gc_count => :'GC.minor_gc_count' }

      GAUGE_COUNTERS = {}

      # Detect Ruby 2.1 vs 2.2 GC.stat naming
      begin
        GC.stat :total_allocated_objects
      rescue ArgumentError
        GAUGE_COUNTERS.update \
          :total_allocated_object => :'GC.total_allocated_objects',
          :total_freed_object => :'GC.total_freed_objects'
      else
        GAUGE_COUNTERS.update \
          :total_allocated_objects => :'GC.total_allocated_objects',
          :total_freed_objects => :'GC.total_freed_objects'
      end

      def initialize(sample_rate)
        # see header for an explanation of how this sample_rate is used
        @sample_rate = sample_rate
      end

      def start!(state)
        state[:ruby_gc] = GC.stat
      end

      def instrument!(state, counters, gauges)
        last = state[:ruby_gc]
        cur = state[:ruby_gc] = GC.stat

        COUNTERS.each do |stat, metric|
          counters[metric] = cur[stat] - last[stat] if cur.include? stat
        end

        # special treatment gauges
        GAUGE_COUNTERS.each do |stat, metric|
          if cur.include? stat
            val = cur[stat] - last[stat] if cur.include? stat
            gauges[metric] = val * (1/@sample_rate)
          end
        end

        # the rest of the gauges
        cur.each do |k, v|
          unless GAUGE_COUNTERS.include? k
            gauges[:"GC.#{k}"] = v
          end
        end
      end
    end
  end
end