canvas_statsd 1.0.8 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
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