techarch-newrelic_rpm 2.10.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. data/CHANGELOG +335 -0
  2. data/LICENSE +37 -0
  3. data/Manifest +159 -0
  4. data/README.md +138 -0
  5. data/Rakefile +22 -0
  6. data/bin/mongrel_rpm +33 -0
  7. data/bin/newrelic_cmd +4 -0
  8. data/cert/cacert.pem +34 -0
  9. data/init.rb +38 -0
  10. data/install.rb +45 -0
  11. data/lib/new_relic/agent.rb +281 -0
  12. data/lib/new_relic/agent/agent.rb +636 -0
  13. data/lib/new_relic/agent/chained_call.rb +13 -0
  14. data/lib/new_relic/agent/collection_helper.rb +66 -0
  15. data/lib/new_relic/agent/error_collector.rb +121 -0
  16. data/lib/new_relic/agent/instrumentation/active_merchant.rb +18 -0
  17. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +88 -0
  18. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  19. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +437 -0
  20. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  21. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +167 -0
  22. data/lib/new_relic/agent/instrumentation/memcache.rb +24 -0
  23. data/lib/new_relic/agent/instrumentation/merb/controller.rb +26 -0
  24. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +13 -0
  25. data/lib/new_relic/agent/instrumentation/merb/errors.rb +8 -0
  26. data/lib/new_relic/agent/instrumentation/net.rb +23 -0
  27. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +20 -0
  28. data/lib/new_relic/agent/instrumentation/rack.rb +108 -0
  29. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +59 -0
  30. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  31. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +41 -0
  32. data/lib/new_relic/agent/instrumentation/rails/errors.rb +27 -0
  33. data/lib/new_relic/agent/instrumentation/sinatra.rb +39 -0
  34. data/lib/new_relic/agent/method_tracer.rb +349 -0
  35. data/lib/new_relic/agent/patch_const_missing.rb +125 -0
  36. data/lib/new_relic/agent/sampler.rb +12 -0
  37. data/lib/new_relic/agent/samplers/cpu_sampler.rb +49 -0
  38. data/lib/new_relic/agent/samplers/memory_sampler.rb +138 -0
  39. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +22 -0
  40. data/lib/new_relic/agent/shim_agent.rb +21 -0
  41. data/lib/new_relic/agent/stats_engine.rb +22 -0
  42. data/lib/new_relic/agent/stats_engine/metric_stats.rb +111 -0
  43. data/lib/new_relic/agent/stats_engine/samplers.rb +71 -0
  44. data/lib/new_relic/agent/stats_engine/transactions.rb +152 -0
  45. data/lib/new_relic/agent/transaction_sampler.rb +310 -0
  46. data/lib/new_relic/agent/worker_loop.rb +118 -0
  47. data/lib/new_relic/commands/deployments.rb +145 -0
  48. data/lib/new_relic/commands/new_relic_commands.rb +30 -0
  49. data/lib/new_relic/control.rb +473 -0
  50. data/lib/new_relic/control/external.rb +13 -0
  51. data/lib/new_relic/control/merb.rb +22 -0
  52. data/lib/new_relic/control/rails.rb +145 -0
  53. data/lib/new_relic/control/ruby.rb +36 -0
  54. data/lib/new_relic/control/sinatra.rb +14 -0
  55. data/lib/new_relic/histogram.rb +89 -0
  56. data/lib/new_relic/local_environment.rb +328 -0
  57. data/lib/new_relic/merbtasks.rb +6 -0
  58. data/lib/new_relic/metric_data.rb +42 -0
  59. data/lib/new_relic/metric_parser.rb +124 -0
  60. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  61. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  62. data/lib/new_relic/metric_parser/active_record.rb +25 -0
  63. data/lib/new_relic/metric_parser/controller.rb +54 -0
  64. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  65. data/lib/new_relic/metric_parser/errors.rb +6 -0
  66. data/lib/new_relic/metric_parser/external.rb +50 -0
  67. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  68. data/lib/new_relic/metric_parser/view.rb +61 -0
  69. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  70. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  71. data/lib/new_relic/metric_spec.rb +52 -0
  72. data/lib/new_relic/metrics.rb +7 -0
  73. data/lib/new_relic/noticed_error.rb +23 -0
  74. data/lib/new_relic/rack/metric_app.rb +56 -0
  75. data/lib/new_relic/rack/newrelic.ru +25 -0
  76. data/lib/new_relic/rack/newrelic.yml +25 -0
  77. data/lib/new_relic/rack_app.rb +5 -0
  78. data/lib/new_relic/recipes.rb +82 -0
  79. data/lib/new_relic/stats.rb +361 -0
  80. data/lib/new_relic/transaction_analysis.rb +121 -0
  81. data/lib/new_relic/transaction_sample.rb +666 -0
  82. data/lib/new_relic/version.rb +54 -0
  83. data/lib/new_relic_api.rb +313 -0
  84. data/lib/newrelic_rpm.rb +40 -0
  85. data/lib/tasks/all.rb +4 -0
  86. data/lib/tasks/install.rake +7 -0
  87. data/lib/tasks/tests.rake +13 -0
  88. data/newrelic.yml +227 -0
  89. data/recipes/newrelic.rb +6 -0
  90. data/techarch-newrelic_rpm.gemspec +32 -0
  91. data/test/active_record_fixtures.rb +55 -0
  92. data/test/config/newrelic.yml +46 -0
  93. data/test/config/test_control.rb +39 -0
  94. data/test/new_relic/agent/active_record_instrumentation_test.rb +264 -0
  95. data/test/new_relic/agent/agent_controller_test.rb +107 -0
  96. data/test/new_relic/agent/agent_test.rb +119 -0
  97. data/test/new_relic/agent/agent_test_controller.rb +44 -0
  98. data/test/new_relic/agent/classloader_patch_test.rb +56 -0
  99. data/test/new_relic/agent/collection_helper_test.rb +125 -0
  100. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +76 -0
  101. data/test/new_relic/agent/error_collector_test.rb +172 -0
  102. data/test/new_relic/agent/method_tracer_test.rb +340 -0
  103. data/test/new_relic/agent/metric_data_test.rb +56 -0
  104. data/test/new_relic/agent/mock_ar_connection.rb +40 -0
  105. data/test/new_relic/agent/mock_scope_listener.rb +23 -0
  106. data/test/new_relic/agent/net_instrumentation_test.rb +63 -0
  107. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +79 -0
  108. data/test/new_relic/agent/stats_engine/samplers_test.rb +81 -0
  109. data/test/new_relic/agent/stats_engine/stats_engine_test.rb +184 -0
  110. data/test/new_relic/agent/task_instrumentation_test.rb +126 -0
  111. data/test/new_relic/agent/testable_agent.rb +13 -0
  112. data/test/new_relic/agent/transaction_sample_builder_test.rb +195 -0
  113. data/test/new_relic/agent/transaction_sample_test.rb +186 -0
  114. data/test/new_relic/agent/transaction_sampler_test.rb +385 -0
  115. data/test/new_relic/agent/worker_loop_test.rb +103 -0
  116. data/test/new_relic/control_test.rb +113 -0
  117. data/test/new_relic/deployments_api_test.rb +68 -0
  118. data/test/new_relic/environment_test.rb +75 -0
  119. data/test/new_relic/metric_parser_test.rb +172 -0
  120. data/test/new_relic/metric_spec_test.rb +177 -0
  121. data/test/new_relic/shim_agent_test.rb +9 -0
  122. data/test/new_relic/stats_test.rb +291 -0
  123. data/test/new_relic/version_number_test.rb +74 -0
  124. data/test/test_helper.rb +38 -0
  125. data/test/ui/newrelic_controller_test.rb +14 -0
  126. data/test/ui/newrelic_helper_test.rb +53 -0
  127. data/ui/controllers/newrelic_controller.rb +220 -0
  128. data/ui/helpers/google_pie_chart.rb +55 -0
  129. data/ui/helpers/newrelic_helper.rb +317 -0
  130. data/ui/views/layouts/newrelic_default.rhtml +47 -0
  131. data/ui/views/newrelic/_explain_plans.rhtml +27 -0
  132. data/ui/views/newrelic/_sample.rhtml +19 -0
  133. data/ui/views/newrelic/_segment.rhtml +28 -0
  134. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  135. data/ui/views/newrelic/_segment_row.rhtml +14 -0
  136. data/ui/views/newrelic/_show_sample_detail.rhtml +24 -0
  137. data/ui/views/newrelic/_show_sample_sql.rhtml +20 -0
  138. data/ui/views/newrelic/_show_sample_summary.rhtml +3 -0
  139. data/ui/views/newrelic/_sql_row.rhtml +11 -0
  140. data/ui/views/newrelic/_stack_trace.rhtml +30 -0
  141. data/ui/views/newrelic/_table.rhtml +12 -0
  142. data/ui/views/newrelic/explain_sql.rhtml +42 -0
  143. data/ui/views/newrelic/images/arrow-close.png +0 -0
  144. data/ui/views/newrelic/images/arrow-open.png +0 -0
  145. data/ui/views/newrelic/images/blue_bar.gif +0 -0
  146. data/ui/views/newrelic/images/file_icon.png +0 -0
  147. data/ui/views/newrelic/images/gray_bar.gif +0 -0
  148. data/ui/views/newrelic/images/new_relic_rpm_desktop.gif +0 -0
  149. data/ui/views/newrelic/images/textmate.png +0 -0
  150. data/ui/views/newrelic/index.rhtml +57 -0
  151. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +7288 -0
  152. data/ui/views/newrelic/javascript/transaction_sample.js +107 -0
  153. data/ui/views/newrelic/sample_not_found.rhtml +2 -0
  154. data/ui/views/newrelic/show_sample.rhtml +80 -0
  155. data/ui/views/newrelic/show_source.rhtml +3 -0
  156. data/ui/views/newrelic/stylesheets/style.css +484 -0
  157. data/ui/views/newrelic/threads.rhtml +52 -0
  158. metadata +330 -0
