Class: Cosmos::Metric

Inherits:
Object show all
Defined in:
lib/cosmos/utilities/metric.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(microservice:, scope:) ⇒ Metric

Returns a new instance of Metric.



42
43
44
45
46
47
48
49
50
51
# File 'lib/cosmos/utilities/metric.rb', line 42

def initialize(microservice:, scope:)
  if microservice.include? '|' or scope.include? '|'
    raise ArgumentError.new('invalid input must not contain: |')
  end

  @items = {}
  @scope = scope
  @microservice = microservice
  @size = 5000
end

Instance Attribute Details

#itemsObject (readonly)

This class is designed to output metrics to the cosmos-cmd-tlm-api InternalMetricsController. Output format can be read about here prometheus.io/docs/concepts/data_model/

Warning contains some sorcery.

examples:

TYPE foobar histogram
HELP foobar internal metric generated from cosmos/utilities/metric.rb
foobar{code="200",method="get",path="/metrics"} 5.0

items = => [value_array], …



37
38
39
# File 'lib/cosmos/utilities/metric.rb', line 37

def items
  @items
end

#microserviceObject (readonly)

Returns the value of attribute microservice.



40
41
42
# File 'lib/cosmos/utilities/metric.rb', line 40

def microservice
  @microservice
end

#scopeObject (readonly)

Returns the value of attribute scope.



39
40
41
# File 'lib/cosmos/utilities/metric.rb', line 39

def scope
  @scope
end

#sizeObject

Returns the value of attribute size.



38
39
40
# File 'lib/cosmos/utilities/metric.rb', line 38

def size
  @size
end

Instance Method Details

#add_sample(name:, value:, labels:) ⇒ Object



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/cosmos/utilities/metric.rb', line 53

def add_sample(name:, value:, labels:)
  # add a value to the metric to report out later or a seperate thread
  # name is a string often function_name_duration_seconds
  #    name: debug_duration_seconds
  # value is a numerical value to add to a round robin array.
  #    value: 0.1211
  # labels is a hash of values that could have an effect on the value.
  #    labels: {"code"=>200,"method"=>"get","path"=>"/metrics"}
  # internal:
  # the internal items hash is used as a lookup table to store unique
  # varients of a similar metric. these varients are values that could
  # cause a difference in run time to add context to the metric. the
  # microservice and scope are added to the labels the labels are
  # converted to a string and joined with the name to create a unique
  # metric item. this is looked up in the items hash and if not found
  # the key is created and an array of size @size is allocated. then
  # the value is added to @items and the count of the value is increased
  # if the count of the values exceed the size of the array it sets the
  # count back to zero and the array will over write older data.
  key = "#{name}|" + labels.map { |k, v| "#{k}=#{v}" }.join(',')
  if not @items.has_key?(key)
    Logger.debug("new data for #{@scope}, #{key}")
    @items[key] = { 'values' => Array.new(@size), 'count' => 0 }
  end
  count = @items[key]['count']
  # Logger.info("adding data for #{@scope}, #{count} #{key}, #{value}")
  @items[key]['values'][count] = value
  @items[key]['count'] = count + 1 >= @size ? 0 : count + 1
end

#destroyObject



131
132
133
# File 'lib/cosmos/utilities/metric.rb', line 131

def destroy
  MetricModel.destroy(scope: @scope, name: @microservice)
end

#outputObject



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/cosmos/utilities/metric.rb', line 93

def output
  # Output percentile based metrics to Redis under the key of the
  # #{@scope}__cosmos__metric we will use hset with a subkey.
  # internal:
  # loop over the key value pairs within the @items hash, remove nil
  # and sort the values added via the add_sample method. calculate the
  # different percentiles. the labels are still only contained in the
  # key of the @items hash to extract these you split the key on | the
  # name|labels then to make the labels back into a hash we split the ,
  # into an array ["foo=bar", ...] and split again this time on the =
  # into [["foo","bar"], ...] to map the internal array into a hash
  # [{"foo"=>"bar"}, ...] finally reducing the array into a single hash
  # to add the percentile and percentile value. this hash is added to an
  # array. to store the array as the value with the metric name again joined
  # with the @microservice and @scope.
  Logger.debug("#{@microservice} #{@scope} sending metrics to redis, #{@items.length}") if @items.length > 0
  @items.each do |key, values|
    label_list = []
    name, labels = key.split('|')
    metric_labels = labels.nil? ? {} : labels.split(',').map { |x| x.split('=') }.map { |k, v| { k => v } }.reduce({}, :merge)
    sorted_values = values['values'].compact.sort
    for percentile_value in [10, 50, 90, 95, 99]
      percentile_result = percentile(sorted_values, percentile_value)
      labels = metric_labels.clone.merge({ 'scope' => @scope, 'microservice' => @microservice })
      labels['percentile'] = percentile_value
      labels['metric__value'] = percentile_result
      label_list.append(labels)
    end
    begin
      Logger.debug("sending metrics summary to redis key: #{@microservice}")
      metric = MetricModel.new(name: @microservice, scope: @scope, metric_name: name, label_list: label_list)
      metric.create(force: true)
    rescue RuntimeError
      Logger.error("failed attempt to update metric, #{key}, #{name} #{@scope}")
    end
  end
end

#percentile(sorted_values, percentile) ⇒ Object



83
84
85
86
87
88
89
90
91
# File 'lib/cosmos/utilities/metric.rb', line 83

def percentile(sorted_values, percentile)
  # get the percentile out of an ordered array
  len = sorted_values.length
  return sorted_values.first if len == 1

  k = ((percentile / 100.0) * (len - 1) + 1).floor - 1
  f = ((percentile / 100.0) * (len - 1) + 1).modulo(1)
  return sorted_values[k] + (f * (sorted_values[k + 1] - sorted_values[k]))
end