canvas_statsd 1.0.8 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/canvas_statsd.rb +44 -44
- data/lib/canvas_statsd/block_stat.rb +37 -0
- data/lib/canvas_statsd/block_tracking.rb +47 -0
- data/lib/canvas_statsd/counter.rb +16 -7
- data/lib/canvas_statsd/default_tracking.rb +19 -55
- data/lib/canvas_statsd/request_logger.rb +5 -15
- data/lib/canvas_statsd/request_stat.rb +13 -21
- data/lib/canvas_statsd/request_tracking.rb +27 -0
- data/lib/canvas_statsd/sql_tracker.rb +10 -14
- data/spec/canvas_statsd/block_stat_spec.rb +11 -0
- data/spec/canvas_statsd/block_tracking_spec.rb +37 -0
- data/spec/canvas_statsd/counter_spec.rb +12 -17
- data/spec/canvas_statsd/request_logger_spec.rb +26 -10
- data/spec/canvas_statsd/request_stat_spec.rb +6 -21
- data/spec/canvas_statsd/request_tracking_spec.rb +14 -0
- data/spec/canvas_statsd/sql_tracker_spec.rb +33 -14
- metadata +26 -5
- data/spec/canvas_statsd/default_tracking_spec.rb +0 -53
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 951dbaa395f7d79e76329d841ca6ce9aa8716a07
         | 
| 4 | 
            +
              data.tar.gz: 2b9f6679bc1dd954fbe53ddf22fe43febde61380
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 9465805cde29e5584b03fd173fb1b9d08cd21892413c6e1c8a06daa5a6295b45ea32ef8acdcab8a05bb967e8f385b9013e8e98b54dcb9a1922f9d849f6734b8d
         | 
| 7 | 
            +
              data.tar.gz: a2c901c2764ff4a5690a6704a0f68c28da6ea13f97b52a5237caaeb3c15ff38fa9d05509fb1a345bce2a0dfc278af3104f64b39d81e91eb0ddd828bcd09abdba
         | 
    
        data/lib/canvas_statsd.rb
    CHANGED
    
    | @@ -1,5 +1,4 @@ | |
| 1 1 | 
             
            require 'statsd'
         | 
| 2 | 
            -
            require "aroi" if defined?(ActiveRecord)
         | 
| 3 2 |  | 
| 4 3 | 
             
            module CanvasStatsd
         | 
| 5 4 | 
             
              VALID_SETTINGS = [:host, :port, :namespace, :append_hostname]
         | 
| @@ -7,59 +6,60 @@ module CanvasStatsd | |
| 7 6 | 
             
              class ConfigurationError < StandardError; end
         | 
| 8 7 |  | 
| 9 8 | 
             
              require "canvas_statsd/statsd"
         | 
| 9 | 
            +
              require "canvas_statsd/block_stat"
         | 
| 10 | 
            +
              require "canvas_statsd/block_tracking"
         | 
| 10 11 | 
             
              require "canvas_statsd/request_stat"
         | 
| 11 12 | 
             
              require "canvas_statsd/counter"
         | 
| 12 13 | 
             
              require "canvas_statsd/sql_tracker"
         | 
| 13 14 | 
             
              require "canvas_statsd/default_tracking"
         | 
| 14 15 | 
             
              require "canvas_statsd/request_logger"
         | 
| 16 | 
            +
              require "canvas_statsd/request_tracking"
         | 
| 15 17 | 
             
              require "canvas_statsd/null_logger"
         | 
| 16 18 |  | 
| 17 | 
            -
               | 
| 18 | 
            -
                 | 
| 19 | 
            -
             | 
| 20 | 
            -
             | 
| 21 | 
            -
               | 
| 22 | 
            -
                 | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
               | 
| 26 | 
            -
                 | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
                   | 
| 31 | 
            -
                     | 
| 19 | 
            +
              class << self
         | 
| 20 | 
            +
                def settings
         | 
| 21 | 
            +
                  @settings || env_settings
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              
         | 
| 24 | 
            +
                def settings=(value)
         | 
| 25 | 
            +
                  @settings = validate_settings(value)
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              
         | 
| 28 | 
            +
                def validate_settings(value)
         | 
| 29 | 
            +
                  return nil if value.nil?
         | 
| 30 | 
            +
              
         | 
| 31 | 
            +
                  validated = {}
         | 
| 32 | 
            +
                  value.each do |k,v|
         | 
| 33 | 
            +
                    if !VALID_SETTINGS.include?(k.to_sym)
         | 
| 34 | 
            +
                      raise CanvasStatsd::ConfigurationError, "Invalid key: #{k}"
         | 
| 35 | 
            +
                    end
         | 
| 36 | 
            +
                    validated[k.to_sym] = v
         | 
| 32 37 | 
             
                  end
         | 
| 33 | 
            -
             | 
| 38 | 
            +
              
         | 
| 39 | 
            +
                  env_settings.merge(validated)
         | 
| 34 40 | 
             
                end
         | 
| 35 | 
            -
             | 
| 36 | 
            -
                env_settings | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
                   | 
| 43 | 
            -
                   | 
| 44 | 
            -
                   | 
| 45 | 
            -
             | 
| 46 | 
            -
                 | 
| 47 | 
            -
             | 
| 48 | 
            -
                 | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
                   | 
| 56 | 
            -
                  raise CanvasStatsd::ConfigurationError, message
         | 
| 41 | 
            +
              
         | 
| 42 | 
            +
                def env_settings(env=ENV)
         | 
| 43 | 
            +
                  config = {
         | 
| 44 | 
            +
                    host: env.fetch('CANVAS_STATSD_HOST', nil),
         | 
| 45 | 
            +
                    port: env.fetch('CANVAS_STATSD_PORT', nil),
         | 
| 46 | 
            +
                    namespace: env.fetch('CANVAS_STATSD_NAMESPACE', nil),
         | 
| 47 | 
            +
                    append_hostname: env.fetch('CANVAS_STATSD_APPEND_HOSTNAME', nil),
         | 
| 48 | 
            +
                  }
         | 
| 49 | 
            +
                  config.delete_if {|k,v| v.nil?}
         | 
| 50 | 
            +
                  convert_bool(config, :append_hostname)
         | 
| 51 | 
            +
                  config[:host] ? config : {}
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
              
         | 
| 54 | 
            +
                def convert_bool(hash, key)
         | 
| 55 | 
            +
                  value = hash[key]
         | 
| 56 | 
            +
                  return if value.nil?
         | 
| 57 | 
            +
                  unless ['true', 'True', 'false', 'False', true, false].include?(value)
         | 
| 58 | 
            +
                    message = "#{key} must be a boolean, or the string representation of a boolean, got: #{value}"
         | 
| 59 | 
            +
                    raise CanvasStatsd::ConfigurationError, message
         | 
| 60 | 
            +
                  end
         | 
| 61 | 
            +
                  hash[key] = ['true', 'True', true].include?(value)
         | 
| 57 62 | 
             
                end
         | 
| 58 | 
            -
                hash[key] = ['true', 'True', true].include?(value)
         | 
| 59 | 
            -
              end
         | 
| 60 | 
            -
             | 
| 61 | 
            -
              def self.track_default_metrics options={}
         | 
| 62 | 
            -
                CanvasStatsd::DefaultTracking.track_default_metrics options
         | 
| 63 63 | 
             
              end
         | 
| 64 64 |  | 
| 65 65 | 
             
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            module CanvasStatsd
         | 
| 2 | 
            +
              class BlockStat
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                attr_accessor :stats
         | 
| 5 | 
            +
                attr_accessor :common_key
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def initialize(common_key, statsd=CanvasStatsd::Statsd)
         | 
| 8 | 
            +
                  self.common_key = common_key
         | 
