Class: Prometheus::Client::VmHistogram

Inherits:
Metric
  • Object
show all
Defined in:
lib/prometheus/client/vm_histogram.rb

Overview

A histogram samples observations (usually things like request durations or response sizes) and counts them in dynamic VictoriaMetrics buckets. It also provides a total count and sum of all observed values.

Constant Summary collapse

E10MIN =
-9
E10MAX =
18
BUCKETS_PER_DECIMAL =
18
DECIMAL_BUCKETS_COUNT =
E10MAX - E10MIN
BUCKETS_COUNT =
DECIMAL_BUCKETS_COUNT * BUCKETS_PER_DECIMAL
BUCKETS_MULTIPLIER =
10**(1.0 / BUCKETS_PER_DECIMAL)
VMRANGES =
begin
  h = {}
  value = 10**E10MIN
  range_start = format('%.3e', value)

  BUCKETS_COUNT.times do |i|
    value *= BUCKETS_MULTIPLIER
    range_end = format('%.3e', value)
    h[i] = "#{range_start}...#{range_end}"
    range_start = range_end
  end

  # edge case fo zeros
  h[-1] = '0...1.000e-09'

  h
end
MAX_VMRANGE_BUCKET =
VMRANGES.keys.max

Instance Attribute Summary collapse

Attributes inherited from Metric

#docstring, #labels, #name, #preset_labels

Instance Method Summary collapse

Constructor Details

#initialize(name, docstring:, labels: [], preset_labels: {}, buckets: [], store_settings: {}) ⇒ VmHistogram

Returns a new instance of VmHistogram.



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/prometheus/client/vm_histogram.rb', line 38

def initialize(name,
               docstring:,
               labels: [],
               preset_labels: {},
               # VM histogram ignores passed buckets, accepts only for compatibility
               buckets: [], # rubocop:disable Lint/UnusedMethodArgument
               store_settings: {})

  @buckets = %w[sum count]
  # TODO: this should take into account labels
  @non_nil_buckets = {}
  @base_label_set_cache = {}

  super(name,
        docstring: docstring,
        labels: labels,
        preset_labels: preset_labels,
        store_settings: store_settings)
end

Instance Attribute Details

#bucketsObject (readonly)

Returns the value of attribute buckets.



11
12
13
# File 'lib/prometheus/client/vm_histogram.rb', line 11

def buckets
  @buckets
end

Instance Method Details

#get(labels: {}) ⇒ Object



118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/prometheus/client/vm_histogram.rb', line 118

def get(labels: {})
  base_label_set = label_set_for(labels)

  all_buckets = @non_nil_buckets.keys + buckets

  @store.synchronize do
    all_buckets.each_with_object({}) do |bucket, acc|
      if @non_nil_buckets.key? bucket
        value = @store.get(labels: base_label_set.merge(vmrange: bucket.to_s))
        acc[bucket.to_s] = value if value.positive?
      else
        acc[bucket.to_s] = @store.get(labels: base_label_set.merge(le: bucket.to_s))
      end
    end
  end
end

#init_label_set(labels) ⇒ Object



147
148
149
150
151
152
153
154
155
# File 'lib/prometheus/client/vm_histogram.rb', line 147

def init_label_set(labels)
  base_label_set = label_set_for(labels)

  @store.synchronize do
    @buckets.each do |bucket|
      @store.set(labels: base_label_set.merge(le: bucket.to_s), val: 0)
    end
  end
end

#observe(value, labels: {}) ⇒ Object

Records a given value. The recorded value is usually positive or zero. A negative value is ignored.



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/prometheus/client/vm_histogram.rb', line 79

def observe(value, labels: {})
  return if value.to_f.nan? || value.negative?

  float_bucket_id = (Math.log10(value) - E10MIN) * BUCKETS_PER_DECIMAL

  bucket_id = if float_bucket_id.negative?
                -1
              elsif float_bucket_id > MAX_VMRANGE_BUCKET
                MAX_VMRANGE_BUCKET
              else
                float_bucket_id.to_i
              end

  # Edge case for 10^n values, which must go to the lower bucket
  # according to Prometheus logic for `le`-based histograms
  bucket_id -= 1 if (float_bucket_id - bucket_id.to_f).abs < Float::EPSILON && bucket_id.positive?

  base_label_set = label_set_for(labels)

  # OPTIMIZE: probably we also can use cache for vmranges to avoid using .dup every time
  bucket_label_set = base_label_set.dup
  bucket_label_set[:vmrange] = VMRANGES[bucket_id]

  @non_nil_buckets[bucket_label_set[:vmrange]] = nil # just to track non empty buckets

  unless @base_label_set_cache.key? base_label_set
    @base_label_set_cache[base_label_set] = {
      sum: base_label_set.merge({ le: 'sum' }),
      count: base_label_set.merge({ le: 'count' })
    }
  end

  @store.synchronize do
    @store.increment(labels: bucket_label_set, by: 1)
    @store.increment(labels: @base_label_set_cache[base_label_set][:sum], by: value)
    @store.increment(labels: @base_label_set_cache[base_label_set][:count], by: 1)
  end
end

#typeObject



73
74
75
# File 'lib/prometheus/client/vm_histogram.rb', line 73

def type
  :vm_histogram
end

#valuesObject

Returns all label sets with their values expressed as hashes with their buckets



136
137
138
139
140
141
142
143
144
145
# File 'lib/prometheus/client/vm_histogram.rb', line 136

def values
  values = @store.all_values

  values.each_with_object({}) do |(label_set, v), acc|
    actual_label_set = label_set.reject { |l| [:vmrange, :le].include? l }
    acc[actual_label_set] ||= {}
    label_name = label_set[:vmrange] || label_set[:le]
    acc[actual_label_set][label_name.to_s] = v
  end
end

#with_labels(labels) ⇒ Object



58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'lib/prometheus/client/vm_histogram.rb', line 58

def with_labels(labels)
  new_metric = self.class.new(name,
                              docstring: docstring,
                              labels: @labels,
                              preset_labels: preset_labels.merge(labels),
                              buckets: @buckets,
                              store_settings: @store_settings)

  # The new metric needs to use the same store as the "main" declared one, otherwise
  # any observations on that copy with the pre-set labels won't actually be exported.
  new_metric.replace_internal_store(@store)

  new_metric
end