From 6502c7db5a84e0b8ff62c0632886ef83de22594a Mon Sep 17 00:00:00 2001 From: Willem van Bergen Date: Mon, 19 Jun 2017 08:56:07 -0400 Subject: [PATCH 1/3] Use Module builder pattern for method instrumentation. --- lib/statsd/instrument.rb | 5 +++ lib/statsd/instrument/method_counter.rb | 35 +++++++++++++++++++ test/method_counter_test.rb | 45 +++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100644 lib/statsd/instrument/method_counter.rb create mode 100644 test/method_counter_test.rb diff --git a/lib/statsd/instrument.rb b/lib/statsd/instrument.rb index 92de80d4..bd964f0b 100644 --- a/lib/statsd/instrument.rb +++ b/lib/statsd/instrument.rb @@ -206,6 +206,10 @@ def statsd_remove_measure(method, name) remove_from_method(method, name, :measure) end + def self.count_method(method_name, **kwargs) + StatsD::Instrument::MethodCounter.new(method_name, **kwargs) + end + private def statsd_instrumentation_for(method, name, action) @@ -403,5 +407,6 @@ def collect_metric(options) require 'statsd/instrument/helpers' require 'statsd/instrument/assertions' require 'statsd/instrument/metric_expectation' +require 'statsd/instrument/method_counter' require 'statsd/instrument/matchers' if defined?(::RSpec) require 'statsd/instrument/railtie' if defined?(Rails) diff --git a/lib/statsd/instrument/method_counter.rb b/lib/statsd/instrument/method_counter.rb new file mode 100644 index 00000000..c9bf19b6 --- /dev/null +++ b/lib/statsd/instrument/method_counter.rb @@ -0,0 +1,35 @@ +module StatsD + module Instrument + class MethodCounter < Module + attr_reader :method_name + + def initialize(method_name, metric_name: nil, sample_rate: 1.0, value: 1, tags: []) + @method_name = method_name.to_sym + + metric = StatsD::Instrument::Metric.new( + type: :c, + name: metric_name || generate_metric_name, + value: value, + sample_rate: sample_rate, + tags: StatsD::Instrument::Metric.normalize_tags(tags), + ) + + define_method(method_name) do |*args, &block| + begin + super(*args, &block) + ensure + StatsD.backend.collect_metric(metric) + end + end + end + + def generate_metric_name + method_name.to_s + end + + def inspect + "#<#{self.class.name}[#{method_name.inspect}]>" + end + end + end +end diff --git a/test/method_counter_test.rb b/test/method_counter_test.rb new file mode 100644 index 00000000..3746e852 --- /dev/null +++ b/test/method_counter_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' + +class MethodCounterTest < Minitest::Test + include StatsD::Instrument::Assertions + + class ToBeInstrumented + prepend StatsD::Instrument.count_method(:to_be_counted) + + def to_be_counted + :return_value + end + + def inspect + 'original_inspect' + end + + def self.to_be_counted_too + :return_value + end + end + + def test_instrumented_method_increments_statsd_counter_when_called + assert_statsd_increment('to_be_counted') do + return_value = ToBeInstrumented.new.to_be_counted + assert_equal :return_value, return_value + end + end + + def test_count_method_ancestors + counter_module = ToBeInstrumented.ancestors.first + assert_instance_of StatsD::Instrument::MethodCounter, counter_module + assert_equal :to_be_counted, counter_module.method_name + assert_equal '#', counter_module.inspect + end + + def test_no_littering_in_instrumented_class + refute_includes ToBeInstrumented.new.methods, :count_method + refute_includes ToBeInstrumented.new.methods, :method_name + refute_includes ToBeInstrumented.methods, :count_method + refute_includes ToBeInstrumented.methods, :method_name + + assert_equal 'original_inspect', ToBeInstrumented.new.inspect + assert_equal 'MethodCounterTest::ToBeInstrumented', ToBeInstrumented.inspect + end +end From fe082faf523216c69ad93f7b4e97e3c9457166f4 Mon Sep 17 00:00:00 2001 From: Willem van Bergen Date: Tue, 20 Jun 2017 07:20:19 -0400 Subject: [PATCH 2/3] Test singleton_class behaviour as well. --- test/method_counter_test.rb | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/test/method_counter_test.rb b/test/method_counter_test.rb index 3746e852..59302ab5 100644 --- a/test/method_counter_test.rb +++ b/test/method_counter_test.rb @@ -4,14 +4,12 @@ class MethodCounterTest < Minitest::Test include StatsD::Instrument::Assertions class ToBeInstrumented - prepend StatsD::Instrument.count_method(:to_be_counted) - def to_be_counted :return_value end def inspect - 'original_inspect' + '#' end def self.to_be_counted_too @@ -19,6 +17,9 @@ def self.to_be_counted_too end end + ToBeInstrumented.prepend StatsD::Instrument.count_method(:to_be_counted) + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:to_be_counted_too) + def test_instrumented_method_increments_statsd_counter_when_called assert_statsd_increment('to_be_counted') do return_value = ToBeInstrumented.new.to_be_counted @@ -26,20 +27,34 @@ def test_instrumented_method_increments_statsd_counter_when_called end end - def test_count_method_ancestors + def test_instrumented_class_method_increments_statsd_counter_when_called + assert_statsd_increment('to_be_counted_too') do + return_value = ToBeInstrumented.to_be_counted_too + assert_equal :return_value, return_value + end + end + + def test_instance_method_ancestors counter_module = ToBeInstrumented.ancestors.first assert_instance_of StatsD::Instrument::MethodCounter, counter_module assert_equal :to_be_counted, counter_module.method_name assert_equal '#', counter_module.inspect end + def test_singleton_class_method_ancestors + counter_module = ToBeInstrumented.singleton_class.ancestors.first + assert_instance_of StatsD::Instrument::MethodCounter, counter_module + assert_equal :to_be_counted_too, counter_module.method_name + assert_equal '#', counter_module.inspect + end + def test_no_littering_in_instrumented_class refute_includes ToBeInstrumented.new.methods, :count_method refute_includes ToBeInstrumented.new.methods, :method_name refute_includes ToBeInstrumented.methods, :count_method refute_includes ToBeInstrumented.methods, :method_name - assert_equal 'original_inspect', ToBeInstrumented.new.inspect + assert_equal '#', ToBeInstrumented.new.inspect assert_equal 'MethodCounterTest::ToBeInstrumented', ToBeInstrumented.inspect end end From 0d950cb1cbf0d3c7f859f1778790be071e29777d Mon Sep 17 00:00:00 2001 From: Maxime Bedard Date: Tue, 7 Nov 2017 15:22:16 -0500 Subject: [PATCH 3/3] Add MethodMeasurer and make sure method visibility is preserved --- Gemfile | 2 + lib/statsd/instrument.rb | 54 +++++-- lib/statsd/instrument/method_counter.rb | 64 ++++++-- lib/statsd/instrument/method_measurer.rb | 28 ++++ lib/statsd/instrument/metric.rb | 10 +- test/method_counter_test.rb | 143 +++++++++++++++- test/method_measurer_test.rb | 197 +++++++++++++++++++++++ 7 files changed, 468 insertions(+), 30 deletions(-) create mode 100644 lib/statsd/instrument/method_measurer.rb create mode 100644 test/method_measurer_test.rb diff --git a/Gemfile b/Gemfile index 3be9c3cd..51b38168 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,4 @@ source "https://rubygems.org" gemspec + +gem "pry-byebug" diff --git a/lib/statsd/instrument.rb b/lib/statsd/instrument.rb index bd964f0b..7b8d7e74 100644 --- a/lib/statsd/instrument.rb +++ b/lib/statsd/instrument.rb @@ -56,19 +56,21 @@ def self.generate_metric_name(metric_name, callee, *args) metric_name.respond_to?(:call) ? metric_name.call(callee, args).gsub('::', '.') : metric_name.gsub('::', '.') end + def self.duration + start = current_timestamp + yield + current_timestamp - start + end + if Process.respond_to?(:clock_gettime) # @private - def self.duration - start = Process.clock_gettime(Process::CLOCK_MONOTONIC) - yield - Process.clock_gettime(Process::CLOCK_MONOTONIC) - start + def self.current_timestamp + Process.clock_gettime(Process::CLOCK_MONOTONIC) end else # @private - def self.duration - start = Time.now - yield - Time.now - start + def self.current_timestamp + Time.now end end @@ -206,12 +208,43 @@ def statsd_remove_measure(method, name) remove_from_method(method, name, :measure) end - def self.count_method(method_name, **kwargs) - StatsD::Instrument::MethodCounter.new(method_name, **kwargs) + def self.count_method( + method_name, + on_success: DEFAULT_ON_SUCCESS, + on_exception: DEFAULT_ON_EXCEPTION, + **metric_options + ) + metric = StatsD::Instrument::Metric.new(metric_options.merge(type: :c)) + + StatsD::Instrument::MethodCounter.new( + method_name, + metric, + on_success: on_success, + on_exception: on_exception, + ) + end + + def self.measure_method( + method_name, + on_success: DEFAULT_ON_SUCCESS, + on_exception: DEFAULT_ON_EXCEPTION, + **metric_options + ) + metric = StatsD::Instrument::Metric.new(metric_options.merge(type: :ms, value: 0)) + + StatsD::Instrument::MethodMeasurer.new( + method_name, + metric, + on_success: on_success, + on_exception: on_exception, + ) end private + DEFAULT_ON_SUCCESS = -> (_metric, _result) {} + DEFAULT_ON_EXCEPTION = -> (metric, _ex) { metric.discard } + def statsd_instrumentation_for(method, name, action) unless statsd_instrumentations.key?([method, name, action]) mod = Module.new do @@ -408,5 +441,6 @@ def collect_metric(options) require 'statsd/instrument/assertions' require 'statsd/instrument/metric_expectation' require 'statsd/instrument/method_counter' +require 'statsd/instrument/method_measurer' require 'statsd/instrument/matchers' if defined?(::RSpec) require 'statsd/instrument/railtie' if defined?(Rails) diff --git a/lib/statsd/instrument/method_counter.rb b/lib/statsd/instrument/method_counter.rb index c9bf19b6..9b50f7c3 100644 --- a/lib/statsd/instrument/method_counter.rb +++ b/lib/statsd/instrument/method_counter.rb @@ -1,34 +1,66 @@ module StatsD module Instrument class MethodCounter < Module - attr_reader :method_name + attr_reader(:method_name) - def initialize(method_name, metric_name: nil, sample_rate: 1.0, value: 1, tags: []) - @method_name = method_name.to_sym + def initialize( + method_name, + metric, + on_success:, + on_exception: + ) + @method_name = method_name + @metric = metric + @on_success = on_success + @on_exception = on_exception + end - metric = StatsD::Instrument::Metric.new( - type: :c, - name: metric_name || generate_metric_name, - value: value, - sample_rate: sample_rate, - tags: StatsD::Instrument::Metric.normalize_tags(tags), - ) + def prepend_features(mod) + super(mod) + preserve_visibility(mod, method_name) do + define_method_lifecycle(@metric, @on_success, @on_exception) + end + end + def inspect + "#<#{self.class.name}[#{method_name.inspect}]>" + end + + private + + def define_method_lifecycle(metric, on_success, on_exception) define_method(method_name) do |*args, &block| begin - super(*args, &block) + result = super(*args, &block) + on_success.call(metric, result) + result + rescue => ex + begin + on_exception.call(metric, ex) + ensure + raise(ex) + end ensure - StatsD.backend.collect_metric(metric) + StatsD.backend.collect_metric(metric) unless metric.discarded? end end end - def generate_metric_name - method_name.to_s + def preserve_visibility(mod, method_name) + original_visibility = method_visibility(mod, method_name) + yield + __send__(original_visibility, method_name) end - def inspect - "#<#{self.class.name}[#{method_name.inspect}]>" + def method_visibility(mod, method) + case + when mod.private_method_defined?(method) + :private + when mod.protected_method_defined?(method) + :protected + else + :public + end end end end diff --git a/lib/statsd/instrument/method_measurer.rb b/lib/statsd/instrument/method_measurer.rb new file mode 100644 index 00000000..bb3170c7 --- /dev/null +++ b/lib/statsd/instrument/method_measurer.rb @@ -0,0 +1,28 @@ +module StatsD + module Instrument + class MethodMeasurer < MethodCounter + private + + def define_method_lifecycle(metric, on_success, on_exception) + define_method(method_name) do |*args, &block| + start = StatsD::Instrument.current_timestamp + begin + result = super(*args, &block) + metric.value = StatsD::Instrument.current_timestamp - start + on_success.call(metric, result) + result + rescue => ex + metric.value = StatsD::Instrument.current_timestamp - start + begin + on_exception.call(metric, ex) + ensure + raise(ex) + end + ensure + StatsD.backend.collect_metric(metric) unless metric.discarded? + end + end + end + end + end +end diff --git a/lib/statsd/instrument/metric.rb b/lib/statsd/instrument/metric.rb index 82e8ae2a..0f2807b9 100644 --- a/lib/statsd/instrument/metric.rb +++ b/lib/statsd/instrument/metric.rb @@ -47,7 +47,7 @@ def initialize(options = {}) @type = options[:type] or raise ArgumentError, "Metric :type is required." @name = options[:name] or raise ArgumentError, "Metric :name is required." @name = StatsD.prefix ? "#{StatsD.prefix}.#{@name}" : @name unless options[:no_prefix] - + @discarded = false @value = options[:value] || default_value @sample_rate = options[:sample_rate] || StatsD.default_sample_rate @tags = StatsD::Instrument::Metric.normalize_tags(options[:tags]) @@ -82,6 +82,14 @@ def inspect "#" end + def discard + @discarded = true + end + + def discarded? + !!@discarded + end + # The metric types that are supported by this library. Note that every StatsD server # implementation only supports a subset of them. TYPES = { diff --git a/test/method_counter_test.rb b/test/method_counter_test.rb index 59302ab5..51722887 100644 --- a/test/method_counter_test.rb +++ b/test/method_counter_test.rb @@ -4,21 +4,84 @@ class MethodCounterTest < Minitest::Test include StatsD::Instrument::Assertions class ToBeInstrumented + class << self + def to_be_counted_too + :return_value + end + + def to_raise_too + raise "💥" + end + + def to_raise_too_with_suffix + raise StandardError.new("boom!") + end + + def to_be_counted_too_with_suffix + :return_value + end + + protected + + def i_am_protected_too + :return_value + end + + private + + def i_am_private_too + :return_value + end + end + def to_be_counted :return_value end + def to_be_counted_with_suffix + :return_value + end + + def to_raise + raise "💥" + end + + def to_raise_with_suffix + raise StandardError.new("boom!") + end + def inspect '#' end - def self.to_be_counted_too + protected + + def i_am_protected + :return_value + end + + private + + def i_am_private :return_value end end - ToBeInstrumented.prepend StatsD::Instrument.count_method(:to_be_counted) - ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:to_be_counted_too) + SuffixedExcpetionHandler = -> (metric, ex) { metric.name = "#{metric.name}.#{ex.class}" } + SuffixedHandler = -> (metric, result) { metric.name = "#{metric.name}.#{result}" } + + ToBeInstrumented.prepend StatsD::Instrument.count_method(:i_am_protected, name: "i_am_protected") + ToBeInstrumented.prepend StatsD::Instrument.count_method(:i_am_private, name: "i_am_private") + ToBeInstrumented.prepend StatsD::Instrument.count_method(:to_raise, name: "to_raise") + ToBeInstrumented.prepend StatsD::Instrument.count_method(:to_raise_with_suffix, name: "to_raise_with_suffix", on_exception: SuffixedExcpetionHandler) + ToBeInstrumented.prepend StatsD::Instrument.count_method(:to_be_counted_with_suffix, name: "to_be_counted_with_suffix", on_success: SuffixedHandler) + ToBeInstrumented.prepend StatsD::Instrument.count_method(:to_be_counted, name: "to_be_counted") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:i_am_protected, name: "i_am_protected_too") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:i_am_private, name: "i_am_private_too") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:to_raise_too, name: "to_raise_too") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:to_raise_too_with_suffix, name: "to_raise_too_with_suffix", on_exception: SuffixedExcpetionHandler) + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:to_be_counted_too_with_suffix, name: "to_be_counted_too_with_suffix", on_success: SuffixedHandler) + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.count_method(:to_be_counted_too, name: "to_be_counted_too") def test_instrumented_method_increments_statsd_counter_when_called assert_statsd_increment('to_be_counted') do @@ -27,6 +90,29 @@ def test_instrumented_method_increments_statsd_counter_when_called end end + def test_instrumented_method_increments_statsd_counter_when_called_and_appends_suffix + assert_statsd_increment('to_be_counted_with_suffix.return_value') do + return_value = ToBeInstrumented.new.to_be_counted_with_suffix + assert_equal :return_value, return_value + end + end + + def test_instrumented_method_is_discarded_on_exceptions + assert_no_statsd_calls do + assert_raises do + ToBeInstrumented.new.to_raise + end + end + end + + def test_instrumented_method_add_suffix_on_exceptions + assert_statsd_increment('to_raise_with_suffix.StandardError') do + assert_raises do + ToBeInstrumented.new.to_raise_with_suffix + end + end + end + def test_instrumented_class_method_increments_statsd_counter_when_called assert_statsd_increment('to_be_counted_too') do return_value = ToBeInstrumented.to_be_counted_too @@ -34,6 +120,29 @@ def test_instrumented_class_method_increments_statsd_counter_when_called end end + def test_instrumented_class_method_increments_statsd_counter_when_called_and_appends_suffix + assert_statsd_increment('to_be_counted_too_with_suffix.return_value') do + return_value = ToBeInstrumented.to_be_counted_too_with_suffix + assert_equal :return_value, return_value + end + end + + def test_instrumented_class_method_is_discarded_on_exceptions + assert_no_statsd_calls do + assert_raises do + ToBeInstrumented.to_raise_too + end + end + end + + def test_instrumented_class_method_add_suffix_on_exceptions + assert_statsd_increment('to_raise_too_with_suffix.StandardError') do + assert_raises do + ToBeInstrumented.to_raise_too_with_suffix + end + end + end + def test_instance_method_ancestors counter_module = ToBeInstrumented.ancestors.first assert_instance_of StatsD::Instrument::MethodCounter, counter_module @@ -57,4 +166,32 @@ def test_no_littering_in_instrumented_class assert_equal '#', ToBeInstrumented.new.inspect assert_equal 'MethodCounterTest::ToBeInstrumented', ToBeInstrumented.inspect end + + def test_instance_method_preserved_visibility + assert ToBeInstrumented.private_method_defined?(:i_am_private) + refute ToBeInstrumented.protected_method_defined?(:i_am_private) + refute ToBeInstrumented.public_method_defined?(:i_am_private) + + refute ToBeInstrumented.private_method_defined?(:i_am_protected) + assert ToBeInstrumented.protected_method_defined?(:i_am_protected) + refute ToBeInstrumented.public_method_defined?(:i_am_protected) + + refute ToBeInstrumented.private_method_defined?(:to_be_counted) + refute ToBeInstrumented.protected_method_defined?(:to_be_counted) + assert ToBeInstrumented.public_method_defined?(:to_be_counted) + end + + def test_class_methods_preserved_visibility + assert ToBeInstrumented.singleton_class.private_method_defined?(:i_am_private_too) + refute ToBeInstrumented.singleton_class.protected_method_defined?(:i_am_private_too) + refute ToBeInstrumented.singleton_class.public_method_defined?(:i_am_private_too) + + refute ToBeInstrumented.singleton_class.private_method_defined?(:i_am_protected_too) + assert ToBeInstrumented.singleton_class.protected_method_defined?(:i_am_protected_too) + refute ToBeInstrumented.singleton_class.public_method_defined?(:i_am_protected_too) + + refute ToBeInstrumented.singleton_class.private_method_defined?(:to_be_counted_too) + refute ToBeInstrumented.singleton_class.protected_method_defined?(:to_be_counted_too) + assert ToBeInstrumented.singleton_class.public_method_defined?(:to_be_counted_too) + end end diff --git a/test/method_measurer_test.rb b/test/method_measurer_test.rb new file mode 100644 index 00000000..fe8b201d --- /dev/null +++ b/test/method_measurer_test.rb @@ -0,0 +1,197 @@ +require 'test_helper' + +class MethodMeasurerTest < Minitest::Test + include StatsD::Instrument::Assertions + + class ToBeInstrumented + class << self + def to_be_measured_too + :return_value + end + + def to_raise_too + raise "💥" + end + + def to_raise_too_with_suffix + raise StandardError.new("boom!") + end + + def to_be_measured_too_with_suffix + :return_value + end + + protected + + def i_am_protected_too + :return_value + end + + private + + def i_am_private_too + :return_value + end + end + + def to_be_measured + :return_value + end + + def to_be_measured_with_suffix + :return_value + end + + def to_raise + raise "💥" + end + + def to_raise_with_suffix + raise StandardError.new("boom!") + end + + def inspect + '#' + end + + protected + + def i_am_protected + :return_value + end + + private + + def i_am_private + :return_value + end + end + + SuffixedExcpetionHandler = -> (metric, ex) { metric.name = "#{metric.name}.#{ex.class}" } + SuffixedHandler = -> (metric, result) { metric.name = "#{metric.name}.#{result}" } + + ToBeInstrumented.prepend StatsD::Instrument.measure_method(:i_am_protected, name: "i_am_protected") + ToBeInstrumented.prepend StatsD::Instrument.measure_method(:i_am_private, name: "i_am_private") + ToBeInstrumented.prepend StatsD::Instrument.measure_method(:to_raise, name: "to_raise") + ToBeInstrumented.prepend StatsD::Instrument.measure_method(:to_raise_with_suffix, name: "to_raise_with_suffix", on_exception: SuffixedExcpetionHandler) + ToBeInstrumented.prepend StatsD::Instrument.measure_method(:to_be_measured_with_suffix, name: "to_be_measured_with_suffix", on_success: SuffixedHandler) + ToBeInstrumented.prepend StatsD::Instrument.measure_method(:to_be_measured, name: "to_be_measured") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.measure_method(:i_am_protected, name: "i_am_protected_too") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.measure_method(:i_am_private, name: "i_am_private_too") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.measure_method(:to_raise_too, name: "to_raise_too") + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.measure_method(:to_raise_too_with_suffix, name: "to_raise_too_with_suffix", on_exception: SuffixedExcpetionHandler) + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.measure_method(:to_be_measured_too_with_suffix, name: "to_be_measured_too_with_suffix", on_success: SuffixedHandler) + ToBeInstrumented.singleton_class.prepend StatsD::Instrument.measure_method(:to_be_measured_too, name: "to_be_measured_too") + + def test_instrumented_method_increments_statsd_counter_when_called + assert_statsd_measure('to_be_measured') do + return_value = ToBeInstrumented.new.to_be_measured + assert_equal :return_value, return_value + end + end + + def test_instrumented_method_increments_statsd_counter_when_called_and_appends_suffix + assert_statsd_measure('to_be_measured_with_suffix.return_value') do + return_value = ToBeInstrumented.new.to_be_measured_with_suffix + assert_equal :return_value, return_value + end + end + + def test_instrumented_method_is_discarded_on_exceptions + assert_no_statsd_calls do + assert_raises do + ToBeInstrumented.new.to_raise + end + end + end + + def test_instrumented_method_add_suffix_on_exceptions + assert_statsd_measure('to_raise_with_suffix.StandardError') do + assert_raises do + ToBeInstrumented.new.to_raise_with_suffix + end + end + end + + def test_instrumented_class_method_increments_statsd_counter_when_called + assert_statsd_measure('to_be_measured_too') do + return_value = ToBeInstrumented.to_be_measured_too + assert_equal :return_value, return_value + end + end + + def test_instrumented_class_method_increments_statsd_counter_when_called_and_appends_suffix + assert_statsd_measure('to_be_measured_too_with_suffix.return_value') do + return_value = ToBeInstrumented.to_be_measured_too_with_suffix + assert_equal :return_value, return_value + end + end + + def test_instrumented_class_method_is_discarded_on_exceptions + assert_no_statsd_calls do + assert_raises do + ToBeInstrumented.to_raise_too + end + end + end + + def test_instrumented_class_method_add_suffix_on_exceptions + assert_statsd_measure('to_raise_too_with_suffix.StandardError') do + assert_raises do + ToBeInstrumented.to_raise_too_with_suffix + end + end + end + + def test_instance_method_ancestors + measurer_module = ToBeInstrumented.ancestors.first + assert_instance_of StatsD::Instrument::MethodMeasurer, measurer_module + assert_equal :to_be_measured, measurer_module.method_name + assert_equal '#', measurer_module.inspect + end + + def test_singleton_class_method_ancestors + measurer_module = ToBeInstrumented.singleton_class.ancestors.first + assert_instance_of StatsD::Instrument::MethodMeasurer, measurer_module + assert_equal :to_be_measured_too, measurer_module.method_name + assert_equal '#', measurer_module.inspect + end + + def test_no_littering_in_instrumented_class + refute_includes ToBeInstrumented.new.methods, :count_method + refute_includes ToBeInstrumented.new.methods, :method_name + refute_includes ToBeInstrumented.methods, :count_method + refute_includes ToBeInstrumented.methods, :method_name + + assert_equal '#', ToBeInstrumented.new.inspect + assert_equal 'MethodMeasurerTest::ToBeInstrumented', ToBeInstrumented.inspect + end + + def test_instance_method_preserved_visibility + assert ToBeInstrumented.private_method_defined?(:i_am_private) + refute ToBeInstrumented.protected_method_defined?(:i_am_private) + refute ToBeInstrumented.public_method_defined?(:i_am_private) + + refute ToBeInstrumented.private_method_defined?(:i_am_protected) + assert ToBeInstrumented.protected_method_defined?(:i_am_protected) + refute ToBeInstrumented.public_method_defined?(:i_am_protected) + + refute ToBeInstrumented.private_method_defined?(:to_be_measured) + refute ToBeInstrumented.protected_method_defined?(:to_be_measured) + assert ToBeInstrumented.public_method_defined?(:to_be_measured) + end + + def test_class_methods_preserved_visibility + assert ToBeInstrumented.singleton_class.private_method_defined?(:i_am_private_too) + refute ToBeInstrumented.singleton_class.protected_method_defined?(:i_am_private_too) + refute ToBeInstrumented.singleton_class.public_method_defined?(:i_am_private_too) + + refute ToBeInstrumented.singleton_class.private_method_defined?(:i_am_protected_too) + assert ToBeInstrumented.singleton_class.protected_method_defined?(:i_am_protected_too) + refute ToBeInstrumented.singleton_class.public_method_defined?(:i_am_protected_too) + + refute ToBeInstrumented.singleton_class.private_method_defined?(:to_be_measured_too) + refute ToBeInstrumented.singleton_class.protected_method_defined?(:to_be_measured_too) + assert ToBeInstrumented.singleton_class.public_method_defined?(:to_be_measured_too) + end +end