| 9 | 
            +
                  @statsd = statsd
         | 
| 10 | 
            +
                  @stats = {}
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def subtract_exclusives(stats)
         | 
| 14 | 
            +
                  @exclusives ||= {}
         | 
| 15 | 
            +
                  stats.each do |(key, value)|
         | 
| 16 | 
            +
                    @exclusives[key] ||= 0.0
         | 
| 17 | 
            +
                    @exclusives[key] += value
         | 
| 18 | 
            +
                  end
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                def exclusive_stats
         | 
| 22 | 
            +
                  return nil unless @exclusives
         | 
| 23 | 
            +
                  stats.map { |key, value| [key, value - (@exclusives[key] || 0.0)] }.to_h
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def report
         | 
| 27 | 
            +
                  if common_key
         | 
| 28 | 
            +
                    stats.each do |(key, value)|
         | 
| 29 | 
            +
                      @statsd.timing("#{common_key}.#{key}", value)
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
                    exclusive_stats&.each do |(key, value)|
         | 
| 32 | 
            +
                      @statsd.timing("#{common_key}.exclusive.#{key}", value)
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -0,0 +1,47 @@ | |
| 1 | 
            +
            require 'benchmark'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module CanvasStatsd
         | 
| 4 | 
            +
              class BlockTracking
         | 
| 5 | 
            +
                class << self
         | 
| 6 | 
            +
                  attr_accessor :logger
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def track(key, category: nil, statsd: CanvasStatsd::Statsd, only: nil)
         | 
| 9 | 
            +
                    cookies = if only
         | 
| 10 | 
            +
                                Array(only).map { |name| [name, Counter.counters[name].start] }
         | 
| 11 | 
            +
                              else
         | 
| 12 | 
            +
                                Counter.counters.map { |(name, counter)| [name, counter.start] }
         | 
| 13 | 
            +
                              end
         | 
| 14 | 
            +
                    block_stat = CanvasStatsd::BlockStat.new(key, statsd)
         | 
| 15 | 
            +
                    stack(category).push(block_stat) if category
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    result = nil
         | 
| 18 | 
            +
                    elapsed = Benchmark.realtime do
         | 
| 19 | 
            +
                      result = yield
         | 
| 20 | 
            +
                    end
         | 
| 21 | 
            +
                    # to be consistent with ActionPack, measure in milliseconds
         | 
| 22 | 
            +
                    elapsed *= 1000
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    block_stat.stats = cookies.map { |(name, cookie)| [name, Counter.counters[name].finalize_count(cookie)] }.to_h
         | 
| 25 | 
            +
                    block_stat.stats['total'] = elapsed
         | 
| 26 | 
            +
                    # we need to make sure to report exclusive timings, even if nobody called us re-entrantly
         | 
| 27 | 
            +
                    block_stat.subtract_exclusives({}) if category
         | 
| 28 | 
            +
                    block_stat.report
         | 
| 29 | 
            +
                    logger.log(block_stat, "STATSD #{key}") if logger
         | 
| 30 | 
            +
                    # -1 is ourselves; we want to subtract from the block above us
         | 
| 31 | 
            +
                    stack(category)[-2].subtract_exclusives(block_stat.stats) if category && stack(category)[-2]
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    result
         | 
| 34 | 
            +
                  ensure
         | 
| 35 | 
            +
                    stack(category).pop if category && stack(category) == block_stat
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  private
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                  def stack(category)
         | 
| 41 | 
            +
                    Thread.current[:stats_block_stack] ||= {}
         | 
| 42 | 
            +
                    Thread.current[:stats_block_stack][category] ||= []
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
             | 
| 46 | 
            +
              end
         | 
| 47 | 
            +
            end
         | 
| @@ -1,5 +1,14 @@ | |
| 1 1 | 
             
            module CanvasStatsd
         | 
| 2 2 | 
             
              class Counter
         | 
| 3 | 
            +
                class << self
         | 
| 4 | 
            +
                  def counters
         | 
| 5 | 
            +
                    @counters ||= {}
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def register(counter)
         | 
| 9 | 
            +
                    counters[counter.key] = counter
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 3 12 |  | 
| 4 13 | 
             
                attr_reader :key
         | 
| 5 14 | 
             
                attr_reader :blocked_names
         | 
| @@ -7,24 +16,24 @@ module CanvasStatsd | |
| 7 16 | 
             
                def initialize(key, blocked_names=[])
         | 
| 8 17 | 
             
                  @blocked_names = blocked_names
         | 
| 9 18 | 
             
                  @key = key
         | 
| 19 | 
            +
                  @tls_key = "statsd.#{key}"
         | 
| 20 | 
            +
                  self.class.register(self)
         | 
| 10 21 | 
             
                end
         | 
| 11 22 |  | 
| 12 23 | 
             
                def start
         | 