@@ -0,0 +1,118 @@
1
+ module NewRelic::Agent
2
+
3
+ # A worker loop executes a set of registered tasks on a single thread.
4
+ # A task is a proc or block with a specified call period in seconds.
5
+ class WorkerLoop
6
+
7
+ attr_reader :log
8
+ attr_reader :pid
9
+
10
+ def initialize(log = Logger.new(STDERR))
11
+ @tasks = []
12
+ @log = log
13
+ @should_run = true
14
+ @pid = $$
15
+ end
16
+
17
+ # Run infinitely, calling the registered tasks at their specified
18
+ # call periods. The caller is responsible for creating the thread
19
+ # that runs this worker loop
20
+ def run
21
+ while keep_running do
22
+ run_next_task
23
+ end
24
+ end
25
+
26
+ def keep_running
27
+ @should_run && (@pid == $$)
28
+ end
29
+
30
+ def stop
31
+ @should_run = false
32
+ end
33
+
34
+ MIN_CALL_PERIOD = 0.1
35
+
36
+ # add a task to the worker loop. The task will be called approximately once
37
+ # every call_period seconds. The task is passed as a block
38
+ def add_task(call_period, desc="", &task_proc)
39
+ if call_period < MIN_CALL_PERIOD
40
+ raise ArgumentError, "Invalid Call Period (must be > #{MIN_CALL_PERIOD}): #{call_period}"
41
+ end
42
+ @tasks << LoopTask.new(call_period, desc, &task_proc)
43
+ end
44
+
45
+ private
46
+ def next_task
47
+ @tasks.inject do |soonest, task|
48
+ (task.next_invocation_time < soonest.next_invocation_time) ? task : soonest
49
+ end
50
+ end
51
+
52
+ def run_next_task
53
+ if @tasks.empty?
54
+ sleep 5.0
55
+ return
56
+ end
57
+
58
+ # get the next task to be executed, which is the task with the lowest (ie, soonest)
59
+ # next invocation time.
60
+ task = next_task
61
+
62
+ # sleep in chunks no longer than 1 second
63
+ while Time.now < task.next_invocation_time
64
+
65
+ # sleep until this next task's scheduled invocation time
66
+ sleep_time = task.next_invocation_time - Time.now
67
+ sleep sleep_time if sleep_time > 0
68
+ return if !keep_running
69
+ end
70
+
71
+ begin
72
+ task.execute
73
+ rescue ServerError => e
74
+ log.debug "Server Error: #{e}"
75
+ rescue NewRelic::Agent::ForceRestartException => e
76
+ # blow out the loop
77
+ raise
78
+ rescue RuntimeError => e
79
+ # This is probably a server error which has been logged in the server along
80
+ # with your account name. Check and see if the agent listener is in the
81
+ # stack trace and log it quietly if it is.
82
+ message = "Error running task in worker loop, likely a server error (#{e})"
83
+ if e.backtrace.grep(/agent_listener/).empty?
84
+ log.error message
85
+ else
86
+ log.debug message
87
+ log.debug e.backtrace.join("\n")
88
+ end
89
+ rescue Timeout::Error, NewRelic::Agent::IgnoreSilentlyException
90
+ # Want to ignore these because they are handled already
91
+ rescue ScriptError, StandardError => e
92
+ log.error "Error running task in Agent Worker Loop (#{e.class}): #{e} "
93
+ log.debug e.backtrace.join("\n")
94
+ end
95
+ end
96
+
97
+ class LoopTask
98
+
99
+ def initialize(call_period, desc="", &task_proc)
100
+ @call_period = call_period
101
+ @last_invocation_time = Time.now
102
+ @task = task_proc
103
+ @desc = desc
104
+ end
105
+ def to_s
106
+ "Task[#{@desc}]"
107
+ end
108
+ def next_invocation_time
109
+ @last_invocation_time + @call_period
110
+ end
111
+
112
+ def execute
113
+ @last_invocation_time = Time.now
114
+ @task.call
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,145 @@
1
+ # This is a class for executing commands related to deployment
2
+ # events. It runs without loading the rails environment
3
+
4
+ $LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__),"..",".."))
5
+ require 'yaml'
6
+ require 'net/http'
7
+ require 'rexml/document'
8
+
9
+ # We need to use the Control object but we don't want to load
10
+ # the rails/merb environment. The defined? clause is so that
11
+ # it won't load it twice, something it does when run inside a test
12
+ require 'new_relic/control' unless defined? NewRelic::Control
13
+
14
+ module NewRelic
15
+ module Commands
16
+ # Capture a failure to execute the command.
17
+ # Ask it for a return status to exit the vm with,
18
+ # if appropriate.
19
+ class CommandFailure < StandardError
20
+ attr_reader :exit_code
21
+ def initialize message, return_status=nil
22
+ super message
23
+ @exit_code = return_status || 0
24
+ end
25
+ end
26
+
27
+ class Deployments
28
+
29
+ attr_reader :config
30
+ def self.command; "deployments"; end
31
+
32
+ # Initialize the deployment uploader with command line args.
33
+ # Use -h to see options.
34
+ # When command_line_args is a hash, we are invoking directly and
35
+ # it's treated as an options with optional sttring values for
36
+ # :user, :description, :appname, :revision, :environment,
37
+ # and :changes.
38
+ #
39
+ # Will throw CommandFailed exception if there's any error.
40
+ #
41
+ def initialize command_line_args
42
+ @config = NewRelic::Control.instance
43
+ @user = ENV['USER']
44
+ if Hash === command_line_args
45
+ # command line args is an options hash
46
+ command_line_args.each do | key, value |
47
+ if %w[user environment description appname revision changelog].include? key.to_s
48
+ instance_variable_set "@#{key}", value.to_s if value
49
+ else
50
+ raise "Unrecognized option #{key}=#{value}"
51
+ end
52
+ end
53
+ else
54
+ # parse command line args. Throw an exception on a bad arg.
55
+ @description = options.parse(command_line_args).join " "
56
+ end
57
+ config.env = @environment if @environment
58
+ @appname ||= config.app_names[0] || config.env || 'development'
59
+ end
60
+
61
+ # Run the Deployment upload in RPM via Active Resource.
62
+ # Will possibly print errors and exit the VM
63
+ def run
64
+ begin
65
+ @description = nil if @description && @description.strip.empty?
66
+ create_params = {}
67
+ {
68
+ :application_id => @appname,
69
+ :host => Socket.gethostname,
70
+ :description => @description,
71
+ :user => @user,
72
+ :revision => @revision,
73
+ :changelog => @changelog
74
+ }.each do |k, v|
75
+ create_params["deployment[#{k}]"] = v unless v.nil? || v == ''
76
+ end
77
+ http = config.http_connection(config.api_server)
78
+
79
+ uri = "/deployments.xml"
80
+
81
+ raise "license_key was not set in newrelic.yml for #{config.env}" if config['license_key'].nil?
82
+ request = Net::HTTP::Post.new(uri, {'x-license-key' => config['license_key']})
83
+ request.content_type = "application/octet-stream"
84
+
85
+ request.set_form_data(create_params)
86
+
87
+ response = http.request(request)
88
+
89
+ if response.is_a? Net::HTTPSuccess
90
+ info "Recorded deployment to '#{@appname}' (#{@description || Time.now })"
91
+ else
92
+ err_string = [ "Unexpected response from server: #{response.code}: #{response.message}" ]
93
+ begin
94
+ doc = REXML::Document.new(response.body)
95
+ doc.elements.each('errors/error') do |error|
96
+ err_string << "Error: #{error.text}"
97
+ end
98
+ rescue
99
+ end
100
+ raise CommandFailure.new(err_string.join("\n"), -1)
101
+ end
102
+ rescue SystemCallError, SocketError => e
103
+ # These include Errno connection errors
104
+ err_string = "Transient error attempting to connect to #{config.api_server} (#{e})"
105
+ raise CommandFailure.new(err_string, -1)
106
+ rescue CommandFailure
107
+ raise
108
+ rescue Exception => e
109
+ err "Unexpected error attempting to connect to #{config.api_server}"
110
+ info "#{e}: #{e.backtrace.join("\n ")}"
111
+ raise CommandFailure.new(e.to_s, -1)
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def options
118
+ OptionParser.new %Q{Usage: #{$0} [OPTIONS] ["description"] }, 40 do |o|
119
+ o.separator "OPTIONS:"
120
+ o.on("-a", "--appname=NAME", String,
121
+ "Set the application name.",
122
+ "Default is app_name setting in newrelic.yml") { | e | @appname = e }
123
+ o.on("-e", "--environment=name", String,
124
+ "Override the (RAILS|MERB|RUBY)_ENV setting",
125
+ "currently: #{config.env}") { | e | @environment = e }
126
+ o.on("-u", "--user=USER", String,
127
+ "Specify the user deploying.",
128
+ "Default: #{@user}") { | u | @user = u }
129
+ o.on("-r", "--revision=REV", String,
130
+ "Specify the revision being deployed") { | r | @revision = r }
131
+ o.on("-c", "--changes",
132
+ "Read in a change log from the standard input") { @changelog = STDIN.read }
133
+ o.on("-h", "--help", "Print this help") { raise CommandFailure.new(o.help, 0) }
134
+ end
135
+ end
136
+
137
+ def info message
138
+ STDOUT.puts message
139
+ end
140
+ def err message
141
+ STDERR.puts message
142
+ end
143
+ end
144
+ end
145
+ end
@@ -0,0 +1,30 @@
1
+ require 'optparse'
2
+
3
+ # Run the command given by the first argument. Right
4
+ # now all we have is deployments. We hope to have other
5
+ # kinds of events here later.
6
+
7
+ libdir = File.expand_path(File.join(File.dirname(__FILE__), '..','..'))
8
+ command_list = Dir[File.join(libdir,'new_relic','commands','*.rb')].map{|command| command =~ /.*\/(.*)\.rb/ && $1}
9
+ command_list.delete 'new_relic_commands'
10
+ extra = []
11
+ options = ARGV.options do |opts|
12
+ script_name = File.basename($0)
13
+ opts.banner = "Usage: #{__FILE__} #{ command_list.join(" | ")} [options]"
14
+ opts.separator "use -h to see detailed command options"
15
+ opts
16
+ end
17
+ extra = options.order!
18
+ command = extra.shift
19
+ if !command_list.include?(command)
20
+ STDERR.puts options
21
+ else
22
+ require File.join(libdir, 'new_relic','commands', command + ".rb")
23
+ command_class = NewRelic::Commands.const_get(command.capitalize)
24
+ begin
25
+ command_class.new(extra).run
26
+ rescue NewRelic::Commands::CommandFailure => failure
27
+ STDERR.puts failure.message
28
+ exit failure.exit_code
29
+ end
30
+ end
@@ -0,0 +1,473 @@
1
+ require 'yaml'
2
+ require 'new_relic/local_environment'
3
+ require 'singleton'
4
+ require 'erb'
5
+ require 'socket'
6
+ require 'net/https'
7
+ require 'logger'
8
+
9
+ module NewRelic
10
+
11
+ # The Control is a singleton responsible for the startup and
12
+ # initialization sequence. The initializer uses a LocalEnvironment to
13
+ # detect the framework and instantiates the framework specific
14
+ # subclass.
15
+ #
16
+ # The Control also implements some of the public API for the agent.
17
+ #
18
+ class Control
19
+
20
+ # A flag used in dev mode to indicate if profiling is available
21
+ def profiling?
22
+ @profiling
23
+ end
24
+
25
+ def profiling_available?
26
+ @profiling_available ||=
27
+ begin
28
+ require 'ruby-prof'
29
+ true
30
+ rescue LoadError; end
31
+ end
32
+ # Set the flag for capturing profiles in dev mode. If RubyProf is not
33
+ # loaded a true value is ignored.
34
+ def profiling=(val)
35
+ @profiling = profiling_available? && val && defined?(RubyProf)
36
+ end
37
+
38
+ attr_accessor :log_file
39
+ # The env is the setting used to identify which section of the newrelic.yml
40
+ # to load. This defaults to a framework specific value, such as ENV['RAILS_ENV']
41
+ # but can be overridden as long as you set it before calling #init_plugin
42
+ attr_writer :env
43
+ attr_reader :local_env
44
+
45
+ # Structs holding info for the remote server and proxy server
46
+ class Server < Struct.new :name, :port, :ip #:nodoc:
47
+ def to_s; "#{name}:#{port}"; end
48
+ end
49
+
50
+ ProxyServer = Struct.new :name, :port, :user, :password #:nodoc:
51
+
52
+ # Access the Control singleton, lazy initialized
53
+ def self.instance
54
+ @instance ||= new_instance
55
+ end
56
+
57
+ # Initialize the plugin/gem and start the agent. This does the necessary configuration based on the
58
+ # framework environment and determines whether or not to start the agent. If the
59
+ # agent is not going to be started then it loads the agent shim which has stubs
60
+ # for all the external api.
61
+ #
62
+ # This may be invoked multiple times, as long as you don't attempt to uninstall
63
+ # the agent after it has been started.
64
+ #
65
+ # If the plugin is initialized and it determines that the agent is not enabled, it
66
+ # will skip starting it and install the shim. But if you later call this with
67
+ # <tt>:agent_enabled => true</tt>, then it will install the real agent and start it.
68
+ #
69
+ # What determines whether the agent is launched is the result of calling agent_enabled?
70
+ # This will indicate whether the instrumentation should/will be installed. If we're
71
+ # in a mode where tracers are not installed then we should not start the agent.
72
+ #
73
+ # Subclasses are not allowed to override, but must implement init_config({}) which
74
+ # is called at most once.
75
+ #
76
+ def init_plugin(options={})
77
+ require 'new_relic/agent'
78
+ # Merge the stringified options into the config as overrides:
79
+ logger_override = options.delete(:log)
80
+ environment_name = options.delete(:env)
81
+ self.env = environment_name if environment_name
82
+ # Clear out the settings, if they've already been loaded. It may be that
83
+ # between calling init_plugin the first time and the second time, the env
84
+ # has been overridden
85
+ @settings = nil
86
+
87
+ options.each { |sym, val | self[sym.to_s] = val unless sym == :config }
88
+ if logger_override
89
+ @log = logger_override
90
+ # Try to grab the log filename
91
+ @log_file = @log.instance_eval { @logdev.filename rescue nil }
92
+ end
93
+ # An artifact of earlier implementation, we put both #add_method_tracer and #trace_execution
94
+ # methods in the module methods.
95
+ Module.send :include, NewRelic::Agent::MethodTracer::ClassMethods
96
+ Module.send :include, NewRelic::Agent::MethodTracer::InstanceMethods
97
+ init_config(options)
98
+ if agent_enabled? && !@started
99
+ setup_log unless logger_override
100
+ start_agent
101
+ install_instrumentation
102
+ load_samplers unless self['disable_samplers']
103
+ local_env.gather_environment_info
104
+ append_environment_info
105
+ @started = true
106
+ elsif !agent_enabled?
107
+ install_shim
108
+ end
109
+ end
110
+
111
+ # Install the real agent into the Agent module, and issue the start command.
112
+ def start_agent
113
+ NewRelic::Agent.agent = NewRelic::Agent::Agent.instance
114
+ NewRelic::Agent.agent.start
115
+ end
116
+
117
+ def [](key)
118
+ fetch(key)
119
+ end
120
+
121
+ def settings
122
+ unless @settings
123
+ @settings = (@yaml && merge_defaults(@yaml[env])) || {}
124
+ # At the time we bind the settings, we also need to run this little piece
125
+ # of magic which allows someone to augment the id with the app name, necessary
126
+ if self['multi_homed'] && app_names.size > 0
127
+ if @local_env.dispatcher_instance_id
128
+ @local_env.dispatcher_instance_id << ":#{app_names.first}"
129
+ else
130
+ @local_env.dispatcher_instance_id = app_names.first
131
+ end
132
+ end
133
+
134
+ end
135
+ @settings
136
+ end
137
+
138
+ def []=(key, value)
139
+ settings[key] = value
140
+ end
141
+
142
+ def fetch(key, default=nil)
143
+ settings.fetch(key, default)
144
+ end
145
+ # Add your own environment value to track for change detection.
146
+ # The name and value should be stable and not vary across app processes on
147
+ # the same host.
148
+ def append_environment_info(name, value)
149
+ local_env.record_environment_info(name,value)
150
+ end
151
+
152
+ ###################################
153
+ # Agent config conveniences
154
+
155
+ def apdex_t
156
+ # Always initialized with a default
157
+ fetch('apdex_t').to_f
158
+ end
159
+ def license_key
160
+ fetch('license_key')
161
+ end
162
+ def capture_params
163
+ fetch('capture_params')
164
+ end
165
+ # True if we are sending data to the server, monitoring production
166
+ def monitor_mode?
167
+ fetch('monitor_mode', fetch('enabled'))
168
+ end
169
+ # True if we are capturing data and displaying in /newrelic
170
+ def developer_mode?
171
+ fetch('developer_mode', fetch('developer'))
172
+ end
173
+ # True if the app runs in multi-threaded mode
174
+ def multi_threaded?
175
+ fetch('multi_threaded')
176
+ end
177
+ # True if we should view files in textmate
178
+ def use_textmate?
179
+ fetch('textmate')
180
+ end
181
+ def post_size_limit
182
+ fetch('post_size_limit', 2 * 1024 * 1024)
183
+ end
184
+ # True if dev mode or monitor mode are enabled, and we are running
185
+ # inside a valid dispatcher like mongrel or passenger. Can be overridden
186
+ # by NEWRELIC_ENABLE env variable, monitor_daemons config option when true, or
187
+ # agent_enabled config option when true or false.
188
+ def agent_enabled?
189
+ return false if !developer_mode? && !monitor_mode?
190
+ return self['agent_enabled'].to_s =~ /true|on|yes/i if !self['agent_enabled'].nil? && self['agent_enabled'] != 'auto'
191
+ return false if ENV['NEWRELIC_ENABLE'].to_s =~ /false|off|no/i
192
+ return true if self['monitor_daemons'].to_s =~ /true|on|yes/i
193
+ return true if ENV['NEWRELIC_ENABLE'].to_s =~ /true|on|yes/i
194
+ # When in 'auto' mode the agent is enabled if there is a known
195
+ # dispatcher running
196
+ return true if @local_env.dispatcher != nil
197
+ end
198
+
199
+ def app
200
+ @local_env.framework
201
+ end
202
+ alias framework app
203
+
204
+ def dispatcher_instance_id
205
+ self['dispatcher_instance_id'] || @local_env.dispatcher_instance_id
206
+ end
207
+ def dispatcher
208
+ (self['dispatcher'] && self['dispatcher'].to_sym) || @local_env.dispatcher
209
+ end
210
+ def app_names
211
+ self['app_name'] ? self['app_name'].split(';') : []
212
+ end
213
+
214
+ def use_ssl?
215
+ @use_ssl ||= fetch('ssl', false)
216
+ end
217
+
218
+ def verify_certificate?
219
+ #this can only be on when SSL is enabled
220
+ @verify_certificate ||= ( use_ssl? ? fetch('verify_certificate', false) : false)
221
+ end
222
+
223
+ def server
224
+ @remote_server ||= server_from_host(nil)
225
+ end
226
+
227
+ def api_server
228
+ api_host = self['api_host'] || 'rpm.newrelic.com'
229
+ @api_server ||=
230
+ NewRelic::Control::Server.new \
231
+ api_host,
232
+ (self['api_port'] || self['port'] || (use_ssl? ? 443 : 80)).to_i,
233
+ nil
234
+ end
235
+
236
+ def proxy_server
237
+ @proxy_server ||=
238
+ NewRelic::Control::ProxyServer.new self['proxy_host'], self['proxy_port'], self['proxy_user'], self['proxy_pass']
239
+ end
240
+
241
+ def server_from_host(hostname=nil)
242
+ host = hostname || self['host'] || 'collector.newrelic.com'
243
+
244
+ # if the host is not an IP address, turn it into one
245
+ NewRelic::Control::Server.new host, (self['port'] || (use_ssl? ? 443 : 80)).to_i, convert_to_ip_address(host)
246
+ end
247
+
248
+ # Return the Net::HTTP with proxy configuration given the NewRelic::Control::Server object.
249
+ # Default is the collector but for api calls you need to pass api_server
250
+ #
251
+ # Experimental support for SSL verification:
252
+ # swap 'VERIFY_NONE' for 'VERIFY_PEER' line to try it out
253
+ # If verification fails, uncomment the 'http.ca_file' line
254
+ # and it will use the included certificate.
255
+ def http_connection(host = nil)
256
+ host ||= server
257
+ # Proxy returns regular HTTP if @proxy_host is nil (the default)
258
+ http_class = Net::HTTP::Proxy(proxy_server.name, proxy_server.port,
259
+ proxy_server.user, proxy_server.password)
260
+ http = http_class.new(host.ip || host.name, host.port)
261
+ if use_ssl?
262
+ http.use_ssl = true
263
+ if verify_certificate?
264
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
265
+ http.ca_file = File.join(File.dirname(__FILE__), '..', '..', 'cert', 'cacert.pem')
266
+ else
267
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
268
+ end
269
+ end
270
+ http
271
+ end
272
+ def to_s
273
+ "Control[#{self.app}]"
274
+ end
275
+
276
+ def log
277
+ # If we try to get a log before one has been set up, return a stdout log
278
+ unless @log
279
+ l = Logger.new(STDOUT)
280
+ l.level = Logger::INFO
281
+ return l
282
+ end
283
+ @log
284
+ end
285
+
286
+ # send the given message to STDOUT so that it shows
287
+ # up in the console. This should be used for important informational messages at boot.
288
+ # The to_stdout may be implemented differently by different config subclasses.
289
+ # This will NOT print anything if tracers are not enabled
290
+ def log!(msg, level=:info)
291
+ return if @settings && !agent_enabled?
292
+ to_stdout msg
293
+ log.send level, msg if @log
294
+ end
295
+
296
+ # Install stubs to the proper location so the app code will not fail
297
+ # if the agent is not running.
298
+ def install_shim
299
+ # Once we install instrumentation, you can't undo that by installing the shim.
300
+ raise "Cannot install the Agent shim after instrumentation has already been installed!" if @instrumented
301
+ NewRelic::Agent.agent = NewRelic::Agent::ShimAgent.instance
302
+ end
303
+
304
+ def install_instrumentation
305
+ return if @instrumented
306
+
307
+ @instrumented = true
308
+
309
+ # Instrumentation for the key code points inside rails for monitoring by NewRelic.
310
+ # note this file is loaded only if the newrelic agent is enabled (through config/newrelic.yml)
311
+ instrumentation_path = File.join(File.dirname(__FILE__), 'agent','instrumentation')
312
+ instrumentation_files = [ ] <<
313
+ File.join(instrumentation_path, '*.rb') <<
314
+ File.join(instrumentation_path, app.to_s, '*.rb')
315
+ instrumentation_files.each do | pattern |
316
+ Dir.glob(pattern) do |file|
317
+ begin
318
+ log.debug "Processing instrumentation file '#{file}'"
319
+ require file
320
+ rescue => e
321
+ log.error "Error loading instrumentation file '#{file}': #{e}"
322
+ log.debug e.backtrace.join("\n")
323
+ end
324
+ end
325
+ end
326
+
327
+ log.debug "Finished instrumentation"
328
+ end
329
+
330
+ def load_samplers
331
+ agent = NewRelic::Agent.instance
332
+ agent.stats_engine.add_sampler NewRelic::Agent::Samplers::MongrelSampler.new if local_env.mongrel
333
+
334
+ if NewRelic::Agent::Samplers::CpuSampler.supported_on_this_platform?
335
+ agent.stats_engine.add_harvest_sampler NewRelic::Agent::Samplers::CpuSampler.new
336
+ end
337
+
338
+ if NewRelic::Agent::Samplers::ObjectSampler.supported_on_this_platform?
339
+ agent.stats_engine.add_harvest_sampler NewRelic::Agent::Samplers::ObjectSampler.new
340
+ end
341
+
342
+ begin
343
+ if NewRelic::Agent::Samplers::MemorySampler.supported_on_this_platform?
344
+ agent.stats_engine.add_sampler NewRelic::Agent::Samplers::MemorySampler.new
345
+ end
346
+ rescue RuntimeError => e
347
+ log.error "Cannot add memory sampling: #{e}"
348
+ end
349
+ # Add sampler for DelayedJob worker
350
+ if local_env.delayed_worker
351
+ agent.stats_engine.add_sampler NewRelic::Agent::Samplers::DelayedJobLockSampler.new
352
+ end
353
+ end
354
+
355
+ protected
356
+
357
+ # Append framework specific environment information for uploading to
358
+ # the server for change detection. Override in subclasses
359
+ def append_environment_info; end
360
+
361
+ # Look up the ip address of the host using the pure ruby lookup
362
+ # to prevent blocking. If that fails, fall back to the regular
363
+ # IPSocket library. Return nil if we can't find the host ip
364
+ # address and don't have a good default.
365
+ def convert_to_ip_address(host)
366
+ # here we leave it as a host name since the cert verification
367
+ # needs it in host form
368
+ return host if verify_certificate?
369
+ return nil if host.nil? || host.downcase == "localhost"
370
+ # Fall back to known ip address in the common case
371
+ ip_address = '65.74.177.195' if host.downcase == 'collector.newrelic.com'
372
+ begin
373
+ ip_address = Resolv.getaddress(host)
374
+ log.info "Resolved #{host} to #{ip_address}"
375
+ rescue => e
376
+ log.warn "DNS Error caching IP address: #{e}"
377
+ log.debug e.backtrace.join("\n ")
378
+ ip_address = IPSocket::getaddress host rescue ip_address
379
+ end
380
+ ip_address
381
+ end
382
+
383
+ def merge_defaults(settings_hash)
384
+ s = {
385
+ 'host' => 'collector.newrelic.com',
386
+ 'ssl' => false,
387
+ 'log_level' => 'info',
388
+ 'apdex_t' => 1.0
389
+ }
390
+ s.merge! settings_hash if settings_hash
391
+ # monitor_daemons replaced with agent_enabled
392
+ s['agent_enabled'] = s.delete('monitor_daemons') if s['agent_enabled'].nil? && s.include?('monitor_daemons')
393
+ s
394
+ end
395
+ # Control subclasses may override this, but it can be called multiple times.
396
+ def setup_log
397
+ @log_file = "#{log_path}/newrelic_agent.log"
398
+ @log = Logger.new @log_file
399
+
400
+ # change the format just for our logger
401
+
402
+ def @log.format_message(severity, timestamp, progname, msg)
403
+ "[#{timestamp.strftime("%m/%d/%y %H:%M:%S %z")} #{Socket.gethostname} (#{$$})] #{severity} : #{msg}\n"
404
+ end
405
+
406
+ # set the log level as specified in the config file
407
+ case fetch("log_level","info").downcase
408
+ when "debug"; @log.level = Logger::DEBUG
409
+ when "info"; @log.level = Logger::INFO
410
+ when "warn"; @log.level = Logger::WARN
411
+ when "error"; @log.level = Logger::ERROR
412
+ when "fatal"; @log.level = Logger::FATAL
413
+ else @log.level = Logger::INFO
414
+ end
415
+ @log
416
+ end
417
+
418
+ def to_stdout(msg)
419
+ STDOUT.puts "** [NewRelic] " + msg
420
+ end
421
+
422
+ def config_file
423
+ File.expand_path(File.join(root,"config","newrelic.yml"))
424
+ end
425
+
426
+ def log_path
427
+ path = File.join(root,'log')
428
+ unless File.directory? path
429
+ path = '.'
430
+ end
431
+ File.expand_path(path)
432
+ end
433
+
434
+ # Create the concrete class for environment specific behavior:
435
+ def self.new_instance
436
+ @local_env = NewRelic::LocalEnvironment.new
437
+ if @local_env.framework == :test
438
+ require File.join(newrelic_root, "test", "config", "test_control.rb")
439
+ NewRelic::Control::Test.new @local_env
440
+ else
441
+ require "new_relic/control/#{@local_env.framework}.rb"
442
+ NewRelic::Control.const_get(@local_env.framework.to_s.capitalize).new @local_env
443
+ end
444
+ end
445
+
446
+ def initialize local_env
447
+ @local_env = local_env
448
+ newrelic_file = config_file
449
+ # Next two are for populating the newrelic.yml via erb binding, necessary
450
+ # when using the default newrelic.yml file
451
+ generated_for_user = ''
452
+ license_key=''
453
+ if !File.exists?(config_file)
454
+ log! "Cannot find newrelic.yml file at #{config_file}."
455
+ @yaml = {}
456
+ else
457
+ @yaml = YAML.load(ERB.new(File.read(config_file)).result(binding))
458
+ end
459
+ rescue ScriptError, StandardError => e
460
+ puts e
461
+ puts e.backtrace.join("\n")
462
+ raise "Error reading newrelic.yml file: #{e}"
463
+ end
464
+
465
+ # The root directory for the plugin or gem
466
+ def self.newrelic_root
467
+ File.expand_path(File.join(File.dirname(__FILE__),"..",".."))
468
+ end
469
+ def newrelic_root
470
+ self.class.newrelic_root
471
+ end
472
+ end
473
+ end