# typed: true # Copyright (c) 2015 Sqreen. All Rights Reserved. # Please refer to our terms for more information: https://www.sqreen.com/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 EVENT_SQ_THREAD_CPU_PCT = 'sq_thread_cpu_pct'.freeze # sqreen thread cpu time # @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 @clock_time = Sqreen.time @watcher_cpu_time = 0 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 ) if Sqreen.thread_cpu_time? metrics_store.create_metric('name' => EVENT_SQ_THREAD_CPU_PCT, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_percent_opts) end @subid = Sqreen::PerformanceNotifications.subscribe(&method(:log)) end def disable return if @subid.nil? Sqreen::PerformanceNotifications.unsubscribe(@subid) @subid = nil end def log(rule, cb, start, finish, _meta) metric_name = "sq.#{rule}.#{cb}" ensure_metric(metric_name) time_millis = (finish - start) * 1000 # Ensure we always have a timings if we somehow missed the request start SharedStorage[:sqreen_request_time] = (SharedStorage[:sqreen_request_time] || 0) + time_millis metrics_store.update(metric_name, finish, nil, time_millis) end def start_request SharedStorage[:request_start_time] = Sqreen.time SharedStorage[:sqreen_request_time] = 0.0 end def finish_request start_time = SharedStorage[:request_start_time] finish_time = Sqreen.time duration_millis = (finish_time - start_time) * 1000 finish_time_obj = Time.now.utc # 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 def finish_watcher_run return unless Sqreen.thread_cpu_time? new_clock_time = Sqreen.time # collect observation at min 30 second intervals so it's nicely averaged return if new_clock_time - @clock_time < 30.0 clock_time_before = @clock_time watcher_cpu_time_before = @watcher_cpu_time @clock_time = new_clock_time @watcher_cpu_time = Sqreen.thread_cpu_time clock_time_diff = @clock_time - clock_time_before watcher_cpu_diff = @watcher_cpu_time - watcher_cpu_time_before Sqreen.observations_queue.push( [EVENT_SQ_THREAD_CPU_PCT, nil, (watcher_cpu_diff * 100.0) / clock_time_diff, Time.now.utc] ) end private # @return [Sqreen::MetricsStore] attr_reader :metrics_store attr_reader :period def ensure_metric(metric_name, rule = nil) return if metrics_store.metric?(metric_name) metrics_store.create_metric( { 'name' => metric_name, 'period' => period, 'kind' => 'Binning', 'options' => @perf_metric_opts, }, rule ) 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 def finish_watcher_run return unless instance instance.finish_watcher_run end end end end end