summaryrefslogtreecommitdiff
path: root/lib/gitlab/metrics
diff options
context:
space:
mode:
authorPaweł Chojnacki <pawel@chojnacki.ws>2017-07-04 15:28:34 +0000
committerRémy Coutable <remy@rymai.me>2017-07-04 15:28:34 +0000
commit26ac691a688cb569a7345d8f31a406d467240bb2 (patch)
treef9922c6175116dd1ba0b2b5a2481c173d4a85951 /lib/gitlab/metrics
parent53c626bc8daff623ac4528dd964bc823458345e0 (diff)
downloadgitlab-ce-26ac691a688cb569a7345d8f31a406d467240bb2.tar.gz
Instrument Unicorn with Ruby exporter
Diffstat (limited to 'lib/gitlab/metrics')
-rw-r--r--lib/gitlab/metrics/base_sampler.rb94
-rw-r--r--lib/gitlab/metrics/connection_rack_middleware.rb45
-rw-r--r--lib/gitlab/metrics/influx_sampler.rb (renamed from lib/gitlab/metrics/sampler.rb)40
-rw-r--r--lib/gitlab/metrics/prometheus.rb4
-rw-r--r--lib/gitlab/metrics/unicorn_sampler.rb48
5 files changed, 193 insertions, 38 deletions
diff --git a/lib/gitlab/metrics/base_sampler.rb b/lib/gitlab/metrics/base_sampler.rb
new file mode 100644
index 00000000000..219accfc029
--- /dev/null
+++ b/lib/gitlab/metrics/base_sampler.rb
@@ -0,0 +1,94 @@
+require 'logger'
+module Gitlab
+ module Metrics
+ class BaseSampler
+ def self.initialize_instance(*args)
+ raise "#{name} singleton instance already initialized" if @instance
+ @instance = new(*args)
+ at_exit(&@instance.method(:stop))
+ @instance
+ end
+
+ def self.instance
+ @instance
+ end
+
+ attr_reader :running
+
+ # interval - The sampling interval in seconds.
+ def initialize(interval)
+ interval_half = interval.to_f / 2
+
+ @interval = interval
+ @interval_steps = (-interval_half..interval_half).step(0.1).to_a
+
+ @mutex = Mutex.new
+ end
+
+ def enabled?
+ true
+ end
+
+ def start
+ return unless enabled?
+
+ @mutex.synchronize do
+ return if running
+ @running = true
+
+ @thread = Thread.new do
+ sleep(sleep_interval)
+
+ while running
+ safe_sample
+
+ sleep(sleep_interval)
+ end
+ end
+ end
+ end
+
+ def stop
+ @mutex.synchronize do
+ return unless running
+
+ @running = false
+
+ if @thread
+ @thread.wakeup if @thread.alive?
+ @thread.join
+ @thread = nil
+ end
+ end
+ end
+
+ def safe_sample
+ sample
+ rescue => e
+ Rails.logger.warn("#{self.class}: #{e}, stopping")
+ stop
+ end
+
+ def sample
+ raise NotImplementedError
+ 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
diff --git a/lib/gitlab/metrics/connection_rack_middleware.rb b/lib/gitlab/metrics/connection_rack_middleware.rb
new file mode 100644
index 00000000000..b3da360be8f
--- /dev/null
+++ b/lib/gitlab/metrics/connection_rack_middleware.rb
@@ -0,0 +1,45 @@
+module Gitlab
+ module Metrics
+ class ConnectionRackMiddleware
+ def initialize(app)
+ @app = app
+ end
+
+ def self.rack_request_count
+ @rack_request_count ||= Gitlab::Metrics.counter(:rack_request, 'Rack request count')
+ end
+
+ def self.rack_response_count
+ @rack_response_count ||= Gitlab::Metrics.counter(:rack_response, 'Rack response count')
+ end
+
+ def self.rack_uncaught_errors_count
+ @rack_uncaught_errors_count ||= Gitlab::Metrics.counter(:rack_uncaught_errors, 'Rack connections handling uncaught errors count')
+ end
+
+ def self.rack_execution_time
+ @rack_execution_time ||= Gitlab::Metrics.histogram(:rack_execution_time, 'Rack connection handling execution time',
+ {}, [0.05, 0.1, 0.25, 0.5, 0.7, 1, 1.5, 2, 2.5, 3, 5, 7, 10])
+ end
+
+ def call(env)
+ method = env['REQUEST_METHOD'].downcase
+ started = Time.now.to_f
+ begin
+ ConnectionRackMiddleware.rack_request_count.increment(method: method)
+
+ status, headers, body = @app.call(env)
+
+ ConnectionRackMiddleware.rack_response_count.increment(method: method, status: status)
+ [status, headers, body]
+ rescue
+ ConnectionRackMiddleware.rack_uncaught_errors_count.increment
+ raise
+ ensure
+ elapsed = Time.now.to_f - started
+ ConnectionRackMiddleware.rack_execution_time.observe({}, elapsed)
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/influx_sampler.rb
index 0000450d9bb..6db1dd755b7 100644
--- a/lib/gitlab/metrics/sampler.rb
+++ b/lib/gitlab/metrics/influx_sampler.rb
@@ -5,14 +5,11 @@ module Gitlab
# 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
+ class InfluxSampler < BaseSampler
# 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
+ super(interval)
+ @last_step = nil
@metrics = []
@@ -26,18 +23,6 @@ module Gitlab
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
@@ -86,7 +71,7 @@ module Gitlab
end
def sample_gc
- time = GC::Profiler.total_time * 1000.0
+ 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
@@ -111,23 +96,6 @@ module Gitlab
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
diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb
index 9d314a56e58..fb7bbc7cfc7 100644
--- a/lib/gitlab/metrics/prometheus.rb
+++ b/lib/gitlab/metrics/prometheus.rb
@@ -29,8 +29,8 @@ module Gitlab
provide_metric(name) || registry.summary(name, docstring, base_labels)
end
- def gauge(name, docstring, base_labels = {})
- provide_metric(name) || registry.gauge(name, docstring, base_labels)
+ def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
+ provide_metric(name) || registry.gauge(name, docstring, base_labels, multiprocess_mode)
end
def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS)
diff --git a/lib/gitlab/metrics/unicorn_sampler.rb b/lib/gitlab/metrics/unicorn_sampler.rb
new file mode 100644
index 00000000000..f6987252039
--- /dev/null
+++ b/lib/gitlab/metrics/unicorn_sampler.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module Metrics
+ class UnicornSampler < BaseSampler
+ def initialize(interval)
+ super(interval)
+ end
+
+ def unicorn_active_connections
+ @unicorn_active_connections ||= Gitlab::Metrics.gauge(:unicorn_active_connections, 'Unicorn active connections', {}, :max)
+ end
+
+ def unicorn_queued_connections
+ @unicorn_queued_connections ||= Gitlab::Metrics.gauge(:unicorn_queued_connections, 'Unicorn queued connections', {}, :max)
+ end
+
+ def enabled?
+ # Raindrops::Linux.tcp_listener_stats is only present on Linux
+ unicorn_with_listeners? && Raindrops::Linux.respond_to?(:tcp_listener_stats)
+ end
+
+ def sample
+ Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
+ unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
+ unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
+ end
+
+ Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
+ unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
+ unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
+ end
+ end
+
+ private
+
+ def tcp_listeners
+ @tcp_listeners ||= Unicorn.listener_names.grep(%r{\A[^/]+:\d+\z})
+ end
+
+ def unix_listeners
+ @unix_listeners ||= Unicorn.listener_names - tcp_listeners
+ end
+
+ def unicorn_with_listeners?
+ defined?(Unicorn) && Unicorn.listener_names.any?
+ end
+ end
+ end
+end