genki-newrelic_rpm 2.10.1

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 +316 -0
  2. data/LICENSE +37 -0
  3. data/Manifest +156 -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/genki-newrelic_rpm.gemspec +32 -0
  10. data/init.rb +38 -0
  11. data/install.rb +37 -0
  12. data/lib/new_relic/agent.rb +280 -0
  13. data/lib/new_relic/agent/agent.rb +627 -0
  14. data/lib/new_relic/agent/chained_call.rb +13 -0
  15. data/lib/new_relic/agent/collection_helper.rb +61 -0
  16. data/lib/new_relic/agent/error_collector.rb +125 -0
  17. data/lib/new_relic/agent/instrumentation/active_merchant.rb +18 -0
  18. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +83 -0
  19. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  20. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +368 -0
  21. data/lib/new_relic/agent/instrumentation/data_mapper.rb +90 -0
  22. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +132 -0
  23. data/lib/new_relic/agent/instrumentation/memcache.rb +21 -0
  24. data/lib/new_relic/agent/instrumentation/merb/controller.rb +26 -0
  25. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +13 -0
  26. data/lib/new_relic/agent/instrumentation/merb/errors.rb +8 -0
  27. data/lib/new_relic/agent/instrumentation/net.rb +12 -0
  28. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +20 -0
  29. data/lib/new_relic/agent/instrumentation/rack.rb +77 -0
  30. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +59 -0
  31. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +27 -0
  32. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +38 -0
  33. data/lib/new_relic/agent/instrumentation/rails/errors.rb +27 -0
  34. data/lib/new_relic/agent/instrumentation/sinatra.rb +39 -0
  35. data/lib/new_relic/agent/method_tracer.rb +277 -0
  36. data/lib/new_relic/agent/patch_const_missing.rb +125 -0
  37. data/lib/new_relic/agent/sampler.rb +12 -0
  38. data/lib/new_relic/agent/samplers/cpu_sampler.rb +49 -0
  39. data/lib/new_relic/agent/samplers/memory_sampler.rb +137 -0
  40. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +22 -0
  41. data/lib/new_relic/agent/shim_agent.rb +21 -0
  42. data/lib/new_relic/agent/stats_engine.rb +24 -0
  43. data/lib/new_relic/agent/stats_engine/metric_stats.rb +111 -0
  44. data/lib/new_relic/agent/stats_engine/samplers.rb +71 -0
  45. data/lib/new_relic/agent/stats_engine/transactions.rb +155 -0
  46. data/lib/new_relic/agent/transaction_sampler.rb +319 -0
  47. data/lib/new_relic/agent/worker_loop.rb +118 -0
  48. data/lib/new_relic/commands/deployments.rb +145 -0
  49. data/lib/new_relic/commands/new_relic_commands.rb +30 -0
  50. data/lib/new_relic/control.rb +436 -0
  51. data/lib/new_relic/control/external.rb +13 -0
  52. data/lib/new_relic/control/merb.rb +22 -0
  53. data/lib/new_relic/control/rails.rb +143 -0
  54. data/lib/new_relic/control/ruby.rb +34 -0
  55. data/lib/new_relic/control/sinatra.rb +14 -0
  56. data/lib/new_relic/histogram.rb +89 -0
  57. data/lib/new_relic/local_environment.rb +285 -0
  58. data/lib/new_relic/merbtasks.rb +6 -0
  59. data/lib/new_relic/metric_data.rb +44 -0
  60. data/lib/new_relic/metric_parser.rb +120 -0
  61. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  62. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  63. data/lib/new_relic/metric_parser/active_record.rb +25 -0
  64. data/lib/new_relic/metric_parser/controller.rb +54 -0
  65. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  66. data/lib/new_relic/metric_parser/errors.rb +6 -0
  67. data/lib/new_relic/metric_parser/external.rb +50 -0
  68. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  69. data/lib/new_relic/metric_parser/view.rb +61 -0
  70. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  71. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  72. data/lib/new_relic/metric_spec.rb +52 -0
  73. data/lib/new_relic/metrics.rb +7 -0
  74. data/lib/new_relic/noticed_error.rb +25 -0
  75. data/lib/new_relic/rack/metric_app.rb +56 -0
  76. data/lib/new_relic/rack/newrelic.ru +25 -0
  77. data/lib/new_relic/rack/newrelic.yml +25 -0
  78. data/lib/new_relic/rack_app.rb +5 -0
  79. data/lib/new_relic/recipes.rb +82 -0
  80. data/lib/new_relic/stats.rb +360 -0
  81. data/lib/new_relic/transaction_analysis.rb +121 -0
  82. data/lib/new_relic/transaction_sample.rb +583 -0
  83. data/lib/new_relic/version.rb +54 -0
  84. data/lib/new_relic_api.rb +315 -0
  85. data/lib/newrelic_rpm.rb +40 -0
  86. data/lib/tasks/all.rb +4 -0
  87. data/lib/tasks/install.rake +7 -0
  88. data/lib/tasks/tests.rake +13 -0
  89. data/newrelic.yml +214 -0
  90. data/recipes/newrelic.rb +6 -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 +234 -0
  95. data/test/new_relic/agent/agent_controller_test.rb +107 -0
  96. data/test/new_relic/agent/agent_test.rb +117 -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 +118 -0
  100. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +76 -0
  101. data/test/new_relic/agent/error_collector_test.rb +155 -0
  102. data/test/new_relic/agent/method_tracer_test.rb +335 -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 +51 -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 +78 -0
  109. data/test/new_relic/agent/stats_engine/stats_engine_test.rb +177 -0
  110. data/test/new_relic/agent/task_instrumentation_test.rb +67 -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 +146 -0
  114. data/test/new_relic/agent/transaction_sampler_test.rb +387 -0
  115. data/test/new_relic/agent/worker_loop_test.rb +103 -0
  116. data/test/new_relic/control_test.rb +94 -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 +214 -0
  128. data/ui/helpers/google_pie_chart.rb +55 -0
  129. data/ui/helpers/newrelic_helper.rb +314 -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 +15 -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 +45 -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 +77 -0
  155. data/ui/views/newrelic/show_source.rhtml +3 -0
  156. data/ui/views/newrelic/stylesheets/style.css +433 -0
  157. data/ui/views/newrelic/threads.rhtml +52 -0
  158. metadata +327 -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, &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, &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, 0.000001].max
