fiveruns-dash-rails 0.7.1

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