class Ezmetrics::Storage
Constants
- AGGREGATION_FUNCTIONS
- METRICS
- PARTITION_UNITS
Attributes
flat[R]
interval_metrics[R]
interval_seconds[R]
options[R]
partitioned_metrics[R]
redis[R]
requests[R]
safe_payload[R]
schema[R]
storage_key[R]
this_second_metrics[R]
Public Class Methods
new(interval_seconds=60)
click to toggle source
# File lib/ezmetrics/storage.rb, line 6 def initialize(interval_seconds=60) @interval_seconds = interval_seconds.to_i @redis = Redis.new(driver: :hiredis) @schema = redis_schema end
Public Instance Methods
flatten()
click to toggle source
# File lib/ezmetrics/storage.rb, line 80 def flatten @flat = true self end
log(payload={duration: 0.0, views: 0.0, db: 0.0, queries: 0, status: 200, store_each_value: false})
click to toggle source
# File lib/ezmetrics/storage.rb, line 12 def log(payload={duration: 0.0, views: 0.0, db: 0.0, queries: 0, status: 200, store_each_value: false}) @safe_payload = { duration: payload[:duration].to_f, views: payload[:views].to_f, db: payload[:db].to_f, queries: payload[:queries].to_i, status: payload[:status].to_i, store_each_value: payload[:store_each_value].to_s == "true" } this_second = Time.now.to_i status_group = "#{payload[:status].to_s[0]}xx" @this_second_metrics = redis.get(this_second) if this_second_metrics @this_second_metrics = Oj.load(this_second_metrics) METRICS.each do |metrics_type| update_sum(metrics_type) update_max(metrics_type) store_value(metrics_type) if safe_payload[:store_each_value] end this_second_metrics[schema["all"]] += 1 this_second_metrics[schema[status_group]] += 1 else @this_second_metrics = { "second" => this_second, "duration_sum" => safe_payload[:duration], "duration_max" => safe_payload[:duration], "views_sum" => safe_payload[:views], "views_max" => safe_payload[:views], "db_sum" => safe_payload[:db], "db_max" => safe_payload[:db], "queries_sum" => safe_payload[:queries], "queries_max" => safe_payload[:queries], "2xx" => 0, "3xx" => 0, "4xx" => 0, "5xx" => 0, "all" => 1 } if safe_payload[:store_each_value] this_second_metrics.merge!( "duration_values" => [safe_payload[:duration]], "views_values" => [safe_payload[:views]], "db_values" => [safe_payload[:db]], "queries_values" => [safe_payload[:queries]] ) end this_second_metrics[status_group] = 1 @this_second_metrics = this_second_metrics.values end redis.setex(this_second, interval_seconds, Oj.dump(this_second_metrics)) true rescue => error formatted_error(error) end
partition_by(time_unit=:minute)
click to toggle source
# File lib/ezmetrics/storage.rb, line 85 def partition_by(time_unit=:minute) time_unit = PARTITION_UNITS.include?(time_unit) ? time_unit : :minute @partitioned_metrics = interval_metrics.group_by { |array| second_to_partition_unit(time_unit, array[schema["second"]]) } self end
show(options=nil)
click to toggle source
# File lib/ezmetrics/storage.rb, line 75 def show(options=nil) @options = options || default_options partitioned_metrics ? aggregate_partitioned_data : aggregate_data end
Private Instance Methods
aggregate(metrics, aggregation_function)
click to toggle source
# File lib/ezmetrics/storage.rb, line 169 def aggregate(metrics, aggregation_function) return avg("#{metrics}_sum") if aggregation_function == :avg return max("#{metrics}_max") if aggregation_function == :max if aggregation_function == :percentile_distribution sorted_values = send("sorted_#{metrics}_values") return (1..99).to_a.inject({}) do |result, number| result[number] = percentile(sorted_values, number) result end end percentile = aggregation_function.match(/percentile_(?<value>\d+)/) if percentile && percentile["value"].to_i.between?(1, 99) sorted_values = send("sorted_#{metrics}_values") percentile(sorted_values, percentile["value"].to_i) end end
aggregate_data()
click to toggle source
# File lib/ezmetrics/storage.rb, line 96 def aggregate_data return {} unless interval_metrics.any? @requests = interval_metrics.sum { |array| array[schema["all"]] } build_result rescue {} end
aggregate_partitioned_data()
click to toggle source
# File lib/ezmetrics/storage.rb, line 104 def aggregate_partitioned_data partitioned_metrics.map do |partition, metrics| @interval_metrics = metrics @requests = interval_metrics.sum { |array| array[schema["all"]] } METRICS.each { |metrics_type| instance_variable_set("@sorted_#{metrics_type}_values", nil) } flat ? { timestamp: partition, **build_result } : { timestamp: partition, data: build_result } end rescue self end
append_metrics_to_result(result, metrics, aggregation_function, aggregated_metrics)
click to toggle source
# File lib/ezmetrics/storage.rb, line 146 def append_metrics_to_result(result, metrics, aggregation_function, aggregated_metrics) return result[:"#{metrics}_#{aggregation_function}"] = aggregated_metrics if flat result[metrics] ||= {} result[metrics][aggregation_function] = aggregated_metrics end
append_requests_to_result(result, aggregated_requests)
click to toggle source
# File lib/ezmetrics/storage.rb, line 137 def append_requests_to_result(result, aggregated_requests) return result[:requests] = aggregated_requests unless flat result[:requests_all] = aggregated_requests[:all] aggregated_requests[:grouped].each do |group, counter| result[:"requests_#{group}"] = counter end end
avg(metrics)
click to toggle source
# File lib/ezmetrics/storage.rb, line 233 def avg(metrics) (interval_metrics.sum { |array| array[schema[metrics]] }.to_f / requests).round end
build_result()
click to toggle source
# File lib/ezmetrics/storage.rb, line 115 def build_result result = {} if options[:requests] append_requests_to_result(result, { all: requests, grouped: count_all_status_groups }) end options.each do |metrics, aggregation_functions| next unless METRICS.include?(metrics) aggregation_functions = [aggregation_functions] unless aggregation_functions.is_a?(Array) next unless aggregation_functions.any? aggregation_functions.each do |aggregation_function| aggregated_metrics = aggregate(metrics, aggregation_function) append_metrics_to_result(result, metrics, aggregation_function, aggregated_metrics) end end result ensure result end
count_all_status_groups()
click to toggle source
# File lib/ezmetrics/storage.rb, line 262 def count_all_status_groups interval_metrics.inject({ "2xx" => 0, "3xx" => 0, "4xx" => 0, "5xx" => 0 }) do |result, array| result["2xx"] += array[schema["2xx"]] result["3xx"] += array[schema["3xx"]] result["4xx"] += array[schema["4xx"]] result["5xx"] += array[schema["5xx"]] result end end
default_options()
click to toggle source
# File lib/ezmetrics/storage.rb, line 272 def default_options { duration: AGGREGATION_FUNCTIONS, views: AGGREGATION_FUNCTIONS, db: AGGREGATION_FUNCTIONS, queries: AGGREGATION_FUNCTIONS, requests: true } end
formatted_error(error)
click to toggle source
# File lib/ezmetrics/storage.rb, line 282 def formatted_error(error) { error: error.class.name, message: error.message, backtrace: error.backtrace.reject { |line| line.match(/ruby|gems/) } } end
max(metrics)
click to toggle source
# File lib/ezmetrics/storage.rb, line 237 def max(metrics) interval_metrics.max { |array| array[schema[metrics]] }[schema[metrics]].round end
percentile(sorted_array, pcnt)
click to toggle source
# File lib/ezmetrics/storage.rb, line 241 def percentile(sorted_array, pcnt) array_length = sorted_array.length return "not enough data (requests: #{array_length}, required: #{pcnt})" if array_length < pcnt rank = (pcnt.to_f / 100) * (array_length + 1) whole = rank.truncate # if has fractional part if whole != rank s0 = sorted_array[whole - 1] s1 = sorted_array[whole] f = (rank - rank.truncate).abs return ((f * (s1 - s0)) + s0)&.round else return (sorted_array[whole - 1])&.round end end
redis_schema()
click to toggle source
# File lib/ezmetrics/storage.rb, line 197 def redis_schema [ "second", "duration_sum", "duration_max", "views_sum", "views_max", "db_sum", "db_max", "queries_sum", "queries_max", "2xx", "3xx", "4xx", "5xx", "all", "duration_values", "views_values", "db_values", "queries_values" ].each_with_index.inject({}){ |result, pair| result[pair[0]] = pair[1] ; result } end
second_to_partition_unit(time_unit, second)
click to toggle source
# File lib/ezmetrics/storage.rb, line 153 def second_to_partition_unit(time_unit, second) return second if time_unit == :second time = Time.at(second) return (time - time.sec - time.min * 60 - time.hour * 3600).to_i if time_unit == :day return (time - time.sec - time.min * 60).to_i if time_unit == :hour (time - time.sec).to_i end
store_value(metrics)
click to toggle source
# File lib/ezmetrics/storage.rb, line 224 def store_value(metrics) this_second_metrics[schema["#{metrics}_values"]] << safe_payload[metrics] end
update_max(metrics)
click to toggle source
# File lib/ezmetrics/storage.rb, line 228 def update_max(metrics) max_value = [safe_payload[metrics], this_second_metrics[schema["#{metrics}_max"]]].max this_second_metrics[schema["#{metrics}_max"]] = max_value end
update_sum(metrics)
click to toggle source
# File lib/ezmetrics/storage.rb, line 220 def update_sum(metrics) this_second_metrics[schema["#{metrics}_sum"]] += safe_payload[metrics] end