159 lines
4.6 KiB
Ruby
159 lines
4.6 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
module Gitlab
|
|
module Metrics
|
|
# Class for storing metrics information of a single transaction.
|
|
class Transaction
|
|
include Gitlab::Metrics::Methods
|
|
|
|
# base label keys shared among all transactions
|
|
BASE_LABEL_KEYS = %i(controller action feature_category).freeze
|
|
# labels that potentially contain sensitive information and will be filtered
|
|
FILTERED_LABEL_KEYS = %i(branch path).freeze
|
|
|
|
THREAD_KEY = :_gitlab_metrics_transaction
|
|
|
|
SMALL_BUCKETS = [0.1, 0.25, 0.5, 1.0, 2.5, 5.0].freeze
|
|
|
|
# The series to store events (e.g. Git pushes) in.
|
|
EVENT_SERIES = 'events'
|
|
|
|
attr_reader :method
|
|
|
|
class << self
|
|
def current
|
|
Thread.current[THREAD_KEY]
|
|
end
|
|
|
|
def prometheus_metric(name, type, &block)
|
|
fetch_metric(type, name) do
|
|
# set default metric options
|
|
docstring "#{name.to_s.humanize} #{type}"
|
|
|
|
evaluate(&block)
|
|
# always filter sensitive labels and merge with base ones
|
|
label_keys BASE_LABEL_KEYS | (label_keys - FILTERED_LABEL_KEYS)
|
|
end
|
|
end
|
|
end
|
|
|
|
def initialize
|
|
@methods = {}
|
|
|
|
@started_at = nil
|
|
@finished_at = nil
|
|
end
|
|
|
|
def duration
|
|
@finished_at ? (@finished_at - @started_at) : 0.0
|
|
end
|
|
|
|
def run
|
|
Thread.current[THREAD_KEY] = self
|
|
|
|
@started_at = System.monotonic_time
|
|
|
|
yield
|
|
ensure
|
|
@finished_at = System.monotonic_time
|
|
|
|
observe(:gitlab_transaction_duration_seconds, duration) do
|
|
buckets SMALL_BUCKETS
|
|
end
|
|
|
|
Thread.current[THREAD_KEY] = nil
|
|
end
|
|
|
|
# Tracks a business level event
|
|
#
|
|
# Business level events including events such as Git pushes, Emails being
|
|
# sent, etc.
|
|
#
|
|
# event_name - The name of the event (e.g. "git_push").
|
|
# tags - A set of tags to attach to the event.
|
|
def add_event(event_name, tags = {})
|
|
event_name = "gitlab_transaction_event_#{event_name}_total".to_sym
|
|
metric = self.class.prometheus_metric(event_name, :counter) do
|
|
label_keys tags.keys
|
|
end
|
|
|
|
metric.increment(filter_labels(tags))
|
|
end
|
|
|
|
# Returns a MethodCall object for the given name.
|
|
def method_call_for(name, module_name, method_name)
|
|
unless method = @methods[name]
|
|
@methods[name] = method = MethodCall.new(name, module_name, method_name, self)
|
|
end
|
|
|
|
method
|
|
end
|
|
|
|
# Increment counter metric
|
|
#
|
|
# It will initialize the metric if metric is not found
|
|
#
|
|
# block - if provided can be used to initialize metric with custom options (docstring, labels, with_feature)
|
|
#
|
|
# Example:
|
|
# ```
|
|
# transaction.increment(:mestric_name, 1, { docstring: 'Custom title', base_labels: {sane: 'yes'} } ) do
|
|
#
|
|
# transaction.increment(:mestric_name, 1) do
|
|
# docstring 'Custom title'
|
|
# label_keys %i(sane)
|
|
# end
|
|
# ```
|
|
def increment(name, value = 1, labels = {}, &block)
|
|
counter = self.class.prometheus_metric(name, :counter, &block)
|
|
|
|
counter.increment(filter_labels(labels), value)
|
|
end
|
|
|
|
# Set gauge metric
|
|
#
|
|
# It will initialize the metric if metric is not found
|
|
#
|
|
# block - if provided, it can be used to initialize metric with custom options (docstring, labels, with_feature, multiprocess_mode)
|
|
# - multiprocess_mode is :all by default
|
|
#
|
|
# Example:
|
|
# ```
|
|
# transaction.set(:mestric_name, 1) do
|
|
# multiprocess_mode :livesum
|
|
# end
|
|
# ```
|
|
def set(name, value, labels = {}, &block)
|
|
gauge = self.class.prometheus_metric(name, :gauge, &block)
|
|
|
|
gauge.set(filter_labels(labels), value)
|
|
end
|
|
|
|
# Observe histogram metric
|
|
#
|
|
# It will initialize the metric if metric is not found
|
|
#
|
|
# block - if provided, it can be used to initialize metric with custom options (docstring, labels, with_feature, buckets)
|
|
#
|
|
# Example:
|
|
# ```
|
|
# transaction.observe(:mestric_name, 1) do
|
|
# buckets [100, 1000, 10000, 100000, 1000000, 10000000]
|
|
# end
|
|
# ```
|
|
def observe(name, value, labels = {}, &block)
|
|
histogram = self.class.prometheus_metric(name, :histogram, &block)
|
|
|
|
histogram.observe(filter_labels(labels), value)
|
|
end
|
|
|
|
def labels
|
|
BASE_LABEL_KEYS.product([nil]).to_h
|
|
end
|
|
|
|
def filter_labels(labels)
|
|
labels.empty? ? self.labels : labels.without(*FILTERED_LABEL_KEYS).merge(self.labels)
|
|
end
|
|
end
|
|
end
|
|
end
|