134 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			134 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
module Gitlab
 | 
						|
  module Metrics
 | 
						|
    # Class that sends certain metrics to InfluxDB at a specific interval.
 | 
						|
    #
 | 
						|
    # This class is used to gather statistics that can't be directly associated
 | 
						|
    # with a transaction such as system memory usage, garbage collection
 | 
						|
    # statistics, etc.
 | 
						|
    class Sampler
 | 
						|
      # interval - The sampling interval in seconds.
 | 
						|
      def initialize(interval = Metrics.settings[:sample_interval])
 | 
						|
        interval_half = interval.to_f / 2
 | 
						|
 | 
						|
        @interval       = interval
 | 
						|
        @interval_steps = (-interval_half..interval_half).step(0.1).to_a
 | 
						|
        @last_step      = nil
 | 
						|
 | 
						|
        @metrics = []
 | 
						|
 | 
						|
        @last_minor_gc = Delta.new(GC.stat[:minor_gc_count])
 | 
						|
        @last_major_gc = Delta.new(GC.stat[:major_gc_count])
 | 
						|
 | 
						|
        if Gitlab::Metrics.mri?
 | 
						|
          require 'allocations'
 | 
						|
 | 
						|
          Allocations.start
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def start
 | 
						|
        Thread.new do
 | 
						|
          Thread.current.abort_on_exception = true
 | 
						|
 | 
						|
          loop do
 | 
						|
            sleep(sleep_interval)
 | 
						|
 | 
						|
            sample
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def sample
 | 
						|
        sample_memory_usage
 | 
						|
        sample_file_descriptors
 | 
						|
        sample_objects
 | 
						|
        sample_gc
 | 
						|
 | 
						|
        flush
 | 
						|
      ensure
 | 
						|
        GC::Profiler.clear
 | 
						|
        @metrics.clear
 | 
						|
      end
 | 
						|
 | 
						|
      def flush
 | 
						|
        Metrics.submit_metrics(@metrics.map(&:to_hash))
 | 
						|
      end
 | 
						|
 | 
						|
      def sample_memory_usage
 | 
						|
        add_metric('memory_usage', value: System.memory_usage)
 | 
						|
      end
 | 
						|
 | 
						|
      def sample_file_descriptors
 | 
						|
        add_metric('file_descriptors', value: System.file_descriptor_count)
 | 
						|
      end
 | 
						|
 | 
						|
      if Metrics.mri?
 | 
						|
        def sample_objects
 | 
						|
          sample = Allocations.to_hash
 | 
						|
          counts = sample.each_with_object({}) do |(klass, count), hash|
 | 
						|
            name = klass.name
 | 
						|
 | 
						|
            next unless name
 | 
						|
 | 
						|
            hash[name] = count
 | 
						|
          end
 | 
						|
 | 
						|
          # Symbols aren't allocated so we'll need to add those manually.
 | 
						|
          counts['Symbol'] = Symbol.all_symbols.length
 | 
						|
 | 
						|
          counts.each do |name, count|
 | 
						|
            add_metric('object_counts', { count: count }, type: name)
 | 
						|
          end
 | 
						|
        end
 | 
						|
      else
 | 
						|
        def sample_objects
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def sample_gc
 | 
						|
        time  = GC::Profiler.total_time * 1000.0
 | 
						|
        stats = GC.stat.merge(total_time: time)
 | 
						|
 | 
						|
        # We want the difference of GC runs compared to the last sample, not the
 | 
						|
        # total amount since the process started.
 | 
						|
        stats[:minor_gc_count] =
 | 
						|
          @last_minor_gc.compared_with(stats[:minor_gc_count])
 | 
						|
 | 
						|
        stats[:major_gc_count] =
 | 
						|
          @last_major_gc.compared_with(stats[:major_gc_count])
 | 
						|
 | 
						|
        stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count]
 | 
						|
 | 
						|
        add_metric('gc_statistics', stats)
 | 
						|
      end
 | 
						|
 | 
						|
      def add_metric(series, values, tags = {})
 | 
						|
        prefix = sidekiq? ? 'sidekiq_' : 'rails_'
 | 
						|
 | 
						|
        @metrics << Metric.new("#{prefix}#{series}", values, tags)
 | 
						|
      end
 | 
						|
 | 
						|
      def sidekiq?
 | 
						|
        Sidekiq.server?
 | 
						|
      end
 | 
						|
 | 
						|
      # Returns the sleep interval with a random adjustment.
 | 
						|
      #
 | 
						|
      # The random adjustment is put in place to ensure we:
 | 
						|
      #
 | 
						|
      # 1. Don't generate samples at the exact same interval every time (thus
 | 
						|
      #    potentially missing anything that happens in between samples).
 | 
						|
      # 2. Don't sample data at the same interval two times in a row.
 | 
						|
      def sleep_interval
 | 
						|
        while step = @interval_steps.sample
 | 
						|
          if step != @last_step
 | 
						|
            @last_step = step
 | 
						|
 | 
						|
            return @interval + @last_step
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |