From 8858f65fe89da1008b90396c1fc3db943248e52e Mon Sep 17 00:00:00 2001 From: Jeff Kunkle Date: Fri, 12 Aug 2011 21:09:18 -0400 Subject: [PATCH] Add async store implementation --- lib/system_metrics.rb | 1 + lib/system_metrics/async_store.rb | 57 +++++++++++++++++++++++++ lib/system_metrics/config.rb | 2 +- spec/system_metrics/async_store_spec.rb | 50 ++++++++++++++++++++++ spec/system_metrics/store_spec.rb | 2 +- 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 lib/system_metrics/async_store.rb create mode 100644 spec/system_metrics/async_store_spec.rb diff --git a/lib/system_metrics.rb b/lib/system_metrics.rb index 637e943..494e525 100644 --- a/lib/system_metrics.rb +++ b/lib/system_metrics.rb @@ -4,6 +4,7 @@ module SystemMetrics autoload :Middleware, 'system_metrics/middleware' autoload :NestedEvent, 'system_metrics/nested_event' autoload :Store, 'system_metrics/store' + autoload :AsyncStore, 'system_metrics/async_store' autoload :Version, 'system_metrics/version' def self.collection_on diff --git a/lib/system_metrics/async_store.rb b/lib/system_metrics/async_store.rb new file mode 100644 index 0000000..73f1305 --- /dev/null +++ b/lib/system_metrics/async_store.rb @@ -0,0 +1,57 @@ +require 'thread' + +module SystemMetrics + class AsyncStore + + # An instrumenter that does not send notifications. This is used in the + # AsyncStore so saving events does not send any notifications, not even + # for logging. + class VoidInstrumenter < ::ActiveSupport::Notifications::Instrumenter + def instrument(name, payload={}) + yield(payload) if block_given? + end + end + + def initialize + @queue = Queue.new + @thread = Thread.new do + set_void_instrumenter + consume + end + end + + def save(events) + @queue << events + end + + protected + def set_void_instrumenter + Thread.current[:"instrumentation_#{notifier.object_id}"] = VoidInstrumenter.new(notifier) + end + + def notifier + ActiveSupport::Notifications.notifier + end + + def consume + while events = @queue.pop + root_event = SystemMetrics::NestedEvent.arrange(events, :presort => false) + root_model = create_metric(root_event) + root_model.update_attributes(:request_id => root_model.id) + save_tree(root_event.children, root_model.id, root_model.id) + end + end + + def save_tree(events, request_id, parent_id) + events.each do |event| + model = create_metric(event, :request_id => request_id, :parent_id => parent_id) + save_tree(event.children, request_id, model.id) + end + end + + def create_metric(event, merge_params={}) + SystemMetrics::Metric.create(event.to_hash.merge(merge_params)) + end + + end +end diff --git a/lib/system_metrics/config.rb b/lib/system_metrics/config.rb index a08eae6..265ade4 100644 --- a/lib/system_metrics/config.rb +++ b/lib/system_metrics/config.rb @@ -3,7 +3,7 @@ class Config attr_accessor :store, :instruments, :notification_exclude_patterns, :path_exclude_patterns def initialize - self.store = SystemMetrics::Store.new + self.store = SystemMetrics::AsyncStore.new self.notification_exclude_patterns = [] self.path_exclude_patterns = [/system\/metrics/, /system_metrics/] self.instruments = [ diff --git a/spec/system_metrics/async_store_spec.rb b/spec/system_metrics/async_store_spec.rb new file mode 100644 index 0000000..445fe1f --- /dev/null +++ b/spec/system_metrics/async_store_spec.rb @@ -0,0 +1,50 @@ +require File.dirname(__FILE__) + '/../spec_helper' +require 'system_metrics/metric' + +describe SystemMetrics::AsyncStore do + include NotificationsSupport + + describe '#save' do + it 'should save an array of events hierarchically' do + parent = event(:start => Time.now - 10.seconds, :end => Time.now) + child = event(:start => Time.now - 9.seconds, :end => Time.now - 1.seconds) + grandchild = event(:start => Time.now - 8.seconds, :end => Time.now - 2.seconds) + + store = SystemMetrics::AsyncStore.new + + lambda { + store.save([grandchild, child, parent]) + sleep(0.1) + }.should change(SystemMetrics::Metric, :count).by(3) + + metrics = SystemMetrics::Metric.all + verify_equal(parent, metrics[0]) + verify_equal(child, metrics[0].children[0]) + verify_equal(grandchild, metrics[0].children[0].children[0]) + end + + it 'should not attempt to save anything if passed an empty array of events' do + store = SystemMetrics::AsyncStore.new + lambda { store.save([]); sleep(0.1) }.should_not change(SystemMetrics::Metric, :count) + end + + it 'should not attempt to save anything if passed a nil' do + store = SystemMetrics::AsyncStore.new + lambda { store.save(nil); sleep(0.1) }.should_not change(SystemMetrics::Metric, :count) + end + end + + private + + def verify_equal(event, metric) + event.name.should == metric.name + event.action.should == metric.action + event.category.should == metric.category + event.transaction_id.should == metric.transaction_id + event.payload.should == metric.payload + event.started_at.should be_within(1).of(metric.started_at) + event.duration.should be_within(1).of(metric.duration) + event.exclusive_duration.should be_within(1).of(metric.exclusive_duration) + end + +end diff --git a/spec/system_metrics/store_spec.rb b/spec/system_metrics/store_spec.rb index 2e99d86..ac9c33d 100644 --- a/spec/system_metrics/store_spec.rb +++ b/spec/system_metrics/store_spec.rb @@ -11,7 +11,7 @@ grandchild = event(:start => Time.now - 8.seconds, :end => Time.now - 2.seconds) store = SystemMetrics::Store.new - + lambda { store.save([grandchild, child, parent]) }.should change(SystemMetrics::Metric, :count).by(3)