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,113 @@
1
+ class NewRelic::Config::Rails < NewRelic::Config
2
+
3
+ def app; :rails; end
4
+
5
+ def env
6
+ RAILS_ENV
7
+ end
8
+ def root
9
+ RAILS_ROOT
10
+ end
11
+
12
+ def log_path
13
+ path = ::RAILS_DEFAULT_LOGGER.instance_eval do
14
+ File.dirname(@log.path) rescue File.dirname(@logdev.filename)
15
+ end rescue "#{root}/log"
16
+ File.expand_path(path)
17
+ end
18
+
19
+ def start_plugin(rails_config=nil)
20
+ if !tracers_enabled?
21
+ require 'new_relic/shim_agent'
22
+ return
23
+ end
24
+ app_config_info
25
+ start_agent
26
+ install_developer_mode rails_config if developer_mode?
27
+ end
28
+
29
+ def install_developer_mode(rails_config)
30
+ controller_path = File.join(newrelic_root, 'ui', 'controllers')
31
+ helper_path = File.join(newrelic_root, 'ui', 'helpers')
32
+ $LOAD_PATH << controller_path
33
+ $LOAD_PATH << helper_path
34
+
35
+ if defined? ActiveSupport::Dependencies
36
+ ActiveSupport::Dependencies.load_paths << controller_path
37
+ ActiveSupport::Dependencies.load_paths << helper_path
38
+ elsif defined? Dependencies.load_paths
39
+ Dependencies.load_paths << controller_path
40
+ Dependencies.load_paths << helper_path
41
+ else
42
+ to_stderr "ERROR: Rails version #{(RAILS_GEM_VERSION) ? RAILS_GEM_VERSION : ''} too old for developer mode to work."
43
+ return
44
+ end
45
+
46
+ # This is a monkey patch to inject the developer tool route into the
47
+ # parent app without requiring users to modify their routes. Of course this
48
+ # has the effect of adding a route indiscriminately which is frowned upon by
49
+ # some: http://www.ruby-forum.com/topic/126316#563328
50
+ ActionController::Routing::RouteSet.class_eval do
51
+ next if defined? draw_with_newrelic_map
52
+ def draw_with_newrelic_map
53
+ draw_without_newrelic_map do | map |
54
+ map.named_route 'newrelic_developer', '/newrelic/:action/:id', :controller => 'newrelic' unless NewRelic::Config.instance['skip_developer_route']
55
+ yield map
56
+ end
57
+ end
58
+ alias_method_chain :draw, :newrelic_map
59
+ end
60
+
61
+ # If we have the config object then add the controller path to the list.
62
+ # Otherwise we have to assume the controller paths have already been
63
+ # set and we can just append newrelic.
64
+
65
+ if rails_config
66
+ rails_config.controller_paths << controller_path
67
+ else
68
+ current_paths = ActionController::Routing.controller_paths
69
+ if current_paths.nil? || current_paths.empty?
70
+ to_stderr "WARNING: Unable to modify the routes in this version of Rails. Developer mode not available."
71
+ end
72
+ current_paths << controller_path
73
+ end
74
+
75
+ #ActionController::Routing::Routes.reload! unless NewRelic::Config.instance['skip_developer_route']
76
+
77
+ # inform user that the dev edition is available if we are running inside
78
+ # a webserver process
79
+ if local_env.identifier
80
+ port = local_env.identifier.to_s =~ /^\d+/ ? ":#{local_env.identifier}" : ":port"
81
+ to_stderr "NewRelic Agent (Developer Mode) enabled."
82
+ to_stderr "To view performance information, go to http://localhost#{port}/newrelic"
83
+ end
84
+ end
85
+
86
+ protected
87
+
88
+ # Collect the Rails::Info into an associative array as well as the list of plugins
89
+ def gather_info
90
+ i = [[:app, app]]
91
+ begin
92
+ begin
93
+ require 'rails/info'
94
+ rescue LoadError
95
+ require 'builtin/rails_info/rails/info'
96
+ end
97
+ i += ::Rails::Info.properties
98
+ rescue SecurityError, ScriptError, StandardError => e
99
+ log.debug "Unable to get the Rails info: #{e.inspect}"
100
+ end
101
+
102
+ # Would like to get this from config, but how?
103
+ plugins = Dir[File.join(File.expand_path(__FILE__+"/../../../../.."),"/*")].collect { |p| File.basename p }
104
+ i << ['Plugin List', plugins]
105
+
106
+ # Look for a capistrano file indicating the current revision:
107
+ rev_file = File.expand_path(File.join(RAILS_ROOT, "REVISION"))
108
+ if File.readable?(rev_file) && File.size(rev_file) < 64
109
+ File.open(rev_file) { | file | i << ['Revision', file.read] } rescue nil
110
+ end
111
+ i
112
+ end
113
+ end
@@ -0,0 +1,9 @@
1
+ class NewRelic::Config::Ruby < NewRelic::Config
2
+ def app; :ruby; end
3
+ def env
4
+ ENV['RUBY_ENV'] || 'development'
5
+ end
6
+ def root
7
+ Dir['.']
8
+ end
9
+ end
@@ -0,0 +1,108 @@
1
+ # An instance of LocalEnvironment will provide the environment name
2
+ # and locally unique identifier for this agent's host.
3
+ # If the environment can't be determined, it will be set to
4
+ # :unknown and identifier will have nil
5
+ module NewRelic
6
+ class LocalEnvironment
7
+
8
+ attr_reader :environment, :identifier
9
+
10
+ # determine the environment we are running in (one of :webrick,
11
+ # :mongrel, :thin, or :unknown) and if the process is listening
12
+ # on a port, use the port # that we are listening on.
13
+ def initialize
14
+ # Note: log won't be available yet.
15
+ @identifier = nil
16
+ @environment = :unknown
17
+ environments = %w[merb jruby webrick mongrel thin litespeed passenger daemon]
18
+ while environments.any? && @identifier.nil?
19
+ send 'check_for_'+(environments.shift)
20
+ end
21
+ end
22
+ def to_s
23
+ "LocalEnvironment[#{environment}:#{identifier}]"
24
+ end
25
+ def check_for_merb
26
+ if config.app == :merb
27
+ @identifier = 'merb'
28
+ end
29
+ end
30
+ def check_for_webrick
31
+ if defined?(OPTIONS) && OPTIONS.respond_to?(:fetch)
32
+ # OPTIONS is set by script/server
33
+ @identifier = OPTIONS.fetch(:port)
34
+ @environment = :webrick
35
+ end
36
+ end
37
+
38
+ # this case covers starting by mongrel_rails
39
+ def check_for_mongrel
40
+ if defined? Mongrel::HttpServer
41
+ ObjectSpace.each_object(Mongrel::HttpServer) do |mongrel|
42
+ next if not mongrel.respond_to? :port
43
+ @environment = :mongrel
44
+ @identifier = mongrel.port
45
+ end
46
+ end
47
+ end
48
+
49
+ def check_for_thin
50
+ if defined? Thin::Server
51
+ # This case covers the thin web server
52
+ # Same issue as above- we assume only one instance per process
53
+ ObjectSpace.each_object(Thin::Server) do |thin_server|
54
+ @environment = :thin
55
+ backend = thin_server.backend
56
+ # We need a way to uniquely identify and distinguish agents. The port
57
+ # works for this. When using sockets, use the socket file name.
58
+ if backend.respond_to? :port
59
+ @identifier = backend.port
60
+ elsif backend.respond_to? :socket
61
+ @identifier = backend.socket
62
+ else
63
+ raise "Unknown thin backend: #{backend}"
64
+ end
65
+ end # each thin instance
66
+ end
67
+ end
68
+
69
+ def check_for_jruby
70
+ if RUBY_PLATFORM =~ /java/
71
+ # Check for JRuby environment. Not sure how this works in different appservers
72
+ require 'java'
73
+ require 'jruby'
74
+ @environment = :jruby
75
+ @identifier = 'jruby'
76
+ @identifier += ":#{config['app_name']}" if config['app_name']
77
+ end
78
+ end
79
+
80
+ def check_for_litespeed
81
+ if caller.pop =~ /fcgi-bin\/RailsRunner\.rb/
82
+ @environment = :litespeed
83
+ @identifier = 'litespeed'
84
+ @identifier += ":#{config['app_name']}" if config['app_name']
85
+ end
86
+ end
87
+
88
+ def check_for_passenger
89
+ if defined? Passenger::AbstractServer || defined? IN_PHUSION_PASSENGER
90
+ @environment = :passenger
91
+ @identifier = 'passenger'
92
+ @identifier += ":#{config['app_name']}" if config['app_name']
93
+ end
94
+ end
95
+
96
+ def check_for_daemon
97
+ if config['monitor_daemons']
98
+ @environment = :daemon
99
+ # return the base part of the file name
100
+ @identifier = File.basename($0).split(".").first
101
+ end
102
+ end
103
+ private
104
+ def config
105
+ NewRelic::Config.instance
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,6 @@
1
+ namespace :newrelic do
2
+ desc "Install the developer mode newrelic.yml file"
3
+ task :default do
4
+ load File.expand_path(File.join(__FILE__,"..","..","install.rb"))
5
+ end
6
+ end
@@ -0,0 +1,26 @@
1
+ module NewRelic
2
+ class MetricData
3
+ attr_accessor :metric_spec
4
+ attr_accessor :metric_id
5
+ attr_accessor :stats
6
+
7
+ def initialize(metric_spec, stats, metric_id)
8
+ self.metric_spec = metric_spec
9
+ self.stats = stats
10
+ self.metric_id = metric_id
11
+ end
12
+
13
+ def eql?(o)
14
+ (metric_spec.eql? o.metric_spec) && (stats.eql? o.stats)
15
+ end
16
+
17
+ def hash
18
+ metric_spec.hash + stats.hash
19
+ end
20
+
21
+ def to_s
22
+ "#{metric_spec.name}(#{metric_spec.scope}): #{stats}" if metric_spec
23
+ "#{metric_id}: #{stats}" if metric_spec.nil?
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ # this struct uniquely defines a metric, optionally inside
2
+ # the call scope of another metric
3
+ class NewRelic::MetricSpec
4
+ attr_accessor :name
5
+ attr_accessor :scope
6
+
7
+ def initialize (name, scope = '')
8
+ self.name = name
9
+ self.scope = scope
10
+ end
11
+
12
+ def eql? (o)
13
+ scope_equal = scope.nil? ? o.scope.nil? : scope.eql?(o.scope)
14
+ name.eql?(o.name) && scope_equal
15
+ end
16
+
17
+ def hash
18
+ h = name.hash
19
+ h += scope.hash unless scope.nil?
20
+ h
21
+ end
22
+
23
+ def <=>(o)
24
+ namecmp = name <=> o.name
25
+ return namecmp if namecmp != 0
26
+
27
+ # i'm sure there's a more elegant way to code this correctly, but at least this passes
28
+ # my unit test
29
+ if scope.nil? && o.scope.nil?
30
+ return 0
31
+ elsif scope.nil?
32
+ return -1
33
+ elsif o.scope.nil?
34
+ return 1
35
+ else
36
+ return scope <=> o.scope
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,7 @@
1
+ module NewRelic::Metrics
2
+ CONTROLLER = "Controller"
3
+ DISPATCHER = "Rails/HTTP Dispatch"
4
+ ACTIVE_RECORD = "ActiveRecord"
5
+ USER_TIME = "CPU/User Time"
6
+ MEMORY = "Memory/Physical"
7
+ end
@@ -0,0 +1,21 @@
1
+ # this class encapsulates an error that was noticed by RPM in a managed app.
2
+ # Unfortunately it was put in the agent in the global namespace early on and
3
+ # for backward compatibility it needs to remain here.
4
+ class NewRelic::NoticedError
5
+ attr_accessor :path, :timestamp, :params, :exception_class, :message
6
+
7
+ def initialize(path, data, exception)
8
+ self.path = path
9
+ self.params = data
10
+
11
+ self.exception_class = exception.class.name
12
+
13
+ if exception.respond_to?('original_exception')
14
+ self.message = exception.original_exception.message
15
+ else
16
+ self.message = exception.message
17
+ end
18
+
19
+ self.timestamp = Time.now
20
+ end
21
+ end
@@ -0,0 +1,95 @@
1
+ #require 'new_relic/stats'
2
+
3
+ # This agent is loaded by the plug when the plug-in is disabled
4
+ # It recreates just enough of the API to not break any clients that
5
+ # invoke the Agent
6
+
7
+
8
+ # from method_tracer.rb
9
+
10
+ class Module
11
+
12
+ def trace_method_execution (*args)
13
+ yield
14
+ end
15
+
16
+ def add_method_tracer (*args)
17
+ end
18
+
19
+ def remove_method_tracer(*args)
20
+ end
21
+
22
+ end
23
+
24
+
25
+ # from agent.rb
26
+
27
+ module NewRelic
28
+ module Agent
29
+
30
+ class << self
31
+ @@dummy_stats = MethodTraceStats.new
32
+ def agent
33
+ NewRelic::Agent::Agent.instance
34
+ end
35
+
36
+ alias instance agent
37
+
38
+ def get_stats(*args)
39
+ @@dummy_stats
40
+ end
41
+ def get_stats_no_scope(*args)
42
+ @@dummy_stats
43
+ end
44
+
45
+ def manual_start(*args)
46
+ end
47
+
48
+ def set_sql_obfuscator(*args)
49
+ end
50
+
51
+ def disable_sql_recording
52
+ yield
53
+ end
54
+
55
+ def disable_transaction_tracing
56
+ yield
57
+ end
58
+
59
+ def add_request_parameters(*args)
60
+ end
61
+
62
+ def should_ignore_error
63
+ end
64
+ end
65
+
66
+ class Agent
67
+
68
+ def initialize
69
+ @error_collector = ErrorCollector.new
70
+ end
71
+ def self.instance
72
+ @@agent ||= new
73
+ end
74
+ end
75
+
76
+ class ErrorCollector
77
+ def notice_error(*args)
78
+ end
79
+ end
80
+
81
+ end
82
+ end
83
+
84
+
85
+ module ActionController
86
+ class Base
87
+ def newrelic_notice_error(*args); end
88
+ def self.newrelic_ignore(*args); end
89
+ def new_relic_trace_controller_action(name); yield; end
90
+ def newrelic_metric_path; end
91
+ def perform_action_with_newrelic_trace(path=nil)
92
+ yield
93
+ end
94
+ end
95
+ end if defined? ActionController::Base
@@ -0,0 +1,359 @@
1
+
2
+ module NewRelic
3
+ module Stats
4
+
5
+ def absent?
6
+ # guess on absent values
7
+ call_count == 0
8
+ end
9
+
10
+ def average_call_time
11
+ return 0 if call_count == 0
12
+ total_call_time / call_count
13
+ end
14
+ def average_exclusive_time
15
+ return 0 if call_count == 0
16
+ total_exclusive_time / call_count
17
+ end
18
+
19
+ def merge! (other_stats)
20
+ Array(other_stats).each do |s|
21
+ self.total_call_time += s.total_call_time
22
+ self.total_exclusive_time += s.total_exclusive_time
23
+ self.min_call_time = s.min_call_time if (s.min_call_time < min_call_time && s.call_count > 0) || call_count == 0
24
+ self.max_call_time = s.max_call_time if s.max_call_time > max_call_time
25
+ self.call_count += s.call_count
26
+ self.sum_of_squares += s.sum_of_squares if s.sum_of_squares
27
+ self.begin_time = s.begin_time if s.begin_time.to_f < begin_time.to_f || begin_time.to_f == 0.0
28
+ self.end_time = s.end_time if s.end_time.to_f > end_time.to_f
29
+ end
30
+
31
+ self
32
+ end
33
+
34
+ def merge (other_stats)
35
+ stats = self.clone
36
+ stats.merge! other_stats
37
+ end
38
+
39
+ # split into an array of timesclices whose
40
+ # time boundaries start on (begin_time + (n * duration)) and whose
41
+ # end time ends on (begin_time * (n + 1) * duration), except for the
42
+ # first and last elements, whose begin time and end time are the begin
43
+ # and end times of this stats instance, respectively. Yield to caller
44
+ # for the code that creates the actual stats instance
45
+ def split(rollup_begin_time, rollup_period)
46
+ rollup_begin_time = rollup_begin_time.to_f
47
+ rollup_begin_time += ((self.begin_time - rollup_begin_time) / rollup_period).floor * rollup_period
48
+
49
+ current_begin_time = self.begin_time
50
+ current_end_time = rollup_begin_time + rollup_period
51
+
52
+ return [self] if current_end_time >= self.end_time
53
+
54
+ timeslices = []
55
+ while current_end_time < self.end_time do
56
+ ts = yield(current_begin_time, current_end_time)
57
+
58
+ ts.fraction_of(self)
59
+ timeslices << ts
60
+ current_begin_time = current_end_time
61
+ current_end_time = current_begin_time + rollup_period
62
+ end
63
+
64
+ if self.end_time > current_begin_time
65
+ percentage = rollup_period / self.duration + (self.begin_time - rollup_begin_time) / rollup_period
66
+ ts = yield(current_begin_time, self.end_time)
67
+ ts.fraction_of(self)
68
+ timeslices << ts
69
+ end
70
+
71
+ timeslices
72
+ end
73
+
74
+ def reset
75
+ self.call_count = 0
76
+ self.total_call_time = 0.0
77
+ self.total_exclusive_time = 0.0
78
+ self.min_call_time = 0.0
79
+ self.max_call_time = 0.0
80
+ self.sum_of_squares = 0.0
81
+ self.begin_time = Time.at(0)
82
+ self.end_time = Time.at(0)
83
+ end
84
+
85
+ def as_percentage_of(other_stats)
86
+ return 0 if other_stats.total_call_time == 0
87
+ return (total_call_time / other_stats.total_call_time).to_percentage
88
+ end
89
+
90
+ # the stat total_call_time is a percent
91
+ def as_percentage
92
+ return 0 if call_count == 0
93
+ (total_call_time / call_count).to_percentage
94
+ end
95
+
96
+ def duration
97
+ end_time - begin_time
98
+ end
99
+
100
+ def calls_per_minute
101
+ return 0 if duration.zero?
102
+ ((call_count / duration.to_f * 6000).round).to_f / 100
103
+ end
104
+
105
+ def calls_per_second
106
+ (calls_per_minute / 60).round_to(2)
107
+ end
108
+
109
+ def standard_deviation
110
+ return 0 if call_count < 2 || self.sum_of_squares.nil?
111
+
112
+ # Convert sum of squares into standard deviation based on
113
+ # formula for the standard deviation for the entire population
114
+ x = self.sum_of_squares - (self.call_count * (self.average_value**2))
115
+ return 0 if x <= 0
116
+
117
+ Math.sqrt(x / self.call_count)
118
+ end
119
+
120
+ # returns the time spent in this component as a percentage of the total
121
+ # time window.
122
+ def time_percentage
123
+ return 0 if duration == 0
124
+ total_call_time / duration
125
+ end
126
+
127
+ def exclusive_time_percentage
128
+ return 0 if duration == 0
129
+ total_exclusive_time / duration
130
+ end
131
+
132
+ alias average_value average_call_time
133
+ alias average_response_time average_call_time
134
+
135
+ def to_s
136
+ s = "Begin=#{begin_time}, "
137
+ s << "Duration=#{duration} s, "
138
+ s << "Count=#{call_count}, "
139
+ s << "Total=#{total_call_time.to_ms}, "
140
+ s << "Total Exclusive=#{total_exclusive_time.to_ms}, "
141
+ s << "Avg=#{average_call_time.to_ms}, "
142
+ s << "Min=#{min_call_time.to_ms}, "
143
+ s << "Max=#{max_call_time.to_ms}, "
144
+ s << "StdDev=#{standard_deviation.to_ms}"
145
+ end
146
+
147
+ # Summary string to facilitate testing
148
+ def summary
149
+ format = "%m/%d %I:%M%p"
150
+ "[#{Time.at(begin_time).strftime(format)}, #{duration}s. #{call_count} calls; #{average_call_time.to_ms}ms]"
151
+ end
152
+
153
+ # round all of the values to n decimal points
154
+ def round!(decimal_places = 3)
155
+ self.total_call_time = total_call_time.round_to(decimal_places)
156
+ self.total_exclusive_time = total_exclusive_time.round_to(decimal_places)
157
+ self.min_call_time = min_call_time.round_to(decimal_places)
158
+ self.max_call_time = max_call_time.round_to(decimal_places)
159
+ self.sum_of_squares = sum_of_squares.round_to(decimal_places)
160
+ self.begin_time = begin_time.round
161
+ self.end_time = end_time.round
162
+ end
163
+
164
+ # calculate this set of stats to be a percentage fraction
165
+ # of the provided stats, which has an overlapping time window.
166
+ # used as a key part of the split algorithm
167
+ def fraction_of(s)
168
+ min_end = (end_time < s.end_time ? end_time : s.end_time)
169
+ max_begin = (begin_time > s.begin_time ? begin_time : s.begin_time)
170
+ percentage = (min_end - max_begin) / s.duration
171
+
172
+ self.total_call_time = s.total_call_time * percentage
173
+ self.min_call_time = s.min_call_time
174
+ self.max_call_time = s.max_call_time
175
+ self.call_count = s.call_count * percentage
176
+ self.sum_of_squares = (s.sum_of_squares || 0) * percentage
177
+ end
178
+
179
+ # multiply the total time and rate by the given percentage
180
+ def multiply_by(percentage)
181
+ self.total_call_time = total_call_time * percentage
182
+ self.call_count = call_count * percentage
183
+ self.sum_of_squares = sum_of_squares * percentage
184
+
185
+ self
186
+ end
187
+
188
+ def get_apdex
189
+ [@call_count, @total_call_time, @total_exclusive_time, @sum_of_squares]
190
+ end
191
+
192
+ end
193
+
194
+ # Statistics used to track the performance of traced methods
195
+ class MethodTraceStats
196
+ include Stats
197
+
198
+ attr_accessor :call_count
199
+ attr_accessor :min_call_time
200
+ attr_accessor :max_call_time
201
+ attr_accessor :total_call_time
202
+ attr_accessor :total_exclusive_time
203
+ attr_accessor :sum_of_squares
204
+
205
+ alias data_point_count call_count
206
+
207
+ def initialize
208
+ reset
209
+ end
210
+
211
+ # record a single data point into the statistical gatherer. The gatherer
212
+ # will aggregate all data points collected over a specified period and upload
213
+ # its data to the NewRelic server
214
+ def record_data_point(value, exclusive_time = value)
215
+ @call_count += 1
216
+ @total_call_time += value
217
+ @min_call_time = value if value < @min_call_time || @call_count == 1
218
+ @max_call_time = value if value > @max_call_time
219
+ @total_exclusive_time += exclusive_time
220
+
221
+ @sum_of_squares += (value * value)
222
+ self
223
+ end
224
+
225
+ alias trace_call record_data_point
226
+
227
+ def record_apdex_s(cpu)
228
+ @call_count += 1
229
+ @sum_of_squares += cpu
230
+ end
231
+
232
+ def record_apdex_t(cpu)
233
+ @total_call_time += 1
234
+ @sum_of_squares += cpu
235
+ end
236
+
237
+ def record_apdex_f(cpu)
238
+ @total_exclusive_time += 1
239
+ @sum_of_squares += cpu
240
+ end
241
+
242
+ def increment_count(value = 1)
243
+ @call_count += value
244
+ end
245
+
246
+
247
+ def freeze
248
+ @end_time = Time.now
249
+ super
250
+ end
251
+
252
+ # In this class, we explicitly don't track begin and end time here, to save space during
253
+ # cross process serialization via xml. Still the accessor methods must be provided for merge to work.
254
+ def begin_time=(t)
255
+ end
256
+
257
+ def end_time=(t)
258
+ end
259
+
260
+ def begin_time
261
+ 0.0
262
+ end
263
+
264
+ def end_time
265
+ 0.0
266
+ end
267
+ end
268
+
269
+ class ScopedMethodTraceStats < MethodTraceStats
270
+ def initialize(unscoped_stats)
271
+ super()
272
+
273
+ @unscoped_stats = unscoped_stats
274
+ end
275
+
276
+ def trace_call(call_time, exclusive_time = call_time)
277
+ @unscoped_stats.trace_call call_time, exclusive_time
278
+ super call_time, exclusive_time
279
+ end
280
+ end
281
+ end
282
+
283
+ class Numeric
284
+
285
+ # copied from rails
286
+ def with_delimiter(delimiter=",", separator=".")
287
+ begin
288
+ parts = self.to_s.split('.')
289
+ parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
290
+ parts.join separator
291
+ rescue
292
+ self
293
+ end
294
+ end
295
+
296
+ # utlity method that converts floating point time values in seconds
297
+ # to integers in milliseconds, to improve readability in ui
298
+ def to_ms(decimal_places = 0)
299
+ (self * 1000).round_to(decimal_places)
300
+ end
301
+
302
+ # return the number of decimal points that this number would best render in
303
+ #
304
+ def get_number_decimals_ms
305
+ base = 0.010
306
+ decimal = 0
307
+
308
+ while decimal <= 6 && self < base do
309
+ base /= 10.0
310
+ decimal += 1
311
+ end
312
+
313
+ decimal
314
+ end
315
+
316
+ # auto-adjust the precision based on the value
317
+ def to_smart_ms
318
+ to_ms get_number_decimals_ms
319
+ end
320
+
321
+
322
+ def to_ns(decimal_places = 0)
323
+ (self * 1000000).round_to(decimal_places)
324
+ end
325
+
326
+ def to_minutes(decimal_places = 0)
327
+ (self / 60).round_to(decimal_places)
328
+ end
329
+
330
+ # utility method that converts floating point percentage values
331
+ # to integers as a percentage, to improve readability in ui
332
+ def to_percentage(decimal_places = 2)
333
+ (self * 100).round_to(decimal_places)
334
+ end
335
+
336
+ def round_to(decimal_places)
337
+ x = self
338
+ decimal_places.times do
339
+ x = x * 10
340
+ end
341
+ x = x.round
342
+ decimal_places.times do
343
+ x = x.to_f / 10
344
+ end
345
+ x
346
+ end
347
+
348
+ def round_to_1
349
+ round_to(1)
350
+ end
351
+
352
+ def round_to_2
353
+ round_to(2)
354
+ end
355
+
356
+ def round_to_3
357
+ round_to(3)
358
+ end
359
+ end