# Copyright (c) 2018 Sqreen. All Rights Reserved. # Please refer to our terms for more information: https://www.sqreen.io/terms.html require 'sqreen/performance_notifications' module Sqreen # module PerformanceNotifications # Logs callback performance class BinnedMetrics DEFAULT_PERF_BASE = 2.0 DEFAULT_PERF_UNIT = 0.1 # ms DEFAULT_PERF_PCT_BASE = 1.3 DEFAULT_PERF_PCT_UNIT = 1.0 # % EVENT_REQ = 'req'.freeze # request total time EVENT_TOTAL_TIME = 'sq'.freeze # sqreen total overhead callback time EVENT_PERCENT = 'pct'.freeze # sqreen total overhead percent of time EVT_NAME_REGEXP = %r{\ACallbacks/([^/]+)/([^/]+)\z} # @param metrics_store [Sqreen::MetricsStore] def initialize(metrics_store, period, perf_metric_opts, perf_metric_percent_opts) @metrics_store = metrics_store @period = period @subid = nil @perf_metric_opts = perf_metric_opts @perf_metric_percent_opts = perf_metric_percent_opts end def enable return unless @subid.nil? metrics_store.create_metric( 'name' => EVENT_REQ, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_opts ) metrics_store.create_metric( 'name' => EVENT_TOTAL_TIME, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_opts ) metrics_store.create_metric( 'name' => EVENT_PERCENT, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_percent_opts ) @subid = Sqreen::PerformanceNotifications.subscribe(&method(:log)) end def disable return if @subid.nil? Sqreen::PerformanceNotifications.unsubscribe(@subid) @subid = nil end def log(event, start, finish, _meta) return unless event =~ EVT_NAME_REGEXP rule, cb = Regexp.last_match.captures metric_name = "sq.#{rule}.#{cb}" ensure_metric(metric_name) finish_time = SQREEN_MONO_TIME ? Time.now.utc : finish time_millis = (finish - start) * 1000 SharedStorage[:sqreen_request_time] += time_millis metrics_store.update(metric_name, finish_time, nil, time_millis) end def start_request SharedStorage[:request_start_time] = PerformanceNotifications.time SharedStorage[:sqreen_request_time] = 0.0 end def finish_request start_time = SharedStorage[:request_start_time] finish_time = PerformanceNotifications.time duration_millis = (finish_time - start_time) * 1000 finish_time_obj = SQREEN_MONO_TIME ? Time.now.utc : finish_time # format of evt is [cat, key, value, timestamp] Sqreen.observations_queue.push( [EVENT_REQ, nil, duration_millis, finish_time_obj] ) total_t = SharedStorage[:sqreen_request_time] Sqreen.observations_queue.push( [EVENT_TOTAL_TIME, nil, total_t, finish_time_obj] ) return if !duration_millis or total_t >= duration_millis Sqreen.observations_queue.push( [EVENT_PERCENT, nil, (total_t*100.0)/(duration_millis-total_t), finish_time_obj] ) end private # @return [Sqreen::MetricsStore] attr_reader :metrics_store attr_reader :period def ensure_metric(metric_name) return if metrics_store.metric?(metric_name) metrics_store.create_metric( 'name' => metric_name, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_opts ) end @instance = nil class << self # @return [Sqreen::PerformanceNotifications::BinnedMetrics] attr_reader :instance def enable(metrics_store, period = 60, base = DEFAULT_PERF_BASE, factor = DEFAULT_PERF_UNIT, base_pct = DEFAULT_PERF_PCT_BASE, factor_pct = DEFAULT_PERF_PCT_UNIT) disable @instance = new(metrics_store, period, {'base'=> base, 'factor' => factor}, {'base' => base_pct, 'factor' => factor_pct} ).tap(&:enable) end def disable return unless instance instance.disable @instance = nil end def start_request return unless instance instance.start_request end def finish_request return unless instance instance.finish_request end end end end end