| 13 | 
            -
                  Thread.current[ | 
| 24 | 
            +
                  Thread.current[@tls_key] ||= 0
         | 
| 14 25 | 
             
                end
         | 
| 15 26 |  | 
| 16 27 | 
             
                def track(name)
         | 
| 17 | 
            -
                  Thread.current[ | 
| 28 | 
            +
                  Thread.current[@tls_key] += 1 if Thread.current[@tls_key] && accepted_name?(name)
         | 
| 18 29 | 
             
                end
         | 
| 19 30 |  | 
| 20 | 
            -
                def finalize_count
         | 
| 21 | 
            -
                   | 
| 22 | 
            -
                  Thread.current[key] = 0
         | 
| 23 | 
            -
                  final_count
         | 
| 31 | 
            +
                def finalize_count(cookie)
         | 
| 32 | 
            +
                  Thread.current[@tls_key] - cookie
         | 
| 24 33 | 
             
                end
         | 
| 25 34 |  | 
| 26 35 | 
             
                def count
         | 
| 27 | 
            -
                  Thread.current[ | 
| 36 | 
            +
                  Thread.current[@tls_key]
         | 
| 28 37 | 
             
                end
         | 
| 29 38 |  | 
| 30 39 | 
             
                def accepted_name?(name)
         | 
| @@ -1,78 +1,42 @@ | |
| 1 | 
            +
            require "active_support"
         | 
| 2 | 
            +
             | 
| 1 3 | 
             
            module CanvasStatsd
         | 
| 2 4 | 
             
              class DefaultTracking
         | 
| 3 | 
            -
             | 
| 4 | 
            -
                @ar_counter = CanvasStatsd::Counter.new('ar_counter')
         | 
| 5 | 
            -
                @cache_read_counter = CanvasStatsd::Counter.new('cache_read_counter')
         | 
| 6 | 
            -
                @sql_tracker = CanvasStatsd::SqlTracker.new(blocked_names: ['SCHEMA'])
         | 
| 7 | 
            -
             | 
| 8 | 
            -
                def self.track_default_metrics(options={})
         | 
| 9 | 
            -
                  options = {sql: true, active_record: true, cache: true, logger: nil}.merge(options)
         | 
| 10 | 
            -
                  @logger = RequestLogger.new(options[:logger])
         | 
| 11 | 
            -
                  track_timing
         | 
| 12 | 
            -
                  track_sql if !!options[:sql]
         | 
| 13 | 
            -
                  track_active_record if !!options[:active_record]
         | 
| 14 | 
            -
                  track_cache if !!options[:cache]
         | 
| 15 | 
            -
                end
         | 
| 16 | 
            -
             | 
| 17 | 
            -
                def self.subscribe type, &block
         | 
| 18 | 
            -
                  ActiveSupport::Notifications.subscribe type, &block
         | 
| 19 | 
            -
                end
         | 
| 20 | 
            -
             | 
| 21 | 
            -
                private
         | 
| 22 | 
            -
             | 
| 23 | 
            -
                def self.instrument_active_record_creation
         | 
| 24 | 
            -
                  ::Aroi::Instrumentation.instrument_creation!
         | 
| 25 | 
            -
                end
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                def self.track_timing
         | 
| 28 | 
            -
                  subscribe(/start_processing\.action_controller/) {|*args| start_processing(*args)}
         | 
| 29 | 
            -
                  subscribe(/process_action\.action_controller/) {|*args| finalize_processing(*args)}
         | 
| 30 | 
            -
                end
         | 
| 31 | 
            -
             | 
| 32 5 | 
             
                def self.track_sql
         | 
| 33 | 
            -
                   | 
| 34 | 
            -
                   | 
| 6 | 
            +
                  return if @sql_tracker
         | 
| 7 | 
            +
                  @sql_tracker = CanvasStatsd::SqlTracker.new(blocked_names: ['SCHEMA'])
         | 
| 8 | 
            +
                  ActiveSupport::Notifications.subscribe(/sql\.active_record/) {|*args| update_sql_count(*args)}
         | 
| 35 9 | 
             
                end
         | 
| 36 10 |  | 
| 37 11 | 
             
                def self.track_active_record
         | 
| 38 | 
            -
                   | 
| 39 | 
            -
                   | 
| 40 | 
            -
             | 
| 12 | 
            +
                  return if @ar_counter
         | 
| 13 | 
            +
                  require 'aroi'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  ::Aroi::Instrumentation.instrument_creation!
         | 
| 16 | 
            +
                  @ar_counter = CanvasStatsd::Counter.new('active_record')
         | 
| 17 | 
            +
                  ActiveSupport::Notifications.subscribe(/instance\.active_record/) {|*args| update_active_record_count(*args)}
         | 
| 41 18 | 
             
                end
         | 
| 42 19 |  | 
| 43 20 | 
             
                def self.track_cache
         | 
| 44 | 
            -
                   | 
| 45 | 
            -
                  subscribe(/cache_read\.active_support/) {|*args| update_cache_read_count(*args)}
         | 
| 46 | 
            -
                end
         | 
| 21 | 
            +
                  return if @cache_read_counter
         | 
| 47 22 |  | 
| 48 | 
            -
             | 
| 49 | 
            -
                   | 
| 50 | 
            -
                  @ar_counter.start
         | 
| 51 | 
            -
                  @cache_read_counter.start
         | 
| 23 | 
            +
                  @cache_read_counter = CanvasStatsd::Counter.new('cache.read')
         | 
| 24 | 
            +
                  ActiveSupport::Notifications.subscribe(/cache_read\.active_support/) {|*args| update_cache_read_count(*args)}
         | 
| 52 25 | 
             
                end
         | 
| 53 26 |  | 
| 54 | 
            -
                 | 
| 27 | 
            +
                private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def self.update_sql_count(_name, _start, _finish, _id, payload)
         | 
| 55 30 | 
             
                  @sql_tracker.track payload.fetch(:name), payload.fetch(:sql)
         | 
| 56 31 | 
             
                end
         | 
| 57 32 |  | 
| 58 | 
            -
                def self.update_active_record_count | 
| 33 | 
            +
                def self.update_active_record_count(_name, _start, _finish, _id, payload)
         | 
| 59 34 | 
             
                  @ar_counter.track payload.fetch(:name, '')
         | 
| 60 35 | 
             
                end
         | 
| 61 36 |  | 
| 62 | 
            -
                def self.update_cache_read_count | 
| 37 | 
            +
                def self.update_cache_read_count(_name, _start, _finish, _id, _payload)
         | 
| 63 38 | 
             
                  @cache_read_counter.track "read"
         | 
| 64 39 | 
             
                end
         | 
| 65 40 |  | 
| 66 | 
            -
                def self.finalize_processing *args
         | 
| 67 | 
            -
                  request_stat = CanvasStatsd::RequestStat.new(*args)
         | 
| 68 | 
            -
                  request_stat.ar_count = @ar_counter.finalize_count if @tracking_active_record
         | 
| 69 | 
            -
                  request_stat.sql_read_count = @sql_tracker.num_reads if @tracking_sql
         | 
| 70 | 
            -
                  request_stat.sql_write_count = @sql_tracker.num_writes if @tracking_sql
         | 
| 71 | 
            -
                  request_stat.sql_cache_count = @sql_tracker.num_caches if @tracking_sql
         | 
| 72 | 
            -
                  request_stat.cache_read_count = @cache_read_counter.finalize_count if @tracking_cache
         | 
| 73 | 
            -
                  request_stat.report
         | 
| 74 | 
            -
                  @logger.log(request_stat)
         | 
| 75 | 
            -
                end
         | 
| 76 | 
            -
             | 
| 77 41 | 
             
              end
         | 
| 78 42 | 
             
            end
         | 
| @@ -1,18 +1,6 @@ | |
| 1 1 | 
             
            module CanvasStatsd
         | 
| 2 2 | 
             
              class RequestLogger
         | 
| 3 3 |  | 
| 4 | 
            -
                VALUES_MAP = {
         | 
| 5 | 
            -
                    total: :ms,
         | 
| 6 | 
            -
                    view: :view_runtime,
         | 
| 7 | 
            -
                    db: :db_runtime,
         | 
| 8 | 
            -
                    sql_read: :sql_read_count,
         | 
| 9 | 
            -
                    sql_write: :sql_write_count,
         | 
| 10 | 
            -
                    sql_cache: :sql_cache_count,
         | 
| 11 | 
            -
                    active_record: :ar_count,
         | 
| 12 | 
            -
                    cache_read: :cache_read_count,
         | 
| 13 | 
            -
                  }.freeze
         | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 4 | 
             
                def initialize(logger)
         | 
| 17 5 | 
             
                  @logger = logger || CanvasStatsd::NullLogger.new
         | 
| 18 6 | 
             
                end
         | 
| @@ -24,9 +12,11 @@ module CanvasStatsd | |
| 24 12 | 
             
                def build_log_message(request_stat, header=nil)
         | 
| 25 13 | 
             
                  header ||= "STATSD"
         | 
| 26 14 | 
             
                  message = "[#{header}]"
         | 
| 27 | 
            -
                   | 
| 28 | 
            -
                     | 
| 29 | 
            -
             | 
| 15 | 
            +
                  request_stat.stats.each do |(name, value)|
         | 
| 16 | 
            +
                    message += " (#{name.to_s.gsub('.', '_')}: #{"%.2f" % value})"
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                  request_stat.exclusive_stats&.each do |(name, value)|
         | 
| 19 | 
            +
                    message += " (exclusive_#{name.to_s.gsub('.', '_')}: #{"%.2f" % value})"
         | 
| 30 20 | 
             
                  end
         | 
| 31 21 | 
             
                  message
         | 
| 32 22 | 
             
                end
         | 
| @@ -1,33 +1,25 @@ | |
| 1 1 | 
             
            module CanvasStatsd
         | 
| 2 | 
            -
              class RequestStat
         | 
| 3 | 
            -
             | 
| 4 | 
            -
                attr_accessor :sql_read_count
         | 
| 5 | 
            -
                attr_accessor :sql_write_count
         | 
| 6 | 
            -
                attr_accessor :sql_cache_count
         | 
| 7 | 
            -
                attr_accessor :ar_count
         | 
| 8 | 
            -
                attr_accessor :cache_read_count
         | 
| 9 | 
            -
             | 
| 2 | 
            +
              class RequestStat < BlockStat
         | 
| 10 3 | 
             
                def initialize(name, start, finish, id, payload, statsd=CanvasStatsd::Statsd)
         | 
| 4 | 
            +
                  super(nil, statsd)
         | 
| 11 5 | 
             
                  @name = name
         | 
| 12 6 | 
             
                  @start = start
         | 
| 13 7 | 
             
                  @finish = finish
         | 
| 14 8 | 
             
                  @id = id
         | 
| 15 9 | 
             
                  @payload = payload
         | 
| 16 | 
            -
             | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def common_key
         | 
| 13 | 
            +
                  common_key = super
         | 
| 14 | 
            +
                  return common_key if common_key
         | 
| 15 | 
            +
                  self.common_key = "request.#{controller}.#{action}" if controller && action
         | 
| 17 16 | 
             
                end
         | 
| 18 17 |  | 
| 19 18 | 
             
                def report
         | 
| 20 | 
            -
                   | 
| 21 | 
            -
             | 
| 22 | 
            -
             | 
| 23 | 
            -
             | 
| 24 | 
            -
                    @statsd.timing("#{common_key}.db", db_runtime) if db_runtime
         | 
| 25 | 
            -
                    @statsd.timing("#{common_key}.sql.read", sql_read_count) if sql_read_count
         | 
| 26 | 
            -
                    @statsd.timing("#{common_key}.sql.write", sql_write_count) if sql_write_count
         | 
| 27 | 
            -
                    @statsd.timing("#{common_key}.sql.cache", sql_cache_count) if sql_cache_count
         | 
| 28 | 
            -
                    @statsd.timing("#{common_key}.active_record", ar_count) if ar_count
         | 
| 29 | 
            -
                    @statsd.timing("#{common_key}.cache.read", cache_read_count) if cache_read_count
         | 
| 30 | 
            -
                  end
         | 
| 19 | 
            +
                  stats['total'] = total
         | 
| 20 | 
            +
                  stats['view'] = view_runtime if view_runtime
         | 
| 21 | 
            +
                  stats['db'] = db_runtime if db_runtime
         | 
| 22 | 
            +
                  super
         | 
| 31 23 | 
             
                end
         | 
| 32 24 |  | 
| 33 25 | 
             
                def db_runtime
         | 
| @@ -46,7 +38,7 @@ module CanvasStatsd | |
| 46 38 | 
             
                  @payload.fetch(:params, {})['action']
         | 
| 47 39 | 
             
                end
         | 
| 48 40 |  | 
| 49 | 
            -
                def  | 
| 41 | 
            +
                def total
         | 
| 50 42 | 
             
                  if (!@finish || !@start)
         | 
| 51 43 | 
             
                    return 0
         | 
| 52 44 | 
             
                  end
         | 
| @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            module CanvasStatsd
         | 
| 2 | 
            +
              class RequestTracking
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def self.enable(logger: nil)
         | 
| 5 | 
            +
                  @logger = RequestLogger.new(logger)
         | 
| 6 | 
            +
                  track_timing
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                private
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def self.track_timing
         | 
| 12 | 
            +
                  ActiveSupport::Notifications.subscribe(/start_processing\.action_controller/, &method(:start_processing))
         | 
| 13 | 
            +
                  ActiveSupport::Notifications.subscribe(/process_action\.action_controller/, &method(:finalize_processing))
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def self.start_processing(*_args)
         | 
| 17 | 
            +
                  @cookies = Counter.counters.map { |(name, counter)| [name, counter.start] }
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def self.finalize_processing *args
         | 
| 21 | 
            +
                  request_stat = CanvasStatsd::RequestStat.new(*args)
         | 
| 22 | 
            +
                  request_stat.stats = @cookies.map { |(name, cookie)| [name, Counter.counters[name].finalize_count(cookie)] }.to_h
         | 
| 23 | 
            +
                  request_stat.report
         | 
| 24 | 
            +
                  @logger.log(request_stat)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
              end
         | 
| 27 | 
            +
            end
         | 
| @@ -6,13 +6,13 @@ module CanvasStatsd | |
| 6 6 | 
             
                def initialize(opts=nil)
         | 
| 7 7 | 
             
                  opts ||= {}
         | 
| 8 8 | 
             
                  @blocked_names = opts.fetch(:blocked_names, [])
         | 
| 9 | 
            -
                  @read_counts = opts.fetch(:read_counter, CanvasStatsd::Counter.new(' | 
| 10 | 
            -
                  @write_counts = opts.fetch(:write_counter, CanvasStatsd::Counter.new(' | 
| 11 | 
            -
                  @cache_counts = opts.fetch(:cache_counter, CanvasStatsd::Counter.new(' | 
| 9 | 
            +
                  @read_counts = opts.fetch(:read_counter, CanvasStatsd::Counter.new('sql.read'))
         | 
| 10 | 
            +
                  @write_counts = opts.fetch(:write_counter, CanvasStatsd::Counter.new('sql.write'))
         | 
| 11 | 
            +
                  @cache_counts = opts.fetch(:cache_counter, CanvasStatsd::Counter.new('sql.cache'))
         | 
| 12 12 | 
             
                end
         | 
| 13 13 |  | 
| 14 14 | 
             
                def start
         | 
| 15 | 
            -
                  [read_counts, write_counts, cache_counts]. | 
| 15 | 
            +
                  [read_counts, write_counts, cache_counts].map(&:start)
         | 
| 16 16 | 
             
                end
         | 
| 17 17 |  | 
| 18 18 | 
             
                def track name, sql
         | 
| @@ -27,16 +27,12 @@ module CanvasStatsd | |
| 27 27 | 
             
                  end
         | 
| 28 28 | 
             
                end
         | 
| 29 29 |  | 
| 30 | 
            -
                def  | 
| 31 | 
            -
                   | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                   | 
| 36 | 
            -
                end
         | 
| 37 | 
            -
             | 
| 38 | 
            -
                def num_caches
         | 
| 39 | 
            -
                  cache_counts.finalize_count
         | 
| 30 | 
            +
                def finalize_counts(cookies)
         | 
| 31 | 
            +
                  [
         | 
| 32 | 
            +
                    read_counts.finalize_count(cookies[0]),
         | 
| 33 | 
            +
                    write_counts.finalize_count(cookies[1]),
         | 
| 34 | 
            +
                    cache_counts.finalize_count(cookies[2])
         | 
| 35 | 
            +
                  ]
         | 
| 40 36 | 
             
                end
         | 
| 41 37 |  | 
| 42 38 | 
             
                private
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe CanvasStatsd::BlockStat do
         | 
| 4 | 
            +
              it "track exclusives correctly" do
         | 
| 5 | 
            +
                stat = CanvasStatsd::BlockStat.new("key")
         | 
| 6 | 
            +
                stat.stats['total'] = 5.0
         | 
| 7 | 
            +
                stat.subtract_exclusives("total" => 1.5)
         | 
| 8 | 
            +
                stat.subtract_exclusives("total" => 2.1)
         | 
| 9 | 
            +
                expect(stat.exclusive_stats).to eql("total" => 1.4)
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe CanvasStatsd::BlockTracking do
         | 
| 4 | 
            +
              before(:all) do
         | 
| 5 | 
            +
                CanvasStatsd::DefaultTracking.track_sql
         | 
| 6 | 
            +
              end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              it "works" do
         | 
| 9 | 
            +
                statsd = double()
         | 
| 10 | 
            +
                allow(statsd).to receive(:timing).with('mykey.total', anything)
         | 
| 11 | 
            +
                expect(statsd).to receive(:timing).with("mykey.sql.read", 1)
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                CanvasStatsd::BlockTracking.track("mykey", statsd: statsd, only: 'sql.read') do
         | 
| 14 | 
            +
                  ActiveSupport::Notifications.instrument('sql.active_record', name: "LOAD", sql: "SELECT * FROM users") {}
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              it "keeps track of exclusive stats too" do
         | 
| 19 | 
            +
                statsd = double()
         | 
| 20 | 
            +
                expect(statsd).to receive(:timing).with("mykey.sql.read", 2).ordered
         | 
| 21 | 
            +
                expect(statsd).to receive(:timing).with('mykey.total', anything).ordered
         | 
| 22 | 
            +
                expect(statsd).to receive(:timing).with("mykey.exclusive.sql.read", 2).ordered
         | 
| 23 | 
            +
                expect(statsd).to receive(:timing).with('mykey.exclusive.total', anything).ordered
         | 
| 24 | 
            +
                expect(statsd).to receive(:timing).with("mykey.sql.read", 3).ordered
         | 
| 25 | 
            +
                expect(statsd).to receive(:timing).with('mykey.total', anything).ordered
         | 
| 26 | 
            +
                expect(statsd).to receive(:timing).with("mykey.exclusive.sql.read", 1).ordered
         | 
| 27 | 
            +
                expect(statsd).to receive(:timing).with('mykey.exclusive.total', anything).ordered
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                CanvasStatsd::BlockTracking.track("mykey", category: :nested, statsd: statsd, only: 'sql.read') do
         | 
| 30 | 
            +
                  ActiveSupport::Notifications.instrument('sql.active_record', name: "LOAD", sql: "SELECT * FROM users") {}
         | 
| 31 | 
            +
                  CanvasStatsd::BlockTracking.track("mykey", category: :nested, statsd: statsd, only: 'sql.read') do
         | 
| 32 | 
            +
                    ActiveSupport::Notifications.instrument('sql.active_record', name: "LOAD", sql: "SELECT * FROM users") {}
         | 
| 33 | 
            +
                    ActiveSupport::Notifications.instrument('sql.active_record', name: "LOAD", sql: "SELECT * FROM users") {}
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -22,41 +22,36 @@ describe CanvasStatsd::Counter do | |
| 22 22 | 
             
                end
         | 
| 23 23 | 
             
              end
         | 
| 24 24 |  | 
| 25 | 
            -
              describe "#start" do
         | 
| 26 | 
            -
                it 'should reset count to zero' do
         | 
| 27 | 
            -
                  subject.start
         | 
| 28 | 
            -
                  expect(subject.count).to eq 0
         | 
| 29 | 
            -
                end
         | 
| 30 | 
            -
              end
         | 
| 31 | 
            -
             | 
| 32 25 | 
             
              describe "#track" do
         | 
| 33 26 | 
             
                it 'should increment when given allowed names' do
         | 
| 34 | 
            -
                  subject.start
         | 
| 27 | 
            +
                  cookie = subject.start
         | 
| 35 28 | 
             
                  subject.track('bar')
         | 
| 36 29 | 
             
                  subject.track('baz')
         | 
| 37 | 
            -
                  expect(subject. | 
| 30 | 
            +
                  expect(subject.finalize_count(cookie)).to eq 2
         | 
| 38 31 | 
             
                end
         | 
| 39 32 |  | 
| 40 33 | 
             
                it 'should not increment when given a blocked name' do
         | 
| 41 | 
            -
                  subject.start
         | 
| 34 | 
            +
                  cookie = subject.start
         | 
| 42 35 | 
             
                  subject.track('foo') #shouldn't count as foo is a blocked name
         | 
| 43 36 | 
             
                  subject.track('name')
         | 
| 44 | 
            -
                  expect(subject. | 
| 37 | 
            +
                  expect(subject.finalize_count(cookie)).to eq 1
         | 
| 45 38 | 
             
                end
         | 
| 46 39 | 
             
              end
         | 
| 47 40 |  | 
| 48 41 | 
             
              describe "#finalize_count" do
         | 
| 49 42 | 
             
                it 'should return the current count' do
         | 
| 50 | 
            -
                  subject.start
         | 
| 43 | 
            +
                  cookie = subject.start
         | 
| 51 44 | 
             
                  subject.track('bar')
         | 
| 52 | 
            -
                  expect(subject.finalize_count).to eq 1
         | 
| 45 | 
            +
                  expect(subject.finalize_count(cookie)).to eq 1
         | 
| 53 46 | 
             
                end
         | 
| 54 47 |  | 
| 55 | 
            -
                it 'should  | 
| 56 | 
            -
                  subject.start
         | 
| 48 | 
            +
                it 'should not interfere with multiple people using the object' do
         | 
| 49 | 
            +
                  cookie1 = subject.start
         | 
| 50 | 
            +
                  subject.track('bar')
         | 
| 51 | 
            +
                  cookie2 = subject.start
         | 
| 57 52 | 
             
                  subject.track('bar')
         | 
| 58 | 
            -
                  subject.finalize_count
         | 
| 59 | 
            -
                  expect(subject. | 
| 53 | 
            +
                  expect(subject.finalize_count(cookie1)).to eq 2
         | 
| 54 | 
            +
                  expect(subject.finalize_count(cookie2)).to eq 1
         | 
| 60 55 | 
             
                end
         | 
| 61 56 | 
             
              end
         | 
| 62 57 |  | 
| @@ -9,42 +9,55 @@ describe CanvasStatsd::RequestLogger do | |
| 9 9 | 
             
                end
         | 
| 10 10 | 
             
                it 'includes the supplied header' do
         | 
| 11 11 | 
             
                  request_stat = double('request_stat')
         | 
| 12 | 
            +
                  allow(request_stat).to receive(:exclusive_stats).and_return(nil)
         | 
| 13 | 
            +
                  allow(request_stat).to receive(:stats).and_return({})
         | 
| 12 14 | 
             
                  results = @logger.build_log_message(request_stat, 'FOO_STATS')
         | 
| 13 15 | 
             
                  expect(results).to eq("[FOO_STATS]")
         | 
| 14 16 | 
             
                end
         | 
| 15 17 | 
             
                it 'falls back to the default header' do
         | 
| 16 18 | 
             
                  request_stat = double('request_stat')
         | 
| 19 | 
            +
                  allow(request_stat).to receive(:exclusive_stats).and_return(nil)
         | 
| 20 | 
            +
                  allow(request_stat).to receive(:stats).and_return({})
         | 
| 17 21 | 
             
                  results = @logger.build_log_message(request_stat)
         | 
| 18 22 | 
             
                  expect(results).to eq("[STATSD]")
         | 
| 19 23 | 
             
                end
         | 
| 20 24 | 
             
                it 'includes stats that are available' do
         | 
| 21 25 | 
             
                  request_stat = double('request_stat')
         | 
| 22 | 
            -
                  allow(request_stat).to receive(: | 
| 23 | 
            -
                  allow(request_stat).to receive(: | 
| 26 | 
            +
                  allow(request_stat).to receive(:exclusive_stats).and_return(nil)
         | 
| 27 | 
            +
                  allow(request_stat).to receive(:stats).and_return(
         | 
| 28 | 
            +
                      "total" => 100.21,
         | 
| 29 | 
            +
                      "active.record" => 24)
         | 
| 24 30 | 
             
                  results = @logger.build_log_message(request_stat)
         | 
| 25 31 | 
             
                  expect(results).to eq("[STATSD] (total: 100.21) (active_record: 24.00)")
         | 
| 26 32 | 
             
                end
         | 
| 27 | 
            -
             | 
| 33 | 
            +
             | 
| 34 | 
            +
                it 'includes exclusive_stats if there are any' do
         | 
| 28 35 | 
             
                  request_stat = double('request_stat')
         | 
| 29 | 
            -
                  allow(request_stat).to receive(: | 
| 30 | 
            -
             | 
| 36 | 
            +
                  allow(request_stat).to receive(:stats).and_return(
         | 
| 37 | 
            +
                      "total" => 100.21,
         | 
| 38 | 
            +
                      "active.record" => 24)
         | 
| 39 | 
            +
                  allow(request_stat).to receive(:exclusive_stats).and_return(
         | 
| 40 | 
            +
                      "total" => 54.32,
         | 
| 41 | 
            +
                      "active.record" => 1)
         | 
| 31 42 | 
             
                  results = @logger.build_log_message(request_stat)
         | 
| 32 | 
            -
                  expect(results).to eq("[STATSD] (total: 100. | 
| 43 | 
            +
                  expect(results).to eq("[STATSD] (total: 100.21) (active_record: 24.00) (exclusive_total: 54.32) (exclusive_active_record: 1.00)")
         | 
| 33 44 | 
             
                end
         | 
| 34 45 |  | 
| 35 46 | 
             
                describe 'decimal precision' do
         | 
| 36 47 | 
             
                  it 'forces 2 decimal precision' do
         | 
| 37 48 | 
             
                    request_stat = double('request_stat')
         | 
| 38 | 
            -
                    allow(request_stat).to receive(: | 
| 49 | 
            +
                    allow(request_stat).to receive(:stats).and_return(total: 72.1)
         | 
| 50 | 
            +
                    allow(request_stat).to receive(:exclusive_stats).and_return(nil)
         | 
| 39 51 | 
             
                    results = @logger.build_log_message(request_stat)
         | 
| 40 52 | 
             
                    expect(results).to eq("[STATSD] (total: 72.10)")
         | 
| 41 53 | 
             
                  end
         | 
| 42 54 | 
             
                  it 'rounds values to 2 decimals' do
         | 
| 43 55 | 
             
                    request_stat = double('request_stat')
         | 
| 44 | 
            -
                    allow(request_stat).to receive(: | 
| 56 | 
            +
                    allow(request_stat).to receive(:stats).and_return(total: 72.1382928)
         | 
| 57 | 
            +
                    allow(request_stat).to receive(:exclusive_stats).and_return(nil)
         | 
| 45 58 | 
             
                    results = @logger.build_log_message(request_stat)
         | 
| 46 59 | 
             
                    expect(results).to eq("[STATSD] (total: 72.14)")
         | 
| 47 | 
            -
                    allow(request_stat).to receive(: | 
| 60 | 
            +
                    allow(request_stat).to receive(:stats).and_return(total: 72.1348209)
         | 
| 48 61 | 
             
                    results = @logger.build_log_message(request_stat)
         | 
| 49 62 | 
             
                    expect(results).to eq("[STATSD] (total: 72.13)")
         | 
| 50 63 | 
             
                  end
         | 
| @@ -58,6 +71,8 @@ describe CanvasStatsd::RequestLogger do | |
| 58 71 | 
             
                  logger = CanvasStatsd::RequestLogger.new(std_out_logger)
         | 
| 59 72 | 
             
                  expect(std_out_logger).to receive(:info)
         | 
| 60 73 | 
             
                  request_stat = double('request_stat')
         | 
| 74 | 
            +
                  allow(request_stat).to receive(:stats).and_return({})
         | 
| 75 | 
            +
                  allow(request_stat).to receive(:exclusive_stats).and_return(nil)
         | 
| 61 76 | 
             
                  logger.log(request_stat)
         | 
| 62 77 | 
             
                end
         | 
| 63 78 | 
             
                it 'sends info method with build_log_message output if logger exists' do
         | 
| @@ -65,7 +80,8 @@ describe CanvasStatsd::RequestLogger do | |
| 65 80 | 
             
                  logger = CanvasStatsd::RequestLogger.new(std_out_logger)
         | 
| 66 81 | 
             
                  expect(std_out_logger).to receive(:info).with("[DEFAULT_METRICS] (total: 100.20)")
         | 
| 67 82 | 
             
                  request_stat = double('request_stat')
         | 
| 68 | 
            -
                  allow(request_stat).to receive(: | 
| 83 | 
            +
                  allow(request_stat).to receive(:stats).and_return(total: 100.2)
         | 
| 84 | 
            +
                  allow(request_stat).to receive(:exclusive_stats).and_return(nil)
         | 
| 69 85 | 
             
                  logger.log(request_stat, "DEFAULT_METRICS")
         | 
| 70 86 | 
             
                end
         | 
| 71 87 | 
             
              end
         | 
| @@ -68,18 +68,18 @@ describe CanvasStatsd::RequestStat do | |
| 68 68 | 
             
                end
         | 
| 69 69 | 
             
              end
         | 
| 70 70 |  | 
| 71 | 
            -
              describe '# | 
| 71 | 
            +
              describe '#total' do
         | 
| 72 72 | 
             
                it 'correctly calcuates milliseconds from start, finish' do
         | 
| 73 73 | 
             
                  rs = create_subject({params: {}})
         | 
| 74 74 | 
             
                  # start and finish are in seconds
         | 
| 75 | 
            -
                  expect(rs. | 
| 75 | 
            +
                  expect(rs.total).to eq 1000
         | 
| 76 76 | 
             
                end
         | 
| 77 77 |  | 
| 78 78 | 
             
                it 'defaults to zero if either start or finish are nil' do
         | 
| 79 79 | 
             
                  rs = CanvasStatsd::RequestStat.new('name', nil, 1001, 1111, {params: {}})
         | 
| 80 | 
            -
                  expect(rs. | 
| 80 | 
            +
                  expect(rs.total).to eq 0
         | 
| 81 81 | 
             
                  rs = CanvasStatsd::RequestStat.new('name', 1, nil, 1111, {params: {}})
         | 
| 82 | 
            -
                  expect(rs. | 
| 82 | 
            +
                  expect(rs.total).to eq 0
         | 
| 83 83 | 
             
                end
         | 
| 84 84 | 
             
              end
         | 
| 85 85 |  | 
| @@ -142,7 +142,7 @@ describe CanvasStatsd::RequestStat do | |
| 142 142 | 
             
                      }
         | 
| 143 143 | 
             
                    }
         | 
| 144 144 | 
             
                    @rs = create_subject(payload, @statsd)
         | 
| 145 | 
            -
                    @rs. | 
| 145 | 
            +
                    @rs.stats['cache.read'] = 25
         | 
| 146 146 | 
             
                    expect(@statsd).to receive(:timing).with('request.foo.index.cache.read', 25)
         | 
| 147 147 | 
             
                  end
         | 
| 148 148 |  | 
| @@ -155,26 +155,11 @@ describe CanvasStatsd::RequestStat do | |
| 155 155 | 
             
                  end
         | 
| 156 156 |  | 
| 157 157 | 
             
                  it 'sends sql_read_count when present' do
         | 
| 158 | 
            -
                    @rs. | 
| 158 | 
            +
                    @rs.stats['sql.read'] = 10
         | 
| 159 159 | 
             
                    allow(@statsd).to receive(:timing).with('request.foo.index.total', 1000)
         | 
| 160 160 | 
             
                    expect(@statsd).to receive(:timing).with('request.foo.index.sql.read', 10)
         | 
| 161 161 | 
             
                    @rs.report
         | 
| 162 162 | 
             
                  end
         | 
| 163 | 
            -
             | 
| 164 | 
            -
                  it 'sends sql_read_count when present' do
         | 
| 165 | 
            -
                    @rs.sql_write_count = 3
         | 
| 166 | 
            -
                    allow(@statsd).to receive(:timing).with('request.foo.index.total', 1000)
         | 
| 167 | 
            -
                    expect(@statsd).to receive(:timing).with('request.foo.index.sql.write', 3)
         | 
| 168 | 
            -
                    @rs.report
         | 
| 169 | 
            -
                  end
         | 
| 170 | 
            -
             | 
| 171 | 
            -
                  it 'sends sql_cache_count when present' do
         | 
| 172 | 
            -
                    @rs.sql_cache_count = 1
         | 
| 173 | 
            -
                    allow(@statsd).to receive(:timing).with('request.foo.index.total', 1000)
         | 
| 174 | 
            -
                    expect(@statsd).to receive(:timing).with('request.foo.index.sql.cache', 1)
         | 
| 175 | 
            -
                    @rs.report
         | 
| 176 | 
            -
                  end
         | 
| 177 | 
            -
             | 
| 178 163 | 
             
                end
         | 
| 179 164 |  | 
| 180 165 | 
             
              end
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe CanvasStatsd::RequestTracking do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              describe '#enable' do
         | 
| 6 | 
            +
                it 'should delegate log messages to the optional logger' do
         | 
| 7 | 
            +
                  log_double = double()
         | 
| 8 | 
            +
                  expect(log_double).to receive(:info)
         | 
| 9 | 
            +
                  CanvasStatsd::RequestTracking.enable logger: log_double
         | 
| 10 | 
            +
                  CanvasStatsd::RequestTracking.start_processing
         | 
| 11 | 
            +
                  CanvasStatsd::RequestTracking.finalize_processing('name', 1000, 10001, 1234, {})
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -8,48 +8,67 @@ module CanvasStatsd | |
| 8 8 | 
             
                    subject = SqlTracker.new
         | 
| 9 9 | 
             
                    subject.start
         | 
| 10 10 | 
             
                    subject.track 'CACHE', 'SELECT * FROM some_table'
         | 
| 11 | 
            -
                    subject.start
         | 
| 12 | 
            -
                    expect(subject. | 
| 13 | 
            -
                    expect(subject.num_reads).to eq(0)
         | 
| 14 | 
            -
                    expect(subject.num_writes).to eq(0)
         | 
| 11 | 
            +
                    cookies = subject.start
         | 
| 12 | 
            +
                    expect(subject.finalize_counts(cookies)).to eq([0, 0, 0])
         | 
| 15 13 | 
             
                  end
         | 
| 16 14 | 
             
                end
         | 
| 17 15 |  | 
| 18 16 | 
             
                describe '#track' do
         | 
| 19 17 | 
             
                  before :each do
         | 
| 20 18 | 
             
                    @subject = SqlTracker.new
         | 
| 21 | 
            -
                    @subject.start
         | 
| 19 | 
            +
                    @cookies = @subject.start
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def finish
         | 
| 23 | 
            +
                    if @num_reads.nil?
         | 
| 24 | 
            +
                      @num_reads, @num_writes, @num_caches = @subject.finalize_counts(@cookies)
         | 
| 25 | 
            +
                    end
         | 
| 26 | 
            +
                  end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                  def num_reads
         | 
| 29 | 
            +
                    finish
         | 
| 30 | 
            +
                    @num_reads
         | 
| 31 | 
            +
                  end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                  def num_writes
         | 
| 34 | 
            +
                    finish
         | 
| 35 | 
            +
                    @num_writes
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def num_caches
         | 
| 39 | 
            +
                    finish
         | 
| 40 | 
            +
                    @num_caches
         | 
| 22 41 | 
             
                  end
         | 
| 23 42 |  | 
| 24 43 | 
             
                  it 'considers CACHE above all' do
         | 
| 25 44 | 
             
                    @subject.track 'CACHE', 'SELECT * FROM some_table'
         | 
| 26 | 
            -
                    expect( | 
| 27 | 
            -
                    expect( | 
| 45 | 
            +
                    expect(num_caches).to eq(1)
         | 
| 46 | 
            +
                    expect(num_reads).to eq(0)
         | 
| 28 47 | 
             
                  end
         | 
| 29 48 |  | 
| 30 49 | 
             
                  it 'marks as read when select is in the first 15 chars of the sql' do
         | 
| 31 50 | 
             
                    @subject.track 'LOAD', '  SELECT "context_external_tools".* FROM'
         | 
| 32 | 
            -
                    expect( | 
| 33 | 
            -
                    expect( | 
| 51 | 
            +
                    expect(num_reads).to eq(1)
         | 
| 52 | 
            +
                    expect(num_writes).to eq(0)
         | 
| 34 53 | 
             
                  end
         | 
| 35 54 |  | 
| 36 55 | 
             
                  it 'marks as read with no select, but a LOAD name' do
         | 
| 37 56 | 
             
                    @subject.track 'LOAD', 'WITH RECURSIVE t AS'
         | 
| 38 | 
            -
                    expect( | 
| 39 | 
            -
                    expect( | 
| 57 | 
            +
                    expect(num_reads).to eq(1)
         | 
| 58 | 
            +
                    expect(num_writes).to eq(0)
         | 
| 40 59 | 
             
                  end
         | 
| 41 60 |  | 
| 42 61 | 
             
                  it 'doesnt track names set as blocked' do
         | 
| 43 62 | 
             
                    tracker = SqlTracker.new(blocked_names: ['SCHEMA'])
         | 
| 44 | 
            -
                    tracker.start
         | 
| 63 | 
            +
                    cookies = tracker.start
         | 
| 45 64 | 
             
                    tracker.track 'SCHEMA', 'SELECT * FROM some_table'
         | 
| 46 | 
            -
                    expect(tracker. | 
| 65 | 
            +
                    expect(tracker.finalize_counts(cookies)[0]).to eq(0)
         | 
| 47 66 | 
             
                  end
         | 
| 48 67 |  | 
| 49 68 | 
             
                  it 'doesnt track nil names or sql values' do
         | 
| 50 69 | 
             
                    @subject.track nil, 'SELECT *'
         | 
| 51 70 | 
             
                    @subject.track 'CACHE', nil
         | 
| 52 | 
            -
                    expect( | 
| 71 | 
            +
                    expect(num_reads).to eq(0)
         | 
| 53 72 | 
             
                  end
         | 
| 54 73 |  | 
| 55 74 | 
             
                  it 'passes full sql to counter.track calls for reads' do
         | 
    
        metadata
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: canvas_statsd
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version:  | 
| 4 | 
            +
              version: 2.0.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Nick Cloward
         | 
| @@ -9,7 +9,7 @@ authors: | |
| 9 9 | 
             
            autorequire: 
         | 
| 10 10 | 
             
            bindir: bin
         | 
| 11 11 | 
             
            cert_chain: []
         | 
| 12 | 
            -
            date:  | 
| 12 | 
            +
            date: 2017-01-26 00:00:00.000000000 Z
         | 
| 13 13 | 
             
            dependencies:
         | 
| 14 14 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 15 15 | 
             
              name: statsd-ruby
         | 
| @@ -53,6 +53,20 @@ dependencies: | |
| 53 53 | 
             
                - - "~>"
         | 
| 54 54 | 
             
                  - !ruby/object:Gem::Version
         | 
| 55 55 | 
             
                    version: '1.5'
         | 
| 56 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 57 | 
            +
              name: byebug
         | 
| 58 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 59 | 
            +
                requirements:
         | 
| 60 | 
            +
                - - ">="
         | 
| 61 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 62 | 
            +
                    version: '0'
         | 
| 63 | 
            +
              type: :development
         | 
| 64 | 
            +
              prerelease: false
         | 
| 65 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 66 | 
            +
                requirements:
         | 
| 67 | 
            +
                - - ">="
         | 
| 68 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 69 | 
            +
                    version: '0'
         | 
| 56 70 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 57 71 | 
             
              name: rake
         | 
| 58 72 | 
             
              requirement: !ruby/object:Gem::Requirement
         | 
| @@ -90,19 +104,24 @@ extensions: [] | |
| 90 104 | 
             
            extra_rdoc_files: []
         | 
| 91 105 | 
             
            files:
         | 
| 92 106 | 
             
            - lib/canvas_statsd.rb
         | 
| 107 | 
            +
            - lib/canvas_statsd/block_stat.rb
         | 
| 108 | 
            +
            - lib/canvas_statsd/block_tracking.rb
         | 
| 93 109 | 
             
            - lib/canvas_statsd/counter.rb
         | 
| 94 110 | 
             
            - lib/canvas_statsd/default_tracking.rb
         | 
| 95 111 | 
             
            - lib/canvas_statsd/null_logger.rb
         | 
| 96 112 | 
             
            - lib/canvas_statsd/request_logger.rb
         | 
| 97 113 | 
             
            - lib/canvas_statsd/request_stat.rb
         | 
| 114 | 
            +
            - lib/canvas_statsd/request_tracking.rb
         | 
| 98 115 | 
             
            - lib/canvas_statsd/sql_tracker.rb
         | 
| 99 116 | 
             
            - lib/canvas_statsd/statsd.rb
         | 
| 117 | 
            +
            - spec/canvas_statsd/block_stat_spec.rb
         | 
| 118 | 
            +
            - spec/canvas_statsd/block_tracking_spec.rb
         | 
| 100 119 | 
             
            - spec/canvas_statsd/canvas_statsd_spec.rb
         | 
| 101 120 | 
             
            - spec/canvas_statsd/counter_spec.rb
         | 
| 102 | 
            -
            - spec/canvas_statsd/default_tracking_spec.rb
         | 
| 103 121 | 
             
            - spec/canvas_statsd/null_logger_spec.rb
         | 
| 104 122 | 
             
            - spec/canvas_statsd/request_logger_spec.rb
         | 
| 105 123 | 
             
            - spec/canvas_statsd/request_stat_spec.rb
         | 
| 124 | 
            +
            - spec/canvas_statsd/request_tracking_spec.rb
         | 
| 106 125 | 
             
            - spec/canvas_statsd/sql_tracker_spec.rb
         | 
| 107 126 | 
             
            - spec/canvas_statsd/statsd_spec.rb
         | 
| 108 127 | 
             
            - spec/spec_helper.rb
         | 
| @@ -128,17 +147,19 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 128 147 | 
             
                  version: '0'
         | 
| 129 148 | 
             
            requirements: []
         | 
| 130 149 | 
             
            rubyforge_project: 
         | 
| 131 | 
            -
            rubygems_version: 2.6. | 
| 150 | 
            +
            rubygems_version: 2.6.10
         | 
| 132 151 | 
             
            signing_key: 
         | 
| 133 152 | 
             
            specification_version: 4
         | 
| 134 153 | 
             
            summary: Statsd for Canvas
         | 
| 135 154 | 
             
            test_files:
         | 
| 155 | 
            +
            - spec/canvas_statsd/block_stat_spec.rb
         | 
| 156 | 
            +
            - spec/canvas_statsd/block_tracking_spec.rb
         | 
| 136 157 | 
             
            - spec/canvas_statsd/canvas_statsd_spec.rb
         | 
| 137 158 | 
             
            - spec/canvas_statsd/counter_spec.rb
         | 
| 138 | 
            -
            - spec/canvas_statsd/default_tracking_spec.rb
         | 
| 139 159 | 
             
            - spec/canvas_statsd/null_logger_spec.rb
         | 
| 140 160 | 
             
            - spec/canvas_statsd/request_logger_spec.rb
         | 
| 141 161 | 
             
            - spec/canvas_statsd/request_stat_spec.rb
         | 
| 162 | 
            +
            - spec/canvas_statsd/request_tracking_spec.rb
         | 
| 142 163 | 
             
            - spec/canvas_statsd/sql_tracker_spec.rb
         | 
| 143 164 | 
             
            - spec/canvas_statsd/statsd_spec.rb
         | 
| 144 165 | 
             
            - spec/spec_helper.rb
         | 
| @@ -1,53 +0,0 @@ | |
| 1 | 
            -
            require 'spec_helper'
         | 
| 2 | 
            -
            require 'aroi' # ensure aroi is loaded (its only conditionally loaded by default)
         | 
| 3 | 
            -
             | 
| 4 | 
            -
            describe CanvasStatsd::DefaultTracking do
         | 
| 5 | 
            -
             | 
| 6 | 
            -
              describe '#track_default_metrics' do
         | 
| 7 | 
            -
                it 'should track timing, sql, and active_record by default' do
         | 
| 8 | 
            -
                  expect(CanvasStatsd::DefaultTracking).to receive(:track_timing)
         | 
| 9 | 
            -
                  expect(CanvasStatsd::DefaultTracking).to receive(:track_sql)
         | 
| 10 | 
            -
                  expect(CanvasStatsd::DefaultTracking).to receive(:track_active_record)
         | 
| 11 | 
            -
                  CanvasStatsd::DefaultTracking.track_default_metrics
         | 
| 12 | 
            -
                end
         | 
| 13 | 
            -
             | 
| 14 | 
            -
                it 'should not track sql when sql: false option' do
         | 
| 15 | 
            -
                  expect(CanvasStatsd::DefaultTracking).not_to receive(:track_sql)
         | 
| 16 | 
            -
                  CanvasStatsd::DefaultTracking.track_default_metrics sql: false
         | 
| 17 | 
            -
                end
         | 
| 18 | 
            -
             | 
| 19 | 
            -
                it 'should not track active_record when active_record: false option' do
         | 
| 20 | 
            -
                  expect(CanvasStatsd::DefaultTracking).not_to receive(:track_active_record)
         | 
| 21 | 
            -
                  CanvasStatsd::DefaultTracking.track_default_metrics active_record: false
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                it 'should not track cache when cache: false option' do
         | 
| 25 | 
            -
                  expect(CanvasStatsd::DefaultTracking).not_to receive(:track_cache)
         | 
| 26 | 
            -
                  CanvasStatsd::DefaultTracking.track_default_metrics cache: false
         | 
| 27 | 
            -
                end
         | 
| 28 | 
            -
             | 
| 29 | 
            -
                it 'should delegate log messages to the optional logger' do
         | 
| 30 | 
            -
                  log_double = double()
         | 
| 31 | 
            -
                  expect(log_double).to receive(:info)
         | 
| 32 | 
            -
                  CanvasStatsd::DefaultTracking.track_default_metrics logger: log_double
         | 
| 33 | 
            -
                  CanvasStatsd::DefaultTracking.finalize_processing('name', 1000, 10001, 1234, {})
         | 
| 34 | 
            -
                end
         | 
| 35 | 
            -
              end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
              describe '#track_active_record' do
         | 
| 38 | 
            -
                it 'should turn on active record instrumentation' do
         | 
| 39 | 
            -
                  expect(CanvasStatsd::DefaultTracking).to receive(:instrument_active_record_creation)
         | 
| 40 | 
            -
                  CanvasStatsd::DefaultTracking.send(:track_active_record)
         | 
| 41 | 
            -
                end
         | 
| 42 | 
            -
              end
         | 
| 43 | 
            -
             | 
| 44 | 
            -
              describe '#subscribe' do
         | 
| 45 | 
            -
                it 'should subscribe via ActiveSupport::Notifications' do
         | 
| 46 | 
            -
                  target = double()
         | 
| 47 | 
            -
                  CanvasStatsd::DefaultTracking.subscribe(/test\.notification/) {|*args| target.callback(*args)}
         | 
| 48 | 
            -
                  expect(target).to receive(:callback)
         | 
| 49 | 
            -
                  ActiveSupport::Notifications.instrument('test.notification') {}
         | 
| 50 | 
            -
                end
         | 
| 51 | 
            -
              end
         | 
| 52 | 
            -
             | 
| 53 | 
            -
            end
         |