diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..5052887 --- /dev/null +++ b/.rspec @@ -0,0 +1 @@ +--color \ No newline at end of file diff --git a/Gemfile b/Gemfile index e789b91..3cbf1a0 100644 --- a/Gemfile +++ b/Gemfile @@ -11,6 +11,8 @@ group :development do gem "rake" gem "ruby-debug", :platforms => :ruby_18 gem "debugger", :platforms => :ruby_19 + gem "mongo_mapper" + gem "bson_ext" end gem "codeclimate-test-reporter", group: :test, require: nil, github: "codeclimate/ruby-test-reporter" diff --git a/lib/oink/instrumentation.rb b/lib/oink/instrumentation.rb index de800f0..cd0b6ef 100644 --- a/lib/oink/instrumentation.rb +++ b/lib/oink/instrumentation.rb @@ -1,2 +1,3 @@ require 'oink/instrumentation/active_record' -require 'oink/instrumentation/memory_snapshot' \ No newline at end of file +require 'oink/instrumentation/mongo_mapper' +require 'oink/instrumentation/memory_snapshot' diff --git a/lib/oink/instrumentation/mongo_mapper.rb b/lib/oink/instrumentation/mongo_mapper.rb new file mode 100644 index 0000000..407f05a --- /dev/null +++ b/lib/oink/instrumentation/mongo_mapper.rb @@ -0,0 +1,68 @@ +module Oink + def self.extended_mongo_mapper? + @oink_extended_mongo_mapper + end + + def self.extended_mongo_mapper! + @oink_extended_mongo_mapper = true + end + + def self.extend_mongo_mapper! + return if extended_mongo_mapper? + + ::MongoMapper::Document.instance_eval do + def included(klass) + super + + klass.class_eval do + class << self + alias_method :allocate_before_oink, :allocate + + def allocate + value = allocate_before_oink + Oink::Instrumentation::MongoMapper.increment_instance_type_count(self) + value + end + end + + alias_method :initialize_before_oink, :initialize + + def initialize(*args, &block) + value = initialize_before_oink(*args, &block) + Oink::Instrumentation::MongoMapper.increment_instance_type_count(self.class) + value + end + + Oink.extended_mongo_mapper! + end + end + end + end + + module Instrumentation + module MongoMapper + def self.reset_instance_type_count + self.instantiated_hash = {} + Thread.current['oink.mongomapper.instantiations_count'] = nil + end + + def self.increment_instance_type_count(klass) + self.instantiated_hash ||= {} + self.instantiated_hash[klass.name] ||= 0 + self.instantiated_hash[klass.name] += 1 + end + + def self.instantiated_hash + Thread.current['oink.mongomapper.instantiations'] ||= {} + end + + def self.instantiated_hash=(hsh) + Thread.current['oink.mongomapper.instantiations'] = hsh + end + + def self.total_objects_instantiated + Thread.current['oink.mongomapper.instantiations_count'] ||= self.instantiated_hash.values.sum + end + end + end +end \ No newline at end of file diff --git a/lib/oink/middleware.rb b/lib/oink/middleware.rb index 2b08b4f..56763a0 100644 --- a/lib/oink/middleware.rb +++ b/lib/oink/middleware.rb @@ -11,6 +11,7 @@ def initialize(app, options = {}) @instruments = options[:instruments] ? Array(options[:instruments]) : [:memory, :activerecord] Oink.extend_active_record! if @instruments.include?(:activerecord) + Oink.extend_mongo_mapper! if @instruments.include?(:mongomapper) end def call(env) @@ -19,6 +20,7 @@ def call(env) log_routing(env) log_memory log_activerecord + log_mongomapper log_completed [status, headers, body] end @@ -47,8 +49,17 @@ def log_activerecord if @instruments.include?(:activerecord) sorted_list = Oink::HashUtils.to_sorted_array(ActiveRecord::Base.instantiated_hash) sorted_list.unshift("Total: #{ActiveRecord::Base.total_objects_instantiated}") - @logger.info("Instantiation Breakdown: #{sorted_list.join(' | ')}") - reset_objects_instantiated + @logger.info("ActiveRecord Instantiation Breakdown: #{sorted_list.join(' | ')}") + reset_active_record_objects_instantiated + end + end + + def log_mongomapper + if @instruments.include?(:mongomapper) + sorted_list = Oink::HashUtils.to_sorted_array(Oink::Instrumentation::MongoMapper.instantiated_hash) + sorted_list.unshift("Total: #{Oink::Instrumentation::MongoMapper.total_objects_instantiated}") + @logger.info("MongoMapper Instantiation Breakdown: #{sorted_list.join(' | ')}") + reset_mongo_mapper_objects_instantiated end end @@ -62,9 +73,12 @@ def rails2_routing_info(env) env['action_controller.request.path_parameters'] end - def reset_objects_instantiated + def reset_active_record_objects_instantiated ActiveRecord::Base.reset_instance_type_count end + def reset_mongo_mapper_objects_instantiated + Oink::Instrumentation::MongoMapper.reset_instance_type_count + end end end diff --git a/spec/helpers/database.rb b/spec/helpers/database.rb index 9895d6e..9260618 100644 --- a/spec/helpers/database.rb +++ b/spec/helpers/database.rb @@ -1,4 +1,5 @@ require 'active_record' +require 'mongo_mapper' def setup_memory_database ActiveRecord::Base.establish_connection( @@ -17,4 +18,7 @@ def setup_memory_database t.string "location" end end + + MongoMapper.connection = Mongo::Connection.new('localhost') + MongoMapper.database = "oink" end \ No newline at end of file diff --git a/spec/oink/middleware_spec.rb b/spec/oink/middleware_spec.rb index 7f04bd7..8be453f 100644 --- a/spec/oink/middleware_spec.rb +++ b/spec/oink/middleware_spec.rb @@ -16,6 +16,15 @@ def call(env) Pig.create(:name => "Babe") Pen.create(:location => "Backyard") Pig.first + + when "/mm_no_pigs" + when "/mm_two_pigs" + MMPig.create(:name => "Babe") + MMPig.first + when "/mm_two_pigs_in_a_pen" + MMPig.create(:name => "Babe") + MMPen.create(:location => "Backyard") + MMPig.first end [200, {}, ""] end @@ -58,25 +67,78 @@ def call(env) end end - it "reports 0 totals" do - get "/no_pigs" - log_output.string.should include("Instantiation Breakdown: Total: 0") - end + describe "for active record" do + let(:app) { Oink::Middleware.new(SampleApplication.new, :logger => logger, :instruments => [:memory, :activerecord]) } - it "reports totals first even if it's a tie" do - get "/two_pigs" - log_output.string.should include("Instantiation Breakdown: Total: 2 | Pig: 2") - end + it "reports 0 totals" do + get "/no_pigs" + log_output.string.should include("ActiveRecord Instantiation Breakdown: Total: 0") + end - it "reports pigs and pens instantiated" do - get "/two_pigs_in_a_pen" - log_output.string.should include("Instantiation Breakdown: Total: 3 | Pig: 2 | Pen: 1") - end + it "reports totals first even if it's a tie" do + get "/two_pigs" + log_output.string.should include("ActiveRecord Instantiation Breakdown: Total: 2 | Pig: 2") + end - it "logs memory usage" do - Oink::Instrumentation::MemorySnapshot.should_receive(:memory).and_return(4092) - get "/two_pigs_in_a_pen" - log_output.string.should include("Memory usage: 4092 | PID: #{$$}") + it "reports pigs and pens instantiated" do + get "/two_pigs_in_a_pen" + log_output.string.should include("ActiveRecord Instantiation Breakdown: Total: 3 | Pig: 2 | Pen: 1") + end + + it "logs memory usage" do + Oink::Instrumentation::MemorySnapshot.should_receive(:memory).and_return(4092) + get "/two_pigs_in_a_pen" + log_output.string.should include("Memory usage: 4092 | PID: #{$$}") + end end + describe "for mongo mapper" do + def define_class(class_name, &block) + if Object.const_defined?(class_name) + Object.send(:remove_const, class_name) + end + + Object.const_set(class_name, Class.new(&block)) + end + + def define_classes + define_class :MMPig do + include MongoMapper::Document + belongs_to :pen, :class_name => "MMPen" + end + + define_class :MMPen do + include MongoMapper::Document + end + end + + let(:app) { Oink::Middleware.new(SampleApplication.new, :logger => logger, :instruments => [:memory, :mongomapper]) } + + before :each do + define_classes + MMPig.delete_all + MMPen.delete_all + end + + it "reports 0 totals" do + get "/mm_no_pigs" + log_output.string.should include("MongoMapper Instantiation Breakdown: Total: 0") + end + + it "reports totals first even if it's a tie" do + get "/mm_two_pigs" + log_output.string.should include("MongoMapper Instantiation Breakdown: Total: 2 | MMPig: 2") + end + + it "reports pigs and pens instantiated" do + get "/mm_two_pigs_in_a_pen" + log_output.string.should include("MongoMapper Instantiation Breakdown: Total: 3 | MMPig: 2 | MMPen: 1") + end + + it "logs memory usage" do + Oink::Instrumentation::MemorySnapshot.should_receive(:memory).and_return(4092) + get "/mm_two_pigs_in_a_pen" + log_output.string.should include("Memory usage: 4092 | PID: #{$$}") + end + end end \ No newline at end of file diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 61705dd..cf93c2d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -15,9 +15,9 @@ config.before :suite do setup_memory_database + Pig = Class.new(ActiveRecord::Base) Pen = Class.new(ActiveRecord::Base) Pig.belongs_to :pen end - end