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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bf80962c746b4bd54e9a0e57a75764f33b6b35eb
4
- data.tar.gz: 733d4b3269e6a8b636451f60aa8b48027b0f7600
3
+ metadata.gz: 951dbaa395f7d79e76329d841ca6ce9aa8716a07
4
+ data.tar.gz: 2b9f6679bc1dd954fbe53ddf22fe43febde61380
5
5
  SHA512:
6
- metadata.gz: 51687134299407eb74454c51b07bf321af1a52908fb353ac131b173f19a3370f25eede3c0dceac2020af07e0d5fec2cb25351e0d0bc83e4086595abbbf5f37ef
7
- data.tar.gz: a2d02a7f7bdb17633639de33f99d283f22c21529729a17aff09d6ff0112379f3824948972c27c9de161ab249a068bbf2565c4f400e2b8cf954b4377366699481
6
+ metadata.gz: 9465805cde29e5584b03fd173fb1b9d08cd21892413c6e1c8a06daa5a6295b45ea32ef8acdcab8a05bb967e8f385b9013e8e98b54dcb9a1922f9d849f6734b8d
7
+ data.tar.gz: a2c901c2764ff4a5690a6704a0f68c28da6ea13f97b52a5237caaeb3c15ff38fa9d05509fb1a345bce2a0dfc278af3104f64b39d81e91eb0ddd828bcd09abdba
@@ -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
- def self.settings
18
- @settings || env_settings
19
- end
20
-
21
- def self.settings=(value)
22
- @settings = validate_settings(value)
23
- end
24
-
25
- def self.validate_settings(value)
26
- return nil if value.nil?
27
-
28
- validated = {}
29
- value.each do |k,v|
30
- if !VALID_SETTINGS.include?(k.to_sym)
31
- raise CanvasStatsd::ConfigurationError, "Invalid key: #{k}"
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
- validated[k.to_sym] = v
38
+
39
+ env_settings.merge(validated)
34
40
  end
35
-
36
- env_settings.merge(validated)
37
- end
38
-
39
- def self.env_settings(env=ENV)
40
- config = {
41
- host: env.fetch('CANVAS_STATSD_HOST', nil),
42
- port: env.fetch('CANVAS_STATSD_PORT', nil),
43
- namespace: env.fetch('CANVAS_STATSD_NAMESPACE', nil),
44
- append_hostname: env.fetch('CANVAS_STATSD_APPEND_HOSTNAME', nil),
45
- }
46
- config.delete_if {|k,v| v.nil?}
47
- convert_bool(config, :append_hostname)
48
- config[:host] ? config : {}
49
- end
50
-
51
- def self.convert_bool(hash, key)
52
- value = hash[key]
53
- return if value.nil?
54
- unless ['true', 'True', 'false', 'False', true, false].include?(value)
55
- message = "#{key} must be a boolean, or the string representation of a boolean, got: #{value}"
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[key] = 0
24
+ Thread.current[@tls_key] ||= 0
14
25
  end
15
26
 
16
27
  def track(name)
17
- Thread.current[key] += 1 if Thread.current[key] && accepted_name?(name)
28
+ Thread.current[@tls_key] += 1 if Thread.current[@tls_key] && accepted_name?(name)
18
29
  end
19
30
 
20
- def finalize_count
21
- final_count = count
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[key]
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
- @tracking_sql = true
34
- subscribe(/sql\.active_record/) {|*args| update_sql_count(*args)}
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
- instrument_active_record_creation
39
- @tracking_active_record = true
40
- subscribe(/instance\.active_record/) {|*args| update_active_record_count(*args)}
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
- @tracking_cache = true
45
- subscribe(/cache_read\.active_support/) {|*args| update_cache_read_count(*args)}
46
- end
21
+ return if @cache_read_counter
47
22
 
48
- def self.start_processing *args
49
- @sql_tracker.start
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
- def self.update_sql_count name, start, finish, id, payload
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 name, start, finish, id, payload
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 name, start, finish, id, payload
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
- VALUES_MAP.each do |k,v|
28
- value = request_stat.respond_to?(v) ? request_stat.send(v) : nil
29
- message += " (#{k}: #{"%.2f" % value})" if value
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
- @statsd = statsd
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
- if controller && action
21
- common_key = "request.#{controller}.#{action}"
22
- @statsd.timing("#{common_key}.total", ms)
23
- @statsd.timing("#{common_key}.view", view_runtime) if view_runtime
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 ms
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('sql_read_counter'))
10
- @write_counts = opts.fetch(:write_counter, CanvasStatsd::Counter.new('sql_write_counter'))
11
- @cache_counts = opts.fetch(:cache_counter, CanvasStatsd::Counter.new('sql_cache_counter'))
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].each(&:start)
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 num_reads
31
- read_counts.finalize_count
32
- end
33
-
34
- def num_writes
35
- write_counts.finalize_count
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.count).to eq 2
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.count).to eq 1
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 reset the current count to 0' do
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.count).to eq 0
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(:ms).and_return(100.21)
23
- allow(request_stat).to receive(:ar_count).and_return(24)
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
- it 'doesnt include nil stats' do
33
+
34
+ it 'includes exclusive_stats if there are any' do
28
35
  request_stat = double('request_stat')
29
- allow(request_stat).to receive(:ms).and_return(100.22)
30
- allow(request_stat).to receive(:ar_count).and_return(nil)
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.22)")
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(:ms).and_return(72.1)
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(:ms).and_return(72.1382928)
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(:ms).and_return(72.1348209)
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(:ms).and_return(100.2)
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 '#ms' do
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.ms).to eq 1000
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.ms).to eq 0
80
+ expect(rs.total).to eq 0
81
81
  rs = CanvasStatsd::RequestStat.new('name', 1, nil, 1111, {params: {}})
82
- expect(rs.ms).to eq 0
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.cache_read_count = 25
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.sql_read_count = 10
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.num_caches).to eq(0)
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(@subject.num_caches).to eq(1)
27
- expect(@subject.num_reads).to eq(0)
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(@subject.num_reads).to eq(1)
33
- expect(@subject.num_writes).to eq(0)
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(@subject.num_reads).to eq(1)
39
- expect(@subject.num_writes).to eq(0)
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.num_reads).to eq(0)
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(@subject.num_reads).to eq(0)
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: 1.0.8
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: 2016-12-30 00:00:00.000000000 Z
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.7
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