newrelic_rpm 2.8.0

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of newrelic_rpm might be problematic. Click here for more details.

Files changed (107) hide show
  1. data/LICENSE +37 -0
  2. data/README +93 -0
  3. data/Rakefile +38 -0
  4. data/install.rb +37 -0
  5. data/lib/new_relic/agent.rb +26 -0
  6. data/lib/new_relic/agent/agent.rb +762 -0
  7. data/lib/new_relic/agent/chained_call.rb +13 -0
  8. data/lib/new_relic/agent/collection_helper.rb +81 -0
  9. data/lib/new_relic/agent/error_collector.rb +105 -0
  10. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +95 -0
  11. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +151 -0
  12. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  13. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +105 -0
  14. data/lib/new_relic/agent/instrumentation/memcache.rb +18 -0
  15. data/lib/new_relic/agent/instrumentation/merb/controller.rb +17 -0
  16. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +15 -0
  17. data/lib/new_relic/agent/instrumentation/merb/errors.rb +6 -0
  18. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +35 -0
  19. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  20. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +30 -0
  21. data/lib/new_relic/agent/instrumentation/rails/errors.rb +23 -0
  22. data/lib/new_relic/agent/instrumentation/rails/rails.rb +6 -0
  23. data/lib/new_relic/agent/method_tracer.rb +171 -0
  24. data/lib/new_relic/agent/patch_const_missing.rb +31 -0
  25. data/lib/new_relic/agent/samplers/cpu.rb +29 -0
  26. data/lib/new_relic/agent/samplers/memory.rb +55 -0
  27. data/lib/new_relic/agent/samplers/mongrel.rb +26 -0
  28. data/lib/new_relic/agent/stats_engine.rb +241 -0
  29. data/lib/new_relic/agent/synchronize.rb +40 -0
  30. data/lib/new_relic/agent/transaction_sampler.rb +281 -0
  31. data/lib/new_relic/agent/worker_loop.rb +128 -0
  32. data/lib/new_relic/api/deployments.rb +92 -0
  33. data/lib/new_relic/config.rb +194 -0
  34. data/lib/new_relic/config/merb.rb +35 -0
  35. data/lib/new_relic/config/rails.rb +113 -0
  36. data/lib/new_relic/config/ruby.rb +9 -0
  37. data/lib/new_relic/local_environment.rb +108 -0
  38. data/lib/new_relic/merbtasks.rb +6 -0
  39. data/lib/new_relic/metric_data.rb +26 -0
  40. data/lib/new_relic/metric_spec.rb +39 -0
  41. data/lib/new_relic/metrics.rb +7 -0
  42. data/lib/new_relic/noticed_error.rb +21 -0
  43. data/lib/new_relic/shim_agent.rb +95 -0
  44. data/lib/new_relic/stats.rb +359 -0
  45. data/lib/new_relic/transaction_analysis.rb +122 -0
  46. data/lib/new_relic/transaction_sample.rb +499 -0
  47. data/lib/new_relic/version.rb +111 -0
  48. data/lib/new_relic_api.rb +275 -0
  49. data/lib/newrelic_rpm.rb +27 -0
  50. data/lib/tasks/agent_tests.rake +14 -0
  51. data/lib/tasks/all.rb +4 -0
  52. data/lib/tasks/install.rake +7 -0
  53. data/newrelic.yml +137 -0
  54. data/recipes/newrelic.rb +46 -0
  55. data/test/config/newrelic.yml +26 -0
  56. data/test/config/test_config.rb +9 -0
  57. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  58. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  59. data/test/new_relic/agent/model_fixture.rb +17 -0
  60. data/test/new_relic/agent/tc_active_record.rb +91 -0
  61. data/test/new_relic/agent/tc_agent.rb +112 -0
  62. data/test/new_relic/agent/tc_collection_helper.rb +104 -0
  63. data/test/new_relic/agent/tc_controller.rb +98 -0
  64. data/test/new_relic/agent/tc_dispatcher_instrumentation.rb +52 -0
  65. data/test/new_relic/agent/tc_error_collector.rb +127 -0
  66. data/test/new_relic/agent/tc_method_tracer.rb +306 -0
  67. data/test/new_relic/agent/tc_stats_engine.rb +218 -0
  68. data/test/new_relic/agent/tc_synchronize.rb +37 -0
  69. data/test/new_relic/agent/tc_transaction_sample.rb +175 -0
  70. data/test/new_relic/agent/tc_transaction_sample_builder.rb +200 -0
  71. data/test/new_relic/agent/tc_transaction_sampler.rb +305 -0
  72. data/test/new_relic/agent/tc_worker_loop.rb +101 -0
  73. data/test/new_relic/agent/testable_agent.rb +13 -0
  74. data/test/new_relic/tc_config.rb +36 -0
  75. data/test/new_relic/tc_deployments_api.rb +37 -0
  76. data/test/new_relic/tc_environment.rb +94 -0
  77. data/test/new_relic/tc_metric_spec.rb +150 -0
  78. data/test/new_relic/tc_shim_agent.rb +9 -0
  79. data/test/new_relic/tc_stats.rb +141 -0
  80. data/test/test_helper.rb +39 -0
  81. data/test/ui/tc_newrelic_helper.rb +44 -0
  82. data/ui/controllers/newrelic_controller.rb +200 -0
  83. data/ui/helpers/google_pie_chart.rb +55 -0
  84. data/ui/helpers/newrelic_helper.rb +286 -0
  85. data/ui/views/layouts/newrelic_default.rhtml +49 -0
  86. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  87. data/ui/views/newrelic/_sample.rhtml +12 -0
  88. data/ui/views/newrelic/_segment.rhtml +28 -0
  89. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  90. data/ui/views/newrelic/_show_sample_detail.rhtml +22 -0
  91. data/ui/views/newrelic/_show_sample_sql.rhtml +19 -0
  92. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  93. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  94. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  95. data/ui/views/newrelic/_table.rhtml +12 -0
  96. data/ui/views/newrelic/explain_sql.rhtml +45 -0
  97. data/ui/views/newrelic/images/arrow-close.png +0 -0
  98. data/ui/views/newrelic/images/arrow-open.png +0 -0
  99. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  100. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  101. data/ui/views/newrelic/index.rhtml +37 -0
  102. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  103. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  104. data/ui/views/newrelic/show_sample.rhtml +62 -0
  105. data/ui/views/newrelic/show_source.rhtml +3 -0
  106. data/ui/views/newrelic/stylesheets/style.css +394 -0
  107. metadata +180 -0
@@ -0,0 +1,90 @@
1
+
2
+ # NewRelic instrumentation for DataMapper
3
+ # For now, we have to refer to all db metrics as "ActiveRecord"
4
+ if defined? DataMapper
5
+
6
+ DataMapper::Model.class_eval do
7
+ add_method_tracer :get, 'ActiveRecord/#{self.name}/find'
8
+ add_method_tracer :first, 'ActiveRecord/#{self.name}/find'
9
+ add_method_tracer :first_or_create, 'ActiveRecord/#{self.name}/find'
10
+ add_method_tracer :all, 'ActiveRecord/#{self.name}/find_all'
11
+ end
12
+ DataMapper::Adapters::DataObjectsAdapter.class_eval do
13
+
14
+ @@my_sql_defined = defined? ActiveRecord::ConnectionAdapters::MysqlAdapter
15
+ @@postgres_defined = defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
16
+
17
+ for method in [:read_many, :read_one] do
18
+ add_method_tracer method, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/find' # Need to get the model somehow
19
+ add_method_tracer method, 'ActiveRecord/find', :push_scope => false
20
+ add_method_tracer method, 'ActiveRecord/all', :push_scope => false
21
+ end
22
+ for method in [:execute, :query] do
23
+ add_method_tracer method, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/execute' # Need to get the model somehow
24
+ add_method_tracer method, 'ActiveRecord/all', :push_scope => false
25
+ end
26
+ for method in [:create, :update]do
27
+ add_method_tracer method, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/save'
28
+ add_method_tracer method, 'ActiveRecord/save', :push_scope => false
29
+ end
30
+ add_method_tracer :delete, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/destroy'
31
+ add_method_tracer :delete, 'ActiveRecord/destroy', :push_scope => false
32
+
33
+ def log_with_newrelic_instrumentation(sql, name, &block)
34
+ # if we aren't in a blamed context, then add one so that we can see that
35
+ # controllers are calling SQL directly
36
+ # we check scope_depth vs 2 since the controller is 1, and the
37
+ #
38
+ if NewRelic::Agent.instance.transaction_sampler.scope_depth < 2
39
+ self.class.trace_method_execution "Database/DirectSQL", true, true do
40
+ log_with_capture_sql(sql, name, &block)
41
+ end
42
+ else
43
+ log_with_capture_sql(sql, name, &block)
44
+ end
45
+ end
46
+
47
+ def log_with_capture_sql(sql, name, &block)
48
+ if @@my_sql_defined && self.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
49
+ config = @config
50
+ elsif @@postgres_defined && self.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
51
+ config = @config
52
+ else
53
+ config = nil
54
+ end
55
+
56
+ t0 = Time.now
57
+ result = log_without_newrelic_instrumentation(sql, name, &block)
58
+
59
+ NewRelic::Agent.instance.transaction_sampler.notice_sql(sql, config, Time.now - t0)
60
+
61
+ result
62
+ end
63
+
64
+ # Compare with #alias_method_chain, which is not available in
65
+ # Rails 1.1:
66
+ #alias_method :log_without_newrelic_instrumentation, :log
67
+ #alias_method :log, :log_with_newrelic_instrumentation
68
+
69
+ =begin
70
+ # instrumentation for associations
71
+ module Associations
72
+ class AssociationCollection
73
+ add_method_tracer :delete, 'ActiveRecord/#{@owner.class.name}/association delete'
74
+ end
75
+
76
+ def HasAndBelongsToManyAssociation
77
+ add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
78
+ add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
79
+ add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
80
+ end
81
+
82
+ class HasManyAssociation
83
+ # add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
84
+ # add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
85
+ # add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
86
+ end
87
+ end
88
+ =end
89
+ end
90
+ end
@@ -0,0 +1,105 @@
1
+ # We have to patch the mongrel dispatcher live since the classes
2
+ # aren't defined when our instrumentation loads
3
+ module NewRelic::Agent::Instrumentation
4
+ module DispatcherInstrumentation
5
+
6
+ @@newrelic_agent = NewRelic::Agent.agent
7
+ @@newrelic_rails_dispatch_stat = @@newrelic_agent.stats_engine.get_stats 'Rails/HTTP Dispatch'
8
+ @@newrelic_mongrel_queue_stat = @@newrelic_agent.stats_engine.get_stats('WebFrontend/Mongrel/Average Queue Time')
9
+
10
+ def newrelic_dispatcher_start
11
+ # Put the current time on the thread. Can't put in @ivar because this could
12
+ # be a class or instance context
13
+ t0 = Time.now.to_f
14
+ NewRelic::Config.instance.log.warn "Recursive entry into dispatcher_start!\n#{caller.join("\n ")}" if Thread.current[:newrelic_t0]
15
+ Thread.current[:newrelic_t0] = t0
16
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_start t0
17
+ # capture the time spent in the mongrel queue, if running in mongrel. This is the
18
+ # current time less the timestamp placed in 'started_on' by mongrel.
19
+ mongrel_start = Thread.current[:started_on]
20
+ @@newrelic_mongrel_queue_stat.trace_call(t0 - mongrel_start.to_f) if mongrel_start
21
+ @@newrelic_agent.start_transaction
22
+
23
+ # Reset the flag indicating the controller action should be ignored.
24
+ # It may be set by the action to either true or false or left nil meaning false
25
+ Thread.current[:controller_ignored] = nil
26
+ end
27
+
28
+ def newrelic_dispatcher_finish
29
+ t0 = Thread.current[:newrelic_t0]
30
+ if t0.nil?
31
+ NewRelic::Config.instance.log.warn "Dispatcher finish called twice!\n#{caller.join("\n ")}"
32
+ return
33
+ end
34
+ t1 = Time.now.to_f
35
+ @@newrelic_agent.end_transaction
36
+ @@newrelic_rails_dispatch_stat.trace_call(t1 - t0) unless Thread.current[:controller_ignored]
37
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_finish t1
38
+ Thread.current[:newrelic_t0] = nil
39
+ end
40
+
41
+ def dispatch_newrelic(*args)
42
+ newrelic_dispatcher_start
43
+ begin
44
+ dispatch_without_newrelic(*args)
45
+ ensure
46
+ newrelic_dispatcher_finish
47
+ end
48
+ end
49
+
50
+ # This won't work with Rails 2.2 multi-threading
51
+ module BusyCalculator
52
+ extend self
53
+ # the fraction of the sample period that the dispatcher was busy
54
+ @instance_busy = NewRelic::Agent.agent.stats_engine.get_stats('Instance/Busy')
55
+ @harvest_start = Time.now.to_f
56
+ @accumulator = 0
57
+ @dispatcher_start = nil
58
+ def dispatcher_start(time)
59
+ Thread.critical = true
60
+ @dispatcher_start = time
61
+ Thread.critical = false
62
+ end
63
+
64
+ def dispatcher_finish(time)
65
+ Thread.critical = true
66
+ @accumulator += (time - @dispatcher_start)
67
+ @dispatcher_start = nil
68
+
69
+ Thread.critical = false
70
+ end
71
+
72
+ def is_busy?
73
+ @dispatcher_start
74
+ end
75
+
76
+ def harvest_busy
77
+ Thread.critical = true
78
+
79
+ busy = @accumulator
80
+ @accumulator = 0
81
+
82
+ t0 = Time.now.to_f
83
+
84
+ if @dispatcher_start
85
+ busy += (t0 - @dispatcher_start)
86
+ @dispatcher_start = t0
87
+ end
88
+
89
+
90
+ Thread.critical = false
91
+
92
+ busy = 0.0 if busy < 0.0 # don't go below 0%
93
+
94
+ time_window = (t0 - @harvest_start)
95
+ time_window = 1.0 if time_window == 0.0 # protect against divide by zero
96
+
97
+ busy = busy / time_window
98
+
99
+ busy = 1.0 if busy > 1.0 # cap at 100%
100
+ @instance_busy.record_data_point busy
101
+ @harvest_start = t0
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,18 @@
1
+ # NOTE there are multiple implementations of the MemCache client in Ruby,
2
+ # each with slightly different API's and semantics.
3
+ # Currently we only cover memcache-client. Need to cover Ruby-MemCache.
4
+ # See:
5
+ # http://www.deveiate.org/code/Ruby-MemCache/ (Gem: Ruby-MemCache)
6
+ # http://dev.robotcoop.com/Libraries/memcache-client/ (Gem: memcache-client)
7
+ MemCache.class_eval do
8
+ add_method_tracer :get, 'MemCache/read' if self.method_defined? :get
9
+ add_method_tracer :set, 'MemCache/write' if self.method_defined? :set
10
+ add_method_tracer :get_multi, 'MemCache/read' if self.method_defined? :get_multi
11
+ end if defined? MemCache
12
+
13
+ # Support for libmemcached through Evan Weaver's memcached wrapper
14
+ # http://blog.evanweaver.com/files/doc/fauna/memcached/classes/Memcached.html
15
+ Memcached.class_eval do
16
+ add_method_tracer :get, 'Memcached/read' if self.method_defined? :get
17
+ add_method_tracer :set, 'Memcached/write' if self.method_defined? :set
18
+ end if defined? Memcached
@@ -0,0 +1,17 @@
1
+ require 'set'
2
+ require 'merb-core/controller/abstract_controller'
3
+
4
+ Merb::AbstractController.class_eval do
5
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
6
+
7
+ class_inheritable_accessor :newrelic_ignore_attr
8
+
9
+ protected
10
+ # determine the path that is used in the metric name for
11
+ # the called controller action
12
+ def newrelic_metric_path(action)
13
+ "#{controller_name}/#{action}"
14
+ end
15
+ alias_method :perform_action_without_newrelic_trace, :_dispatch
16
+ alias_method :_dispatch, :perform_action_with_newrelic_trace
17
+ end
@@ -0,0 +1,15 @@
1
+ require 'merb-core/dispatch/dispatcher'
2
+ # NewRelic RPM instrumentation for http request dispatching (Routes mapping)
3
+ # Note, the dispatcher class from no module into into the ActionController modile
4
+ # in rails 2.0. Thus we need to check for both
5
+
6
+ Merb::Request.class_eval do
7
+
8
+ # This is for merb prior to 1.0
9
+ include NewRelic::Agent::Instrumentation::DispatcherInstrumentation
10
+ alias_method :dispatch_without_newrelic, :handle
11
+ alias_method :handle, :dispatch_newrelic
12
+
13
+ # After merb 1.0, you can use before and after callbacks
14
+ # for this?
15
+ end
@@ -0,0 +1,6 @@
1
+ # Hook in the notification to merb
2
+ error_notifier = Proc.new {
3
+ NewRelic::Agent.agent.error_collector.notice_error("#{request.controller.name}/#{params[:action]}", request.path, params, request.exceptions.first)
4
+ }
5
+ Merb::Dispatcher::DefaultException.before error_notifier
6
+ Exceptions.before error_notifier
@@ -0,0 +1,35 @@
1
+
2
+ if defined? ActionController
3
+
4
+ ActionController::Base.class_eval do
5
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
6
+
7
+ # Compare with #alias_method_chain, which is not available in
8
+ # Rails 1.1:
9
+ alias_method :perform_action_without_newrelic_trace, :perform_action
10
+ alias_method :perform_action, :perform_action_with_newrelic_trace
11
+ private :perform_action
12
+
13
+ add_method_tracer :render, 'View/#{newrelic_metric_path}/Rendering'
14
+
15
+ def self.newrelic_ignore_attr=(value)
16
+ write_inheritable_attribute('do_not_trace', value)
17
+ end
18
+ def self.newrelic_ignore_attr
19
+ read_inheritable_attribute('do_not_trace')
20
+ end
21
+
22
+ # determine the path that is used in the metric name for
23
+ # the called controller action
24
+ def newrelic_metric_path(action_name_override = nil)
25
+ action_part = action_name_override || action_name
26
+ if action_name_override || self.class.action_methods.include?(action_part)
27
+ "#{self.class.controller_path}/#{action_part}"
28
+ else
29
+ "#{self.class.controller_path}/(other)"
30
+ end
31
+ end
32
+ end
33
+ else
34
+ NewRelic::Agent.instance.log.debug "WARNING: ActionController instrumentation not added"
35
+ end
@@ -0,0 +1,27 @@
1
+ # NewRelic Agent instrumentation for WebServices
2
+
3
+ # Note Action Web Service is removed from default package in rails 2.0
4
+ if defined? ActionWebService
5
+
6
+ # instrumentation for Web Service martialing - XML RPC
7
+ ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.class_eval do
8
+ add_method_tracer :decode_request, "WebService/Xml Rpc/XML Decode"
9
+ add_method_tracer :encode_request, "WebService/Xml Rpc/XML Encode"
10
+ add_method_tracer :decode_response, "WebService/Xml Rpc/XML Decode"
11
+ add_method_tracer :encode_response, "WebService/Xml Rpc/XML Encode"
12
+ end
13
+
14
+ # instrumentation for Web Service martialing - Soap
15
+ ActionWebService::Protocol::Soap::SoapProtocol.class_eval do
16
+ add_method_tracer :decode_request, "WebService/Soap/XML Decode"
17
+ add_method_tracer :encode_request, "WebService/Soap/XML Encode"
18
+ add_method_tracer :decode_response, "WebService/Soap/XML Decode"
19
+ add_method_tracer :encode_response, "WebService/Soap/XML Encode"
20
+ end
21
+
22
+ ActionController::Base.class_eval do
23
+ if method_defined? :perform_invocation
24
+ add_method_tracer :perform_invocation, 'WebService/#{controller_name}/#{args.first}'
25
+ end
26
+ end if defined? ActionController::Base
27
+ end
@@ -0,0 +1,30 @@
1
+ require 'dispatcher'
2
+
3
+ # NewRelic RPM instrumentation for http request dispatching (Routes mapping)
4
+ # Note, the dispatcher class from no module into into the ActionController module
5
+ # in Rails 2.0. Thus we need to check for both
6
+ if defined? ActionController::Dispatcher
7
+ target = ActionController::Dispatcher
8
+ elsif defined? Dispatcher
9
+ target = Dispatcher
10
+ else
11
+ target = nil
12
+ end
13
+
14
+ if target
15
+ NewRelic::Agent.instance.log.debug "Adding #{target} instrumentation"
16
+
17
+ # in Rails 2.3 (Rack-based) we don't want to add instrumentation on class level
18
+ unless defined? ::Rails::Rack
19
+ target = target.class_eval { class << self; self; end }
20
+ end
21
+
22
+ target.class_eval do
23
+ include NewRelic::Agent::Instrumentation::DispatcherInstrumentation
24
+
25
+ alias_method :dispatch_without_newrelic, :dispatch
26
+ alias_method :dispatch, :dispatch_newrelic
27
+ end
28
+ else
29
+ NewRelic::Agent.instance.log.debug "WARNING: Dispatcher instrumentation not added"
30
+ end
@@ -0,0 +1,23 @@
1
+ # todo: patch rescue_action and track how many are occuring and capture instances as well
2
+ ActionController::Base.class_eval do
3
+
4
+ def newrelic_notice_error(exception)
5
+ local_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
6
+
7
+ NewRelic::Agent.agent.error_collector.notice_error(newrelic_metric_path, (request) ? request.path : nil,
8
+ local_params, exception)
9
+ end
10
+
11
+ def rescue_action_with_newrelic_trace(exception)
12
+ newrelic_notice_error exception
13
+
14
+ rescue_action_without_newrelic_trace exception
15
+ end
16
+
17
+ # Compare with #alias_method_chain, which is not available in
18
+ # Rails 1.1:
19
+ alias_method :rescue_action_without_newrelic_trace, :rescue_action
20
+ alias_method :rescue_action, :rescue_action_with_newrelic_trace
21
+ protected :rescue_action
22
+
23
+ end if defined? ActionController
@@ -0,0 +1,6 @@
1
+ # Instrument the compilation of ERB files.
2
+ ERB::Compiler.class_eval do
3
+ add_method_tracer :compile, 'View/.rhtml Processing'
4
+ end
5
+
6
+
@@ -0,0 +1,171 @@
1
+
2
+ class Module
3
+
4
+ # Original method preserved for API backward compatibility
5
+ def trace_method_execution (metric_name, push_scope, produce_metric, deduct_call_time_from_parent, &block)
6
+ if push_scope
7
+ trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent, &block)
8
+ else
9
+ trace_method_execution_no_scope(metric_name, &block)
10
+ end
11
+ end
12
+
13
+ # This is duplicated inline in add_method_tracer
14
+ def trace_method_execution_no_scope(metric_name)
15
+ t0 = Time.now.to_f
16
+ stats = @@newrelic_stats_engine.get_stats_no_scope metric_name
17
+
18
+ result = yield
19
+ duration = Time.now.to_f - t0 # for some reason this is 3 usec faster than Time - Time
20
+ stats.trace_call(duration, duration)
21
+ result
22
+ end
23
+
24
+ def trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent)
25
+
26
+ t0 = Time.now.to_f
27
+ stats = nil
28
+
29
+ begin
30
+ # Keep a reference to the scope we are pushing so we can do a sanity check making
31
+ # sure when we pop we get the one we 'expected'
32
+ expected_scope = @@newrelic_stats_engine.push_scope(metric_name, t0, deduct_call_time_from_parent)
33
+
34
+ stats = @@newrelic_stats_engine.get_stats metric_name, true if produce_metric
35
+ rescue => e
36
+ NewRelic::Config.instance.log.error("Caught exception in trace_method_execution header. Metric name = #{metric_name}, exception = #{e}")
37
+ NewRelic::Config.instance.log.error(e.backtrace.join("\n"))
38
+ end
39
+
40
+ begin
41
+ yield
42
+ ensure
43
+ t1 = Time.now.to_f
44
+ duration = t1 - t0
45
+
46
+ begin
47
+ if expected_scope
48
+ scope = @@newrelic_stats_engine.pop_scope expected_scope, duration, t1
49
+
50
+ exclusive = duration - scope.children_time
51
+ stats.trace_call(duration, exclusive) if stats
52
+ end
53
+ rescue => e
54
+ NewRelic::Config.instance.log.error("Caught exception in trace_method_execution footer. Metric name = #{metric_name}, exception = #{e}")
55
+ NewRelic::Config.instance.log.error(e.backtrace.join("\n"))
56
+ end
57
+ end
58
+ end
59
+
60
+ # Add a method tracer to the specified method.
61
+ # metric_name_code is ruby code that determines the name of the
62
+ # metric to be collected during tracing. As such, the code
63
+ # should be provided in 'single quote' strings rather than
64
+ # "double quote" strings, so that #{} evaluation happens
65
+ # at traced method execution time.
66
+ # Example: tracing a method :foo, where the metric name is
67
+ # the first argument converted to a string
68
+ # add_method_tracer :foo, '#{args.first.to_s}'
69
+ # statically defined metric names can be specified as regular strings
70
+ # push_scope specifies whether this method tracer should push
71
+ # the metric name onto the scope stack.
72
+ def add_method_tracer (method_name, metric_name_code, options = {})
73
+ return unless NewRelic::Agent.agent.config.tracers_enabled?
74
+
75
+ @@newrelic_stats_engine ||= NewRelic::Agent.agent.stats_engine
76
+
77
+ if !options.is_a?(Hash)
78
+ options = {:push_scope => options}
79
+ end
80
+ # options[:push_scope] true if we are noting the scope of this for
81
+ # stats collection as well as the transaction tracing
82
+ options[:push_scope] = true if options[:push_scope].nil?
83
+ # options[:metric] true if you are tracking stats for a metric, otherwise
84
+ # it's just for transaction tracing.
85
+ options[:metric] = true if options[:metric].nil?
86
+ options[:deduct_call_time_from_parent] = false if options[:deduct_call_time_from_parent].nil? && !options[:metric]
87
+ options[:deduct_call_time_from_parent] = true if options[:deduct_call_time_from_parent].nil?
88
+ options[:code_header] ||= ""
89
+ options[:code_footer] ||= ""
90
+
91
+ klass = (self === Module) ? "self" : "self.class"
92
+
93
+ unless method_defined?(method_name) || private_method_defined?(method_name)
94
+ NewRelic::Config.instance.log.warn("Did not trace #{self}##{method_name} because that method does not exist")
95
+ return
96
+ end
97
+
98
+ traced_method_name = _traced_method_name(method_name, metric_name_code)
99
+ if method_defined? traced_method_name
100
+ NewRelic::Config.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}")
101
+ return
102
+ end
103
+
104
+ fail "Can't add a tracer where push_scope is false and metric is false" if options[:push_scope] == false && !options[:metric]
105
+
106
+ if options[:push_scope] == false
107
+ class_eval "@@newrelic_stats_engine = NewRelic::Agent.agent.stats_engine"
108
+ code = <<-CODE
109
+ def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
110
+ #{options[:code_header]}
111
+
112
+ t0 = Time.now.to_f
113
+ stats = @@newrelic_stats_engine.get_stats_no_scope "#{metric_name_code}"
114
+
115
+ result = #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
116
+ duration = Time.now.to_f - t0
117
+ stats.trace_call(duration, duration) # for some reason this is 3 usec faster than Time - Time
118
+ #{options[:code_footer]}
119
+ result
120
+ end
121
+ CODE
122
+ else
123
+ code = <<-CODE
124
+ def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
125
+ #{options[:code_header]}
126
+ result = #{klass}.trace_method_execution_with_scope("#{metric_name_code}", #{options[:metric]}, #{options[:deduct_call_time_from_parent]}) do
127
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
128
+ end
129
+ #{options[:code_footer]}
130
+ result
131
+ end
132
+ CODE
133
+ end
134
+
135
+ class_eval code, __FILE__, __LINE__
136
+
137
+ alias_method _untraced_method_name(method_name, metric_name_code), method_name
138
+ alias_method method_name, _traced_method_name(method_name, metric_name_code)
139
+
140
+ NewRelic::Config.instance.log.debug("Traced method: class = #{self}, method = #{method_name}, "+
141
+ "metric = '#{metric_name_code}', options: #{options}, ")
142
+ end
143
+
144
+ # Not recommended for production use, because tracers must be removed in reverse-order
145
+ # from when they were added, or else other tracers that were added to the same method
146
+ # may get removed as well.
147
+ def remove_method_tracer(method_name, metric_name_code)
148
+ return unless NewRelic::Agent.agent.config.tracers_enabled?
149
+
150
+ if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
151
+ alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
152
+ undef_method "#{_traced_method_name(method_name, metric_name_code)}"
153
+ else
154
+ raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
155
+ end
156
+ end
157
+
158
+ private
159
+
160
+ def _untraced_method_name(method_name, metric_name)
161
+ "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
162
+ end
163
+
164
+ def _traced_method_name(method_name, metric_name)
165
+ "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
166
+ end
167
+
168
+ def _sanitize_name(name)
169
+ name.to_s.tr('^a-z,A-Z,0-9', '_')
170
+ end
171
+ end