Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'master' of git@github.com:redinger/enki

  • Loading branch information...
commit 3358ff9c09a6dfd60b44e39a941c22555b88ff6c 2 parents f4528e3 + 6f32288
root authored
Showing with 9,691 additions and 0 deletions.
  1. +37 −0 vendor/plugins/newrelic_rpm/LICENSE
  2. +136 −0 vendor/plugins/newrelic_rpm/README
  3. +40 −0 vendor/plugins/newrelic_rpm/Rakefile
  4. +4 −0 vendor/plugins/newrelic_rpm/bin/newrelic_cmd
  5. +30 −0 vendor/plugins/newrelic_rpm/init.rb
  6. +37 −0 vendor/plugins/newrelic_rpm/install.rb
  7. +29 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent.rb
  8. +740 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/agent.rb
  9. +13 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/chained_call.rb
  10. +72 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/collection_helper.rb
  11. +105 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/error_collector.rb
  12. +18 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/active_merchant.rb
  13. +95 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb
  14. +151 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/controller_instrumentation.rb
  15. +90 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/data_mapper.rb
  16. +105 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb
  17. +18 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/memcache.rb
  18. +17 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/merb/controller.rb
  19. +15 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/merb/dispatcher.rb
  20. +6 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/merb/errors.rb
  21. +35 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/action_controller.rb
  22. +27 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/action_web_service.rb
  23. +30 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/dispatcher.rb
  24. +23 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/errors.rb
  25. +6 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/rails.rb
  26. +171 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/method_tracer.rb
  27. +105 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/patch_const_missing.rb
  28. +29 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/samplers/cpu.rb
  29. +55 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/samplers/memory.rb
  30. +26 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/samplers/mongrel.rb
  31. +240 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/stats_engine.rb
  32. +40 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/synchronize.rb
  33. +274 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/transaction_sampler.rb
  34. +128 −0 vendor/plugins/newrelic_rpm/lib/new_relic/agent/worker_loop.rb
  35. +147 −0 vendor/plugins/newrelic_rpm/lib/new_relic/commands/deployments.rb
  36. +30 −0 vendor/plugins/newrelic_rpm/lib/new_relic/commands/new_relic_commands.rb
  37. +278 −0 vendor/plugins/newrelic_rpm/lib/new_relic/config.rb
  38. +35 −0 vendor/plugins/newrelic_rpm/lib/new_relic/config/merb.rb
  39. +119 −0 vendor/plugins/newrelic_rpm/lib/new_relic/config/rails.rb
  40. +22 −0 vendor/plugins/newrelic_rpm/lib/new_relic/config/ruby.rb
  41. +135 −0 vendor/plugins/newrelic_rpm/lib/new_relic/local_environment.rb
  42. +6 −0 vendor/plugins/newrelic_rpm/lib/new_relic/merbtasks.rb
  43. +26 −0 vendor/plugins/newrelic_rpm/lib/new_relic/metric_data.rb
  44. +39 −0 vendor/plugins/newrelic_rpm/lib/new_relic/metric_spec.rb
  45. +7 −0 vendor/plugins/newrelic_rpm/lib/new_relic/metrics.rb
  46. +21 −0 vendor/plugins/newrelic_rpm/lib/new_relic/noticed_error.rb
  47. +75 −0 vendor/plugins/newrelic_rpm/lib/new_relic/recipes.rb
  48. +95 −0 vendor/plugins/newrelic_rpm/lib/new_relic/shim_agent.rb
  49. +359 −0 vendor/plugins/newrelic_rpm/lib/new_relic/stats.rb
  50. +122 −0 vendor/plugins/newrelic_rpm/lib/new_relic/transaction_analysis.rb
  51. +499 −0 vendor/plugins/newrelic_rpm/lib/new_relic/transaction_sample.rb
  52. +151 −0 vendor/plugins/newrelic_rpm/lib/new_relic/version.rb
  53. +255 −0 vendor/plugins/newrelic_rpm/lib/new_relic_api.rb
  54. +27 −0 vendor/plugins/newrelic_rpm/lib/newrelic_rpm.rb
  55. +17 −0 vendor/plugins/newrelic_rpm/lib/tasks/agent_tests.rake
  56. +4 −0 vendor/plugins/newrelic_rpm/lib/tasks/all.rb
  57. +7 −0 vendor/plugins/newrelic_rpm/lib/tasks/install.rake
  58. +137 −0 vendor/plugins/newrelic_rpm/newrelic.yml
  59. +9 −0 vendor/plugins/newrelic_rpm/newrelic_spec.rb
  60. +6 −0 vendor/plugins/newrelic_rpm/recipes/newrelic.rb
  61. +1 −0  vendor/plugins/newrelic_rpm/spec_helper.rb
  62. +28 −0 vendor/plugins/newrelic_rpm/test/config/newrelic.yml
  63. +35 −0 vendor/plugins/newrelic_rpm/test/config/test_config.rb
  64. +25 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/agent_test_controller.rb
  65. +40 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/mock_ar_connection.rb
  66. +23 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/mock_scope_listener.rb
  67. +15 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/model_fixture.rb
  68. +90 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_active_record.rb
  69. +148 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_agent.rb
  70. +113 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_collection_helper.rb
  71. +77 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_controller.rb
  72. +52 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_dispatcher_instrumentation.rb
  73. +127 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_error_collector.rb
  74. +306 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_method_tracer.rb
  75. +218 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_stats_engine.rb
  76. +37 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_synchronize.rb
  77. +175 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_transaction_sample.rb
  78. +194 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_transaction_sample_builder.rb
  79. +302 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_transaction_sampler.rb
  80. +101 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/tc_worker_loop.rb
  81. +13 −0 vendor/plugins/newrelic_rpm/test/new_relic/agent/testable_agent.rb
  82. +36 −0 vendor/plugins/newrelic_rpm/test/new_relic/tc_config.rb
  83. +64 −0 vendor/plugins/newrelic_rpm/test/new_relic/tc_deployments_api.rb
  84. +94 −0 vendor/plugins/newrelic_rpm/test/new_relic/tc_environment.rb
  85. +150 −0 vendor/plugins/newrelic_rpm/test/new_relic/tc_metric_spec.rb
  86. +9 −0 vendor/plugins/newrelic_rpm/test/new_relic/tc_shim_agent.rb
  87. +141 −0 vendor/plugins/newrelic_rpm/test/new_relic/tc_stats.rb
  88. +40 −0 vendor/plugins/newrelic_rpm/test/test_helper.rb
  89. +44 −0 vendor/plugins/newrelic_rpm/test/ui/tc_newrelic_helper.rb
  90. +200 −0 vendor/plugins/newrelic_rpm/ui/controllers/newrelic_controller.rb
  91. +55 −0 vendor/plugins/newrelic_rpm/ui/helpers/google_pie_chart.rb
  92. +286 −0 vendor/plugins/newrelic_rpm/ui/helpers/newrelic_helper.rb
  93. +49 −0 vendor/plugins/newrelic_rpm/ui/views/layouts/newrelic_default.rhtml
  94. +27 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_explain_plans.rhtml
  95. +12 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_sample.rhtml
  96. +28 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_segment.rhtml
  97. +14 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_segment_row.rhtml
  98. +22 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_show_sample_detail.rhtml
  99. +19 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_show_sample_sql.rhtml
  100. +3 −0  vendor/plugins/newrelic_rpm/ui/views/newrelic/_show_sample_summary.rhtml
  101. +11 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_sql_row.rhtml
  102. +30 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_stack_trace.rhtml
  103. +12 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/_table.rhtml
  104. +45 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/explain_sql.rhtml
  105. BIN  vendor/plugins/newrelic_rpm/ui/views/newrelic/images/arrow-close.png
  106. BIN  vendor/plugins/newrelic_rpm/ui/views/newrelic/images/arrow-open.png
  107. BIN  vendor/plugins/newrelic_rpm/ui/views/newrelic/images/blue_bar.gif
  108. BIN  vendor/plugins/newrelic_rpm/ui/views/newrelic/images/gray_bar.gif
  109. +37 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/index.rhtml
  110. +107 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/javascript/transaction_sample.js
  111. +2 −0  vendor/plugins/newrelic_rpm/ui/views/newrelic/sample_not_found.rhtml
  112. +62 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/show_sample.rhtml
  113. +3 −0  vendor/plugins/newrelic_rpm/ui/views/newrelic/show_source.rhtml
  114. +394 −0 vendor/plugins/newrelic_rpm/ui/views/newrelic/stylesheets/style.css