67
+ sleep_time = (sleep_time > 1) ? 1 : sleep_time
68
+
69
+ sleep sleep_time
70
+
71
+ return if !keep_running
72
+ end
73
+
74
+ begin
75
+ task.execute
76
+ rescue ServerError => e
77
+ log.debug "Server Error: #{e}"
78
+ rescue NewRelic::Agent::ForceRestartException => e
79
+ # blow out the loop
80
+ raise
81
+ rescue RuntimeError => e
82
+ # This is probably a server error which has been logged in the server along
83
+ # with your account name. Check and see if the agent listener is in the
84
+ # stack trace and log it quietly if it is.
85
+ message = "Error running task in worker loop, likely a server error (#{e})"
86
+ if e.backtrace.grep(/agent_listener/).empty?
87
+ log.error message
88
+ else
89
+ log.debug message
90
+ log.debug e.backtrace.join("\n")
91
+ end
92
+ rescue Timeout::Error, NewRelic::Agent::IgnoreSilentlyException
93
+ # Want to ignore these because they are handled already
94
+ rescue ScriptError, StandardError => e
95
+ log.error "Error running task in Agent Worker Loop (#{e.class}): #{e} "
96
+ log.debug e.backtrace.join("\n")
97
+ end
98
+ end
99
+
100
+ class LoopTask
101
+
102
+ def initialize(call_period, &task_proc)
103
+ @call_period = call_period
104
+ @last_invocation_time = Time.now
105
+ @task = task_proc
106
+ end
107
+
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.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,436 @@
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
+
10
+ module NewRelic
11
+
12
+ # The Control is a singleton responsible for the startup and
13
+ # initialization sequence. The initializer uses a LocalEnvironment to
14
+ # detect the framework and instantiates the framework specific
15
+ # subclass.
16
+ #
17
+ # The Control also implements some of the public API for the agent.
18
+ #
19
+ class Control
20
+
21
+ attr_accessor :log_file
22
+ # The env is the setting used to identify which section of the newrelic.yml
23
+ # to load. This defaults to a framework specific value, such as ENV['RAILS_ENV']
24
+ # but can be overridden as long as you set it before calling #init_plugin
25
+ attr_writer :env
26
+ attr_reader :local_env
27
+
28
+ # Structs holding info for the remote server and proxy server
29
+ class Server < Struct.new :name, :port, :ip #:nodoc:
30
+ def to_s; "#{name}:#{port}"; end
31
+ end
32
+
33
+ ProxyServer = Struct.new :name, :port, :user, :password #:nodoc:
34
+
35
+ # Access the Control singleton, lazy initialized
36
+ def self.instance
37
+ @instance ||= new_instance
38
+ end
39
+
40
+ # Initialize the plugin/gem and start the agent. This does the necessary configuration based on the
41
+ # framework environment and determines whether or not to start the agent. If the
42
+ # agent is not going to be started then it loads the agent shim which has stubs
43
+ # for all the external api.
44
+ #
45
+ # This may be invoked multiple times, as long as you don't attempt to uninstall
46
+ # the agent after it has been started.
47
+ #
48
+ # If the plugin is initialized and it determines that the agent is not enabled, it
49
+ # will skip starting it and install the shim. But if you later call this with
50
+ # <tt>:agent_enabled => true</tt>, then it will install the real agent and start it.
51
+ #
52
+ # What determines whether the agent is launched is the result of calling agent_enabled?
53
+ # This will indicate whether the instrumentation should/will be installed. If we're
54
+ # in a mode where tracers are not installed then we should not start the agent.
55
+ #
56
+ # Subclasses are not allowed to override, but must implement init_config({}) which
57
+ # is called at most once.
58
+ #
59
+ def init_plugin(options={})
60
+ require 'new_relic/agent'
61
+ # Merge the stringified options into the config as overrides:
62
+ logger_override = options.delete(:log)
63
+ environment_name = options.delete(:env)
64
+ self.env = environment_name if environment_name
65
+ # Clear out the settings, if they've already been loaded. It may be that
66
+ # between calling init_plugin the first time and the second time, the env
67
+ # has been overridden
68
+ @settings = nil
69
+
70
+ options.each { |sym, val | self[sym.to_s] = val unless sym == :config }
71
+ if logger_override
72
+ @log = logger_override
73
+ # Try to grab the log filename
74
+ @log_file = @log.instance_eval { @logdev.filename rescue nil }
75
+ end
76
+ Module.send :include, NewRelic::Agent::MethodTracer
77
+ init_config(options)
78
+ if agent_enabled? && !@started
79
+ setup_log unless logger_override
80
+ start_agent
81
+ install_instrumentation
82
+ load_samplers unless self['disable_samplers']
83
+ local_env.gather_environment_info
84
+ append_environment_info
85
+ @started = true
86
+ elsif !agent_enabled?
87
+ install_shim
88
+ end
89
+ end
90
+
91
+ # Install the real agent into the Agent module, and issue the start command.
92
+ def start_agent
93
+ NewRelic::Agent.agent = NewRelic::Agent::Agent.instance
94
+ NewRelic::Agent.agent.start
95
+ end
96
+
97
+ def [](key)
98
+ fetch(key)
99
+ end
100
+
101
+ def settings
102
+ unless @settings
103
+ @settings = (@yaml && merge_defaults(@yaml[env])) || {}
104
+ # At the time we bind the settings, we also need to run this little piece
105
+ # of magic which allows someone to augment the id with the app name, necessary
106
+ if self['multi_homed'] && app_names.size > 0
107
+ if @local_env.dispatcher_instance_id
108
+ @local_env.dispatcher_instance_id << ":#{app_names.first}"
109
+ else
110
+ @local_env.dispatcher_instance_id = app_names.first
111
+ end
112
+ end
113
+
114
+ end
115
+ @settings
116
+ end
117
+
118
+ def []=(key, value)
119
+ settings[key] = value
120
+ end
121
+
122
+ def fetch(key, default=nil)
123
+ settings.fetch(key, default)
124
+ end
125
+ # Add your own environment value to track for change detection.
126
+ # The name and value should be stable and not vary across app processes on
127
+ # the same host.
128
+ def append_environment_info(name, value)
129
+ local_env.record_environment_info(name,value)
130
+ end
131
+
132
+ ###################################
133
+ # Agent config conveniences
134
+
135
+ def apdex_t
136
+ # Always initialized with a default
137
+ fetch('apdex_t').to_f
138
+ end
139
+ def license_key
140
+ fetch('license_key')
141
+ end
142
+ def capture_params
143
+ fetch('capture_params')
144
+ end
145
+ # True if we are sending data to the server, monitoring production
146
+ def monitor_mode?
147
+ fetch('enabled')
148
+ end
149
+ # True if we are capturing data and displaying in /newrelic
150
+ def developer_mode?
151
+ fetch('developer')
152
+ end
153
+ # True if we should view files in textmate
154
+ def use_textmate?
155
+ fetch('textmate')
156
+ end
157
+ # True if dev mode or monitor mode are enabled, and we are running
158
+ # inside a valid dispatcher like mongrel or passenger. Can be overridden
159
+ # by NEWRELIC_ENABLE env variable, monitor_daemons config option when true, or
160
+ # agent_enabled config option when true or false.
161
+ def agent_enabled?
162
+ return false if !developer_mode? && !monitor_mode?
163
+ return self['agent_enabled'].to_s =~ /true|on|yes/i if !self['agent_enabled'].nil? && self['agent_enabled'] != 'auto'
164
+ return false if ENV['NEWRELIC_ENABLE'].to_s =~ /false|off|no/i
165
+ return true if self['monitor_daemons'].to_s =~ /true|on|yes/i
166
+ return true if ENV['NEWRELIC_ENABLE'].to_s =~ /true|on|yes/i
167
+ # When in 'auto' mode the agent is enabled if there is a known
168
+ # dispatcher running
169
+ return true if @local_env.dispatcher != nil
170
+ end
171
+
172
+ def app
173
+ @local_env.framework
174
+ end
175
+ alias framework app
176
+
177
+ def dispatcher_instance_id
178
+ self['dispatcher_instance_id'] || @local_env.dispatcher_instance_id
179
+ end
180
+ def dispatcher
181
+ self['dispatcher'] || @local_env.dispatcher
182
+ end
183
+ def app_names
184
+ self['app_name'] ? self['app_name'].split(';') : []
185
+ end
186
+
187
+ def use_ssl?
188
+ @use_ssl ||= fetch('ssl', false)
189
+ end
190
+
191
+ def verify_certificate?
192
+ #this can only be on when SSL is enabled
193
+ @verify_certificate ||= ( use_ssl? ? fetch('verify_certificate', false) : false)
194
+ end
195
+
196
+ def server
197
+ @remote_server ||= server_from_host(nil)
198
+ end
199
+
200
+ def api_server
201
+ api_host = self['api_host'] || 'rpm.newrelic.com'
202
+ @api_server ||=
203
+ NewRelic::Control::Server.new \
204
+ api_host,
205
+ (self['api_port'] || self['port'] || (use_ssl? ? 443 : 80)).to_i,
206
+ nil
207
+ end
208
+
209
+ def proxy_server
210
+ @proxy_server ||=
211
+ NewRelic::Control::ProxyServer.new self['proxy_host'], self['proxy_port'], self['proxy_user'], self['proxy_pass']
212
+ end
213
+
214
+ def server_from_host(hostname=nil)
215
+ host = hostname || self['host'] || 'collector.newrelic.com'
216
+
217
+ # if the host is not an IP address, turn it into one
218
+ NewRelic::Control::Server.new host, (self['port'] || (use_ssl? ? 443 : 80)).to_i, convert_to_ip_address(host)
219
+ end
220
+
221
+ # Return the Net::HTTP with proxy configuration given the NewRelic::Control::Server object.
222
+ # Default is the collector but for api calls you need to pass api_server
223
+ #
224
+ # Experimental support for SSL verification:
225
+ # swap 'VERIFY_NONE' for 'VERIFY_PEER' line to try it out
226
+ # If verification fails, uncomment the 'http.ca_file' line
227
+ # and it will use the included certificate.
228
+ def http_connection(host = nil)
229
+ host ||= server
230
+ # Proxy returns regular HTTP if @proxy_host is nil (the default)
231
+ http_class = Net::HTTP::Proxy(proxy_server.name, proxy_server.port,
232
+ proxy_server.user, proxy_server.password)
233
+ http = http_class.new(host.ip || host.name, host.port)
234
+ if use_ssl?
235
+ http.use_ssl = true
236
+ if verify_certificate?
237
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
238
+ http.ca_file = File.join(File.dirname(__FILE__), '..', '..', 'cert', 'cacert.pem')
239
+ else
240
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
241
+ end
242
+ end
243
+ http
244
+ end
245
+ def to_s
246
+ "Control[#{self.app}]"
247
+ end
248
+
249
+ def log
250
+ # If we try to get a log before one has been set up, return a stdout log
251
+ unless @log
252
+ l = Logger.new(STDOUT)
253
+ l.level = Logger::INFO
254
+ return l
255
+ end
256
+ @log
257
+ end
258
+
259
+ # send the given message to STDOUT so that it shows
260
+ # up in the console. This should be used for important informational messages at boot.
261
+ # The to_stdout may be implemented differently by different config subclasses.
262
+ # This will NOT print anything if tracers are not enabled
263
+ def log!(msg, level=:info)
264
+ return if @settings && !agent_enabled?
265
+ to_stdout msg
266
+ log.send level, msg if @log
267
+ end
268
+
269
+ # Install stubs to the proper location so the app code will not fail
270
+ # if the agent is not running.
271
+ def install_shim
272
+ # Once we install instrumentation, you can't undo that by installing the shim.
273
+ raise "Cannot install the Agent shim after instrumentation has already been installed!" if @instrumented
274
+ NewRelic::Agent.agent = NewRelic::Agent::ShimAgent.instance
275
+ end
276
+
277
+ def install_instrumentation
278
+ return if @instrumented
279
+
280
+ @instrumented = true
281
+
282
+ # Instrumentation for the key code points inside rails for monitoring by NewRelic.
283
+ # note this file is loaded only if the newrelic agent is enabled (through config/newrelic.yml)
284
+ instrumentation_path = File.join(File.dirname(__FILE__), 'agent','instrumentation')
285
+ instrumentation_files = [ ] <<
286
+ File.join(instrumentation_path, '*.rb') <<
287
+ File.join(instrumentation_path, app.to_s, '*.rb')
288
+ instrumentation_files.each do | pattern |
289
+ Dir.glob(pattern) do |file|
290
+ begin
291
+ log.debug "Processing instrumentation file '#{file}'"
292
+ require file
293
+ rescue => e
294
+ log.error "Error loading instrumentation file '#{file}': #{e}"
295
+ log.debug e.backtrace.join("\n")
296
+ end
297
+ end
298
+ end
299
+
300
+ log.debug "Finished instrumentation"
301
+ end
302
+
303
+ def load_samplers
304
+ agent = NewRelic::Agent.instance
305
+ agent.stats_engine.add_sampler NewRelic::Agent::Samplers::MongrelSampler.new if local_env.mongrel
306
+ if NewRelic::Agent::Samplers::CpuSampler.supported_on_this_platform?
307
+ agent.stats_engine.add_harvest_sampler NewRelic::Agent::Samplers::CpuSampler.new
308
+ end
309
+ begin
310
+ if NewRelic::Agent::Samplers::MemorySampler.supported_on_this_platform?
311
+ agent.stats_engine.add_sampler NewRelic::Agent::Samplers::MemorySampler.new
312
+ end
313
+ rescue RuntimeError => e
314
+ log.error "Cannot add memory sampling: #{e}"
315
+ end
316
+ end
317
+
318
+ protected
319
+
320
+ # Append framework specific environment information for uploading to
321
+ # the server for change detection. Override in subclasses
322
+ def append_environment_info; end
323
+
324
+ # Look up the ip address of the host using the pure ruby lookup
325
+ # to prevent blocking. If that fails, fall back to the regular
326
+ # IPSocket library. Return nil if we can't find the host ip
327
+ # address and don't have a good default.
328
+ def convert_to_ip_address(host)
329
+ # here we leave it as a host name since the cert verification
330
+ # needs it in host form
331
+ return host if verify_certificate?
332
+ return nil if host.nil? || host.downcase == "localhost"
333
+ # Fall back to known ip address in the common case
334
+ ip_address = '65.74.177.195' if host.downcase == 'collector.newrelic.com'
335
+ begin
336
+ ip_address = Resolv.getaddress(host)
337
+ log.info "Resolved #{host} to #{ip_address}"
338
+ rescue => e
339
+ log.warn "DNS Error caching IP address: #{e}"
340
+ log.debug e.backtrace.join("\n ")
341
+ ip_address = IPSocket::getaddress host rescue ip_address
342
+ end
343
+ ip_address
344
+ end
345
+
346
+ def merge_defaults(settings_hash)
347
+ s = {
348
+ 'host' => 'collector.newrelic.com',
349
+ 'ssl' => false,
350
+ 'log_level' => 'info',
351
+ 'apdex_t' => 1.0
352
+ }
353
+ s.merge! settings_hash if settings_hash
354
+ # monitor_daemons replaced with agent_enabled
355
+ s['agent_enabled'] = s.delete('monitor_daemons') if s['agent_enabled'].nil? && s.include?('monitor_daemons')
356
+ s
357
+ end
358
+ # Control subclasses may override this, but it can be called multiple times.
359
+ def setup_log
360
+ @log_file = "#{log_path}/newrelic_agent.log"
361
+ @log = Logger.new @log_file
362
+
363
+ # change the format just for our logger
364
+
365
+ def @log.format_message(severity, timestamp, progname, msg)
366
+ "[#{timestamp.strftime("%m/%d/%y %H:%M:%S %z")} #{Socket.gethostname} (#{$$})] #{severity} : #{msg}\n"
367
+ end
368
+
369
+ # set the log level as specified in the config file
370
+ case fetch("log_level","info").downcase
371
+ when "debug"; @log.level = Logger::DEBUG
372
+ when "info"; @log.level = Logger::INFO
373
+ when "warn"; @log.level = Logger::WARN
374
+ when "error"; @log.level = Logger::ERROR
375
+ when "fatal"; @log.level = Logger::FATAL
376
+ else @log.level = Logger::INFO
377
+ end
378
+ @log
379
+ end
380
+
381
+ def to_stdout(msg)
382
+ STDOUT.puts "** [NewRelic] " + msg
383
+ end
384
+
385
+ def config_file
386
+ File.expand_path(File.join(root,"config","newrelic.yml"))
387
+ end
388
+
389
+ def log_path
390
+ path = File.join(root,'log')
391
+ unless File.directory? path
392
+ path = '.'
393
+ end
394
+ File.expand_path(path)
395
+ end
396
+
397
+ # Create the concrete class for environment specific behavior:
398
+ def self.new_instance
399
+ @local_env = NewRelic::LocalEnvironment.new
400
+ if @local_env.framework == :test
401
+ require File.join(newrelic_root, "test", "config", "test_control.rb")
402
+ NewRelic::Control::Test.new @local_env
403
+ else
404
+ require "new_relic/control/#{@local_env.framework}.rb"
405
+ NewRelic::Control.const_get(@local_env.framework.to_s.capitalize).new @local_env
406
+ end
407
+ end
408
+
409
+ def initialize local_env
410
+ @local_env = local_env
411
+ newrelic_file = config_file
412
+ # Next two are for populating the newrelic.yml via erb binding, necessary
413
+ # when using the default newrelic.yml file
414
+ generated_for_user = ''
415
+ license_key=''
416
+ if !File.exists?(config_file)
417
+ log! "Cannot find newrelic.yml file at #{config_file}."
418
+ @yaml = {}
419
+ else
420
+ @yaml = YAML.load(ERB.new(File.read(config_file)).result(binding))
421
+ end
422
+ rescue ScriptError, StandardError => e
423
+ puts e
424
+ puts e.backtrace.join("\n")
425
+ raise "Error reading newrelic.yml file: #{e}"
426
+ end
427
+
428
+ # The root directory for the plugin or gem
429
+ def self.newrelic_root
430
+ File.expand_path(File.join(File.dirname(__FILE__),"..",".."))
431
+ end
432
+ def newrelic_root
433
+ self.class.newrelic_root
434
+ end
435
+ end
436
+ end