fiveruns-dash-rails 0.7.1

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.
data/README.rdoc ADDED
@@ -0,0 +1,73 @@
1
+ = FiveRuns Dash recipe for Ruby on Rails
2
+
3
+ Provides a Ruby API to push metrics from a Rails app to the FiveRuns Dash service, http://dash.fiveruns.com, currently in beta.
4
+
5
+ You'll need a Dash account before using this library.
6
+
7
+ == Installation
8
+
9
+ This library is released as a gem from the official repository at http://github.com/fiveruns/dash-rails
10
+
11
+ sudo gem install fiveruns-dash-rails --source 'http://gems.github.com'
12
+
13
+ == Usage
14
+
15
+ See the Ruby Language Guide http://dash.fiveruns.com/help/ruby.html for information on how to use this library.
16
+
17
+ == Authors
18
+
19
+ The FiveRuns Development Team & Dash community
20
+
21
+ == Dependencies
22
+
23
+ * The fiveruns-dash-ruby gem (see http://github.com/fiveruns/dash-ruby)
24
+ * The json gem (as a dependency for fiveruns-dash-ruby)
25
+
26
+ == Rails versions
27
+
28
+ Dash has been tested with Rails 2.2 and 2.1. It may (or may not) work with Rails 2.0 applications.
29
+
30
+ We haven't done a lot of testing with the gem being unpacked in Rails apps. YMMV; please let us know if you have issues.
31
+
32
+ == Platforms
33
+
34
+ This library has only been tested on OSX and Linux. See the notes for fiveruns-dash-ruby for more information: http://github.com/fiveruns/dash-ruby
35
+
36
+ == Contributing
37
+
38
+ As an open source project, we welcome community contributions!
39
+
40
+ The best way to contribute is by sending pull requests via GitHub. The official repository for this project is:
41
+
42
+ http://github.com/fiveruns/dash-rails
43
+
44
+ == Support
45
+
46
+ Please join the dash-users Google group, http://groups.google.com/group/dash-users
47
+
48
+ You can also contact us via Twitter, Campfire, or email; see the main help page, http://dash.fiveruns.com/help, for details.
49
+
50
+ == License
51
+
52
+ # (The FiveRuns License)
53
+ #
54
+ # Copyright (c) 2006-2008 FiveRuns Corporation
55
+ #
56
+ # Permission is hereby granted, free of charge, to any person obtaining
57
+ # a copy of this software and associated documentation files (the
58
+ # 'Software'), to deal in the Software without restriction, including
59
+ # without limitation the rights to use, copy, modify, merge, publish,
60
+ # distribute, sublicense, and/or sell copies of the Software, and to
61
+ # permit persons to whom the Software is furnished to do so, subject to
62
+ # the following conditions:
63
+ #
64
+ # The above copyright notice and this permission notice shall be
65
+ # included in all copies or substantial portions of the Software.
66
+ #
67
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
68
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
69
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
70
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
71
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
72
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
73
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rubygems'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.verbose = true
6
+ t.test_files = FileList['test/*_test.rb']
7
+ t.libs << 'lib'
8
+ end
9
+
10
+ task :default => :test
11
+
12
+ begin
13
+ require 'jeweler'
14
+
15
+ Jeweler::Tasks.new do |s|
16
+ s.name = "dash-rails"
17
+ s.rubyforge_project = 'fiveruns'
18
+ s.summary = "FiveRuns Dash recipe for Ruby on Rails"
19
+ s.email = "dev@fiveruns.com"
20
+ s.homepage = "http://github.com/fiveruns/dash-rails"
21
+ s.description = "Provides an API to send metrics from Rails applications to the FiveRuns Dash service"
22
+ s.authors = ["FiveRuns Development Team"]
23
+ s.files = FileList['README.rdoc', 'Rakefile', 'version.yml', 'init.rb', "{lib,rails,test}/**/*", ]
24
+ s.add_dependency 'fiveruns-dash-ruby', '>= 0.7.1'
25
+ end
26
+ rescue LoadError
27
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
28
+ end
29
+
30
+ NAME = "dash-rails"
31
+ AUTHOR = "FiveRuns Development Team"
32
+ EMAIL = "dev@fiveruns.com"
33
+ HOMEPAGE = "http://dash.fiveruns.com/"
34
+ SUMMARY = "FiveRuns Dash library for Ruby on Rails"
35
+
36
+ task :coverage do
37
+ rm_f "coverage"
38
+ rm_f "coverage.data"
39
+ rcov = "rcov --exclude gems --exclude version.rb --sort coverage --text-summary --html -o coverage"
40
+ system("#{rcov} test/*_test.rb")
41
+ if ccout = ENV['CC_BUILD_ARTIFACTS']
42
+ FileUtils.rm_rf '#{ccout}/coverage'
43
+ FileUtils.cp_r 'coverage', ccout
44
+ end
45
+ system "open coverage/index.html" if PLATFORM['darwin']
46
+ end
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ # For Rails < 2.1.0
2
+ require File.dirname(__FILE__) << '/rails/init'
@@ -0,0 +1,166 @@
1
+ require File.dirname(__FILE__) << "/rails/version"
2
+ require File.dirname(__FILE__) << "/rails/startup"
3
+
4
+ if START_FIVERUNS_DASH_RAILS
5
+
6
+ module Fiveruns
7
+ module Dash
8
+
9
+ module Rails
10
+
11
+ class << self
12
+ attr_accessor :server
13
+ end
14
+
15
+ def self.queue_size
16
+ return 0 unless server_type
17
+ case server_type
18
+ when :mongrel
19
+ server.workers.list.length
20
+ else
21
+ 0 # Skip
22
+ end
23
+ end
24
+
25
+ def self.server_type
26
+ return @server_type if defined?(@server_type)
27
+ @server_type = if server
28
+ case server.class.to_s
29
+ when /Mongrel/
30
+ :mongrel
31
+ else
32
+ ::Fiveruns::Dash.logger.warn "Unrecognized app server type: #{server.class}, not collecting queue size"
33
+ false
34
+ end
35
+ else
36
+ ::Fiveruns::Dash.logger.warn "Could not find app server, not collecting queue size"
37
+ nil
38
+ end
39
+ end
40
+
41
+ def self.load_recipes
42
+ Dir[File.dirname(__FILE__) << "/recipes/**/*.rb"].each do |file|
43
+ require file
44
+ end
45
+ end
46
+
47
+ def self.start(tokens = {}, &block)
48
+ return if Fiveruns::Dash.session.reporter.started?
49
+ ::Rails::Initializer.send(:include, Fiveruns::Dash::Rails::Initializer)
50
+ store_dash_start_block do
51
+ configure(tokens, &block) unless tokens.empty?
52
+ if Fiveruns::Dash.configuration.ready?
53
+ RAILS_DEFAULT_LOGGER.info "Starting Dash"
54
+ Fiveruns::Dash.session.start
55
+ else
56
+ log_error unless env == 'development'
57
+ end
58
+ end
59
+ end
60
+
61
+ def self.store_dash_start_block(&block)
62
+ @dash_start_block = block
63
+ end
64
+
65
+ def self.dash_start_block
66
+ @dash_start_block ||= lambda {}
67
+ end
68
+
69
+
70
+ def self.configure(tokens = {}, &block)
71
+ tokens.each do |environment, token|
72
+ if environment.to_s == self.env
73
+ Fiveruns::Dash.configure({:app => token}, &block)
74
+ break
75
+ end
76
+ end
77
+ end
78
+
79
+ def self.log_error
80
+ # TODO: Add URL for help
81
+ message =<<-EOM
82
+ FiveRuns Dash [Rails] (v#{Version::STRING}) Application token missing
83
+ ===
84
+ In config/initializers/dash.rb or at the bottom of config/environment.rb, please add:
85
+
86
+ Fiveruns::Dash::Rails.configure :#{env} => 'YOUR-#{env.upcase}-ENV-APP-TOKEN-HERE'
87
+
88
+ You can also set app tokens for other environments (eg, staging), at the same time.
89
+ See http://todo/path/to/help for more information
90
+ ===
91
+ EOM
92
+ RAILS_DEFAULT_LOGGER.warn(message.strip)
93
+ end
94
+
95
+ def self.env
96
+ ::Rails.env # >= Rails 2.1
97
+ rescue
98
+ ENV['RAILS_ENV'] # <= Rails 2.0
99
+ end
100
+
101
+ module ActionContext
102
+
103
+ def self.included(base)
104
+ base.send(:include, InstanceMethods)
105
+ base.alias_method_chain :perform_action, :fiveruns_dash_context
106
+ base.extend(ClassMethods)
107
+ (class << base; self; end).alias_method_chain :process, :fiveruns_dash_tracing
108
+ end
109
+
110
+ module ClassMethods
111
+
112
+ def process_with_fiveruns_dash_tracing(*args, &block)
113
+ operation = lambda { process_without_fiveruns_dash_tracing(*args, &block) }
114
+ params = args.first.parameters
115
+ # TODO/FIXME: For now, we simply look for a 'trace' parameter to select requests to trace; in the
116
+ # future, we need a more advanced sampling mechanism (some operation in a recipe a
117
+ # request must pass, or selection criteria returned in a response from the service)
118
+ trace_context = ['action', "#{params['controller'].camelize}Controller##{params['action']}"]
119
+ if ::Fiveruns::Dash.trace_contexts.include?(trace_context)
120
+ ::Fiveruns::Dash.session.trace(trace_context) do
121
+ operation.call
122
+ end
123
+ else
124
+ operation.call
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ module InstanceMethods
131
+
132
+ def perform_action_with_fiveruns_dash_context(*args, &block)
133
+ action_name = (request.parameters['action'] || 'index').to_s
134
+ Fiveruns::Dash::Context.set ['action', %(#{self.class.name}##{action_name})]
135
+ perform_action_without_fiveruns_dash_context(*args, &block)
136
+ end
137
+
138
+ end
139
+
140
+ end
141
+
142
+ module Initializer
143
+
144
+ def self.included(base)
145
+ base.send(:include, InstanceMethods)
146
+ base.alias_method_chain :load_application_classes, :dash
147
+ end
148
+
149
+ module InstanceMethods
150
+
151
+ def load_application_classes_with_dash
152
+ load_application_classes_without_dash
153
+ Fiveruns::Dash::Rails.dash_start_block.call
154
+ end
155
+
156
+ end
157
+
158
+ end
159
+
160
+ end
161
+
162
+ end
163
+
164
+ end
165
+
166
+ end
@@ -0,0 +1,17 @@
1
+ # AKK: remove me in favor of inlining into init
2
+ START_FIVERUNS_DASH_RAILS = if ENV['START_FIVERUNS_DASH'] || File.basename($0) != 'irb'
3
+ true
4
+ else
5
+ module Fiveruns
6
+ module Dash
7
+ module Rails
8
+
9
+ def self.configure(*args, &block)
10
+ RAILS_DEFAULT_LOGGER.info "[FiveRuns Dash] Skipping configuration (`#{$0}' not supported for collection)"
11
+ end
12
+
13
+ end
14
+ end
15
+ end
16
+ false
17
+ end
@@ -0,0 +1,97 @@
1
+ # (The MIT License)
2
+ #
3
+ # Copyright (c) 2008 Jamis Buck <jamis@37signals.com>,
4
+ # with modifications by Bruce Williams <bruce@fiveruns.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining
7
+ # a copy of this software and associated documentation files (the
8
+ # 'Software'), to deal in the Software without restriction, including
9
+ # without limitation the rights to use, copy, modify, merge, publish,
10
+ # distribute, sublicense, and/or sell copies of the Software, and to
11
+ # permit persons to whom the Software is furnished to do so, subject to
12
+ # the following conditions:
13
+ #
14
+ # The above copyright notice and this permission notice shall be
15
+ # included in all copies or substantial portions of the Software.
16
+ #
17
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
18
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
20
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
21
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
22
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
23
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24
+ module Fiveruns
25
+
26
+ module Dash
27
+
28
+ module Rails
29
+
30
+ # A class for describing the current version of a library. The version
31
+ # consists of three parts: the +major+ number, the +minor+ number, and the
32
+ # +tiny+ (or +patch+) number.
33
+ class Version
34
+
35
+ include Comparable
36
+
37
+ # A convenience method for instantiating a new Version instance with the
38
+ # given +major+, +minor+, and +tiny+ components.
39
+ def self.[](major, minor, tiny)
40
+ new(major, minor, tiny)
41
+ end
42
+
43
+ # Borrowed from TuneUp
44
+ def self.rails
45
+ @rails ||= begin
46
+ # handle ::Rails::VERSION not being set
47
+ Version.new(::Rails::VERSION::MAJOR, ::Rails::VERSION::MINOR, ::Rails::VERSION::TINY) rescue Version.new(0,0,0)
48
+ end
49
+ end
50
+
51
+
52
+ attr_reader :major, :minor, :tiny
53
+
54
+ # Create a new Version object with the given components.
55
+ def initialize(major, minor, tiny)
56
+ @major, @minor, @tiny = major, minor, tiny
57
+ end
58
+
59
+ # Compare this version to the given +version+ object.
60
+ def <=>(version)
61
+ to_i <=> version.to_i
62
+ end
63
+
64
+ # Converts this version object to a string, where each of the three
65
+ # version components are joined by the '.' character. E.g., 2.0.0.
66
+ def to_s
67
+ @to_s ||= [@major, @minor, @tiny].join(".")
68
+ end
69
+
70
+ # Converts this version to a canonical integer that may be compared
71
+ # against other version objects.
72
+ def to_i
73
+ @to_i ||= @major * 1_000_000 + @minor * 1_000 + @tiny
74
+ end
75
+
76
+ def to_a
77
+ [@major, @minor, @tiny]
78
+ end
79
+
80
+ PARSED = YAML.load(File.read(File.dirname(__FILE__) << "/../../../../version.yml"))
81
+
82
+ MAJOR = PARSED['major']
83
+ MINOR = PARSED['minor']
84
+ TINY = PARSED['patch']
85
+
86
+ # The current version as a Version instance
87
+ CURRENT = new(MAJOR, MINOR, TINY)
88
+ # The current version as a String
89
+ STRING = CURRENT.to_s
90
+
91
+ end
92
+
93
+ end
94
+
95
+ end
96
+
97
+ end
@@ -0,0 +1,96 @@
1
+ module Fiveruns::Dash::ActiveRecordContext
2
+ CLASS_METHODS = %w(find find_by_sql calculate create create! update_all destroy destroy_all delete delete_all)
3
+ INSTANCE_METHODS = %w(update save save! destroy)
4
+
5
+ def self.included(base)
6
+ class << base
7
+ CLASS_METHODS.each do |meth|
8
+ head = meth
9
+ tail = ''
10
+ head, tail = meth[0..(meth.length-2)], meth[-1..-1] if %w(? !).include? meth[-1..-1]
11
+ self.class_eval <<-EOM
12
+ def #{head}_with_dash_context#{tail}(*args, &block)
13
+ Fiveruns::Dash::ActiveRecordContext.with_model_context(self.name) do
14
+ #{head}_without_dash_context#{tail}(*args, &block)
15
+ end
16
+ end
17
+ EOM
18
+ alias_method_chain(meth.to_sym, :dash_context)
19
+ end
20
+ end
21
+
22
+ INSTANCE_METHODS.each do |meth|
23
+ head = meth
24
+ tail = ''
25
+ head, tail = meth[0..meth.length-2], meth[-1..-1] if %w(? !).include? meth[-1..-1]
26
+ base.class_eval <<-EOM
27
+ def #{head}_with_dash_context#{tail}(*args, &block)
28
+ Fiveruns::Dash::ActiveRecordContext.with_model_context(self.class.name) do
29
+ #{head}_without_dash_context#{tail}(*args, &block)
30
+ end
31
+ end
32
+ EOM
33
+ base.alias_method_chain(meth.to_sym, :dash_context)
34
+ end
35
+ end
36
+
37
+ def self.with_model_context(model_name)
38
+ ctx = Fiveruns::Dash::Context.context
39
+ # don't change context if model context has already been set.
40
+ return yield if ctx.size > 0 && ctx[-2] == 'model' && ctx[-1] == model_name
41
+
42
+ original_context = Fiveruns::Dash::Context.context.dup
43
+ begin
44
+ if ctx[-2] == 'model'
45
+ # Some models will internally load other models.
46
+ Fiveruns::Dash::Context.context.pop
47
+ Fiveruns::Dash::Context.context << model_name
48
+ else
49
+ Fiveruns::Dash::Context.context << 'model'
50
+ Fiveruns::Dash::Context.context << model_name
51
+ end
52
+ return yield
53
+ ensure
54
+ Fiveruns::Dash::Context.set original_context
55
+ end
56
+ end
57
+
58
+ def self.all_methods
59
+ CLASS_METHODS.map { |m| "ActiveRecord::Base.#{m}" } + INSTANCE_METHODS.map { |m| "ActiveRecord::Base##{m}"}
60
+ end
61
+
62
+ end
63
+
64
+ # ActiveRecord ################################################################
65
+
66
+ Fiveruns::Dash.register_recipe :activerecord, :url => 'http://dash.fiveruns.com' do |recipe|
67
+ recipe.time :ar_time, 'ActiveRecord Time', :methods => Fiveruns::Dash::ActiveRecordContext.all_methods, :reentrant => true
68
+ recipe.time :db_time, 'Database Time', :methods => %w(ActiveRecord::ConnectionAdapters::AbstractAdapter#log)
69
+
70
+ # We need a way to get the total time for a request/operation so that we can
71
+ # calculate the relative percentage used by AR/DB. Default to "response_time" for the Rails
72
+ # recipe but daemons can set this constant to provide their own total time metric.
73
+ total_time = recipe.options[:ar_total_time] ? recipe.options[:ar_total_time] : "response_time"
74
+
75
+ recipe.percentage :ar_util, 'ActiveRecord Utilization', :sources => ["ar_time", total_time] do |ar_time, all_time|
76
+ (ar_time / all_time) * 100.0
77
+ end
78
+ recipe.percentage :db_util, 'Database Utilization', :sources => ["db_time", total_time] do |db_time, all_time|
79
+ (db_time / all_time) * 100.0
80
+ end
81
+
82
+ recipe.modify :recipe_name => :activerecord, :recipe_url => 'http://dash.fiveruns.com' do |metric|
83
+ metric.find_context_with do |obj, *args|
84
+ if Fiveruns::Dash::Context.context == []
85
+ []
86
+ else
87
+ [[], Fiveruns::Dash::Context.context]
88
+ end
89
+ end
90
+ end
91
+
92
+ recipe.added do
93
+ ActiveRecord::Base.send(:include, Fiveruns::Dash::ActiveRecordContext)
94
+ end
95
+ end
96
+
@@ -0,0 +1,112 @@
1
+ module Fiveruns::Dash::Rails::Hash
2
+
3
+ def self.clean(extended_hash = {})
4
+ (extended_hash || {}).keys.inject({}) do |all, key|
5
+ val = extended_hash[key]
6
+ if val.kind_of? Hash
7
+ val = clean(val)
8
+ end
9
+ all[key.to_s] = val
10
+ all
11
+ end
12
+ end
13
+
14
+ end
15
+
16
+ # ActionPack ##################################################################
17
+ Fiveruns::Dash.register_recipe :actionpack, :url => 'http://dash.fiveruns.com' do |recipe|
18
+ recipe.time :response_time, :method => 'ActionController::Base#perform_action'
19
+ recipe.counter :requests, 'Requests', :incremented_by => 'ActionController::Base#perform_action'
20
+
21
+ targets = []
22
+ targets << 'ActionView::Template#render' if defined?(ActionView::Template)
23
+ targets << 'ActionView::PartialTemplate#render' if defined?(ActionView::PartialTemplate)
24
+ if !targets.empty?
25
+ recipe.time :render_time, :method => targets
26
+ else
27
+ Fiveruns::Dash.logger.warn 'Collection of "render_time" unsupported for this version of Rails'
28
+ end
29
+ end
30
+
31
+ # Rails #######################################################################
32
+ Fiveruns::Dash.register_recipe :rails, :url => 'http://dash.fiveruns.com' do |recipe|
33
+ recipe.add_recipe :activerecord, :url => 'http://dash.fiveruns.com'
34
+
35
+ recipe.add_recipe :actionpack, :url => 'http://dash.fiveruns.com'
36
+ recipe.modify :recipe_name => :actionpack, :recipe_url => 'http://dash.fiveruns.com' do |metric|
37
+ if metric.name.to_s == 'render_time'
38
+ metric.find_context_with do |obj, *args|
39
+ Fiveruns::Dash::Context.context
40
+ end
41
+ else
42
+ metric.find_context_with do |obj, *args|
43
+ [[], Fiveruns::Dash::Context.context]
44
+ end
45
+ end
46
+ end
47
+
48
+ recipe.add_exceptions_from 'ActionController::Base#perform_action_without_rescue' do |ex, controller|
49
+ session_data = nil
50
+ begin
51
+ session_data = controller.request.session.instance_variable_get("@data")
52
+ rescue Exception => e
53
+ Fiveruns::Dash.logger.warn "Could not retrieve session data for exception: #{e.message}"
54
+ end
55
+ {
56
+ :name => "#{ex.class.name} in #{controller.class.name}##{controller.params[:action]}", # Override the standard name
57
+ :session => Fiveruns::Dash::Rails::Hash.clean(session_data).to_json,
58
+ :headers => Fiveruns::Dash::Rails::Hash.clean(controller.request.headers).to_json,
59
+ :request => { :url => controller.request.url, :params => controller.params.inspect }.to_json,
60
+ }
61
+ end
62
+
63
+ # Same classes as the exception_notification plugin
64
+ IGNORE = [ActiveRecord::RecordNotFound, ActionController::RoutingError, ActionController::UnknownController, ActionController::UnknownAction]
65
+
66
+ recipe.ignore_exceptions do |exc|
67
+ IGNORE.include? exc.class
68
+ end
69
+
70
+ recipe.added do
71
+ ActionController::Base.send(:include, Fiveruns::Dash::Rails::ActionContext)
72
+ ActionView::Template.send(:include, Fiveruns::Dash::Rails::TemplateContext) if defined?(ActionView::Template)
73
+ ActionView::InlineTemplate.send(:include, Fiveruns::Dash::Rails::TemplateContext) if defined?(ActionView::InlineTemplate)
74
+ ActionView::PartialTemplate.send(:include, Fiveruns::Dash::Rails::TemplateContext) if defined?(ActionView::PartialTemplate)
75
+
76
+ begin
77
+ if defined?(Mongrel)
78
+ ActiveSupport::Deprecation.silence do
79
+ # Unfortunately there is no known way to get direct access
80
+ # to the Mongrel singleton. Wade through the Ruby heap to
81
+ # find it.
82
+ ObjectSpace.each_object do |obj|
83
+ if obj.class == Mongrel::HttpServer
84
+ Fiveruns::Dash::Rails.server = obj
85
+ end
86
+ end
87
+ end
88
+ end
89
+ rescue Exception => e
90
+ if RUBY_PLATFORM =~ /java/ && e.message =~ /ObjectSpace/
91
+ Fiveruns::Dash.logger.info "Cannot find Mongrel: #{e.message}"
92
+ else
93
+ raise e
94
+ end
95
+ end
96
+
97
+ # Passenger forks the Rails processes, which has the side effect of
98
+ # killing our reporter thread. We need to revive the thread.
99
+ class ActionController::Base
100
+ def perform_action_with_dash_startup(*args, &block)
101
+ Fiveruns::Dash.session.reporter.revive!
102
+ perform_action_without_dash_startup(*args, &block)
103
+ end
104
+
105
+ alias_method_chain :perform_action, :dash_startup
106
+ end
107
+ end
108
+
109
+ recipe.absolute :queue_size do
110
+ Fiveruns::Dash::Rails.queue_size || 0
111
+ end
112
+ end
@@ -0,0 +1,51 @@
1
+
2
+ module Fiveruns::Dash::Rails::TemplateContext
3
+
4
+ def self.included(base)
5
+ base.send(:include, InstanceMethods)
6
+ base.alias_method_chain(:render, :fiveruns_dash_context)
7
+ end
8
+
9
+ RAILS_ROOT_RE = /\A#{Regexp.quote RAILS_ROOT}/
10
+
11
+ GEM_REs = Gem.path.map do |path|
12
+ /\A#{Regexp.quote path}\/gems/
13
+ end
14
+
15
+ def self.sanitize_view_path(path)
16
+ path = if path[0..0] == '/'
17
+ if path =~ RAILS_ROOT_RE
18
+ trimmed = path.sub(RAILS_ROOT_RE, 'RAILS_ROOT')
19
+ trimmed
20
+ elsif (re = GEM_REs.find { |re| path =~ re })
21
+ trimmed = path.sub(re, 'GEMS')
22
+ else
23
+ path
24
+ end
25
+ else
26
+ path
27
+ end
28
+ # Remove extensions, if any
29
+ path.sub(/\.[^\/\\]*$/, '')
30
+ end
31
+
32
+ module InstanceMethods
33
+
34
+ def render_with_fiveruns_dash_context(*args, &block)
35
+ original_context = Fiveruns::Dash::Context.context.dup
36
+
37
+ begin
38
+ template = Fiveruns::Dash::Rails::TemplateContext.sanitize_view_path(path)
39
+ Fiveruns::Dash::Context.context << 'view'
40
+ Fiveruns::Dash::Context.context << template
41
+ result = render_without_fiveruns_dash_context(*args, &block)
42
+ ensure
43
+ Fiveruns::Dash::Context.set original_context
44
+ end
45
+
46
+ result
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,9 @@
1
+ require File.dirname(__FILE__) << "/fiveruns/dash/rails/startup"
2
+
3
+ if START_FIVERUNS_DASH_RAILS
4
+ gem 'fiveruns-dash-ruby' # Put its path first
5
+ require 'fiveruns/dash'
6
+ require 'fiveruns/dash/rails'
7
+ require 'fiveruns/dash/template_context'
8
+ require File.dirname(__FILE__) << "/../rails/init"
9
+ end
@@ -0,0 +1,57 @@
1
+ namespace :dash do
2
+ desc "Verify FiveRuns Dash connectivity and configuration"
3
+ task :test => :environment do
4
+ unless $tested
5
+ $tested = true
6
+ begin
7
+ puts ""
8
+ puts "FiveRuns Dash installation verification"
9
+ puts "======================================="
10
+ puts ""
11
+ RAILS_DEFAULT_LOGGER = Fiveruns::Dash.logger = Logger.new(STDOUT)
12
+ Fiveruns::Dash.logger.level = Logger::WARN
13
+ verify('FiveRuns Dash loaded',
14
+ 'The FiveRuns Dash plugin has not been loaded. Verify you are initializing Dash in config/initializers/dash.rb.') do
15
+ defined? ::Fiveruns::Dash::Rails
16
+ end
17
+ verify('FiveRuns Dash configuration',
18
+ "No application token was found for the #{Rails.env} environment.") do
19
+ Fiveruns::Dash.configuration.options[:app]
20
+ end
21
+ verify('FiveRuns Dash session running', "FiveRuns Dash session is not active") do
22
+ Fiveruns::Dash.session.reporter.alive?
23
+ end
24
+ verify('FiveRuns Dash network connectivity') do
25
+ Fiveruns::Dash.session.reporter.ping
26
+ end
27
+ puts ""
28
+ puts "All appears normal. If you are experiencing a problem, please email support@fiveruns.com with details about the problem and your environment."
29
+ puts ""
30
+ rescue ArgumentError
31
+ end
32
+ end
33
+ end
34
+ end
35
+
36
+ def verify(test, fail=nil)
37
+ $test_count = ($test_count || 0) + 1
38
+ print " #{$test_count}. #{test}..."
39
+ begin
40
+ result = yield
41
+ if result
42
+ puts "OK." if fail
43
+ else
44
+ if fail
45
+ puts "FAIL!"
46
+ puts fail
47
+ end
48
+ raise ArgumentError
49
+ end
50
+ rescue ArgumentError => ex
51
+ raise ex
52
+ rescue => e
53
+ puts "FAIL!"
54
+ puts fail
55
+ raise e
56
+ end
57
+ end
data/rails/init.rb ADDED
@@ -0,0 +1,32 @@
1
+
2
+ unless defined?(START_FIVERUNS_DASH_RAILS)
3
+ START_FIVERUNS_DASH_RAILS = if ENV['START_FIVERUNS_DASH'] || File.basename($0) != 'irb'
4
+ true
5
+ else
6
+ module Fiveruns
7
+ module Dash
8
+ module Rails
9
+
10
+ def self.configure(*args, &block)
11
+ RAILS_DEFAULT_LOGGER.info "[FiveRuns Dash] Skipping configuration (`#{$0}' not supported for collection)"
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ false
18
+ end
19
+ end
20
+
21
+ if START_FIVERUNS_DASH_RAILS
22
+
23
+ require 'fiveruns_dash_rails'
24
+ Fiveruns::Dash.logger = RAILS_DEFAULT_LOGGER
25
+ Fiveruns::Dash::Rails.load_recipes
26
+ Fiveruns::Dash.configure do |config|
27
+ config.add_recipe :ruby, :url => 'http://dash.fiveruns.com'
28
+ config.add_recipe :jruby, :url => 'http://dash.fiveruns.com' if RUBY_PLATFORM[/java/]
29
+ config.add_recipe :rails, :url => 'http://dash.fiveruns.com'
30
+ end
31
+
32
+ end
@@ -0,0 +1,113 @@
1
+ require File.dirname(__FILE__) << "/test_helper"
2
+
3
+ class ActiverecordTest < Test::Unit::TestCase
4
+
5
+ class TestModel < ActiveRecord::Base
6
+ end
7
+
8
+ class TestEngine
9
+ def doit
10
+ sleep 1
11
+ 2.times do
12
+ t = TestModel.create!(:name => 'foo')
13
+ t.destroy
14
+ end
15
+ end
16
+
17
+ def conn
18
+ TestModel.connection.execute("select sleep(1)")
19
+ end
20
+
21
+ def entry(meth)
22
+ send(meth)
23
+ end
24
+ end
25
+
26
+
27
+ context "Metric" do
28
+
29
+ setup do
30
+ ActiveRecord::Base.configurations = { 'test' => { 'database' => 'test', 'adapter' => 'mysql', 'user' => 'root', 'hostname' => 'localhost' }}
31
+ ActiveRecord::Base.establish_connection
32
+ ActiveRecord::Base.connection.execute("create table if not exists test_models (id integer PRIMARY KEY, name varchar(32) not null)")
33
+ ActiveRecord::Base.connection.execute("delete from test_models")
34
+ end
35
+
36
+ should "collect basic AR metrics" do
37
+ ar_scenario do
38
+ TestEngine.new.entry(:doit)
39
+
40
+ data = Fiveruns::Dash.session.data
41
+ # data.each do |hsh|
42
+ # puts "#{hsh[:name]}: #{hsh[:values].inspect}"
43
+ # end
44
+
45
+ assert metric('test_time', data) > 1.0
46
+ assert metric('ar_util', data) > metric('db_util', data)
47
+ assert metric('db_util', data) < 5
48
+ end
49
+ end
50
+
51
+ should "collect DB metrics" do
52
+ ar_scenario do
53
+ TestEngine.new.entry(:conn)
54
+
55
+ data = Fiveruns::Dash.session.data
56
+ # data.each do |hsh|
57
+ # puts "#{hsh[:name]}: #{hsh[:values].inspect}"
58
+ # end
59
+
60
+ assert metric('test_time', data) > 1.0
61
+ assert metric('test_time', data) < 1.1
62
+ assert metric('db_time', data) > 1.0
63
+ assert metric('db_time', data) < 1.1
64
+ assert metric('db_util', data) > 90.0
65
+ assert metric('db_util', data) < 100.0
66
+ end
67
+ end
68
+ end
69
+
70
+ def ar_scenario(&block)
71
+ child = fork do
72
+ mock_activerecord!
73
+ yield
74
+ end
75
+ Process.wait
76
+ assert_equal 0, $?.exitstatus
77
+ end
78
+
79
+ def metric(metric, data, in_ctx=[])
80
+ hsh = data.detect { |hsh| hsh[:name] == metric }
81
+ assert hsh, "No metric named #{metric} was found in metrics payload"
82
+ vals = hsh[:values]
83
+ assert vals, "No values found for #{metric} in metrics payload"
84
+ val = vals.detect { |val| val[:context] == in_ctx }
85
+ assert val, "No value for #{metric} found for context #{in_ctx.inspect}"
86
+ val[:value]
87
+ end
88
+
89
+ def mock_activerecord!
90
+ require 'fiveruns/dash'
91
+ require 'fiveruns/dash/recipes/activerecord'
92
+
93
+ eval <<-MOCK
94
+ module Fiveruns::Dash
95
+ class Reporter
96
+ private
97
+ def run
98
+ end
99
+ end
100
+ end
101
+ MOCK
102
+
103
+ Fiveruns::Dash.register_recipe :tester, :url => 'http://dash.fiveruns.com' do |recipe|
104
+ recipe.time :test_time, 'Test Time', :method => 'ActiverecordTest::TestEngine#entry'
105
+ end
106
+ Fiveruns::Dash.configure :app => '666', :ar_total_time => 'test_time' do |config|
107
+ config.add_recipe :ruby
108
+ config.add_recipe :activerecord
109
+ config.add_recipe :tester
110
+ end
111
+ Fiveruns::Dash.session.start(true)
112
+ end
113
+ end
@@ -0,0 +1,30 @@
1
+ RAILS_ENV='test'
2
+ RAILS_ROOT=File.dirname(__FILE__)
3
+ require 'test/unit'
4
+ require 'logger'
5
+ require 'rubygems'
6
+
7
+ begin
8
+ require 'shoulda'
9
+ require 'flexmock/test_unit'
10
+ require 'fake_web'
11
+ rescue
12
+ puts "Please install the Shoulda, FakeWeb and flexmock gems to run the Dash plugin tests."
13
+ end
14
+
15
+ require 'shoulda'
16
+ require 'flexmock/test_unit'
17
+
18
+ RAILS_DEFAULT_LOGGER=Logger.new(STDOUT)
19
+ require 'active_record'
20
+ require 'action_controller'
21
+ require 'action_controller/test_process'
22
+ require 'action_controller/test_case'
23
+ require 'action_view'
24
+
25
+ $:.unshift(File.dirname(__FILE__) << '/../lib')
26
+ $:.unshift(File.dirname(__FILE__) << '/../../fiveruns_dash/lib')
27
+
28
+ class Test::Unit::TestCase
29
+
30
+ end
data/version.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 7
4
+ :patch: 1
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fiveruns-dash-rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.7.1
5
+ platform: ruby
6
+ authors:
7
+ - FiveRuns Development Team
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-01-28 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: fiveruns-dash-ruby
17
+ version_requirement:
18
+ version_requirements: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 0.7.1
23
+ version:
24
+ description: Provides an API to send metrics from Rails applications to the FiveRuns Dash service
25
+ email: dev@fiveruns.com
26
+ executables: []
27
+
28
+ extensions: []
29
+
30
+ extra_rdoc_files: []
31
+
32
+ files:
33
+ - README.rdoc
34
+ - Rakefile
35
+ - version.yml
36
+ - init.rb
37
+ - lib/fiveruns
38
+ - lib/fiveruns/dash
39
+ - lib/fiveruns/dash/rails
40
+ - lib/fiveruns/dash/rails/startup.rb
41
+ - lib/fiveruns/dash/rails/version.rb
42
+ - lib/fiveruns/dash/rails.rb
43
+ - lib/fiveruns/dash/recipes
44
+ - lib/fiveruns/dash/recipes/activerecord.rb
45
+ - lib/fiveruns/dash/recipes/rails.rb
46
+ - lib/fiveruns/dash/template_context.rb
47
+ - lib/fiveruns_dash_rails.rb
48
+ - lib/tasks
49
+ - lib/tasks/dash.rake
50
+ - rails/init.rb
51
+ - test/activerecord_test.rb
52
+ - test/test_helper.rb
53
+ has_rdoc: true
54
+ homepage: http://github.com/fiveruns/dash-rails
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --inline-source
58
+ - --charset=UTF-8
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project: fiveruns
76
+ rubygems_version: 1.2.0
77
+ signing_key:
78
+ specification_version: 2
79
+ summary: FiveRuns Dash recipe for Ruby on Rails
80
+ test_files: []
81
+