Class: OpenC3::Metric
Instance Attribute Summary collapse
-
#items ⇒ Object
readonly
This class is designed to output metrics to the openc3-cosmos-cmd-tlm-api InternalMetricsController.
-
#microservice ⇒ Object
readonly
Returns the value of attribute microservice.
-
#scope ⇒ Object
readonly
Returns the value of attribute scope.
-
#size ⇒ Object
Returns the value of attribute size.
Instance Method Summary collapse
- #add_sample(name:, value:, labels:) ⇒ Object
- #destroy ⇒ Object
-
#initialize(microservice:, scope:) ⇒ Metric
constructor
A new instance of Metric.
- #output ⇒ Object
- #percentile(sorted_values, percentile) ⇒ Object
Constructor Details
#initialize(microservice:, scope:) ⇒ Metric
Returns a new instance of Metric.
46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/openc3/utilities/metric.rb', line 46 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 @mutex = Mutex.new end |
Instance Attribute Details
#items ⇒ Object (readonly)
This class is designed to output metrics to the openc3-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 openc3/utilities/metric.rb
foobar{code="200",method="get",path="/metrics"} 5.0
items = => [value_array], …
41 42 43 |
# File 'lib/openc3/utilities/metric.rb', line 41 def items @items end |
#microservice ⇒ Object (readonly)
Returns the value of attribute microservice.
44 45 46 |
# File 'lib/openc3/utilities/metric.rb', line 44 def microservice @microservice end |
#scope ⇒ Object (readonly)
Returns the value of attribute scope.
43 44 45 |
# File 'lib/openc3/utilities/metric.rb', line 43 def scope @scope end |
#size ⇒ Object
Returns the value of attribute size.
42 43 44 |
# File 'lib/openc3/utilities/metric.rb', line 42 def size @size end |
Instance Method Details
#add_sample(name:, value:, labels:) ⇒ Object
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/openc3/utilities/metric.rb', line 58 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. @mutex.synchronize do 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 end |
#destroy ⇒ Object
140 141 142 |
# File 'lib/openc3/utilities/metric.rb', line 140 def destroy MetricModel.destroy(scope: @scope, name: @microservice) end |
#output ⇒ Object
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 130 131 132 133 134 135 136 137 138 |
# File 'lib/openc3/utilities/metric.rb', line 100 def output # Output percentile based metrics to Redis under the key of the # #{@scope}__openc3__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 @mutex.synchronize do @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 end |
#percentile(sorted_values, percentile) ⇒ Object
90 91 92 93 94 95 96 97 98 |
# File 'lib/openc3/utilities/metric.rb', line 90 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 |