module PulseMeter module Sensor class Base attr_accessor :color end end end require 'pulse-meter/visualizer' module PulseToolbox module Sensor class Manager extend PulseToolbox::Sensor::Mixins::Iterators # @!attribute [rw] default_options # @return [Hash] default sensor options class_attribute :default_options # @!attribute [rw] sensors_config # @return [Hash] sensors config class_attribute :sensors_config # @!attribute [rw] configurator # @return [PulseMeter::Sensor::Configuration] configurator instance class_attribute :configurator # @!attribute [rw] monitoring_layout # @return [PulseMeter::Visualize::DSL::Layout] layout for monitoring page class_attribute :monitoring_layout self.monitoring_layout = PulseMeter::Visualize::DSL::Layout.new self.default_options = { ttl: 7.days, interval: 1.minute, raw_data_ttl: 1.hour, reduce_delay: 2.minutes }.freeze self.sensors_config = { max: { title: "Max times", values: "Time, ms", sensors: { db_time: { sensor_type: 'timelined/max', color: '#0000FF', args: { annotation: "DB" } }, view_time: { sensor_type: 'timelined/max', color: '#00FF00', args: { annotation: "View" } }, total_time: { sensor_type: 'timelined/max', color: '#FF0000', args: { annotation: "Total" } } } }, p95: { title: "95% percentile times", values: "Time, ms", sensors: { db_time: { sensor_type: 'timelined/percentile', color: '#0000FF', args: { annotation: "DB", p: 0.95 } }, view_time: { sensor_type: 'timelined/percentile', color: '#00FF00', args: { annotation: "View", p: 0.95 } }, total_time: { sensor_type: 'timelined/percentile', color: '#FF0000', args: { annotation: "Total", p: 0.95 } } } }, p99: { title: "99% percentile times", values: "Time, ms", sensors: { db_time: { sensor_type: 'timelined/percentile', color: '#0000FF', args: { annotation: "DB", p: 0.99 } }, view_time: { sensor_type: 'timelined/percentile', color: '#00FF00', args: { annotation: "View", p: 0.99 } }, total_time: { sensor_type: 'timelined/percentile', color: '#FF0000', args: { annotation: "Total", p: 0.99 } } } }, status: { title: 'Request count', values: "Count", sensors: { total: { sensor_type: 'timelined/counter', color: '#FF0000', args: { annotation: "Total", } }, count: { sensor_type: 'timelined/hashed_counter', color: '#008800', args: { annotation: "Status" } } } }, action: { title: 'Request count by action', values: "Count", sensors: { count: { sensor_type: 'timelined/hashed_counter', color: '#008800', args: { annotation: "Action" } } } } } # Creates all sensors from sensors_config def self.create_sensors PulseToolbox.maybe_reconnect config = cfg self.configurator = PulseMeter::Sensor::Configuration.new(cfg) each_sensor do |s| s.color = config[s.name.to_sym][:color] end end class << self # Logs rails request timing to various sensors # @param total_time [Float] total request time # @param view_time [Float] view time of request # @param db_time [Float] db time of request def log_request(total_time, payload) view_time = payload[:view_runtime] db_time = payload[:db_runtime] lazy_configurator PulseToolbox.redis.multi do { max_db_time: db_time, max_view_time: view_time, max_total_time: total_time, p95_db_time: db_time, p95_view_time: view_time, p95_total_time: total_time, p99_db_time: db_time, p99_view_time: view_time, p99_total_time: total_time, status_count: {payload[:status].to_s => 1}, status_total: 1, action_count: {"#{payload[:controller]}##{payload[:action]}" => 1} }.each_pair {|name, value| event(name, value)} end end # Sends value to sensor by name # @param sensor [Symbol] sensor name # @param value [Float] event value def event(sensor, value) lazy_configurator.sensor(sensor).event(value) end # Adds group to config # @param name [Symbol] group name # @param title [String] group title def add_group(name, title = nil) name = name.to_sym sensors_config[name] ||= {} sensors_config[name][:title] = title if title sensors_config[name][:sensors] ||= {} return name end # Adds sensor to group in config # @param group [Symbol] group name # @param name [Symbol] sensor name # @param options [Hash] sensor options def add_sensor(group, name, options) name = name.to_sym g = add_group(group) sensors_config[g][:sensors][name] = options return name_in_group(group, name) end # Returns monitoring page layout def layout yield(monitoring_layout) monitoring_layout end private def cfg cfg = {} each_group do |group| sensors_config[group][:sensors].each_pair do |key, params| name = name_in_group(group, key) full_args = default_options.merge(params[:args]) params[:args] = default_options.merge(params[:args]) cfg[name] = params end end cfg end def name_in_group(group, sensor_name) "#{group}_#{sensor_name}".to_sym end def get_sensor(group, name) lazy_configurator.sensor(name_in_group(group, name)) end def lazy_configurator create_sensors unless configurator configurator end end end end end