From 5bc099c2de1e05fa4dbe45b59caeced209834178 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Tue, 9 May 2017 09:39:28 +0200 Subject: Prometheus metrics first pass metrics wip --- lib/gitlab/metrics.rb | 65 ++++++++++++++++++++++++++++++-- lib/gitlab/metrics/dummy_metric.rb | 29 ++++++++++++++ lib/gitlab/metrics/prometheus_sampler.rb | 51 +++++++++++++++++++++++++ 3 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 lib/gitlab/metrics/dummy_metric.rb create mode 100644 lib/gitlab/metrics/prometheus_sampler.rb (limited to 'lib') diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index cb8db2f1e9f..e784ca785f0 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -1,3 +1,5 @@ +require 'prometheus/client' + module Gitlab module Metrics extend Gitlab::CurrentSettings @@ -9,6 +11,7 @@ module Gitlab def self.settings @settings ||= { enabled: current_application_settings[:metrics_enabled], + prometheus_metrics_enabled: true, pool_size: current_application_settings[:metrics_pool_size], timeout: current_application_settings[:metrics_timeout], method_call_threshold: current_application_settings[:metrics_method_call_threshold], @@ -19,10 +22,18 @@ module Gitlab } end - def self.enabled? + def self.prometheus_metrics_enabled? + settings[:prometheus_metrics_enabled] || false + end + + def self.influx_metrics_enabled? settings[:enabled] || false end + def self.enabled? + influx_metrics_enabled? || prometheus_metrics_enabled? || false + end + def self.mri? RUBY_ENGINE == 'ruby' end @@ -38,10 +49,58 @@ module Gitlab @pool end + def self.registry + @registry ||= ::Prometheus::Client.registry + end + + def self.counter(name, docstring, base_labels = {}) + dummy_metric || registry.get(name) || registry.counter(name, docstring, base_labels) + end + + def self.summary(name, docstring, base_labels = {}) + dummy_metric || registry.get(name) || registry.summary(name, docstring, base_labels) + end + + def self.gauge(name, docstring, base_labels = {}) + dummy_metric || registry.get(name) || registry.gauge(name, docstring, base_labels) + end + + def self.histogram(name, docstring, base_labels = {}, buckets = Histogram::DEFAULT_BUCKETS) + dummy_metric || registry.get(name) || registry.histogram(name, docstring, base_labels, buckets) + end + + def self.dummy_metric + unless prometheus_metrics_enabled? + DummyMetric.new + end + end + def self.submit_metrics(metrics) prepared = prepare_metrics(metrics) - pool.with do |connection| + if prometheus_metrics_enabled? + metrics.map do |metric| + known = [:series, :tags,:values, :timestamp] + value = metric&.[](:values)&.[](:value) + handled= [:rails_gc_statistics] + if handled.include? metric[:series].to_sym + next + end + + if metric.keys.any? {|k| !known.include?(k)} || value.nil? + print metric + print "\n" + + {:series=>"rails_gc_statistics", :tags=>{}, :values=>{:count=>0, :heap_allocated_pages=>4245, :heap_sorted_length=>4426, :heap_allocatable_pages=>0, :heap_available_slots=>1730264, :heap_live_slots=>1729935, :heap_free_slots=>329, :heap_final_slots=>0, :heap_marked_slots=>1184216, :heap_swept_slots=>361843, :heap_eden_pages=>4245, :heap_tomb_pages=>0, :total_allocated_pages=>4245, :total_freed_pages=>0, :total_allocated_objects=>15670757, :total_freed_objects=>13940822, :malloc_increase_bytes=>4842256, :malloc_increase_bytes_limit=>29129457, :minor_gc_count=>0, :major_gc_count=>0, :remembered_wb_unprotected_objects=>39905, :remembered_wb_unprotected_objects_limit=>74474, :old_objects=>1078731, :old_objects_limit=>1975860, :oldmalloc_increase_bytes=>4842640, :oldmalloc_increase_bytes_limit=>31509677, :total_time=>0.0}, :timestamp=>1494356175592659968} + + next + end + metric_value = gauge(metric[:series].to_sym, metric[:series]) + metric_value.set(metric[:tags], value) + end + end + + pool&.with do |connection| prepared.each_slice(settings[:packet_size]) do |slice| begin connection.write_points(slice) @@ -148,7 +207,7 @@ module Gitlab # When enabled this should be set before being used as the usual pattern # "@foo ||= bar" is _not_ thread-safe. - if enabled? + if influx_metrics_enabled? @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do host = settings[:host] port = settings[:port] diff --git a/lib/gitlab/metrics/dummy_metric.rb b/lib/gitlab/metrics/dummy_metric.rb new file mode 100644 index 00000000000..d27bb83854a --- /dev/null +++ b/lib/gitlab/metrics/dummy_metric.rb @@ -0,0 +1,29 @@ +module Gitlab + module Metrics + # Mocks ::Prometheus::Client::Metric and all derived metrics + class DummyMetric + def get(*args) + raise NotImplementedError + end + + def values(*args) + raise NotImplementedError + end + + # counter + def increment(*args) + # noop + end + + # gauge + def set(*args) + # noop + end + + # histogram / summary + def observe(*args) + # noop + end + end + end +end diff --git a/lib/gitlab/metrics/prometheus_sampler.rb b/lib/gitlab/metrics/prometheus_sampler.rb new file mode 100644 index 00000000000..5f90d4f0b66 --- /dev/null +++ b/lib/gitlab/metrics/prometheus_sampler.rb @@ -0,0 +1,51 @@ +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 PrometheusSamples + # 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 + end + + def start + Thread.new do + Thread.current.abort_on_exception = true + + loop do + sleep(sleep_interval) + + sample + end + end + 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 -- cgit v1.2.1 From c28546177e2b4d5f7f3cc0e5b3a7b404206565fb Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Fri, 19 May 2017 12:49:15 +0200 Subject: Prometheus settings --- lib/api/settings.rb | 1 + lib/gitlab/metrics.rb | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) (limited to 'lib') diff --git a/lib/api/settings.rb b/lib/api/settings.rb index 82f513c984e..25027c3b114 100644 --- a/lib/api/settings.rb +++ b/lib/api/settings.rb @@ -110,6 +110,7 @@ module API optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts" optional :max_pages_size, type: Integer, desc: 'Maximum size of pages in MB' optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)' + optional :prometheus_metrics_enabled, type: Boolean, desc: 'Enable Prometheus metrics' optional :metrics_enabled, type: Boolean, desc: 'Enable the InfluxDB metrics' given metrics_enabled: ->(val) { val } do requires :metrics_host, type: String, desc: 'The InfluxDB host' diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index e784ca785f0..6f50c0aa028 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -10,15 +10,15 @@ module Gitlab def self.settings @settings ||= { - enabled: current_application_settings[:metrics_enabled], - prometheus_metrics_enabled: true, - pool_size: current_application_settings[:metrics_pool_size], - timeout: current_application_settings[:metrics_timeout], - method_call_threshold: current_application_settings[:metrics_method_call_threshold], - host: current_application_settings[:metrics_host], - port: current_application_settings[:metrics_port], - sample_interval: current_application_settings[:metrics_sample_interval] || 15, - packet_size: current_application_settings[:metrics_packet_size] || 1 + enabled: current_application_settings[:metrics_enabled], + prometheus_metrics_enabled: current_application_settings[:prometheus_metrics_enabled], + pool_size: current_application_settings[:metrics_pool_size], + timeout: current_application_settings[:metrics_timeout], + method_call_threshold: current_application_settings[:metrics_method_call_threshold], + host: current_application_settings[:metrics_host], + port: current_application_settings[:metrics_port], + sample_interval: current_application_settings[:metrics_sample_interval] || 15, + packet_size: current_application_settings[:metrics_packet_size] || 1 } end @@ -31,7 +31,7 @@ module Gitlab end def self.enabled? - influx_metrics_enabled? || prometheus_metrics_enabled? || false + influx_metrics_enabled? || prometheus_metrics_enabled? end def self.mri? -- cgit v1.2.1 From 0f4050430d400daffbc5a68b15d79b896bb8a692 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Fri, 19 May 2017 17:03:10 +0200 Subject: Split metrics from health controller into metrics controller --- lib/gitlab/metrics.rb | 22 ---------------------- 1 file changed, 22 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 6f50c0aa028..9783d4e3582 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -78,28 +78,6 @@ module Gitlab def self.submit_metrics(metrics) prepared = prepare_metrics(metrics) - if prometheus_metrics_enabled? - metrics.map do |metric| - known = [:series, :tags,:values, :timestamp] - value = metric&.[](:values)&.[](:value) - handled= [:rails_gc_statistics] - if handled.include? metric[:series].to_sym - next - end - - if metric.keys.any? {|k| !known.include?(k)} || value.nil? - print metric - print "\n" - - {:series=>"rails_gc_statistics", :tags=>{}, :values=>{:count=>0, :heap_allocated_pages=>4245, :heap_sorted_length=>4426, :heap_allocatable_pages=>0, :heap_available_slots=>1730264, :heap_live_slots=>1729935, :heap_free_slots=>329, :heap_final_slots=>0, :heap_marked_slots=>1184216, :heap_swept_slots=>361843, :heap_eden_pages=>4245, :heap_tomb_pages=>0, :total_allocated_pages=>4245, :total_freed_pages=>0, :total_allocated_objects=>15670757, :total_freed_objects=>13940822, :malloc_increase_bytes=>4842256, :malloc_increase_bytes_limit=>29129457, :minor_gc_count=>0, :major_gc_count=>0, :remembered_wb_unprotected_objects=>39905, :remembered_wb_unprotected_objects_limit=>74474, :old_objects=>1078731, :old_objects_limit=>1975860, :oldmalloc_increase_bytes=>4842640, :oldmalloc_increase_bytes_limit=>31509677, :total_time=>0.0}, :timestamp=>1494356175592659968} - - next - end - metric_value = gauge(metric[:series].to_sym, metric[:series]) - metric_value.set(metric[:tags], value) - end - end - pool&.with do |connection| prepared.each_slice(settings[:packet_size]) do |slice| begin -- cgit v1.2.1 From 138a5577a9e083933c02675dfa11112693ca7a94 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Fri, 19 May 2017 17:23:34 +0200 Subject: remove prometheus sampler --- lib/gitlab/metrics/prometheus_sampler.rb | 51 -------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 lib/gitlab/metrics/prometheus_sampler.rb (limited to 'lib') diff --git a/lib/gitlab/metrics/prometheus_sampler.rb b/lib/gitlab/metrics/prometheus_sampler.rb deleted file mode 100644 index 5f90d4f0b66..00000000000 --- a/lib/gitlab/metrics/prometheus_sampler.rb +++ /dev/null @@ -1,51 +0,0 @@ -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 PrometheusSamples - # 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 - end - - def start - Thread.new do - Thread.current.abort_on_exception = true - - loop do - sleep(sleep_interval) - - sample - end - end - 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 -- cgit v1.2.1 From ef9d9ddeb2e063fa8ed1b01e4f82cc9662b919b2 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Mon, 22 May 2017 15:47:04 +0200 Subject: Add tests for metrics behavior --- lib/gitlab/metrics.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 9783d4e3582..a41cbd214a1 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -54,23 +54,25 @@ module Gitlab end def self.counter(name, docstring, base_labels = {}) - dummy_metric || registry.get(name) || registry.counter(name, docstring, base_labels) + provide_metric(name) || registry.counter(name, docstring, base_labels) end def self.summary(name, docstring, base_labels = {}) - dummy_metric || registry.get(name) || registry.summary(name, docstring, base_labels) + provide_metric(name) || registry.summary(name, docstring, base_labels) end def self.gauge(name, docstring, base_labels = {}) - dummy_metric || registry.get(name) || registry.gauge(name, docstring, base_labels) + provide_metric(name) || registry.gauge(name, docstring, base_labels) end - def self.histogram(name, docstring, base_labels = {}, buckets = Histogram::DEFAULT_BUCKETS) - dummy_metric || registry.get(name) || registry.histogram(name, docstring, base_labels, buckets) + def self.histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS) + provide_metric(name) || registry.histogram(name, docstring, base_labels, buckets) end - def self.dummy_metric - unless prometheus_metrics_enabled? + def self.provide_metric(name) + if prometheus_metrics_enabled? + registry.get(name) + else DummyMetric.new end end -- cgit v1.2.1 From c134a72cdb7e6de8b70dc60de99cf4edc68a9227 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Mon, 29 May 2017 14:19:43 +0200 Subject: Move Prometheus presentation logic to PrometheusText + Use NullMetrics to mock metrics when unused + Use method_missing in NullMetrics mocking + Update prometheus gem to version that correctly uses transitive dependencies + Ensure correct folders are used in Multiprocess prometheus client tests. + rename Sessions controller's metric --- lib/gitlab/health_checks/prometheus_text.rb | 38 +++++++++++++++++++++++++++++ lib/gitlab/metrics.rb | 2 +- lib/gitlab/metrics/dummy_metric.rb | 29 ---------------------- lib/gitlab/metrics/null_metric.rb | 19 +++++++++++++++ 4 files changed, 58 insertions(+), 30 deletions(-) create mode 100644 lib/gitlab/health_checks/prometheus_text.rb delete mode 100644 lib/gitlab/metrics/dummy_metric.rb create mode 100644 lib/gitlab/metrics/null_metric.rb (limited to 'lib') diff --git a/lib/gitlab/health_checks/prometheus_text.rb b/lib/gitlab/health_checks/prometheus_text.rb new file mode 100644 index 00000000000..a01e6b2be1f --- /dev/null +++ b/lib/gitlab/health_checks/prometheus_text.rb @@ -0,0 +1,38 @@ +module Gitlab::HealthChecks + class PrometheusText + def marshal(metrics) + metrics_with_type_declarations(metrics).join("\n") + end + + private + + def metrics_with_type_declarations(metrics) + type_declaration_added = {} + + metrics.flat_map do |metric| + metric_lines = [] + + unless type_declaration_added.has_key?(metric.name) + type_declaration_added[metric.name] = true + metric_lines << metric_type_declaration(metric) + end + + metric_lines << metric_text(metric) + end + end + + def metric_type_declaration(metric) + "# TYPE #{metric.name} gauge" + end + + def metric_text(metric) + labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || '' + + if labels.empty? + "#{metric.name} #{metric.value}" + else + "#{metric.name}{#{labels}} #{metric.value}" + end + end + end +end diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index a41cbd214a1..34f6b32f7da 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -73,7 +73,7 @@ module Gitlab if prometheus_metrics_enabled? registry.get(name) else - DummyMetric.new + NullMetric.new end end diff --git a/lib/gitlab/metrics/dummy_metric.rb b/lib/gitlab/metrics/dummy_metric.rb deleted file mode 100644 index d27bb83854a..00000000000 --- a/lib/gitlab/metrics/dummy_metric.rb +++ /dev/null @@ -1,29 +0,0 @@ -module Gitlab - module Metrics - # Mocks ::Prometheus::Client::Metric and all derived metrics - class DummyMetric - def get(*args) - raise NotImplementedError - end - - def values(*args) - raise NotImplementedError - end - - # counter - def increment(*args) - # noop - end - - # gauge - def set(*args) - # noop - end - - # histogram / summary - def observe(*args) - # noop - end - end - end -end diff --git a/lib/gitlab/metrics/null_metric.rb b/lib/gitlab/metrics/null_metric.rb new file mode 100644 index 00000000000..1501cd38676 --- /dev/null +++ b/lib/gitlab/metrics/null_metric.rb @@ -0,0 +1,19 @@ +module Gitlab + module Metrics + # Mocks ::Prometheus::Client::Metric and all derived metrics + class NullMetric + def method_missing(name, *args, &block) + nil + end + + # these methods shouldn't be called when metrics are disabled + def get(*args) + raise NotImplementedError + end + + def values(*args) + raise NotImplementedError + end + end + end +end -- cgit v1.2.1 From ae8f7666e597493ab404f8524c1216a924338291 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Mon, 29 May 2017 23:23:19 +0200 Subject: Add prometheus text formatter + rename controler method to #index from #metrics + remove assertion from nullMetric --- lib/gitlab/health_checks/prometheus_text.rb | 38 ---------------------- lib/gitlab/health_checks/prometheus_text_format.rb | 38 ++++++++++++++++++++++ lib/gitlab/metrics/null_metric.rb | 9 ----- 3 files changed, 38 insertions(+), 47 deletions(-) delete mode 100644 lib/gitlab/health_checks/prometheus_text.rb create mode 100644 lib/gitlab/health_checks/prometheus_text_format.rb (limited to 'lib') diff --git a/lib/gitlab/health_checks/prometheus_text.rb b/lib/gitlab/health_checks/prometheus_text.rb deleted file mode 100644 index a01e6b2be1f..00000000000 --- a/lib/gitlab/health_checks/prometheus_text.rb +++ /dev/null @@ -1,38 +0,0 @@ -module Gitlab::HealthChecks - class PrometheusText - def marshal(metrics) - metrics_with_type_declarations(metrics).join("\n") - end - - private - - def metrics_with_type_declarations(metrics) - type_declaration_added = {} - - metrics.flat_map do |metric| - metric_lines = [] - - unless type_declaration_added.has_key?(metric.name) - type_declaration_added[metric.name] = true - metric_lines << metric_type_declaration(metric) - end - - metric_lines << metric_text(metric) - end - end - - def metric_type_declaration(metric) - "# TYPE #{metric.name} gauge" - end - - def metric_text(metric) - labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || '' - - if labels.empty? - "#{metric.name} #{metric.value}" - else - "#{metric.name}{#{labels}} #{metric.value}" - end - end - end -end diff --git a/lib/gitlab/health_checks/prometheus_text_format.rb b/lib/gitlab/health_checks/prometheus_text_format.rb new file mode 100644 index 00000000000..5fc6f19c37c --- /dev/null +++ b/lib/gitlab/health_checks/prometheus_text_format.rb @@ -0,0 +1,38 @@ +module Gitlab::HealthChecks + class PrometheusTextFormat + def marshal(metrics) + metrics_with_type_declarations(metrics).join("\n") + end + + private + + def metrics_with_type_declarations(metrics) + type_declaration_added = {} + + metrics.flat_map do |metric| + metric_lines = [] + + unless type_declaration_added.has_key?(metric.name) + type_declaration_added[metric.name] = true + metric_lines << metric_type_declaration(metric) + end + + metric_lines << metric_text(metric) + end + end + + def metric_type_declaration(metric) + "# TYPE #{metric.name} gauge" + end + + def metric_text(metric) + labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || '' + + if labels.empty? + "#{metric.name} #{metric.value}" + else + "#{metric.name}{#{labels}} #{metric.value}" + end + end + end +end diff --git a/lib/gitlab/metrics/null_metric.rb b/lib/gitlab/metrics/null_metric.rb index 1501cd38676..3b5a2907195 100644 --- a/lib/gitlab/metrics/null_metric.rb +++ b/lib/gitlab/metrics/null_metric.rb @@ -5,15 +5,6 @@ module Gitlab def method_missing(name, *args, &block) nil end - - # these methods shouldn't be called when metrics are disabled - def get(*args) - raise NotImplementedError - end - - def values(*args) - raise NotImplementedError - end end end end -- cgit v1.2.1 From b668aaf4268d552315152057729f73f5c5d72147 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Tue, 30 May 2017 00:18:46 +0200 Subject: Split the metrics implementation to separate modules for Influx and Prometheus --- lib/gitlab/health_checks/prometheus_text_format.rb | 54 +++--- lib/gitlab/metrics.rb | 196 +-------------------- lib/gitlab/metrics/influx_db.rb | 163 +++++++++++++++++ lib/gitlab/metrics/prometheus.rb | 41 +++++ 4 files changed, 235 insertions(+), 219 deletions(-) create mode 100644 lib/gitlab/metrics/influx_db.rb create mode 100644 lib/gitlab/metrics/prometheus.rb (limited to 'lib') diff --git a/lib/gitlab/health_checks/prometheus_text_format.rb b/lib/gitlab/health_checks/prometheus_text_format.rb index 5fc6f19c37c..e7ec1c516a2 100644 --- a/lib/gitlab/health_checks/prometheus_text_format.rb +++ b/lib/gitlab/health_checks/prometheus_text_format.rb @@ -1,38 +1,40 @@ -module Gitlab::HealthChecks - class PrometheusTextFormat - def marshal(metrics) - metrics_with_type_declarations(metrics).join("\n") - end +module Gitlab + module HealthChecks + class PrometheusTextFormat + def marshal(metrics) + metrics_with_type_declarations(metrics).join("\n") + end - private + private - def metrics_with_type_declarations(metrics) - type_declaration_added = {} + def metrics_with_type_declarations(metrics) + type_declaration_added = {} - metrics.flat_map do |metric| - metric_lines = [] + metrics.flat_map do |metric| + metric_lines = [] - unless type_declaration_added.has_key?(metric.name) - type_declaration_added[metric.name] = true - metric_lines << metric_type_declaration(metric) - end + unless type_declaration_added.has_key?(metric.name) + type_declaration_added[metric.name] = true + metric_lines << metric_type_declaration(metric) + end - metric_lines << metric_text(metric) + metric_lines << metric_text(metric) + end end - end - def metric_type_declaration(metric) - "# TYPE #{metric.name} gauge" - end + def metric_type_declaration(metric) + "# TYPE #{metric.name} gauge" + end - def metric_text(metric) - labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || '' + def metric_text(metric) + labels = metric.labels&.map { |key, value| "#{key}=\"#{value}\"" }&.join(',') || '' - if labels.empty? - "#{metric.name} #{metric.value}" - else - "#{metric.name}{#{labels}} #{metric.value}" + if labels.empty? + "#{metric.name} #{metric.value}" + else + "#{metric.name}{#{labels}} #{metric.value}" + end end end end -end +end \ No newline at end of file diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 34f6b32f7da..995715417f8 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -1,200 +1,10 @@ -require 'prometheus/client' - module Gitlab module Metrics - extend Gitlab::CurrentSettings - - RAILS_ROOT = Rails.root.to_s - METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s - PATH_REGEX = /^#{RAILS_ROOT}\/?/ - - def self.settings - @settings ||= { - enabled: current_application_settings[:metrics_enabled], - prometheus_metrics_enabled: current_application_settings[:prometheus_metrics_enabled], - pool_size: current_application_settings[:metrics_pool_size], - timeout: current_application_settings[:metrics_timeout], - method_call_threshold: current_application_settings[:metrics_method_call_threshold], - host: current_application_settings[:metrics_host], - port: current_application_settings[:metrics_port], - sample_interval: current_application_settings[:metrics_sample_interval] || 15, - packet_size: current_application_settings[:metrics_packet_size] || 1 - } - end - - def self.prometheus_metrics_enabled? - settings[:prometheus_metrics_enabled] || false - end - - def self.influx_metrics_enabled? - settings[:enabled] || false - end + extend Gitlab::Metrics::InfluxDb + extend Gitlab::Metrics::Prometheus def self.enabled? influx_metrics_enabled? || prometheus_metrics_enabled? end - - def self.mri? - RUBY_ENGINE == 'ruby' - end - - def self.method_call_threshold - # This is memoized since this method is called for every instrumented - # method. Loading data from an external cache on every method call slows - # things down too much. - @method_call_threshold ||= settings[:method_call_threshold] - end - - def self.pool - @pool - end - - def self.registry - @registry ||= ::Prometheus::Client.registry - end - - def self.counter(name, docstring, base_labels = {}) - provide_metric(name) || registry.counter(name, docstring, base_labels) - end - - def self.summary(name, docstring, base_labels = {}) - provide_metric(name) || registry.summary(name, docstring, base_labels) - end - - def self.gauge(name, docstring, base_labels = {}) - provide_metric(name) || registry.gauge(name, docstring, base_labels) - end - - def self.histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS) - provide_metric(name) || registry.histogram(name, docstring, base_labels, buckets) - end - - def self.provide_metric(name) - if prometheus_metrics_enabled? - registry.get(name) - else - NullMetric.new - end - end - - def self.submit_metrics(metrics) - prepared = prepare_metrics(metrics) - - pool&.with do |connection| - prepared.each_slice(settings[:packet_size]) do |slice| - begin - connection.write_points(slice) - rescue StandardError - end - end - end - rescue Errno::EADDRNOTAVAIL, SocketError => ex - Gitlab::EnvironmentLogger.error('Cannot resolve InfluxDB address. GitLab Performance Monitoring will not work.') - Gitlab::EnvironmentLogger.error(ex) - end - - def self.prepare_metrics(metrics) - metrics.map do |hash| - new_hash = hash.symbolize_keys - - new_hash[:tags].each do |key, value| - if value.blank? - new_hash[:tags].delete(key) - else - new_hash[:tags][key] = escape_value(value) - end - end - - new_hash - end - end - - def self.escape_value(value) - value.to_s.gsub('=', '\\=') - end - - # Measures the execution time of a block. - # - # Example: - # - # Gitlab::Metrics.measure(:find_by_username_duration) do - # User.find_by_username(some_username) - # end - # - # name - The name of the field to store the execution time in. - # - # Returns the value yielded by the supplied block. - def self.measure(name) - trans = current_transaction - - return yield unless trans - - real_start = Time.now.to_f - cpu_start = System.cpu_time - - retval = yield - - cpu_stop = System.cpu_time - real_stop = Time.now.to_f - - real_time = (real_stop - real_start) * 1000.0 - cpu_time = cpu_stop - cpu_start - - trans.increment("#{name}_real_time", real_time) - trans.increment("#{name}_cpu_time", cpu_time) - trans.increment("#{name}_call_count", 1) - - retval - end - - # Adds a tag to the current transaction (if any) - # - # name - The name of the tag to add. - # value - The value of the tag. - def self.tag_transaction(name, value) - trans = current_transaction - - trans&.add_tag(name, value) - end - - # Sets the action of the current transaction (if any) - # - # action - The name of the action. - def self.action=(action) - trans = current_transaction - - trans&.action = action - end - - # Tracks an event. - # - # See `Gitlab::Metrics::Transaction#add_event` for more details. - def self.add_event(*args) - trans = current_transaction - - trans&.add_event(*args) - end - - # Returns the prefix to use for the name of a series. - def self.series_prefix - @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_' - end - - # Allow access from other metrics related middlewares - def self.current_transaction - Transaction.current - end - - # When enabled this should be set before being used as the usual pattern - # "@foo ||= bar" is _not_ thread-safe. - if influx_metrics_enabled? - @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do - host = settings[:host] - port = settings[:port] - - InfluxDB::Client. - new(udp: { host: host, port: port }) - end - end end -end +end \ No newline at end of file diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb new file mode 100644 index 00000000000..c5d7a2b56b9 --- /dev/null +++ b/lib/gitlab/metrics/influx_db.rb @@ -0,0 +1,163 @@ +module Gitlab + module Metrics + module InfluxDb + include Gitlab::CurrentSettings + + def influx_metrics_enabled? + settings[:enabled] || false + end + + RAILS_ROOT = Rails.root.to_s + METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s + PATH_REGEX = /^#{RAILS_ROOT}\/?/ + + def settings + @settings ||= { + enabled: current_application_settings[:metrics_enabled], + pool_size: current_application_settings[:metrics_pool_size], + timeout: current_application_settings[:metrics_timeout], + method_call_threshold: current_application_settings[:metrics_method_call_threshold], + host: current_application_settings[:metrics_host], + port: current_application_settings[:metrics_port], + sample_interval: current_application_settings[:metrics_sample_interval] || 15, + packet_size: current_application_settings[:metrics_packet_size] || 1 + } + end + + def mri? + RUBY_ENGINE == 'ruby' + end + + def method_call_threshold + # This is memoized since this method is called for every instrumented + # method. Loading data from an external cache on every method call slows + # things down too much. + @method_call_threshold ||= settings[:method_call_threshold] + end + + def pool + @pool + end + + def submit_metrics(metrics) + prepared = prepare_metrics(metrics) + + pool&.with do |connection| + prepared.each_slice(settings[:packet_size]) do |slice| + begin + connection.write_points(slice) + rescue StandardError + end + end + end + rescue Errno::EADDRNOTAVAIL, SocketError => ex + Gitlab::EnvironmentLogger.error('Cannot resolve InfluxDB address. GitLab Performance Monitoring will not work.') + Gitlab::EnvironmentLogger.error(ex) + end + + def prepare_metrics(metrics) + metrics.map do |hash| + new_hash = hash.symbolize_keys + + new_hash[:tags].each do |key, value| + if value.blank? + new_hash[:tags].delete(key) + else + new_hash[:tags][key] = escape_value(value) + end + end + + new_hash + end + end + + def escape_value(value) + value.to_s.gsub('=', '\\=') + end + + # Measures the execution time of a block. + # + # Example: + # + # Gitlab::Metrics.measure(:find_by_username_duration) do + # User.find_by_username(some_username) + # end + # + # name - The name of the field to store the execution time in. + # + # Returns the value yielded by the supplied block. + def measure(name) + trans = current_transaction + + return yield unless trans + + real_start = Time.now.to_f + cpu_start = System.cpu_time + + retval = yield + + cpu_stop = System.cpu_time + real_stop = Time.now.to_f + + real_time = (real_stop - real_start) * 1000.0 + cpu_time = cpu_stop - cpu_start + + trans.increment("#{name}_real_time", real_time) + trans.increment("#{name}_cpu_time", cpu_time) + trans.increment("#{name}_call_count", 1) + + retval + end + + # Adds a tag to the current transaction (if any) + # + # name - The name of the tag to add. + # value - The value of the tag. + def tag_transaction(name, value) + trans = current_transaction + + trans&.add_tag(name, value) + end + + # Sets the action of the current transaction (if any) + # + # action - The name of the action. + def action=(action) + trans = current_transaction + + trans&.action = action + end + + # Tracks an event. + # + # See `Gitlab::Metrics::Transaction#add_event` for more details. + def add_event(*args) + trans = current_transaction + + trans&.add_event(*args) + end + + # Returns the prefix to use for the name of a series. + def series_prefix + @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_' + end + + # Allow access from other metrics related middlewares + def current_transaction + Transaction.current + end + + # When enabled this should be set before being used as the usual pattern + # "@foo ||= bar" is _not_ thread-safe. + if influx_metrics_enabled? + @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do + host = settings[:host] + port = settings[:port] + + InfluxDB::Client. + new(udp: { host: host, port: port }) + end + end + end + end +end \ No newline at end of file diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb new file mode 100644 index 00000000000..493da4d779c --- /dev/null +++ b/lib/gitlab/metrics/prometheus.rb @@ -0,0 +1,41 @@ +require 'prometheus/client' + +module Gitlab + module Metrics + module Prometheus + include Gitlab::CurrentSettings + + def prometheus_metrics_enabled? + @prometheus_metrics_enabled ||= current_application_settings[:prometheus_metrics_enabled] || false + end + + def registry + @registry ||= ::Prometheus::Client.registry + end + + def counter(name, docstring, base_labels = {}) + provide_metric(name) || registry.counter(name, docstring, base_labels) + end + + def summary(name, docstring, base_labels = {}) + 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) + end + + def histogram(name, docstring, base_labels = {}, buckets = ::Prometheus::Client::Histogram::DEFAULT_BUCKETS) + provide_metric(name) || registry.histogram(name, docstring, base_labels, buckets) + end + + def provide_metric(name) + if prometheus_metrics_enabled? + registry.get(name) + else + NullMetric.new + end + end + end + end +end \ No newline at end of file -- cgit v1.2.1 From 68b946e3c8d3a1e7463cf8923ecd748f33c8ccee Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Thu, 1 Jun 2017 01:46:19 +0200 Subject: Fix circular dependency condition with `current_application_settings` `current_application_settings` used by `influx_metrics_enabled` executed a markdown parsing code that was measured using `Gitlab::Metrics.measure` But since the Gitlab::Metrics::InfluxDb was not yet build so Gitlab::Metrics did not yet have `measure` method. Causing the NoMethodError. However If run was successful at least once then result was cached in a file and this code never executed again. Which caused this issue to only show up in CI preparation step. --- lib/gitlab/metrics/influx_db.rb | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb index c5d7a2b56b9..3a39791edbf 100644 --- a/lib/gitlab/metrics/influx_db.rb +++ b/lib/gitlab/metrics/influx_db.rb @@ -1,7 +1,11 @@ module Gitlab module Metrics module InfluxDb - include Gitlab::CurrentSettings + extend Gitlab::CurrentSettings + extend self + + MUTEX = Mutex.new + private_constant :MUTEX def influx_metrics_enabled? settings[:enabled] || false @@ -35,10 +39,6 @@ module Gitlab @method_call_threshold ||= settings[:method_call_threshold] end - def pool - @pool - end - def submit_metrics(metrics) prepared = prepare_metrics(metrics) @@ -143,21 +143,28 @@ module Gitlab end # Allow access from other metrics related middlewares - def current_transaction + def current_transaction Transaction.current end # When enabled this should be set before being used as the usual pattern # "@foo ||= bar" is _not_ thread-safe. - if influx_metrics_enabled? - @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do - host = settings[:host] - port = settings[:port] - - InfluxDB::Client. - new(udp: { host: host, port: port }) + def pool + if influx_metrics_enabled? + if @pool.nil? + MUTEX.synchronize do + @pool ||= ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do + host = settings[:host] + port = settings[:port] + + InfluxDB::Client. + new(udp: { host: host, port: port }) + end + end + end + @pool end end end end -end \ No newline at end of file +end -- cgit v1.2.1 From 7b75004d603618d67aa50574bfc80627a6eaf72b Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Thu, 1 Jun 2017 14:38:40 +0200 Subject: Add missing trailing newlines --- lib/gitlab/health_checks/prometheus_text_format.rb | 2 +- lib/gitlab/metrics.rb | 2 +- lib/gitlab/metrics/prometheus.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) (limited to 'lib') diff --git a/lib/gitlab/health_checks/prometheus_text_format.rb b/lib/gitlab/health_checks/prometheus_text_format.rb index e7ec1c516a2..6b81eeddfc9 100644 --- a/lib/gitlab/health_checks/prometheus_text_format.rb +++ b/lib/gitlab/health_checks/prometheus_text_format.rb @@ -37,4 +37,4 @@ module Gitlab end end end -end \ No newline at end of file +end diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 995715417f8..4779755bb22 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -7,4 +7,4 @@ module Gitlab influx_metrics_enabled? || prometheus_metrics_enabled? end end -end \ No newline at end of file +end diff --git a/lib/gitlab/metrics/prometheus.rb b/lib/gitlab/metrics/prometheus.rb index 493da4d779c..60686509332 100644 --- a/lib/gitlab/metrics/prometheus.rb +++ b/lib/gitlab/metrics/prometheus.rb @@ -38,4 +38,4 @@ module Gitlab end end end -end \ No newline at end of file +end -- cgit v1.2.1 From d26573c6e3de535f69437deaf54d5c151ac343c8 Mon Sep 17 00:00:00 2001 From: Pawel Chojnacki Date: Fri, 2 Jun 2017 15:55:44 +0200 Subject: Make PrometheusTextFormat return proper output terminated with '\n' remove file dangling after rebase --- lib/gitlab/health_checks/prometheus_text_format.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib') diff --git a/lib/gitlab/health_checks/prometheus_text_format.rb b/lib/gitlab/health_checks/prometheus_text_format.rb index 6b81eeddfc9..462c8e736a0 100644 --- a/lib/gitlab/health_checks/prometheus_text_format.rb +++ b/lib/gitlab/health_checks/prometheus_text_format.rb @@ -2,7 +2,7 @@ module Gitlab module HealthChecks class PrometheusTextFormat def marshal(metrics) - metrics_with_type_declarations(metrics).join("\n") + "#{metrics_with_type_declarations(metrics).join("\n")}\n" end private -- cgit v1.2.1