ab91f1226f
We can't do a lot with classes without names as we can't filter by them, have no idea where they come from, etc. As such it's best to just ignore these.
133 lines
3.5 KiB
Ruby
133 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
|