All Files ( 91.74% covered at 1424.22 hits/line )
46 files in total.
1936 relevant lines,
1776 lines covered and
160 lines missed.
(
91.74%
)
-
1
require 'prometheus/client/registry'
-
1
require 'prometheus/client/configuration'
-
1
require 'prometheus/client/mmaped_value'
-
-
1
module Prometheus
-
# Client is a ruby implementation for a Prometheus compatible client.
-
1
module Client
-
1
class << self
-
1
attr_writer :configuration
-
-
1
def configuration
-
40695
@configuration ||= Configuration.new
-
end
-
-
1
def configure
-
yield(configuration)
-
end
-
-
# Returns a default registry object
-
1
def registry
-
8
@registry ||= Registry.new
-
end
-
-
1
def logger
-
3
configuration.logger
-
end
-
-
1
def pid
-
111
configuration.pid_provider.call
-
end
-
-
# Resets the registry and reinitializes all metrics files.
-
# Use case: clean up everything in specs `before` block,
-
# to prevent leaking the state between specs which are updating metrics.
-
1
def reset!
-
5
@registry = nil
-
5
::Prometheus::Client::MmapedValue.reset_and_reinitialize
-
end
-
-
1
def cleanup!
-
27
Dir.glob("#{configuration.multiprocess_files_dir}/*.db").each { |f| File.unlink(f) if File.exist?(f) }
-
end
-
-
# With `force: false`: reinitializes metric files only for processes with the changed PID.
-
# With `force: true`: reinitializes all metrics files.
-
# Always keeps the registry.
-
# Use case (`force: false`): pick up new metric files on each worker start,
-
# without resetting already registered files for the master or previously initialized workers.
-
1
def reinitialize_on_pid_change(force: false)
-
3
if force
-
1
::Prometheus::Client::MmapedValue.reset_and_reinitialize
-
else
-
2
::Prometheus::Client::MmapedValue.reinitialize_on_pid_change
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/registry'
-
1
require 'prometheus/client/mmaped_value'
-
1
require 'prometheus/client/page_size'
-
1
require 'logger'
-
1
require 'tmpdir'
-
-
1
module Prometheus
-
1
module Client
-
1
class Configuration
-
1
attr_accessor :value_class, :multiprocess_files_dir, :initial_mmap_file_size, :logger, :pid_provider, :rust_multiprocess_metrics
-
-
1
def initialize
-
1
@value_class = ::Prometheus::Client::MmapedValue
-
1
@initial_mmap_file_size = ::Prometheus::Client::PageSize.page_size(fallback_page_size: 4096)
-
1
@logger = Logger.new($stdout)
-
1
@pid_provider = Process.method(:pid)
-
1
@rust_multiprocess_metrics = ENV.fetch('prometheus_rust_multiprocess_metrics', 'true') == 'true'
-
1
@multiprocess_files_dir = ENV.fetch('prometheus_multiproc_dir') do
-
Dir.mktmpdir("prometheus-mmap")
-
end
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client/metric'
-
-
1
module Prometheus
-
1
module Client
-
# Counter is a metric that exposes merely a sum or tally of things.
-
1
class Counter < Metric
-
1
def type
-
80047
:counter
-
end
-
-
1
def increment(labels = {}, by = 1)
-
40120
raise ArgumentError, 'increment must be a non-negative number' if by < 0
-
-
40119
label_set = label_set_for(labels)
-
80238
synchronize { @values[label_set].increment(by) }
-
end
-
-
1
private
-
-
1
def default(labels)
-
40022
value_object(type, @name, @name, labels)
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/uses_value_type'
-
1
require 'prometheus/client/helper/json_parser'
-
1
require 'prometheus/client/helper/loader'
-
1
require 'prometheus/client/helper/plain_file'
-
1
require 'prometheus/client/helper/metrics_processing'
-
1
require 'prometheus/client/helper/metrics_representation'
-
-
1
module Prometheus
-
1
module Client
-
1
module Formats
-
# Text format is human readable mainly used for manual inspection.
-
1
module Text
-
1
MEDIA_TYPE = 'text/plain'.freeze
-
1
VERSION = '0.0.4'.freeze
-
1
CONTENT_TYPE = "#{MEDIA_TYPE}; version=#{VERSION}".freeze
-
-
1
class << self
-
1
def marshal(registry)
-
10
metrics = registry.metrics.map do |metric|
-
5
samples = metric.values.flat_map do |label_set, value|
-
7
representation(metric, label_set, value)
-
end
-
-
5
[metric.name, { type: metric.type, help: metric.docstring, samples: samples }]
-
end
-
-
10
Helper::MetricsRepresentation.to_text(metrics)
-
end
-
-
1
def marshal_multiprocess(path = Prometheus::Client.configuration.multiprocess_files_dir, use_rust: true)
-
4
file_list = Dir.glob(File.join(path, '*.db')).sort
-
20
.map {|f| Helper::PlainFile.new(f) }
-
20
.map {|f| [f.filepath, f.multiprocess_mode.to_sym, f.type.to_sym, f.pid] }
-
-
4
if use_rust && Prometheus::Client::Helper::Loader.rust_impl_available?
-
3
FastMmapedFileRs.to_metrics(file_list.to_a)
-
else
-
1
FastMmapedFile.to_metrics(file_list.to_a)
-
end
-
end
-
-
1
def rust_impl_available?
-
return @rust_available unless @rust_available.nil?
-
-
check_for_rust
-
end
-
-
1
private
-
-
1
def load_metrics(path)
-
metrics = {}
-
Dir.glob(File.join(path, '*.db')).sort.each do |f|
-
Helper::PlainFile.new(f).to_metrics(metrics)
-
end
-
-
metrics
-
end
-
-
1
def representation(metric, label_set, value)
-
7
labels = metric.base_labels.merge(label_set)
-
-
7
if metric.type == :summary
-
1
summary(metric.name, labels, value)
-
6
elsif metric.type == :histogram
-
1
histogram(metric.name, labels, value)
-
else
-
5
[[metric.name, labels, value.get]]
-
end
-
end
-
-
1
def summary(name, set, value)
-
1
rv = value.get.map do |q, v|
-
3
[name, set.merge(quantile: q), v]
-
end
-
-
1
rv << ["#{name}_sum", set, value.get.sum]
-
1
rv << ["#{name}_count", set, value.get.total]
-
1
rv
-
end
-
-
1
def histogram(name, set, value)
-
# |metric_name, labels, value|
-
1
rv = value.get.map do |q, v|
-
3
[name, set.merge(le: q), v]
-
end
-
-
1
rv << [name, set.merge(le: '+Inf'), value.get.total]
-
1
rv << ["#{name}_sum", set, value.get.sum]
-
1
rv << ["#{name}_count", set, value.get.total]
-
1
rv
-
end
-
end
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client/metric'
-
-
1
module Prometheus
-
1
module Client
-
# A Gauge is a metric that exposes merely an instantaneous value or some
-
# snapshot thereof.
-
1
class Gauge < Metric
-
1
def initialize(name, docstring, base_labels = {}, multiprocess_mode=:all)
-
39
super(name, docstring, base_labels)
-
36
if value_class.multiprocess and ![:min, :max, :livesum, :liveall, :all].include?(multiprocess_mode)
-
raise ArgumentError, 'Invalid multiprocess mode: ' + multiprocess_mode
-
end
-
36
@multiprocess_mode = multiprocess_mode
-
end
-
-
1
def type
-
80
:gauge
-
end
-
-
1
def default(labels)
-
40
value_object(type, @name, @name, labels, @multiprocess_mode)
-
end
-
-
# Sets the value for the given label set
-
1
def set(labels, value)
-
34
@values[label_set_for(labels)].set(value)
-
end
-
-
1
def increment(labels, value)
-
3
@values[label_set_for(labels)].increment(value)
-
end
-
-
1
def decrement(labels, value)
-
2
@values[label_set_for(labels)].decrement(value)
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/helper/json_parser'
-
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
1
module EntryParser
-
1
class ParsingError < RuntimeError;
-
end
-
-
1
MINIMUM_SIZE = 8
-
1
START_POSITION = 8
-
1
VALUE_BYTES = 8
-
1
ENCODED_LENGTH_BYTES = 4
-
-
1
def used
-
107
slice(0..3).unpack('l')[0]
-
end
-
-
1
def parts
-
60
@parts ||= File.basename(filepath, '.db')
-
.split('_')
-
78
.map { |e| e.gsub(/-\d+$/, '') } # remove trailing -number
-
end
-
-
1
def type
-
20
parts[0].to_sym
-
end
-
-
1
def pid
-
20
(parts[2..-1] || []).join('_')
-
end
-
-
1
def multiprocess_mode
-
20
parts[1]
-
end
-
-
1
def empty?
-
95
size < MINIMUM_SIZE || used.zero?
-
end
-
-
1
def entries(ignore_errors = false)
-
95
return Enumerator.new {} if empty?
-
-
12
Enumerator.new do |yielder|
-
12
used_ = used # cache used to avoid unnecessary unpack operations
-
-
12
pos = START_POSITION # used + padding offset
-
12
while pos < used_ && pos < size && pos > 0
-
410
data = slice(pos..-1)
-
410
unless data
-
raise ParsingError, "data slice is nil at pos #{pos}" unless ignore_errors
-
pos += 8
-
next
-
end
-
-
410
encoded_len, first_encoded_bytes = data.unpack('LL')
-
410
if encoded_len.nil? || encoded_len.zero? || first_encoded_bytes.nil? || first_encoded_bytes.zero?
-
# do not parse empty data
-
pos += 8
-
next
-
end
-
-
410
entry_len = ENCODED_LENGTH_BYTES + encoded_len
-
410
padding_len = 8 - entry_len % 8
-
-
410
value_offset = entry_len + padding_len # align to 8 bytes
-
410
pos += value_offset
-
-
410
if value_offset > 0 && (pos + VALUE_BYTES) <= size # if positions are safe
-
410
yielder.yield data, encoded_len, value_offset, pos
-
else
-
raise ParsingError, "data slice is nil at pos #{pos}" unless ignore_errors
-
end
-
410
pos += VALUE_BYTES
-
end
-
end
-
end
-
-
1
def parsed_entries(ignore_errors = false)
-
result = entries(ignore_errors).map do |data, encoded_len, value_offset, _|
-
begin
-
encoded, value = data.unpack(format('@4A%d@%dd', encoded_len, value_offset))
-
[encoded, value]
-
rescue ArgumentError => e
-
Prometheus::Client.logger.debug("Error processing data: #{bin_to_hex(data[0, 7])} len: #{encoded_len} value_offset: #{value_offset}")
-
raise ParsingError, e unless ignore_errors
-
end
-
end
-
-
result.reject!(&:nil?) if ignore_errors
-
result
-
end
-
-
1
def to_metrics(metrics = {}, ignore_errors = false)
-
parsed_entries(ignore_errors).each do |key, value|
-
begin
-
metric_name, name, labelnames, labelvalues = JsonParser.load(key)
-
labelnames ||= []
-
labelvalues ||= []
-
-
metric = metrics.fetch(metric_name,
-
metric_name: metric_name,
-
help: 'Multiprocess metric',
-
type: type,
-
samples: [])
-
if type == :gauge
-
metric[:multiprocess_mode] = multiprocess_mode
-
metric[:samples] += [[name, labelnames.zip(labelvalues) + [['pid', pid]], value]]
-
else
-
# The duplicates and labels are fixed in the next for.
-
metric[:samples] += [[name, labelnames.zip(labelvalues), value]]
-
end
-
metrics[metric_name] = metric
-
-
rescue JSON::ParserError => e
-
raise ParsingError(e) unless ignore_errors
-
end
-
end
-
-
metrics.reject! { |e| e.nil? } if ignore_errors
-
metrics
-
end
-
-
1
private
-
-
1
def bin_to_hex(s)
-
s.each_byte.map { |b| b.to_s(16) }.join
-
end
-
end
-
end
-
end
-
end
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
1
class FileLocker
-
1
class << self
-
1
LOCK_FILE_MUTEX = Mutex.new
-
-
1
def lock_to_process(filepath)
-
111
LOCK_FILE_MUTEX.synchronize do
-
111
@file_locks ||= {}
-
111
return false if @file_locks[filepath]
-
-
99
file = File.open(filepath, 'ab')
-
99
if file.flock(File::LOCK_NB | File::LOCK_EX)
-
99
@file_locks[filepath] = file
-
99
return true
-
else
-
return false
-
end
-
end
-
end
-
-
1
def unlock(filepath)
-
65
LOCK_FILE_MUTEX.synchronize do
-
65
@file_locks ||= {}
-
65
return false unless @file_locks[filepath]
-
-
49
file = @file_locks[filepath]
-
49
file.flock(File::LOCK_UN)
-
49
file.close
-
49
@file_locks.delete(filepath)
-
end
-
end
-
-
1
def unlock_all
-
LOCK_FILE_MUTEX.synchronize do
-
@file_locks ||= {}
-
@file_locks.values.each do |file|
-
file.flock(File::LOCK_UN)
-
file.close
-
end
-
-
@file_locks = {}
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'json'
-
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
1
module JsonParser
-
1
class << self
-
1
if defined?(Oj)
-
def load(s)
-
Oj.load(s)
-
rescue Oj::ParseError, EncodingError => e
-
raise JSON::ParserError.new(e.message)
-
end
-
else
-
1
def load(s)
-
2
JSON.parse(s)
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
1
module Loader
-
1
class << self
-
1
def rust_impl_available?
-
3
return @rust_available unless @rust_available.nil?
-
-
1
check_for_rust
-
end
-
-
1
private
-
-
1
def load_rust_extension
-
begin
-
1
ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
-
1
require_relative "../../../#{ruby_version}/fast_mmaped_file_rs"
-
1
rescue LoadError
-
1
require 'fast_mmaped_file_rs'
-
end
-
end
-
-
1
def check_for_rust
-
# This will be evaluated on each invocation even with `||=` if
-
# `@rust_available` if false. Running a `require` statement is slow,
-
# so the `rust_impl_available?` method memoizes the result, external
-
# callers can only trigger this method a single time.
-
@rust_available = begin
-
1
load_rust_extension
-
1
true
-
rescue LoadError
-
warn <<~WARN
-
WARNING: The Rust extension for prometheus-client-mmap is unavailable, falling back to the legacy C extension.
-
The Rust extension will be required in the next version. If you are compiling this gem from source,
-
ensure your build system has a Rust compiler and clang: https://gitlab.com/gitlab-org/ruby/gems/prometheus-client-mmap
-
WARN
-
false
-
end
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
1
module MetricsProcessing
-
1
def self.merge_metrics(metrics)
-
metrics.each_value do |metric|
-
metric[:samples] = merge_samples(metric[:samples], metric[:type], metric[:multiprocess_mode]).map do |(name, labels), value|
-
[name, labels.to_h, value]
-
end
-
end
-
end
-
-
1
def self.merge_samples(raw_samples, metric_type, multiprocess_mode)
-
samples = {}
-
raw_samples.each do |name, labels, value|
-
without_pid = labels.reject { |l| l[0] == 'pid' }
-
-
case metric_type
-
when :gauge
-
case multiprocess_mode
-
when 'min'
-
s = samples.fetch([name, without_pid], value)
-
samples[[name, without_pid]] = [s, value].min
-
when 'max'
-
s = samples.fetch([name, without_pid], value)
-
samples[[name, without_pid]] = [s, value].max
-
when 'livesum'
-
s = samples.fetch([name, without_pid], 0.0)
-
samples[[name, without_pid]] = s + value
-
else # all/liveall
-
samples[[name, labels]] = value
-
end
-
else
-
# Counter, Histogram and Summary.
-
s = samples.fetch([name, without_pid], 0.0)
-
samples[[name, without_pid]] = s + value
-
end
-
end
-
-
samples
-
end
-
end
-
end
-
end
-
end
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
1
module MetricsRepresentation
-
1
METRIC_LINE = '%s%s %s'.freeze
-
1
TYPE_LINE = '# TYPE %s %s'.freeze
-
1
HELP_LINE = '# HELP %s %s'.freeze
-
-
1
LABEL = '%s="%s"'.freeze
-
1
SEPARATOR = ','.freeze
-
1
DELIMITER = "\n".freeze
-
-
1
REGEX = { doc: /[\n\\]/, label: /[\n\\"]/ }.freeze
-
1
REPLACE = { "\n" => '\n', '\\' => '\\\\', '"' => '\"' }.freeze
-
-
1
def self.to_text(metrics)
-
10
lines = []
-
-
10
metrics.each do |name, metric|
-
5
lines << format(HELP_LINE, name, escape(metric[:help]))
-
5
lines << format(TYPE_LINE, name, metric[:type])
-
5
metric[:samples].each do |metric_name, labels, value|
-
16
lines << metric(metric_name, format_labels(labels), value)
-
end
-
end
-
-
# there must be a trailing delimiter
-
10
(lines << nil).join(DELIMITER)
-
end
-
-
1
def self.metric(name, labels, value)
-
16
format(METRIC_LINE, name, labels, value)
-
end
-
-
1
def self.format_labels(set)
-
16
return if set.empty?
-
-
16
strings = set.each_with_object([]) do |(key, value), memo|
-
35
memo << format(LABEL, key, escape(value, :label))
-
end
-
-
16
"{#{strings.join(SEPARATOR)}}"
-
end
-
-
1
def self.escape(string, format = :doc)
-
40
string.to_s.gsub(REGEX[format], REPLACE)
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/helper/entry_parser'
-
1
require 'prometheus/client/helper/file_locker'
-
1
require 'prometheus/client/helper/loader'
-
-
# load precompiled extension if available
-
begin
-
1
ruby_version = /(\d+\.\d+)/.match(RUBY_VERSION)
-
1
require_relative "../../../#{ruby_version}/fast_mmaped_file"
-
rescue LoadError
-
1
require 'fast_mmaped_file'
-
end
-
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
# We can't check `Prometheus::Client.configuration` as this creates a circular dependency
-
1
if (ENV.fetch('prometheus_rust_mmaped_file', 'true') == "true" &&
-
Prometheus::Client::Helper::Loader.rust_impl_available?)
-
class MmapedFile < FastMmapedFileRs
-
end
-
else
-
1
class MmapedFile < FastMmapedFile
-
end
-
end
-
-
1
class MmapedFile
-
1
include EntryParser
-
-
1
attr_reader :filepath, :size
-
-
1
def initialize(filepath)
-
105
@filepath = filepath
-
-
105
File.open(filepath, 'a+b') do |file|
-
105
file.truncate(initial_mmap_file_size) if file.size < MINIMUM_SIZE
-
105
@size = file.size
-
end
-
-
105
super(filepath)
-
end
-
-
1
def close
-
59
munmap
-
59
FileLocker.unlock(filepath)
-
end
-
-
1
private
-
-
1
def initial_mmap_file_size
-
93
Prometheus::Client.configuration.initial_mmap_file_size
-
end
-
-
1
public
-
-
1
class << self
-
1
def open(filepath)
-
105
MmapedFile.new(filepath)
-
end
-
-
1
def ensure_exclusive_file(file_prefix = 'mmaped_file')
-
99
(0..Float::INFINITY).lazy
-
111
.map { |f_num| "#{file_prefix}_#{Prometheus::Client.pid}-#{f_num}.db" }
-
111
.map { |filename| File.join(Prometheus::Client.configuration.multiprocess_files_dir, filename) }
-
111
.find { |path| Helper::FileLocker.lock_to_process(path) }
-
end
-
-
1
def open_exclusive_file(file_prefix = 'mmaped_file')
-
80
filename = Helper::MmapedFile.ensure_exclusive_file(file_prefix)
-
80
open(filename)
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/helper/entry_parser'
-
-
1
module Prometheus
-
1
module Client
-
1
module Helper
-
# Parses DB files without using mmap
-
1
class PlainFile
-
1
include EntryParser
-
1
attr_reader :filepath
-
-
1
def source
-
408
@data ||= File.read(filepath, mode: 'rb')
-
end
-
-
1
def initialize(filepath)
-
23
@filepath = filepath
-
end
-
-
1
def slice(*args)
-
139
source.slice(*args)
-
end
-
-
1
def size
-
269
source.length
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/metric'
-
1
require 'prometheus/client/uses_value_type'
-
-
1
module Prometheus
-
1
module Client
-
# A histogram samples observations (usually things like request durations
-
# or response sizes) and counts them in configurable buckets. It also
-
# provides a sum of all observed values.
-
1
class Histogram < Metric
-
# Value represents the state of a Histogram at a given point.
-
1
class Value < Hash
-
1
include UsesValueType
-
1
attr_accessor :sum, :total, :total_inf
-
-
1
def initialize(type, name, labels, buckets)
-
11
@sum = value_object(type, name, "#{name}_sum", labels)
-
11
@total = value_object(type, name, "#{name}_count", labels)
-
11
@total_inf = value_object(type, name, "#{name}_bucket", labels.merge(le: "+Inf"))
-
-
11
buckets.each do |bucket|
-
113
self[bucket] = value_object(type, name, "#{name}_bucket", labels.merge(le: bucket.to_s))
-
end
-
end
-
-
1
def observe(value)
-
9
@sum.increment(value)
-
9
@total.increment()
-
9
@total_inf.increment()
-
-
9
each_key do |bucket|
-
91
self[bucket].increment() if value <= bucket
-
end
-
end
-
-
1
def get()
-
4
hash = {}
-
4
each_key do |bucket|
-
28
hash[bucket] = self[bucket].get()
-
end
-
4
hash
-
end
-
end
-
-
# DEFAULT_BUCKETS are the default Histogram buckets. The default buckets
-
# are tailored to broadly measure the response time (in seconds) of a
-
# network service. (From DefBuckets client_golang)
-
1
DEFAULT_BUCKETS = [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1,
-
2.5, 5, 10].freeze
-
-
# Offer a way to manually specify buckets
-
1
def initialize(name, docstring, base_labels = {},
-
buckets = DEFAULT_BUCKETS)
-
20
raise ArgumentError, 'Unsorted buckets, typo?' unless sorted? buckets
-
-
19
@buckets = buckets
-
19
super(name, docstring, base_labels)
-
end
-
-
1
def type
-
31
:histogram
-
end
-
-
1
def observe(labels, value)
-
9
label_set = label_set_for(labels)
-
18
synchronize { @values[label_set].observe(value) }
-
end
-
-
1
private
-
-
1
def default(labels)
-
# TODO: default function needs to know key of hash info (label names and values)
-
11
Value.new(type, @name, labels, @buckets)
-
end
-
-
1
def sorted?(bucket)
-
203
bucket.each_cons(2).all? { |i, j| i <= j }
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
module Prometheus
-
1
module Client
-
# LabelSetValidator ensures that all used label sets comply with the
-
# Prometheus specification.
-
1
class LabelSetValidator
-
1
RESERVED_LABELS = [].freeze
-
-
1
class LabelSetError < StandardError; end
-
1
class InvalidLabelSetError < LabelSetError; end
-
1
class InvalidLabelError < LabelSetError; end
-
1
class ReservedLabelError < LabelSetError; end
-
-
1
def initialize(reserved_labels = [])
-
40133
@reserved_labels = (reserved_labels + RESERVED_LABELS).freeze
-
40133
@validated = {}
-
end
-
-
1
def valid?(labels)
-
80255
unless labels.is_a?(Hash)
-
2
raise InvalidLabelSetError, "#{labels} is not a valid label set"
-
end
-
-
80253
labels.all? do |key, value|
-
225
validate_symbol(key)
-
223
validate_name(key)
-
218
validate_reserved_key(key)
-
217
validate_value(key, value)
-
end
-
end
-
-
1
def validate(labels)
-
40248
return labels if @validated.key?(labels.hash)
-
-
40109
valid?(labels)
-
-
40107
unless @validated.empty? || match?(labels, @validated.first.last)
-
1
raise InvalidLabelSetError, "labels must have the same signature: (#{label_diff(labels, @validated.first.last)})"
-
end
-
-
40106
@validated[labels.hash] = labels
-
end
-
-
1
private
-
-
1
def label_diff(a, b)
-
1
"expected keys: #{b.keys.sort}, got: #{a.keys.sort}"
-
end
-
-
1
def match?(a, b)
-
21
a.keys.sort == b.keys.sort
-
end
-
-
1
def validate_symbol(key)
-
225
return true if key.is_a?(Symbol)
-
-
2
raise InvalidLabelError, "label #{key} is not a symbol"
-
end
-
-
1
def validate_name(key)
-
223
return true unless key.to_s.start_with?('__')
-
-
5
raise ReservedLabelError, "label #{key} must not start with __"
-
end
-
-
1
def validate_reserved_key(key)
-
218
return true unless @reserved_labels.include?(key)
-
-
1
raise ReservedLabelError, "#{key} is reserved"
-
end
-
-
1
def validate_value(key, value)
-
217
return true if value.is_a?(String) ||
-
value.is_a?(Numeric) ||
-
value.is_a?(Symbol) ||
-
value.is_a?(FalseClass) ||
-
value.is_a?(TrueClass) ||
-
value.nil?
-
-
1
raise InvalidLabelError, "#{key} does not contain a valid value (type #{value.class})"
-
end
-
end
-
end
-
end
-
1
require 'thread'
-
1
require 'prometheus/client/label_set_validator'
-
1
require 'prometheus/client/uses_value_type'
-
-
1
module Prometheus
-
1
module Client
-
1
class Metric
-
1
include UsesValueType
-
1
attr_reader :name, :docstring, :base_labels
-
-
1
def initialize(name, docstring, base_labels = {})
-
40100
@mutex = Mutex.new
-
40100
@validator = case type
-
when :summary
-
18
LabelSetValidator.new(['quantile'])
-
when :histogram
-
19
LabelSetValidator.new(['le'])
-
else
-
40063
LabelSetValidator.new
-
end
-
80184
@values = Hash.new { |hash, key| hash[key] = default(key) }
-
-
40100
validate_name(name)
-
40096
validate_docstring(docstring)
-
40092
@validator.valid?(base_labels)
-
-
40087
@name = name
-
40087
@docstring = docstring
-
40087
@base_labels = base_labels
-
end
-
-
# Returns the value for the given label set
-
1
def get(labels = {})
-
45
label_set = label_set_for(labels)
-
45
@validator.valid?(label_set)
-
-
45
@values[label_set].get
-
end
-
-
# Returns all label sets with their values
-
1
def values
-
1
synchronize do
-
1
@values.each_with_object({}) do |(labels, value), memo|
-
1
memo[labels] = value
-
end
-
end
-
end
-
-
1
private
-
-
1
def touch_default_value
-
@values[label_set_for({})]
-
end
-
-
1
def default(labels)
-
value_object(type, @name, @name, labels)
-
end
-
-
1
def validate_name(name)
-
40100
return true if name.is_a?(Symbol)
-
-
4
raise ArgumentError, 'given name must be a symbol'
-
end
-
-
1
def validate_docstring(docstring)
-
40096
return true if docstring.respond_to?(:empty?) && !docstring.empty?
-
-
4
raise ArgumentError, 'docstring must be given'
-
end
-
-
1
def label_set_for(labels)
-
40221
@validator.validate(@base_labels.merge(labels))
-
end
-
-
1
def synchronize(&block)
-
40142
@mutex.synchronize(&block)
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/helper/mmaped_file'
-
1
require 'prometheus/client/helper/plain_file'
-
1
require 'prometheus/client'
-
-
1
module Prometheus
-
1
module Client
-
1
class ParsingError < StandardError
-
end
-
-
# A dict of doubles, backed by an mmapped file.
-
#
-
# The file starts with a 4 byte int, indicating how much of it is used.
-
# Then 4 bytes of padding.
-
# There's then a number of entries, consisting of a 4 byte int which is the
-
# size of the next field, a utf-8 encoded string key, padding to an 8 byte
-
# alignment, and then a 8 byte float which is the value.
-
1
class MmapedDict
-
1
attr_reader :m, :used, :positions
-
-
1
def self.read_all_values(f)
-
3
Helper::PlainFile.new(f).entries.map do |data, encoded_len, value_offset, _|
-
133
encoded, value = data.unpack(format('@4A%d@%dd', encoded_len, value_offset))
-
133
[encoded, value]
-
end
-
end
-
-
1
def initialize(m)
-
92
@mutex = Mutex.new
-
-
92
@m = m
-
# @m.mlock # TODO: Ensure memory is locked to RAM
-
-
92
@positions = {}
-
92
read_all_positions.each do |key, pos|
-
277
@positions[key] = pos
-
end
-
rescue StandardError => e
-
raise ParsingError, "exception #{e} while processing metrics file #{path}"
-
end
-
-
1
def read_value(key)
-
40594
@m.fetch_entry(@positions, key, 0.0)
-
end
-
-
1
def write_value(key, value)
-
41343
@m.upsert_entry(@positions, key, value)
-
end
-
-
1
def path
-
1
@m.filepath if @m
-
end
-
-
1
def close
-
47
@m.sync
-
47
@m.close
-
rescue TypeError => e
-
Prometheus::Client.logger.warn("munmap raised error #{e}")
-
end
-
-
1
def inspect
-
2
"#<#{self.class}:0x#{(object_id << 1).to_s(16)}>"
-
end
-
-
1
private
-
-
1
def init_value(key)
-
@m.add_entry(@positions, key, 0.0)
-
end
-
-
# Yield (key, pos). No locking is performed.
-
1
def read_all_positions
-
92
@m.entries.map do |data, encoded_len, _, absolute_pos|
-
277
encoded, = data.unpack(format('@4A%d', encoded_len))
-
277
[encoded, absolute_pos]
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client'
-
1
require 'prometheus/client/mmaped_dict'
-
1
require 'json'
-
-
1
module Prometheus
-
1
module Client
-
# A float protected by a mutex backed by a per-process mmaped file.
-
1
class MmapedValue
-
1
VALUE_LOCK = Mutex.new
-
-
1
@@files = {}
-
1
@@pid = -1
-
-
1
def initialize(type, metric_name, name, labels, multiprocess_mode = '')
-
40170
@file_prefix = type.to_s
-
40170
@metric_name = metric_name
-
40170
@name = name
-
40170
@labels = labels
-
40170
if type == :gauge
-
34
@file_prefix += '_' + multiprocess_mode.to_s
-
end
-
-
40170
@pid = -1
-
-
40170
@mutex = Mutex.new
-
40170
initialize_file
-
end
-
-
1
def increment(amount = 1)
-
40170
@mutex.synchronize do
-
40170
initialize_file if pid_changed?
-
-
40170
@value += amount
-
40170
write_value(@key, @value)
-
40170
@value
-
end
-
end
-
-
1
def decrement(amount = 1)
-
increment(-amount)
-
end
-
-
1
def set(value)
-
32
@mutex.synchronize do
-
32
initialize_file if pid_changed?
-
-
32
@value = value
-
32
write_value(@key, @value)
-
32
@value
-
end
-
end
-
-
1
def get
-
64
@mutex.synchronize do
-
64
initialize_file if pid_changed?
-
64
return @value
-
end
-
end
-
-
1
def pid_changed?
-
40332
@pid != Process.pid
-
end
-
-
# method needs to be run in VALUE_LOCK mutex
-
1
def unsafe_reinitialize_file(check_pid = true)
-
468
unsafe_initialize_file if !check_pid || pid_changed?
-
end
-
-
1
def self.reset_and_reinitialize
-
8
VALUE_LOCK.synchronize do
-
8
@@pid = Process.pid
-
8
@@files = {}
-
-
8
ObjectSpace.each_object(MmapedValue).each do |v|
-
402
v.unsafe_reinitialize_file(false)
-
end
-
end
-
end
-
-
1
def self.reset_on_pid_change
-
40597
if pid_changed?
-
11
@@pid = Process.pid
-
11
@@files = {}
-
end
-
end
-
-
1
def self.reinitialize_on_pid_change
-
3
VALUE_LOCK.synchronize do
-
3
reset_on_pid_change
-
-
3
ObjectSpace.each_object(MmapedValue, &:unsafe_reinitialize_file)
-
end
-
end
-
-
1
def self.pid_changed?
-
40597
@@pid != Process.pid
-
end
-
-
1
def self.multiprocess
-
26
true
-
end
-
-
1
private
-
-
1
def initialize_file
-
40175
VALUE_LOCK.synchronize do
-
40175
unsafe_initialize_file
-
end
-
end
-
-
1
def unsafe_initialize_file
-
40594
self.class.reset_on_pid_change
-
-
40594
@pid = Process.pid
-
40594
unless @@files.has_key?(@file_prefix)
-
80
unless @file.nil?
-
41
@file.close
-
end
-
80
mmaped_file = Helper::MmapedFile.open_exclusive_file(@file_prefix)
-
-
80
@@files[@file_prefix] = MmapedDict.new(mmaped_file)
-
end
-
-
40594
@file = @@files[@file_prefix]
-
40594
@key = rebuild_key
-
-
40594
@value = read_value(@key)
-
end
-
-
-
1
def rebuild_key
-
40594
keys = @labels.keys.sort
-
40594
values = @labels.values_at(*keys)
-
-
40594
[@metric_name, @name, keys, values].to_json
-
end
-
-
1
def write_value(key, val)
-
40202
@file.write_value(key, val)
-
rescue StandardError => e
-
1
Prometheus::Client.logger.warn("writing value to #{@file.path} failed with #{e}")
-
1
Prometheus::Client.logger.debug(e.backtrace.join("\n"))
-
end
-
-
1
def read_value(key)
-
40594
@file.read_value(key)
-
rescue StandardError => e
-
Prometheus::Client.logger.warn("reading value from #{@file.path} failed with #{e}")
-
Prometheus::Client.logger.debug(e.backtrace.join("\n"))
-
0
-
end
-
end
-
end
-
end
-
1
require 'open3'
-
-
1
module Prometheus
-
1
module Client
-
1
module PageSize
-
1
def self.page_size(fallback_page_size: 4096)
-
5
stdout, status = Open3.capture2('getconf PAGESIZE')
-
5
return fallback_page_size if status.nil? || !status.success?
-
-
5
page_size = stdout.chomp.to_i
-
5
return fallback_page_size if page_size <= 0
-
-
5
page_size
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'base64'
-
1
require 'thread'
-
1
require 'net/http'
-
1
require 'uri'
-
1
require 'erb'
-
1
require 'set'
-
-
1
require 'prometheus/client'
-
1
require 'prometheus/client/formats/text'
-
1
require 'prometheus/client/label_set_validator'
-
-
1
module Prometheus
-
# Client is a ruby implementation for a Prometheus compatible client.
-
1
module Client
-
# Push implements a simple way to transmit a given registry to a given
-
# Pushgateway.
-
1
class Push
-
1
class HttpError < StandardError; end
-
1
class HttpRedirectError < HttpError; end
-
1
class HttpClientError < HttpError; end
-
1
class HttpServerError < HttpError; end
-
-
1
DEFAULT_GATEWAY = 'http://localhost:9091'.freeze
-
1
PATH = '/metrics/job/%s'.freeze
-
1
SUPPORTED_SCHEMES = %w(http https).freeze
-
-
1
attr_reader :job, :gateway, :path
-
-
1
def initialize(job:, gateway: DEFAULT_GATEWAY, grouping_key: {}, **kwargs)
-
25
raise ArgumentError, "job cannot be nil" if job.nil?
-
24
raise ArgumentError, "job cannot be empty" if job.empty?
-
23
@validator = LabelSetValidator.new()
-
23
@validator.validate(grouping_key)
-
-
22
@mutex = Mutex.new
-
22
@job = job
-
22
@gateway = gateway || DEFAULT_GATEWAY
-
22
@grouping_key = grouping_key
-
22
@path = build_path(job, grouping_key)
-
-
22
@uri = parse("#{@gateway}#{@path}")
-
20
validate_no_basic_auth!(@uri)
-
-
19
@http = Net::HTTP.new(@uri.host, @uri.port)
-
19
@http.use_ssl = (@uri.scheme == 'https')
-
19
@http.open_timeout = kwargs[:open_timeout] if kwargs[:open_timeout]
-
19
@http.read_timeout = kwargs[:read_timeout] if kwargs[:read_timeout]
-
end
-
-
1
def basic_auth(user, password)
-
1
@user = user
-
1
@password = password
-
end
-
-
1
def add(registry)
-
1
synchronize do
-
1
request(Net::HTTP::Post, registry)
-
end
-
end
-
-
1
def replace(registry)
-
1
synchronize do
-
1
request(Net::HTTP::Put, registry)
-
end
-
end
-
-
1
def delete
-
1
synchronize do
-
1
request(Net::HTTP::Delete)
-
end
-
end
-
-
1
private
-
-
1
def parse(url)
-
22
uri = URI.parse(url)
-
-
21
unless SUPPORTED_SCHEMES.include?(uri.scheme)
-
1
raise ArgumentError, 'only HTTP gateway URLs are supported currently.'
-
end
-
-
20
uri
-
rescue URI::InvalidURIError => e
-
1
raise ArgumentError, "#{url} is not a valid URL: #{e}"
-
end
-
-
1
def build_path(job, grouping_key)
-
22
path = format(PATH, ERB::Util::url_encode(job))
-
-
22
grouping_key.each do |label, value|
-
6
if value.include?('/')
-
1
encoded_value = Base64.urlsafe_encode64(value)
-
1
path += "/#{label}@base64/#{encoded_value}"
-
# While it's valid for the urlsafe_encode64 function to return an
-
# empty string when the input string is empty, it doesn't work for
-
# our specific use case as we're putting the result into a URL path
-
# segment. A double slash (`//`) can be normalised away by HTTP
-
# libraries, proxies, and web servers.
-
#
-
# For empty strings, we use a single padding character (`=`) as the
-
# value.
-
#
-
# See the pushgateway docs for more details:
-
#
-
# https://github.com/prometheus/pushgateway/blob/6393a901f56d4dda62cd0f6ab1f1f07c495b6354/README.md#url
-
5
elsif value.empty?
-
1
path += "/#{label}@base64/="
-
else
-
4
path += "/#{label}/#{ERB::Util::url_encode(value)}"
-
end
-
end
-
-
22
path
-
end
-
-
1
def request(req_class, registry = nil)
-
8
validate_no_label_clashes!(registry) if registry
-
-
7
req = req_class.new(@uri)
-
7
req.content_type = Formats::Text::CONTENT_TYPE
-
7
req.basic_auth(@user, @password) if @user
-
7
req.body = Formats::Text.marshal(registry) if registry
-
-
7
response = @http.request(req)
-
7
validate_response!(response)
-
-
4
response
-
end
-
-
1
def synchronize
-
6
@mutex.synchronize { yield }
-
end
-
-
1
def validate_no_basic_auth!(uri)
-
20
if uri.user || uri.password
-
1
raise ArgumentError, <<~EOF
-
Setting Basic Auth credentials in the gateway URL is not supported, please call the `basic_auth` method.
-
-
Received username `#{uri.user}` in gateway URL. Instead of passing
-
Basic Auth credentials like this:
-
-
```
-
push = Prometheus::Client::Push.new(job: "my-job", gateway: "http://user:password@localhost:9091")
-
```
-
-
please pass them like this:
-
-
```
-
push = Prometheus::Client::Push.new(job: "my-job", gateway: "http://localhost:9091")
-
push.basic_auth("user", "password")
-
```
-
-
While URLs do support passing Basic Auth credentials using the
-
`http://user:password@example.com/` syntax, the username and
-
password in that syntax have to follow the usual rules for URL
-
encoding of characters per RFC 3986
-
(https://datatracker.ietf.org/doc/html/rfc3986#section-2.1).
-
-
Rather than place the burden of correctly performing that encoding
-
on users of this gem, we decided to have a separate method for
-
supplying Basic Auth credentials, with no requirement to URL encode
-
the characters in them.
-
EOF
-
end
-
end
-
-
1
def validate_no_label_clashes!(registry)
-
# There's nothing to check if we don't have a grouping key
-
7
return if @grouping_key.empty?
-
-
# We could be doing a lot of comparisons, so let's do them against a
-
# set rather than an array
-
1
grouping_key_labels = @grouping_key.keys.to_set
-
-
1
registry.metrics.each do |metric|
-
1
metric.values.keys.first.keys.each do |label|
-
1
if grouping_key_labels.include?(label)
-
1
raise LabelSetValidator::InvalidLabelSetError,
-
"label :#{label} from grouping key collides with label of the " \
-
"same name from metric :#{metric.name} and would overwrite it"
-
end
-
end
-
end
-
end
-
-
1
def validate_response!(response)
-
7
status = Integer(response.code)
-
7
if status >= 300
-
3
message = "status: #{response.code}, message: #{response.message}, body: #{response.body}"
-
3
if status <= 399
-
1
raise HttpRedirectError, message
-
2
elsif status <= 499
-
1
raise HttpClientError, message
-
else
-
1
raise HttpServerError, message
-
end
-
end
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client'
-
-
1
module Prometheus
-
1
module Client
-
1
module Rack
-
# Collector is a Rack middleware that provides a sample implementation of
-
# a HTTP tracer. The default label builder can be modified to export a
-
# different set of labels per recorded metric.
-
1
class Collector
-
1
attr_reader :app, :registry
-
-
1
def initialize(app, options = {}, &label_builder)
-
5
@app = app
-
5
@registry = options[:registry] || Client.registry
-
5
@label_builder = label_builder || DEFAULT_LABEL_BUILDER
-
-
5
init_request_metrics
-
5
init_exception_metrics
-
end
-
-
1
def call(env) # :nodoc:
-
12
trace(env) { @app.call(env) }
-
end
-
-
1
protected
-
-
1
DEFAULT_LABEL_BUILDER = proc do |env|
-
{
-
4
method: env['REQUEST_METHOD'].downcase,
-
host: env['HTTP_HOST'].to_s,
-
path: env['PATH_INFO'].to_s,
-
}
-
end
-
-
1
def init_request_metrics
-
5
@requests = @registry.counter(
-
:http_requests_total,
-
'A counter of the total number of HTTP requests made.',
-
)
-
5
@durations = @registry.summary(
-
:http_request_duration_seconds,
-
'A summary of the response latency.',
-
)
-
5
@durations_hist = @registry.histogram(
-
:http_req_duration_seconds,
-
'A histogram of the response latency.',
-
)
-
end
-
-
1
def init_exception_metrics
-
5
@exceptions = @registry.counter(
-
:http_exceptions_total,
-
'A counter of the total number of exceptions raised.',
-
)
-
end
-
-
1
def trace(env)
-
6
start = Time.now
-
6
yield.tap do |response|
-
5
duration = (Time.now - start).to_f
-
5
record(labels(env, response), duration)
-
end
-
rescue => exception
-
2
@exceptions.increment(exception: exception.class.name)
-
2
raise
-
end
-
-
1
def labels(env, response)
-
5
@label_builder.call(env).tap do |labels|
-
5
labels[:code] = response.first.to_s
-
end
-
end
-
-
1
def record(labels, duration)
-
5
@requests.increment(labels)
-
4
@durations.observe(labels, duration)
-
4
@durations_hist.observe(labels, duration)
-
rescue => exception
-
1
@exceptions.increment(exception: exception.class.name)
-
1
raise
-
nil
-
end
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client'
-
1
require 'prometheus/client/formats/text'
-
-
1
module Prometheus
-
1
module Client
-
1
module Rack
-
# Exporter is a Rack middleware that provides a sample implementation of
-
# a Prometheus HTTP client API.
-
1
class Exporter
-
1
attr_reader :app, :registry, :path
-
-
1
FORMATS = [Formats::Text].freeze
-
1
FALLBACK = Formats::Text
-
-
1
def initialize(app, options = {})
-
@app = app
-
@registry = options[:registry] || Client.registry
-
@path = options[:path] || '/metrics'
-
@acceptable = build_dictionary(FORMATS, FALLBACK)
-
end
-
-
1
def call(env)
-
if env['PATH_INFO'] == @path
-
format = negotiate(env['HTTP_ACCEPT'], @acceptable)
-
format ? respond_with(format) : not_acceptable(FORMATS)
-
else
-
@app.call(env)
-
end
-
end
-
-
1
private
-
-
1
def negotiate(accept, formats)
-
accept = '*/*' if accept.to_s.empty?
-
-
parse(accept).each do |content_type, _|
-
return formats[content_type] if formats.key?(content_type)
-
end
-
-
nil
-
end
-
-
1
def parse(header)
-
header.to_s.split(/\s*,\s*/).map do |type|
-
attributes = type.split(/\s*;\s*/)
-
quality = extract_quality(attributes)
-
-
[attributes.join('; '), quality]
-
end.sort_by(&:last).reverse
-
end
-
-
1
def extract_quality(attributes, default = 1.0)
-
quality = default
-
-
attributes.delete_if do |attr|
-
quality = attr.split('q=').last.to_f if attr.start_with?('q=')
-
end
-
-
quality
-
end
-
-
1
def respond_with(format)
-
rust_enabled = Prometheus::Client.configuration.rust_multiprocess_metrics
-
-
response = if Prometheus::Client.configuration.value_class.multiprocess
-
format.marshal_multiprocess(use_rust: rust_enabled)
-
else
-
format.marshal
-
end
-
[
-
200,
-
{ 'Content-Type' => format::CONTENT_TYPE },
-
[response],
-
]
-
end
-
-
1
def not_acceptable(formats)
-
types = formats.map { |format| format::MEDIA_TYPE }
-
-
[
-
406,
-
{ 'Content-Type' => 'text/plain' },
-
["Supported media types: #{types.join(', ')}"],
-
]
-
end
-
-
1
def build_dictionary(formats, fallback)
-
formats.each_with_object('*/*' => fallback) do |format, memo|
-
memo[format::CONTENT_TYPE] = format
-
memo[format::MEDIA_TYPE] = format
-
end
-
end
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'thread'
-
-
1
require 'prometheus/client/counter'
-
1
require 'prometheus/client/summary'
-
1
require 'prometheus/client/gauge'
-
1
require 'prometheus/client/histogram'
-
-
1
module Prometheus
-
1
module Client
-
# Registry
-
1
class Registry
-
1
class AlreadyRegisteredError < StandardError; end
-
-
1
def initialize
-
36
@metrics = {}
-
36
@mutex = Mutex.new
-
end
-
-
1
def register(metric)
-
72
name = metric.name
-
-
72
@mutex.synchronize do
-
72
if exist?(name.to_sym)
-
5
raise AlreadyRegisteredError, "#{name} has already been registered"
-
else
-
67
@metrics[name.to_sym] = metric
-
end
-
end
-
-
67
metric
-
end
-
-
1
def counter(name, docstring, base_labels = {})
-
15
register(Counter.new(name, docstring, base_labels))
-
end
-
-
1
def summary(name, docstring, base_labels = {})
-
10
register(Summary.new(name, docstring, base_labels))
-
end
-
-
1
def gauge(name, docstring, base_labels = {}, multiprocess_mode = :all)
-
21
register(Gauge.new(name, docstring, base_labels, multiprocess_mode))
-
end
-
-
1
def histogram(name, docstring, base_labels = {},
-
buckets = Histogram::DEFAULT_BUCKETS)
-
10
register(Histogram.new(name, docstring, base_labels, buckets))
-
end
-
-
1
def exist?(name)
-
74
@metrics.key?(name)
-
end
-
-
1
def get(name)
-
11
@metrics[name.to_sym]
-
end
-
-
1
def metrics
-
15
@metrics.values
-
end
-
end
-
end
-
end
-
1
require 'json'
-
-
1
module Prometheus
-
1
module Client
-
1
class SimpleValue
-
1
def initialize(_type, _metric_name, _name, _labels, *_args)
-
88
@value = 0.0
-
end
-
-
1
def set(value)
-
12
@value = value
-
end
-
-
1
def increment(by = 1)
-
74
@value += by
-
end
-
-
1
def decrement(by = 1)
-
2
@value -= by
-
end
-
-
1
def get
-
35
@value
-
end
-
-
1
def self.multiprocess
-
10
false
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/metric'
-
1
require 'prometheus/client/uses_value_type'
-
-
1
module Prometheus
-
1
module Client
-
# Summary is an accumulator for samples. It captures Numeric data and
-
# provides an efficient quantile calculation mechanism.
-
1
class Summary < Metric
-
1
extend Gem::Deprecate
-
-
# Value represents the state of a Summary at a given point.
-
1
class Value < Hash
-
1
include UsesValueType
-
1
attr_accessor :sum, :total
-
-
1
def initialize(type, name, labels)
-
11
@sum = value_object(type, name, "#{name}_sum", labels)
-
11
@total = value_object(type, name, "#{name}_count", labels)
-
end
-
-
1
def observe(value)
-
9
@sum.increment(value)
-
9
@total.increment
-
end
-
end
-
-
1
def initialize(name, docstring, base_labels = {})
-
18
super(name, docstring, base_labels)
-
end
-
-
1
def type
-
30
:summary
-
end
-
-
# Records a given value.
-
1
def observe(labels, value)
-
9
label_set = label_set_for(labels)
-
18
synchronize { @values[label_set].observe(value) }
-
end
-
-
1
alias add observe
-
1
deprecate :add, :observe, 2016, 10
-
-
# Returns the value for the given label set
-
1
def get(labels = {})
-
4
@validator.valid?(labels)
-
-
4
synchronize do
-
4
@values[labels].sum.get
-
end
-
end
-
-
# Returns all label sets with their values
-
1
def values
-
synchronize do
-
@values.each_with_object({}) do |(labels, value), memo|
-
memo[labels] = value.sum
-
end
-
end
-
end
-
-
1
private
-
-
1
def default(labels)
-
11
Value.new(type, @name, labels)
-
end
-
end
-
end
-
end
-
1
module Prometheus
-
1
module Client
-
1
module Support
-
1
module Puma
-
1
extend self
-
-
1
def worker_pid_provider
-
4
wid = worker_id
-
4
if wid = worker_id
-
3
wid
-
else
-
1
"process_id_#{Process.pid}"
-
end
-
end
-
-
1
private
-
-
1
def object_based_worker_id
-
6
return unless defined?(::Puma::Cluster::Worker)
-
-
2
workers = ObjectSpace.each_object(::Puma::Cluster::Worker)
-
2
return if workers.nil?
-
-
2
workers_first = workers.first
-
2
workers_first.index unless workers_first.nil?
-
end
-
-
1
def program_name
-
$PROGRAM_NAME
-
end
-
-
1
def worker_id
-
8
if matchdata = program_name.match(/puma.*cluster worker ([0-9]+):/)
-
2
"puma_#{matchdata[1]}"
-
6
elsif object_worker_id = object_based_worker_id
-
2
"puma_#{object_worker_id}"
-
4
elsif program_name.include?('puma')
-
2
'puma_master'
-
end
-
end
-
end
-
end
-
end
-
end
-
1
module Prometheus
-
1
module Client
-
1
module Support
-
1
module Unicorn
-
1
def self.worker_pid_provider
-
2
wid = worker_id
-
2
if wid.nil?
-
1
"process_id_#{Process.pid}"
-
else
-
1
"worker_id_#{wid}"
-
end
-
end
-
-
1
def self.worker_id
-
2
match = $0.match(/worker\[([^\]]+)\]/)
-
2
if match
-
1
match[1]
-
else
-
1
object_based_worker_id
-
end
-
end
-
-
1
def self.object_based_worker_id
-
3
return unless defined?(::Unicorn::Worker)
-
-
2
workers = ObjectSpace.each_object(::Unicorn::Worker)
-
2
return if workers.nil?
-
-
1
workers_first = workers.first
-
1
workers_first.nr unless workers_first.nil?
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/simple_value'
-
-
1
module Prometheus
-
1
module Client
-
# Module providing convenience methods for creating value_object
-
1
module UsesValueType
-
1
def value_class
-
40266
Prometheus::Client.configuration.value_class
-
end
-
-
1
def value_object(type, metric_name, name, labels, *args)
-
40230
value_class.new(type, metric_name, name, labels, *args)
-
rescue StandardError => e
-
Prometheus::Client.logger.info("error #{e} while creating instance of #{value_class} defaulting to SimpleValue")
-
Prometheus::Client.logger.debug("error #{e} backtrace #{e.backtrace.join("\n")}")
-
Prometheus::Client::SimpleValue.new(type, metric_name, name, labels)
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
shared_examples_for Prometheus::Client::Metric do
-
20
subject { described_class.new(:foo, 'foo description') }
-
-
4
describe '.new' do
-
4
it 'returns a new metric' do
-
4
expect(subject).to be
-
end
-
-
4
it 'raises an exception if a reserved base label is used' do
-
4
exception = Prometheus::Client::LabelSetValidator::ReservedLabelError
-
-
4
expect do
-
4
described_class.new(:foo, 'foo docstring', __name__: 'reserved')
-
end.to raise_exception exception
-
end
-
-
4
it 'raises an exception if the given name is blank' do
-
4
expect do
-
4
described_class.new(nil, 'foo')
-
end.to raise_exception ArgumentError
-
end
-
-
4
it 'raises an exception if docstring is missing' do
-
4
expect do
-
4
described_class.new(:foo, '')
-
end.to raise_exception ArgumentError
-
end
-
end
-
-
4
describe '#type' do
-
4
it 'returns the metric type as symbol' do
-
4
expect(subject.type).to be_a(Symbol)
-
end
-
end
-
-
4
describe '#get' do
-
4
it 'returns the current metric value' do
-
4
expect(subject.get).to be_a(type)
-
end
-
-
4
it 'returns the current metric value for a given label set' do
-
4
expect(subject.get(test: 'label')).to be_a(type)
-
end
-
end
-
end
-
1
require 'prometheus/client/counter'
-
1
require 'prometheus/client'
-
1
require 'examples/metric_example'
-
-
1
describe Prometheus::Client::Counter do
-
1
before do
-
14
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return('tmp/')
-
end
-
-
7
let(:counter) { Prometheus::Client::Counter.new(:foo, 'foo description') }
-
-
1
it_behaves_like Prometheus::Client::Metric do
-
3
let(:type) { Float }
-
end
-
-
1
describe 'Memory Error tests' do
-
1
it "creating many counters shouldn't cause a SIGBUS" do
-
1
4.times do |j|
-
4
9999.times do |i|
-
39996
counter = Prometheus::Client::Counter.new("foo#{j}_z#{i}".to_sym, 'some string')
-
39996
counter.increment
-
end
-
4
GC.start
-
end
-
end
-
end
-
-
1
describe '#increment' do
-
1
it 'increments the counter' do
-
4
expect { counter.increment }.to change { counter.get }.by(1)
-
end
-
-
1
it 'increments the counter for a given label set' do
-
1
expect do
-
1
expect do
-
1
counter.increment(test: 'label')
-
2
end.to change { counter.get(test: 'label') }.by(1)
-
2
end.to_not change { counter.get(test: 'other_label') }
-
end
-
-
1
it 'increments the counter by a given value' do
-
1
expect do
-
1
counter.increment({}, 5)
-
2
end.to change { counter.get }.by(5)
-
end
-
-
1
it 'raises an ArgumentError on negative increments' do
-
1
expect do
-
1
counter.increment({}, -1)
-
end.to raise_error ArgumentError
-
end
-
-
1
it 'returns the new counter value' do
-
1
expect(counter.increment).to eql(counter.get)
-
end
-
-
1
it 'is thread safe' do
-
1
expect do
-
1
Array.new(10) do
-
10
Thread.new do
-
110
10.times { counter.increment }
-
end
-
end.each(&:join)
-
2
end.to change { counter.get }.by(100)
-
end
-
end
-
end
-
1
require 'spec_helper'
-
1
require 'prometheus/client/formats/text'
-
1
require 'prometheus/client/mmaped_value'
-
-
1
describe Prometheus::Client::Formats::Text do
-
1
context 'single process metrics' do
-
2
let(:value_class) { Prometheus::Client::SimpleValue }
-
-
1
let(:summary_value) do
-
1
{ 0.5 => 4.2, 0.9 => 8.32, 0.99 => 15.3 }.tap do |value|
-
1
allow(value).to receive_messages(sum: 1243.21, total: 93)
-
end
-
end
-
-
1
let(:histogram_value) do
-
1
{ 10 => 1, 20 => 2, 30 => 2 }.tap do |value|
-
1
allow(value).to receive_messages(sum: 15.2, total: 2)
-
end
-
end
-
-
1
let(:registry) do
-
metrics = [
-
1
double(
-
name: :foo,
-
docstring: 'foo description',
-
base_labels: { umlauts: 'Björn', utf: '佖佥' },
-
type: :counter,
-
values: {
-
{ code: 'red' } => 42,
-
{ code: 'green' } => 3.14E42,
-
{ code: 'blue' } => -1.23e-45,
-
},
-
),
-
double(
-
name: :bar,
-
docstring: "bar description\nwith newline",
-
base_labels: { status: 'success' },
-
type: :gauge,
-
values: {
-
{ code: 'pink' } => 15,
-
},
-
),
-
double(
-
name: :baz,
-
docstring: 'baz "description" \\escaping',
-
base_labels: {},
-
type: :counter,
-
values: {
-
{ text: "with \"quotes\", \\escape \n and newline" } => 15,
-
},
-
),
-
double(
-
name: :qux,
-
docstring: 'qux description',
-
base_labels: { for: 'sake' },
-
type: :summary,
-
values: {
-
{ code: '1' } => summary_value,
-
},
-
),
-
double(
-
name: :xuq,
-
docstring: 'xuq description',
-
base_labels: {},
-
type: :histogram,
-
values: {
-
{ code: 'ah' } => histogram_value,
-
},
-
),
-
]
-
1
metrics.each do |m|
-
5
m.values.each do |k, v|
-
7
m.values[k] = value_class.new(m.type, m.name, m.name, k)
-
7
m.values[k].set(v)
-
end
-
end
-
1
double(metrics: metrics)
-
end
-
-
1
describe '.marshal' do
-
1
it 'returns a Text format version 0.0.4 compatible representation' do
-
1
expect(subject.marshal(registry)).to eql <<-'TEXT'.gsub(/^\s+/, '')
-
# HELP foo foo description
-
# TYPE foo counter
-
foo{umlauts="Björn",utf="佖佥",code="red"} 42
-
foo{umlauts="Björn",utf="佖佥",code="green"} 3.14e+42
-
foo{umlauts="Björn",utf="佖佥",code="blue"} -1.23e-45
-
# HELP bar bar description\nwith newline
-
# TYPE bar gauge
-
bar{status="success",code="pink"} 15
-
# HELP baz baz "description" \\escaping
-
# TYPE baz counter
-
baz{text="with \"quotes\", \\escape \n and newline"} 15
-
# HELP qux qux description
-
# TYPE qux summary
-
qux{for="sake",code="1",quantile="0.5"} 4.2
-
qux{for="sake",code="1",quantile="0.9"} 8.32
-
qux{for="sake",code="1",quantile="0.99"} 15.3
-
qux_sum{for="sake",code="1"} 1243.21
-
qux_count{for="sake",code="1"} 93
-
# HELP xuq xuq description
-
# TYPE xuq histogram
-
xuq{code="ah",le="10"} 1
-
xuq{code="ah",le="20"} 2
-
xuq{code="ah",le="30"} 2
-
xuq{code="ah",le="+Inf"} 2
-
xuq_sum{code="ah"} 15.2
-
xuq_count{code="ah"} 2
-
TEXT
-
end
-
end
-
end
-
-
1
context 'multi process metrics', :temp_metrics_dir do
-
1
[true, false].each do |use_rust|
-
2
context "when rust_multiprocess_metrics is #{use_rust}" do
-
6
let(:registry) { Prometheus::Client::Registry.new }
-
-
2
before do
-
4
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(temp_metrics_dir)
-
4
allow(Prometheus::Client.configuration).to receive(:rust_multiprocess_metrics).and_return(use_rust)
-
# reset all current metrics
-
4
Prometheus::Client::MmapedValue.class_variable_set(:@@files, {})
-
end
-
-
2
context 'pid provider returns compound ID', :temp_metrics_dir, :sample_metrics do
-
2
before do
-
12
allow(Prometheus::Client.configuration).to receive(:pid_provider).and_return(-> { 'pid_provider_id_1' })
-
# Prometheus::Client::MmapedValue.class_variable_set(:@@files, {})
-
2
add_simple_metrics(registry)
-
end
-
-
2
it '.marshal_multiprocess' do
-
2
expect(described_class.marshal_multiprocess(temp_metrics_dir, use_rust: true)).to eq <<-'TEXT'.gsub(/^\s+/, '')
-
# HELP counter Multiprocess metric
-
# TYPE counter counter
-
counter{a="1",b="1"} 1
-
counter{a="1",b="2"} 1
-
counter{a="2",b="1"} 1
-
# HELP gauge Multiprocess metric
-
# TYPE gauge gauge
-
gauge{b="1"} 1
-
gauge{b="2"} 1
-
# HELP gauge_with_big_value Multiprocess metric
-
# TYPE gauge_with_big_value gauge
-
gauge_with_big_value{a="0.12345678901234566"} 0.12345678901234566
-
gauge_with_big_value{a="12345678901234567"} 12345678901234568
-
# HELP gauge_with_null_labels Multiprocess metric
-
# TYPE gauge_with_null_labels gauge
-
gauge_with_null_labels{a="",b=""} 1
-
# HELP gauge_with_pid Multiprocess metric
-
# TYPE gauge_with_pid gauge
-
gauge_with_pid{b="1",c="1",pid="pid_provider_id_1"} 1
-
# HELP histogram Multiprocess metric
-
# TYPE histogram histogram
-
histogram_bucket{a="1",le="+Inf"} 1
-
histogram_bucket{a="1",le="0.005"} 0
-
histogram_bucket{a="1",le="0.01"} 0
-
histogram_bucket{a="1",le="0.025"} 0
-
histogram_bucket{a="1",le="0.05"} 0
-
histogram_bucket{a="1",le="0.1"} 0
-
histogram_bucket{a="1",le="0.25"} 0
-
histogram_bucket{a="1",le="0.5"} 0
-
histogram_bucket{a="1",le="1"} 1
-
histogram_bucket{a="1",le="10"} 1
-
histogram_bucket{a="1",le="2.5"} 1
-
histogram_bucket{a="1",le="5"} 1
-
histogram_count{a="1"} 1
-
histogram_sum{a="1"} 1
-
# HELP summary Multiprocess metric
-
# TYPE summary summary
-
summary_count{a="1",b="1"} 1
-
summary_sum{a="1",b="1"} 1
-
TEXT
-
end
-
end
-
-
2
context 'pid provider returns numerical value', :temp_metrics_dir, :sample_metrics do
-
2
before do
-
12
allow(Prometheus::Client.configuration).to receive(:pid_provider).and_return(-> { -1 })
-
2
add_simple_metrics(registry)
-
end
-
-
2
it '.marshal_multiprocess' do
-
2
expect(described_class.marshal_multiprocess(temp_metrics_dir, use_rust: use_rust)).to eq <<-'TEXT'.gsub(/^\s+/, '')
-
# HELP counter Multiprocess metric
-
# TYPE counter counter
-
counter{a="1",b="1"} 1
-
counter{a="1",b="2"} 1
-
counter{a="2",b="1"} 1
-
# HELP gauge Multiprocess metric
-
# TYPE gauge gauge
-
gauge{b="1"} 1
-
gauge{b="2"} 1
-
# HELP gauge_with_big_value Multiprocess metric
-
# TYPE gauge_with_big_value gauge
-
gauge_with_big_value{a="0.12345678901234566"} 0.12345678901234566
-
gauge_with_big_value{a="12345678901234567"} 12345678901234568
-
# HELP gauge_with_null_labels Multiprocess metric
-
# TYPE gauge_with_null_labels gauge
-
gauge_with_null_labels{a="",b=""} 1
-
# HELP gauge_with_pid Multiprocess metric
-
# TYPE gauge_with_pid gauge
-
gauge_with_pid{b="1",c="1",pid="-1"} 1
-
# HELP histogram Multiprocess metric
-
# TYPE histogram histogram
-
histogram_bucket{a="1",le="+Inf"} 1
-
histogram_bucket{a="1",le="0.005"} 0
-
histogram_bucket{a="1",le="0.01"} 0
-
histogram_bucket{a="1",le="0.025"} 0
-
histogram_bucket{a="1",le="0.05"} 0
-
histogram_bucket{a="1",le="0.1"} 0
-
histogram_bucket{a="1",le="0.25"} 0
-
histogram_bucket{a="1",le="0.5"} 0
-
histogram_bucket{a="1",le="1"} 1
-
histogram_bucket{a="1",le="10"} 1
-
histogram_bucket{a="1",le="2.5"} 1
-
histogram_bucket{a="1",le="5"} 1
-
histogram_count{a="1"} 1
-
histogram_sum{a="1"} 1
-
# HELP summary Multiprocess metric
-
# TYPE summary summary
-
summary_count{a="1",b="1"} 1
-
summary_sum{a="1",b="1"} 1
-
TEXT
-
end
-
end
-
-
2
context 'when OJ is available uses OJ to parse keys' do
-
2
let(:oj) { double(oj) }
-
2
before do
-
stub_const 'Oj', oj
-
allow(oj).to receive(:load)
-
end
-
end
-
-
2
context 'with metric having whitespace and UTF chars', :temp_metrics_dir do
-
2
before do
-
registry.gauge(:gauge, "bar description\nwith newline", { umlauts: 'Björn', utf: '佖佥' }, :all).set({ umlauts: 'Björn', utf: '佖佥' }, 1)
-
end
-
-
2
xit '.marshall_multiprocess' do
-
expect(described_class.marshal_multiprocess(temp_metrics_dir, use_rust: true)).to eq <<-'TEXT'.gsub(/^\s+/, '')
-
TODO...
-
TEXT
-
end
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client'
-
1
require 'prometheus/client/gauge'
-
1
require 'examples/metric_example'
-
-
1
describe Prometheus::Client::Gauge do
-
7
let(:gauge) { Prometheus::Client::Gauge.new(:foo, 'foo description', test: nil) }
-
-
1
before do
-
13
allow(Prometheus::Client.configuration).to receive(:value_class).and_return(Prometheus::Client::SimpleValue)
-
end
-
-
1
it_behaves_like Prometheus::Client::Metric do
-
3
let(:type) { Float }
-
end
-
-
1
describe '#set' do
-
1
it 'sets a metric value' do
-
1
expect do
-
1
gauge.set({}, 42)
-
2
end.to change { gauge.get }.from(0).to(42)
-
end
-
-
1
it 'sets a metric value for a given label set' do
-
1
expect do
-
1
expect do
-
1
gauge.set({ test: 'value' }, 42)
-
2
end.to(change { gauge.get(test: 'value') }.from(0).to(42))
-
2
end.to_not(change { gauge.get })
-
end
-
end
-
-
1
describe '#increment' do
-
1
it 'increments a metric value' do
-
1
gauge.set({}, 1)
-
-
1
expect do
-
1
gauge.increment({}, 42)
-
2
end.to change { gauge.get }.from(1).to(43)
-
end
-
-
1
it 'sets a metric value for a given label set' do
-
1
gauge.increment({ test: 'value' }, 1)
-
1
expect do
-
1
expect do
-
1
gauge.increment({ test: 'value' }, 42)
-
2
end.to(change { gauge.get(test: 'value') }.from(1).to(43))
-
2
end.to_not(change { gauge.get })
-
end
-
end
-
-
1
describe '#decrement' do
-
1
it 'decrements a metric value' do
-
1
gauge.set({}, 10)
-
-
1
expect do
-
1
gauge.decrement({}, 1)
-
2
end.to change { gauge.get }.from(10).to(9)
-
end
-
-
1
it 'sets a metric value for a given label set' do
-
1
gauge.set({ test: 'value' }, 10)
-
1
expect do
-
1
expect do
-
1
gauge.decrement({ test: 'value' }, 5)
-
2
end.to(change { gauge.get(test: 'value') }.from(10).to(5))
-
2
end.to_not(change { gauge.get })
-
end
-
end
-
-
end
-
1
require 'spec_helper'
-
1
require 'oj'
-
1
require 'prometheus/client/helper/json_parser'
-
-
1
describe Prometheus::Client::Helper::JsonParser do
-
1
describe '.load' do
-
3
let(:input) { %({ "a": 1 }) }
-
-
1
shared_examples 'JSON parser' do
-
2
it 'parses JSON' do
-
2
expect(described_class.load(input)).to eq({ 'a' => 1 })
-
end
-
-
2
it 'raises JSON::ParserError' do
-
4
expect { described_class.load("{false}") }.to raise_error(JSON::ParserError)
-
end
-
end
-
-
1
context 'with Oj' do
-
1
it_behaves_like 'JSON parser'
-
end
-
-
1
context 'without Oj' do
-
1
before(:all) do
-
1
Object.send(:remove_const, 'Oj')
-
1
load File.join(__dir__, "../../../../lib/prometheus/client/helper/json_parser.rb")
-
end
-
-
1
it_behaves_like 'JSON parser'
-
end
-
end
-
end
-
1
require 'spec_helper'
-
1
require 'prometheus/client/helper/mmaped_file'
-
1
require 'prometheus/client/page_size'
-
-
1
describe Prometheus::Client::Helper::MmapedFile do
-
11
let(:filename) { Dir::Tmpname.create('mmaped_file_') {} }
-
-
1
after do
-
10
File.delete(filename) if File.exist?(filename)
-
end
-
-
1
describe '.open' do
-
1
it 'initialize PRIVATE mmaped file read only' do
-
1
expect(described_class).to receive(:new).with(filename).and_call_original
-
-
1
expect(described_class.open(filename)).to be_instance_of(described_class)
-
end
-
end
-
-
1
context 'file does not exist' do
-
4
let (:subject) { described_class.open(filename) }
-
1
it 'creates and initializes file correctly' do
-
1
expect(File.exist?(filename)).to be_falsey
-
-
1
subject
-
-
1
expect(File.exist?(filename)).to be_truthy
-
end
-
-
1
it 'creates a file with minimum initial size' do
-
1
expect(File.size(subject.filepath)).to eq(subject.send(:initial_mmap_file_size))
-
end
-
-
1
context 'when initial mmap size is larger' do
-
2
let(:page_size) { Prometheus::Client::PageSize.page_size }
-
2
let (:initial_mmap_file_size) { page_size + 1024 }
-
-
1
before do
-
1
allow_any_instance_of(described_class).to receive(:initial_mmap_file_size).and_return(initial_mmap_file_size)
-
end
-
-
1
it 'creates a file with increased minimum initial size' do
-
1
expect(File.size(subject.filepath)).to eq(page_size * 2);
-
end
-
end
-
end
-
-
1
describe '.ensure_exclusive_file' do
-
7
let(:tmpdir) { Dir.mktmpdir('mmaped_file') }
-
7
let(:pid) { 'pid' }
-
-
1
before do
-
6
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(tmpdir)
-
6
allow(Prometheus::Client.configuration).to receive(:pid_provider).and_return(pid.method(:to_s))
-
end
-
-
1
context 'when no files are already locked' do
-
1
it 'provides first possible filename' do
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-0\.db/)
-
end
-
-
1
it 'provides first and second possible filenames for two invocations' do
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-0\.db/)
-
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-1\.db/)
-
end
-
end
-
-
1
context 'when first possible file exists for current file ID' do
-
5
let(:first_mmaped_file) { described_class.ensure_exclusive_file('mmaped_file') }
-
1
before do
-
4
first_mmaped_file
-
end
-
-
1
context 'first file is unlocked' do
-
1
before do
-
2
Prometheus::Client::Helper::FileLocker.unlock(first_mmaped_file)
-
end
-
-
1
it 'provides first possible filename discarding the lock' do
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-0\.db/)
-
end
-
-
1
it 'provides second possible filename for second invocation' do
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-0\.db/)
-
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-1\.db/)
-
end
-
end
-
-
1
context 'first file is not unlocked' do
-
1
it 'provides second possible filename' do
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-1\.db/)
-
end
-
-
1
it 'provides second and third possible filename for two invocations' do
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-1\.db/)
-
-
1
expect(described_class.ensure_exclusive_file('mmaped_file'))
-
.to match(/.*mmaped_file_pid-2\.db/)
-
end
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
1
require 'prometheus/client'
-
1
require 'prometheus/client/histogram'
-
1
require 'examples/metric_example'
-
-
1
describe Prometheus::Client::Histogram do
-
1
before do
-
10
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return('tmp/')
-
end
-
-
1
let(:histogram) do
-
1
described_class.new(:bar, 'bar description', {}, [2.5, 5, 10])
-
end
-
-
1
it_behaves_like Prometheus::Client::Metric do
-
3
let(:type) { Hash }
-
end
-
-
1
describe '#initialization' do
-
1
it 'raise error for unsorted buckets' do
-
1
expect do
-
1
described_class.new(:bar, 'bar description', {}, [5, 2.5, 10])
-
end.to raise_error ArgumentError
-
end
-
-
1
it 'raise error for accidentally missing out an argument' do
-
1
expect do
-
1
described_class.new(:bar, 'bar description', [5, 2.5, 10])
-
end.to raise_error Prometheus::Client::LabelSetValidator::InvalidLabelSetError
-
end
-
end
-
-
1
describe '#observe' do
-
1
it 'records the given value' do
-
1
expect do
-
1
histogram.observe({}, 5)
-
2
end.to change { histogram.get }
-
end
-
-
1
xit 'raise error for le labels' do
-
expect do
-
histogram.observe({ le: 1 }, 5)
-
end.to raise_error ArgumentError
-
end
-
end
-
-
1
describe '#get' do
-
1
before do
-
histogram.observe({ foo: 'bar' }, 3)
-
histogram.observe({ foo: 'bar' }, 5.2)
-
histogram.observe({ foo: 'bar' }, 13)
-
histogram.observe({ foo: 'bar' }, 4)
-
end
-
-
1
xit 'returns a set of buckets values' do
-
expect(histogram.get(foo: 'bar')).to eql(2.5 => 0, 5 => 2, 10 => 3)
-
end
-
-
1
xit 'returns a value which responds to #sum and #total' do
-
value = histogram.get(foo: 'bar')
-
-
expect(value.sum).to eql(25.2)
-
expect(value.total).to eql(4)
-
expect(value.total_inf).to eql(4)
-
end
-
-
1
xit 'uses zero as default value' do
-
expect(histogram.get({})).to eql(2.5 => 0, 5 => 0, 10 => 0)
-
end
-
end
-
-
1
xdescribe '#values' do
-
1
it 'returns a hash of all recorded summaries' do
-
histogram.observe({ status: 'bar' }, 3)
-
histogram.observe({ status: 'foo' }, 6)
-
-
expect(histogram.values).to eql(
-
{ status: 'bar' } => { 2.5 => 0, 5 => 1, 10 => 1 },
-
{ status: 'foo' } => { 2.5 => 0, 5 => 0, 10 => 1 },
-
)
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client/label_set_validator'
-
-
1
describe Prometheus::Client::LabelSetValidator do
-
11
let(:validator) { Prometheus::Client::LabelSetValidator.new reserved_labels }
-
10
let(:reserved_labels) { [] }
-
-
1
describe '.new' do
-
1
it 'returns an instance of a LabelSetValidator' do
-
1
expect(validator).to be_a(Prometheus::Client::LabelSetValidator)
-
end
-
end
-
-
1
describe '#valid?' do
-
1
it 'returns true for a valid label check' do
-
1
expect(validator.valid?(version: 'alpha')).to eql(true)
-
end
-
-
1
it 'raises InvalidLabelError if a label value is an array' do
-
1
expect do
-
1
validator.valid?(version: [1, 2, 3])
-
end.to raise_exception(described_class::InvalidLabelError)
-
end
-
-
1
it 'raises Invaliddescribed_classError if a label set is not a hash' do
-
1
expect do
-
1
validator.valid?('invalid')
-
end.to raise_exception(described_class::InvalidLabelSetError)
-
end
-
-
1
it 'raises InvalidLabelError if a label key is not a symbol' do
-
1
expect do
-
1
validator.valid?('key' => 'value')
-
end.to raise_exception(described_class::InvalidLabelError)
-
end
-
-
1
it 'raises InvalidLabelError if a label key starts with __' do
-
1
expect do
-
1
validator.valid?(__reserved__: 'key')
-
end.to raise_exception(described_class::ReservedLabelError)
-
end
-
-
1
context "when reserved labels were set" do
-
2
let(:reserved_labels) { [:reserved] }
-
-
1
it 'raises ReservedLabelError if a label key is reserved' do
-
1
reserved_labels.each do |label|
-
1
expect do
-
1
validator.valid?(label => 'value')
-
end.to raise_exception(described_class::ReservedLabelError)
-
end
-
end
-
end
-
end
-
-
1
describe '#validate' do
-
1
it 'returns a given valid label set' do
-
1
hash = { version: 'alpha' }
-
-
1
expect(validator.validate(hash)).to eql(hash)
-
end
-
-
1
it 'raises an exception if a given label set is not valid' do
-
1
input = 'broken'
-
1
expect(validator).to receive(:valid?).with(input).and_raise(described_class::InvalidLabelSetError)
-
-
2
expect { validator.validate(input) }.to raise_exception(described_class::InvalidLabelSetError)
-
end
-
-
1
it 'raises InvalidLabelSetError for varying label sets' do
-
1
validator.validate(method: 'get', code: '200')
-
-
1
expect do
-
1
validator.validate(method: 'get', exception: 'NoMethodError')
-
end.to raise_exception(described_class::InvalidLabelSetError, "labels must have the same signature: (expected keys: [:code, :method], got: [:exception, :method])")
-
end
-
end
-
end
-
1
require 'prometheus/client/mmaped_dict'
-
1
require 'prometheus/client/page_size'
-
1
require 'tempfile'
-
-
1
describe Prometheus::Client::MmapedDict do
-
9
let(:tmp_file) { Tempfile.new('mmaped_dict') }
-
9
let(:tmp_mmaped_file) { Prometheus::Client::Helper::MmapedFile.open(tmp_file.path) }
-
-
1
after do
-
8
tmp_mmaped_file.close
-
8
tmp_file.close
-
8
tmp_file.unlink
-
end
-
-
1
describe '#initialize' do
-
1
describe "empty mmap'ed file" do
-
1
it 'is initialized with correct size' do
-
1
described_class.new(tmp_mmaped_file)
-
-
1
expect(File.size(tmp_file.path)).to eq(tmp_mmaped_file.send(:initial_mmap_file_size))
-
end
-
end
-
-
1
describe "mmap'ed file that is above minimum size" do
-
2
let(:above_minimum_size) { Prometheus::Client::Helper::EntryParser::MINIMUM_SIZE + 1 }
-
2
let(:page_size) { Prometheus::Client::PageSize.page_size }
-
-
1
before do
-
1
tmp_file.truncate(above_minimum_size)
-
end
-
-
1
it 'is initialized with the a page size' do
-
1
described_class.new(tmp_mmaped_file)
-
-
1
tmp_file.open
-
1
expect(tmp_file.size).to eq(page_size);
-
end
-
end
-
end
-
-
1
describe 'read on boundary conditions' do
-
2
let(:locked_file) { Prometheus::Client::Helper::MmapedFile.ensure_exclusive_file }
-
1
let(:mmaped_file) { Prometheus::Client::Helper::MmapedFile.open(locked_file) }
-
2
let(:page_size) { Prometheus::Client::PageSize.page_size }
-
1
let(:target_size) { page_size }
-
2
let(:iterations) { page_size / 32 }
-
2
let(:dummy_key) { '1234' }
-
2
let(:dummy_value) { 1.0 }
-
2
let(:expected) { { dummy_key => dummy_value } }
-
-
1
before do
-
1
Prometheus::Client.configuration.multiprocess_files_dir = Dir.tmpdir
-
-
1
data = described_class.new(Prometheus::Client::Helper::MmapedFile.open(locked_file))
-
-
# This test exercises the case when the value ends on the last byte.
-
# To generate a file like this, we create entries that require 32 bytes
-
# total to store with 7 bytes of padding at the end.
-
#
-
# To make things align evenly against the system page size, add a dummy
-
# entry that will occupy the next 3 bytes to start on a 32-byte boundary.
-
# The filestructure looks like:
-
#
-
# Bytes 0-3 : Total used size of file
-
# Bytes 4-7 : Padding
-
# Bytes 8-11 : Length of '1234' (4)
-
# Bytes 12-15: '1234'
-
# Bytes 24-31: 1.0
-
# Bytes 32-35: Length of '1000000000000' (13)
-
# Bytes 36-48: '1000000000000'
-
# Bytes 49-55: Padding
-
# Bytes 56-63: 0.0
-
# Bytes 64-67: Length of '1000000000001' (13)
-
# Bytes 68-80: '1000000000001'
-
# Bytes 81-87: Padding
-
# Bytes 88-95: 1.0
-
# ...
-
1
data.write_value(dummy_key, dummy_value)
-
-
1
(1..iterations - 1).each do |i|
-
# Using a 13-byte string
-
127
text = (1000000000000 + i).to_s
-
127
expected[text] = i.to_f
-
127
data.write_value(text, i)
-
end
-
-
1
data.close
-
end
-
-
1
it '#read_all_values' do
-
1
values = described_class.read_all_values(locked_file)
-
-
1
expect(values.count).to eq(iterations)
-
1
expect(values).to match_array(expected.to_a)
-
end
-
end
-
-
1
describe 'read and write values' do
-
6
let(:locked_file) { Prometheus::Client::Helper::MmapedFile.ensure_exclusive_file }
-
6
let(:mmaped_file) { Prometheus::Client::Helper::MmapedFile.open(locked_file) }
-
-
1
before do
-
5
Prometheus::Client.configuration.multiprocess_files_dir = Dir.tmpdir
-
-
5
data = described_class.new(Prometheus::Client::Helper::MmapedFile.open(locked_file))
-
5
data.write_value('foo', 100)
-
5
data.write_value('bar', 500)
-
-
5
data.close
-
end
-
-
1
after do
-
5
mmaped_file.close if File.exist?(mmaped_file.filepath)
-
5
Prometheus::Client::Helper::FileLocker.unlock(locked_file) if File.exist?(mmaped_file.filepath)
-
5
File.unlink(locked_file) if File.exist?(mmaped_file.filepath)
-
end
-
-
1
it '#inspect' do
-
1
data = described_class.new(Prometheus::Client::Helper::MmapedFile.open(locked_file))
-
-
1
expect(data.inspect).to match(/#{described_class}:0x/)
-
1
expect(data.inspect).not_to match(/@position/)
-
end
-
-
1
it '#read_all_values' do
-
1
values = described_class.read_all_values(locked_file)
-
-
1
expect(values.count).to eq(2)
-
1
expect(values[0]).to eq(['foo', 100])
-
1
expect(values[1]).to eq(['bar', 500])
-
end
-
-
1
it '#read_all_positions' do
-
1
data = described_class.new(Prometheus::Client::Helper::MmapedFile.open(locked_file))
-
-
1
positions = data.positions
-
-
# Generated via https://github.com/luismartingarcia/protocol:
-
# protocol "Used:4,Pad:4,K1 Size:4,K1 Name:4,K1 Value:8,K2 Size:4,K2 Name:4,K2 Value:8"
-
#
-
# 0 1 2 3
-
# 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
# | Used | Pad |K1 Size|K1 Name| K1 Value |K2 Size|K2 Name|
-
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
-
# | K2 Value |
-
# +-+-+-+-+-+-+-+
-
1
expect(positions).to eq({ 'foo' => 16, 'bar' => 32 })
-
end
-
-
1
describe '#write_value' do
-
1
it 'writes values' do
-
# Reload dictionary
-
#
-
1
data = described_class.new(mmaped_file)
-
1
data.write_value('new value', 500)
-
# Overwrite existing values
-
1
data.write_value('foo', 200)
-
1
data.write_value('bar', 300)
-
-
1
values = described_class.read_all_values(locked_file)
-
-
1
expect(values.count).to eq(3)
-
-
1
expect(values[0]).to eq(['foo', 200])
-
1
expect(values[1]).to eq(['bar', 300])
-
1
expect(values[2]).to eq(['new value', 500])
-
end
-
-
1
context 'when mmaped_file got deleted' do
-
1
it 'is able to write to and expand metrics file' do
-
1
data = described_class.new(mmaped_file)
-
1
data.write_value('new value', 500)
-
1
FileUtils.rm(mmaped_file.filepath)
-
-
1
1000.times do |i|
-
1000
data.write_value("new new value #{i}", 567)
-
end
-
-
1
expect(File.exist?(locked_file)).not_to be_truthy
-
end
-
end
-
end
-
end
-
end
-
1
require 'prometheus/client/mmaped_dict'
-
1
require 'prometheus/client/page_size'
-
1
require 'tempfile'
-
-
1
describe Prometheus::Client::MmapedValue, :temp_metrics_dir do
-
1
before do
-
13
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(temp_metrics_dir)
-
end
-
-
1
describe '.reset_and_reinitialize' do
-
3
let(:counter) { described_class.new(:counter, :counter, 'counter', {}) }
-
-
1
before do
-
2
counter.increment(1)
-
end
-
-
1
it 'calls reinitialize on the counter' do
-
1
expect(counter).to receive(:unsafe_reinitialize_file).with(false).and_call_original
-
-
1
described_class.reset_and_reinitialize
-
end
-
-
1
context 'when metrics folder changes' do
-
1
around do |example|
-
1
Dir.mktmpdir('temp_metrics_dir') do |path|
-
1
@tmp_path = path
-
-
1
example.run
-
end
-
end
-
-
1
before do
-
1
allow(Prometheus::Client.configuration).to receive(:multiprocess_files_dir).and_return(@tmp_path)
-
end
-
-
1
it 'resets the counter to zero' do
-
1
expect(counter).to receive(:unsafe_reinitialize_file).with(false).and_call_original
-
-
4
expect { described_class.reset_and_reinitialize }.to(change { counter.get }.from(1).to(0))
-
end
-
end
-
end
-
-
1
describe '#initialize' do
-
12
let(:pid) { 1234 }
-
1
before do
-
11
described_class.class_variable_set(:@@files, {})
-
11
described_class.class_variable_set(:@@pid, pid)
-
23
allow(Prometheus::Client.configuration).to receive(:pid_provider).and_return(-> { pid })
-
11
allow(Process).to receive(:pid).and_return(pid)
-
end
-
-
1
describe 'counter type object initialized' do
-
12
let!(:counter) { described_class.new(:counter, :counter, 'counter', {}) }
-
-
1
describe 'PID unchanged' do
-
1
it 'initializing gauge MmapValue object type keeps old file data' do
-
1
described_class.new(:gauge, :gauge, 'gauge', {}, :all)
-
1
expect(described_class.class_variable_get(:@@files)).to have_key('counter')
-
1
expect(described_class.class_variable_get(:@@files)).to have_key('gauge_all')
-
end
-
end
-
-
1
describe 'PID changed' do
-
10
let(:new_pid) { pid - 1 }
-
2
let(:page_size) { Prometheus::Client::PageSize.page_size }
-
-
1
before do
-
9
counter.increment
-
9
@old_value = counter.get
-
-
20
allow(Prometheus::Client.configuration).to receive(:pid_provider).and_return(-> { new_pid })
-
9
allow(Process).to receive(:pid).and_return(new_pid)
-
end
-
-
1
it 'initializing gauge MmapValue object type keeps old file data' do
-
1
described_class.new(:gauge, :gauge, 'gauge', {}, :all)
-
-
1
expect(described_class.class_variable_get(:@@files)).not_to have_key('counter')
-
1
expect(described_class.class_variable_get(:@@files)).to have_key('gauge_all')
-
end
-
-
1
it 'updates pid' do
-
2
expect { described_class.new(:gauge, :gauge, 'gauge', {}, :all) }
-
2
.to change { described_class.class_variable_get(:@@pid) }.from(pid).to(new_pid)
-
end
-
-
1
it '#increment updates pid' do
-
2
expect { counter.increment }
-
2
.to change { described_class.class_variable_get(:@@pid) }.from(pid).to(new_pid)
-
end
-
-
1
it '#increment updates pid' do
-
2
expect { counter.increment }
-
2
.to change { described_class.class_variable_get(:@@pid) }.from(pid).to(new_pid)
-
end
-
-
1
it '#get updates pid' do
-
2
expect { counter.get }
-
2
.to change { described_class.class_variable_get(:@@pid) }.from(pid).to(new_pid)
-
end
-
-
1
it '#set updates pid' do
-
2
expect { counter.set(1) }
-
2
.to change { described_class.class_variable_get(:@@pid) }.from(pid).to(new_pid)
-
end
-
-
1
it '#set logs an error' do
-
1
counter.set(1)
-
-
1
allow(counter.instance_variable_get(:@file))
-
.to receive(:write_value)
-
.and_raise('error writing value')
-
1
expect(Prometheus::Client.logger).to receive(:warn).and_call_original
-
-
1
counter.set(1)
-
end
-
-
1
it 'reinitialize restores all used file references and resets data' do
-
1
described_class.new(:gauge, :gauge, 'gauge', {}, :all)
-
1
described_class.reinitialize_on_pid_change
-
-
1
expect(described_class.class_variable_get(:@@files)).to have_key('counter')
-
1
expect(described_class.class_variable_get(:@@files)).to have_key('gauge_all')
-
1
expect(counter.get).not_to eq(@old_value)
-
end
-
-
1
it 'updates strings properly upon memory expansion', :page_size do
-
1
described_class.new(:gauge, :gauge, 'gauge2', { label_1: 'x' * page_size * 2 }, :all)
-
-
# This previously failed on Linux but not on macOS since mmap() may re-allocate the same region.
-
1
ObjectSpace.each_object(String, &:valid_encoding?)
-
end
-
end
-
-
1
context 'different label ordering' do
-
1
it 'does not care about label ordering' do
-
1
counter1 = described_class.new(:counter, :counter, 'ordered_counter', { label_1: 'hello', label_2: 'world', label_3: 'baz' }).increment
-
1
counter2 = described_class.new(:counter, :counter, 'ordered_counter', { label_2: 'world', label_3: 'baz', label_1: 'hello' }).increment
-
-
1
reading_counter = described_class.new(:counter, :counter, 'ordered_counter', { label_3: 'baz', label_1: 'hello', label_2: 'world' })
-
-
1
expect(reading_counter.get).to eq(2)
-
end
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client/gauge'
-
1
require 'prometheus/client/push'
-
-
1
describe Prometheus::Client::Push do
-
11
let(:gateway) { 'http://localhost:9091' }
-
10
let(:registry) { Prometheus::Client::Registry.new }
-
13
let(:grouping_key) { {} }
-
14
let(:push) { Prometheus::Client::Push.new(job: 'test-job', gateway: gateway, grouping_key: grouping_key, open_timeout: 5, read_timeout: 30) }
-
-
1
describe '.new' do
-
1
it 'returns a new push instance' do
-
1
expect(push).to be_a(Prometheus::Client::Push)
-
end
-
-
1
it 'uses localhost as default Pushgateway' do
-
1
push = Prometheus::Client::Push.new(job: 'test-job')
-
-
1
expect(push.gateway).to eql('http://localhost:9091')
-
end
-
-
1
it 'allows to specify a custom Pushgateway' do
-
1
push = Prometheus::Client::Push.new(job: 'test-job', gateway: 'http://pu.sh:1234')
-
-
1
expect(push.gateway).to eql('http://pu.sh:1234')
-
end
-
-
1
it 'raises an ArgumentError if the job is nil' do
-
1
expect do
-
1
Prometheus::Client::Push.new(job: nil)
-
end.to raise_error ArgumentError
-
end
-
-
1
it 'raises an ArgumentError if the job is empty' do
-
1
expect do
-
1
Prometheus::Client::Push.new(job: "")
-
end.to raise_error ArgumentError
-
end
-
-
1
it 'raises an ArgumentError if the given gateway URL is invalid' do
-
1
['inva.lid:1233', 'http://[invalid]'].each do |url|
-
2
expect do
-
2
Prometheus::Client::Push.new(job: 'test-job', gateway: url)
-
end.to raise_error ArgumentError
-
end
-
end
-
-
1
it 'raises InvalidLabelError if a grouping key label has an invalid name' do
-
1
expect do
-
1
Prometheus::Client::Push.new(job: "test-job", grouping_key: { "not_a_symbol" => "foo" })
-
end.to raise_error Prometheus::Client::LabelSetValidator::InvalidLabelError
-
end
-
end
-
-
1
describe '#add' do
-
1
it 'sends a given registry to via HTTP POST' do
-
1
expect(push).to receive(:request).with(Net::HTTP::Post, registry)
-
-
1
push.add(registry)
-
end
-
end
-
-
1
describe '#replace' do
-
1
it 'sends a given registry to via HTTP PUT' do
-
1
expect(push).to receive(:request).with(Net::HTTP::Put, registry)
-
-
1
push.replace(registry)
-
end
-
end
-
-
1
describe '#delete' do
-
1
it 'deletes existing metrics with HTTP DELETE' do
-
1
expect(push).to receive(:request).with(Net::HTTP::Delete)
-
-
1
push.delete
-
end
-
end
-
-
1
describe '#path' do
-
1
it 'uses the default metrics path if no grouping key given' do
-
1
push = Prometheus::Client::Push.new(job: 'test-job')
-
-
1
expect(push.path).to eql('/metrics/job/test-job')
-
end
-
-
1
it 'appends additional grouping labels to the path if specified' do
-
1
push = Prometheus::Client::Push.new(
-
job: 'test-job',
-
grouping_key: { foo: "bar", baz: "qux"},
-
)
-
-
1
expect(push.path).to eql('/metrics/job/test-job/foo/bar/baz/qux')
-
end
-
-
1
it 'encodes grouping key label values containing `/` in url-safe base64' do
-
1
push = Prometheus::Client::Push.new(
-
job: 'test-job',
-
grouping_key: { foo: "bar/baz"},
-
)
-
-
1
expect(push.path).to eql('/metrics/job/test-job/foo@base64/YmFyL2Jheg==')
-
end
-
-
1
it 'encodes empty grouping key label values as a single base64 padding character' do
-
1
push = Prometheus::Client::Push.new(
-
job: 'test-job',
-
grouping_key: { foo: ""},
-
)
-
-
1
expect(push.path).to eql('/metrics/job/test-job/foo@base64/=')
-
end
-
-
1
it 'URL-encodes all other non-URL-safe characters' do
-
1
push = Prometheus::Client::Push.new(job: '<bar job>', grouping_key: { foo_label: '<bar value>' })
-
-
1
expected = '/metrics/job/%3Cbar%20job%3E/foo_label/%3Cbar%20value%3E'
-
1
expect(push.path).to eql(expected)
-
end
-
end
-
-
1
describe '#request' do
-
5
let(:content_type) { Prometheus::Client::Formats::Text::CONTENT_TYPE }
-
4
let(:data) { Prometheus::Client::Formats::Text.marshal(registry) }
-
8
let(:uri) { URI.parse("#{gateway}/metrics/job/test-job") }
-
1
let(:response) do
-
4
double(
-
:response,
-
code: '200',
-
message: 'OK',
-
body: 'Everything worked'
-
)
-
end
-
-
1
it 'sends marshalled registry to the specified gateway' do
-
1
request = double(:request)
-
1
expect(request).to receive(:content_type=).with(content_type)
-
1
expect(request).to receive(:body=).with(data)
-
1
expect(Net::HTTP::Post).to receive(:new).with(uri).and_return(request)
-
-
1
http = double(:http)
-
1
expect(http).to receive(:use_ssl=).with(false)
-
1
expect(http).to receive(:open_timeout=).with(5)
-
1
expect(http).to receive(:read_timeout=).with(30)
-
1
expect(http).to receive(:request).with(request).and_return(response)
-
1
expect(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
-
-
1
push.send(:request, Net::HTTP::Post, registry)
-
end
-
-
1
context 'for a 3xx response' do
-
1
let(:response) do
-
1
double(
-
:response,
-
code: '301',
-
message: 'Moved Permanently',
-
body: 'Probably no body, but technically you can return one'
-
)
-
end
-
-
1
it 'raises a redirect error' do
-
1
request = double(:request)
-
1
allow(request).to receive(:content_type=)
-
1
allow(request).to receive(:body=)
-
1
allow(Net::HTTP::Post).to receive(:new).with(uri).and_return(request)
-
-
1
http = double(:http)
-
1
allow(http).to receive(:use_ssl=)
-
1
allow(http).to receive(:open_timeout=)
-
1
allow(http).to receive(:read_timeout=)
-
1
allow(http).to receive(:request).with(request).and_return(response)
-
1
allow(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
-
-
2
expect { push.send(:request, Net::HTTP::Post, registry) }.to raise_error(
-
Prometheus::Client::Push::HttpRedirectError
-
)
-
end
-
end
-
-
1
context 'for a 4xx response' do
-
1
let(:response) do
-
1
double(
-
:response,
-
code: '400',
-
message: 'Bad Request',
-
body: 'Info on why the request was bad'
-
)
-
end
-
-
1
it 'raises a client error' do
-
1
request = double(:request)
-
1
allow(request).to receive(:content_type=)
-
1
allow(request).to receive(:body=)
-
1
allow(Net::HTTP::Post).to receive(:new).with(uri).and_return(request)
-
-
1
http = double(:http)
-
1
allow(http).to receive(:use_ssl=)
-
1
allow(http).to receive(:open_timeout=)
-
1
allow(http).to receive(:read_timeout=)
-
1
allow(http).to receive(:request).with(request).and_return(response)
-
1
allow(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
-
-
2
expect { push.send(:request, Net::HTTP::Post, registry) }.to raise_error(
-
Prometheus::Client::Push::HttpClientError
-
)
-
end
-
end
-
-
1
context 'for a 5xx response' do
-
1
let(:response) do
-
1
double(
-
:response,
-
code: '500',
-
message: 'Internal Server Error',
-
body: 'Apology for the server code being broken'
-
)
-
end
-
-
1
it 'raises a server error' do
-
1
request = double(:request)
-
1
allow(request).to receive(:content_type=)
-
1
allow(request).to receive(:body=)
-
1
allow(Net::HTTP::Post).to receive(:new).with(uri).and_return(request)
-
-
1
http = double(:http)
-
1
allow(http).to receive(:use_ssl=)
-
1
allow(http).to receive(:open_timeout=)
-
1
allow(http).to receive(:read_timeout=)
-
1
allow(http).to receive(:request).with(request).and_return(response)
-
1
allow(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
-
-
2
expect { push.send(:request, Net::HTTP::Post, registry) }.to raise_error(
-
Prometheus::Client::Push::HttpServerError
-
)
-
end
-
end
-
-
1
it 'deletes data from the registry' do
-
1
request = double(:request)
-
1
expect(request).to receive(:content_type=).with(content_type)
-
1
expect(Net::HTTP::Delete).to receive(:new).with(uri).and_return(request)
-
-
1
http = double(:http)
-
1
expect(http).to receive(:use_ssl=).with(false)
-
1
expect(http).to receive(:open_timeout=).with(5)
-
1
expect(http).to receive(:read_timeout=).with(30)
-
1
expect(http).to receive(:request).with(request).and_return(response)
-
1
expect(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
-
-
1
push.send(:request, Net::HTTP::Delete)
-
end
-
-
1
context 'HTTPS support' do
-
2
let(:gateway) { 'https://localhost:9091' }
-
-
1
it 'uses HTTPS when requested' do
-
1
request = double(:request)
-
1
expect(request).to receive(:content_type=).with(content_type)
-
1
expect(request).to receive(:body=).with(data)
-
1
expect(Net::HTTP::Post).to receive(:new).with(uri).and_return(request)
-
-
1
http = double(:http)
-
1
expect(http).to receive(:use_ssl=).with(true)
-
1
expect(http).to receive(:open_timeout=).with(5)
-
1
expect(http).to receive(:read_timeout=).with(30)
-
1
expect(http).to receive(:request).with(request).and_return(response)
-
1
expect(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
-
-
1
push.send(:request, Net::HTTP::Post, registry)
-
end
-
end
-
-
1
context 'Basic Auth support' do
-
1
context 'when credentials are passed in the gateway URL' do
-
2
let(:gateway) { 'https://super:secret@localhost:9091' }
-
-
1
it "raises an ArgumentError explaining why we don't support that mechanism" do
-
2
expect { push }.to raise_error ArgumentError, /in the gateway URL.*username `super`/m
-
end
-
end
-
-
1
context 'when credentials are passed to the separate `basic_auth` method' do
-
2
let(:gateway) { 'https://localhost:9091' }
-
-
1
it 'passes the credentials on to the HTTP client' do
-
1
request = double(:request)
-
1
expect(request).to receive(:content_type=).with(content_type)
-
1
expect(request).to receive(:basic_auth).with('super', 'secret')
-
1
expect(request).to receive(:body=).with(data)
-
1
expect(Net::HTTP::Put).to receive(:new).with(uri).and_return(request)
-
-
1
http = double(:http)
-
1
expect(http).to receive(:use_ssl=).with(true)
-
1
expect(http).to receive(:open_timeout=).with(5)
-
1
expect(http).to receive(:read_timeout=).with(30)
-
1
expect(http).to receive(:request).with(request).and_return(response)
-
1
expect(Net::HTTP).to receive(:new).with('localhost', 9091).and_return(http)
-
-
1
push.basic_auth("super", "secret")
-
-
1
push.send(:request, Net::HTTP::Put, registry)
-
end
-
end
-
end
-
-
1
context 'with a grouping key that clashes with a metric label' do
-
2
let(:grouping_key) { { foo: "bar"} }
-
-
1
before do
-
1
gauge = Prometheus::Client::Gauge.new(
-
:test_gauge,
-
'test docstring',
-
foo: nil
-
)
-
1
registry.register(gauge)
-
1
gauge.set({ foo: "bar"}, 42)
-
end
-
-
1
it 'raises an error when grouping key labels conflict with metric labels' do
-
2
expect { push.send(:request, Net::HTTP::Post, registry) }.to raise_error(
-
Prometheus::Client::LabelSetValidator::InvalidLabelSetError
-
)
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'rack/test'
-
1
require 'prometheus/client/rack/collector'
-
-
1
describe Prometheus::Client::Rack::Collector do
-
1
include Rack::Test::Methods
-
-
1
before do
-
5
allow(Prometheus::Client.configuration).to receive(:value_class).and_return(Prometheus::Client::SimpleValue)
-
end
-
-
1
let(:registry) do
-
5
Prometheus::Client::Registry.new
-
end
-
-
1
let(:original_app) do
-
8
->(_) { [200, { 'Content-Type' => 'text/html' }, ['OK']] }
-
end
-
-
1
let!(:app) do
-
4
described_class.new(original_app, registry: registry)
-
end
-
-
1
it 'returns the app response' do
-
1
get '/foo'
-
-
1
expect(last_response).to be_ok
-
1
expect(last_response.body).to eql('OK')
-
end
-
-
1
it 'propagates errors in the registry' do
-
1
counter = registry.get(:http_requests_total)
-
1
expect(counter).to receive(:increment).and_raise(NoMethodError)
-
-
2
expect { get '/foo' }.to raise_error(NoMethodError)
-
end
-
-
1
it 'traces request information' do
-
# expect(Time).to receive(:now).and_return(Time.at(0.0), Time.at(0.2))
-
1
labels = { method: 'get', host: 'example.org', path: '/foo', code: '200' }
-
-
1
get '/foo'
-
-
1
{
-
http_requests_total: 1.0,
-
# http_request_duration_seconds: { 0.5 => 0.2, 0.9 => 0.2, 0.99 => 0.2 }, # TODO: Fix summaries
-
}.each do |metric, result|
-
1
expect(registry.get(metric).get(labels)).to eql(result)
-
end
-
end
-
-
1
context 'when the app raises an exception' do
-
1
let(:original_app) do
-
1
lambda do |env|
-
2
raise NoMethodError if env['PATH_INFO'] == '/broken'
-
-
1
[200, { 'Content-Type' => 'text/html' }, ['OK']]
-
end
-
end
-
-
1
before do
-
1
get '/foo'
-
end
-
-
1
it 'traces exceptions' do
-
1
labels = { exception: 'NoMethodError' }
-
-
2
expect { get '/broken' }.to raise_error NoMethodError
-
-
1
expect(registry.get(:http_exceptions_total).get(labels)).to eql(1.0)
-
end
-
end
-
-
1
context 'setting up with a block' do
-
1
let(:app) do
-
1
described_class.new(original_app, registry: registry) do |env|
-
1
{ method: env['REQUEST_METHOD'].downcase } # and ignore the path
-
end
-
end
-
-
1
it 'allows labels configuration' do
-
1
get '/foo/bar'
-
-
1
labels = { method: 'get', code: '200' }
-
-
1
expect(registry.get(:http_requests_total).get(labels)).to eql(1.0)
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'rack/test'
-
1
require 'prometheus/client/rack/exporter'
-
-
1
xdescribe Prometheus::Client::Rack::Exporter do
-
1
include Rack::Test::Methods
-
-
1
let(:registry) do
-
Prometheus::Client::Registry.new
-
end
-
-
1
let(:app) do
-
app = ->(_) { [200, { 'Content-Type' => 'text/html' }, ['OK']] }
-
Prometheus::Client::Rack::Exporter.new(app, registry: registry)
-
end
-
-
1
context 'when requesting app endpoints' do
-
1
it 'returns the app response' do
-
get '/foo'
-
-
expect(last_response).to be_ok
-
expect(last_response.body).to eql('OK')
-
end
-
end
-
-
1
context 'when requesting /metrics' do
-
1
text = Prometheus::Client::Formats::Text
-
-
1
shared_examples 'ok' do |headers, fmt|
-
7
it "responds with 200 OK and Content-Type #{fmt::CONTENT_TYPE}" do
-
registry.counter(:foo, 'foo counter').increment({}, 9)
-
-
get '/metrics', nil, headers
-
-
expect(last_response.status).to eql(200)
-
expect(last_response.header['Content-Type']).to eql(fmt::CONTENT_TYPE)
-
expect(last_response.body).to eql(fmt.marshal(registry))
-
end
-
end
-
-
1
shared_examples 'not acceptable' do |headers|
-
2
it 'responds with 406 Not Acceptable' do
-
message = 'Supported media types: text/plain'
-
-
get '/metrics', nil, headers
-
-
expect(last_response.status).to eql(406)
-
expect(last_response.header['Content-Type']).to eql('text/plain')
-
expect(last_response.body).to eql(message)
-
end
-
end
-
-
1
context 'when client does not send a Accept header' do
-
1
include_examples 'ok', {}, text
-
end
-
-
1
context 'when client accpets any media type' do
-
1
include_examples 'ok', { 'HTTP_ACCEPT' => '*/*' }, text
-
end
-
-
1
context 'when client requests application/json' do
-
1
include_examples 'not acceptable', 'HTTP_ACCEPT' => 'application/json'
-
end
-
-
1
context 'when client requests text/plain' do
-
1
include_examples 'ok', { 'HTTP_ACCEPT' => 'text/plain' }, text
-
end
-
-
1
context 'when client uses different white spaces in Accept header' do
-
1
accept = 'text/plain;q=1.0 ; version=0.0.4'
-
-
1
include_examples 'ok', { 'HTTP_ACCEPT' => accept }, text
-
end
-
-
1
context 'when client does not include quality attribute' do
-
1
accept = 'application/json;q=0.5, text/plain'
-
-
1
include_examples 'ok', { 'HTTP_ACCEPT' => accept }, text
-
end
-
-
1
context 'when client accepts some unknown formats' do
-
1
accept = 'text/plain;q=0.3, proto/buf;q=0.7'
-
-
1
include_examples 'ok', { 'HTTP_ACCEPT' => accept }, text
-
end
-
-
1
context 'when client accepts only unknown formats' do
-
1
accept = 'fancy/woo;q=0.3, proto/buf;q=0.7'
-
-
1
include_examples 'not acceptable', 'HTTP_ACCEPT' => accept
-
end
-
-
1
context 'when client accepts unknown formats and wildcard' do
-
1
accept = 'fancy/woo;q=0.3, proto/buf;q=0.7, */*;q=0.1'
-
-
1
include_examples 'ok', { 'HTTP_ACCEPT' => accept }, text
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'thread'
-
1
require 'prometheus/client/registry'
-
-
1
describe Prometheus::Client::Registry do
-
13
let(:registry) { Prometheus::Client::Registry.new }
-
-
1
describe '.new' do
-
1
it 'returns a new registry instance' do
-
1
expect(registry).to be_a(Prometheus::Client::Registry)
-
end
-
end
-
-
1
describe '#register' do
-
1
it 'registers a new metric container and returns it' do
-
1
metric = double(name: :test)
-
-
1
expect(registry.register(metric)).to eql(metric)
-
end
-
-
1
it 'raises an exception if a metric name gets registered twice' do
-
1
metric = double(name: :test)
-
-
1
registry.register(metric)
-
-
1
expect do
-
1
registry.register(metric)
-
end.to raise_exception described_class::AlreadyRegisteredError
-
end
-
-
1
it 'is thread safe' do
-
1
mutex = Mutex.new
-
1
containers = []
-
-
1
def registry.exist?(*args)
-
10
super.tap { sleep(0.01) }
-
end
-
-
1
Array.new(5) do
-
5
Thread.new do
-
result = begin
-
5
registry.register(double(name: :test))
-
rescue Prometheus::Client::Registry::AlreadyRegisteredError
-
4
nil
-
end
-
10
mutex.synchronize { containers << result }
-
end
-
end.each(&:join)
-
-
1
expect(containers.compact.size).to eql(1)
-
end
-
end
-
-
1
describe '#counter' do
-
1
it 'registers a new counter metric container and returns the counter' do
-
1
metric = registry.counter(:test, 'test docstring')
-
-
1
expect(metric).to be_a(Prometheus::Client::Counter)
-
end
-
end
-
-
1
describe '#gauge' do
-
1
it 'registers a new gauge metric container and returns the gauge' do
-
1
metric = registry.gauge(:test, 'test docstring')
-
-
1
expect(metric).to be_a(Prometheus::Client::Gauge)
-
end
-
end
-
-
1
describe '#summary' do
-
1
it 'registers a new summary metric container and returns the summary' do
-
1
metric = registry.summary(:test, 'test docstring')
-
-
1
expect(metric).to be_a(Prometheus::Client::Summary)
-
end
-
end
-
-
1
describe '#histogram' do
-
1
it 'registers a new histogram metric container and returns the histogram' do
-
1
metric = registry.histogram(:test, 'test docstring')
-
-
1
expect(metric).to be_a(Prometheus::Client::Histogram)
-
end
-
end
-
-
1
describe '#exist?' do
-
1
it 'returns true if a metric name has been registered' do
-
1
registry.register(double(name: :test))
-
-
1
expect(registry.exist?(:test)).to eql(true)
-
end
-
-
1
it 'returns false if a metric name has not been registered yet' do
-
1
expect(registry.exist?(:test)).to eql(false)
-
end
-
end
-
-
1
describe '#get' do
-
1
it 'returns a previously registered metric container' do
-
1
registry.register(double(name: :test))
-
-
1
expect(registry.get(:test)).to be
-
end
-
-
1
it 'returns nil if the metric has not been registered yet' do
-
1
expect(registry.get(:test)).to eql(nil)
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client/summary'
-
1
require 'examples/metric_example'
-
-
1
describe Prometheus::Client::Summary do
-
2
let(:summary) { Prometheus::Client::Summary.new(:bar, 'bar description') }
-
-
1
it_behaves_like Prometheus::Client::Metric do
-
3
let(:type) { Float }
-
end
-
-
1
describe '#observe' do
-
1
it 'records the given value' do
-
1
expect do
-
1
summary.observe({}, 5)
-
2
end.to change { summary.get }
-
end
-
end
-
-
1
xdescribe '#get' do
-
1
before do
-
summary.observe({ foo: 'bar' }, 3)
-
summary.observe({ foo: 'bar' }, 5.2)
-
summary.observe({ foo: 'bar' }, 13)
-
summary.observe({ foo: 'bar' }, 4)
-
end
-
-
1
it 'returns a set of quantile values' do
-
expect(summary.get(foo: 'bar')).to eql(0.5 => 4, 0.9 => 5.2, 0.99 => 5.2)
-
end
-
-
1
it 'returns a value which responds to #sum and #total' do
-
value = summary.get(foo: 'bar')
-
-
expect(value.sum).to eql(25.2)
-
expect(value.total).to eql(4)
-
end
-
-
1
it 'uses nil as default value' do
-
expect(summary.get({})).to eql(0.5 => nil, 0.9 => nil, 0.99 => nil)
-
end
-
end
-
-
1
xdescribe '#values' do
-
1
it 'returns a hash of all recorded summaries' do
-
summary.observe({ status: 'bar' }, 3)
-
summary.observe({ status: 'foo' }, 5)
-
-
expect(summary.values).to eql(
-
{ status: 'bar' } => { 0.5 => 3, 0.9 => 3, 0.99 => 3 },
-
{ status: 'foo' } => { 0.5 => 5, 0.9 => 5, 0.99 => 5 },
-
)
-
end
-
end
-
end
-
1
require 'spec_helper'
-
1
require 'prometheus/client/support/puma'
-
-
1
class FakePumaWorker
-
1
attr_reader :index
-
-
1
def initialize(index)
-
1
@index = index
-
end
-
end
-
-
1
describe Prometheus::Client::Support::Puma do
-
1
describe '.worker_pid_provider' do
-
1
let(:worker_id) { '2' }
-
3
let(:program_name) { $PROGRAM_NAME }
-
-
5
subject(:worker_pid_provider) { described_class.worker_pid_provider }
-
-
1
before do
-
4
expect(described_class).to receive(:program_name)
-
.at_least(:once)
-
.and_return(program_name)
-
end
-
-
1
context 'when the current process is a Puma cluster worker' do
-
1
context 'when the process name contains a worker id' do
-
2
let(:program_name) { 'puma: cluster worker 2: 34740 [my-app]' }
-
-
2
it { is_expected.to eq('puma_2') }
-
end
-
-
1
context 'when the process name does not include a worker id' do
-
2
let(:worker_number) { 10 }
-
-
1
before do
-
1
stub_const('Puma::Cluster::Worker', FakePumaWorker)
-
1
FakePumaWorker.new(worker_number)
-
end
-
-
2
it { is_expected.to eq("puma_#{worker_number}") }
-
end
-
end
-
-
1
context 'when the current process is the Puma master' do
-
2
let(:program_name) { 'bin/puma' }
-
-
2
it { is_expected.to eq('puma_master') }
-
end
-
-
1
context 'when it cannot be determined that Puma is running' do
-
2
let(:process_id) { 10 }
-
-
1
before do
-
1
allow(Process).to receive(:pid).and_return(process_id)
-
end
-
-
2
it { is_expected.to eq("process_id_#{process_id}") }
-
end
-
end
-
end
-
1
require 'spec_helper'
-
1
require 'prometheus/client/support/unicorn'
-
-
1
class FakeUnicornWorker
-
1
attr_reader :nr
-
-
1
def initialize(nr)
-
2
@nr = nr
-
end
-
end
-
-
1
describe Prometheus::Client::Support::Unicorn do
-
1
describe '.worker_id' do
-
3
let(:worker_id) { '09' }
-
-
1
around do |example|
-
2
old_name = $0
-
2
example.run
-
2
$0 = old_name
-
end
-
-
1
context 'process name contains worker id' do
-
1
before do
-
1
$0 = "program worker[#{worker_id}] arguments"
-
end
-
-
1
it 'returns worker_id' do
-
1
expect(subject.worker_id).to eq(worker_id)
-
end
-
end
-
-
1
context 'process name is without worker id' do
-
1
it 'calls .object_based_worker_id id provider' do
-
1
expect(subject).to receive(:object_based_worker_id).and_return(worker_id)
-
-
1
expect(subject.worker_id).to eq(worker_id)
-
end
-
end
-
end
-
-
1
describe '.object_based_worker_id' do
-
1
context 'when Unicorn is defined' do
-
1
before do
-
4
stub_const('Unicorn::Worker', FakeUnicornWorker)
-
end
-
-
1
context 'Worker instance is present in ObjectSpace' do
-
3
let(:worker_number) { 10 }
-
3
let!(:unicorn_worker) { FakeUnicornWorker.new(worker_number) }
-
-
1
it 'Unicorn::Worker to be defined' do
-
1
expect(defined?(Unicorn::Worker)).to be_truthy
-
end
-
-
1
it 'returns worker id' do
-
1
expect(described_class.object_based_worker_id).to eq(worker_number)
-
end
-
end
-
-
1
context 'Worker instance is not present in ObjectSpace' do
-
1
it 'Unicorn::Worker id defined' do
-
1
expect(defined?(Unicorn::Worker)).to be_truthy
-
end
-
-
1
it 'returns no worker id' do
-
1
expect(ObjectSpace).to receive(:each_object).with(::Unicorn::Worker).and_return(nil)
-
-
1
expect(described_class.object_based_worker_id).to eq(nil)
-
end
-
end
-
end
-
-
1
context 'Unicorn::Worker is not defined' do
-
1
it 'Unicorn::Worker not defined' do
-
1
expect(defined?(Unicorn::Worker)).to be_falsey
-
end
-
-
1
it 'returns no worker_id' do
-
1
expect(described_class.object_based_worker_id).to eq(nil)
-
end
-
end
-
end
-
-
1
describe '.worker_pid_provider' do
-
1
context 'worker_id is provided' do
-
2
let(:worker_id) { 2 }
-
1
before do
-
1
allow(described_class).to receive(:worker_id).and_return(worker_id)
-
end
-
-
1
it 'returns worker pid created from worker id' do
-
1
expect(described_class.worker_pid_provider).to eq("worker_id_#{worker_id}")
-
end
-
end
-
-
1
context 'worker_id is not provided' do
-
2
let(:process_id) { 10 }
-
1
before do
-
1
allow(described_class).to receive(:worker_id).and_return(nil)
-
1
allow(Process).to receive(:pid).and_return(process_id)
-
end
-
-
1
it 'returns worker pid created from Process ID' do
-
1
expect(described_class.worker_pid_provider).to eq("process_id_#{process_id}")
-
end
-
end
-
end
-
end
-
# encoding: UTF-8
-
-
1
require 'prometheus/client'
-
-
1
describe Prometheus::Client do
-
1
describe '.registry' do
-
1
it 'returns a registry object' do
-
1
expect(described_class.registry).to be_a(described_class::Registry)
-
end
-
-
1
it 'memorizes the returned object' do
-
1
expect(described_class.registry).to eql(described_class.registry)
-
end
-
end
-
-
1
context '.reset! and .reinitialize_on_pid_change' do
-
5
let(:metric_name) { :room_temperature_celsius }
-
5
let(:label) { { room: 'kitchen' } }
-
5
let(:value) { 21 }
-
5
let(:gauge) { Prometheus::Client::Gauge.new(metric_name, 'test') }
-
-
1
before do
-
4
described_class.cleanup!
-
4
described_class.reset! # registering metrics will leak into other specs
-
-
4
registry = described_class.registry
-
4
gauge.set(label, value)
-
4
registry.register(gauge)
-
-
4
expect(registry.metrics.count).to eq(1)
-
4
expect(registry.get(metric_name).get(label)).to eq(value)
-
end
-
-
1
describe '.reset!' do
-
1
it 'resets registry and clears existing metrics' do
-
1
described_class.cleanup!
-
1
described_class.reset!
-
-
1
registry = described_class.registry
-
1
expect(registry.metrics.count).to eq(0)
-
-
1
registry.register(gauge)
-
1
expect(registry.get(metric_name).get(label)).not_to eq(value)
-
end
-
end
-
-
1
describe '.reinitialize_on_pid_change' do
-
1
context 'with force: false' do
-
1
it 'calls `MmapedValue.reinitialize_on_pid_change`' do
-
1
expect(Prometheus::Client::MmapedValue).to receive(:reinitialize_on_pid_change).and_call_original
-
-
1
described_class.reinitialize_on_pid_change(force: false)
-
end
-
end
-
-
1
context 'without explicit :force param' do
-
1
it 'defaults to `false` and calls `MmapedValue.reinitialize_on_pid_change`' do
-
1
expect(Prometheus::Client::MmapedValue).to receive(:reinitialize_on_pid_change).and_call_original
-
-
1
described_class.reinitialize_on_pid_change
-
end
-
end
-
-
1
context 'with force: true' do
-
1
it 'calls `MmapedValue.reset_and_reinitialize`' do
-
1
expect(Prometheus::Client::MmapedValue).to receive(:reset_and_reinitialize).and_call_original
-
-
1
described_class.reinitialize_on_pid_change(force: true)
-
end
-
end
-
end
-
end
-
end