View
37 vendor/plugins/newrelic_rpm/LICENSE
@@ -0,0 +1,37 @@
+Copyright (c) 2008 New Relic, Inc. All rights reserved.
+
+Certain inventions disclosed in this file may be claimed within
+patents owned or patent applications filed by New Relic, Inc. or third
+parties.
+
+Subject to the terms of this notice, New Relic grants you a
+nonexclusive, nontransferable license, without the right to
+sublicense, to (a) install and execute one copy of these files on any
+number of workstations owned or controlled by you and (b) distribute
+verbatim copies of these files to third parties. As a condition to the
+foregoing grant, you must provide this notice along with each copy you
+distribute and you must not remove, alter, or obscure this notice. All
+other use, reproduction, modification, distribution, or other
+exploitation of these files is strictly prohibited, except as may be set
+forth in a separate written license agreement between you and New
+Relic. The terms of any such license agreement will control over this
+notice. The license stated above will be automatically terminated and
+revoked if you exceed its scope or violate any of the terms of this
+notice.
+
+This License does not grant permission to use the trade names,
+trademarks, service marks, or product names of New Relic, except as
+required for reasonable and customary use in describing the origin of
+this file and reproducing the content of this notice. You may not
+mark or brand this file with any trade name, trademarks, service
+marks, or product names other than the original brand (if any)
+provided by New Relic.
+
+Unless otherwise expressly agreed by New Relic in a separate written
+license agreement, these files are provided AS IS, WITHOUT WARRANTY OF
+ANY KIND, including without any implied warranties of MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE, TITLE, or NON-INFRINGEMENT. As a
+condition to your use of these files, you are solely responsible for
+such use. New Relic will have no liability to you for direct,
+indirect, consequential, incidental, special, or punitive damages or
+for lost profits or data.
View
136 vendor/plugins/newrelic_rpm/README
@@ -0,0 +1,136 @@
+= New Relic RPM
+
+* http://www.newrelic.com
+
+New Relic RPM is a Ruby performance management system, developed by
+New Relic, Inc. RPM provides you with deep information about the
+performance of your Ruby on Rails or Merb application as it runs in
+production. The New Relic Agent is distributed as a either a Rails
+plugin or a Gem, both hosted on RubyForge.
+
+The New Relic Agent runs in one of two modes:
+
+* Developer Mode : Adds a web interface mapped to /newrelic to your
+ application for showing detailed performance metrics on a page by
+ page basis.
+
+* Production Mode : Low overhead instrumentation that captures
+ detailed information on your application running in production and
+ transmits them to rpm.newrelic.com where you can monitor them in
+ real time.
+
+=== Developer Mode
+
+Developer mode is on by default when you run your application in the
+development environment (but not when it runs in other environments.)
+When running in developer mode, RPM will track the performance of
+every http request serviced by your application, and store in memory
+this information for the last 100 http transactions.
+
+When running in Developer Mode, the RPM will also add a few pages to
+your application that allow you to analyze this performance
+information. (Don't worry - those pages are not added to your
+application's routes when you run in production mode.)
+
+To view this performance information, including detailed SQL statement
+analysis, open '/newrelic' in your web application. For instance if
+you are running mongrel or thin on port 3000, enter the following into
+your browser:
+
+http://localhost:3000/newrelic
+
+=== Production Mode
+
+To monitor your applications in production, create an account at
+http://newrelic.com/get-RPM.html
+
+When your application runs in the production environment, the New
+Relic agent runs in production mode. It connects to the New Relic RPM
+service and sends deep performance data to the RPM service for your
+analysis. To view this data, login to http://rpm.newrelic.com.
+
+NOTE: You must have a valid account and license key to view this data
+online. When you sign up for an account at www.newrelic.com, you will
+be provided with a license key, as well as a default configuration
+file for New Relic RPM. You can either paste your license key into
+your existing configuration file, config/newrelic.yml, or you can
+replace that config file with the one included in your welcome email.
+
+== Installation
+
+RPM requires an agent be installed in the application as either a
+Rails plug-in or a gem. Both are available on RubyForge--instructions
+below.
+
+=== Git your RPM on!
+
+The agent is also available on Github under newrelic/rpm. As of March,
+2009, we keep the latest release on the master, the edge on a working
+branch, and previous releases with tags. Fork away!
+
+=== Requirements:
+
+Ruby 1.8.6
+Rails 1.2.6 or above
+Merb 1.0 or above
+
+=== Rails Plug-In Installation
+
+ script/plugin install http://newrelic.rubyforge.org/svn/newrelic_rpm
+
+=== Gem Installation
+
+ sudo gem install newrelic_rpm
+
+For Rails, edit environment.rb and add to the initalizer block:
+
+ config.gem "newrelic_rpm"
+
+The Developer Mode is unavailable when using the gem on Rails versions
+prior to 2.0.
+
+== Merb Support
+
+To monitor a merb app install the newrelic_rpm gem and add
+
+ dependency 'newrelic_rpm'
+
+to your init.rb file.
+
+Current features implemented:
+
+ * Standard monitoring, overview pages
+ * Error capturing
+ * Full Active Record instrumentation, including SQL explains
+ * Very limited Data Mapper instrumentation
+ * Transaction Traces are implemented but will not be very useful
+ with Data Mapper until more work is done with the Data Mapper
+ instrumentation
+
+Still under development:
+
+ * Developer Mode
+ * Data Mapper bindings
+
+Also some of the instrumentation has been implemented with method
+chaining, a mechanism that truly goes against the merb way. This was
+necessary in some cases where the API’s were not yet available or
+understood well enough to use. Converting these to hook methods using
+the Merb public API is a work in progress.
+
+== Support
+
+Reach out to us--and to fellow RPM users--at
+http://support.newrelic.com. There you'll find documentation, FAQs,
+and forums where you can submit suggestions and discuss RPM with New
+Relic staff and other users.
+
+Find a bug? E-mail support@newrelic.com, or post it to
+support.newrelic.com.
+
+For other support channels, see http://www.newrelic.com/support.
+
+Thank you, and may your application scale to infinity plus one.
+
+Lew Cirne, Founder and CEO
+New Relic, Inc.
View
40 vendor/plugins/newrelic_rpm/Rakefile
@@ -0,0 +1,40 @@
+require 'rubygems'
+require 'rake/gempackagetask'
+require 'lib/new_relic/version.rb'
+
+GEM_NAME = "newrelic_rpm"
+GEM_VERSION = NewRelic::VERSION::STRING
+AUTHOR = "Bill Kayser"
+EMAIL = "bkayser@newrelic.com"
+HOMEPAGE = "http://www.newrelic.com"
+SUMMARY = "New Relic Ruby Performance Monitoring Agent"
+
+spec = Gem::Specification.new do |s|
+ s.rubyforge_project = 'newrelic'
+ s.name = GEM_NAME
+ s.version = GEM_VERSION
+ s.platform = Gem::Platform::RUBY
+ s.has_rdoc = true
+ s.extra_rdoc_files = ["README", "LICENSE"]
+ s.summary = SUMMARY
+ s.description = s.summary
+ s.author = AUTHOR
+ s.email = EMAIL
+ s.homepage = HOMEPAGE
+ s.require_path = 'lib'
+ s.files = %w(install.rb LICENSE README newrelic.yml Rakefile) + Dir.glob("{lib,bin,recipes,test,ui}/**/*")
+ s.bindir = "bin" # Use these for applications.
+ s.executables = ["newrelic_cmd"]
+ s.default_executable = "newrelic_cmd"
+end
+
+Rake::GemPackageTask.new(spec) do |pkg|
+ pkg.gem_spec = spec
+end
+
+desc "Create a gemspec file"
+task :gemspec do
+ File.open("#{GEM_NAME}.gemspec", "w") do |file|
+ file.puts spec.to_ruby
+ end
+end
View
4 vendor/plugins/newrelic_rpm/bin/newrelic_cmd
@@ -0,0 +1,4 @@
+#!/usr/bin/env ruby
+# executes one of the commands in the new_relic/commands directory
+# pass the name of the command as an argument
+require File.dirname(__FILE__) + '/../lib/new_relic/commands/new_relic_commands'
View
30 vendor/plugins/newrelic_rpm/init.rb
@@ -0,0 +1,30 @@
+# This is the initialization for the RPM Rails plugin
+require 'new_relic/config'
+
+# If you are having problems seeing data, be sure and check the
+# newrelic_agent log files.
+#
+# If you can't find any log files and you don't see anything in your
+# application log files, try uncommenting these lines to verify
+# the plugin is being loaded, then contact support@newrelic.com
+# if you are unable to resolve the issue.
+# STDOUT.puts "RPM detected environment: #{NewRelic::Config.instance.local_env}, RAILS_ENV: #{RAILS_ENV}"
+
+# Initializer for the NewRelic Agent
+
+begin
+ # JRuby's glassfish plugin is trying to run the Initializer twice,
+ # which isn't a good thing so we ignore subsequent invocations here.
+ if ! defined?(::NEWRELIC_STARTED)
+ ::NEWRELIC_STARTED = "#{caller.join("\n")}"
+ NewRelic::Config.instance.start_plugin (defined?(config) ? config : nil)
+ else
+ NewRelic::Config.instance.log.debug "Attempt to initialize the plugin twice!"
+ NewRelic::Config.instance.log.debug "Original call: \n#{::NEWRELIC_STARTED}"
+ NewRelic::Config.instance.log.debug "Here we are now: \n#{caller.join("\n")}"
+ end
+rescue => e
+ NewRelic::Config.instance.log! "Error initializing New Relic plugin (#{e})", :error
+ NewRelic::Config.instance.log! e.backtrace.join("\n"), :error
+ NewRelic::Config.instance.log! "Agent is disabled."
+end
View
37 vendor/plugins/newrelic_rpm/install.rb
@@ -0,0 +1,37 @@
+require 'ftools'
+require 'erb'
+
+# Install a newrelic.yml file into the local config directory.
+# If no such directory exists, install it in ~/.newrelic.
+#
+# If a config file already exists, print a warning and exit.
+#
+if File.directory? "config"
+ dest_dir = "config"
+else
+ dest_dir = File.join(ENV["HOME"],".newrelic") rescue nil
+ FileUtils.mkdir(dest_dir) if dest_dir
+end
+
+src_config_file = File.join(File.dirname(__FILE__),"newrelic.yml")
+dest_config_file = File.join(dest_dir, "newrelic.yml") if dest_dir
+
+if !dest_dir
+ STDERR.puts "Could not find a config or ~/.newrelic directory to locate the default newrelic.yml file"
+elsif File::exists? dest_config_file
+ STDERR.puts "\nA config file already exists at #{dest_config_file}.\n"
+else
+ generated_for_user = ""
+ license_key = "PASTE_YOUR_KEY_HERE"
+ yaml = ERB.new(File.read(src_config_file)).result(binding)
+ File.open( dest_config_file, 'w' ) do |out|
+ out.puts yaml
+ end
+
+ puts IO.read(File.join(File.dirname(__FILE__), 'README'))
+ puts "\n--------------------------------------------------------\n"
+ puts "Installing a default configuration file in #{dest_dir}."
+ puts "To monitor your application in production mode, you must enter a license key."
+ puts "See #{dest_config_file}"
+ puts "For a license key, sign up at http://rpm.newrelic.com/signup."
+end
View
29 vendor/plugins/newrelic_rpm/lib/new_relic/agent.rb
@@ -0,0 +1,29 @@
+require 'new_relic/version'
+require 'new_relic/local_environment'
+require 'new_relic/stats'
+require 'new_relic/metric_spec'
+require 'new_relic/metric_data'
+require 'new_relic/transaction_analysis'
+require 'new_relic/transaction_sample'
+require 'new_relic/noticed_error'
+
+require 'new_relic/agent/chained_call'
+require 'new_relic/agent/agent'
+require 'new_relic/agent/method_tracer'
+require 'new_relic/agent/synchronize'
+require 'new_relic/agent/worker_loop'
+require 'new_relic/agent/stats_engine'
+require 'new_relic/agent/collection_helper'
+require 'new_relic/agent/transaction_sampler'
+require 'new_relic/agent/error_collector'
+
+require 'set'
+require 'sync'
+require 'thread'
+require 'resolv'
+require 'timeout'
+
+
+module NewRelic::Agent
+
+end
View
740 vendor/plugins/newrelic_rpm/lib/new_relic/agent/agent.rb
@@ -0,0 +1,740 @@
+require 'socket'
+require 'net/https'
+require 'net/http'
+require 'logger'
+require 'singleton'
+require 'zlib'
+require 'stringio'
+
+# The NewRelic Agent collects performance data from ruby applications in realtime as the
+# application runs, and periodically sends that data to the NewRelic server.
+module NewRelic::Agent
+ # an exception that is thrown by the server if the agent license is invalid
+ class LicenseException < StandardError; end
+
+ # an exception that forces an agent to stop reporting until its mongrel is restarted
+ class ForceDisconnectException < StandardError; end
+
+ class IgnoreSilentlyException < StandardError; end
+
+ # Reserved for future use
+ class ServerError < StandardError; end
+
+ class BackgroundLoadingError < StandardError; end
+
+ # add some convenience methods for easy access to the Agent singleton.
+ # the following static methods all point to the same Agent instance:
+ #
+ # NewRelic::Agent.agent
+ # NewRelic::Agent.instance
+ # NewRelic::Agent::Agent.instance
+ class << self
+ def agent
+ NewRelic::Agent::Agent.instance
+ end
+
+ alias instance agent
+
+ # Get or create a statistics gatherer that will aggregate numerical data
+ # under a metric name.
+ #
+ # metric_name should follow a slash separated path convention. Application
+ # specific metrics should begin with "Custom/".
+ #
+ # the statistical gatherer returned by get_stats accepts data
+ # via calls to add_data_point(value)
+ def get_stats(metric_name, use_scope=false)
+ agent.stats_engine.get_stats(metric_name, use_scope)
+ end
+
+ def get_stats_no_scope(metric_name)
+ agent.stats_engine.get_stats_no_scope(metric_name)
+ end
+
+
+ # Call this to manually start the Agent in situations where the Agent does
+ # not auto-start.
+ # When the app environment loads, so does the Agent. However, the Agent will
+ # only connect to RPM if a web front-end is found. If you want to selectively monitor
+ # ruby processes that don't use web plugins, then call this method in your
+ # code and the Agent will fire up and start reporting to RPM.
+ #
+ # environment - the name of the environment. used for logging only
+ # port - the name of this instance. shows up in the RPM UI screens. can be any String
+ #
+ def manual_start(environment, identifier)
+ agent.manual_start(environment, identifier)
+ end
+
+ # This method sets the block sent to this method as a sql obfuscator.
+ # The block will be called with a single String SQL statement to obfuscate.
+ # The method must return the obfuscated String SQL.
+ # If chaining of obfuscators is required, use type = :before or :after
+ #
+ # type = :before, :replace, :after
+ #
+ # example:
+ # NewRelic::Agent.set_sql_obfuscator(:replace) do |sql|
+ # my_obfuscator(sql)
+ # end
+ #
+ def set_sql_obfuscator(type = :replace, &block)
+ agent.set_sql_obfuscator type, &block
+ end
+
+
+ # This method sets the state of sql recording in the transaction
+ # sampler feature. Within the given block, no sql will be recorded
+ #
+ # usage:
+ #
+ # NewRelic::Agent.disable_sql_recording do
+ # ...
+ # end
+ #
+ def disable_sql_recording
+ state = agent.set_record_sql(false)
+ begin
+ yield
+ ensure
+ agent.set_record_sql(state)
+ end
+ end
+
+ # This method disables the recording of transaction traces in the given
+ # block.
+ def disable_transaction_tracing
+ state = agent.set_record_tt(false)
+ begin
+ yield
+ ensure
+ agent.set_record_tt(state)
+ end
+ end
+
+ # This method allows a filter to be applied to errors that RPM will track.
+ # The block should return the exception to track (which could be different from
+ # the original exception) or nil to ignore this exception
+ #
+ def ignore_error_filter(&block)
+ agent.error_collector.ignore_error_filter(&block)
+ end
+
+ # Add parameters to the current transaction trace
+ #
+ def add_custom_parameters(params)
+ agent.add_custom_parameters(params)
+ end
+
+ alias add_request_parameters add_custom_parameters
+
+ end
+
+ # Implementation default for the NewRelic Agent
+ class Agent
+ # Specifies the version of the agent's communication protocol
+ # with the NewRelic hosted site.
+
+ PROTOCOL_VERSION = 5
+
+ include Singleton
+
+ # Config object
+ attr_accessor :config
+ attr_reader :obfuscator
+ attr_reader :stats_engine
+ attr_reader :transaction_sampler
+ attr_reader :error_collector
+ attr_reader :worker_loop
+ attr_reader :license_key
+ attr_reader :record_sql
+ attr_reader :identifier
+
+ # This method is deprecated. Use start.
+ def manual_start(environment, identifier)
+ start(environment, identifier, true)
+ end
+
+ # Start up the agent, which will connect to the newrelic server and start
+ # reporting performance information. Typically this is done from the
+ # environment configuration file.
+ # environment identifies the host environment, like mongrel, thin, or take.
+ # identifier is an identifier which uniquely identifies the process hosting
+ # the agent. It should be ideally something like a server port, like 3000,
+ # a handler thread name, or a script name. It should not be a PID because
+ # that will change
+ # from invocation to invocation. For something like rake, you could use
+ # the task name.
+ # Return false if the agent was not started
+ def start(environment, identifier, force=false)
+
+ if @started
+ config.log! "Agent Started Already!"
+ return
+ end
+ @environment = environment
+ @identifier = identifier && identifier.to_s
+ if @identifier
+ start_reporting(force)
+ config.log! "New Relic RPM Agent #{NewRelic::VERSION::STRING} Initialized: pid = #{$$}, handler = #{@environment}"
+ config.log! "Agent Log is found in #{NewRelic::Config.instance.log_file}"
+ return true
+ else
+ return false
+ end
+ end
+
+ # this method makes sure that the agent is running. it's important
+ # for passenger where processes are forked and the agent is dormant
+ #
+ def ensure_worker_thread_started
+ return unless @prod_mode_enabled && !@invalid_license
+ if @worker_loop.nil? || @worker_loop.pid != $$
+ launch_worker_thread
+ @stats_engine.spawn_sampler_thread
+ end
+ end
+
+ # True if we have initialized and completed 'start_reporting'
+ def started?
+ @started
+ end
+
+
+ # Attempt a graceful shutdown of the agent.
+ def shutdown
+ return if !@started
+ if @worker_loop
+ @worker_loop.stop
+
+ log.debug "Starting Agent shutdown"
+
+ # if litespeed, then ignore all future SIGUSR1 - it's litespeed trying to shut us down
+
+ if @environment == :litespeed
+ Signal.trap("SIGUSR1", "IGNORE")
+ Signal.trap("SIGTERM", "IGNORE")
+ end
+
+ begin
+ graceful_disconnect
+ rescue => e
+ log.error e
+ log.error e.backtrace.join("\n")
+ end
+ end
+ @started = nil
+ end
+
+ def start_transaction
+ Thread::current[:custom_params] = nil
+ @stats_engine.start_transaction
+ end
+
+ def end_transaction
+ Thread::current[:custom_params] = nil
+ @stats_engine.end_transaction
+ end
+
+ def set_record_sql(should_record)
+ prev = Thread::current[:record_sql]
+ Thread::current[:record_sql] = should_record
+
+ prev || true
+ end
+
+ def set_record_tt(should_record)
+ prev = Thread::current[:record_tt]
+ Thread::current[:record_tt] = should_record
+
+ prev || true
+ end
+
+ def add_custom_parameters(params)
+ p = Thread::current[:custom_params] || (Thread::current[:custom_params] = {})
+
+ p.merge!(params)
+ end
+
+ def custom_params
+ Thread::current[:custom_params] || {}
+ end
+
+ def set_sql_obfuscator(type, &block)
+ if type == :before
+ @obfuscator = NewRelic::ChainedCall.new(block, @obfuscator)
+ elsif type == :after
+ @obfuscator = NewRelic::ChainedCall.new(@obfuscator, block)
+ elsif type == :replace
+ @obfuscator = block
+ else
+ fail "unknown sql_obfuscator type #{type}"
+ end
+ end
+
+ def instrument_app
+ return if @instrumented
+
+ @instrumented = true
+
+ # Instrumentation for the key code points inside rails for monitoring by NewRelic.
+ # note this file is loaded only if the newrelic agent is enabled (through config/newrelic.yml)
+ instrumentation_path = File.join(File.dirname(__FILE__), 'instrumentation')
+ instrumentation_files = [ ] <<
+ File.join(instrumentation_path, '*.rb') <<
+ File.join(instrumentation_path, config.app.to_s, '*.rb')
+ instrumentation_files.each do | pattern |
+ Dir.glob(pattern) do |file|
+ begin
+ log.debug "Processing instrumentation file '#{file}'"
+ require file
+ rescue => e
+ log.error "Error loading instrumentation file '#{file}': #{e}"
+ log.debug e.backtrace.join("\n")
+ end
+ end
+ end
+
+ log.debug "Finished instrumentation"
+ end
+
+ def log
+ setup_log unless @log
+ @log
+ end
+
+ def apdex_t
+ @apdex_t ||= config['apdex_t'].to_f
+ end
+
+ private
+
+ # Connect to the server, and run the worker loop forever. Will not return.
+ def run_worker_loop
+
+ # connect to the server. this will keep retrying until successful or
+ # it determines the license is bad.
+ connect
+
+ # We may not be connected now but keep going for dev mode
+ if @connected
+ begin
+ # determine the reporting period (server based)
+ # note if the agent attempts to report more frequently than the specified
+ # report data, then it will be ignored.
+
+ config.log! "Reporting performance data every #{@report_period} seconds"
+ @worker_loop.add_task(@report_period) do
+ harvest_and_send_timeslice_data
+ end
+
+ if @should_send_samples && @use_transaction_sampler
+ @worker_loop.add_task(@report_period) do
+ harvest_and_send_slowest_sample
+ end
+ elsif !config.developer_mode?
+ # We still need the sampler for dev mode.
+ @transaction_sampler.disable
+ end
+
+ if @should_send_errors && @error_collector.enabled
+ @worker_loop.add_task(@report_period) do
+ harvest_and_send_errors
+ end
+ end
+ @worker_loop.run
+ rescue StandardError
+ @connected = false
+ raise
+ end
+ end
+ end
+
+ def launch_worker_thread
+ if (@environment == :passenger && $0 =~ /ApplicationSpawner/)
+ log.info "Process is passenger spawner - don't connect to RPM service"
+ return
+ end
+
+ @worker_loop = WorkerLoop.new(log)
+
+ if config['check_bg_loading']
+ require 'new_relic/agent/patch_const_missing'
+ log.warn "Agent background loading checking turned on"
+ ClassLoadingWatcher.enable_warning
+ end
+
+ @worker_thread = Thread.new do
+ begin
+ ClassLoadingWatcher.set_background_thread(Thread.current) if config['check_bg_loading']
+ run_worker_loop
+ rescue IgnoreSilentlyException
+ config.log! "Unable to establish connection with the server. Run with log level set to debug for more information."
+ rescue StandardError => e
+ config.log! e
+ config.log! e.backtrace.join("\n")
+ end
+ end
+
+ # This code should be activated to check that no dependency loading is occuring in the background thread
+ # by stopping the foreground thread after the background thread is created. Turn on dependency loading logging
+ # and make sure that no loading occurs.
+ #
+ # config.log! "FINISHED AGENT INIT"
+ # while true
+ # sleep 1
+ # end
+
+ end
+ def start_reporting(force_enable=false)
+ @local_host = determine_host
+
+ setup_log
+
+ if @environment == :passenger
+ log.warn "Phusion Passenger has been detected. Some RPM memory statistics may have inaccuracies due to short process lifespans"
+ end
+
+ @started = true
+
+ @license_key = config.fetch('license_key', nil)
+
+ error_collector_config = config.fetch('error_collector', {})
+
+ @error_collector.enabled = error_collector_config.fetch('enabled', true)
+ @error_collector.capture_source = error_collector_config.fetch('capture_source', true)
+
+ log.info "Error collector is enabled in agent config" if @error_collector.enabled
+
+ ignore_errors = error_collector_config.fetch('ignore_errors', "")
+ ignore_errors = ignore_errors.split(",")
+ ignore_errors.each { |error| error.strip! }
+
+ @error_collector.ignore(ignore_errors)
+
+ @capture_params = config.fetch('capture_params', false)
+
+ sampler_config = config.fetch('transaction_tracer', {})
+
+ @use_transaction_sampler = sampler_config.fetch('enabled', false)
+ @record_sql = (sampler_config.fetch('record_sql', 'obfuscated') || 'off').intern
+ @slowest_transaction_threshold = sampler_config.fetch('transaction_threshold', '2.0').to_f
+ @explain_threshold = sampler_config.fetch('explain_threshold', '0.5').to_f
+ @explain_enabled = sampler_config.fetch('explain_enabled', true)
+ @stack_trace_threshold = sampler_config.fetch('stack_trace_threshold', '0.500').to_f
+
+ log.info "Transaction tracing is enabled in agent config" if @use_transaction_sampler
+ log.warn "Agent is configured to send raw SQL to RPM service" if @record_sql == :raw
+
+ @prod_mode_enabled = force_enable || config['enabled']
+
+ # Initialize transaction sampler
+ TransactionSampler.capture_params = @capture_params
+ @transaction_sampler.stack_trace_threshold = @stack_trace_threshold
+ @error_collector.capture_params = @capture_params
+
+
+ # make sure the license key exists and is likely to be really a license key
+ # by checking it's string length (license keys are 40 character strings.)
+ if @prod_mode_enabled && (!@license_key || @license_key.length != 40)
+ config.log! "No license key found. Please edit your newrelic.yml file and insert your license key"
+ return
+ end
+
+ instrument_app
+
+ if @prod_mode_enabled
+ load_samplers
+ launch_worker_thread
+ # When the VM shuts down, attempt to send a message to the server that
+ # this agent run is stopping, assuming it has successfully connected
+ at_exit { shutdown }
+ end
+
+ log.debug "Finished starting reporting"
+ end
+ def initialize
+ @connected = false
+ @launch_time = Time.now
+
+ @metric_ids = {}
+ @environment = :unknown
+
+ @config = NewRelic::Config.instance
+
+ @stats_engine = NewRelic::Agent::StatsEngine.new
+ @transaction_sampler = NewRelic::Agent::TransactionSampler.new(self)
+ @error_collector = NewRelic::Agent::ErrorCollector.new(self)
+
+ @request_timeout = NewRelic::Config.instance.fetch('timeout', 2 * 60)
+
+ @invalid_license = false
+
+ @last_harvest_time = Time.now
+ end
+
+ def setup_log
+ @log = config.setup_log(identifier)
+ log.info "Runtime environment: #{@environment.to_s}"
+ end
+
+ # Connect to the server and validate the license.
+ # If successful, @connected has true when finished.
+ # If not successful, you can keep calling this.
+ # Return false if we could not establish a connection with the
+ # server and we should not retry, such as if there's
+ # a bad license key.
+ def connect
+ # wait a few seconds for the web server to boot, necessary in development
+ connect_retry_period = 5
+ connect_attempts = 0
+
+ begin
+ sleep connect_retry_period.to_i
+ @agent_id = invoke_remote :launch,
+ @local_host,
+ @identifier,
+ determine_home_directory,
+ $$,
+ @launch_time.to_f,
+ NewRelic::VERSION::STRING,
+ config.app_config_info,
+ config['app_name'],
+ config.settings
+ @report_period = invoke_remote :get_data_report_period, @agent_id
+
+ config.log! "Connected to NewRelic Service at #{config.server}"
+ log.debug "Agent ID = #{@agent_id}."
+
+ # Ask the server for permission to send transaction samples. determined by subscription license.
+ @should_send_samples = invoke_remote :should_collect_samples, @agent_id
+
+ # Ask for mermission to collect error data
+ @should_send_errors = invoke_remote :should_collect_errors, @agent_id
+
+ log.info "Transaction traces will be sent to the RPM service" if @use_transaction_sampler && @should_send_samples
+ log.info "Errors will be sent to the RPM service" if @error_collector.enabled && @should_send_errors
+
+ @connected = true
+
+ rescue LicenseException => e
+ config.log! e.message, :error
+ config.log! "Visit NewRelic.com to obtain a valid license key, or to upgrade your account."
+ @invalid_license = true
+ return false
+
+ rescue Timeout::Error, StandardError => e
+ log.info "Unable to establish connection with New Relic RPM Service at #{config.server}"
+ unless e.instance_of? IgnoreSilentlyException
+ log.error e.message
+ log.debug e.backtrace.join("\n")
+ end
+ # retry logic
+ connect_attempts += 1
+ case connect_attempts
+ when 1..5
+ connect_retry_period, period_msg = 5, nil
+ when 6..10 then
+ connect_retry_period, period_msg = 30, nil
+ when 11..20 then
+ connect_retry_period, period_msg = 1.minutes, "1 minute"
+ else
+ connect_retry_period, period_msg = 10.minutes, "10 minutes"
+ end
+ log.info "Will re-attempt in #{period_msg}" if period_msg
+ retry
+ end
+ end
+
+ def load_samplers
+ sampler_files = File.join(File.dirname(__FILE__), 'samplers', '*.rb')
+ Dir.glob(sampler_files) do |file|
+ begin
+ require file
+ rescue => e
+ log.error "Error loading sampler '#{file}': #{e}"
+ end
+ end
+ end
+
+ def determine_host
+ Socket.gethostname
+ end
+
+ def determine_home_directory
+ config.root
+ end
+
+ def harvest_and_send_timeslice_data
+
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.harvest_busy
+
+ now = Time.now
+
+ # Fixme: remove the harvest thread tracking
+ @harvest_thread ||= Thread.current
+
+ if @harvest_thread != Thread.current
+ config.log! "ERROR - two harvest threads are running (current=#{Thread.current}, havest=#{@harvest_thread}"
+ @harvest_thread = Thread.current
+ end
+
+ # Fixme: remove this check
+ config.log! "Agent sending data too frequently - #{now - @last_harvest_time} seconds" if (now.to_f - @last_harvest_time.to_f) < 45
+
+ @unsent_timeslice_data ||= {}
+ @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
+
+ begin
+ metric_ids = invoke_remote(:metric_data, @agent_id,
+ @last_harvest_time.to_f,
+ now.to_f,
+ @unsent_timeslice_data.values)
+
+ rescue Timeout::Error
+ # assume that the data was received. chances are that it was
+ metric_ids = nil
+ end
+
+
+ @metric_ids.merge! metric_ids if metric_ids
+
+ log.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@agent_id}) in #{Time.now - now} seconds"
+
+ # if we successfully invoked this web service, then clear the unsent message cache.
+ @unsent_timeslice_data = {}
+ @last_harvest_time = now
+
+ # handle_messages
+
+ # note - exceptions are logged in invoke_remote. If an exception is encountered here,
+ # then the metric data is downsampled for another timeslices
+ end
+
+ def harvest_and_send_slowest_sample
+ @slowest_sample = @transaction_sampler.harvest_slowest_sample(@slowest_sample)
+
+ if @slowest_sample && @slowest_sample.duration > @slowest_transaction_threshold
+ now = Time.now
+ log.debug "Sending slowest sample: #{@slowest_sample.params[:path]}, #{@slowest_sample.duration.round_to(2)}s (explain=#{@explain_enabled})" if @slowest_sample
+
+ # take the slowest sample, and prepare it for sending across the wire. This includes
+ # gathering SQL explanations, stripping out stack traces, and normalizing SQL.
+ # note that we explain only the sql statements whose segments' execution times exceed
+ # our threshold (to avoid unnecessary overhead of running explains on fast queries.)
+ sample = @slowest_sample.prepare_to_send(:explain_sql => @explain_threshold, :record_sql => @record_sql, :keep_backtraces => true, :explain_enabled => @explain_enabled)
+
+ invoke_remote :transaction_sample_data, @agent_id, sample
+
+ log.debug "#{now}: sent slowest sample (#{@agent_id}) in #{Time.now - now} seconds"
+ end
+
+ # if we succeed sending this sample, then we don't need to keep the slowest sample
+ # around - it has been sent already and we can collect the next one
+ @slowest_sample = nil
+
+ # note - exceptions are logged in invoke_remote. If an exception is encountered here,
+ # then the slowest sample of is determined of the entire period since the last
+ # reported sample.
+ end
+
+ def harvest_and_send_errors
+ @unsent_errors = @error_collector.harvest_errors(@unsent_errors)
+ if @unsent_errors && @unsent_errors.length > 0
+ log.debug "Sending #{@unsent_errors.length} errors"
+
+ invoke_remote :error_data, @agent_id, @unsent_errors
+
+ # if the remote invocation fails, then we never clear @unsent_errors,
+ # and therefore we can re-attempt to send on the next heartbeat. Note
+ # the error collector maxes out at 20 instances to prevent leakage
+ @unsent_errors = []
+ end
+ end
+
+ # send a message via post
+ def invoke_remote(method, *args)
+ # we currently optimize for CPU here since we get roughly a 10x reduction in
+ # message size with this, and CPU overhead is at a premium. If we wanted
+ # to go for higher compression instead, we could use Zlib::BEST_COMPRESSION and
+ # pay a little more CPU.
+ post_data = Zlib::Deflate.deflate(Marshal.dump(args), Zlib::BEST_SPEED)
+ http = config.http_connection
+
+ # params = {:method => method, :license_key => license_key, :protocol_version => PROTOCOL_VERSION }
+ # uri = "/agent_listener/invoke_raw_method?#{params.to_query}"
+ uri = "/agent_listener/invoke_raw_method?method=#{method}&license_key=#{license_key}&protocol_version=#{PROTOCOL_VERSION}"
+ uri += "&run_id=#{@agent_id}" if @agent_id
+
+ request = Net::HTTP::Post.new(uri, 'ACCEPT-ENCODING' => 'gzip')
+ request.content_type = "application/octet-stream"
+ request.body = post_data
+
+ log.debug "#{uri}"
+
+ response = nil
+
+ begin
+ timeout(@request_timeout) do
+ response = http.request(request)
+ end
+ rescue Timeout::Error
+ log.warn "Timed out trying to post data to RPM (timeout = #{@request_timeout} seconds)"
+ raise IgnoreSilentlyException
+ end
+
+ if response.is_a? Net::HTTPSuccess
+ body = nil
+ if response['content-encoding'] == 'gzip'
+ log.debug "Decompressing return value"
+ i = Zlib::GzipReader.new(StringIO.new(response.body))
+ body = i.read
+ else
+ log.debug "Uncompressed content returned"
+ body = response.body
+ end
+ return_value = Marshal.load(body)
+ if return_value.is_a? Exception
+ raise return_value
+ else
+ return return_value
+ end
+ else
+ log.debug "Unexpected response from server: #{response.code}: #{response.message}"
+ raise IgnoreSilentlyException
+ end
+ rescue ForceDisconnectException => e
+ config.log! "RPM forced this agent to disconnect", :error
+ config.log! e.message, :error
+ config.log! "Restart this process to resume RPM's agent communication with NewRelic.com"
+ # when a disconnect is requested, stop the current thread, which is the worker thread that
+ # gathers data and talks to the server.
+ @connected = false
+ Thread.exit
+ rescue SystemCallError, SocketError => e
+ # These include Errno connection errors
+ log.debug "Recoverable error connecting to the server: #{e}"
+ raise IgnoreSilentlyException
+ end
+
+ def graceful_disconnect
+ if @connected && !(config.server.host == "localhost" && @identifier == '3000')
+ begin
+ log.debug "Sending graceful shutdown message to #{config.server}"
+
+ @request_timeout = 5
+
+ log.debug "Sending RPM service agent run shutdown message"
+ invoke_remote :shutdown, @agent_id, Time.now.to_f
+
+ log.debug "Graceful shutdown complete"
+
+ rescue Timeout::Error, StandardError => e
+ end
+ else
+ log.debug "Bypassing graceful shutdown - agent in development mode"
+ end
+ end
+ end
+
+end
View
13 vendor/plugins/newrelic_rpm/lib/new_relic/agent/chained_call.rb
@@ -0,0 +1,13 @@
+# This is used to allow obfuscators to be chained.
+
+class NewRelic::ChainedCall
+ def initialize(block1, block2)
+ @block1 = block1
+ @block2 = block2
+ end
+
+ def call(sql)
+ sql = @block1.call(sql)
+ @block2.call(sql)
+ end
+end
View
72 vendor/plugins/newrelic_rpm/lib/new_relic/agent/collection_helper.rb
@@ -0,0 +1,72 @@
+module NewRelic::Agent::CollectionHelper
+ # Transform parameter hash into a hash whose values are strictly
+ # strings
+ def normalize_params(params)
+ case params
+ when Symbol, FalseClass, TrueClass, nil
+ params
+ when Numeric
+ truncate(params.to_s)
+ when String
+ truncate(params)
+ when Hash
+ new_params = {}
+ params.each do | key, value |
+ new_params[truncate(normalize_params(key),32)] = normalize_params(value)
+ end
+ new_params
+ when Array
+ params.first(20).map{|item| normalize_params(item)}
+ else
+ truncate(flatten(params))
+ end
+ end
+
+ # Return an array of strings (backtrace), cleaned up for readability
+ # Return nil if there is no backtrace
+
+ def strip_nr_from_backtrace(backtrace)
+ if backtrace
+ # strip newrelic from the trace
+ backtrace = backtrace.reject {|line| line =~ /new_relic\/agent\// }
+ # rename methods back to their original state
+ backtrace = backtrace.collect {|line| line.gsub /_without_(newrelic|trace)/, ""}
+ end
+ backtrace
+ end
+
+ private
+
+ # Convert any kind of object to a descriptive string
+ # Only call this on unknown objects. Otherwise call to_s.
+ def flatten(object)
+ s =
+ if object.respond_to? :inspect
+ object.inspect
+ elsif object.respond_to? :to_s
+ object.to_s
+ elsif object.nil?
+ "nil"
+ else
+ "#<#{object.class.to_s}>"
+ end
+
+ if !(s.instance_of? String)
+ s = "#<#{object.class.to_s}>"
+ end
+
+ s
+ end
+
+ def truncate(string, len=256)
+ if string.instance_of? Symbol
+ string
+ elsif string.nil?
+ ""
+ elsif string.instance_of? String
+ string.to_s.gsub(/^(.{#{len}})(.*)/) {$2.blank? ? $1 : $1 + "..."}
+ else
+ truncate(flatten(string), len)
+ end
+ end
+end
View
105 vendor/plugins/newrelic_rpm/lib/new_relic/agent/error_collector.rb
@@ -0,0 +1,105 @@
+
+module NewRelic::Agent
+ class ErrorCollector
+ include Synchronize
+ include CollectionHelper
+
+ MAX_ERROR_QUEUE_LENGTH = 20 unless defined? MAX_ERROR_QUEUE_LENGTH
+
+ attr_accessor :capture_params
+ attr_accessor :capture_source
+ attr_accessor :enabled
+
+ def initialize(agent = nil)
+ @agent = agent
+ @errors = []
+ # lookup of exception class names to ignore. Hash for fast access
+ @ignore = {}
+ @ignore_filter = nil
+ @capture_params = true
+ @capture_source = false
+ @enabled = true
+ end
+
+ def ignore_error_filter(&block)
+ @ignore_filter = block
+ end
+
+
+ # errors is an array of Exception Class Names
+ #
+ def ignore(errors)
+ errors.each { |error| @ignore[error] = true; log.debug("Ignoring error: '#{error}'") }
+ end
+
+
+ def notice_error(path, request_uri, params, exception)
+
+ return unless @enabled
+ return if @ignore[exception.class.name]
+
+ if @ignore_filter
+ exception = @ignore_filter.call(exception)
+
+ return if exception.nil?
+ end
+
+ error_stat.increment_count
+
+ data = {}
+
+ data[:request_params] = normalize_params(params) if @capture_params
+ data[:custom_params] = normalize_params(@agent.custom_params) if @agent
+
+ data[:request_uri] = request_uri
+
+ data[:rails_root] = NewRelic::Config.instance.root
+
+ data[:file_name] = exception.file_name if exception.respond_to?('file_name')
+ data[:line_number] = exception.line_number if exception.respond_to?('line_number')
+
+ if @capture_source && exception.respond_to?('source_extract')
+ data[:source] = exception.source_extract
+ end
+
+ if exception.respond_to? 'original_exception'
+ inside_exception = exception.original_exception
+ else
+ inside_exception = exception
+ end
+ data[:stack_trace] = strip_nr_from_backtrace(inside_exception.backtrace)
+ noticed_error = NewRelic::NoticedError.new(path, data, exception)
+
+ synchronize do
+ if @errors.length >= MAX_ERROR_QUEUE_LENGTH
+ log.info("The error reporting queue has reached #{MAX_ERROR_QUEUE_LENGTH}. This error will not be reported to RPM: #{exception.message}")
+ else
+ @errors << noticed_error
+ end
+ end
+ end
+
+ # Get the errors currently queued up. Unsent errors are left
+ # over from a previous unsuccessful attempt to send them to the server.
+ # We first clear out all unsent errors before sending the newly queued errors.
+ def harvest_errors(unsent_errors)
+ if unsent_errors && !unsent_errors.empty?
+ return unsent_errors
+ else
+ synchronize do
+ errors = @errors
+ @errors = []
+ return errors
+ end
+ end
+ end
+
+ private
+ def error_stat
+ @error_stat ||= NewRelic::Agent.get_stats("Errors/all")
+ end
+ def log
+ NewRelic::Config.instance.log
+ end
+ end
+end
View
18 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/active_merchant.rb
@@ -0,0 +1,18 @@
+# ActiveMerchant Instrumentation.
+
+if defined? ActiveMerchant
+
+ ActiveMerchant::Billing::Gateway.implementations.each do |gateway|
+ gateway.class_eval do
+ implemented_methods = public_instance_methods(false)
+ gateway_name = self.name.split('::').last
+ [:authorize, :purchase, :credit, :void, :capture, :recurring].each do |operation|
+ if implemented_methods.include?(operation.to_s)
+ add_method_tracer operation, "ActiveMerchant/gateway/#{gateway_name}/#{operation}", :scoped_metric_only => true
+ add_method_tracer operation, "ActiveMerchant/gateway/#{gateway_name}", :push_scope => false
+ add_method_tracer operation, "ActiveMerchant/operation/#{operation}", :push_scope => false
+ end
+ end
+ end
+ end
+end
View
95 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb
@@ -0,0 +1,95 @@
+
+# NewRelic instrumentation for ActiveRecord
+if defined? ActiveRecord::Base
+
+ ActiveRecord::Base.class_eval do
+ class << self
+ [:find_by_sql, :count].each do |find_method|
+ add_method_tracer find_method, 'ActiveRecord/#{self.name}/find'
+ add_method_tracer find_method, 'ActiveRecord/find', :push_scope => false
+ add_method_tracer find_method, 'ActiveRecord/all', :push_scope => false
+ end
+ end
+ [:save, :save!].each do |save_method|
+ add_method_tracer save_method, 'ActiveRecord/#{self.class.name}/save'
+ add_method_tracer save_method, 'ActiveRecord/save', :push_scope => false
+ add_method_tracer save_method, 'ActiveRecord/all', :push_scope => false
+ end
+
+ add_method_tracer :destroy, 'ActiveRecord/#{self.class.name}/destroy'
+ add_method_tracer :destroy, 'ActiveRecord/destroy', :push_scope => false
+ add_method_tracer :destroy, 'ActiveRecord/all', :push_scope => false
+ end
+
+ # instrumentation to catch logged SQL statements in sampled transactions
+
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
+ @@my_sql_defined = defined? ActiveRecord::ConnectionAdapters::MysqlAdapter
+ @@postgres_defined = defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
+
+ def log_with_newrelic_instrumentation(sql, name, &block)
+ # if we aren't in a blamed context, then add one so that we can see that
+ # controllers are calling SQL directly
+ # we check scope_depth vs 2 since the controller is 1, and the
+ #
+ if NewRelic::Agent.instance.transaction_sampler.scope_depth < 2
+ self.class.trace_method_execution_with_scope "Database/DirectSQL", true, true do
+ log_with_capture_sql(sql, name, &block)
+ end
+ else
+ log_with_capture_sql(sql, name, &block)
+ end
+ end
+
+ def log_with_capture_sql(sql, name, &block)
+ if @@my_sql_defined && self.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
+ config = @config
+ elsif @@postgres_defined && self.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
+ config = @config
+ else
+ config = nil
+ end
+
+ t0 = Time.now
+ result = log_without_newrelic_instrumentation(sql, name, &block)
+
+ NewRelic::Agent.instance.transaction_sampler.notice_sql(sql, config, Time.now - t0)
+
+ result
+ end
+
+ # Compare with #alias_method_chain, which is not available in
+ # Rails 1.1:
+ alias_method :log_without_newrelic_instrumentation, :log
+ alias_method :log, :log_with_newrelic_instrumentation
+ protected :log
+
+ add_method_tracer :log, 'Database/#{adapter_name}/#{args[1]}', :metric => false
+ add_method_tracer :log, 'Database/all', :push_scope => false
+
+ end
+ ActiveRecord::Associations::ClassMethods.class_eval do
+ add_method_tracer :find_with_associations, 'ActiveRecord/#{self.name}/find'
+ add_method_tracer :find_with_associations, 'ActiveRecord/find', :push_scope => false
+ add_method_tracer :find_with_associations, 'ActiveRecord/all', :push_scope => false
+ end
+
+ # instrumentation for associations
+ ActiveRecord::Associations::AssociationCollection.class_eval do
+ add_method_tracer :delete, 'ActiveRecord/#{@owner.class.name}/association delete'
+ end
+=begin
+ # Consider enabling these in the future
+ class HasAndBelongsToManyAssociation
+ add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
+ add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
+ add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
+ end
+
+ class HasManyAssociation
+ add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
+ add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
+ add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
+ end
+=end
+end
View
151 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/controller_instrumentation.rb
@@ -0,0 +1,151 @@
+# NewRelic instrumentation for controllers
+#
+# This instrumentation is applied to the action controller by default if the agent
+# is actively collecting statistics. It will collect statistics for the
+# given action.
+#
+# In cases where you don't want to instrument the top level action, but instead
+# have other methods which are dispatched to by your action, and you want to treat
+# these as distinct actions, then what you need to do is call newrelic_ignore
+# on the top level action, and manually instrument the called 'actions'.
+#
+# Here's an example of a controller with a send_message action which dispatches
+# to more specific send_servicename methods. This results in the controller
+# action stats showing up for send_servicename.
+#
+# MyController < ActionController::Base
+# newrelic_ignore :only => 'send_message'
+# # dispatch this action to the method given by the service parameter.
+# def send_message
+# service = params['service']
+# dispatch_to_method = "send_messge_to_#{service}"
+# perform_action_with_newrelic_trace(dispatch_to_method) do
+# send dispatch_to_method, params['message']
+# end
+# end
+# end
+
+module NewRelic::Agent::Instrumentation
+ module ControllerInstrumentation
+
+ @@newrelic_apdex_t = NewRelic::Agent.instance.apdex_t
+ @@newrelic_apdex_overall = NewRelic::Agent.instance.stats_engine.get_stats_no_scope("Apdex")
+ def self.included(clazz)
+ clazz.extend(ClassMethods)
+ end
+
+ module ClassMethods
+ # Have NewRelic ignore actions in this controller. Specify the actions as hash options
+ # using :except and :only. If no actions are specified, all actions are ignored.
+ def newrelic_ignore(specifiers={})
+ if specifiers.empty?
+ self.newrelic_ignore_attr = true
+ elsif ! (Hash === specifiers)
+ logger.error "newrelic_ignore takes an optional hash with :only and :except lists of actions (illegal argument type '#{specifiers.class}')"
+ else
+ self.newrelic_ignore_attr = specifiers
+ end
+ end
+ # Should be implemented in the controller class via the inheritable attribute mechanism.
+ def newrelic_ignore_attr=(value); end
+ def newrelic_ignore_attr; end
+ end
+
+ # Must be implemented in the controller class:
+ # Determine the path that is used in the metric name for
+ # the called controller action. Of the form controller_path/action_name
+ #
+ def newrelic_metric_path(action_name_override = nil)
+ raise "Not implemented!"
+ end
+
+ @@newrelic_apdex_t = NewRelic::Agent.instance.apdex_t
+ # Perform the current action with NewRelic tracing. Used in a method
+ # chain via aliasing. Call directly if you want to instrument a specifc
+ # block as if it were an action. Pass the block along with the path.
+ # The metric is named according to the action name, or the given path if called
+ # directly.
+ def perform_action_with_newrelic_trace(*args)
+ agent = NewRelic::Agent.instance
+ stats_engine = agent.stats_engine
+
+ ignore_actions = self.class.newrelic_ignore_attr
+ # Skip instrumentation based on the value of 'do_not_trace' and if
+ # we aren't calling directly with a block.
+ should_skip = !block_given? && case ignore_actions
+ when nil; false
+ when Hash
+ only_actions = Array(ignore_actions[:only])
+ except_actions = Array(ignore_actions[:except])
+ only_actions.include?(action_name.to_sym) || (except_actions.any? && !except_actions.include?(action_name.to_sym))
+ else
+ true
+ end
+ if should_skip
+ begin
+ return perform_action_without_newrelic_trace(*args)
+ ensure
+ # Tell the dispatcher instrumentation that we ignored this action and it shouldn't
+ # be counted for the overall HTTP operations measurement. The if.. appears here
+ # because we might be ignoring the top level action instrumenting but instrumenting
+ # a direct invocation that already happened, so we need to make sure if this var
+ # has already been set to false we don't reset it.
+ Thread.current[:controller_ignored] = true if Thread.current[:controller_ignored].nil?
+ end
+ end
+
+ Thread.current[:controller_ignored] = false
+
+ start = Time.now.to_f
+ agent.ensure_worker_thread_started
+
+ # generate metrics for all all controllers (no scope)
+ self.class.trace_method_execution_no_scope "Controller" do
+ # generate metrics for this specific action
+ # assuming the first argument, if present, is the action name
+ path = newrelic_metric_path(args.size > 0 ? args[0] : nil)
+ stats_engine.transaction_name ||= "Controller/#{path}" if stats_engine
+
+ self.class.trace_method_execution_with_scope "Controller/#{path}", true, true do
+ # send request and parameter info to the transaction sampler
+
+ local_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
+
+ agent.transaction_sampler.notice_transaction(path, request, local_params)
+
+ t = Process.times.utime + Process.times.stime
+
+ begin
+ # run the action
+ if block_given?
+ yield
+ else
+ perform_action_without_newrelic_trace(*args)
+ end
+ ensure
+ cpu_burn = (Process.times.utime + Process.times.stime) - t
+ agent.transaction_sampler.notice_transaction_cpu_time(cpu_burn)
+
+ duration = Time.now.to_f - start
+ # do the apdex bucketing
+ if duration <= @@newrelic_apdex_t
+ @@newrelic_apdex_overall.record_apdex_s cpu_burn # satisfied
+ stats_engine.get_stats_no_scope("Apdex/#{path}").record_apdex_s cpu_burn
+ elsif duration <= (4 * @@newrelic_apdex_t)
+ @@newrelic_apdex_overall.record_apdex_t cpu_burn # tolerating
+ stats_engine.get_stats_no_scope("Apdex/#{path}").record_apdex_t cpu_burn
+ else
+ @@newrelic_apdex_overall.record_apdex_f cpu_burn # frustrated
+ stats_engine.get_stats_no_scope("Apdex/#{path}").record_apdex_f cpu_burn
+ end
+
+ end
+ end
+ end
+
+ ensure
+ # clear out the name of the traced transaction under all circumstances
+ stats_engine.transaction_name = nil
+ end
+ end
+end
View
90 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/data_mapper.rb
@@ -0,0 +1,90 @@
+
+# NewRelic instrumentation for DataMapper
+# For now, we have to refer to all db metrics as "ActiveRecord"
+if defined? DataMapper
+
+ DataMapper::Model.class_eval do
+ add_method_tracer :get, 'ActiveRecord/#{self.name}/find'
+ add_method_tracer :first, 'ActiveRecord/#{self.name}/find'
+ add_method_tracer :first_or_create, 'ActiveRecord/#{self.name}/find'
+ add_method_tracer :all, 'ActiveRecord/#{self.name}/find_all'
+ end
+ DataMapper::Adapters::DataObjectsAdapter.class_eval do
+
+ @@my_sql_defined = defined? ActiveRecord::ConnectionAdapters::MysqlAdapter
+ @@postgres_defined = defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
+
+ for method in [:read_many, :read_one] do
+ add_method_tracer method, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/find' # Need to get the model somehow
+ add_method_tracer method, 'ActiveRecord/find', :push_scope => false
+ add_method_tracer method, 'ActiveRecord/all', :push_scope => false
+ end
+ for method in [:execute, :query] do
+ add_method_tracer method, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/execute' # Need to get the model somehow
+ add_method_tracer method, 'ActiveRecord/all', :push_scope => false
+ end
+ for method in [:create, :update]do
+ add_method_tracer method, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/save'
+ add_method_tracer method, 'ActiveRecord/save', :push_scope => false
+ end
+ add_method_tracer :delete, 'ActiveRecord/#{self.class.name[/[^:]*$/]}/destroy'
+ add_method_tracer :delete, 'ActiveRecord/destroy', :push_scope => false
+
+ def log_with_newrelic_instrumentation(sql, name, &block)
+ # if we aren't in a blamed context, then add one so that we can see that
+ # controllers are calling SQL directly
+ # we check scope_depth vs 2 since the controller is 1, and the
+ #
+ if NewRelic::Agent.instance.transaction_sampler.scope_depth < 2
+ self.class.trace_method_execution "Database/DirectSQL", true, true do
+ log_with_capture_sql(sql, name, &block)
+ end
+ else
+ log_with_capture_sql(sql, name, &block)
+ end
+ end
+
+ def log_with_capture_sql(sql, name, &block)
+ if @@my_sql_defined && self.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
+ config = @config
+ elsif @@postgres_defined && self.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
+ config = @config
+ else
+ config = nil
+ end
+
+ t0 = Time.now
+ result = log_without_newrelic_instrumentation(sql, name, &block)
+
+ NewRelic::Agent.instance.transaction_sampler.notice_sql(sql, config, Time.now - t0)
+
+ result
+ end
+
+ # Compare with #alias_method_chain, which is not available in
+ # Rails 1.1:
+ #alias_method :log_without_newrelic_instrumentation, :log
+ #alias_method :log, :log_with_newrelic_instrumentation
+
+=begin
+ # instrumentation for associations
+ module Associations
+ class AssociationCollection
+ add_method_tracer :delete, 'ActiveRecord/#{@owner.class.name}/association delete'
+ end
+
+ def HasAndBelongsToManyAssociation
+ add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
+ add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
+ add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
+ end
+
+ class HasManyAssociation
+ # add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
+ # add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
+ # add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
+ end
+ end
+=end
+ end
+end
View
105 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb
@@ -0,0 +1,105 @@
+# We have to patch the mongrel dispatcher live since the classes
+# aren't defined when our instrumentation loads
+module NewRelic::Agent::Instrumentation
+ module DispatcherInstrumentation
+
+ @@newrelic_agent = NewRelic::Agent.agent
+ @@newrelic_rails_dispatch_stat = @@newrelic_agent.stats_engine.get_stats 'Rails/HTTP Dispatch'
+ @@newrelic_mongrel_queue_stat = @@newrelic_agent.stats_engine.get_stats('WebFrontend/Mongrel/Average Queue Time')
+
+ def newrelic_dispatcher_start
+ # Put the current time on the thread. Can't put in @ivar because this could
+ # be a class or instance context
+ t0 = Time.now.to_f
+ NewRelic::Config.instance.log.warn "Recursive entry into dispatcher_start!\n#{caller.join("\n ")}" if Thread.current[:newrelic_t0]
+ Thread.current[:newrelic_t0] = t0
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_start t0
+ # capture the time spent in the mongrel queue, if running in mongrel. This is the
+ # current time less the timestamp placed in 'started_on' by mongrel.
+ mongrel_start = Thread.current[:started_on]
+ @@newrelic_mongrel_queue_stat.trace_call(t0 - mongrel_start.to_f) if mongrel_start
+ @@newrelic_agent.start_transaction
+
+ # Reset the flag indicating the controller action should be ignored.
+ # It may be set by the action to either true or false or left nil meaning false
+ Thread.current[:controller_ignored] = nil
+ end
+
+ def newrelic_dispatcher_finish
+ t0 = Thread.current[:newrelic_t0]
+ if t0.nil?
+ NewRelic::Config.instance.log.warn "Dispatcher finish called twice!\n#{caller.join("\n ")}"
+ return
+ end
+ t1 = Time.now.to_f
+ @@newrelic_agent.end_transaction
+ @@newrelic_rails_dispatch_stat.trace_call(t1 - t0) unless Thread.current[:controller_ignored]
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_finish t1
+ Thread.current[:newrelic_t0] = nil
+ end
+
+ def dispatch_newrelic(*args)
+ newrelic_dispatcher_start
+ begin
+ dispatch_without_newrelic(*args)
+ ensure
+ newrelic_dispatcher_finish
+ end
+ end
+
+ # This won't work with Rails 2.2 multi-threading
+ module BusyCalculator
+ extend self
+ # the fraction of the sample period that the dispatcher was busy
+ @instance_busy = NewRelic::Agent.agent.stats_engine.get_stats('Instance/Busy')
+ @harvest_start = Time.now.to_f
+ @accumulator = 0
+ @dispatcher_start = nil
+ def dispatcher_start(time)
+ Thread.critical = true
+ @dispatcher_start = time
+ Thread.critical = false
+ end
+
+ def dispatcher_finish(time)
+ Thread.critical = true
+ @accumulator += (time - @dispatcher_start)
+ @dispatcher_start = nil
+
+ Thread.critical = false
+ end
+
+ def is_busy?
+ @dispatcher_start
+ end
+
+ def harvest_busy
+ Thread.critical = true
+
+ busy = @accumulator
+ @accumulator = 0
+
+ t0 = Time.now.to_f
+
+ if @dispatcher_start
+ busy += (t0 - @dispatcher_start)
+ @dispatcher_start = t0
+ end
+
+
+ Thread.critical = false
+
+ busy = 0.0 if busy < 0.0 # don't go below 0%
+
+ time_window = (t0 - @harvest_start)
+ time_window = 1.0 if time_window == 0.0 # protect against divide by zero
+
+ busy = busy / time_window
+
+ busy = 1.0 if busy > 1.0 # cap at 100%
+ @instance_busy.record_data_point busy
+ @harvest_start = t0
+ end
+ end
+ end
+end
View
18 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/memcache.rb
@@ -0,0 +1,18 @@
+# NOTE there are multiple implementations of the MemCache client in Ruby,
+# each with slightly different API's and semantics.
+# Currently we only cover memcache-client. Need to cover Ruby-MemCache.
+# See:
+# http://www.deveiate.org/code/Ruby-MemCache/ (Gem: Ruby-MemCache)
+# http://dev.robotcoop.com/Libraries/memcache-client/ (Gem: memcache-client)
+MemCache.class_eval do
+ add_method_tracer :get, 'MemCache/read' if self.method_defined? :get
+ add_method_tracer :set, 'MemCache/write' if self.method_defined? :set
+ add_method_tracer :get_multi, 'MemCache/read' if self.method_defined? :get_multi
+end if defined? MemCache
+
+# Support for libmemcached through Evan Weaver's memcached wrapper
+# http://blog.evanweaver.com/files/doc/fauna/memcached/classes/Memcached.html
+Memcached.class_eval do
+ add_method_tracer :get, 'Memcached/read' if self.method_defined? :get
+ add_method_tracer :set, 'Memcached/write' if self.method_defined? :set
+end if defined? Memcached
View
17 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/merb/controller.rb
@@ -0,0 +1,17 @@
+require 'set'
+require 'merb-core/controller/merb_controller'
+
+Merb::Controller.class_eval do
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
+
+ class_inheritable_accessor :newrelic_ignore_attr
+
+ protected
+ # determine the path that is used in the metric name for
+ # the called controller action
+ def newrelic_metric_path(action)
+ "#{controller_name}/#{action}"
+ end
+ alias_method :perform_action_without_newrelic_trace, :_dispatch
+ alias_method :_dispatch, :perform_action_with_newrelic_trace
+end
View
15 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/merb/dispatcher.rb
@@ -0,0 +1,15 @@
+require 'merb-core/dispatch/dispatcher'
+# NewRelic RPM instrumentation for http request dispatching (Routes mapping)
+# Note, the dispatcher class from no module into into the ActionController modile
+# in rails 2.0. Thus we need to check for both
+
+Merb::Request.class_eval do
+
+ # This is for merb prior to 1.0
+ include NewRelic::Agent::Instrumentation::DispatcherInstrumentation
+ alias_method :dispatch_without_newrelic, :handle
+ alias_method :handle, :dispatch_newrelic
+
+ # After merb 1.0, you can use before and after callbacks
+ # for this?
+end
View
6 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/merb/errors.rb
@@ -0,0 +1,6 @@
+# Hook in the notification to merb
+error_notifier = Proc.new {
+ NewRelic::Agent.agent.error_collector.notice_error("#{request.controller.name}/#{params[:action]}", request.path, params, request.exceptions.first)
+}
+Merb::Dispatcher::DefaultException.before error_notifier
+Exceptions.before error_notifier
View
35 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/action_controller.rb
@@ -0,0 +1,35 @@
+
+if defined? ActionController
+
+ ActionController::Base.class_eval do
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
+
+ # Compare with #alias_method_chain, which is not available in
+ # Rails 1.1:
+ alias_method :perform_action_without_newrelic_trace, :perform_action
+ alias_method :perform_action, :perform_action_with_newrelic_trace
+ private :perform_action
+
+ add_method_tracer :render, 'View/#{newrelic_metric_path}/Rendering'
+
+ def self.newrelic_ignore_attr=(value)
+ write_inheritable_attribute('do_not_trace', value)
+ end
+ def self.newrelic_ignore_attr
+ read_inheritable_attribute('do_not_trace')
+ end
+
+ # determine the path that is used in the metric name for
+ # the called controller action
+ def newrelic_metric_path(action_name_override = nil)
+ action_part = action_name_override || action_name
+ if action_name_override || self.class.action_methods.include?(action_part)
+ "#{self.class.controller_path}/#{action_part}"
+ else
+ "#{self.class.controller_path}/(other)"
+ end
+ end
+ end
+else
+ NewRelic::Agent.instance.log.debug "WARNING: ActionController instrumentation not added"
+end
View
27 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/action_web_service.rb
@@ -0,0 +1,27 @@
+# NewRelic Agent instrumentation for WebServices
+
+# Note Action Web Service is removed from default package in rails 2.0
+if defined? ActionWebService
+
+ # instrumentation for Web Service martialing - XML RPC
+ ActionWebService::Protocol::XmlRpc::XmlRpcProtocol.class_eval do
+ add_method_tracer :decode_request, "WebService/Xml Rpc/XML Decode"
+ add_method_tracer :encode_request, "WebService/Xml Rpc/XML Encode"
+ add_method_tracer :decode_response, "WebService/Xml Rpc/XML Decode"
+ add_method_tracer :encode_response, "WebService/Xml Rpc/XML Encode"
+ end
+
+ # instrumentation for Web Service martialing - Soap
+ ActionWebService::Protocol::Soap::SoapProtocol.class_eval do
+ add_method_tracer :decode_request, "WebService/Soap/XML Decode"
+ add_method_tracer :encode_request, "WebService/Soap/XML Encode"
+ add_method_tracer :decode_response, "WebService/Soap/XML Decode"
+ add_method_tracer :encode_response, "WebService/Soap/XML Encode"
+ end
+
+ ActionController::Base.class_eval do
+ if method_defined? :perform_invocation
+ add_method_tracer :perform_invocation, 'WebService/#{controller_name}/#{args.first}'
+ end
+ end if defined? ActionController::Base
+end
View
30 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/dispatcher.rb
@@ -0,0 +1,30 @@
+require 'dispatcher'
+
+# NewRelic RPM instrumentation for http request dispatching (Routes mapping)
+# Note, the dispatcher class from no module into into the ActionController module
+# in Rails 2.0. Thus we need to check for both
+if defined? ActionController::Dispatcher
+ target = ActionController::Dispatcher
+elsif defined? Dispatcher
+ target = Dispatcher
+else
+ target = nil
+end
+
+if target
+ NewRelic::Agent.instance.log.debug "Adding #{target} instrumentation"
+
+ # in Rails 2.3 (Rack-based) we don't want to add instrumentation on class level
+ unless defined? ::Rails::Rack
+ target = target.class_eval { class << self; self; end }
+ end
+
+ target.class_eval do
+ include NewRelic::Agent::Instrumentation::DispatcherInstrumentation
+
+ alias_method :dispatch_without_newrelic, :dispatch
+ alias_method :dispatch, :dispatch_newrelic
+ end
+else
+ NewRelic::Agent.instance.log.debug "WARNING: Dispatcher instrumentation not added"
+end
View
23 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/errors.rb
@@ -0,0 +1,23 @@
+# todo: patch rescue_action and track how many are occuring and capture instances as well
+ActionController::Base.class_eval do
+
+ def newrelic_notice_error(exception)
+ local_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
+
+ NewRelic::Agent.agent.error_collector.notice_error(newrelic_metric_path, (request) ? request.path : nil,
+ local_params, exception)
+ end
+
+ def rescue_action_with_newrelic_trace(exception)
+ newrelic_notice_error exception
+
+ rescue_action_without_newrelic_trace exception
+ end
+
+ # Compare with #alias_method_chain, which is not available in
+ # Rails 1.1:
+ alias_method :rescue_action_without_newrelic_trace, :rescue_action
+ alias_method :rescue_action, :rescue_action_with_newrelic_trace
+ protected :rescue_action
+
+end if defined? ActionController
View
6 vendor/plugins/newrelic_rpm/lib/new_relic/agent/instrumentation/rails/rails.rb
@@ -0,0 +1,6 @@
+# Instrument the compilation of ERB files.
+ERB::Compiler.class_eval do
+ add_method_tracer :compile, 'View/.rhtml Processing'
+end
+
+
View
171 vendor/plugins/newrelic_rpm/lib/new_relic/agent/method_tracer.rb
@@ -0,0 +1,171 @@
+
+class Module
+
+ # Original method preserved for API backward compatibility
+ def trace_method_execution (metric_name, push_scope, produce_metric, deduct_call_time_from_parent, &block)
+ if push_scope
+ trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent, &block)
+ else
+ trace_method_execution_no_scope(metric_name, &block)
+ end
+ end
+
+ # This is duplicated inline in add_method_tracer
+ def trace_method_execution_no_scope(metric_name)
+ t0 = Time.now.to_f
+ stats = @@newrelic_stats_engine.get_stats_no_scope metric_name
+
+ result = yield
+ duration = Time.now.to_f - t0 # for some reason this is 3 usec faster than Time - Time
+ stats.trace_call(duration, duration)
+ result
+ end
+
+ def trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent)
+
+ t0 = Time.now.to_f
+ stats = nil
+
+ begin
+ # Keep a reference to the scope we are pushing so we can do a sanity check making
+ # sure when we pop we get the one we 'expected'
+ expected_scope = @@newrelic_stats_engine.push_scope(metric_name, t0, deduct_call_time_from_parent)
+
+ stats = @@newrelic_stats_engine.get_stats metric_name, true if produce_metric
+ rescue => e
+ NewRelic::Config.instance.log.error("Caught exception in trace_method_execution header. Metric name = #{metric_name}, exception = #{e}")
+ NewRelic::Config.instance.log.error(e.backtrace.join("\n"))
+ end
+
+ begin
+ yield
+ ensure
+ t1 = Time.now.to_f
+ duration = t1 - t0
+
+ begin
+ if expected_scope
+ scope = @@newrelic_stats_engine.pop_scope expected_scope, duration, t1
+
+ exclusive = duration - scope.children_time
+ stats.trace_call(duration, exclusive) if stats
+ end
+ rescue => e
+ NewRelic::Config.instance.log.error("Caught exception in trace_method_execution footer. Metric name = #{metric_name}, exception = #{e}")
+ NewRelic::Config.instance.log.error(e.backtrace.join("\n"))
+ end
+ end
+ end
+
+ # Add a method tracer to the specified method.
+ # metric_name_code is ruby code that determines the name of the
+ # metric to be collected during tracing. As such, the code
+ # should be provided in 'single quote' strings rather than
+ # "double quote" strings, so that #{} evaluation happens
+ # at traced method execution time.
+ # Example: tracing a method :foo, where the metric name is
+ # the first argument converted to a string
+ # add_method_tracer :foo, '#{args.first.to_s}'
+ # statically defined metric names can be specified as regular strings
+ # push_scope specifies whether this method tracer should push
+ # the metric name onto the scope stack.
+ def add_method_tracer (method_name, metric_name_code, options = {})
+ return unless NewRelic::Agent.agent.config.tracers_enabled?
+
+ @@newrelic_stats_engine ||= NewRelic::Agent.agent.stats_engine
+
+ if !options.is_a?(Hash)
+ options = {:push_scope => options}
+ end
+ # options[:push_scope] true if we are noting the scope of this for
+ # stats collection as well as the transaction tracing
+ options[:push_scope] = true if options[:push_scope].nil?
+ # options[:metric] true if you are tracking stats for a metric, otherwise
+ # it's just for transaction tracing.
+ options[:metric] = true if options[:metric].nil?
+ options[:deduct_call_time_from_parent] = false if options[:deduct_call_time_from_parent].nil? && !options[:metric]
+ options[:deduct_call_time_from_parent] = true if options[:deduct_call_time_from_parent].nil?
+ options[:code_header] ||= ""
+ options[:code_footer] ||= ""
+
+ klass = (self === Module) ? "self" : "self.class"
+
+ unless method_defined?(method_name) || private_method_defined?(method_name)
+ NewRelic::Config.instance.log.warn("Did not trace #{self}##{method_name} because that method does not exist")
+ return
+ end
+
+ traced_method_name = _traced_method_name(method_name, metric_name_code)
+ if method_defined? traced_method_name
+ NewRelic::Config.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}")
+ return
+ end
+
+ fail "Can't add a tracer where push_scope is false and metric is false" if options[:push_scope] == false && !options[:metric]
+
+ if options[:push_scope] == false
+ class_eval "@@newrelic_stats_engine = NewRelic::Agent.agent.stats_engine"
+ code = <<-CODE
+ def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
+ #{options[:code_header]}
+
+ t0 = Time.now.to_f
+ stats = @@newrelic_stats_engine.get_stats_no_scope "#{metric_name_code}"
+
+ result = #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
+ duration = Time.now.to_f - t0
+ stats.trace_call(duration, duration) # for some reason this is 3 usec faster than Time - Time
+ #{options[:code_footer]}
+ result
+ end
+ CODE
+ else
+ code = <<-CODE
+ def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
+ #{options[:code_header]}
+ result = #{klass}.trace_method_execution_with_scope("#{metric_name_code}", #{options[:metric]}, #{options[:deduct_call_time_from_parent]}) do
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
+ end
+ #{options[:code_footer]}
+ result
+ end
+ CODE
+ end
+
+ class_eval code, __FILE__, __LINE__
+
+ alias_method _untraced_method_name(method_name, metric_name_code), method_name
+ alias_method method_name, _traced_method_name(method_name, metric_name_code)
+
+ NewRelic::Config.instance.log.debug("Traced method: class = #{self}, method = #{method_name}, "+
+ "metric = '#{metric_name_code}', options: #{options}, ")
+ end
+
+ # Not recommended for production use, because tracers must be removed in reverse-order
+ # from when they were added, or else other tracers that were added to the same method
+ # may get removed as well.
+ def remove_method_tracer(method_name, metric_name_code)
+ return unless NewRelic::Agent.agent.config.tracers_enabled?
+
+ if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
+ alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
+ undef_method "#{_traced_method_name(method_name, metric_name_code)}"
+ else
+ raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
+ end
+ end
+
+private
+
+ def _untraced_method_name(method_name, metric_name)
+ "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
+ end
+
+ def _traced_method_name(method_name, metric_name)
+ "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
+ end
+
+ def _sanitize_name(name)
+ name.to_s.tr('^a-z,A-Z,0-9', '_')
+ end
+end
View
105 vendor/plugins/newrelic_rpm/lib/new_relic/agent/patch_const_missing.rb
@@ -0,0 +1,105 @@
+# This class is for debugging purposes only.
+# It inserts instrumentation into class loading to verify
+# that no classes are being loaded on the new relic thread,
+# which can cause problems in the class loader code.
+
+module ClassLoadingWatcher
+
+ extend self
+ @@background_thread = nil
+
+ def background_thread
+ @@background_thread
+ end
+
+ def set_background_thread(thread)
+ @@background_thread = thread
+
+ # these tests that check is working...
+ # begin
+ # bad = Bad
+ # rescue
+ # end
+
+ # require 'new_relic/agent/patch_const_missing'
+ # load 'new_relic/agent/patch_const_missing.rb'
+ end
+ module SanityCheck
+ def new_relic_check_for_badness(*args)
+
+ if Thread.current == ClassLoadingWatcher.background_thread
+ msg = "Agent background thread shouldn't be loading classes (#{args.inspect})\n"
+
+ exception = NewRelic::Agent::BackgroundLoadingError.new(msg.clone)
+ exception.set_backtrace(caller)
+