# Copyright (c) 2015 Sqreen. All Rights Reserved. # Please refer to our terms for more information: https://www.sqreen.io/terms.html require 'sqreen/exception' require 'sqreen/metrics' require 'sqreen/mono_time' module Sqreen # This store and register metrics class MetricsStore # When a metric is not yet created class UnregisteredMetric < Sqreen::Exception end # When the metric is unknown class UnknownMetric < Sqreen::Exception end # When this name as already been declared with another kind class AlreadyRegisteredMetric < Sqreen::Exception end # definition keys NAME_KEY = 'name'.freeze KIND_KEY = 'kind'.freeze PERIOD_KEY = 'period'.freeze OPTIONS_KEY = 'options'.freeze # Currently ready samples attr_reader :store # All known metrics attr_reader :metrics def initialize @store = [] @metrics = {} # name => (metric, period, start) end # Definition contains a name,period and aggregate at least # @param definition [Hash] a metric definition # @param mklass [Object] Override metric object (used in testing) def create_metric(definition, mklass = nil) name = definition[NAME_KEY] kind = definition[KIND_KEY] klass = valid_metric(kind, name) opts = definition[OPTIONS_KEY] metric = mklass || klass.new(opts) @metrics[name] = [ metric, definition[PERIOD_KEY], nil # Start ] metric end def metric?(name) @metrics.key?(name) end # @params at [Time] when is the store emptied def update(name, at, key, value) metric, period, start = @metrics[name] raise UnregisteredMetric, "Unknown metric #{name}" unless metric next_sample(name, at) if start.nil? || (start + period) < at metric.update(key, value) end # Drains every metrics and returns the store content # @params at [Time] when is the store emptied def publish(flush = true, at = Sqreen.time) @metrics.each do |name, (_, period, start)| next_sample(name, at) if flush || !start.nil? && (start + period) < at end out = @store @store = [] out end protected def next_sample(name, at) metric = @metrics[name][0] r = metric.next_sample(at) @metrics[name][2] = at # new start if r r[NAME_KEY] = name obs = r[Metric::OBSERVATION_KEY] start_of_mono = Time.now.utc - Sqreen.time r[Metric::START_KEY] = start_of_mono + r[Metric::START_KEY] r[Metric::FINISH_KEY] = start_of_mono + r[Metric::FINISH_KEY] @store << r if obs && (!obs.respond_to?(:empty?) || !obs.empty?) end r end def valid_metric(kind, name) unless Sqreen::Metric.const_defined?(kind) raise UnknownMetric, "No such #{kind} metric" end klass = Sqreen::Metric.const_get(kind) metric = @metrics[name] && @metrics[name][0] if metric && metric.class != klass msg = "Was a #{metric.class.name} not a #{klass.name} " raise AlreadyRegisteredMetric, msg end klass end end end