Skip to content

Instantly share code, notes, and snippets.

@wedtm
Last active December 15, 2015 15:59
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wedtm/5285611 to your computer and use it in GitHub Desktop.
Save wedtm/5285611 to your computer and use it in GitHub Desktop.
diff --git a/.travis.yml b/.travis.yml
index d9c0a57..c094c87 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ rvm:
- ree
- 1.9.2
- 1.9.3
+ - 2.0.0
- jruby-19mode
- jruby-18mode
diff --git a/CHANGELOG b/CHANGELOG
index 3ab9b42..dbcc690 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,71 @@
# New Relic Ruby Agent Release Notes #
+## v3.6.0 ##
+
+ * Sidekiq supprt
+
+ The Ruby agent now supports the Sidekiq background job framework. Traces from
+ Sidekiq jobs will automatically show up in the Background tasks on New Relic
+ similar to Resque and Delayed::Job tasks.
+
+ * Improved thread safety
+
+ The primary metrics data structures in the Ruby agent are now thread safe.
+ This should provide better reliability for the agent under JRuby and threaded
+ scenarios such as Sidekiq or Puma.
+
+ * More robust environment report
+
+ The agent's analysis of the local environment (e.g. OS, Processors, loaded
+ gems) will now work in a wider variety of app environments, including
+ Sinatra.
+
+ * Experimental Rainbows! support
+
+ The Ruby agent now automatically detects and instruments the Rainbows! web
+ server. This support is considered experimental at present, and has not been
+ tested with all dispatch modes.
+
+ Thanks to Joseph Chen for the contribution.
+
+ * Fix a potential file descriptor leak in Resque instrumentation
+
+ A file descriptor leak that occurred when DontPerform exceptions were used to
+ abort processing of a job has been fixed. This should allow the Resque
+ instrumentation work correctly with the resque-lonely_job gem.
+
+## v3.5.8 ##
+
+ * Key Transactions
+
+ The Ruby agent now supports Key Transactions! Check out more details on the
+ feature at https://newrelic.com/docs/site/key-transactions
+
+ * Ruby 2.0
+
+ The Ruby agent is compatible with Ruby 2.0.0 which was just released.
+
+ * Improved Sinatra instrumentation
+
+ Several cases around the use of conditions and pass in Sinatra are now
+ better supported by the Ruby agent. Thanks Konstantin for the help!
+
+ * Outbound HTTP headers
+
+ Adds a 'X-NewRelic-ID' header to outbound Net::HTTP requests. This change
+ helps improve the correlation of performance between services in a service-
+ oriented architecture for a forthcoming feature. In the meantime, to disable
+ the header, set this in your newrelic.yml:
+
+ cross_application_tracer:
+ enabled: false
+
+ * Automatically detect Resque dispatcher
+
+ The agent does better auto-detection for the Resque worker process.
+ This should reduce the need to set NEW_RELIC_DISPATCHER=resque directly.
+
## v3.5.7 ##
* Resolved some issues with tracking of frontend queue time, particularly
@@ -15,9 +80,9 @@
* Use HTTPS by default
The agent now defaults to using SSL when it communicates with New Relic's
- servers. By defaults already configured, New Relic does not transmit any
- sensitive information (e.g. SQL parameters are masked), but SSL adds
- another layer of security. Upgrading customers may need to remove the
+ servers. By default is already configured New Relic does not transmit any
+ sensitive information (e.g. SQL parameters are masked), but SSL adds an
+ additional layer of security. Upgrading customers may need to remove the
"ssl: false" directive from their newrelic.yml to enable ssl. Customers on
Jruby may need to install the jruby-openssl gem to take advantage of this
feature.
diff --git a/Gemfile b/Gemfile
index 782c984..f6557a1 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,11 +1,15 @@
-source :rubygems
+source 'https://rubygems.org'
group :development do
- # require 0.9.2.2.
+ # require 0.9.6.
# There's problems with the test task in rake 10
# https://github.com/jimweirich/rake/issues/144
- gem 'rake', '0.9.2.2'
- gem 'mocha', '~>0.12.0'
+ gem 'rake', '0.9.6'
+ if RUBY_VERSION > '1.9.0'
+ gem 'mocha', '~>0.13.0', :require => false
+ else
+ gem 'mocha', '~>0.12.0'
+ end
gem 'shoulda', '~>3.0.1'
gem 'sdoc-helpers'
gem 'rdoc', '>= 2.4.2'
diff --git a/LICENSE b/LICENSE
index 4fb27a9..387649e 100644
--- a/LICENSE
+++ b/LICENSE
@@ -23,9 +23,96 @@ See https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+It includes source derived from 'okjson' by Keith Rarick, distributed under the
+MIT license.
+See https://github.com/kr/okjson/blob/bdd1113/okjson.rb#L3-21
+
+ Copyright 2011, 2012 Keith Rarick
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ THE SOFTWARE.
+
+It includes source derived from 'system_timer' by David Vollbracht & Philippe
+Hanrigou, distributed under Ruby's license terms.
+
+Copyright: (C) 2008 David Vollbracht & Philippe Hanrigou
+
+Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
+You can redistribute it and/or modify it under either the terms of the GPL
+(see COPYING.txt file), or the conditions below:
+
+ 1. You may make and give away verbatim copies of the source form of the
+ software without restriction, provided that you duplicate all of the
+ original copyright notices and associated disclaimers.
+
+ 2. You may modify your copy of the software in any way, provided that
+ you do at least ONE of the following:
+
+ a) place your modifications in the Public Domain or otherwise
+ make them Freely Available, such as by posting said
+modifications to Usenet or an equivalent medium, or by allowing
+the author to include your modifications in the software.
+
+ b) use the modified software only within your corporation or
+ organization.
+
+ c) rename any non-standard executables so the names do not conflict
+with standard executables, which must also be provided.
+
+ d) make other distribution arrangements with the author.
+
+ 3. You may distribute the software in object code or executable
+ form, provided that you do at least ONE of the following:
+
+ a) distribute the executables and library files of the software,
+together with instructions (in the manual page or equivalent)
+on where to get the original distribution.
+
+ b) accompany the distribution with the machine-readable source of
+the software.
+
+ c) give non-standard executables non-standard names, with
+ instructions on where to get the original software distribution.
+
+ d) make other distribution arrangements with the author.
+
+ 4. You may modify and include the part of the software into any other
+ software (possibly commercial). But some files in the distribution
+ are not written by the author, so that they are not under this terms.
+
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
+ files under the ./missing directory. See each file for the copying
+ condition.
+
+ 5. The scripts and library files supplied as input to or produced as
+ output from the software do not automatically fall under the
+ copyright of the software, but belong to whomever generated them,
+ and may be sold commercially, and may be aggregated with this
+ software.
+
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ PURPOSE.
+
All other components of this product are
-Copyright (c) 2008-2012 New Relic, Inc. All rights reserved.
+Copyright (c) 2008-2013 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
diff --git a/Rakefile b/Rakefile
index 46c958d..3620165 100644
--- a/Rakefile
+++ b/Rakefile
@@ -11,6 +11,14 @@ namespace :test do
desc "Run all tests"
task :all => %w{newrelic multiverse}
+ begin
+ require 'test_bisect'
+ TestBisect::BisectTask.new do |t|
+ t.test_task_name = 'test:newrelic'
+ end
+ rescue LoadError
+ end
+
agent_home = File.expand_path(File.dirname(__FILE__))
desc "Run functional test suite for newrelic"
diff --git a/init.rb b/init.rb
index fd45c27..e095923 100644
--- a/init.rb
+++ b/init.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# This is the initialization for the New Relic Ruby Agent when used as
# a plugin
require 'new_relic/control'
diff --git a/install.rb b/install.rb
index 8414f3c..78818ee 100644
--- a/install.rb
+++ b/install.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
if __FILE__ == $0 || $0 =~ /script\/plugin/
$LOAD_PATH << File.expand_path(File.join(File.dirname(__FILE__), 'lib'))
require 'new_relic/command'
diff --git a/lib/new_relic/agent.rb b/lib/new_relic/agent.rb
index f5bf6b4..6f4bd41 100644
--- a/lib/new_relic/agent.rb
+++ b/lib/new_relic/agent.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'forwardable'
require 'new_relic/control'
@@ -63,7 +67,6 @@ module NewRelic
require 'new_relic/version'
require 'new_relic/local_environment'
- require 'new_relic/stats'
require 'new_relic/metrics'
require 'new_relic/metric_spec'
require 'new_relic/metric_data'
@@ -75,9 +78,10 @@ module NewRelic
require 'new_relic/timer_lib'
require 'new_relic/agent'
+ require 'new_relic/agent/stats'
require 'new_relic/agent/chained_call'
require 'new_relic/agent/browser_monitoring'
- require 'new_relic/agent/cross_process_monitoring'
+ require 'new_relic/agent/cross_app_monitor'
require 'new_relic/agent/agent'
require 'new_relic/agent/shim_agent'
require 'new_relic/agent/method_tracer'
@@ -93,6 +97,7 @@ module NewRelic
require 'new_relic/agent/pipe_channel_manager'
require 'new_relic/agent/transaction_info'
require 'new_relic/agent/configuration'
+ require 'new_relic/agent/rules_engine'
require 'new_relic/agent/instrumentation/controller_instrumentation'
@@ -154,14 +159,60 @@ module NewRelic
@logger = log
end
+ # Record a value for the given metric name.
+ #
+ # This method should be used to record event-based metrics such as method
+ # calls that are associated with a specific duration or magnitude.
+ #
+ # +metric_name+ should follow a slash separated path convention. Application
+ # specific metrics should begin with "Custom/".
+ #
+ # +value+ should be either a single Numeric value representing the duration/
+ # magnitude of the event being recorded, or a Hash containing :count,
+ # :total, :min, :max, and :sum_of_squares keys. The latter form is useful
+ # for recording pre-aggregated metrics collected externally.
+ #
+ # This method is safe to use from any thread.
+ #
+ # @api public
+ def record_metric(metric_name, value)
+ if value.is_a?(Hash)
+ stats = NewRelic::Agent::Stats.new
+ stats.call_count = value[:count]
+ stats.total_call_time = value[:total]
+ stats.total_exclusive_time = value[:total]
+ stats.min_call_time = value[:min]
+ stats.max_call_time = value[:max]
+ stats.sum_of_squares = value[:sum_of_squares]
+ value = stats
+ end
+ agent.stats_engine.record_metrics(metric_name, value)
+ end
+
+ # Increment a simple counter metric.
+ #
+ # +metric_name+ should follow a slash separated path convention. Application
+ # specific metrics should begin with "Custom/".
+ #
+ # This method is safe to use from any thread.
+ #
+ # @api public
+ def increment_metric(metric_name, amount=1)
+ agent.stats_engine.record_metrics(metric_name) do |stats|
+ stats.increment_count(amount)
+ end
+ end
+
# 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/".
#
- # Return a NewRelic::Stats that accepts data
+ # Return a NewRelic::Agent::Stats that accepts data
# via calls to add_data_point(value).
+ #
+ # @deprecated
def get_stats(metric_name, use_scope=false)
agent.stats_engine.get_stats(metric_name, use_scope)
end
@@ -203,7 +254,7 @@ module NewRelic
# jobs or other work. If you are doing this with a web dispatcher
# that forks worker processes then you will need to force the
# agent to reconnect, which it won't do by default. Passenger and
- # Unicorn are already handled, nothing special needed for them.
+ # Rainbows and Unicorn are already handled, nothing special needed for them.
#
# Options:
# * <tt>:force_reconnect => true</tt> to force the spawned process to
diff --git a/lib/new_relic/agent/agent.rb b/lib/new_relic/agent/agent.rb
index a2ae1f6..f972f2b 100644
--- a/lib/new_relic/agent/agent.rb
+++ b/lib/new_relic/agent/agent.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'socket'
require 'net/https'
require 'net/http'
@@ -9,6 +13,9 @@ require 'new_relic/agent/pipe_service'
require 'new_relic/agent/configuration/manager'
require 'new_relic/agent/database'
require 'new_relic/agent/thread_profiler'
+require 'new_relic/agent/event_listener'
+require 'new_relic/agent/cross_app_monitor'
+require 'new_relic/environment_report'
module NewRelic
module Agent
@@ -23,21 +30,21 @@ module NewRelic
def initialize
@launch_time = Time.now
- @metric_ids = {}
- @events = NewRelic::Agent::EventListener.new
- @stats_engine = NewRelic::Agent::StatsEngine.new
- @transaction_sampler = NewRelic::Agent::TransactionSampler.new
- @sql_sampler = NewRelic::Agent::SqlSampler.new
- @thread_profiler = NewRelic::Agent::ThreadProfiler.new
- @cross_process_monitor = NewRelic::Agent::CrossProcessMonitor.new(@events)
- @error_collector = NewRelic::Agent::ErrorCollector.new
+ @events = NewRelic::Agent::EventListener.new
+ @stats_engine = NewRelic::Agent::StatsEngine.new
+ @transaction_sampler = NewRelic::Agent::TransactionSampler.new
+ @sql_sampler = NewRelic::Agent::SqlSampler.new
+ @thread_profiler = NewRelic::Agent::ThreadProfiler.new
+ @cross_app_monitor = NewRelic::Agent::CrossAppMonitor.new(@events)
+ @error_collector = NewRelic::Agent::ErrorCollector.new
+ @transaction_rules = NewRelic::Agent::RulesEngine.new
+ @metric_rules = NewRelic::Agent::RulesEngine.new
@connect_state = :pending
@connect_attempts = 0
@last_harvest_time = Time.now
@obfuscator = lambda {|sql| NewRelic::Agent::Database.default_sql_obfuscator(sql) }
- @forked = false
# FIXME: temporary work around for RUBY-839
if Agent.config[:monitor_mode]
@@ -82,27 +89,24 @@ module NewRelic
attr_reader :error_collector
# whether we should record raw, obfuscated, or no sql
attr_reader :record_sql
- # a cached set of metric_ids to save the collector some time -
- # it returns a metric id for every metric name we send it, and
- # in the future we transmit using the metric id only
- attr_reader :metric_ids
- # in theory a set of rules applied by the agent to the output
- # of its metrics. Currently unimplemented
- attr_reader :url_rules
# a configuration for the Real User Monitoring system -
# handles things like static setup of the header for inclusion
# into pages
attr_reader :beacon_configuration
- # cross process id's and encoding
+ # cross application tracing ids and encoding
attr_reader :cross_process_id
- attr_reader :cross_process_encoding_bytes
+ attr_reader :cross_app_encoding_bytes
# service for communicating with collector
attr_accessor :service
# Global events dispatcher. This will provides our primary mechanism
# for agent-wide events, such as finishing configuration, error notification
# and request before/after from Rack.
attr_reader :events
-
+ # Transaction and metric renaming rules as provided by the
+ # collector on connect. The former are applied during txns,
+ # the latter during harvest.
+ attr_reader :transaction_rules
+ attr_reader :metric_rules
# Returns the length of the unsent errors array, if it exists,
# otherwise nil
@@ -143,8 +147,7 @@ module NewRelic
metrics << metric
metrics.each do |name|
- stats = stats_engine.get_stats_no_scope(name)
- stats.record_data_point(duration_seconds)
+ NewRelic::Agent.record_metric(name, duration_seconds)
end
if is_error
@@ -180,14 +183,12 @@ module NewRelic
# connection, this tells me to only try it once so this method returns
# quickly if there is some kind of latency with the server.
def after_fork(options={})
- @forked = true
Agent.config.apply_config(NewRelic::Agent::Configuration::ManualSource.new(options), 1)
if channel_id = options[:report_to_channel]
@service = NewRelic::Agent::PipeService.new(channel_id)
if connected?
@connected_pid = $$
- @metric_ids = {}
else
::NewRelic::Agent.logger.debug("Child process #{$$} not reporting to non-connected parent.")
@service.shutdown(Time.now)
@@ -211,10 +212,6 @@ module NewRelic
@stats_engine.start_sampler_thread
end
- def forked?
- @forked
- end
-
# True if we have initialized and completed 'start'
def started?
@started
@@ -452,7 +449,7 @@ module NewRelic
# requests, we need to wait until the children are forked
# before connecting, otherwise the parent process sends odd data
def using_forking_dispatcher?
- log_if([:passenger, :unicorn].include?(Agent.config[:dispatcher]),
+ log_if([:passenger, :rainbows, :unicorn].include?(Agent.config[:dispatcher]),
:info, "Connecting workers after forking.")
end
@@ -533,7 +530,7 @@ module NewRelic
def handle_force_restart(error)
::NewRelic::Agent.logger.debug error.message
reset_stats
- @metric_ids = {}
+ @service.reset_metric_id_cache if @service
@connect_state = :pending
sleep 30
end
@@ -693,7 +690,7 @@ module NewRelic
# Checks whether we should send environment info, and if so,
# returns the snapshot from the local environment
def environment_for_connect
- Agent.config[:send_environment_info] ? Control.instance.local_env.snapshot : []
+ Agent.config[:send_environment_info] ? Array(EnvironmentReport.new) : []
end
# Initializes the hash of settings that we send to the
@@ -743,16 +740,28 @@ module NewRelic
end
::NewRelic::Agent.logger.debug "Server provided config: #{config_data.inspect}"
- server_config = NewRelic::Agent::Configuration::ServerSource.new(config_data)
+ server_config = NewRelic::Agent::Configuration::ServerSource.new(config_data, Agent.config)
Agent.config.apply_config(server_config, 1)
log_connection!(config_data) if @service
+ add_rules_to_engine(config_data['transaction_name_rules'],
+ NewRelic::Agent.instance.transaction_rules)
+ add_rules_to_engine(config_data['metric_name_rules'],
+ NewRelic::Agent.instance.metric_rules)
+
# If you're adding something else here to respond to the server-side config,
# use Agent.instance.events.subscribe(:finished_configuring) callback instead!
@beacon_configuration = BeaconConfiguration.new
end
+ def add_rules_to_engine(rule_specifications, rules_engine)
+ return unless rule_specifications && rule_specifications.any?
+ rule_specifications.each do |rule_spec|
+ rules_engine << NewRelic::Agent::RulesEngine::Rule.new(rule_spec)
+ end
+ end
+
# Logs when we connect to the server, for debugging purposes
# - makes sure we know if an agent has not connected
def log_connection!(config_data)
@@ -772,30 +781,13 @@ module NewRelic
end
include Connect
-
- # Serialize all the important data that the agent might want
- # to send to the server. We could be sending this to file (
- # common in short-running background transactions ) or
- # alternately we could serialize via a pipe or socket to a
- # local aggregation device
- def serialize
- accumulator = []
- accumulator[1] = harvest_transaction_traces if @transaction_sampler
- accumulator[2] = harvest_errors if @error_collector
- accumulator[0] = harvest_timeslice_data
- reset_stats
- @metric_ids = {}
- accumulator
- end
- public :serialize
-
- # Accepts data as provided by the serialize method and merges
+ # Accepts an array of (metrics, transaction_traces, errors) and merges
# it into our current collection of data to send. Can be
# dangerous if we re-merge the same data more than once - it
# will be sent multiple times.
def merge_data_from(data)
metrics, transaction_traces, errors = data
- @stats_engine.merge_data(metrics) if metrics
+ @stats_engine.merge!(metrics) if metrics
if transaction_traces && transaction_traces.respond_to?(:any?) &&
transaction_traces.any?
if @traces
@@ -831,8 +823,8 @@ module NewRelic
# agent run and New Relic sees it as a separate instance (default is false).
def connect(options={})
defaults = {
- :keep_retrying => true,
- :force_reconnect => false
+ :keep_retrying => Agent.config[:keep_retrying],
+ :force_reconnect => Agent.config[:force_reconnect]
}
opts = defaults.merge(options)
@@ -876,40 +868,26 @@ module NewRelic
NewRelic::Agent::BusyCalculator.harvest_busy
@unsent_timeslice_data ||= {}
- @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data, @metric_ids)
+ @unsent_timeslice_data = @stats_engine.harvest_timeslice_data(@unsent_timeslice_data,
+ @metric_rules)
@unsent_timeslice_data
end
- # takes an array of arrays of spec and id, adds it into the
- # metric cache so we can save the collector some work by
- # sending integers instead of strings
- def fill_metric_id_cache(pairs_of_specs_and_ids)
- Array(pairs_of_specs_and_ids).each do |metric_spec_hash, metric_id|
- metric_spec = MetricSpec.new(metric_spec_hash['name'],
- metric_spec_hash['scope'])
- @metric_ids[metric_spec] = metric_id
- end
- end
-
# note - exceptions are logged in invoke_remote. If an exception is encountered here,
# then the metric data is downsampled for another
# transmission later
def harvest_and_send_timeslice_data
now = Time.now
- NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote').record_data_point(0.0)
- NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Supportability/invoke_remote/metric_data').record_data_point(0.0)
+ NewRelic::Agent.record_metric('Supportability/invoke_remote', 0.0)
+ NewRelic::Agent.record_metric('Supportability/invoke_remote/metric_data', 0.0)
harvest_timeslice_data(now)
- # In this version of the protocol
- # we get back an assoc array of spec to id.
- metric_specs_and_ids = []
begin
- metric_specs_and_ids = @service.metric_data(@last_harvest_time.to_f,
- now.to_f,
- @unsent_timeslice_data.values)
+ @service.metric_data(@last_harvest_time.to_f,
+ now.to_f,
+ @unsent_timeslice_data)
rescue UnrecoverableServerException => e
::NewRelic::Agent.logger.debug e.message
end
- fill_metric_id_cache(metric_specs_and_ids)
::NewRelic::Agent.logger.debug "#{now}: sent #{@unsent_timeslice_data.length} timeslices (#{@service.agent_id}) in #{Time.now - now} seconds"
@@ -1047,9 +1025,9 @@ module NewRelic
end
raise e
ensure
- NewRelic::Agent::Database.close_connections unless forked?
- @stats_engine.get_stats_no_scope('Supportability/Harvest') \
- .record_data_point((Time.now - now).to_f)
+ NewRelic::Agent::Database.close_connections
+ duration = (Time.now - now).to_f
+ @stats_engine.record_metrics('Supportability/Harvest', duration)
end
# This method contacts the server to send remaining data and
diff --git a/lib/new_relic/agent/agent_logger.rb b/lib/new_relic/agent/agent_logger.rb
index 9405ee6..2c1903f 100644
--- a/lib/new_relic/agent/agent_logger.rb
+++ b/lib/new_relic/agent/agent_logger.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'logger'
module NewRelic
@@ -78,8 +82,7 @@ module NewRelic
end
def create_null_logger
- null_path = ["/dev/null", "NUL"].detect{|f| File.exists?(f)}
- @log = ::Logger.new(null_path)
+ @log = NewRelic::Agent::NullLogger.new
end
def wants_stdout(config)
diff --git a/lib/new_relic/agent/audit_logger.rb b/lib/new_relic/agent/audit_logger.rb
index c7451dc..db68a40 100644
--- a/lib/new_relic/agent/audit_logger.rb
+++ b/lib/new_relic/agent/audit_logger.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'logger'
require 'fileutils'
@@ -37,9 +41,13 @@ module NewRelic
def setup_logger
path = ensure_log_path
- @log = ::Logger.new(path || "/dev/null")
- @log.formatter = log_formatter
- ::NewRelic::Agent.logger.info("Audit log enabled at '#{path}'") if path
+ if path
+ @log = ::Logger.new(path)
+ @log.formatter = log_formatter
+ ::NewRelic::Agent.logger.info("Audit log enabled at '#{path}'")
+ else
+ @log = NewRelic::Agent::NullLogger.new
+ end
end
def ensure_log_path
diff --git a/lib/new_relic/agent/beacon_configuration.rb b/lib/new_relic/agent/beacon_configuration.rb
index afd9940..fc1b38c 100644
--- a/lib/new_relic/agent/beacon_configuration.rb
+++ b/lib/new_relic/agent/beacon_configuration.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
# This class contains the configuration data for setting up RUM
diff --git a/lib/new_relic/agent/browser_monitoring.rb b/lib/new_relic/agent/browser_monitoring.rb
index bcc46db..29b242c 100644
--- a/lib/new_relic/agent/browser_monitoring.rb
+++ b/lib/new_relic/agent/browser_monitoring.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'base64'
require 'new_relic/agent/beacon_configuration'
module NewRelic
diff --git a/lib/new_relic/agent/busy_calculator.rb b/lib/new_relic/agent/busy_calculator.rb
index 9688a2d..8f9f2d4 100644
--- a/lib/new_relic/agent/busy_calculator.rb
+++ b/lib/new_relic/agent/busy_calculator.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
# This module supports calculation of actual time spent processing requests over the course of
@@ -14,7 +18,7 @@ module NewRelic
# For testability, add accessors:
attr_reader :harvest_start, :accumulator
-
+
# sets up busy calculations based on the start and end of
# transactions - used for a rough estimate of what percentage of
# wall clock time is spent processing requests
@@ -26,7 +30,7 @@ module NewRelic
@entrypoint_stack.push time
end
end
-
+
# called when a transaction finishes, to add time to the
# instance variable accumulator. this is harvested when we send
# data to the server
@@ -48,7 +52,7 @@ module NewRelic
end
end
end
-
+
# this returns the size of the entry point stack, which
# determines how many transactions are running
def busy_count
@@ -90,7 +94,9 @@ module NewRelic
busy = busy / time_window
- instance_busy_stats.record_data_point busy if Agent.config[:report_instance_busy]
+ if Agent.config[:report_instance_busy]
+ NewRelic::Agent.record_metric('Instance/Busy', busy)
+ end
@harvest_start = t0
end
@@ -101,11 +107,6 @@ module NewRelic
Time.now
end
- def instance_busy_stats
- # Late binding on the Instance/busy stats
- NewRelic::Agent.agent.stats_engine.get_stats_no_scope 'Instance/Busy'
- end
-
self.reset
end
end
diff --git a/lib/new_relic/agent/chained_call.rb b/lib/new_relic/agent/chained_call.rb
index b2a8907..0641953 100644
--- a/lib/new_relic/agent/chained_call.rb
+++ b/lib/new_relic/agent/chained_call.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# This class is used by NewRelic::Agent.set_sql_obfuscator to chain multiple
# obfuscation blocks when not using the default :replace action
class NewRelic::ChainedCall
diff --git a/lib/new_relic/agent/configuration.rb b/lib/new_relic/agent/configuration.rb
index 6cecf5d..daf9d42 100644
--- a/lib/new_relic/agent/configuration.rb
+++ b/lib/new_relic/agent/configuration.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/configuration/manager'
# The agent's configuration is accessed through a configuration object exposed
diff --git a/lib/new_relic/agent/configuration/defaults.rb b/lib/new_relic/agent/configuration/defaults.rb
index 8e52384..0be570a 100644
--- a/lib/new_relic/agent/configuration/defaults.rb
+++ b/lib/new_relic/agent/configuration/defaults.rb
@@ -1,7 +1,16 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Configuration
- DEFAULTS = {
+ # This is so we can easily differentiate between the actual
+ # default source and a Hash that was simply pushed onto the
+ # config stack.
+ class DefaultSource < Hash; end
+
+ DEFAULTS = DefaultSource[
:config_path => Proc.new {
# Check a sequence of file locations for newrelic.yml
files = []
@@ -115,12 +124,13 @@ module NewRelic
:'rum.load_episodes_file' => true,
:'browser_monitoring.auto_instrument' => Proc.new { self[:'rum.enabled'] },
- :'cross_process.enabled' => true,
+ :trusted_account_ids => [],
+ :"cross_application_tracer.enabled" => true,
:'thread_profiler.enabled' => Proc.new { NewRelic::Agent::ThreadProfiler.is_supported? },
:marshaller => Proc.new { NewRelic::Agent::NewRelicService::JsonMarshaller.is_supported? ? 'json' : 'pruby' }
- }.freeze
+ ].freeze
end
end
end
diff --git a/lib/new_relic/agent/configuration/environment_source.rb b/lib/new_relic/agent/configuration/environment_source.rb
index 5ad8e0c..8c83bfe 100644
--- a/lib/new_relic/agent/configuration/environment_source.rb
+++ b/lib/new_relic/agent/configuration/environment_source.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Configuration
diff --git a/lib/new_relic/agent/configuration/manager.rb b/lib/new_relic/agent/configuration/manager.rb
index 424d13a..a462eee 100644
--- a/lib/new_relic/agent/configuration/manager.rb
+++ b/lib/new_relic/agent/configuration/manager.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'forwardable'
require 'new_relic/agent/configuration/defaults'
require 'new_relic/agent/configuration/mask_defaults'
diff --git a/lib/new_relic/agent/configuration/mask_defaults.rb b/lib/new_relic/agent/configuration/mask_defaults.rb
index 620eb92..7d3a87a 100644
--- a/lib/new_relic/agent/configuration/mask_defaults.rb
+++ b/lib/new_relic/agent/configuration/mask_defaults.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Configuration
diff --git a/lib/new_relic/agent/configuration/server_source.rb b/lib/new_relic/agent/configuration/server_source.rb
index 6dd376d..e8a5cd6 100644
--- a/lib/new_relic/agent/configuration/server_source.rb
+++ b/lib/new_relic/agent/configuration/server_source.rb
@@ -1,8 +1,12 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Configuration
class ServerSource < DottedHash
- def initialize(hash)
+ def initialize(hash, existing_config={})
if hash['agent_config']
if hash['agent_config']['transaction_tracer.transaction_threshold'] =~ /apdex_f/i
# when value is "apdex_f" remove the config and defer to default
@@ -11,15 +15,32 @@ module NewRelic
super(hash.delete('agent_config'))
end
- string_map = [
- ['collect_traces', 'transaction_tracer.enabled'],
- ['collect_traces', 'slow_sql.enabled'],
- ['collect_errors', 'error_collector.enabled']
- ].each do |pair|
- hash[pair[1]] = hash[pair[0]] if hash[pair[0]] != nil
+ if hash['web_transactions_apdex']
+ self[:web_transactions_apdex] = hash.delete('web_transactions_apdex')
end
+ apply_feature_gates(hash, existing_config)
+
+ super(hash)
+ end
- super
+ # These feature gates are not intended to be bullet-proof, but only to
+ # avoid the overhead of collecting and transmitting additional data if
+ # the user's subscription level precludes its use. The server is the
+ # ultimate authority regarding subscription levels, so we expect it to
+ # do the real enforcement there.
+ def apply_feature_gates(hash, existing_config)
+ gated_features = {
+ :'transaction_tracer.enabled' => 'collect_traces',
+ :'slow_sql.enabled' => 'collect_traces',
+ :'error_collector.enabled' => 'collect_errors'
+ }
+ gated_features.each do |feature, gate_key|
+ unless hash[gate_key].nil?
+ existing_value = existing_config[feature]
+ allowed_by_server = hash[gate_key]
+ hash[feature] = (allowed_by_server && existing_value)
+ end
+ end
end
end
end
diff --git a/lib/new_relic/agent/configuration/yaml_source.rb b/lib/new_relic/agent/configuration/yaml_source.rb
index b602e16..a0fe520 100644
--- a/lib/new_relic/agent/configuration/yaml_source.rb
+++ b/lib/new_relic/agent/configuration/yaml_source.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/configuration'
module NewRelic
@@ -7,7 +11,7 @@ module NewRelic
attr_accessor :file_path
def initialize(path, env)
- ::NewRelic::Agent.logger.debug("Reading configuration from #{path}")
+ ::NewRelic::Agent.logger.info("Reading configuration from #{path}")
config = {}
begin
diff --git a/lib/new_relic/agent/cross_app_monitor.rb b/lib/new_relic/agent/cross_app_monitor.rb
new file mode 100644
index 0000000..5cd5905
--- /dev/null
+++ b/lib/new_relic/agent/cross_app_monitor.rb
@@ -0,0 +1,239 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require 'new_relic/rack/agent_hooks'
+require 'new_relic/agent/thread'
+
+module NewRelic
+ module Agent
+
+ class CrossAppMonitor
+
+ NEWRELIC_ID_HEADER = 'X-NewRelic-ID'
+ NEWRELIC_APPDATA_HEADER = 'X-NewRelic-App-Data'
+ NEWRELIC_TXN_HEADER = 'X-NewRelic-Transaction'
+ NEWRELIC_TXN_HEADER_KEYS = %W{
+ #{NEWRELIC_TXN_HEADER} HTTP_X_NEWRELIC_TRANSACTION X_NEWRELIC_TRANSACTION
+ }
+ NEWRELIC_ID_HEADER_KEYS = %W{
+ #{NEWRELIC_ID_HEADER} HTTP_X_NEWRELIC_ID X_NEWRELIC_ID
+ }
+ CONTENT_LENGTH_HEADER_KEYS = %w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH}
+
+ # Because we aren't in the right spot when our transaction actually
+ # starts, hold client_cross_app_id we get thread local until then.
+ THREAD_ID_KEY = :newrelic_client_cross_app_id
+
+ # Same for the referring transaction guid
+ THREAD_TXN_KEY = :newrelic_cross_app_referring_txn_info
+
+
+ # Functions for obfuscating and unobfuscating header values
+ module EncodingFunctions
+
+ module_function
+
+ def obfuscate_with_key(key, text)
+ [ encode_with_key(key, text) ].pack('m').chomp.gsub(/\n/, '')
+ end
+
+ def decode_with_key(key, text)
+ encode_with_key( key, text.unpack('m').first )
+ end
+
+ def encode_with_key(key, text)
+ return text unless key
+ key = key.bytes.to_a if key.respond_to?( :bytes )
+
+ encoded = ""
+ encoded.force_encoding('binary') if encoded.respond_to?( :force_encoding )
+ index = 0
+ text.each_byte do |byte|
+ encoded.concat((byte ^ key[index % key.length].to_i))
+ index+=1
+ end
+ encoded
+ end
+
+ end
+ include EncodingFunctions
+
+
+ def initialize(events = nil)
+ # When we're starting up for real in the agent, we get passed the events
+ # Other spots can pull from the agent, during startup the agent doesn't exist yet!
+ events ||= Agent.instance.events
+
+ events.subscribe(:finished_configuring) do
+ register_event_listeners
+ end
+ end
+
+
+ # Expected sequence of events:
+ # :before_call will save our cross application request id to the thread
+ # :start_transaction will get called when a transaction starts up
+ # :after_call will write our response headers/metrics and clean up the thread
+ def register_event_listeners
+ NewRelic::Agent.logger.
+ debug("Wiring up Cross Application Tracing to events after finished configuring")
+
+ events = Agent.instance.events
+ events.subscribe(:before_call) do |env|
+ if should_process_request(env)
+ save_client_cross_app_id(env)
+ save_referring_transaction_info(env)
+ end
+ end
+
+ events.subscribe(:start_transaction) do |name|
+ set_transaction_custom_parameters
+ end
+
+ events.subscribe(:after_call) do |env, (status_code, headers, body)|
+ insert_response_header(env, headers)
+ end
+
+ events.subscribe(:notice_error) do |_, options|
+ set_error_custom_parameters(options)
+ end
+ end
+
+ def save_client_cross_app_id(request_headers)
+ NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = decoded_id(request_headers)
+ end
+
+ def clear_client_cross_app_id
+ NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = nil
+ end
+
+ def client_cross_app_id
+ NewRelic::Agent::AgentThread.current[THREAD_ID_KEY]
+ end
+
+ def save_referring_transaction_info(request_headers)
+ key = NewRelic::Agent.config[:encoding_key]
+ txn_header = from_headers( request_headers, NEWRELIC_TXN_HEADER_KEYS ) or return
+ txn_header = decode_with_key( key, txn_header )
+ txn_info = NewRelic.json_load( txn_header )
+ NewRelic::Agent.logger.debug "Referring txn_info: %p" % [ txn_info ]
+
+ NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] = txn_info
+ end
+
+ def clear_referring_transaction_info
+ NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] = nil
+ end
+
+ def client_referring_transaction_guid
+ info = NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] or return nil
+ return info[0]
+ end
+
+ def client_referring_transaction_record_flag
+ info = NewRelic::Agent::AgentThread.current[THREAD_TXN_KEY] or return nil
+ return info[1]
+ end
+
+ def insert_response_header(request_headers, response_headers)
+ unless client_cross_app_id.nil?
+ timings = NewRelic::Agent::BrowserMonitoring.timings
+ content_length = content_length_from_request(request_headers)
+
+ set_response_headers(response_headers, timings, content_length)
+ set_metrics(client_cross_app_id, timings)
+
+ clear_client_cross_app_id
+ end
+ end
+
+ def should_process_request(request_headers)
+ return cross_app_enabled? && trusts?(request_headers)
+ end
+
+ def cross_app_enabled?
+ NewRelic::Agent.config[:cross_process_id] &&
+ (NewRelic::Agent.config[:"cross_application_tracer.enabled"] ||
+ NewRelic::Agent.config[:cross_application_tracing])
+ end
+
+ # Expects an ID of format "12#345", and will only accept that!
+ def trusts?(request)
+ id = decoded_id(request)
+ split_id = id.match(/(\d+)#\d+/)
+ return false if split_id.nil?
+
+ NewRelic::Agent.config[:trusted_account_ids].include?(split_id.captures.first.to_i)
+ end
+
+ def set_response_headers(response_headers, timings, content_length)
+ response_headers[NEWRELIC_APPDATA_HEADER] = build_payload(timings, content_length)
+ end
+
+ def build_payload(timings, content_length)
+ payload = [
+ NewRelic::Agent.config[:cross_process_id],
+ timings.transaction_name,
+ timings.queue_time_in_seconds.to_f,
+ timings.app_time_in_seconds.to_f,
+ content_length,
+ transaction_guid()
+ ]
+ key = NewRelic::Agent.config[:encoding_key]
+ payload = obfuscate_with_key( key, NewRelic.json_dump(payload) )
+ end
+
+ def set_transaction_custom_parameters
+ # We expect to get the before call to set the id (if we have it) before
+ # this, and then write our custom parameter when the transaction starts
+ NewRelic::Agent.add_custom_parameters(:client_cross_process_id => client_cross_app_id()) if client_cross_app_id()
+ NewRelic::Agent.add_custom_parameters(:referring_transaction_guid => client_referring_transaction_guid()) if
+ client_referring_transaction_guid()
+
+ NewRelic::Agent.logger.debug "Referring transaction guid: %p" % [client_referring_transaction_guid()]
+ end
+
+ def set_error_custom_parameters(options)
+ options[:client_cross_process_id] = client_cross_app_id() if client_cross_app_id()
+ # [MG] TODO: Should the CAT metrics be set here too?
+ end
+
+ def set_metrics(id, timings)
+ metric_name = "ClientApplication/#{id}/all"
+ NewRelic::Agent.record_metric(metric_name, timings.app_time_in_seconds)
+ end
+
+ def decoded_id(request)
+ encoded_id = from_headers(request, NEWRELIC_ID_HEADER_KEYS)
+ return "" if encoded_id.nil?
+
+ key = NewRelic::Agent.config[:encoding_key]
+ decode_with_key( key, encoded_id )
+ end
+
+ def content_length_from_request(request)
+ from_headers(request, CONTENT_LENGTH_HEADER_KEYS) || -1
+ end
+
+ def transaction_guid
+ NewRelic::Agent::TransactionInfo.get.guid
+ end
+
+ private
+
+ def from_headers(request, try_keys)
+ # For lookups, upcase all our keys on both sides just to be safe
+ upcased_keys = try_keys.map{|k| k.upcase}
+ upcased_keys.each do |header|
+ found_key = request.keys.find { |k| k.upcase == header }
+ return request[found_key] unless found_key.nil?
+ end
+ nil
+ end
+
+ end
+
+ end
+end
+
diff --git a/lib/new_relic/agent/cross_app_tracing.rb b/lib/new_relic/agent/cross_app_tracing.rb
new file mode 100644
index 0000000..a9e9cb1
--- /dev/null
+++ b/lib/new_relic/agent/cross_app_tracing.rb
@@ -0,0 +1,269 @@
+# -*- ruby -*-
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+module NewRelic
+ module Agent
+ module CrossAppTracing
+ extend NewRelic::Agent::CrossAppMonitor::EncodingFunctions
+
+ # Exception raised if there is a problem with cross app transactions.
+ class Error < RuntimeError; end
+
+
+ # The cross app response header for "outgoing" calls
+ NR_APPDATA_HEADER = 'X-NewRelic-App-Data'
+
+ # The cross app id header for "outgoing" calls
+ NR_ID_HEADER = 'X-NewRelic-ID'
+
+ # The cross app transaction header for "outgoing" calls
+ NR_TXN_HEADER = 'X-NewRelic-Transaction'
+
+ # The index of the transaction GUID in the appdata header of responses
+ APPDATA_TXN_GUID_INDEX = 5
+
+
+ ###############
+ module_function
+ ###############
+
+ # Send the given +request+, adding metrics appropriate to the
+ # response when it comes back.
+ def trace_http_request( http, request )
+ return yield unless NewRelic::Agent.is_execution_traced?
+
+ t0, segment = start_trace( http, request )
+ begin
+ response = yield
+ ensure
+ finish_trace( t0, segment, request, response, http ) if t0
+ end
+
+ return response
+ end
+
+
+ # Set up the necessary state for cross-application tracing before the
+ # given +request+ goes out on the specified +http+ connection.
+ def start_trace( http, request )
+ inject_request_headers( request ) if cross_app_enabled?
+
+ # Create a segment and time the call
+ t0 = Time.now
+ segment = stats_engine.push_scope( "External/#{http.address}/all", t0 )
+
+ return t0, segment
+ rescue => err
+ NewRelic::Agent.logger.error "Uncaught exception while tracing HTTP request", err
+ return nil
+ end
+
+
+ # Finish tracing the HTTP +request+ that started at +t0+ with the information in
+ # +response+ and the given +http+ connection.
+ def finish_trace( t0, segment, request, response, http )
+ t1 = Time.now
+ duration = t1.to_f - t0.to_f
+
+ begin
+ if request && response && http
+ # Figure out which metrics we need to report based on the request and response
+ # The last (most-specific) one is scoped.
+ metrics = metrics_for( http, request, response )
+ scoped_metric = metrics.pop
+
+ stats_engine.record_metrics(metrics, duration)
+ stats_engine.record_metrics(scoped_metric, duration, :scoped => true)
+
+ # Add TT custom parameters
+ stats_engine.rename_scope_segment( scoped_metric )
+ extract_custom_parameters( response ) if response_is_crossapp?( response )
+ end
+ ensure
+ # We always need to pop the scope stack to avoid an inconsistent
+ # state, which will prevent tracing of the whole transaction.
+ stats_engine.pop_scope( segment, duration, t1 )
+ end
+ rescue NewRelic::Agent::CrossAppTracing::Error => err
+ NewRelic::Agent.logger.debug "while cross app tracing", err
+ rescue => err
+ NewRelic::Agent.logger.error "Uncaught exception while finishing an HTTP request trace", err
+ end
+
+
+ # Return +true+ if cross app tracing is enabled in the config.
+ def cross_app_enabled?
+ NewRelic::Agent.config[:cross_process_id] &&
+ (NewRelic::Agent.config[:"cross_application_tracer.enabled"] ||
+ NewRelic::Agent.config[:cross_application_tracing])
+ end
+
+
+ # Memoized fetcher for the cross app encoding key. Raises a
+ # NewRelic::Agent::CrossAppTracing::Error if the key isn't configured.
+ def cross_app_encoding_key
+ NewRelic::Agent.config[:encoding_key] or
+ raise NewRelic::Agent::CrossAppTracing::Error, "No encoding_key set."
+ end
+
+
+ # Inject the X-Process header into the outgoing +request+.
+ def inject_request_headers( request )
+ key = cross_app_encoding_key()
+ cross_app_id = NewRelic::Agent.config[:cross_process_id] or
+ raise NewRelic::Agent::CrossAppTracing::Error, "no cross app ID configured"
+ txn_guid = NewRelic::Agent::TransactionInfo.get.guid
+ txn_data = NewRelic.json_dump([ txn_guid, false ])
+
+ request[ NR_ID_HEADER ] = obfuscate_with_key( key, cross_app_id )
+ request[ NR_TXN_HEADER ] = obfuscate_with_key( key, txn_data )
+
+ rescue NewRelic::Agent::CrossAppTracing::Error => err
+ NewRelic::Agent.logger.debug "Not injecting x-process header", err
+ end
+
+
+ # Extract any custom parameters from +response+ if it's cross-application and
+ # add them to the current TT node.
+ def extract_custom_parameters( response )
+
+ appdata = extract_appdata( response )
+ sampler = NewRelic::Agent.instance.transaction_sampler
+ sampler.add_segment_parameters( :transaction_guid => appdata[APPDATA_TXN_GUID_INDEX] )
+
+ end
+
+
+ # Return the set of metric names that correspond to
+ # the given +request+ and +response+.
+ def metrics_for( http, request, response )
+ metrics = common_metrics( http )
+
+ if response_is_crossapp?( response )
+ begin
+ metrics.concat metrics_for_crossapp_response( http, response )
+ rescue => err
+ # Fall back to regular metrics if there's a problem with x-process metrics
+ NewRelic::Agent.logger.debug "%p while fetching x-process metrics: %s" %
+ [ err.class, err.message ]
+ metrics.concat metrics_for_regular_response( http, request, response )
+ end
+ else
+ NewRelic::Agent.logger.debug "Response doesn't have CAT headers."
+ metrics.concat metrics_for_regular_response( http, request, response )
+ end
+
+ return metrics
+ end
+
+
+ # Return an Array of metrics used for every response.
+ def common_metrics( http )
+ metrics = [ "External/all" ]
+ metrics << "External/#{http.address}/all"
+
+ if NewRelic::Agent::Instrumentation::MetricFrame.recording_web_transaction?
+ metrics << "External/allWeb"
+ else
+ metrics << "External/allOther"
+ end
+
+ return metrics
+ end
+
+
+ # Returns +true+ if Cross Application Tracing is enabled, and the given +response+
+ # has the appropriate headers.
+ def response_is_crossapp?( response )
+ return false unless cross_app_enabled?
+ unless response[NR_APPDATA_HEADER]
+ NewRelic::Agent.logger.debug "Response doesn't have the %p header: %p" %
+ [ NR_APPDATA_HEADER, response.to_hash ]
+ return false
+ end
+
+ return true
+ end
+
+
+ # Return the set of metric objects appropriate for the given cross app
+ # +response+.
+ def metrics_for_crossapp_response( http, response )
+ xp_id, txn_name, q_time, r_time, req_len, _ = extract_appdata( response )
+
+ check_crossapp_id( xp_id )
+ check_transaction_name( txn_name )
+
+ NewRelic::Agent.logger.debug "CAT xp_id: %p, txn_name: %p." % [ xp_id, txn_name ]
+
+ metrics = []
+ metrics << "ExternalApp/#{http.address}/#{xp_id}/all"
+ metrics << "ExternalTransaction/#{http.address}/#{xp_id}/#{txn_name}"
+
+ return metrics
+ end
+
+
+ # Extract x-process application data from the specified +response+ and return
+ # it as an array of the form:
+ #
+ # [
+ # <cross app ID>,
+ # <transaction name>,
+ # <queue time in seconds>,
+ # <response time in seconds>,
+ # <request content length in bytes>,
+ # <transaction GUID>
+ # ]
+ def extract_appdata( response )
+ appdata = response[NR_APPDATA_HEADER] or
+ raise NewRelic::Agent::CrossAppTracing::Error,
+ "Can't derive metrics for response: no #{NR_APPDATA_HEADER} header!"
+
+ key = cross_app_encoding_key()
+ decoded_appdata = decode_with_key( key, appdata )
+ decoded_appdata.set_encoding( ::Encoding::UTF_8 ) if
+ decoded_appdata.respond_to?( :set_encoding )
+
+ return NewRelic.json_load( decoded_appdata )
+ end
+
+
+ # Return the set of metric objects appropriate for the given (non-cross app)
+ # +response+.
+ def metrics_for_regular_response( http, request, response )
+ metrics = []
+ metrics << "External/#{http.address}/Net::HTTP/#{request.method}"
+
+ return metrics
+ end
+
+
+ # Fetch a reference to the stats engine.
+ def stats_engine
+ NewRelic::Agent.instance.stats_engine
+ end
+
+
+ # Check the given +id+ to ensure it conforms to the format of a cross-application
+ # ID. Raises an NewRelic::Agent::CrossAppTracing::Error if it doesn't.
+ def check_crossapp_id( id )
+ id =~ /\A\d+#\d+\z/ or
+ raise NewRelic::Agent::CrossAppTracing::Error,
+ "malformed cross application ID %p" % [ id ]
+ end
+
+
+ # Check the given +name+ to ensure it conforms to the format of a valid transaction
+ # name.
+ def check_transaction_name( name )
+ # No-op -- apparently absolutely anything is a valid transaction name?
+ # This is here for when that inevitably comes back to haunt us.
+ end
+
+ end
+ end
+end
+
diff --git a/lib/new_relic/agent/cross_process_monitoring.rb b/lib/new_relic/agent/cross_process_monitoring.rb
deleted file mode 100644
index ff46a8f..0000000
--- a/lib/new_relic/agent/cross_process_monitoring.rb
+++ /dev/null
@@ -1,187 +0,0 @@
-require 'new_relic/rack/agent_hooks'
-require 'new_relic/agent/thread'
-
-module NewRelic
- module Agent
- class CrossProcessMonitor
-
- def initialize(events = nil)
- # When we're starting up for real in the agent, we get passed the events
- # Other spots can pull from the agent, during startup the agent doesn't exist yet!
- events ||= Agent.instance.events
- @trusted_ids = []
-
- events.subscribe(:finished_configuring) do
- finish_setup(Agent.config)
- register_event_listeners
- end
- end
-
- def finish_setup(config)
- @cross_process_id = config[:cross_process_id]
- @encoding_key = config[:encoding_key]
- @encoding_bytes = get_bytes(@encoding_key) unless @encoding_key.nil?
- @trusted_ids = config[:trusted_account_ids] || []
- end
-
- # Expected sequence of events:
- # :before_call will save our cross process request id to the thread
- # :start_transaction will get called when a transaction starts up
- # :after_call will write our response headers/metrics and clean up the thread
- def register_event_listeners
- NewRelic::Agent.logger.debug("Wiring up Cross Process monitoring to events after finished configuring")
-
- events = Agent.instance.events
- events.subscribe(:before_call) do |env|
- save_client_cross_process_id(env)
- end
-
- events.subscribe(:start_transaction) do |name|
- set_transaction_custom_parameters
- end
-
- events.subscribe(:after_call) do |env, (status_code, headers, body)|
- insert_response_header(env, headers)
- end
-
- events.subscribe(:notice_error) do |_, options|
- set_error_custom_parameters(options)
- end
- end
-
- # Because we aren't in the right spot when our transaction actually
- # starts, hold client_cross_process_id we get thread local until then.
- THREAD_ID_KEY = :newrelic_client_cross_process_id
-
- def save_client_cross_process_id(request_headers)
- if should_process_request(request_headers)
- NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = decoded_id(request_headers)
- end
- end
-
- def clear_client_cross_process_id
- NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = nil
- end
-
- def client_cross_process_id
- NewRelic::Agent::AgentThread.current[THREAD_ID_KEY]
- end
-
- def insert_response_header(request_headers, response_headers)
- unless client_cross_process_id.nil?
- timings = NewRelic::Agent::BrowserMonitoring.timings
- content_length = content_length_from_request(request_headers)
-
- set_response_headers(response_headers, timings, content_length)
- set_metrics(client_cross_process_id, timings)
-
- clear_client_cross_process_id
- end
- end
-
- def should_process_request(request_headers)
- return Agent.config[:'cross_process.enabled'] &&
- @cross_process_id &&
- trusts?(request_headers)
- end
-
- # Expects an ID of format "12#345", and will only accept that!
- def trusts?(request)
- id = decoded_id(request)
- split_id = id.match(/(\d+)#\d+/)
- return false if split_id.nil?
-
- @trusted_ids.include?(split_id.captures.first.to_i)
- end
-
- def set_response_headers(response_headers, timings, content_length)
- response_headers['X-NewRelic-App-Data'] = build_payload(timings, content_length)
- end
-
- def build_payload(timings, content_length)
-
- # FIXME The transaction name might not be properly encoded. use a json generator
- # For now we just handle quote characters by dropping them
- transaction_name = timings.transaction_name.gsub(/["']/, "")
-
- payload = %[["#{@cross_process_id}","#{transaction_name}",#{timings.queue_time_in_seconds},#{timings.app_time_in_seconds},#{content_length}] ]
- payload = obfuscate_with_key(payload)
- end
-
- def set_transaction_custom_parameters
- # We expect to get the before call to set the id (if we have it) before
- # this, and then write our custom parameter when the transaction starts
- NewRelic::Agent.add_custom_parameters(:client_cross_process_id => client_cross_process_id) unless client_cross_process_id.nil?
- end
-
- def set_error_custom_parameters(options)
- options[:client_cross_process_id] = client_cross_process_id unless client_cross_process_id.nil?
- end
-
- def set_metrics(id, timings)
- metric = NewRelic::Agent.instance.stats_engine.get_stats_no_scope("ClientApplication/#{id}/all")
- metric.record_data_point(timings.app_time_in_seconds)
- end
-
- def obfuscate_with_key(text)
- Base64.encode64(encode_with_key(text)).chomp
- end
-
- def decode_with_key(text)
- encode_with_key(Base64.decode64(text))
- end
-
- NEWRELIC_ID_HEADER_KEYS = %w{X-NewRelic-ID HTTP_X_NEWRELIC_ID X_NEWRELIC_ID}
- CONTENT_LENGTH_HEADER_KEYS = %w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH}
-
- def decoded_id(request)
- encoded_id = from_headers(request, NEWRELIC_ID_HEADER_KEYS)
- return "" if encoded_id.nil?
-
- decode_with_key(encoded_id)
- end
-
- def content_length_from_request(request)
- from_headers(request, CONTENT_LENGTH_HEADER_KEYS) || -1
- end
-
-
-
- private
-
- # Ruby 1.8.6 doesn't support the bytes method on strings.
- def get_bytes(value)
- return [] if value.nil?
-
- bytes = []
- value.each_byte do |b|
- bytes << b
- end
- bytes
- end
-
- def encode_with_key(text)
- key_bytes = @encoding_bytes
-
- encoded = ""
- index = 0
- text.each_byte{|byte|
- encoded.concat((byte ^ key_bytes[index % key_bytes.length].to_i))
- index+=1
- }
- encoded
- end
-
- def from_headers(request, try_keys)
- # For lookups, upcase all our keys on both sides just to be safe
- upcased_keys = try_keys.map{|k| k.upcase}
- upcased_keys.each do |header|
- found_key = request.keys.find { |k| k.upcase == header }
- return request[found_key] unless found_key.nil?
- end
- nil
- end
-
- end
- end
-end
diff --git a/lib/new_relic/agent/database.rb b/lib/new_relic/agent/database.rb
index 0414f86..131365d 100644
--- a/lib/new_relic/agent/database.rb
+++ b/lib/new_relic/agent/database.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'singleton'
module NewRelic
@@ -62,20 +66,32 @@ module NewRelic
def explain_sql(sql, connection_config)
return nil unless sql && connection_config
statement = sql.split(";\n")[0] # only explain the first
- explain_sql = explain_statement(statement, connection_config)
- return explain_sql || []
+ explain_plan = explain_statement(statement, connection_config)
+ return explain_plan || []
end
def explain_statement(statement, config)
- if is_select?(statement)
- handle_exception_in_explain do
- connection = get_connection(config)
- plan = nil
- if connection
- plan = process_resultset(connection.execute("EXPLAIN #{statement}"))
- end
- return plan
+ return unless is_select?(statement)
+
+ if statement[-3,3] == '...'
+ NewRelic::Agent.logger.debug('Unable to collect explain plan for truncated query.')
+ return
+ end
+
+ if parameterized?(statement)
+ NewRelic::Agent.logger.debug('Unable to collect explain plan for parameterized query.')
+ return
+ end
+
+ handle_exception_in_explain do
+ connection = get_connection(config)
+ plan = nil
+ if connection
+ start = Time.now
+ plan = process_resultset(connection.execute("EXPLAIN #{statement}"))
+ ::NewRelic::Agent.record_metric("Supportability/Database/execute_explain_plan", Time.now - start)
end
+ return plan
end
end
@@ -126,6 +142,10 @@ module NewRelic
(first_word.upcase == 'SELECT')
end
+ def parameterized?(statement)
+ Obfuscator.instance.obfuscate_single_quote_literals(statement) =~ /\$\d+/
+ end
+
class ConnectionManager
include Singleton
@@ -200,6 +220,10 @@ module NewRelic
end
def default_sql_obfuscator(sql)
+ if sql[-3,3] == '...'
+ return "Query too large (over 16k characters) to safely obfuscate"
+ end
+
stmt = sql.kind_of?(Statement) ? sql : Statement.new(sql)
adapter = stmt.adapter
obfuscated = remove_escaped_quotes(stmt)
diff --git a/lib/new_relic/agent/error_collector.rb b/lib/new_relic/agent/error_collector.rb
index 67d2aec..45ae2f0 100644
--- a/lib/new_relic/agent/error_collector.rb
+++ b/lib/new_relic/agent/error_collector.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
# This class collects errors from the parent application, storing
@@ -16,12 +20,15 @@ module NewRelic
# memory and data retention
MAX_ERROR_QUEUE_LENGTH = 20 unless defined? MAX_ERROR_QUEUE_LENGTH
+ # This ivar is used to tag exceptions that we've alreday seen, so that we
+ # don't double-count them.
+ EXCEPTION_TAG_IVAR = :@__new_relic_exception_tag
+
attr_accessor :errors
# Returns a new error collector
def initialize
@errors = []
- @seen_error_ids = []
# lookup of exception class names to ignore. Hash for fast access
@ignore = {}
@@ -37,7 +44,7 @@ module NewRelic
initialize_ignored_errors(ignore_errors)
end
end
-
+
def initialize_ignored_errors(ignore_errors)
@ignore.clear
ignore_errors = ignore_errors.split(",") if ignore_errors.is_a? String
@@ -93,13 +100,27 @@ module NewRelic
error && filtered_error?(error)
end
+ def seen?(exception)
+ exception.instance_variable_get(EXCEPTION_TAG_IVAR)
+ end
+
+ def tag_as_seen(exception)
+ exception.instance_variable_set(EXCEPTION_TAG_IVAR, true)
+ end
+
# Increments a statistic that tracks total error rate
# Be sure not to double-count same exception. This clears per harvest.
def increment_error_count!(exception)
- return if @seen_error_ids.include?(exception.object_id)
- @seen_error_ids << exception.object_id
-
- NewRelic::Agent.get_stats("Errors/all").increment_count
+ return if seen?(exception)
+ tag_as_seen(exception)
+
+ txn_info = NewRelic::Agent::TransactionInfo.get
+ metric_names = ["Errors/all"]
+ metric_names << "Errors/#{txn_info.transaction_name}" if txn_info.transaction_name_set?
+ stats_engine = NewRelic::Agent.agent.stats_engine
+ stats_engine.record_metrics(metric_names) do |stats|
+ stats.increment_count
+ end
end
# whether we should return early from the notice_error process
@@ -246,9 +267,6 @@ module NewRelic
errors = @errors
@errors = []
- # Only expect to re-see errors on same request, so clear on harvest
- @seen_error_ids = []
-
if unsent_errors && !unsent_errors.empty?
errors = unsent_errors + errors
end
diff --git a/lib/new_relic/agent/event_listener.rb b/lib/new_relic/agent/event_listener.rb
index 7799cf7..bafd0d4 100644
--- a/lib/new_relic/agent/event_listener.rb
+++ b/lib/new_relic/agent/event_listener.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic::Agent
# Basic mechanism for the agent instance to provide agent-wide eventing.
# It is intended to keep different pieces of the app decoupled from each other.
@@ -24,6 +28,10 @@ module NewRelic::Agent
NewRelic::Agent.logger.debug("Run-away event subscription on #{event}? Subscribed #{count}") if count > @runaway_threshold
end
+ def clear
+ @events.clear
+ end
+
def notify(event, *args)
return unless @events.has_key?(event)
diff --git a/lib/new_relic/agent/instrumentation.rb b/lib/new_relic/agent/instrumentation.rb
index f3b0d52..1bfb21c 100644
--- a/lib/new_relic/agent/instrumentation.rb
+++ b/lib/new_relic/agent/instrumentation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent'
module NewRelic
module Agent
diff --git a/lib/new_relic/agent/instrumentation/active_merchant.rb b/lib/new_relic/agent/instrumentation/active_merchant.rb
index 121da26..c918dd7 100644
--- a/lib/new_relic/agent/instrumentation/active_merchant.rb
+++ b/lib/new_relic/agent/instrumentation/active_merchant.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :active_merchant
diff --git a/lib/new_relic/agent/instrumentation/active_record.rb b/lib/new_relic/agent/instrumentation/active_record.rb
index 7c12743..4c8a54a 100644
--- a/lib/new_relic/agent/instrumentation/active_record.rb
+++ b/lib/new_relic/agent/instrumentation/active_record.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Instrumentation
@@ -11,16 +15,16 @@ module NewRelic
end
end
end
-
+
def log_with_newrelic_instrumentation(*args, &block)
if !NewRelic::Agent.is_execution_traced?
return log_without_newrelic_instrumentation(*args, &block)
end
-
+
sql, name, binds = args
metric = metric_for_name(NewRelic::Helper.correctly_encoded(name)) ||
metric_for_sql(NewRelic::Helper.correctly_encoded(sql))
-
+
if !metric
log_without_newrelic_instrumentation(*args, &block)
else
@@ -40,15 +44,15 @@ module NewRelic
end
end
end
-
+
def remote_service_metric
if @config && @config[:adapter]
type = @config[:adapter].sub(/\d*/, '')
host = @config[:host] || 'localhost'
"RemoteService/sql/#{type}/#{host}"
- end
+ end
end
-
+
def metric_for_name(name)
if name && (parts = name.split " ") && parts.size == 2
model = parts.first
@@ -81,7 +85,7 @@ module NewRelic
end
metric
end
-
+
def rollup_metrics_for(metric)
metrics = ["ActiveRecord/all"]
metrics << "ActiveRecord/#{$1}" if metric =~ /ActiveRecord\/\w+\/(\w+)/
@@ -94,19 +98,20 @@ end
DependencyDetection.defer do
@name = :active_record
-
+
depends_on do
- defined?(ActiveRecord) && defined?(ActiveRecord::Base)
+ defined?(::ActiveRecord) && (!defined?(::ActiveRecord::VERSION) ||
+ ::ActiveRecord::VERSION::MAJOR.to_i <= 3)
end
depends_on do
!NewRelic::Agent.config[:disable_activerecord_instrumentation]
end
-
+
executes do
::NewRelic::Agent.logger.info 'Installing ActiveRecord instrumentation'
end
-
+
executes do
if defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 3
Rails.configuration.after_initialize do
@@ -121,7 +126,7 @@ DependencyDetection.defer do
ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
include ::NewRelic::Agent::Instrumentation::ActiveRecord
end
-
+
ActiveRecord::Base.class_eval do
class << self
add_method_tracer(:find_by_sql, 'ActiveRecord/#{self.name}/find_by_sql',
@@ -129,6 +134,6 @@ DependencyDetection.defer do
add_method_tracer(:transaction, 'ActiveRecord/#{self.name}/transaction',
:metric => false)
end
- end
+ end
end
end
diff --git a/lib/new_relic/agent/instrumentation/active_record_helper.rb b/lib/new_relic/agent/instrumentation/active_record_helper.rb
new file mode 100644
index 0000000..c945977
--- /dev/null
+++ b/lib/new_relic/agent/instrumentation/active_record_helper.rb
@@ -0,0 +1,72 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+module NewRelic
+ module Agent
+ module Instrumentation
+ module ActiveRecordHelper
+ module_function
+
+ def metric_for_name(name)
+ return unless name
+ parts = name.split(' ')
+ if parts.size == 2
+ model = parts.first
+ operation = parts.last.downcase
+ case operation
+ when 'load', 'count', 'exists'
+ op_name = 'find'
+ when 'indexes', 'columns'
+ op_name = nil # fall back to DirectSQL
+ when 'destroy', 'find', 'save', 'create'
+ op_name = operation
+ when 'update'
+ op_name = 'save'
+ else
+ if model == 'Join'
+ op_name = operation
+ end
+ end
+ "ActiveRecord/#{model}/#{op_name}" if op_name
+ end
+ end
+
+ def metric_for_sql(sql)
+ metric = NewRelic::Agent::Instrumentation::MetricFrame.database_metric_name
+ if metric.nil?
+ if sql =~ /^(select|update|insert|delete|show)/i
+ # Could not determine the model/operation so let's find a better
+ # metric. If it doesn't match the regex, it's probably a show
+ # command or some DDL which we'll ignore.
+ metric = "Database/SQL/#{$1.downcase}"
+ else
+ metric = "Database/SQL/other"
+ end
+ end
+ metric
+ end
+
+ # Given a metric name such as "ActiveRecord/model/action" this
+ # returns an array of rollup metrics:
+ # [ "ActiveRecord/all", "ActiveRecord/action" ]
+ # If the metric name is in the form of "ActiveRecord/action"
+ # this returns merely: [ "ActiveRecord/all" ]
+ def rollup_metrics_for(metric)
+ metrics = ["ActiveRecord/all"]
+ metrics << "ActiveRecord/#{$1}" if metric =~ /ActiveRecord\/[\w|\:]+\/(\w+)/
+ metrics
+ end
+
+ # Given a database adapter name and a database server host
+ # this returns a metric name in the form:
+ # "RemoteService/sql/adapter/host"
+ # Host defaults to "localhost".
+ def remote_service_metric(adapter, host)
+ host ||= 'localhost'
+ type = adapter.sub(/\d*/, '')
+ "RemoteService/sql/#{type}/#{host}"
+ end
+ end
+ end
+ end
+end
diff --git a/lib/new_relic/agent/instrumentation/acts_as_solr.rb b/lib/new_relic/agent/instrumentation/acts_as_solr.rb
index a90fa16..5bf21bc 100644
--- a/lib/new_relic/agent/instrumentation/acts_as_solr.rb
+++ b/lib/new_relic/agent/instrumentation/acts_as_solr.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Instrumentation
module ActsAsSolrInstrumentation
diff --git a/lib/new_relic/agent/instrumentation/authlogic.rb b/lib/new_relic/agent/instrumentation/authlogic.rb
index 2d1b9dd..c9f7338 100644
--- a/lib/new_relic/agent/instrumentation/authlogic.rb
+++ b/lib/new_relic/agent/instrumentation/authlogic.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :authlogic
diff --git a/lib/new_relic/agent/instrumentation/browser_monitoring_timings.rb b/lib/new_relic/agent/instrumentation/browser_monitoring_timings.rb
index 0f70f6e..af22229 100644
--- a/lib/new_relic/agent/instrumentation/browser_monitoring_timings.rb
+++ b/lib/new_relic/agent/instrumentation/browser_monitoring_timings.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Instrumentation
diff --git a/lib/new_relic/agent/instrumentation/controller_instrumentation.rb b/lib/new_relic/agent/instrumentation/controller_instrumentation.rb
index 1c02a95..0841a47 100644
--- a/lib/new_relic/agent/instrumentation/controller_instrumentation.rb
+++ b/lib/new_relic/agent/instrumentation/controller_instrumentation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/instrumentation/metric_frame'
require 'new_relic/agent/instrumentation/queue_time'
module NewRelic
@@ -254,7 +258,7 @@ module NewRelic
return perform_action_without_newrelic_trace(*args)
end
end
-
+
control = NewRelic::Control.instance
return perform_action_with_newrelic_profile(args, &block) if control.profiling?
@@ -286,7 +290,7 @@ module NewRelic
# Clear the thread local when finished to ensure it only gets called once.
frame_data.record_apdex unless ignore_apdex?
frame_data.pop
-
+
NewRelic::Agent::TransactionInfo.get.ignore_end_user = true if ignore_enduser?
end
end
@@ -314,14 +318,14 @@ module NewRelic
def newrelic_request_headers
self.respond_to?(:request) && self.request.respond_to?(:headers) && self.request.headers
end
-
+
# overrideable method to determine whether to trace an action
# or not - you may override this in your controller and supply
# your own logic for ignoring transactions.
def do_not_trace?
_is_filtered?('do_not_trace')
end
-
+
# overrideable method to determine whether to trace an action
# for purposes of apdex measurement - you can use this to
# ignore things like api calls or other fast non-user-facing
@@ -329,7 +333,7 @@ module NewRelic
def ignore_apdex?
_is_filtered?('ignore_apdex')
end
-
+
def ignore_enduser?
_is_filtered?('ignore_enduser')
end
@@ -360,6 +364,45 @@ module NewRelic
frame_data.pop
end
+ def transaction_name(options={})
+ name = "#{category_name(options)}/#{path_name(options)}"
+ NewRelic::Agent.instance.transaction_rules.rename(name)
+ end
+
+ def category_name(options)
+ case options[:category]
+ when :controller, nil then 'Controller'
+ when :task then 'OtherTransaction/Background' # 'Task'
+ when :rack then 'Controller/Rack' #'WebTransaction/Rack'
+ when :uri then 'Controller' #'WebTransaction/Uri'
+ when :sinatra then 'Controller/Sinatra' #'WebTransaction/Uri'
+ # for internal use only
+ else options[:category].to_s
+ end
+ end
+
+ def path_name(options)
+ if options.any?
+ options[:path] || path_class_and_action(options)
+ else
+ newrelic_metric_path
+ end
+ end
+
+ def path_class_and_action(options)
+ metric_class = options[:class_name]
+
+ if !metric_class
+ if (self.is_a?(Class) || self.is_a?(Module))
+ metric_class = self.name
+ else
+ metric_class = self.class.name
+ end
+ end
+
+ [ metric_class, options[:name] ].compact.join('/')
+ end
+
# Write a metric frame onto a thread local if there isn't already one there.
# If there is one, just update it.
def _push_metric_frame(args) # :nodoc:
@@ -367,49 +410,31 @@ module NewRelic
frame_data.apdex_start ||= _detect_upstream_wait(frame_data.start)
_record_queue_length
+
# If a block was passed in, then the arguments represent options for the instrumentation,
# not app method arguments.
+ options = {}
if args.any?
if args.last.is_a?(Hash)
- options = args.last
+ options = args.pop
frame_data.force_flag = options[:force]
frame_data.request = options[:request] if options[:request]
end
- category, path, available_params = _convert_args_to_path(args)
+ available_params = options[:params] || {}
+ options[:name] ||= args.first
else
- category = 'Controller'
- path = newrelic_metric_path
available_params = self.respond_to?(:params) ? self.params : {}
end
frame_data.request ||= self.request if self.respond_to? :request
- transaction_name = category + '/' + path
- frame_data.push(transaction_name)
- NewRelic::Agent::TransactionInfo.get.transaction_name = transaction_name
+
+ txn_name = transaction_name(options || {})
+
+ frame_data.push(txn_name)
+ NewRelic::Agent::TransactionInfo.get.transaction_name = txn_name
frame_data.filtered_params = (respond_to? :filter_parameters) ? filter_parameters(available_params) : available_params
frame_data
end
- def _convert_args_to_path(args)
- options = args.last.is_a?(Hash) ? args.pop : {}
- params = options[:params] || {}
- category = case options[:category]
- when :controller, nil then 'Controller'
- when :task then 'OtherTransaction/Background' # 'Task'
- when :rack then 'Controller/Rack' #'WebTransaction/Rack'
- when :uri then 'Controller' #'WebTransaction/Uri'
- when :sinatra then 'Controller/Sinatra' #'WebTransaction/Uri'
- # for internal use only
- else options[:category].to_s
- end
- unless path = options[:path]
- action = options[:name] || args.first
- metric_class = options[:class_name] || ((self.is_a?(Class)||self.is_a?(Module)) ? self.name : self.class.name)
- path = metric_class
- path += ('/' + action) if action
- end
- [category, path, params]
- end
-
# Filter out a request if it matches one of our parameters for
# ignoring it - the key is either 'do_not_trace' or 'ignore_apdex'
def _is_filtered?(key)
@@ -434,7 +459,7 @@ module NewRelic
# Always subtrace 1 for the active mongrel
queue_depth = [mongrel.workers.list.length.to_i - 1, 0].max rescue nil
end
- NewRelic::Agent.agent.stats_engine.get_stats_no_scope('Mongrel/Queue Length').trace_call(queue_depth) if queue_depth
+ NewRelic::Agent.record_metric('Mongrel/Queue Length', queue_depth) if queue_depth
end
end
@@ -451,13 +476,6 @@ module NewRelic
::NewRelic::Agent.logger.error("Error detecting upstream wait time:", e)
now
end
-
- # returns the NewRelic::MethodTraceStats object associated
- # with the dispatcher time measurement
- def _dispatch_stat
- NewRelic::Agent.agent.stats_engine.get_stats_no_scope 'HttpDispatcher'
- end
-
end
end
end
diff --git a/lib/new_relic/agent/instrumentation/data_mapper.rb b/lib/new_relic/agent/instrumentation/data_mapper.rb
index 01fdb1d..8b1dd8a 100644
--- a/lib/new_relic/agent/instrumentation/data_mapper.rb
+++ b/lib/new_relic/agent/instrumentation/data_mapper.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
## NewRelic instrumentation for DataMapper
#
# Instrumenting DM has different key challenges versus AR:
@@ -205,7 +209,7 @@ module NewRelic
# Record query duration associated with each of the desired metrics.
metrics = [ "ActiveRecord/#{operation}", 'ActiveRecord/all' ]
metrics.each do |metric|
- NewRelic::Agent.instance.stats_engine.get_stats_no_scope(metric).trace_call(duration)
+ NewRelic::Agent.record_metric(metric, duration)
end
ensure
super
diff --git a/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb b/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb
index 093f3a5..dd9c404 100644
--- a/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb
+++ b/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/instrumentation/controller_instrumentation'
DependencyDetection.defer do
diff --git a/lib/new_relic/agent/instrumentation/memcache.rb b/lib/new_relic/agent/instrumentation/memcache.rb
index a106832..1bc9fb8 100644
--- a/lib/new_relic/agent/instrumentation/memcache.rb
+++ b/lib/new_relic/agent/instrumentation/memcache.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# NOTE there are multiple implementations of the MemCache client in Ruby,
# each with slightly different API's and semantics.
# See:
diff --git a/lib/new_relic/agent/instrumentation/merb/controller.rb b/lib/new_relic/agent/instrumentation/merb/controller.rb
index 351a192..3765438 100644
--- a/lib/new_relic/agent/instrumentation/merb/controller.rb
+++ b/lib/new_relic/agent/instrumentation/merb/controller.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'set'
DependencyDetection.defer do
diff --git a/lib/new_relic/agent/instrumentation/merb/errors.rb b/lib/new_relic/agent/instrumentation/merb/errors.rb
index 07a4a7c..01bdcbf 100644
--- a/lib/new_relic/agent/instrumentation/merb/errors.rb
+++ b/lib/new_relic/agent/instrumentation/merb/errors.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :merb_error
diff --git a/lib/new_relic/agent/instrumentation/metric_frame.rb b/lib/new_relic/agent/instrumentation/metric_frame.rb
index 9841876..17a09b5 100644
--- a/lib/new_relic/agent/instrumentation/metric_frame.rb
+++ b/lib/new_relic/agent/instrumentation/metric_frame.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/instrumentation/metric_frame/pop'
# A struct holding the information required to measure a controller
@@ -300,17 +304,19 @@ module NewRelic
end
def self.record_apdex(current_metric, action_duration, total_duration, is_error)
- summary_stat = agent.stats_engine.get_custom_stats("Apdex", NewRelic::ApdexStats)
- controller_stat = agent.stats_engine.get_custom_stats(current_metric.apdex_metric_path, NewRelic::ApdexStats)
- update_apdex(summary_stat, total_duration, is_error)
- update_apdex(controller_stat, action_duration, is_error)
+ agent.stats_engine.record_metrics('Apdex') do |stat|
+ update_apdex(stat, total_duration, is_error)
+ end
+ agent.stats_engine.record_metrics(current_metric.apdex_metric_path) do |stat|
+ update_apdex(stat, action_duration, is_error)
+ end
end
# Record an apdex value for the given stat. when `failed`
# the apdex should be recorded as a failure regardless of duration.
def self.update_apdex(stat, duration, failed)
+ apdex_t = TransactionInfo.get.apdex_t
duration = duration.to_f
- apdex_t = Agent.config[:apdex_t]
case
when failed
stat.record_apdex_f
@@ -321,6 +327,10 @@ module NewRelic
else
stat.record_apdex_f
end
+ # Apdex min and max values should be initialized to the
+ # current apdex_t
+ stat.min_call_time = apdex_t
+ stat.max_call_time = apdex_t
end
private
diff --git a/lib/new_relic/agent/instrumentation/metric_frame/pop.rb b/lib/new_relic/agent/instrumentation/metric_frame/pop.rb
index ef5c6e2..df5a0df 100644
--- a/lib/new_relic/agent/instrumentation/metric_frame/pop.rb
+++ b/lib/new_relic/agent/instrumentation/metric_frame/pop.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/instrumentation'
module NewRelic
module Agent
@@ -43,7 +47,7 @@ module NewRelic
# process doesn't work on JRuby. See the cpu_sampler.rb file
# to understand where cpu is recorded for non-jruby processes
def record_jruby_cpu_burn(burn)
- NewRelic::Agent.get_stats_no_scope(NewRelic::Metrics::USER_TIME).record_data_point(burn)
+ NewRelic::Agent.record_metric(NewRelic::Metrics::USER_TIME, burn)
end
def cpu_burn
diff --git a/lib/new_relic/agent/instrumentation/net.rb b/lib/new_relic/agent/instrumentation/net.rb
index 88d227a..89f4541 100644
--- a/lib/new_relic/agent/instrumentation/net.rb
+++ b/lib/new_relic/agent/instrumentation/net.rb
@@ -1,27 +1,31 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+
DependencyDetection.defer do
@name = :net
depends_on do
defined?(Net) && defined?(Net::HTTP)
end
-
+
executes do
::NewRelic::Agent.logger.info 'Installing Net instrumentation'
+ require 'new_relic/agent/cross_app_tracing'
end
-
+
executes do
- Net::HTTP.class_eval do
- def request_with_newrelic_trace(*args, &block)
- metrics = ["External/#{@address}/Net::HTTP/#{args[0].method}", "External/#{@address}/all", "External/all"]
- if NewRelic::Agent::Instrumentation::MetricFrame.recording_web_transaction?
- metrics << "External/allWeb"
- else
- metrics << "External/allOther"
- end
- self.class.trace_execution_scoped metrics do
- request_without_newrelic_trace(*args, &block)
+ class Net::HTTP
+
+ # Instrument outgoing HTTP requests and fire associated events back
+ # into the Agent.
+ def request_with_newrelic_trace(request, *args, &block)
+ NewRelic::Agent::CrossAppTracing.trace_http_request( self, request ) do
+ request_without_newrelic_trace( request, *args, &block )
end
end
+
alias request_without_newrelic_trace request
alias request request_with_newrelic_trace
end
diff --git a/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb b/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb
index c2a1955..b5df524 100644
--- a/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb
+++ b/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :passenger
diff --git a/lib/new_relic/agent/instrumentation/queue_time.rb b/lib/new_relic/agent/instrumentation/queue_time.rb
index b34daaf..a021a06 100644
--- a/lib/new_relic/agent/instrumentation/queue_time.rb
+++ b/lib/new_relic/agent/instrumentation/queue_time.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Instrumentation
@@ -36,8 +40,8 @@ module NewRelic
end
def record_frontend_metrics(start_time, now=Time.now)
- NewRelic::Agent.instance.stats_engine.get_stats(ALL_QUEUE_METRIC) \
- .record_data_point((now - start_time).to_f)
+ NewRelic::Agent.instance.stats_engine.record_metrics(
+ ALL_QUEUE_METRIC, (now - start_time).to_f, :scoped => true)
end
def timestamp_string_from_header_value(value)
diff --git a/lib/new_relic/agent/instrumentation/rack.rb b/lib/new_relic/agent/instrumentation/rack.rb
index cb50e54..7450649 100644
--- a/lib/new_relic/agent/instrumentation/rack.rb
+++ b/lib/new_relic/agent/instrumentation/rack.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/instrumentation/controller_instrumentation'
module NewRelic
diff --git a/lib/new_relic/agent/instrumentation/rails/action_controller.rb b/lib/new_relic/agent/instrumentation/rails/action_controller.rb
index 461c457..2ac224f 100644
--- a/lib/new_relic/agent/instrumentation/rails/action_controller.rb
+++ b/lib/new_relic/agent/instrumentation/rails/action_controller.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :rails21_view
diff --git a/lib/new_relic/agent/instrumentation/rails/action_web_service.rb b/lib/new_relic/agent/instrumentation/rails/action_web_service.rb
index 233ba5d..de95c5a 100644
--- a/lib/new_relic/agent/instrumentation/rails/action_web_service.rb
+++ b/lib/new_relic/agent/instrumentation/rails/action_web_service.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :rails_action_web_service
diff --git a/lib/new_relic/agent/instrumentation/rails/errors.rb b/lib/new_relic/agent/instrumentation/rails/errors.rb
index c8dc8e7..909bf5f 100644
--- a/lib/new_relic/agent/instrumentation/rails/errors.rb
+++ b/lib/new_relic/agent/instrumentation/rails/errors.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :rails2_error
diff --git a/lib/new_relic/agent/instrumentation/rails3/action_controller.rb b/lib/new_relic/agent/instrumentation/rails3/action_controller.rb
index 8a8079e..61f45e1 100644
--- a/lib/new_relic/agent/instrumentation/rails3/action_controller.rb
+++ b/lib/new_relic/agent/instrumentation/rails3/action_controller.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Instrumentation
diff --git a/lib/new_relic/agent/instrumentation/rails3/errors.rb b/lib/new_relic/agent/instrumentation/rails3/errors.rb
index 7300cdd..0dd7ea6 100644
--- a/lib/new_relic/agent/instrumentation/rails3/errors.rb
+++ b/lib/new_relic/agent/instrumentation/rails3/errors.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
module Instrumentation
diff --git a/lib/new_relic/agent/instrumentation/rails4/action_controller.rb b/lib/new_relic/agent/instrumentation/rails4/action_controller.rb
new file mode 100644
index 0000000..cf3ed9d
--- /dev/null
+++ b/lib/new_relic/agent/instrumentation/rails4/action_controller.rb
@@ -0,0 +1,69 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+module NewRelic
+ module Agent
+ module Instrumentation
+ module Rails4
+ module ActionController
+ def self.newrelic_write_attr(attr_name, value) # :nodoc:
+ write_inheritable_attribute(attr_name, value)
+ end
+
+ def self.newrelic_read_attr(attr_name) # :nodoc:
+ read_inheritable_attribute(attr_name)
+ 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
+
+ def process_action(*args)
+ # skip instrumentation if we are in an ignored action
+ if _is_filtered?('do_not_trace')
+ NewRelic::Agent.disable_all_tracing do
+ return super
+ end
+ end
+
+ perform_action_with_newrelic_trace(:category => :controller, :name => self.action_name, :path => newrelic_metric_path, :params => request.filtered_parameters, :class_name => self.class.name) do
+ super
+ end
+ end
+
+ end
+ end
+ end
+ end
+end
+
+DependencyDetection.defer do
+ @name = :rails4_controller
+
+ depends_on do
+ defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 4
+ end
+
+ depends_on do
+ defined?(ActionController) && defined?(ActionController::Base)
+ end
+
+ executes do
+ ::NewRelic::Agent.logger.info 'Installing Rails 4 Controller instrumentation'
+ end
+
+ executes do
+ class ActionController::Base
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
+ include NewRelic::Agent::Instrumentation::Rails4::ActionController
+ end
+ end
+end
diff --git a/lib/new_relic/agent/instrumentation/rails4/action_view.rb b/lib/new_relic/agent/instrumentation/rails4/action_view.rb
new file mode 100644
index 0000000..1582276
--- /dev/null
+++ b/lib/new_relic/agent/instrumentation/rails4/action_view.rb
@@ -0,0 +1,138 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+# Listen for ActiveSupport::Notifications events for ActionView render
+# events. Write metric data and transaction trace segments for each event.
+module NewRelic
+ module Agent
+ module Instrumentation
+ class ActionViewSubscriber
+ def initialize
+ @queue_key = ['NewRelic', self.class.name, object_id].join('-')
+ end
+
+ def self.subscribe
+ if !subscribed?
+ ActiveSupport::Notifications.subscribe(/render_.+\.action_view$/,
+ new)
+ end
+ end
+
+ def self.subscribed?
+ # TODO: need to talk to Rails core about an API for this,
+ # rather than digging through Listener ivars
+ ActiveSupport::Notifications.notifier.instance_variable_get(:@subscribers) \
+ .find{|s| s.instance_variable_get(:@delegate).class == self }
+ end
+
+ def start(name, id, payload)
+ event = RenderEvent.new(name, Time.now, nil, id, payload)
+ parent = event_stack[id].last
+ event.parent = parent
+ parent << event if parent
+ event_stack[id].push event
+
+ if NewRelic::Agent.is_execution_traced? && event.recordable?
+ event.scope = NewRelic::Agent.instance.stats_engine \
+ .push_scope(event.metric_name, event.time)
+ end
+ end
+
+ def finish(name, id, payload)
+ event = event_stack[id].pop
+ event.end = Time.now
+
+ if NewRelic::Agent.is_execution_traced? && event.recordable?
+ record_metrics(event)
+ NewRelic::Agent.instance.stats_engine \
+ .pop_scope(event.scope, event.duration, event.end)
+ end
+ end
+
+ def record_metrics(event)
+ NewRelic::Agent.instance.stats_engine \
+ .record_metrics(event.metric_name,
+ Helper.milliseconds_to_seconds(event.duration),
+ :scoped => true)
+ end
+
+ def event_stack
+ Thread.current[@queue_key] ||= Hash.new {|h,id| h[id] = [] }
+ end
+
+ if defined?(ActiveSupport::Notifications::Event)
+ class RenderEvent < ActiveSupport::Notifications::Event
+ attr_accessor :parent, :scope
+
+ # Nearly every "render_blah.action_view" event has a child
+ # in the form of "!render_blah.action_view". The children
+ # are the ones we want to record. There are a couple
+ # special cases of events without children.
+ def recordable?
+ name[0] == '!' ||
+ metric_name == 'View/text template/Rendering' ||
+ metric_name == 'View/(unknown)/Partial'
+ end
+
+ def metric_name
+ if parent && (payload[:virtual_path] ||
+ (parent.payload[:identifier] =~ /template$/))
+ return parent.metric_name
+ elsif payload[:virtual_path]
+ identifier = payload[:virtual_path]
+ else
+ identifier = payload[:identifier]
+ end
+
+ # memoize
+ @metric_name ||= "View/#{metric_path(identifier)}/#{metric_action(name)}"
+ @metric_name
+ end
+
+ def metric_path(identifier)
+ if identifier == nil
+ 'file'
+ elsif identifier =~ /template$/
+ identifier
+ elsif (parts = identifier.split('/')).size > 1
+ parts[-2..-1].join('/')
+ else
+ '(unknown)'
+ end
+ end
+
+ def metric_action(name)
+ case name
+ when /render_template.action_view$/ then 'Rendering'
+ when 'render_partial.action_view' then 'Partial'
+ when 'render_collection.action_view' then 'Partial'
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
+
+DependencyDetection.defer do
+ @name = :rails4_view
+
+ depends_on do
+ defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 4
+ end
+
+ depends_on do
+ !NewRelic::Agent.config[:disable_view_instrumentation] &&
+ !NewRelic::Agent::Instrumentation::ActionViewSubscriber.subscribed?
+ end
+
+ executes do
+ ::NewRelic::Agent.logger.info 'Installing Rails 4 view instrumentation'
+ end
+
+ executes do
+ NewRelic::Agent::Instrumentation::ActionViewSubscriber.subscribe
+ end
+end
diff --git a/lib/new_relic/agent/instrumentation/rails4/active_record.rb b/lib/new_relic/agent/instrumentation/rails4/active_record.rb
new file mode 100644
index 0000000..2c7e37c
--- /dev/null
+++ b/lib/new_relic/agent/instrumentation/rails4/active_record.rb
@@ -0,0 +1,108 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+require 'new_relic/agent/instrumentation/active_record_helper'
+
+# Listen for ActiveSupport::Notifications events for ActiveRecord query
+# events. Write metric data, transaction trace segments and slow sql
+# nodes for each event.
+module NewRelic
+ module Agent
+ module Instrumentation
+ class ActiveRecordSubscriber
+ include NewRelic::Agent::Instrumentation
+
+ def self.subscribed?
+ # TODO: need to talk to Rails core about an API for this,
+ # rather than digging through Listener ivars
+ ActiveSupport::Notifications.notifier.listeners_for('sql.active_record') \
+ .find{|l| l.instance_variable_get(:@delegate).class == self }
+ end
+
+ def call(*args)
+ return unless NewRelic::Agent.is_execution_traced?
+
+ event = ActiveSupport::Notifications::Event.new(*args)
+ record_metrics(event)
+ notice_sql(event)
+ end
+
+ def notice_sql(event)
+ config = active_record_config_for_event(event)
+ metric = base_metric(event)
+
+ # enter transaction trace segment
+ scope = NewRelic::Agent.instance.stats_engine.push_scope(metric, event.time)
+
+ NewRelic::Agent.instance.transaction_sampler \
+ .notice_sql(event.payload[:sql], config,
+ Helper.milliseconds_to_seconds(event.duration))
+
+ NewRelic::Agent.instance.sql_sampler \
+ .notice_sql(event.payload[:sql], metric, config,
+ Helper.milliseconds_to_seconds(event.duration))
+
+ # exit transaction trace segment
+ NewRelic::Agent.instance.stats_engine.pop_scope(scope, event.duration, event.end)
+ end
+
+ def record_metrics(event)
+ base = base_metric(event)
+ NewRelic::Agent.instance.stats_engine.record_metrics(base,
+ Helper.milliseconds_to_seconds(event.duration),
+ :scoped => true)
+
+ other_metrics = ActiveRecordHelper.rollup_metrics_for(base)
+ if config = active_record_config_for_event(event)
+ other_metrics << ActiveRecordHelper.remote_service_metric(config[:adapter], config[:host])
+ end
+
+ other_metrics.compact.each do |metric_name|
+ NewRelic::Agent.instance.stats_engine.record_metrics(metric_name,
+ Helper.milliseconds_to_seconds(event.duration),
+ :scoped => false)
+ end
+ end
+
+ def base_metric(event)
+ ActiveRecordHelper.metric_for_name(event.payload[:name]) ||
+ ActiveRecordHelper.metric_for_sql(NewRelic::Helper.correctly_encoded(event.payload[:sql]))
+ end
+
+ def active_record_config_for_event(event)
+ return unless event.payload[:connection_id]
+
+ # TODO: This will not work for JRuby and in any case we want
+ # this to be part of the event meta data so it doesn't have
+ # to be dug out of an ivar.
+ connection = ObjectSpace._id2ref(event.payload[:connection_id])
+ connection.instance_variable_get(:@config) if connection
+ end
+ end
+ end
+ end
+end
+
+DependencyDetection.defer do
+ @name = :active_record
+
+ depends_on do
+ defined?(::ActiveRecord) && defined?(::ActiveRecord::Base) &&
+ defined?(::ActiveRecord::VERSION) &&
+ ::ActiveRecord::VERSION::MAJOR.to_i >= 4
+ end
+
+ depends_on do
+ !NewRelic::Agent.config[:disable_activerecord_instrumentation] &&
+ !NewRelic::Agent::Instrumentation::ActiveRecordSubscriber.subscribed?
+ end
+
+ executes do
+ ::NewRelic::Agent.logger.info 'Installing ActiveRecord instrumentation'
+ end
+
+ executes do
+ ActiveSupport::Notifications.subscribe('sql.active_record',
+ NewRelic::Agent::Instrumentation::ActiveRecordSubscriber.new)
+ end
+end
diff --git a/lib/new_relic/agent/instrumentation/rails4/errors.rb b/lib/new_relic/agent/instrumentation/rails4/errors.rb
new file mode 100644
index 0000000..07e9eb9
--- /dev/null
+++ b/lib/new_relic/agent/instrumentation/rails4/errors.rb
@@ -0,0 +1,45 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+module NewRelic
+ module Agent
+ module Instrumentation
+ module Rails4
+ module Errors
+ def newrelic_notice_error(exception, custom_params = {})
+ filtered_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
+ filtered_params.merge!(custom_params)
+ NewRelic::Agent::Instrumentation::MetricFrame.notice_error( \
+ exception, \
+ :request => request, \
+ :metric => newrelic_metric_path, \
+ :custom_params => filtered_params)
+ end
+ end
+ end
+ end
+ end
+end
+
+DependencyDetection.defer do
+ @name = :rails4_error
+
+ depends_on do
+ defined?(::Rails) && ::Rails.respond_to?(:version) && ::Rails.version.to_i == 4
+ end
+
+ depends_on do
+ defined?(ActionController) && defined?(ActionController::Base)
+ end
+
+ executes do
+ ::NewRelic::Agent.logger.info 'Installing Rails4 Error instrumentation'
+ end
+
+ executes do
+ class ActionController::Base
+ include NewRelic::Agent::Instrumentation::Rails4::Errors
+ end
+ end
+end
diff --git a/lib/new_relic/agent/instrumentation/rainbows_instrumentation.rb b/lib/new_relic/agent/instrumentation/rainbows_instrumentation.rb
new file mode 100644
index 0000000..cc172c0
--- /dev/null
+++ b/lib/new_relic/agent/instrumentation/rainbows_instrumentation.rb
@@ -0,0 +1,26 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+DependencyDetection.defer do
+ @name = :rainbows
+
+ depends_on do
+ defined?(::Rainbows) && defined?(::Rainbows::HttpServer)
+ end
+
+ executes do
+ ::NewRelic::Agent.logger.info 'Installing Rainbows instrumentation'
+ ::NewRelic::Agent.logger.info 'Detected Rainbows, please see additional documentation: https://newrelic.com/docs/troubleshooting/im-using-unicorn-and-i-dont-see-any-data'
+ end
+
+ executes do
+ Rainbows::HttpServer.class_eval do
+ old_worker_loop = instance_method(:worker_loop)
+ define_method(:worker_loop) do |worker|
+ NewRelic::Agent.after_fork(:force_reconnect => true)
+ old_worker_loop.bind(self).call(worker)
+ end
+ end
+ end
+end
diff --git a/lib/new_relic/agent/instrumentation/resque.rb b/lib/new_relic/agent/instrumentation/resque.rb
index a406383..2337ace 100644
--- a/lib/new_relic/agent/instrumentation/resque.rb
+++ b/lib/new_relic/agent/instrumentation/resque.rb
@@ -1,6 +1,10 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :resque
-
+
depends_on do
defined?(::Resque::Job) && !NewRelic::Agent.config[:disable_resque] &&
!NewRelic::LanguageSupport.using_version?('1.9.1')
@@ -9,19 +13,19 @@ DependencyDetection.defer do
executes do
::NewRelic::Agent.logger.info 'Installing Resque instrumentation'
end
-
+
executes do
# == Resque Instrumentation
#
# Installs a hook to ensure the agent starts manually when the worker
# starts and also adds the tracer to the process method which executes
# in the forked task.
-
+
module Resque
module Plugins
module NewRelicInstrumentation
include NewRelic::Agent::Instrumentation::ControllerInstrumentation
-
+
def around_perform_with_monitoring(*args)
begin
perform_action_with_newrelic_trace(:name => 'perform',
@@ -36,7 +40,7 @@ DependencyDetection.defer do
end
end
end
-
+
module NewRelic
module Agent
module Instrumentation
@@ -51,13 +55,13 @@ DependencyDetection.defer do
end
end
end
-
+
::Resque::Job.class_eval do
def self.new(*args)
super(*args).extend NewRelic::Agent::Instrumentation::ResqueInstrumentationInstaller
end
end
-
+
if NewRelic::LanguageSupport.can_fork?
::Resque.before_first_fork do
NewRelic::Agent.manual_start(:dispatcher => :resque,
@@ -65,17 +69,17 @@ DependencyDetection.defer do
:start_channel_listener => true,
:report_instance_busy => false)
end
-
+
::Resque.before_fork do |job|
NewRelic::Agent.register_report_channel(job.object_id)
end
-
+
::Resque.after_fork do |job|
NewRelic::Agent.after_fork(:report_to_channel => job.object_id)
end
end
end
-end
+end
# call this now so it is memoized before potentially forking worker processes
NewRelic::LanguageSupport.can_fork?
diff --git a/lib/new_relic/agent/instrumentation/sidekiq.rb b/lib/new_relic/agent/instrumentation/sidekiq.rb
new file mode 100644
index 0000000..a1dcf13
--- /dev/null
+++ b/lib/new_relic/agent/instrumentation/sidekiq.rb
@@ -0,0 +1,36 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+DependencyDetection.defer do
+ @name = :sidekiq
+
+ depends_on do
+ defined?(::Sidekiq) && !NewRelic::Agent.config[:disable_sidekiq]
+ end
+
+ executes do
+ ::NewRelic::Agent.logger.info 'Installing Sidekiq instrumentation'
+ end
+
+ executes do
+ class NewRelic::SidekiqInstrumentation
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
+
+ def call(worker, msg, queue)
+ perform_action_with_newrelic_trace(
+ :name => 'perform',
+ :class_name => msg['class'],
+ :category => 'OtherTransaction/SidekiqJob') do
+ yield
+ end
+ end
+ end
+
+ Sidekiq.configure_server do |config|
+ config.server_middleware do |chain|
+ chain.add NewRelic::SidekiqInstrumentation
+ end
+ end
+ end
+end
diff --git a/lib/new_relic/agent/instrumentation/sinatra.rb b/lib/new_relic/agent/instrumentation/sinatra.rb
index b031781..dde9444 100644
--- a/lib/new_relic/agent/instrumentation/sinatra.rb
+++ b/lib/new_relic/agent/instrumentation/sinatra.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/instrumentation/controller_instrumentation'
DependencyDetection.defer do
@@ -37,26 +41,33 @@ module NewRelic
include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
def dispatch_with_newrelic
+ # We're trying to determine the transaction name via Sinatra's
+ # process_route, but calling it here misses Sinatra's normal error handling.
+ #
+ # Relies on transaction_name to always safely return a value for us
txn_name = NewRelic.transaction_name(self.class.routes, @request) do |pattern, keys, conditions|
- process_route(pattern, keys, conditions) do
+ result = process_route(pattern, keys, conditions) do
pattern.source
end
+ result if result.class == String
end
perform_action_with_newrelic_trace(:category => :sinatra,
:name => txn_name,
:params => @request.params) do
- result = dispatch_without_newrelic
-
- # Will only see an error raised if :show_exceptions is true, but
- # will always see them in the env hash if they occur
- had_error = env.has_key?('sinatra.error')
- ::NewRelic::Agent.notice_error(env['sinatra.error']) if had_error
-
- result
+ dispatch_and_notice_errors_with_newrelic
end
end
+ def dispatch_and_notice_errors_with_newrelic
+ dispatch_without_newrelic
+ ensure
+ # Will only see an error raised if :show_exceptions is true, but
+ # will always see them in the env hash if they occur
+ had_error = env.has_key?('sinatra.error')
+ ::NewRelic::Agent.notice_error(env['sinatra.error']) if had_error
+ end
+
# Define Request Header accessor for Sinatra
def newrelic_request_headers
request.env
@@ -89,6 +100,9 @@ module NewRelic
end
name
+ rescue => e
+ ::NewRelic::Agent.logger.debug("#{e.class} : #{e.message} - Error encountered trying to identify Sinatra transaction name")
+ '(unknown)'
end
end
end
diff --git a/lib/new_relic/agent/instrumentation/sunspot.rb b/lib/new_relic/agent/instrumentation/sunspot.rb
index 1cf5e9e..f73901c 100644
--- a/lib/new_relic/agent/instrumentation/sunspot.rb
+++ b/lib/new_relic/agent/instrumentation/sunspot.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :sunspot
diff --git a/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb b/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb
index 0843c2a..431a0c2 100644
--- a/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb
+++ b/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
DependencyDetection.defer do
@name = :unicorn
diff --git a/lib/new_relic/agent/method_tracer.rb b/lib/new_relic/agent/method_tracer.rb
index 315b707..3b3802c 100644
--- a/lib/new_relic/agent/method_tracer.rb
+++ b/lib/new_relic/agent/method_tracer.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control'
module NewRelic
module Agent
@@ -70,16 +74,13 @@ module NewRelic
def trace_execution_unscoped(metric_names, options={})
return yield unless NewRelic::Agent.is_execution_traced?
t0 = Time.now
- stats = Array(metric_names).map do | metric_name |
- NewRelic::Agent.instance.stats_engine.get_stats_no_scope metric_name
- end
begin
NewRelic::Agent.instance.push_trace_execution_flag(true) if options[:force]
yield
ensure
NewRelic::Agent.instance.pop_trace_execution_flag if options[:force]
duration = (Time.now - t0).to_f # for some reason this is 3 usec faster than Time - Time
- stats.each { |stat| stat.trace_call(duration) }
+ stat_engine.record_metrics(metric_names, duration)
end
end
@@ -117,7 +118,7 @@ module NewRelic
def stat_engine
agent_instance.stats_engine
end
-
+
# returns a scoped metric stat for the specified name
def get_stats_scoped(first_name, scoped_metric_only)
stat_engine.get_stats(first_name, true, scoped_metric_only)
@@ -126,25 +127,16 @@ module NewRelic
def get_stats_unscoped(name)
stat_engine.get_stats_no_scope(name)
end
-
- # the main statistic we should record in
- # #trace_execution_scoped - a scoped metric provided by the
- # first item in the metric array
- def main_stat(metric, options)
- get_stats_scoped(metric, options[:scoped_metric_only])
- end
-
- # returns an array containing the first metric, and an array
- # of other unscoped statistics we should also record along
- # side it
- def get_metric_stats(metrics, options)
- metrics = Array(metrics)
- first_name = metrics.shift
- stats = metrics.map do | name |
- get_stats_unscoped(name)
+
+ def get_metric_specs(first_name, other_names, scope, options)
+ specs = other_names.map { |name| NewRelic::MetricSpec.new(name) }
+ if options[:metric]
+ if scope && scope != first_name
+ specs << NewRelic::MetricSpec.new(first_name, scope)
+ end
+ specs << NewRelic::MetricSpec.new(first_name) unless options[:scoped_metric_only]
end
- stats.unshift(main_stat(first_name, options)) if options[:metric]
- [first_name, stats]
+ specs
end
# Helper for setting a hash key if the hash key is nil,
@@ -207,7 +199,7 @@ module NewRelic
# this method fails safely if the header does not manage to
# push the scope onto the stack - it simply does not trace
# any metrics.
- def trace_execution_scoped_footer(t0, first_name, metric_stats, expected_scope, forced, t1=Time.now.to_f)
+ def trace_execution_scoped_footer(t0, first_name, metric_specs, expected_scope, forced, t1=Time.now.to_f)
log_errors("trace_method_execution footer", first_name) do
duration = t1 - t0
@@ -215,7 +207,9 @@ module NewRelic
if expected_scope
scope = stat_engine.pop_scope(expected_scope, duration, t1)
exclusive = duration - scope.children_time
- metric_stats.each { |stats| stats.trace_call(duration, exclusive) }
+ stat_engine.record_metrics(metric_specs) do |stat|
+ stat.record_data_point(duration, exclusive)
+ end
end
end
end
@@ -229,17 +223,19 @@ module NewRelic
# Generally you pass an array of metric names if you want to record the metric under additional
# categories, but generally this *should never ever be done*. Most of the time you can aggregate
# on the server.
-
def trace_execution_scoped(metric_names, options={})
return yield if trace_disabled?(options)
set_if_nil(options, :metric)
set_if_nil(options, :deduct_call_time_from_parent)
- first_name, metric_stats = get_metric_stats(metric_names, options)
+ metric_names = Array(metric_names)
+ first_name = metric_names.shift
+ scope = stat_engine.scope_name
start_time, expected_scope = trace_execution_scoped_header(first_name, options)
- begin
+ begin
yield
ensure
- trace_execution_scoped_footer(start_time, first_name, metric_stats, expected_scope, options[:force])
+ metric_specs = get_metric_specs(first_name, metric_names, scope, options)
+ trace_execution_scoped_footer(start_time, first_name, metric_specs, expected_scope, options[:force])
end
end
@@ -356,14 +352,13 @@ module NewRelic
"def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
#{assemble_code_header(method_name, metric_name_code, options)}
t0 = Time.now
- stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope \"#{metric_name_code}\"
begin
#{"NewRelic::Agent.instance.push_trace_execution_flag(true)\n" if options[:force]}
#{_untraced_method_name(method_name, metric_name_code)}(*args, &block)\n
ensure
#{"NewRelic::Agent.instance.pop_trace_execution_flag\n" if options[:force] }
duration = (Time.now - t0).to_f
- stats.trace_call(duration)
+ NewRelic::Agent.record_metric(\"#{metric_name_code}\", duration)
#{options[:code_footer]}
end
end"
diff --git a/lib/new_relic/agent/new_relic_service.rb b/lib/new_relic/agent/new_relic_service.rb
index a7d9163..385f571 100644
--- a/lib/new_relic/agent/new_relic_service.rb
+++ b/lib/new_relic/agent/new_relic_service.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'zlib'
require 'new_relic/agent/audit_logger'
@@ -7,7 +11,8 @@ module NewRelic
# Specifies the version of the agent's communication protocol with
# the NewRelic hosted site.
- PROTOCOL_VERSION = 10
+ PROTOCOL_VERSION = 12
+ # 1f147a42: v10 (tag 3.5.3.17)
# cf0d1ff1: v9 (tag 3.5.0)
# 14105: v8 (tag 2.10.3)
# (no v7)
@@ -18,12 +23,13 @@ module NewRelic
# 534: v2 (shows up in 2.1.0, our first tag)
attr_accessor :request_timeout, :agent_id
- attr_reader :collector, :marshaller
+ attr_reader :collector, :marshaller, :metric_id_cache
def initialize(license_key=nil, collector=control.server)
@license_key = license_key || Agent.config[:license_key]
@collector = collector
@request_timeout = Agent.config[:timeout]
+ @metric_id_cache = {}
@audit_logger = ::NewRelic::Agent::AuditLogger.new(Agent.config)
Agent.config.register_callback(:'audit_log.enabled') do |enabled|
@@ -70,9 +76,47 @@ module NewRelic
invoke_remote(:shutdown, @agent_id, time.to_i) if @agent_id
end
+ def reset_metric_id_cache
+ @metric_id_cache = {}
+ end
+
+ # takes an array of arrays of spec and id, adds it into the
+ # metric cache so we can save the collector some work by
+ # sending integers instead of strings the next time around
+ def fill_metric_id_cache(pairs_of_specs_and_ids)
+ Array(pairs_of_specs_and_ids).each do |metric_spec_hash, metric_id|
+ metric_spec = MetricSpec.new(metric_spec_hash['name'],
+ metric_spec_hash['scope'])
+ metric_id_cache[metric_spec] = metric_id
+ end
+ end
+
+ # The collector wants to recieve metric data in a format that's different
+ # from how we store it internally, so this method handles the translation.
+ # It also handles translating metric names to IDs using our metric ID cache.
+ def build_metric_data_array(stats_hash)
+ metric_data_array = []
+ stats_hash.each do |metric_spec, stats|
+ # Omit empty stats as an optimization
+ unless stats.is_reset?
+ metric_id = metric_id_cache[metric_spec]
+ metric_data = if metric_id
+ NewRelic::MetricData.new(nil, stats, metric_id)
+ else
+ NewRelic::MetricData.new(metric_spec, stats, nil)
+ end
+ metric_data_array << metric_data
+ end
+ end
+ metric_data_array
+ end
+
def metric_data(last_harvest_time, now, unsent_timeslice_data)
- invoke_remote(:metric_data, @agent_id, last_harvest_time, now,
- unsent_timeslice_data)
+ metric_data_array = build_metric_data_array(unsent_timeslice_data)
+ result = invoke_remote(:metric_data, @agent_id, last_harvest_time, now,
+ metric_data_array)
+ fill_metric_id_cache(result)
+ result
end
def error_data(unsent_errors)
@@ -207,7 +251,14 @@ module NewRelic
def invoke_remote(method, *args)
now = Time.now
- data = @marshaller.dump(args)
+ data = nil
+ begin
+ data = @marshaller.dump(args)
+ rescue JsonError
+ @marshaller = PrubyMarshaller.new
+ retry
+ end
+
data, encoding = compress_request_if_needed(data)
uri = remote_method_uri(method, @marshaller.format)
@@ -227,12 +278,9 @@ module NewRelic
end
def record_supportability_metrics(method, now)
- NewRelic::Agent.instance.stats_engine. \
- get_stats_no_scope('Supportability/invoke_remote'). \
- record_data_point((Time.now - now).to_f)
- NewRelic::Agent.instance.stats_engine. \
- get_stats_no_scope('Supportability/invoke_remote/' + method.to_s). \
- record_data_point((Time.now - now).to_f)
+ duration = (Time.now - now).to_f
+ NewRelic::Agent.record_metric('Supportability/invoke_remote', duration)
+ NewRelic::Agent.record_metric('Supportability/invoke_remote/' + method.to_s, duration)
end
# Raises an UnrecoverableServerException if the post_string is longer
@@ -341,6 +389,12 @@ module NewRelic
end
end
+ # Used to wrap errors reported to agent by the collector
+ class CollectorError < StandardError; end
+
+ # Used to wrap any problem with the JSON marshaller
+ class JsonError < StandardError; end
+
class Marshaller
def parsed_error(error)
error_class = error['error_type'].split('::') \
@@ -426,13 +480,16 @@ module NewRelic
def dump(ruby, opts={})
JSON.dump(prepare(ruby, opts))
+ rescue => e
+ ::NewRelic::Agent.logger.debug "#{e.class.name} : #{e.message} encountered dumping agent data: #{ruby}"
+ raise JsonError.new(e)
end
def load(data)
return unless data && data != ''
return_value(JSON.load(data))
- rescue
- ::NewRelic::Agent.logger.debug "Error encountered loading collector response: #{data}"
+ rescue => e
+ ::NewRelic::Agent.logger.debug "#{e.class.name} : #{e.message} encountered loading collector response: #{data}"
raise
end
@@ -452,8 +509,6 @@ module NewRelic
true # for some definitions of 'human'
end
end
-
- class CollectorError < StandardError; end
end
end
end
diff --git a/lib/new_relic/agent/null_logger.rb b/lib/new_relic/agent/null_logger.rb
new file mode 100644
index 0000000..0d3b18a
--- /dev/null
+++ b/lib/new_relic/agent/null_logger.rb
@@ -0,0 +1,15 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+# A stub object that we can use in place of a real Logger instance when
+# the agent is disabled.
+module NewRelic
+ module Agent
+ class NullLogger
+ def method_missing(method, *args, &blk)
+ nil
+ end
+ end
+ end
+end
diff --git a/lib/new_relic/agent/pipe_channel_manager.rb b/lib/new_relic/agent/pipe_channel_manager.rb
index 3cf888f..cb1e7ee 100644
--- a/lib/new_relic/agent/pipe_channel_manager.rb
+++ b/lib/new_relic/agent/pipe_channel_manager.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'base64'
module NewRelic
@@ -22,9 +26,27 @@ module NewRelic
@listener ||= Listener.new
end
+ # Expected initial sequence of events for Pipe usage:
+ #
+ # 1. Pipe is created in parent process (read and write ends open)
+ # 2. Parent process forks
+ # 3. An after_fork hook is invoked in the child
+ # 4. From after_fork hook, child closes read end of pipe, and
+ # writes a ready marker on the pipe (after_fork_in_child).
+ # 5. The parent receives the ready marker, and closes the write end of the
+ # pipe in response (after_fork_in_parent).
+ #
+ # After this sequence of steps, an exit (whether clean or not) of the
+ # child will result in the pipe being marked readable again, and giving an
+ # EOF marker (nil) when read. Note that closing of the unused ends of the
+ # pipe in the parent and child processes is essential in order for the EOF
+ # to be correctly triggered. The ready marker mechanism is used because
+ # there's no easy hook for after_fork in the parent process.
class Pipe
+ READY_MARKER = "READY"
+
attr_accessor :in, :out
- attr_reader :last_read
+ attr_reader :last_read, :parent_pid
def initialize
@out, @in = IO.pipe
@@ -32,6 +54,7 @@ module NewRelic
@in.set_encoding(::Encoding::ASCII_8BIT)
end
@last_read = Time.now
+ @parent_pid = $$
end
def close
@@ -53,6 +76,15 @@ module NewRelic
@out.gets("\n\n")
end
+ def after_fork_in_child
+ @out.close unless @out.closed?
+ write(READY_MARKER)
+ end
+
+ def after_fork_in_parent
+ @in.close unless @in.closed?
+ end
+
def closed?
@out.closed? && @in.closed?
end
@@ -68,9 +100,13 @@ module NewRelic
@select_timeout = 60
end
+ def wakeup
+ wake.in << '.'
+ end
+
def register_pipe(id)
@pipes[id] = Pipe.new
- wake.in << '.'
+ wakeup
end
def start
@@ -81,9 +117,10 @@ module NewRelic
loop do
clean_up_pipes
pipes_to_listen_to = @pipes.values.map{|pipe| pipe.out} + [wake.out]
- NewRelic::Agent.instance.stats_engine \
- .get_stats_no_scope('Supportability/Listeners') \
- .record_data_point((Time.now - now).to_f) if now
+
+ NewRelic::Agent.record_metric('Supportability/Listeners',
+ (Time.now - now).to_f) if now
+
if ready = IO.select(pipes_to_listen_to, [], [], @select_timeout)
now = Time.now
pipe = ready[0][0]
@@ -94,17 +131,21 @@ module NewRelic
end
end
- break if !should_keep_listening?
+ break unless should_keep_listening?
end
end
sleep 0.001 # give time for the thread to spawn
end
+ def stop_listener_thread
+ @started = false
+ wakeup
+ @thread.join
+ end
+
def stop
return unless @started == true
- @started = false
- wake.in << '.' unless wake.in.closed?
- @thread.join # make sure we wait for the thread to exit
+ stop_listener_thread
close_all_pipes
@wake.close
@wake = nil
@@ -129,12 +170,14 @@ module NewRelic
def merge_data_from_pipe(pipe_handle)
pipe = find_pipe_for_handle(pipe_handle)
- got = pipe.read
-
- if got && !got.empty?
- payload = unmarshal(got)
- if payload == 'EOF'
- pipe.close
+ raw_payload = pipe.read
+
+ if raw_payload.nil?
+ pipe.close
+ elsif raw_payload && !raw_payload.empty?
+ payload = unmarshal(raw_payload)
+ if payload == Pipe::READY_MARKER
+ pipe.after_fork_in_parent
elsif payload
NewRelic::Agent.agent.merge_data_from([payload[:stats],
payload[:transaction_traces],
diff --git a/lib/new_relic/agent/pipe_service.rb b/lib/new_relic/agent/pipe_service.rb
index 5cd4e05..56f3443 100644
--- a/lib/new_relic/agent/pipe_service.rb
+++ b/lib/new_relic/agent/pipe_service.rb
@@ -1,15 +1,25 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
class PipeService
- attr_reader :channel_id, :buffer
+ attr_reader :channel_id, :buffer, :pipe
attr_accessor :request_timeout, :agent_id, :collector
-
+
def initialize(channel_id)
@channel_id = channel_id
@collector = NewRelic::Control::Server.new(:name => 'parent',
:port => 0)
+ @pipe = NewRelic::Agent::PipeChannelManager.channels[@channel_id]
+ if @pipe && @pipe.parent_pid != $$
+ @pipe.after_fork_in_child
+ else
+ NewRelic::Agent.logger.error("No communication channel to parent process, please see https://newrelic.com/docs/ruby/resque-instrumentation for more information.")
+ end
end
-
+
def connect(config)
nil
end
@@ -19,7 +29,7 @@ module NewRelic
end
def metric_data(last_harvest_time, now, unsent_timeslice_data)
- write_to_pipe(:stats => hash_from_metric_data(unsent_timeslice_data))
+ write_to_pipe(:stats => unsent_timeslice_data)
{}
end
@@ -34,10 +44,9 @@ module NewRelic
def sql_trace_data(sql)
write_to_pipe(:sql_traces => sql) if sql
end
-
+
def shutdown(time)
- write_to_pipe('EOF')
- NewRelic::Agent::PipeChannelManager.channels[@channel_id].close
+ @pipe.close if @pipe
end
# Invokes the block it is passed. This is used to implement HTTP
@@ -46,21 +55,15 @@ module NewRelic
def session
yield
end
-
- private
- def hash_from_metric_data(metric_data)
- metric_hash = {}
- metric_data.each do |metric_entry|
- metric_hash[metric_entry.metric_spec] = metric_entry
- end
- metric_hash
+ def reset_metric_id_cache
+ # we don't cache metric IDs, so nothing to do
end
+ private
+
def write_to_pipe(data)
- NewRelic::Agent::PipeChannelManager.channels[@channel_id].write(data)
- rescue => e
- NewRelic::Agent.logger.error("#{e.message}: Unable to send data to parent process, please see https://newrelic.com/docs/ruby/resque-instrumentation for more information.")
+ @pipe.write(data) if @pipe
end
end
end
diff --git a/lib/new_relic/agent/rules_engine.rb b/lib/new_relic/agent/rules_engine.rb
new file mode 100644
index 0000000..7bf6722
--- /dev/null
+++ b/lib/new_relic/agent/rules_engine.rb
@@ -0,0 +1,76 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+module NewRelic
+ module Agent
+ class RulesEngine
+ include Enumerable
+ extend Forwardable
+
+ def_delegators :@rules, :size, :<<, :inspect, :each
+
+ attr_accessor :rules
+
+ def initialize(rules=[])
+ @rules = rules
+ end
+
+ def rename(original_string)
+ @rules.sort.inject(original_string) do |string,rule|
+ if rule.each_segment
+ result, matched = rule.map_to_list(string.split('/'))
+ result = result.join('/')
+ else
+ result, matched = rule.apply(string)
+ end
+
+ break result if matched && rule.terminate_chain
+ result
+ end
+ end
+
+ class Rule
+ attr_reader(:terminate_chain, :each_segment, :ignore, :replace_all, :eval_order,
+ :match_expression, :replacement)
+
+ def initialize(options)
+ if !options['match_expression']
+ raise ArgumentError.new('missing required match_expression')
+ end
+ if !options['replacement'] && !options['ignore']
+ raise ArgumentError.new('must specify replacement when ignore is false')
+ end
+
+ @match_expression = Regexp.new(options['match_expression'])
+ @replacement = options['replacement']
+ @ignore = options['ignore'] || false
+ @eval_order = options['eval_order'] || 0
+ @replace_all = options['replace_all'] || false
+ @each_segment = options['each_segment'] || false
+ @terminate_chain = options['terminate_chain'] || false
+ end
+
+ def apply(string)
+ method = @replace_all ? :gsub : :sub
+ result = string.send(method, @match_expression, @replacement)
+ [result, result != string]
+ end
+
+ def map_to_list(list)
+ matched = false
+ result = list.map do |string|
+ str_result, str_match = apply(string)
+ matched ||= str_match
+ str_result
+ end
+ [result, matched]
+ end
+
+ def <=>(other)
+ eval_order <=> other.eval_order
+ end
+ end
+ end
+ end
+end
diff --git a/lib/new_relic/agent/sampler.rb b/lib/new_relic/agent/sampler.rb
index beb95c4..5e4f8d7 100644
--- a/lib/new_relic/agent/sampler.rb
+++ b/lib/new_relic/agent/sampler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# A Sampler is used to capture meaningful metrics in a background thread
# periodically. They will either be invoked once a minute just before the
# data is sent to the agent (default) or every 10 seconds, when #use_harvest_sampler?
diff --git a/lib/new_relic/agent/samplers/cpu_sampler.rb b/lib/new_relic/agent/samplers/cpu_sampler.rb
index 62dc08f..d206675 100644
--- a/lib/new_relic/agent/samplers/cpu_sampler.rb
+++ b/lib/new_relic/agent/samplers/cpu_sampler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/sampler'
module NewRelic
@@ -10,17 +14,20 @@ module NewRelic
poll
end
- def user_util_stats
- stats_engine.get_stats_no_scope("CPU/User/Utilization")
+ def record_user_util(value)
+ NewRelic::Agent.record_metric("CPU/User/Utilization", value)
end
- def system_util_stats
- stats_engine.get_stats_no_scope("CPU/System/Utilization")
+
+ def record_system_util(value)
+ NewRelic::Agent.record_metric("CPU/System/Utilization", value)
end
- def usertime_stats
- stats_engine.get_stats_no_scope("CPU/User Time")
+
+ def record_usertime(value)
+ NewRelic::Agent.record_metric("CPU/User Time", value)
end
- def systemtime_stats
- stats_engine.get_stats_no_scope("CPU/System Time")
+
+ def record_systemtime(value)
+ NewRelic::Agent.record_metric("CPU/System Time", value)
end
def self.supported_on_this_platform?
@@ -39,13 +46,14 @@ module NewRelic
usertime = t.utime - @last_utime
systemtime = t.stime - @last_stime
- systemtime_stats.record_data_point(systemtime) if systemtime >= 0
- usertime_stats.record_data_point(usertime) if usertime >= 0
+ record_systemtime(systemtime) if systemtime >= 0
+ record_usertime(usertime) if usertime >= 0
# Calculate the true utilization by taking cpu times and dividing by
# elapsed time X num_processors.
- user_util_stats.record_data_point usertime / (elapsed * num_processors)
- system_util_stats.record_data_point systemtime / (elapsed * num_processors)
+
+ record_user_util(usertime / (elapsed * num_processors))
+ record_system_util(systemtime / (elapsed * num_processors))
end
@last_utime = t.utime
@last_stime = t.stime
diff --git a/lib/new_relic/agent/samplers/delayed_job_sampler.rb b/lib/new_relic/agent/samplers/delayed_job_sampler.rb
index b4839d4..fd44b00 100644
--- a/lib/new_relic/agent/samplers/delayed_job_sampler.rb
+++ b/lib/new_relic/agent/samplers/delayed_job_sampler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/sampler'
require 'new_relic/delayed_job_injection'
@@ -18,11 +22,12 @@ module NewRelic
raise Unsupported, "No DJ worker present" unless NewRelic::DelayedJobInjection.worker_name
end
- def error_stats
- stats_engine.get_stats("Workers/DelayedJob/failed_jobs", false)
+ def record_failed_jobs(value)
+ NewRelic::Agent.record_metric("Workers/DelayedJob/failed_jobs", value)
end
- def locked_job_stats
- stats_engine.get_stats("Workers/DelayedJob/locked_jobs", false)
+
+ def record_locked_jobs(value)
+ NewRelic::Agent.record_metric("Workers/DelayedJob/locked_jobs", value)
end
def local_env
@@ -45,8 +50,8 @@ module NewRelic
end
def poll
- record error_stats, failed_jobs
- record locked_job_stats, locked_jobs
+ record_failed_jobs(failed_jobs)
+ record_locked_jobs(locked_jobs)
if @queue
record_queue_length_across_dimension('queue')
@@ -61,9 +66,11 @@ module NewRelic
all_count = 0
Delayed::Job.count(:group => column, :conditions => ['run_at < ? and failed_at is NULL', Time.now]).each do | column_val, count |
all_count += count
- record stats_engine.get_stats("Workers/DelayedJob/queue_length/#{column == 'queue' ? 'name' : column}/#{column_val}", false), count
+ metric = "Workers/DelayedJob/queue_length/#{column == 'queue' ? 'name' : column}/#{column_val}"
+ NewRelic::Agent.record_metric(metric, count)
end
- record(stats_engine.get_stats("Workers/DelayedJob/queue_length/all", false), all_count)
+ all_metric = "Workers/DelayedJob/queue_length/all"
+ NewRelic::Agent.record_metric(all_metric, all_count)
end
# Figure out if we get the queues.
@@ -76,10 +83,6 @@ module NewRelic
end
@queue ||= false
end
-
- def record(stat, size)
- stat.record_data_point size
- end
end
end
end
diff --git a/lib/new_relic/agent/samplers/memory_sampler.rb b/lib/new_relic/agent/samplers/memory_sampler.rb
index 77412cc..48ad141 100644
--- a/lib/new_relic/agent/samplers/memory_sampler.rb
+++ b/lib/new_relic/agent/samplers/memory_sampler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/sampler'
module NewRelic
@@ -49,20 +53,20 @@ module NewRelic
NewRelic::Agent::Samplers::MemorySampler.platform
end
- def stats
- stats_engine.get_stats("Memory/Physical", false)
- end
def poll
sample = @sampler.get_sample
- stats.record_data_point sample if sample
- stats
+ if sample
+ NewRelic::Agent.record_metric("Memory/Physical", sample)
+ end
end
+
class Base
def can_run?
return false if @broken
m = get_memory rescue nil
m && m > 0
end
+
def get_sample
return nil if @broken
begin
diff --git a/lib/new_relic/agent/samplers/object_sampler.rb b/lib/new_relic/agent/samplers/object_sampler.rb
index ba375cf..e957607 100644
--- a/lib/new_relic/agent/samplers/object_sampler.rb
+++ b/lib/new_relic/agent/samplers/object_sampler.rb
@@ -1,24 +1,24 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/sampler'
module NewRelic
module Agent
module Samplers
class ObjectSampler < NewRelic::Agent::Sampler
-
def initialize
super :objects
end
- def stats
- stats_engine.get_stats_no_scope("GC/objects")
- end
-
def self.supported_on_this_platform?
defined?(ObjectSpace) && ObjectSpace.respond_to?(:live_objects)
end
def poll
- stats.record_data_point(ObjectSpace.live_objects)
+ live_objects = ObjectSpace.live_objects
+ NewRelic::Agent.record_metric("GC/objects", live_objects)
end
end
end
diff --git a/lib/new_relic/agent/shim_agent.rb b/lib/new_relic/agent/shim_agent.rb
index b0e1257..9a2c4e6 100644
--- a/lib/new_relic/agent/shim_agent.rb
+++ b/lib/new_relic/agent/shim_agent.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# This agent is loaded by the plug when the plug-in is disabled
# It recreates just enough of the API to not break any clients that
# invoke the Agent.
@@ -18,7 +22,6 @@ module NewRelic
def after_fork *args; end
def start *args; end
def shutdown *args; end
- def serialize; end
def merge_data_from *args; end
def push_trace_execution_flag *args; end
def pop_trace_execution_flag *args; end
diff --git a/lib/new_relic/agent/sql_sampler.rb b/lib/new_relic/agent/sql_sampler.rb
index 59c63a6..0fd149b 100644
--- a/lib/new_relic/agent/sql_sampler.rb
+++ b/lib/new_relic/agent/sql_sampler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'zlib'
require 'base64'
require 'digest/md5'
@@ -99,7 +103,8 @@ module NewRelic
if NewRelic::Agent.is_sql_recorded?
if duration > Agent.config[:'slow_sql.explain_threshold']
backtrace = caller.join("\n")
- transaction_data.sql_data << SlowSql.new(sql, metric_name, config,
+ transaction_data.sql_data << SlowSql.new(TransactionSampler.truncate_message(sql),
+ metric_name, config,
duration, backtrace)
end
end
@@ -176,7 +181,7 @@ module NewRelic
end
end
- class SqlTrace < MethodTraceStats
+ class SqlTrace < Stats
attr_reader :path
attr_reader :url
attr_reader :sql_id
diff --git a/lib/new_relic/agent/stats.rb b/lib/new_relic/agent/stats.rb
new file mode 100644
index 0000000..fd5c1ea
--- /dev/null
+++ b/lib/new_relic/agent/stats.rb
@@ -0,0 +1,153 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+module NewRelic
+ module Agent
+ class Stats
+ attr_accessor :call_count
+ attr_accessor :min_call_time
+ attr_accessor :max_call_time
+ attr_accessor :total_call_time
+ attr_accessor :total_exclusive_time
+ attr_accessor :sum_of_squares
+
+ def initialize
+ reset
+ end
+
+ def reset
+ @call_count = 0
+ @total_call_time = 0.0
+ @total_exclusive_time = 0.0
+ @min_call_time = 0.0
+ @max_call_time = 0.0
+ @sum_of_squares = 0.0
+ end
+
+ def is_reset?
+ call_count == 0 && total_call_time == 0.0 && total_exclusive_time == 0.0
+ end
+
+ def merge(other_stats)
+ stats = self.clone
+ stats.merge!(other_stats)
+ end
+
+ def merge!(other_stats)
+ Array(other_stats).each do |other|
+ @min_call_time = other.min_call_time if min_time_less?(other)
+ @max_call_time = other.max_call_time if other.max_call_time > max_call_time
+ @total_call_time += other.total_call_time
+ @total_exclusive_time += other.total_exclusive_time
+ @sum_of_squares += other.sum_of_squares
+ @call_count += other.call_count
+ end
+ self
+ end
+
+ def to_s
+ "[#{'%2i' % call_count.to_i} calls #{'%.4f' % total_call_time.to_f}s]"
+ end
+
+ def to_json(*_)
+ {
+ 'call_count' => call_count.to_i,
+ 'min_call_time' => min_call_time.to_f,
+ 'max_call_time' => max_call_time.to_f,
+ 'total_call_time' => total_call_time.to_f,
+ 'total_exclusive_time' => total_exclusive_time.to_f,
+ 'sum_of_squares' => sum_of_squares.to_f
+ }.to_json(*_)
+ end
+
+ # record a single data point into the statistical gatherer. The gatherer
+ # will aggregate all data points collected over a specified period and upload
+ # its data to the NewRelic server
+ def record_data_point(value, exclusive_time = value)
+ @call_count += 1
+ @total_call_time += value
+ @min_call_time = value if value < @min_call_time || @call_count == 1
+ @max_call_time = value if value > @max_call_time
+ @total_exclusive_time += exclusive_time
+
+ @sum_of_squares += (value * value)
+ self
+ end
+
+ alias trace_call record_data_point
+
+ # Records multiple data points as one method call - this handles
+ # all the aggregation that would be done with multiple
+ # record_data_point calls
+ def record_multiple_data_points(total_value, count=1)
+ return record_data_point(total_value) if count == 1
+ @call_count += count
+ @total_call_time += total_value
+ avg_val = total_value / count
+ @min_call_time = avg_val if avg_val < @min_call_time || @call_count == count
+ @max_call_time = avg_val if avg_val > @max_call_time
+ @total_exclusive_time += total_value
+ @sum_of_squares += (avg_val * avg_val) * count
+ self
+ end
+
+ # increments the call_count by one
+ def increment_count(value = 1)
+ @call_count += value
+ end
+
+ def inspect
+ "#<NewRelic::Agent::Stats #{to_s} >"
+ end
+
+ def ==(other)
+ other.class == self.class &&
+ (
+ @min_call_time == other.min_call_time &&
+ @max_call_time == other.max_call_time &&
+ @total_call_time == other.total_call_time &&
+ @total_exclusive_time == other.total_exclusive_time &&
+ @sum_of_squares == other.sum_of_squares &&
+ @call_count == other.call_count
+ )
+ end
+
+ # Apdex-related accessors
+ alias_method :apdex_s, :call_count
+ alias_method :apdex_t, :total_call_time
+ alias_method :apdex_f, :total_exclusive_time
+
+ def record_apdex_s
+ @call_count += 1
+ end
+
+ def record_apdex_t
+ @total_call_time += 1
+ end
+
+ def record_apdex_f
+ @total_exclusive_time += 1
+ end
+
+ protected
+
+ def min_time_less?(other)
+ (other.min_call_time < min_call_time && other.call_count > 0) || call_count == 0
+ end
+ end
+
+ class ChainedStats
+ attr_accessor :scoped_stats, :unscoped_stats
+
+ def initialize(scoped_stats, unscoped_stats)
+ @scoped_stats = scoped_stats
+ @unscoped_stats = unscoped_stats
+ end
+
+ def method_missing(method, *args)
+ unscoped_stats.send(method, *args)
+ scoped_stats.send(method, *args)
+ end
+ end
+ end
+end
diff --git a/lib/new_relic/agent/stats_engine.rb b/lib/new_relic/agent/stats_engine.rb
index a63f2a5..92d0998 100644
--- a/lib/new_relic/agent/stats_engine.rb
+++ b/lib/new_relic/agent/stats_engine.rb
@@ -1,7 +1,12 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/stats_engine/metric_stats'
require 'new_relic/agent/stats_engine/samplers'
require 'new_relic/agent/stats_engine/transactions'
require 'new_relic/agent/stats_engine/gc_profiler'
+require 'new_relic/agent/stats_engine/stats_hash'
module NewRelic
module Agent
@@ -14,8 +19,16 @@ module NewRelic
def initialize
# Makes the unit tests happy
Thread::current[:newrelic_scope_stack] = nil
+ @stats_lock = Mutex.new
+ @stats_hash = StatsHash.new
start_sampler_thread
end
+
+ # All access to the @stats_hash ivar should be funnelled through this
+ # method to ensure thread-safety.
+ def with_stats_lock
+ @stats_lock.synchronize { yield }
+ end
end
end
end
diff --git a/lib/new_relic/agent/stats_engine/gc_profiler.rb b/lib/new_relic/agent/stats_engine/gc_profiler.rb
index 33f9d6a..f3e1ef7 100644
--- a/lib/new_relic/agent/stats_engine/gc_profiler.rb
+++ b/lib/new_relic/agent/stats_engine/gc_profiler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# -*- coding: utf-8 -*-
module NewRelic
module Agent
@@ -7,7 +11,7 @@ module NewRelic
@profiler = RailsBench.new if RailsBench.enabled?
@profiler = Ruby19.new if Ruby19.enabled?
@profiler = Rubinius.new if Rubinius.enabled?
- @profiler = RubiniusAgent.new if RubiniusAgent.enabled?
+ @profiler
end
def self.capture
@@ -45,10 +49,10 @@ module NewRelic
if num_calls > 0
# GC stats are collected into a blamed metric which allows
# us to show the stats controller by controller
- gc_stats = NewRelic::Agent.instance.stats_engine \
- .get_stats('GC/cumulative', true, false,
- NewRelic::Agent::TransactionInfo.get.transaction_name)
- gc_stats.record_multiple_data_points(elapsed, num_calls)
+ NewRelic::Agent.instance.stats_engine \
+ .record_metrics('GC/cumulative', nil, :scoped => true) do |stat|
+ stat.record_multiple_data_points(elapsed, num_calls)
+ end
end
end
@@ -110,29 +114,6 @@ module NewRelic
::Rubinius::GC.count
end
end
-
- class RubiniusAgent < Profiler
- def self.enabled?
- if NewRelic::LanguageSupport.using_engine?('rbx')
- require 'rubinius/agent'
- true
- else
- false
- end
- end
-
- def call_time
- agent = ::Rubinius::Agent.loopback
- (agent.get('system.gc.young.total_wallclock')[1] +
- agent.get('system.gc.full.total_wallclock')[1]) * 1000
- end
-
- def call_count
- agent = ::Rubinius::Agent.loopback
- agent.get('system.gc.young.count')[1] +
- agent.get('system.gc.full.count')[1]
- end
- end
end
end
end
diff --git a/lib/new_relic/agent/stats_engine/metric_stats.rb b/lib/new_relic/agent/stats_engine/metric_stats.rb
index 87b779c..6060912 100644
--- a/lib/new_relic/agent/stats_engine/metric_stats.rb
+++ b/lib/new_relic/agent/stats_engine/metric_stats.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/language_support'
module NewRelic
@@ -5,109 +9,48 @@ module NewRelic
class StatsEngine
# Handles methods related to actual Metric collection
module MetricStats
- # A simple mutex-synchronized hash to make sure our statistics
- # are internally consistent even in truly-threaded rubies like JRuby
- class SynchronizedHash < ::Hash
- attr_reader :lock
-
- def initialize
- @lock = Mutex.new
- end
-
- def initialize_copy(old)
- super
- old.each do |key, value|
- self.store(key, value.dup)
- end
- end
-
- def []=(*args)
- @lock.synchronize { super }
- rescue => e
- log_error(e)
- end
-
- def clear(*args)
- @lock.synchronize { super }
- rescue => e
- log_error(e)
- end
-
- def delete(*args)
- @lock.synchronize { super }
- rescue => e
- log_error(e)
- end
-
- def delete_if(*args)
- @lock.synchronize { super }
- rescue => e
- log_error(e)
- end
-
- def reset
- values.each { |s| s.reset }
- end
-
- def log_error(e)
- backtraces = Thread.list.map { |t| log_thread(t) }.join("\n\n")
- ::NewRelic::Agent.logger.warn(
- "SynchronizedHash failure: #{e.class.name}: #{e.message}\n#{backtraces}")
- end
-
- def log_thread(t)
- # Ruby 1.8 doesn't expose backtrace properly, so make sure it's there
- if t.nil? || !t.respond_to?(:backtrace) || t.backtrace.nil?
- return "#{t}\n\tNo backtrace for thread"
- end
-
- backtrace = t.backtrace.map { |b| "\t#{b}" }.join("\n")
- "\t#{t}\n#{backtrace}"
-
- rescue Exception => e
- # JRuby 1.7.0 has a nasty habit of raising a
- # java.lang.NullPointerException when we iterate through threads
- # asking for backtraces. This line allows us to swallow java
- # exceptions without referencing their classes (since they don't
- # exist in MRI). It also prevents us from swallowing signals or
- # other nasty things that can happen when you rescue Exception.
- ::NewRelic::Agent.logger.warn(
- "Error collecting thread backtraces: #{e.class.name}: #{e.message}")
- ::NewRelic::Agent.logger.debug( e.backtrace.join("\n") )
-
- raise e if e.class.ancestors.include? Exception
+ # Lookup and write to the named metric in a single call.
+ #
+ # This method is thead-safe, and is preferred to the lookup / modify
+ # method pairs (e.g. get_stats + record_data_point)
+ def record_metrics(metric_names_or_specs, value=nil, options={}, &blk)
+ defaults = {
+ :scoped => false,
+ :scope => scope_name
+ }
+ options = defaults.merge(options)
+ effective_scope = options[:scoped] && options[:scope]
+ specs = coerce_to_metric_spec_array(metric_names_or_specs, effective_scope)
+ with_stats_lock do
+ @stats_hash.record(specs, value, &blk)
end
end
- # Returns all of the metric names of all the stats in the engine
- def metrics
- stats_hash.keys.map(&:to_s)
- end
-
# a simple accessor for looking up a stat with no scope -
# returns a new stats object if no stats object for that
# metric exists yet
def get_stats_no_scope(metric_name)
- stats_hash[NewRelic::MetricSpec.new(metric_name, '')] ||= NewRelic::MethodTraceStats.new
- end
-
- # This version allows a caller to pass a stat class to use
- def get_custom_stats(metric_name, stat_class)
- stats_hash[NewRelic::MetricSpec.new(metric_name)] ||= stat_class.new
+ get_stats(metric_name, false)
end
# If use_scope is true, two chained metrics are created, one with scope and one without
# If scoped_metric_only is true, only a scoped metric is created (used by rendering metrics which by definition are per controller only)
def get_stats(metric_name, use_scope = true, scoped_metric_only = false, scope = nil)
scope ||= scope_name if use_scope
- if scoped_metric_only
- spec = NewRelic::MetricSpec.new metric_name, scope
- stats = stats_hash[spec] ||= NewRelic::MethodTraceStats.new
- else
- stats = stats_hash[NewRelic::MetricSpec.new(metric_name)] ||= NewRelic::MethodTraceStats.new
- if scope && scope != metric_name
- spec = NewRelic::MetricSpec.new metric_name, scope
- stats = stats_hash[spec] ||= NewRelic::ScopedMethodTraceStats.new(stats)
+ stats = nil
+ with_stats_lock do
+ if scoped_metric_only
+ stats = @stats_hash[NewRelic::MetricSpec.new(metric_name, scope)]
+ else
+ unscoped_spec = NewRelic::MetricSpec.new(metric_name)
+ unscoped_stats = @stats_hash[unscoped_spec]
+ if scope && scope != metric_name
+ scoped_spec = NewRelic::MetricSpec.new(metric_name, scope)
+ scoped_stats = @stats_hash[scoped_spec]
+ stats = NewRelic::Agent::ChainedStats.new(scoped_stats, unscoped_stats)
+ else
+ stats = unscoped_stats
+ end
end
end
stats
@@ -116,10 +59,12 @@ module NewRelic
# Returns a stat if one exists, otherwise returns nil. If you
# want auto-initialization, use one of get_stats or get_stats_no_scope
def lookup_stats(metric_name, scope_name = '')
- stats_hash[NewRelic::MetricSpec.new(metric_name, scope_name)]
+ spec = NewRelic::MetricSpec.new(metric_name, scope_name)
+ with_stats_lock do
+ @stats_hash.has_key?(spec) ? @stats_hash[spec] : nil
+ end
end
-
# Helper method for timing supportability metrics
def record_supportability_metrics_timed(metrics)
start_time = Time.now
@@ -127,134 +72,88 @@ module NewRelic
end_time = Time.now
duration = (end_time - start_time).to_f
ensure
- record_supportability_metrics(duration, metrics) do |value, metric|
- metric.record_data_point(value)
+ record_metrics(metrics) do |stat|
+ stat.record_data_point(duration)
end
end
# Helper for recording a straight value into the count
def record_supportability_metrics_count(value, *metrics)
- record_supportability_metrics(value, *metrics) do |value, metric|
- metric.call_count = value
+ record_metrics(metrics) do |stat|
+ stat.call_count = value
end
end
# Helper method for recording supportability metrics consistently
def record_supportability_metrics(value, *metrics)
- metrics.each do |metric|
- yield(value, get_stats_no_scope("Supportability/#{metric}"))
+ real_names = metrics.map { |name| "Supportability/#{name}" }
+ NewRelic::Agent.agent.record_metric(real_names) do |stat|
+ yield stat
end
end
- # This module was extracted from the harvest method and should
- # be refactored
- module Harvest
- # merge data from previous harvests into this stats engine -
- # takes into account the case where there are new stats for
- # that metric, and the case where there is no current data
- # for that metric
- def merge_data(metric_data_hash)
- metric_data_hash.each do |metric_spec, metric_data|
- new_data = lookup_stats(metric_spec.name, metric_spec.scope)
- if new_data
- new_data.merge!(metric_data.stats)
- else
- stats_hash[metric_spec] = metric_data.stats
- end
- end
- end
-
- private
- def get_stats_hash_from(engine_or_hash)
- if engine_or_hash.is_a?(StatsEngine)
- engine_or_hash.stats_hash
- else
- engine_or_hash
- end
- end
-
- def coerce_to_metric_spec(metric_spec)
- if metric_spec.is_a?(NewRelic::MetricSpec)
- metric_spec
- else
- NewRelic::MetricSpec.new(metric_spec)
- end
- end
-
- def clone_and_reset_stats(metric_spec, stats)
- if stats.nil?
- raise "Nil stats for #{metric_spec.name} (#{metric_spec.scope})"
- end
-
- stats_copy = stats.clone
- stats.reset
- stats_copy
- end
-
- # if the previous timeslice data has not been reported (due to an error of some sort)
- # then we need to merge this timeslice with the previously accumulated - but not sent
- # data
- def merge_old_data!(metric_spec, stats, old_data)
- metric_data = old_data[metric_spec]
- stats.merge!(metric_data.stats) unless metric_data.nil?
- end
-
- def add_data_to_send_unless_empty(data, stats, metric_spec, id)
- # don't bother collecting and reporting stats that have
- # zero-values for this timeslice. significant
- # performance boost and storage savings.
- return if stats.is_reset?
- data[metric_spec] = NewRelic::MetricData.new((id ? nil : metric_spec), stats, id)
+ def reset_stats
+ with_stats_lock do
+ old = @stats_hash
+ @stats_hash = StatsHash.new
+ old
end
+ end
- def merge_stats(other_engine_or_hash, metric_ids)
- old_data = get_stats_hash_from(other_engine_or_hash)
- timeslice_data = {}
- stats_hash.lock.synchronize do
- Thread.current['newrelic_stats_hash'] = stats_hash.clone
- stats_hash.reset
- end
- Thread.current['newrelic_stats_hash'].each do |metric_spec, stats|
- metric_spec = coerce_to_metric_spec(metric_spec)
- stats_copy = clone_and_reset_stats(metric_spec, stats)
- merge_old_data!(metric_spec, stats_copy, old_data)
- add_data_to_send_unless_empty(timeslice_data, stats_copy, metric_spec, metric_ids[metric_spec])
- end
- timeslice_data
+ # merge data from previous harvests into this stats engine
+ def merge!(other_stats_hash)
+ with_stats_lock do
+ @stats_hash.merge!(other_stats_hash)
end
-
end
- include Harvest
# Harvest the timeslice data. First recombine current statss
# with any previously
# unsent metrics, clear out stats cache, and return the current
# stats.
- # ---
- # Note: this is not synchronized. There is still some risk in this and
- # we will revisit later to see if we can make this more robust without
- # sacrificing efficiency.
- # +++
- def harvest_timeslice_data(previous_timeslice_data, metric_ids)
+ def harvest_timeslice_data(old_stats_hash, rules_engine=RulesEngine.new)
poll harvest_samplers
- merge_stats(previous_timeslice_data, metric_ids)
+ snapshot = reset_stats
+ snapshot = apply_rules_to_metric_data(rules_engine, snapshot)
+ snapshot.merge!(old_stats_hash)
end
- # Remove all stats. For test code only.
- def clear_stats
- @stats_hash = SynchronizedHash.new
- NewRelic::Agent::BusyCalculator.reset
+ def apply_rules_to_metric_data(rules_engine, stats_hash)
+ renamed_stats = NewRelic::Agent::StatsHash.new
+ stats_hash.each do |spec, stats|
+ new_name = rules_engine.rename(spec.name)
+ new_spec = NewRelic::MetricSpec.new(new_name, spec.scope)
+ renamed_stats[new_spec].merge!(stats)
+ end
+ renamed_stats
end
- # Reset each of the stats, such as when a new passenger instance starts up.
- def reset_stats
- stats_hash.reset
+ def coerce_to_metric_spec_array(metric_names_or_specs, scope)
+ specs = []
+ Array(metric_names_or_specs).map do |name_or_spec|
+ case name_or_spec
+ when String
+ specs << NewRelic::MetricSpec.new(name_or_spec)
+ specs << NewRelic::MetricSpec.new(name_or_spec, scope) if scope
+ when NewRelic::MetricSpec
+ specs << name_or_spec
+ end
+ end
+ specs
+ end
+
+ # For use by test code only.
+ def clear_stats
+ reset_stats
+ NewRelic::Agent::BusyCalculator.reset
end
- # returns a memoized SynchronizedHash that holds the actual
- # instances of Stats keyed off their MetricName
- def stats_hash
- @stats_hash ||= SynchronizedHash.new
+ # Returns all of the metric names of all the stats in the engine.
+ # For use by test code only.
+ def metrics
+ with_stats_lock do
+ @stats_hash.keys.map { |spec| spec.to_s }
+ end
end
end
end
diff --git a/lib/new_relic/agent/stats_engine/samplers.rb b/lib/new_relic/agent/stats_engine/samplers.rb
index 732794e..71b7277 100644
--- a/lib/new_relic/agent/stats_engine/samplers.rb
+++ b/lib/new_relic/agent/stats_engine/samplers.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
class StatsEngine
@@ -34,9 +38,8 @@ module Agent
sleep POLL_PERIOD
poll periodic_samplers
ensure
- NewRelic::Agent.instance.stats_engine \
- .get_stats_no_scope('Supportability/Samplers') \
- .record_data_point((Time.now - now).to_f)
+ duration = (Time.now - now).to_f
+ NewRelic::Agent.record_metric('Supportability/Samplers', duration)
end
end
end
diff --git a/lib/new_relic/agent/stats_engine/stats_hash.rb b/lib/new_relic/agent/stats_engine/stats_hash.rb
new file mode 100644
index 0000000..8d22796
--- /dev/null
+++ b/lib/new_relic/agent/stats_engine/stats_hash.rb
@@ -0,0 +1,62 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+# A Hash-descended class for storing metric data in the NewRelic Agent.
+#
+# Keys are NewRelic::MetricSpec objects.
+# Values are NewRelic::Agent::Stats objects.
+#
+# Missing keys will be automatically created as empty NewRelic::Agent::Stats
+# instances, so use has_key? explicitly to check for key existence.
+#
+# This class makes no provisions for safe usage from multiple threads, such
+# measures should be externally provided.
+module NewRelic
+ module Agent
+ class StatsHash < ::Hash
+ def initialize
+ super { |hash, key| hash[key] = NewRelic::Agent::Stats.new }
+ end
+
+ def marshal_dump
+ Hash[self]
+ end
+
+ def marshal_load(hash)
+ self.merge!(hash)
+ end
+
+ def ==(other)
+ Hash[self] == Hash[other]
+ end
+
+ def record(metric_specs, value=nil)
+ Array(metric_specs).each do |metric_spec|
+ stats = self[metric_spec]
+ if block_given?
+ yield stats
+ else
+ case value
+ when Numeric
+ stats.record_data_point(value)
+ when NewRelic::Agent::Stats
+ stats.merge!(value)
+ end
+ end
+ end
+ end
+
+ def merge!(other)
+ other.each do |key,val|
+ if self.has_key?(key)
+ self[key].merge!(val)
+ else
+ self[key] = val
+ end
+ end
+ self
+ end
+ end
+ end
+end
\ No newline at end of file
diff --git a/lib/new_relic/agent/stats_engine/transactions.rb b/lib/new_relic/agent/stats_engine/transactions.rb
index fba9a00..50e991f 100644
--- a/lib/new_relic/agent/stats_engine/transactions.rb
+++ b/lib/new_relic/agent/stats_engine/transactions.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# -*- coding: utf-8 -*-
module NewRelic
module Agent
@@ -5,8 +9,8 @@ module Agent
# A simple stack element that tracks the current name and length
# of the executing stack
class ScopeStackElement
- attr_reader :name, :deduct_call_time_from_parent
- attr_accessor :children_time
+ attr_reader :deduct_call_time_from_parent
+ attr_accessor :name, :children_time
def initialize(name, deduct_call_time)
@name = name
@deduct_call_time_from_parent = deduct_call_time
@@ -30,6 +34,8 @@ module Agent
def pop_scope(*args); end
end
+ attr_reader :transaction_sampler
+
# add a new transaction sampler, unless we're currently in a
# transaction (then we fail)
def transaction_sampler= sampler
@@ -69,11 +75,17 @@ module Agent
@transaction_sampler.notice_pop_scope(scope.name, time) if sampler_enabled?
scope
end
-
+
def sampler_enabled?
@transaction_sampler && Agent.config[:'transaction_tracer.enabled']
end
-
+
+ # Rename the segment associated with the last pushed scope to +new_name+.
+ def rename_scope_segment( new_name )
+ self.peek_scope.name = new_name
+ @transaction_sampler.rename_scope_segment( new_name ) if sampler_enabled?
+ end
+
# Returns the latest ScopeStackElement
def peek_scope
scope_stack.last
diff --git a/lib/new_relic/agent/thread.rb b/lib/new_relic/agent/thread.rb
index c4c6f4f..f12c085 100644
--- a/lib/new_relic/agent/thread.rb
+++ b/lib/new_relic/agent/thread.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Agent
diff --git a/lib/new_relic/agent/thread_profiler.rb b/lib/new_relic/agent/thread_profiler.rb
index fedd6ba..8837591 100644
--- a/lib/new_relic/agent/thread_profiler.rb
+++ b/lib/new_relic/agent/thread_profiler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/thread'
require 'new_relic/agent/worker_loop'
diff --git a/lib/new_relic/agent/transaction_info.rb b/lib/new_relic/agent/transaction_info.rb
index 5c2d826..164bac8 100644
--- a/lib/new_relic/agent/transaction_info.rb
+++ b/lib/new_relic/agent/transaction_info.rb
@@ -1,19 +1,33 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'erb'
module NewRelic
module Agent
class TransactionInfo
+ DEFAULT_TRANSACTION_NAME = '(unknown)'
- attr_accessor :token, :capture_deep_tt, :transaction_name
+ attr_accessor :token, :capture_deep_tt
+ attr_writer :transaction_name
attr_reader :start_time
def initialize
@guid = ""
- @transaction_name = "(unknown)"
+ @transaction_name = nil
@start_time = Time.now
@ignore_end_user = false
end
+ def transaction_name_set?
+ !@transaction_name.nil?
+ end
+
+ def transaction_name
+ @transaction_name || DEFAULT_TRANSACTION_NAME
+ end
+
def force_persist_sample?(sample)
token && sample.duration > Agent.config[:apdex_t]
end
@@ -37,11 +51,26 @@ module NewRelic
def ignore_end_user?
@ignore_end_user
end
-
+
def ignore_end_user=(value)
@ignore_end_user = value
end
+ def apdex_t
+ (Agent.config[:web_transactions_apdex] &&
+ Agent.config[:web_transactions_apdex][@transaction_name]) ||
+ Agent.config[:apdex_t]
+ end
+
+ def transaction_trace_threshold
+ key = :'transaction_tracer.transaction_threshold'
+ if Agent.config.source(key).class == Configuration::DefaultSource
+ apdex_t * 4
+ else
+ Agent.config[key]
+ end
+ end
+
def self.get()
Thread.current[:newrelic_transaction_info] ||= TransactionInfo.new
end
@@ -64,9 +93,9 @@ module NewRelic
def self.get_token(request)
return nil unless request
-
+
agent_flag = request.cookies['NRAGENT']
- if agent_flag and agent_flag.instance_of? String
+ if agent_flag and agent_flag.instance_of? String
s = agent_flag.split("=")
if s.length == 2
if s[0] == "tk" && s[1]
@@ -78,7 +107,7 @@ module NewRelic
end
end
- # Run through a collection of unsafe characters ( in the context of the token )
+ # Run through a collection of unsafe characters ( in the context of the token )
# and set the token to an empty string if any of them are found in the token so that
# potential XSS attacks via the token are avoided
def self.sanitize_token(token)
diff --git a/lib/new_relic/agent/transaction_sample_builder.rb b/lib/new_relic/agent/transaction_sample_builder.rb
index 121689f..82d5447 100644
--- a/lib/new_relic/agent/transaction_sample_builder.rb
+++ b/lib/new_relic/agent/transaction_sample_builder.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/collection_helper'
require 'new_relic/transaction_sample'
require 'new_relic/control'
@@ -21,22 +25,25 @@ module NewRelic
def sample_id
@sample.sample_id
end
-
+
def ignored?
@ignore || @sample.params[:path].nil?
end
-
+
def ignore_transaction
@ignore = true
end
+ def segment_limit
+ Agent.config[:'transaction_tracer.limit_segments']
+ end
+
def trace_entry(metric_name, time)
- segment_limit = Agent.config[:'transaction_tracer.limit_segments']
- if @sample.count_segments < segment_limit
+ if @sample.count_segments < segment_limit()
segment = @sample.create_segment(time.to_f - @sample_start, metric_name)
@current_segment.add_called_segment(segment)
@current_segment = segment
- if @sample.count_segments == segment_limit
+ if @sample.count_segments == segment_limit()
::NewRelic::Agent.logger.debug("Segment limit of #{segment_limit} reached, ceasing collection.")
end
@current_segment
@@ -44,7 +51,7 @@ module NewRelic
end
def trace_exit(metric_name, time)
- return unless @sample.count_segments < Agent.config[:'transaction_tracer.limit_segments']
+ return unless @sample.count_segments < segment_limit()
if metric_name != @current_segment.metric_name
fail "unbalanced entry/exit: #{metric_name} != #{@current_segment.metric_name}"
end
@@ -52,7 +59,7 @@ module NewRelic
@current_segment = @current_segment.parent_segment
end
- def finish_trace(time)
+ def finish_trace(time=Time.now.to_f)
# This should never get called twice, but in a rare case that we can't reproduce in house it does.
# log forensics and return gracefully
if @sample.frozen?
@@ -63,7 +70,9 @@ module NewRelic
@sample.params[:custom_params] ||= {}
@sample.params[:custom_params].merge!(normalize_params(NewRelic::Agent::Instrumentation::MetricFrame.custom_parameters))
- @sample.force_persist = NewRelic::Agent::TransactionInfo.get.force_persist_sample?(sample)
+ txn_info = NewRelic::Agent::TransactionInfo.get
+ @sample.force_persist = txn_info.force_persist_sample?(sample)
+ @sample.threshold = txn_info.transaction_trace_threshold
@sample.freeze
@current_segment = nil
end
@@ -106,6 +115,12 @@ module NewRelic
@sample.params[:custom_params][:cpu_time] = cpu_time
end
+ # Set the metric name of the current segment to +new_name+ if
+ def rename_current_segment( new_name )
+ return unless @sample.count_segments < segment_limit()
+ @current_segment.metric_name = new_name
+ end
+
def sample
@sample
end
diff --git a/lib/new_relic/agent/transaction_sampler.rb b/lib/new_relic/agent/transaction_sampler.rb
index 949bc13..39ce723 100644
--- a/lib/new_relic/agent/transaction_sampler.rb
+++ b/lib/new_relic/agent/transaction_sampler.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent'
require 'new_relic/control'
require 'new_relic/agent/transaction_sample_builder'
@@ -116,6 +120,12 @@ module NewRelic
end
end
+ # Rename the latest scope's segment in the builder to +new_name+.
+ def rename_scope_segment( new_name )
+ return unless builder
+ builder.rename_current_segment( new_name )
+ end
+
# Defaults to zero, otherwise delegated to the transaction
# sample builder
def scope_depth
@@ -204,7 +214,8 @@ module NewRelic
# Sets @slowest_sample to the passed in sample if it is slower
# than the current sample in @slowest_sample
def store_slowest_sample(sample)
- if slowest_sample?(@slowest_sample, sample)
+ if slowest_sample?(@slowest_sample, sample) && sample.threshold &&
+ sample.duration >= sample.threshold
@slowest_sample = sample
end
end
@@ -256,7 +267,7 @@ module NewRelic
return unless builder
segment = builder.current_segment
if segment
- new_message = truncate_message(append_new_message(segment[key],
+ new_message = self.class.truncate_message(append_new_message(segment[key],
message))
if key == :sql && config.respond_to?(:has_key?) && config.has_key?(:adapter)
segment[key] = Database::Statement.new(new_message)
@@ -274,7 +285,7 @@ module NewRelic
# Truncates the message to `MAX_DATA_LENGTH` if needed, and
# appends an ellipsis because it makes the trucation clearer in
# the UI
- def truncate_message(message)
+ def self.truncate_message(message)
if message.length > (MAX_DATA_LENGTH - 4)
message[0..MAX_DATA_LENGTH - 4] + '...'
else
@@ -319,6 +330,12 @@ module NewRelic
notice_extra_data(key, duration, :key)
end
+ # Set parameters on the current segment.
+ def add_segment_parameters( params )
+ return unless builder
+ builder.current_segment.params.merge!( params )
+ end
+
# Every 1/n harvests, adds the most recent sample to the harvest
# array if it exists. Makes sure that the random sample is not
# also the slowest sample for this harvest by `uniq!`ing the
@@ -352,13 +369,7 @@ module NewRelic
force_persist.each {|sample| store_force_persist(sample)}
-
- # Now get the slowest sample
- if @slowest_sample &&
- @slowest_sample.duration >=
- Agent.config[:'transaction_tracer.transaction_threshold']
- result << @slowest_sample
- end
+ result << @slowest_sample if @slowest_sample
result.compact!
result = result.sort_by { |x| x.duration }
diff --git a/lib/new_relic/agent/worker_loop.rb b/lib/new_relic/agent/worker_loop.rb
index 22813d2..54d4143 100644
--- a/lib/new_relic/agent/worker_loop.rb
+++ b/lib/new_relic/agent/worker_loop.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'thread'
module NewRelic
module Agent
diff --git a/lib/new_relic/coerce.rb b/lib/new_relic/coerce.rb
index 3af96a8..46e4db1 100644
--- a/lib/new_relic/coerce.rb
+++ b/lib/new_relic/coerce.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
# We really don't want to send bad values to the collector, and it doesn't
# accept types like Rational that have occasionally slipped into our data.
diff --git a/lib/new_relic/collection_helper.rb b/lib/new_relic/collection_helper.rb
index 2abd7c9..a240ab4 100644
--- a/lib/new_relic/collection_helper.rb
+++ b/lib/new_relic/collection_helper.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control'
module NewRelic
diff --git a/lib/new_relic/command.rb b/lib/new_relic/command.rb
index ae5d8ce..809414f 100644
--- a/lib/new_relic/command.rb
+++ b/lib/new_relic/command.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'optparse'
# Run the command given by the first argument. Right
diff --git a/lib/new_relic/commands/deployments.rb b/lib/new_relic/commands/deployments.rb
index a2753ef..3fef35c 100644
--- a/lib/new_relic/commands/deployments.rb
+++ b/lib/new_relic/commands/deployments.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# This is a class for executing commands related to deployment
# events. It runs without loading the rails environment
diff --git a/lib/new_relic/commands/install.rb b/lib/new_relic/commands/install.rb
index 361375c..bbd18a2 100644
--- a/lib/new_relic/commands/install.rb
+++ b/lib/new_relic/commands/install.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'fileutils'
require 'new_relic/version'
require 'erb'
diff --git a/lib/new_relic/control.rb b/lib/new_relic/control.rb
index 9ac749e..7ccb67f 100644
--- a/lib/new_relic/control.rb
+++ b/lib/new_relic/control.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'yaml'
require 'conditional_vendored_metric_parser'
require 'conditional_vendored_dependency_detection'
diff --git a/lib/new_relic/control/class_methods.rb b/lib/new_relic/control/class_methods.rb
index ac93c09..a6a0385 100644
--- a/lib/new_relic/control/class_methods.rb
+++ b/lib/new_relic/control/class_methods.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
class Control
# class-level methods for lazy creation of NewRelic::Control and
diff --git a/lib/new_relic/control/frameworks.rb b/lib/new_relic/control/frameworks.rb
index 5950b77..36056a6 100644
--- a/lib/new_relic/control/frameworks.rb
+++ b/lib/new_relic/control/frameworks.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
class Control
# Contains subclasses of NewRelic::Control that are used when
diff --git a/lib/new_relic/control/frameworks/external.rb b/lib/new_relic/control/frameworks/external.rb
index d5a88d1..62dbc4f 100755
--- a/lib/new_relic/control/frameworks/external.rb
+++ b/lib/new_relic/control/frameworks/external.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control/frameworks/ruby'
module NewRelic
class Control
diff --git a/lib/new_relic/control/frameworks/merb.rb b/lib/new_relic/control/frameworks/merb.rb
index cce7efc..ec0059e 100644
--- a/lib/new_relic/control/frameworks/merb.rb
+++ b/lib/new_relic/control/frameworks/merb.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
class Control
module Frameworks
diff --git a/lib/new_relic/control/frameworks/rails.rb b/lib/new_relic/control/frameworks/rails.rb
index 3ed3aab..5cc96b4 100644
--- a/lib/new_relic/control/frameworks/rails.rb
+++ b/lib/new_relic/control/frameworks/rails.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control/frameworks/ruby'
module NewRelic
class Control
@@ -121,43 +125,6 @@ module NewRelic
File.join(root,'vendor','rails')
end
- def rails_gem_list
- ::Rails.configuration.gems.map do |gem|
- version = (gem.respond_to?(:version) && gem.version) ||
- (gem.specification.respond_to?(:version) && gem.specification.version)
- gem.name + (version ? "(#{version})" : "")
- end
- end
-
- # Collect the Rails::Info into an associative array as well as the list of plugins
- def append_environment_info
- local_env.append_environment_value('Rails version'){ ::Rails::VERSION::STRING }
- if rails_version >= NewRelic::VersionNumber.new('2.2.0')
- local_env.append_environment_value('Rails threadsafe') do
- ::Rails.configuration.action_controller.allow_concurrency == true
- end
- end
- local_env.append_environment_value('Rails Env') { ENV['RAILS_ENV'] }
- if rails_version >= NewRelic::VersionNumber.new('2.1.0')
- local_env.append_gem_list do
- (bundler_gem_list + rails_gem_list).uniq
- end
- # The plugins is configured manually. If it's nil, it loads everything non-deterministically
- if ::Rails.configuration.plugins
- local_env.append_plugin_list { ::Rails.configuration.plugins }
- else
- ::Rails.configuration.plugin_paths.each do |path|
- local_env.append_plugin_list { Dir[File.join(path, '*')].collect{ |p| File.basename p if File.directory? p }.compact }
- end
- end
- else
- # Rails prior to 2.1, can't get the gems. Find plugins in the default location
- local_env.append_plugin_list do
- Dir[File.join(root, 'vendor', 'plugins', '*')].collect{ |p| File.basename p if File.directory? p }.compact
- end
- end
- end
-
def install_shim
super
require 'new_relic/agent/instrumentation/controller_instrumentation'
diff --git a/lib/new_relic/control/frameworks/rails3.rb b/lib/new_relic/control/frameworks/rails3.rb
index 30213af..cc6aa6d 100644
--- a/lib/new_relic/control/frameworks/rails3.rb
+++ b/lib/new_relic/control/frameworks/rails3.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control/frameworks/rails'
require 'new_relic/rack/error_collector'
module NewRelic
@@ -42,23 +46,6 @@ module NewRelic
protected
- # Collect the Rails::Info into an associative array as well as the list of plugins
- def append_environment_info
- local_env.append_environment_value('Rails version'){ ::Rails::VERSION::STRING }
- local_env.append_environment_value('Rails threadsafe') do
- true == ::Rails.configuration.action_controller.allow_concurrency
- end
- local_env.append_environment_value('Rails Env') { env }
- local_env.append_gem_list do
- bundler_gem_list
- end
- append_plugin_list
- end
-
- def append_plugin_list
- local_env.append_plugin_list { ::Rails.configuration.plugins.to_a }
- end
-
def install_shim
super
ActiveSupport.on_load(:action_controller) do
diff --git a/lib/new_relic/control/frameworks/rails4.rb b/lib/new_relic/control/frameworks/rails4.rb
index 6db6803..d16a348 100644
--- a/lib/new_relic/control/frameworks/rails4.rb
+++ b/lib/new_relic/control/frameworks/rails4.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control/frameworks/rails3'
require 'new_relic/rack/error_collector'
module NewRelic
diff --git a/lib/new_relic/control/frameworks/ruby.rb b/lib/new_relic/control/frameworks/ruby.rb
index d094d65..a205a0e 100644
--- a/lib/new_relic/control/frameworks/ruby.rb
+++ b/lib/new_relic/control/frameworks/ruby.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
class Control
module Frameworks
diff --git a/lib/new_relic/control/frameworks/sinatra.rb b/lib/new_relic/control/frameworks/sinatra.rb
index 8f33e3f..6b6c22c 100755
--- a/lib/new_relic/control/frameworks/sinatra.rb
+++ b/lib/new_relic/control/frameworks/sinatra.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control/frameworks/ruby'
module NewRelic
diff --git a/lib/new_relic/control/instance_methods.rb b/lib/new_relic/control/instance_methods.rb
index 46b48e0..1a5782b 100644
--- a/lib/new_relic/control/instance_methods.rb
+++ b/lib/new_relic/control/instance_methods.rb
@@ -1,4 +1,9 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/language_support'
+require 'new_relic/agent/null_logger'
require 'new_relic/agent/agent_logger'
module NewRelic
@@ -70,8 +75,6 @@ module NewRelic
start_agent
install_instrumentation
load_samplers unless Agent.config[:disable_samplers]
- local_env.gather_environment_info
- append_environment_info
elsif !Agent.config[:agent_enabled]
install_shim
end
diff --git a/lib/new_relic/control/instrumentation.rb b/lib/new_relic/control/instrumentation.rb
index 9056d0a..7675ed2 100644
--- a/lib/new_relic/control/instrumentation.rb
+++ b/lib/new_relic/control/instrumentation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
class Control
# Contains methods that relate to adding and executing files that
diff --git a/lib/new_relic/control/profiling.rb b/lib/new_relic/control/profiling.rb
index 59aa30f..c0966fb 100644
--- a/lib/new_relic/control/profiling.rb
+++ b/lib/new_relic/control/profiling.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
class Control
module Profiling
diff --git a/lib/new_relic/control/server_methods.rb b/lib/new_relic/control/server_methods.rb
index a577297..275bcc2 100644
--- a/lib/new_relic/control/server_methods.rb
+++ b/lib/new_relic/control/server_methods.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
class Control
diff --git a/lib/new_relic/delayed_job_injection.rb b/lib/new_relic/delayed_job_injection.rb
index 1372de0..df42279 100644
--- a/lib/new_relic/delayed_job_injection.rb
+++ b/lib/new_relic/delayed_job_injection.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'dependency_detection'
# This installs some code to manually start the agent when a delayed
# job worker starts. It's not really instrumentation. It's more like
diff --git a/lib/new_relic/environment_report.rb b/lib/new_relic/environment_report.rb
new file mode 100644
index 0000000..9bacb73
--- /dev/null
+++ b/lib/new_relic/environment_report.rb
@@ -0,0 +1,159 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+module NewRelic
+ # The EnvironmentReport is responsible for analyzing the application's
+ # environment and generating the data for the Environment Report in New
+ # Relic's interface.
+ #
+ # It contains useful system information like Ruby version, OS, loaded gems,
+ # etc.
+ #
+ # Additional logic can be registered by using the EnvironmentReport.report_on
+ # hook.
+ class EnvironmentReport
+
+ # This is the main interface for registering logic that should be included
+ # in the Environment Report. For example:
+ #
+ # EnvironmentReport.report_on "Day of week" do
+ # Time.now.strftime("%A")
+ # end
+ #
+ # The passed blocks will be run in EnvironmentReport instances on #initialize.
+ #
+ # Errors raised in passed blocks will be handled and logged at debug, so it
+ # is safe to report on things that may not work in certain environments.
+ #
+ # The blocks should only return strings or arrays full of strings. Falsey
+ # values will be ignored.
+ def self.report_on(key, &block)
+ registered_reporters[key] = block
+ end
+
+ def self.registered_reporters
+ @registered_reporters ||= Hash.new
+ end
+
+ # allow the logic to be swapped out in tests
+ def self.registered_reporters=(logic)
+ @registered_reporters = logic
+ end
+
+ # register reporting logic
+ ####################################
+ report_on 'Gems' do
+ begin
+ Bundler.rubygems.all_specs.map { |gem| "#{gem.name}(#{gem.version})" }
+ rescue
+ # There are certain rubygem, bundler, rails combinations (e.g. gem
+ # 1.6.2, rails 2.3, bundler 1.2.3) where the code above throws an error
+ # in bundler because of rails monkey patching gem. The below does work
+ # though so try it if the above fails.
+ Bundler.load.specs.map do | spec |
+ version = (spec.respond_to?(:version) && spec.version)
+ spec.name + (version ? "(#{version})" : "")
+ end
+ end
+ end
+ report_on('Plugin List'){ ::Rails.configuration.plugins.to_a }
+ report_on('Ruby version'){ RUBY_VERSION }
+ report_on('Ruby description'){ RUBY_DESCRIPTION }
+ report_on('Ruby platform'){ RUBY_PLATFORM }
+ report_on('Ruby patchlevel'){ RUBY_PATCHLEVEL.to_s }
+ report_on('JRuby version') { JRUBY_VERSION }
+ report_on('Java VM version') { ENV_JAVA['java.vm.version']}
+ report_on 'Processors' do
+ cpuinfo = ''
+ proc_file = '/proc/cpuinfo'
+ File.open(proc_file) do |f|
+ loop do
+ begin
+ cpuinfo << f.read_nonblock(4096).strip
+ rescue EOFError
+ break
+ rescue Errno::EWOULDBLOCK, Errno::EAGAIN
+ cpuinfo = ''
+ break # don't select file handle, just give up
+ end
+ end
+ end
+ processors = cpuinfo.split("\n").select {|line| line =~ /^processor\s*:/ }.size
+
+ if processors == 0
+ processors = nil # assume there is at least one processor
+ ::NewRelic::Agent.logger.warn("Cannot determine the number of processors in #{proc_file}")
+ end
+ processors
+ end
+ report_on 'Arch' do
+ arch = `uname -p`
+ arch = ENV['PROCESSOR_ARCHITECTURE'] if arch == ''
+ arch
+ end
+ report_on('OS version'){ `uname -v` }
+ report_on('OS') do
+ os = `uname -s`
+ os = ENV['OS'] if os == ''
+ os
+ end
+ report_on 'Database adapter' do
+ ActiveRecord::Base.configurations[NewRelic::Control.instance.env]['adapter']
+ end
+ report_on('Framework') { Agent.config[:framework].to_s }
+ report_on('Dispatcher') { Agent.config[:dispatcher].to_s }
+ report_on('Environment') { NewRelic::Control.instance.env }
+ report_on('Rails version') { ::Rails::VERSION::STRING }
+ report_on 'Rails threadsafe' do
+ ::Rails.configuration.action_controller.allow_concurrency
+ end
+ report_on 'Rails Env' do
+ if defined? ::Rails and ::Rails.respond_to?(:env)
+ ::Rails.env
+ else
+ ENV['RAILS_ENV']
+ end
+ end
+ # end reporting logic
+ ####################################
+
+
+ attr_reader :data
+ # Generate the report based on the class level logic.
+ def initialize
+ @data = self.class.registered_reporters.inject(Hash.new) do |data, (key, logic)|
+ begin
+ value = logic.call
+ if value
+ data[key] = value
+
+ Agent.record_metric("Supportability/EnvironmentReport/success", 0.0)
+ Agent.record_metric("Supportability/EnvironmentReport/success/#{key}", 0.0)
+ else
+ Agent.logger.debug("EnvironmentReport ignoring value for #{key.inspect} which came back falsey: #{value.inspect}")
+ Agent.record_metric("Supportability/EnvironmentReport/empty", 0.0)
+ Agent.record_metric("Supportability/EnvironmentReport/empty/#{key}", 0.0)
+ end
+ rescue => e
+ Agent.logger.debug("EnvironmentReport failed to retrieve value for #{key.inspect}: #{e}")
+ Agent.record_metric("Supportability/EnvironmentReport/error", 0.0)
+ Agent.record_metric("Supportability/EnvironmentReport/error/#{key}", 0.0)
+ end
+ data
+ end
+ end
+
+ def [](key)
+ @data[key]
+ end
+
+ def []=(key, value)
+ @data[key] = value
+ end
+
+ def to_a
+ @data.to_a
+ end
+ end
+end
diff --git a/lib/new_relic/helper.rb b/lib/new_relic/helper.rb
index ef759c6..c9f483a 100644
--- a/lib/new_relic/helper.rb
+++ b/lib/new_relic/helper.rb
@@ -1,3 +1,8 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+
module NewRelic
# A singleton for shared generic helper methods
module Helper
@@ -30,5 +35,39 @@ module NewRelic
def time_to_millis(time)
(time.to_f * 1000).round
end
+
+ def milliseconds_to_seconds(milliseconds)
+ milliseconds / 1000.0
+ end
+ end
+
+ # Load the JSON library from the standard library.
+ def self::load_stdlib_json
+ # Don't even try to use 1.9.1's json.
+ return false if RUBY_VERSION == '1.9.1'
+
+ require 'json'
+ define_method( :json_dump, &::JSON.method(:dump) )
+ define_method( :json_load, &::JSON.method(:parse) )
+
+ return true
+ rescue LoadError
+ NewRelic::Agent.logger.debug "%p while loading JSON library: %s" % [ err, err.message ] if
+ defined?( NewRelic::Agent ) && NewRelic::Agent.respond_to?( :logger )
+ return false
end
+
+
+ # Load the fallback JSON library
+ def self::load_okjson
+ require 'new_relic/okjson'
+ define_method( :json_dump, &::NewRelic::OkJson.method(:encode) )
+ define_method( :json_load, &::NewRelic::OkJson.method(:decode) )
+ end
+
+
+ load_stdlib_json or load_okjson
+ module_function :json_dump, :json_load
+
end
+
diff --git a/lib/new_relic/language_support.rb b/lib/new_relic/language_support.rb
index 6283780..5fda5a5 100644
--- a/lib/new_relic/language_support.rb
+++ b/lib/new_relic/language_support.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic::LanguageSupport
extend self
diff --git a/lib/new_relic/latest_changes.rb b/lib/new_relic/latest_changes.rb
index fca1c08..9ba6dd6 100644
--- a/lib/new_relic/latest_changes.rb
+++ b/lib/new_relic/latest_changes.rb
@@ -1,4 +1,8 @@
#!/usr/bin/ruby
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module LatestChanges
@@ -21,7 +25,7 @@ EOS
version_count += 1
end
break if version_count >= 2
- changes << line.chomp
+ changes << line.sub(/^ /, "").chomp
end
changes << footer
diff --git a/lib/new_relic/local_environment.rb b/lib/new_relic/local_environment.rb
index 9f3519b..1750a77 100644
--- a/lib/new_relic/local_environment.rb
+++ b/lib/new_relic/local_environment.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'set'
require 'new_relic/version'
@@ -39,33 +43,7 @@ module NewRelic
@config = Hash.new
end
- # Add the given key/value pair to the app environment
- # settings. Must pass either a value or a block. Block
- # is called to get the value and any raised errors are
- # silently ignored.
- def append_environment_value(name, value = nil)
- value = yield if block_given?
- @config[name] = value if value
- rescue => e
- Agent.logger.error e
- end
-
- # yields to the block and appends the returned value to the list
- # of gems - this catches errors that might be raised in the block
- def append_gem_list
- @gems += yield
- rescue => e
- Agent.logger.error e
- end
-
- # yields to the block and appends the returned value to the list
- # of plugins - this catches errors that might be raised in the block
- def append_plugin_list
- @plugins += yield
- rescue => e
- Agent.logger.error e
- end
-
+
# An instance id pulled from either @dispatcher_instance_id or by
# splitting out the first part of the running file
def dispatcher_instance_id
@@ -76,149 +54,13 @@ module NewRelic
end
@dispatcher_instance_id
end
-
- # Interrogates some common ruby constants for useful information
- # about what kind of ruby environment the agent is running in
- def gather_ruby_info
- append_environment_value('Ruby version'){ RUBY_VERSION }
- append_environment_value('Ruby description'){ RUBY_DESCRIPTION } if defined? ::RUBY_DESCRIPTION
- append_environment_value('Ruby platform') { RUBY_PLATFORM }
- append_environment_value('Ruby patchlevel') { RUBY_PATCHLEVEL }
- # room here for other ruby implementations, when.
- if defined? ::JRUBY_VERSION
- gather_jruby_info
- end
- end
-
- # like gather_ruby_info but for the special case of JRuby
- def gather_jruby_info
- append_environment_value('JRuby version') { JRUBY_VERSION }
- append_environment_value('Java VM version') { ENV_JAVA['java.vm.version']}
- end
-
- # See what the number of cpus is, works only on some linux variants
- def gather_cpu_info(proc_file='/proc/cpuinfo')
- return unless File.readable? proc_file
- @processors = append_environment_value('Processors') do
- cpuinfo = ''
- File.open(proc_file) do |f|
- loop do
- begin
- cpuinfo << f.read_nonblock(4096).strip
- rescue EOFError
- break
- rescue Errno::EWOULDBLOCK, Errno::EAGAIN
- cpuinfo = ''
- break # don't select file handle, just give up
- end
- end
- end
- processors = cpuinfo.split("\n").select {|line| line =~ /^processor\s*:/ }.size
- if processors == 0
- processors = 1 # assume there is at least one processor
- ::NewRelic::Agent.logger.warn("Cannot determine the number of processors in #{proc_file}")
- end
- processors
- end
- end
-
- # Grabs the architecture string from either `uname -p` or the env
- # variable PROCESSOR_ARCHITECTURE
- def gather_architecture_info
- append_environment_value('Arch') { `uname -p` } ||
- append_environment_value('Arch') { ENV['PROCESSOR_ARCHITECTURE'] }
- end
-
- # gathers OS info from either `uname -v`, `uname -s`, or the OS
- # env variable
- def gather_os_info
- append_environment_value('OS version') { `uname -v` }
- append_environment_value('OS') { `uname -s` } ||
- append_environment_value('OS') { ENV['OS'] }
- end
-
- # Gathers the architecture and cpu info
- def gather_system_info
- gather_architecture_info
- gather_cpu_info
- end
-
- # Looks for a capistrano file indicating the current revision
- def gather_revision_info
- rev_file = File.join(NewRelic::Control.instance.root, "REVISION")
- if File.readable?(rev_file) && File.size(rev_file) < 64
- append_environment_value('Revision') do
- File.open(rev_file) { | file | file.readline.strip }
- end
- end
- end
-
- # The name of the AR database adapter for the current environment and
- # the current schema version
- def gather_ar_adapter_info
-
- append_environment_value 'Database adapter' do
- if defined?(ActiveRecord) && defined?(ActiveRecord::Base) &&
- ActiveRecord::Base.respond_to?(:configurations)
- config = ActiveRecord::Base.configurations[NewRelic::Control.instance.env]
- if config
- config['adapter']
- end
- end
- end
- end
-
- # Datamapper version
- def gather_dm_adapter_info
- append_environment_value 'DataMapper version' do
- require 'dm-core/version'
- DataMapper::VERSION
- end
- end
-
- # sensing for which adapter is defined, then appends the relevant
- # config information
- def gather_db_info
- # room here for more database adapters, when.
- if defined? ::ActiveRecord
- gather_ar_adapter_info
- end
- if defined? ::DataMapper
- gather_dm_adapter_info
- end
- end
-
- # Collect base statistics about the environment and record them for
- # comparison and change detection.
- def gather_environment_info
- append_environment_value 'Framework', Agent.config[:framework].to_s
- append_environment_value 'Dispatcher', Agent.config[:dispatcher].to_s if Agent.config[:dispatcher]
- append_environment_value 'Dispatcher instance id', @dispatcher_instance_id if @dispatcher_instance_id
- append_environment_value('Environment') { NewRelic::Control.instance.env }
-
- # miscellaneous other helpful debugging information
- gather_ruby_info
- gather_system_info
- gather_revision_info
- gather_db_info
- end
-
- # Take a snapshot of the environment information for this application
- # Returns an associative array
- def snapshot
- i = @config.to_a
- i << [ 'Plugin List', @plugins.to_a] if not @plugins.empty?
- i << [ 'Gems', @gems.to_a] if not @gems.empty?
- i
- end
-
# it's a working jruby if it has the runtime method, and object
# space is enabled
def working_jruby?
!(defined?(::JRuby) && JRuby.respond_to?(:runtime) && !JRuby.runtime.is_object_space_enabled)
end
-
+
# Runs through all the objects in ObjectSpace to find the first one that
# match the provided class
def find_class_in_object_space(klass)
@@ -227,7 +69,7 @@ module NewRelic
end
return nil
end
-
+
# Sets the @mongrel instance variable if we can find a Mongrel::HttpServer
def mongrel
return @mongrel if @mongrel
@@ -236,13 +78,13 @@ module NewRelic
end
@mongrel
end
-
+
private
# Although you can override the dispatcher with NEWRELIC_DISPATCHER this
# is not advisable since it implies certain api's being available.
def discover_dispatcher
- dispatchers = %w[passenger torquebox trinidad glassfish thin mongrel litespeed webrick fastcgi unicorn sinatra]
+ dispatchers = %w[passenger torquebox trinidad glassfish resque sidekiq thin mongrel litespeed webrick fastcgi rainbows unicorn]
while dispatchers.any? && @discovered_dispatcher.nil?
send 'check_for_'+(dispatchers.shift)
end
@@ -316,20 +158,26 @@ module NewRelic
end
end
- def check_for_sinatra
- return unless defined?(::Sinatra) && defined?(::Sinatra::Base)
-
- begin
- version = ::Sinatra::VERSION
- rescue
- $stderr.puts("Error determining Sinatra version")
+ def check_for_rainbows
+ if (defined?(::Rainbows) && defined?(::Rainbows::HttpServer)) && working_jruby?
+ v = find_class_in_object_space(::Rainbows::HttpServer)
+ @discovered_dispatcher = :rainbows if v
end
+ end
- if ::NewRelic::VersionNumber.new('0.9.2') > version
- $stderr.puts("Your Sinatra version is #{version}, we highly recommend upgrading to >=0.9.2")
- end
+ def check_for_resque
+ using_resque = (
+ defined?(::Resque) &&
+ (ENV['QUEUE'] || ENV['QUEUES']) &&
+ (File.basename($0) == 'rake' && ARGV.include?('resque:work'))
+ )
+ @discovered_dispatcher = :resque if using_resque
+ end
- @discovered_dispatcher = :sinatra
+ def check_for_sidekiq
+ if defined?(::Sidekiq) && File.basename($0) == 'sidekiq'
+ @discovered_dispatcher = :sidekiq
+ end
end
def check_for_thin
diff --git a/lib/new_relic/merbtasks.rb b/lib/new_relic/merbtasks.rb
index 42fcd38..8784cd2 100644
--- a/lib/new_relic/merbtasks.rb
+++ b/lib/new_relic/merbtasks.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
namespace :newrelic do
desc "Install the developer mode newrelic.yml file"
task :default do
diff --git a/lib/new_relic/metric_data.rb b/lib/new_relic/metric_data.rb
index e41ebd2..262372b 100644
--- a/lib/new_relic/metric_data.rb
+++ b/lib/new_relic/metric_data.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/coerce'
module NewRelic
diff --git a/lib/new_relic/metric_spec.rb b/lib/new_relic/metric_spec.rb
index cb489ff..cb9cb41 100644
--- a/lib/new_relic/metric_spec.rb
+++ b/lib/new_relic/metric_spec.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# this struct uniquely defines a metric, optionally inside
# the call scope of another metric
class NewRelic::MetricSpec
diff --git a/lib/new_relic/metrics.rb b/lib/new_relic/metrics.rb
index 8fbb6a9..287b5f9 100644
--- a/lib/new_relic/metrics.rb
+++ b/lib/new_relic/metrics.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Metrics
CONTROLLER = "Controller"
diff --git a/lib/new_relic/noticed_error.rb b/lib/new_relic/noticed_error.rb
index 391fcd9..37de168 100644
--- a/lib/new_relic/noticed_error.rb
+++ b/lib/new_relic/noticed_error.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/helper'
# This class encapsulates an error that was noticed by New Relic in a managed app.
diff --git a/lib/new_relic/okjson.rb b/lib/new_relic/okjson.rb
new file mode 100644
index 0000000..5d9b71a
--- /dev/null
+++ b/lib/new_relic/okjson.rb
@@ -0,0 +1,602 @@
+# encoding: utf-8
+# This file is distributed under okjson's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+#
+# Copyright 2011, 2012 Keith Rarick
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+
+# See https://github.com/kr/okjson for updates.
+
+require 'stringio'
+
+# Some parts adapted from
+# http://golang.org/src/pkg/json/decode.go and
+# http://golang.org/src/pkg/utf8/utf8.go
+module NewRelic
+module OkJson
+ Upstream = 'LTD7LBKLZWFF7OZK'
+ extend self
+
+
+ # Decodes a json document in string s and
+ # returns the corresponding ruby value.
+ # String s must be valid UTF-8. If you have
+ # a string in some other encoding, convert
+ # it first.
+ #
+ # String values in the resulting structure
+ # will be UTF-8.
+ def decode(s)
+ ts = lex(s)
+ v, ts = textparse(ts)
+ if ts.length > 0
+ raise Error, 'trailing garbage'
+ end
+ v
+ end
+
+
+ # Parses a "json text" in the sense of RFC 4627.
+ # Returns the parsed value and any trailing tokens.
+ # Note: this is almost the same as valparse,
+ # except that it does not accept atomic values.
+ def textparse(ts)
+ if ts.length < 0
+ raise Error, 'empty'
+ end
+
+ typ, _, val = ts[0]
+ case typ
+ when '{' then objparse(ts)
+ when '[' then arrparse(ts)
+ else
+ raise Error, "unexpected #{val.inspect}"
+ end
+ end
+
+
+ # Parses a "value" in the sense of RFC 4627.
+ # Returns the parsed value and any trailing tokens.
+ def valparse(ts)
+ if ts.length < 0
+ raise Error, 'empty'
+ end
+
+ typ, _, val = ts[0]
+ case typ
+ when '{' then objparse(ts)
+ when '[' then arrparse(ts)
+ when :val,:str then [val, ts[1..-1]]
+ else
+ raise Error, "unexpected #{val.inspect}"
+ end
+ end
+
+
+ # Parses an "object" in the sense of RFC 4627.
+ # Returns the parsed value and any trailing tokens.
+ def objparse(ts)
+ ts = eat('{', ts)
+ obj = {}
+
+ if ts[0][0] == '}'
+ return obj, ts[1..-1]
+ end
+
+ k, v, ts = pairparse(ts)
+ obj[k] = v
+
+ if ts[0][0] == '}'
+ return obj, ts[1..-1]
+ end
+
+ loop do
+ ts = eat(',', ts)
+
+ k, v, ts = pairparse(ts)
+ obj[k] = v
+
+ if ts[0][0] == '}'
+ return obj, ts[1..-1]
+ end
+ end
+ end
+
+
+ # Parses a "member" in the sense of RFC 4627.
+ # Returns the parsed values and any trailing tokens.
+ def pairparse(ts)
+ (typ, _, k), ts = ts[0], ts[1..-1]
+ if typ != :str
+ raise Error, "unexpected #{k.inspect}"
+ end
+ ts = eat(':', ts)
+ v, ts = valparse(ts)
+ [k, v, ts]
+ end
+
+
+ # Parses an "array" in the sense of RFC 4627.
+ # Returns the parsed value and any trailing tokens.
+ def arrparse(ts)
+ ts = eat('[', ts)
+ arr = []
+
+ if ts[0][0] == ']'
+ return arr, ts[1..-1]
+ end
+
+ v, ts = valparse(ts)
+ arr << v
+
+ if ts[0][0] == ']'
+ return arr, ts[1..-1]
+ end
+
+ loop do
+ ts = eat(',', ts)
+
+ v, ts = valparse(ts)
+ arr << v
+
+ if ts[0][0] == ']'
+ return arr, ts[1..-1]
+ end
+ end
+ end
+
+
+ def eat(typ, ts)
+ if ts[0][0] != typ
+ raise Error, "expected #{typ} (got #{ts[0].inspect})"
+ end
+ ts[1..-1]
+ end
+
+
+ # Scans s and returns a list of json tokens,
+ # excluding white space (as defined in RFC 4627).
+ def lex(s)
+ ts = []
+ while s.length > 0
+ typ, lexeme, val = tok(s)
+ if typ == nil
+ raise Error, "invalid character at #{s[0,10].inspect}"
+ end
+ if typ != :space
+ ts << [typ, lexeme, val]
+ end
+ s = s[lexeme.length..-1]
+ end
+ ts
+ end
+
+
+ # Scans the first token in s and
+ # returns a 3-element list, or nil
+ # if s does not begin with a valid token.
+ #
+ # The first list element is one of
+ # '{', '}', ':', ',', '[', ']',
+ # :val, :str, and :space.
+ #
+ # The second element is the lexeme.
+ #
+ # The third element is the value of the
+ # token for :val and :str, otherwise
+ # it is the lexeme.
+ def tok(s)
+ case s[0]
+ when ?{ then ['{', s[0,1], s[0,1]]
+ when ?} then ['}', s[0,1], s[0,1]]
+ when ?: then [':', s[0,1], s[0,1]]
+ when ?, then [',', s[0,1], s[0,1]]
+ when ?[ then ['[', s[0,1], s[0,1]]
+ when ?] then [']', s[0,1], s[0,1]]
+ when ?n then nulltok(s)
+ when ?t then truetok(s)
+ when ?f then falsetok(s)
+ when ?" then strtok(s)
+ when Spc then [:space, s[0,1], s[0,1]]
+ when ?\t then [:space, s[0,1], s[0,1]]
+ when ?\n then [:space, s[0,1], s[0,1]]
+ when ?\r then [:space, s[0,1], s[0,1]]
+ else numtok(s)
+ end
+ end
+
+
+ def nulltok(s); s[0,4] == 'null' ? [:val, 'null', nil] : [] end
+ def truetok(s); s[0,4] == 'true' ? [:val, 'true', true] : [] end
+ def falsetok(s); s[0,5] == 'false' ? [:val, 'false', false] : [] end
+
+
+ def numtok(s)
+ m = /-?([1-9][0-9]+|[0-9])([.][0-9]+)?([eE][+-]?[0-9]+)?/.match(s)
+ if m && m.begin(0) == 0
+ if m[3] && !m[2]
+ [:val, m[0], Integer(m[1])*(10**Integer(m[3][1..-1]))]
+ elsif m[2]
+ [:val, m[0], Float(m[0])]
+ else
+ [:val, m[0], Integer(m[0])]
+ end
+ else
+ []
+ end
+ end
+
+
+ def strtok(s)
+ m = /"([^"\\]|\\["\/\\bfnrt]|\\u[0-9a-fA-F]{4})*"/.match(s)
+ if ! m
+ raise Error, "invalid string literal at #{abbrev(s)}"
+ end
+ [:str, m[0], unquote(m[0])]
+ end
+
+
+ def abbrev(s)
+ t = s[0,10]
+ p = t['`']
+ t = t[0,p] if p
+ t = t + '...' if t.length < s.length
+ '`' + t + '`'
+ end
+
+
+ # Converts a quoted json string literal q into a UTF-8-encoded string.
+ # The rules are different than for Ruby, so we cannot use eval.
+ # Unquote will raise an error if q contains control characters.
+ def unquote(q)
+ q = q[1...-1]
+ a = q.dup # allocate a big enough string
+ rubydoesenc = false
+ # In ruby >= 1.9, a[w] is a codepoint, not a byte.
+ if a.class.method_defined?(:force_encoding)
+ a.force_encoding('UTF-8')
+ rubydoesenc = true
+ end
+ r, w = 0, 0
+ while r < q.length
+ c = q[r]
+ case true
+ when c == ?\\
+ r += 1
+ if r >= q.length
+ raise Error, "string literal ends with a \"\\\": \"#{q}\""
+ end
+
+ case q[r]
+ when ?",?\\,?/,?'
+ a[w] = q[r]
+ r += 1
+ w += 1
+ when ?b,?f,?n,?r,?t
+ a[w] = Unesc[q[r]]
+ r += 1
+ w += 1
+ when ?u
+ r += 1
+ uchar = begin
+ hexdec4(q[r,4])
+ rescue RuntimeError => e
+ raise Error, "invalid escape sequence \\u#{q[r,4]}: #{e}"
+ end
+ r += 4
+ if surrogate? uchar
+ if q.length >= r+6
+ uchar1 = hexdec4(q[r+2,4])
+ uchar = subst(uchar, uchar1)
+ if uchar != Ucharerr
+ # A valid pair; consume.
+ r += 6
+ end
+ end
+ end
+ if rubydoesenc
+ a[w] = '' << uchar
+ w += 1
+ else
+ w += ucharenc(a, w, uchar)
+ end
+ else
+ raise Error, "invalid escape char #{q[r]} in \"#{q}\""
+ end
+ when c == ?", c < Spc
+ raise Error, "invalid character in string literal \"#{q}\""
+ else
+ # Copy anything else byte-for-byte.
+ # Valid UTF-8 will remain valid UTF-8.
+ # Invalid UTF-8 will remain invalid UTF-8.
+ # In ruby >= 1.9, c is a codepoint, not a byte,
+ # in which case this is still what we want.
+ a[w] = c
+ r += 1
+ w += 1
+ end
+ end
+ a[0,w]
+ end
+
+
+ # Encodes unicode character u as UTF-8
+ # bytes in string a at position i.
+ # Returns the number of bytes written.
+ def ucharenc(a, i, u)
+ case true
+ when u <= Uchar1max
+ a[i] = (u & 0xff).chr
+ 1
+ when u <= Uchar2max
+ a[i+0] = (Utag2 | ((u>>6)&0xff)).chr
+ a[i+1] = (Utagx | (u&Umaskx)).chr
+ 2
+ when u <= Uchar3max
+ a[i+0] = (Utag3 | ((u>>12)&0xff)).chr
+ a[i+1] = (Utagx | ((u>>6)&Umaskx)).chr
+ a[i+2] = (Utagx | (u&Umaskx)).chr
+ 3
+ else
+ a[i+0] = (Utag4 | ((u>>18)&0xff)).chr
+ a[i+1] = (Utagx | ((u>>12)&Umaskx)).chr
+ a[i+2] = (Utagx | ((u>>6)&Umaskx)).chr
+ a[i+3] = (Utagx | (u&Umaskx)).chr
+ 4
+ end
+ end
+
+
+ def hexdec4(s)
+ if s.length != 4
+ raise Error, 'short'
+ end
+ (nibble(s[0])<<12) | (nibble(s[1])<<8) | (nibble(s[2])<<4) | nibble(s[3])
+ end
+
+
+ def subst(u1, u2)
+ if Usurr1 <= u1 && u1 < Usurr2 && Usurr2 <= u2 && u2 < Usurr3
+ return ((u1-Usurr1)<<10) | (u2-Usurr2) + Usurrself
+ end
+ return Ucharerr
+ end
+
+
+ def surrogate?(u)
+ Usurr1 <= u && u < Usurr3
+ end
+
+
+ def nibble(c)
+ case true
+ when ?0 <= c && c <= ?9 then c.ord - ?0.ord
+ when ?a <= c && c <= ?z then c.ord - ?a.ord + 10
+ when ?A <= c && c <= ?Z then c.ord - ?A.ord + 10
+ else
+ raise Error, "invalid hex code #{c}"
+ end
+ end
+
+
+ # Encodes x into a json text. It may contain only
+ # Array, Hash, String, Numeric, true, false, nil.
+ # (Note, this list excludes Symbol.)
+ # X itself must be an Array or a Hash.
+ # No other value can be encoded, and an error will
+ # be raised if x contains any other value, such as
+ # Nan, Infinity, Symbol, and Proc, or if a Hash key
+ # is not a String.
+ # Strings contained in x must be valid UTF-8.
+ def encode(x)
+ case x
+ when Hash then objenc(x)
+ when Array then arrenc(x)
+ else
+ raise Error, 'root value must be an Array or a Hash'
+ end
+ end
+
+
+ def valenc(x)
+ case x
+ when Hash then objenc(x)
+ when Array then arrenc(x)
+ when String then strenc(x)
+ when Numeric then numenc(x)
+ when true then "true"
+ when false then "false"
+ when nil then "null"
+ else
+ raise Error, "cannot encode #{x.class}: #{x.inspect}"
+ end
+ end
+
+
+ def objenc(x)
+ '{' + x.map{|k,v| keyenc(k) + ':' + valenc(v)}.join(',') + '}'
+ end
+
+
+ def arrenc(a)
+ '[' + a.map{|x| valenc(x)}.join(',') + ']'
+ end
+
+
+ def keyenc(k)
+ case k
+ when String then strenc(k)
+ else
+ raise Error, "Hash key is not a string: #{k.inspect}"
+ end
+ end
+
+
+ def strenc(s)
+ t = StringIO.new
+ t.putc(?")
+ r = 0
+
+ # In ruby >= 1.9, s[r] is a codepoint, not a byte.
+ rubydoesenc = s.class.method_defined?(:encoding)
+
+ while r < s.length
+ case s[r]
+ when ?" then t.print('\\"')
+ when ?\\ then t.print('\\\\')
+ when ?\b then t.print('\\b')
+ when ?\f then t.print('\\f')
+ when ?\n then t.print('\\n')
+ when ?\r then t.print('\\r')
+ when ?\t then t.print('\\t')
+ else
+ c = s[r]
+ case true
+ when rubydoesenc
+ begin
+ c.ord # will raise an error if c is invalid UTF-8
+ t.write(c)
+ rescue
+ t.write(Ustrerr)
+ end
+ when Spc <= c && c <= ?~
+ t.putc(c)
+ else
+ n = ucharcopy(t, s, r) # ensure valid UTF-8 output
+ r += n - 1 # r is incremented below
+ end
+ end
+ r += 1
+ end
+ t.putc(?")
+ t.string
+ end
+
+
+ def numenc(x)
+ if ((x.nan? || x.infinite?) rescue false)
+ raise Error, "Numeric cannot be represented: #{x}"
+ end
+ "#{x}"
+ end
+
+
+ # Copies the valid UTF-8 bytes of a single character
+ # from string s at position i to I/O object t, and
+ # returns the number of bytes copied.
+ # If no valid UTF-8 char exists at position i,
+ # ucharcopy writes Ustrerr and returns 1.
+ def ucharcopy(t, s, i)
+ n = s.length - i
+ raise Utf8Error if n < 1
+
+ c0 = s[i].ord
+
+ # 1-byte, 7-bit sequence?
+ if c0 < Utagx
+ t.putc(c0)
+ return 1
+ end
+
+ raise Utf8Error if c0 < Utag2 # unexpected continuation byte?
+
+ raise Utf8Error if n < 2 # need continuation byte
+ c1 = s[i+1].ord
+ raise Utf8Error if c1 < Utagx || Utag2 <= c1
+
+ # 2-byte, 11-bit sequence?
+ if c0 < Utag3
+ raise Utf8Error if ((c0&Umask2)<<6 | (c1&Umaskx)) <= Uchar1max
+ t.putc(c0)
+ t.putc(c1)
+ return 2
+ end
+
+ # need second continuation byte
+ raise Utf8Error if n < 3
+
+ c2 = s[i+2].ord
+ raise Utf8Error if c2 < Utagx || Utag2 <= c2
+
+ # 3-byte, 16-bit sequence?
+ if c0 < Utag4
+ u = (c0&Umask3)<<12 | (c1&Umaskx)<<6 | (c2&Umaskx)
+ raise Utf8Error if u <= Uchar2max
+ t.putc(c0)
+ t.putc(c1)
+ t.putc(c2)
+ return 3
+ end
+
+ # need third continuation byte
+ raise Utf8Error if n < 4
+ c3 = s[i+3].ord
+ raise Utf8Error if c3 < Utagx || Utag2 <= c3
+
+ # 4-byte, 21-bit sequence?
+ if c0 < Utag5
+ u = (c0&Umask4)<<18 | (c1&Umaskx)<<12 | (c2&Umaskx)<<6 | (c3&Umaskx)
+ raise Utf8Error if u <= Uchar3max
+ t.putc(c0)
+ t.putc(c1)
+ t.putc(c2)
+ t.putc(c3)
+ return 4
+ end
+
+ raise Utf8Error
+ rescue Utf8Error
+ t.write(Ustrerr)
+ return 1
+ end
+
+
+ class Utf8Error < ::StandardError
+ end
+
+
+ class Error < ::StandardError
+ end
+
+
+ Utagx = 0x80 # 1000 0000
+ Utag2 = 0xc0 # 1100 0000
+ Utag3 = 0xe0 # 1110 0000
+ Utag4 = 0xf0 # 1111 0000
+ Utag5 = 0xF8 # 1111 1000
+ Umaskx = 0x3f # 0011 1111
+ Umask2 = 0x1f # 0001 1111
+ Umask3 = 0x0f # 0000 1111
+ Umask4 = 0x07 # 0000 0111
+ Uchar1max = (1<<7) - 1
+ Uchar2max = (1<<11) - 1
+ Uchar3max = (1<<16) - 1
+ Ucharerr = 0xFFFD # unicode "replacement char"
+ Ustrerr = "\xef\xbf\xbd" # unicode "replacement char"
+ Usurrself = 0x10000
+ Usurr1 = 0xd800
+ Usurr2 = 0xdc00
+ Usurr3 = 0xe000
+
+ Spc = ' '[0]
+ Unesc = {?b=>?\b, ?f=>?\f, ?n=>?\n, ?r=>?\r, ?t=>?\t}
+end
+end
diff --git a/lib/new_relic/rack.rb b/lib/new_relic/rack.rb
index b59e9e1..edeb07a 100644
--- a/lib/new_relic/rack.rb
+++ b/lib/new_relic/rack.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module Rack
end
diff --git a/lib/new_relic/rack/agent_hooks.rb b/lib/new_relic/rack/agent_hooks.rb
index 00b3984..86e66a0 100644
--- a/lib/new_relic/rack/agent_hooks.rb
+++ b/lib/new_relic/rack/agent_hooks.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/agent/event_listener'
module NewRelic::Rack
diff --git a/lib/new_relic/rack/browser_monitoring.rb b/lib/new_relic/rack/browser_monitoring.rb
index 51aade5..d27b8cc 100644
--- a/lib/new_relic/rack/browser_monitoring.rb
+++ b/lib/new_relic/rack/browser_monitoring.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'rack'
module NewRelic::Rack
diff --git a/lib/new_relic/rack/developer_mode.rb b/lib/new_relic/rack/developer_mode.rb
index 565ccac..c53f94e 100644
--- a/lib/new_relic/rack/developer_mode.rb
+++ b/lib/new_relic/rack/developer_mode.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'rack'
require 'rack/request'
require 'rack/response'
diff --git a/lib/new_relic/rack/error_collector.rb b/lib/new_relic/rack/error_collector.rb
index 876db16..d6ad1b8 100644
--- a/lib/new_relic/rack/error_collector.rb
+++ b/lib/new_relic/rack/error_collector.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic::Rack
class ErrorCollector
def initialize(app, options={})
diff --git a/lib/new_relic/recipes.rb b/lib/new_relic/recipes.rb
index 725fb7f..6ead872 100644
--- a/lib/new_relic/recipes.rb
+++ b/lib/new_relic/recipes.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# When installed as a plugin this is loaded automatically.
#
# When installed as a gem, you need to add
diff --git a/lib/new_relic/stats.rb b/lib/new_relic/stats.rb
deleted file mode 100644
index f2b3525..0000000
--- a/lib/new_relic/stats.rb
+++ /dev/null
@@ -1,337 +0,0 @@
-
-module NewRelic
- module Stats
-
- # a stat is absent if its call count equals zero
- def absent?
- call_count == 0
- end
-
- # outputs a useful human-readable time given a value in milliseconds
- def time_str(value_ms)
- case
- when value_ms >= 10000
- "%.1f s" % (value_ms / 1000.0)
- when value_ms >= 5000
- "%.2f s" % (value_ms / 1000.0)
- else
- "%.0f ms" % value_ms
- end
- end
-
- # makes sure we aren't dividing by zero
- def checked_calculation(numerator, denominator)
- if denominator.nil? || denominator == 0
- 0.0
- else
- numerator.to_f / denominator
- end
- end
-
- def average_call_time
- checked_calculation(total_call_time, call_count)
- end
- def average_exclusive_time
- checked_calculation(total_exclusive_time, call_count)
- end
-
- # merge by adding to average response time
- # - used to compose multiple metrics e.g. dispatcher time + mongrel queue time
- def sum_merge! (other_stats)
- Array(other_stats).each do |other|
- self.sum_attributes(other)
- end
- self
- end
-
- def sum_attributes(other)
- update_totals(other)
- stack_min_max_from(other)
- self.call_count = [self.call_count, other.call_count].max
- update_boundaries(other)
- end
-
- def stack_min_max_from(other)
- self.min_call_time += other.min_call_time
- self.max_call_time += other.max_call_time
- end
-
- def update_boundaries(other)
- self.begin_time = other.begin_time if should_replace_begin_time?(other)
- self.end_time = other.end_time if should_replace_end_time?(other)
- end
-
- def should_replace_end_time?(other)
- end_time.to_f < other.end_time.to_f
- end
-
- def should_replace_begin_time?(other)
- other.begin_time.to_f < begin_time.to_f || begin_time.to_f == 0.0
- end
-
- def update_totals(other)
- self.total_call_time += other.total_call_time
- self.total_exclusive_time += other.total_exclusive_time
- self.sum_of_squares += other.sum_of_squares
- end
-
- def min_time_less?(other)
- (other.min_call_time < min_call_time && other.call_count > 0) || call_count == 0
- end
-
- def expand_min_max_to(other)
- self.min_call_time = other.min_call_time if min_time_less?(other)
- self.max_call_time = other.max_call_time if other.max_call_time > max_call_time
- end
-
- def merge_attributes(other)
- update_totals(other)
- expand_min_max_to(other)
- self.call_count += other.call_count
- update_boundaries(other)
- end
-
- def merge!(other_stats)
- Array(other_stats).each do |other|
- merge_attributes(other)
- end
-
- self
- end
-
- def merge(other_stats)
- stats = self.clone
- stats.merge!(other_stats)
- end
-
-
- def is_reset?
- call_count == 0 && total_call_time == 0.0 && total_exclusive_time == 0.0
- end
-
- def reset
- self.call_count = 0
- self.total_call_time = 0.0
- self.total_exclusive_time = 0.0
- self.min_call_time = 0.0
- self.max_call_time = 0.0
- self.sum_of_squares = 0.0
- self.begin_time = Time.at(0)
- self.end_time = Time.at(0)
- end
-
- def as_percentage_of(other_stats)
- checked_calculation(total_call_time, other_stats.total_call_time) * 100.0
- end
-
- # the stat total_call_time is a percent
- def as_percentage
- average_call_time * 100.0
- end
-
- def duration
- end_time ? (end_time - begin_time) : 0.0
- end
-
- def midpoint
- begin_time + (duration/2)
- end
- def calls_per_minute
- checked_calculation(call_count, duration) * 60
- end
-
- def total_call_time_per_minute
- 60.0 * time_percentage
- end
-
- def standard_deviation
- return 0 if call_count < 2 || self.sum_of_squares.nil?
-
- # Convert sum of squares into standard deviation based on
- # formula for the standard deviation for the entire population
- x = self.sum_of_squares - (self.call_count * (self.average_value**2))
- return 0 if x <= 0
-
- Math.sqrt(x / self.call_count)
- end
-
- # returns the time spent in this component as a percentage of the total
- # time window.
- def time_percentage
- checked_calculation(total_call_time, duration)
- end
-
- def exclusive_time_percentage
- checked_calculation(total_exclusive_time, duration)
- end
-
- alias average_value average_call_time
- alias average_response_time average_call_time
- alias requests_per_minute calls_per_minute
-
- def to_s
- summary
- end
-
- # Summary string to facilitate testing
- def summary
- format = "%m/%d/%y %I:%M%p"
- "[#{Time.at(begin_time.to_f).utc.strftime(format)} UTC, #{'%2.3fs' % duration.to_f}; #{'%2i' % call_count.to_i} calls #{'%4i' % average_call_time.to_f}s]"
- end
-
- # multiply the total time and rate by the given percentage
- def multiply_by(percentage)
- self.total_call_time = total_call_time * percentage
- self.call_count = call_count * percentage
- self.sum_of_squares = sum_of_squares * percentage
-
- self
- end
-
- # returns s,t,f
- def get_apdex
- [@call_count, @total_call_time.to_i, @total_exclusive_time.to_i]
- end
-
- def apdex_score
- s, t, f = get_apdex
- (s.to_f + (t.to_f / 2)) / (s+t+f).to_f
- end
- end
-
-
- class StatsBase
- include Stats
-
- attr_accessor :call_count
- attr_accessor :min_call_time
- attr_accessor :max_call_time
- attr_accessor :total_call_time
- attr_accessor :total_exclusive_time
- attr_accessor :sum_of_squares
-
- def initialize
- reset
- end
-
- def freeze
- @end_time = Time.now
- super
- end
-
- def to_json(*_)
- {
- 'call_count' => call_count.to_i,
- 'min_call_time' => min_call_time.to_f,
- 'max_call_time' => max_call_time.to_f,
- 'total_call_time' => total_call_time.to_f,
- 'total_exclusive_time' => total_exclusive_time.to_f,
- 'sum_of_squares' => sum_of_squares.to_f
- }.to_json(*_)
- end
-
-
- # In this class, we explicitly don't track begin and end time here, to save space during
- # cross process serialization via xml. Still the accessor methods must be provided for merge to work.
- def begin_time=(t)
- end
-
- def end_time=(t)
- end
-
- def begin_time
- 0.0
- end
-
- def end_time
- 0.0
- end
- end
-
-
- class BasicStats < StatsBase
- end
-
- class ApdexStats < StatsBase
-
- def record_apdex_s
- @call_count += 1
- end
-
- def record_apdex_t
- @total_call_time += 1
- end
-
- def record_apdex_f
- @total_exclusive_time += 1
- end
- end
-
- # Statistics used to track the performance of traced methods
- class MethodTraceStats < StatsBase
-
- alias data_point_count call_count
-
- # record a single data point into the statistical gatherer. The gatherer
- # will aggregate all data points collected over a specified period and upload
- # its data to the NewRelic server
- def record_data_point(value, exclusive_time = value)
- @call_count += 1
- @total_call_time += value
- @min_call_time = value if value < @min_call_time || @call_count == 1
- @max_call_time = value if value > @max_call_time
- @total_exclusive_time += exclusive_time
-
- @sum_of_squares += (value * value)
- self
- end
-
- alias trace_call record_data_point
-
- # Records multiple data points as one method call - this handles
- # all the aggregation that would be done with multiple
- # record_data_point calls
- def record_multiple_data_points(total_value, count=1)
- return record_data_point(total_value) if count == 1
- @call_count += count
- @total_call_time += total_value
- avg_val = total_value / count
- @min_call_time = avg_val if avg_val < @min_call_time || @call_count == count
- @max_call_time = avg_val if avg_val > @max_call_time
- @total_exclusive_time += total_value
- @sum_of_squares += (avg_val * avg_val) * count
- self
- end
-
- # increments the call_count by one
- def increment_count(value = 1)
- @call_count += value
- end
-
- # outputs a human-readable version of the MethodTraceStats object
- def inspect
- "#<NewRelic::MethodTraceStats #{summary} >"
- end
-
- end
-
- class ScopedMethodTraceStats < MethodTraceStats
- attr_accessor :unscoped_stats
- def initialize(unscoped_stats)
- super()
- self.unscoped_stats = unscoped_stats
- end
- def trace_call(call_time, exclusive_time = call_time)
- unscoped_stats.trace_call call_time, exclusive_time
- super call_time, exclusive_time
- end
- # Records multiple data points as one method call - this handles
- # all the aggregation that would be done with multiple
- # trace_call calls
- def record_multiple_data_points(total_value, count=1)
- unscoped_stats.record_multiple_data_points(total_value, count)
- super total_value, count
- end
- end
-end
-
diff --git a/lib/new_relic/timer_lib.rb b/lib/new_relic/timer_lib.rb
index fa8a3fd..908c001 100644
--- a/lib/new_relic/timer_lib.rb
+++ b/lib/new_relic/timer_lib.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under Ruby's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# Copyright: (C) 2008 David Vollbracht & Philippe Hanrigou
# This code was borrowed from the system_timer gem under the terms
diff --git a/lib/new_relic/transaction_analysis.rb b/lib/new_relic/transaction_analysis.rb
index d530e90..0240167 100644
--- a/lib/new_relic/transaction_analysis.rb
+++ b/lib/new_relic/transaction_analysis.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/transaction_analysis/segment_summary'
# Add these methods to TransactionSample that enable performance analysis in the user interface.
module NewRelic
diff --git a/lib/new_relic/transaction_analysis/segment_summary.rb b/lib/new_relic/transaction_analysis/segment_summary.rb
index 7c43103..7d74859 100644
--- a/lib/new_relic/transaction_analysis/segment_summary.rb
+++ b/lib/new_relic/transaction_analysis/segment_summary.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module TransactionAnalysis
# summarizes performance data for all calls to segments
diff --git a/lib/new_relic/transaction_sample.rb b/lib/new_relic/transaction_sample.rb
index 40d385d..61129c7 100644
--- a/lib/new_relic/transaction_sample.rb
+++ b/lib/new_relic/transaction_sample.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'base64'
require 'new_relic/transaction_sample/segment'
@@ -11,7 +15,8 @@ module NewRelic
class TransactionSample
- attr_accessor :params, :root_segment, :profile, :force_persist, :guid
+ attr_accessor(:params, :root_segment, :profile, :force_persist, :guid,
+ :threshold)
attr_reader :root_segment, :params, :sample_id
@@start_time = Time.now
diff --git a/lib/new_relic/transaction_sample/composite_segment.rb b/lib/new_relic/transaction_sample/composite_segment.rb
index 144457b..1283e86 100644
--- a/lib/new_relic/transaction_sample/composite_segment.rb
+++ b/lib/new_relic/transaction_sample/composite_segment.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/transaction_sample'
require 'new_relic/transaction_sample/segment'
require 'new_relic/transaction_sample/summary_segment'
diff --git a/lib/new_relic/transaction_sample/fake_segment.rb b/lib/new_relic/transaction_sample/fake_segment.rb
index 2d103fd..6873281 100644
--- a/lib/new_relic/transaction_sample/fake_segment.rb
+++ b/lib/new_relic/transaction_sample/fake_segment.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/transaction_sample'
require 'new_relic/transaction_sample/segment'
module NewRelic
diff --git a/lib/new_relic/transaction_sample/segment.rb b/lib/new_relic/transaction_sample/segment.rb
index 4d4498f..7ccd94e 100644
--- a/lib/new_relic/transaction_sample/segment.rb
+++ b/lib/new_relic/transaction_sample/segment.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/transaction_sample'
module NewRelic
@@ -8,9 +12,10 @@ module NewRelic
# have a timestamp.
attr_reader :exit_timestamp
attr_reader :parent_segment
- attr_reader :metric_name
attr_reader :segment_id
+ attr_accessor :metric_name
+
def initialize(timestamp, metric_name, segment_id)
@entry_timestamp = timestamp
@metric_name = metric_name || '<unknown>'
diff --git a/lib/new_relic/transaction_sample/summary_segment.rb b/lib/new_relic/transaction_sample/summary_segment.rb
index bad5a5d..1caad9d 100644
--- a/lib/new_relic/transaction_sample/summary_segment.rb
+++ b/lib/new_relic/transaction_sample/summary_segment.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/transaction_sample'
require 'new_relic/transaction_sample/segment'
module NewRelic
diff --git a/lib/new_relic/url_rule.rb b/lib/new_relic/url_rule.rb
index 948dd07..e70281e 100644
--- a/lib/new_relic/url_rule.rb
+++ b/lib/new_relic/url_rule.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# Represents url mapping rules stored on the server. These rules should be applied
# to URLs which are not normalized into controller class/action by Rails routes.
# Insantiated strictly by Marshal.
diff --git a/lib/new_relic/version.rb b/lib/new_relic/version.rb
index 9eeb4b0..8a9406f 100644
--- a/lib/new_relic/version.rb
+++ b/lib/new_relic/version.rb
@@ -1,4 +1,8 @@
#!/usr/bin/ruby
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic
module VERSION #:nodoc:
@@ -7,8 +11,8 @@ module NewRelic
end
MAJOR = 3
- MINOR = 5
- TINY = 7
+ MINOR = 6
+ TINY = 0
begin
require File.join(File.dirname(__FILE__), 'build')
diff --git a/lib/newrelic_rpm.rb b/lib/newrelic_rpm.rb
index 4f47ab3..20639e9 100644
--- a/lib/newrelic_rpm.rb
+++ b/lib/newrelic_rpm.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# == New Relic Initialization
#
# When installed as a gem, you can activate the New Relic agent one of the following ways:
@@ -33,7 +37,7 @@ elsif defined? Rails
class Railtie < Rails::Railtie
initializer "newrelic_rpm.start_plugin" do |app|
- NewRelic::Control.instance.init_plugin(:config => app.config)
+ NewRelic::Control.instance.init_plugin(:config => app.config)
end
end
end
diff --git a/lib/tasks/all.rb b/lib/tasks/all.rb
index cad582d..169d7f7 100644
--- a/lib/tasks/all.rb
+++ b/lib/tasks/all.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# This is required to load in task definitions from merb
Dir.glob(File.join(File.dirname(__FILE__),'*.rake')) do |file|
load file
diff --git a/newrelic.yml b/newrelic.yml
index 123e300..a4f9c81 100644
--- a/newrelic.yml
+++ b/newrelic.yml
@@ -16,7 +16,7 @@ common: &default_settings
# account. This key binds your Agent's data to your account in the
# New Relic service.
license_key: '<%= license_key %>'
-
+
# Agent Enabled (Rails Only)
# Use this setting to force the agent to run or not run.
# Default is 'auto' which means the agent will install and run only
@@ -26,7 +26,7 @@ common: &default_settings
# Valid values are true, false and auto.
#
# agent_enabled: auto
-
+
# Application Name Set this to be the name of your application as
# you'd like it show up in New Relic. The service will then auto-map
# instances of your application into an "application" on your
@@ -41,12 +41,12 @@ common: &default_settings
# app_name:
# - Ajax Service
# - All Services
- #
+ #
app_name: <%= @app_name %>
- # When "true", the agent collects performance data about your
- # application and reports this data to the New Relic service at
- # newrelic.com. This global switch is normally overridden for each
+ # When "true", the agent collects performance data about your
+ # application and reports this data to the New Relic service at
+ # newrelic.com. This global switch is normally overridden for each
# environment below (formerly called 'enabled').
monitor_mode: true
@@ -64,7 +64,7 @@ common: &default_settings
# '/var/log/') The agent will attempt to create this directory if it
# does not exist.
# log_file_path: 'log'
-
+
# Optionally set the name of the log file, defaults to 'newrelic_agent.log'
# log_file_name: 'newrelic_agent.log'
@@ -79,9 +79,9 @@ common: &default_settings
# New Relic Real User Monitoring gives you insight into the performance real users are
# experiencing with your website. This is accomplished by measuring the time it takes for
# your users' browsers to download and render your web pages by injecting a small amount
- # of JavaScript code into the header and footer of each page.
+ # of JavaScript code into the header and footer of each page.
browser_monitoring:
- # By default the agent automatically injects the monitoring JavaScript
+ # By default the agent automatically injects the monitoring JavaScript
# into web pages. Set this attribute to false to turn off this behavior.
auto_instrument: true
@@ -101,7 +101,7 @@ common: &default_settings
# See: https://newrelic.com/docs/ruby/audit-log
audit_log:
enabled: false
-
+
# Tells transaction tracer and error collector (when enabled)
# whether or not to capture HTTP params. When true, frameworks can
# exclude HTTP parameters from being captured.
@@ -111,18 +111,17 @@ common: &default_settings
# ex: ignored_params: credit_card, ssn, password
capture_params: false
-
# Transaction tracer captures deep information about slow
# transactions and sends this to the service once a
# minute. Included in the transaction is the exact call sequence of
# the transactions including any SQL statements issued.
transaction_tracer:
-
+
# Transaction tracer is enabled by default. Set this to false to
# turn it off. This feature is only available at the Professional
# and above product levels.
enabled: true
-
+
# Threshold in seconds for when to collect a transaction
# trace. When the response time of a controller action exceeds
# this threshold, a transaction trace will be recorded and sent to
@@ -130,13 +129,13 @@ common: &default_settings
# "apdex_f", which will use the threshold for an dissatisfying
# Apdex controller action - four times the Apdex T value.
transaction_threshold: apdex_f
-
+
# When transaction tracer is on, SQL statements can optionally be
# recorded. The recorder has three modes, "off" which sends no
# SQL, "raw" which sends the SQL statement in its original form,
# and "obfuscated", which strips out numeric and string literals.
record_sql: obfuscated
-
+
# Threshold in seconds for when to collect stack trace for a SQL
# call. In other words, when SQL statements exceed this threshold,
# then capture and send the current stack trace. This is
@@ -148,42 +147,29 @@ common: &default_settings
# set to false when using other adapters.
# explain_enabled: true
- # Threshold for query execution time below which query plans will not
+ # Threshold for query execution time below which query plans will not
# not be captured. Relevant only when `explain_enabled` is true.
# explain_threshold: 0.5
-
+
# Error collector captures information about uncaught exceptions and
# sends them to the service for viewing.
error_collector:
-
+
# Error collector is enabled by default. Set this to false to turn
# it off. This feature is only available at the Professional and above
# product levels.
enabled: true
-
- # Rails Only - tells error collector whether or not to capture a
- # source snippet around the place of the error when errors are View
+
+ # Rails Only - tells error collector whether or not to capture a
+ # source snippet around the place of the error when errors are View
# related.
- capture_source: true
-
+ capture_source: true
+
# To stop specific errors from reporting to New Relic, set this property
# to comma separated values. Default is to ignore routing errors
# which are how 404's get triggered.
ignore_errors: ActionController::RoutingError
- # (Advanced) Uncomment this to ensure the cpu and memory samplers
- # won't run. Useful when you are using the agent to monitor an
- # external resource.
- # disable_samplers: true
-
- # If you aren't interested in visibility in these areas, you can
- # disable the instrumentation to reduce overhead.
- #
- # disable_view_instrumentation: true
- # disable_activerecord_instrumentation: true
- # disable_memcache_instrumentation: true
- # disable_dj: true
-
# If you're interested in capturing memcache keys as though they
# were SQL uncomment this flag. Note that this does increase
# overhead slightly on every memcached call, and can have security
@@ -202,19 +188,19 @@ common: &default_settings
development:
<<: *default_settings
- # Turn off communication to New Relic service in development mode (also
+ # Turn off communication to New Relic service in development mode (also
# 'enabled').
- # NOTE: for initial evaluation purposes, you may want to temporarily
+ # NOTE: for initial evaluation purposes, you may want to temporarily
# turn the agent on in development mode.
monitor_mode: false
- # Rails Only - when running in Developer Mode, the New Relic Agent will
+ # Rails Only - when running in Developer Mode, the New Relic Agent will
# present performance information on the last 100 transactions you have
# executed since starting the mongrel.
# NOTE: There is substantial overhead when running in developer mode.
- # Do not use for production or load testing.
+ # Do not use for production or load testing.
developer_mode: true
-
+
# Enable textmate links
# textmate: true
diff --git a/recipes/newrelic.rb b/recipes/newrelic.rb
index f8dd701..2483207 100644
--- a/recipes/newrelic.rb
+++ b/recipes/newrelic.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# The capistrano recipes in plugins are automatically
# loaded from here. From gems, they are available from
# the lib directory. We have to make them available from
diff --git a/test/active_record_fixtures.rb b/test/active_record_fixtures.rb
index 9033457..19e72b3 100644
--- a/test/active_record_fixtures.rb
+++ b/test/active_record_fixtures.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# Some test models
# These will get created and dropped during the active record tests
# Be sure and call setup and teardown
diff --git a/test/config/test_control.rb b/test/config/test_control.rb
index b5e96b5..a459545 100644
--- a/test/config/test_control.rb
+++ b/test/config/test_control.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'new_relic/control/frameworks/rails'
require 'new_relic/control/frameworks/rails3'
require 'new_relic/control/frameworks/rails4'
diff --git a/test/intentional_fail.rb b/test/intentional_fail.rb
index a902df3..4a76bb7 100644
--- a/test/intentional_fail.rb
+++ b/test/intentional_fail.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
class IntentionalFail < Test::Unit::TestCase
diff --git a/test/multiverse/lib/multiverse/color.rb b/test/multiverse/lib/multiverse/color.rb
index 44fcdb0..4516a97 100644
--- a/test/multiverse/lib/multiverse/color.rb
+++ b/test/multiverse/lib/multiverse/color.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module Multiverse
module Color
def red(string)
diff --git a/test/multiverse/lib/multiverse/envfile.rb b/test/multiverse/lib/multiverse/envfile.rb
index cb6b6f8..764d65b 100644
--- a/test/multiverse/lib/multiverse/envfile.rb
+++ b/test/multiverse/lib/multiverse/envfile.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module Multiverse
# Reads an envfile.rb and converts it into gemfiles that can be used by
# bundler
diff --git a/test/multiverse/lib/multiverse/environment.rb b/test/multiverse/lib/multiverse/environment.rb
index 559402c..5de5188 100644
--- a/test/multiverse/lib/multiverse/environment.rb
+++ b/test/multiverse/lib/multiverse/environment.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'fileutils'
require 'test/unit'
module Multiverse
diff --git a/test/multiverse/lib/multiverse/output_collector.rb b/test/multiverse/lib/multiverse/output_collector.rb
index 83c6f26..70f5dcb 100644
--- a/test/multiverse/lib/multiverse/output_collector.rb
+++ b/test/multiverse/lib/multiverse/output_collector.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# This module is responsible for intercepting output made through various stdlib
# calls (i.e. puts, print, etc.) and printing summary information (e.g. a list
# of failing tests) at the end of the process.
diff --git a/test/multiverse/lib/multiverse/runner.rb b/test/multiverse/lib/multiverse/runner.rb
index 863ba12..5a9c402 100644
--- a/test/multiverse/lib/multiverse/runner.rb
+++ b/test/multiverse/lib/multiverse/runner.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module Multiverse
module Runner
extend self
diff --git a/test/multiverse/lib/multiverse/suite.rb b/test/multiverse/lib/multiverse/suite.rb
index 2fe0246..53dbdb5 100755
--- a/test/multiverse/lib/multiverse/suite.rb
+++ b/test/multiverse/lib/multiverse/suite.rb
@@ -1,4 +1,15 @@
#!/usr/bin/env ruby
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+# this is to make sure that the Multiverse environment loads with the
+# the gem version of Minitest (necessary for Rails 4) and not the one
+# in standard library.
+if defined?(gem)
+ gem 'minitest'
+end
+
require File.expand_path(File.join(File.dirname(__FILE__), 'environment'))
module Multiverse
class Suite
diff --git a/test/multiverse/suites/active_record/Envfile b/test/multiverse/suites/active_record/Envfile
index d287760..f870781 100644
--- a/test/multiverse/suites/active_record/Envfile
+++ b/test/multiverse/suites/active_record/Envfile
@@ -9,5 +9,4 @@ gemfile <<-RB
gem 'activerecord'
gem 'sqlite3', '~> 1.3.5'
end
-
RB
diff --git a/test/multiverse/suites/active_record/ar_method_aliasing.rb b/test/multiverse/suites/active_record/ar_method_aliasing.rb
index 471b7b1..d45806f 100644
--- a/test/multiverse/suites/active_record/ar_method_aliasing.rb
+++ b/test/multiverse/suites/active_record/ar_method_aliasing.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'rubygems'
diff --git a/test/multiverse/suites/active_record/encoding_test.rb b/test/multiverse/suites/active_record/encoding_test.rb
index 228473a..492a668 100644
--- a/test/multiverse/suites/active_record/encoding_test.rb
+++ b/test/multiverse/suites/active_record/encoding_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://support.newrelic.com/tickets/2101
# https://github.com/newrelic/rpm/pull/42
# https://github.com/newrelic/rpm/pull/45
diff --git a/test/multiverse/suites/agent_only/Envfile b/test/multiverse/suites/agent_only/Envfile
index 8e52833..665f88f 100644
--- a/test/multiverse/suites/agent_only/Envfile
+++ b/test/multiverse/suites/agent_only/Envfile
@@ -1,5 +1,9 @@
gemfile <<-RB
gem 'rack'
gem 'rack-test'
- gem 'mocha', '< 0.13'
+ if RUBY_VERSION >= '1.9.0'
+ gem 'mocha', '~> 0.13.0'
+ else
+ gem 'mocha', '< 0.13'
+ end
RB
diff --git a/test/multiverse/suites/agent_only/audit_log_test.rb b/test/multiverse/suites/agent_only/audit_log_test.rb
index 3fb96fb..7ccb38b 100644
--- a/test/multiverse/suites/agent_only/audit_log_test.rb
+++ b/test/multiverse/suites/agent_only/audit_log_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# RUBY-981 Audit Log
require 'newrelic_rpm'
@@ -67,7 +71,7 @@ class AuditLogTest < Test::Unit::TestCase
def run_agent_with_options(options)
NewRelic::Agent.manual_start(options)
yield NewRelic::Agent.agent if block_given?
- NewRelic::Agent.shutdown
+ NewRelic::Agent.shutdown
end
def test_logs_nothing_by_default
@@ -90,10 +94,8 @@ class AuditLogTest < Test::Unit::TestCase
agent.sql_sampler.notice_scope_empty
agent.send(:harvest_and_send_slowest_sql)
end
-
$collector.agent_data.each do |req|
- body = $collector.unpack_inner_blobs(req)
- assert_audit_log_contains_object(body, format)
+ assert_audit_log_contains_object(req.body, format)
end
end
end
diff --git a/test/multiverse/suites/agent_only/config/newrelic.yml b/test/multiverse/suites/agent_only/config/newrelic.yml
index ec648f3..a28572e 100644
--- a/test/multiverse/suites/agent_only/config/newrelic.yml
+++ b/test/multiverse/suites/agent_only/config/newrelic.yml
@@ -3,7 +3,7 @@ development:
error_collector:
capture_source: true
enabled: true
- apdex_t: 0.5
+ apdex_t: 0.24
ssl: false
monitor_mode: true
license_key: bootstrap_newrelic_admin_license_key_000
@@ -16,7 +16,6 @@ development:
record_sql: obfuscated
enabled: true
stack_trace_threshold: 0.5
- transaction_threshold: 1.0
capture_params: false
log_level: debug
log_file_path: agent.log
diff --git a/test/multiverse/suites/agent_only/cross_application_tracing_test.rb b/test/multiverse/suites/agent_only/cross_application_tracing_test.rb
new file mode 100644
index 0000000..6a76cf5
--- /dev/null
+++ b/test/multiverse/suites/agent_only/cross_application_tracing_test.rb
@@ -0,0 +1,60 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require 'rack/test'
+require 'fake_collector'
+require './testing_app'
+
+class CrossProcessTest < Test::Unit::TestCase
+
+ # Important because the hooks are global that we only wire one AgentHooks up
+ @@app = TestingApp.new
+ @@wrapper_app = NewRelic::Rack::AgentHooks.new(@@app)
+
+ def setup
+ $collector ||= NewRelic::FakeCollector.new
+ $collector.reset
+ $collector.mock['connect'] = [200, {'return_value' => {"agent_run_id" => 666 }}]
+ $collector.run
+
+ NewRelic::Agent.manual_start(
+ :cross_process_id => "boo",
+ :encoding_key => "\0",
+ :trusted_account_ids => [1])
+
+ NewRelic::Agent.instance.events.notify(:finished_configuring)
+
+ @@app.reset_headers
+ @@app.response = "<html><head><title>W00t!</title></head><body><p>Hello World</p></body></html>"
+ end
+
+ def teardown
+ NewRelic::Agent.shutdown
+ end
+
+ include Rack::Test::Methods
+
+ def app
+ @@wrapper_app
+ end
+
+ def test_cross_app_doesnt_modify_without_header
+ get '/'
+ assert_nil last_response.headers["X-NewRelic-App-Data"]
+ end
+
+ def test_cross_app_doesnt_modify_with_invalid_header
+ get '/', nil, {'X-NewRelic-ID' => Base64.encode64('otherjunk')}
+ assert_nil last_response.headers["X-NewRelic-App-Data"]
+ end
+
+ def test_cross_app_writes_out_information
+ get '/', nil, {'X-NewRelic-ID' => Base64.encode64('1#234')}
+ assert_not_nil last_response.headers["X-NewRelic-App-Data"]
+
+ metric = NewRelic::Agent.instance.stats_engine.lookup_stats('ClientApplication/1#234/all')
+ assert_equal 1, metric.call_count
+ end
+end
+
diff --git a/test/multiverse/suites/agent_only/cross_process_test.rb b/test/multiverse/suites/agent_only/cross_process_test.rb
deleted file mode 100644
index 3a42dfb..0000000
--- a/test/multiverse/suites/agent_only/cross_process_test.rb
+++ /dev/null
@@ -1,56 +0,0 @@
-require 'rack/test'
-require 'fake_collector'
-require './testing_app'
-
-class CrossProcessTest < Test::Unit::TestCase
-
- # Important because the hooks are global that we only wire one AgentHooks up
- @@app = TestingApp.new
- @@wrapper_app = NewRelic::Rack::AgentHooks.new(@@app)
-
- def setup
- $collector ||= NewRelic::FakeCollector.new
- $collector.reset
- $collector.mock['connect'] = [200, {'return_value' => {"agent_run_id" => 666 }}]
- $collector.run
-
- NewRelic::Agent.manual_start(
- :cross_process_id => "boo",
- :encoding_key => "\0",
- :trusted_account_ids => [1])
-
- NewRelic::Agent.instance.events.notify(:finished_configuring)
-
- @@app.reset_headers
- @@app.response = "<html><head><title>W00t!</title></head><body><p>Hello World</p></body></html>"
- end
-
- def teardown
- NewRelic::Agent.shutdown
- end
-
- include Rack::Test::Methods
-
- def app
- @@wrapper_app
- end
-
- def test_cross_process_doesnt_modify_without_header
- get '/'
- assert_nil last_response.headers["X-NewRelic-App-Data"]
- end
-
- def test_cross_process_doesnt_modify_with_invalid_header
- get '/', nil, {'X-NewRelic-ID' => Base64.encode64('otherjunk')}
- assert_nil last_response.headers["X-NewRelic-App-Data"]
- end
-
- def test_cross_process_writes_out_information
- get '/', nil, {'X-NewRelic-ID' => Base64.encode64('1#234')}
- assert_not_nil last_response.headers["X-NewRelic-App-Data"]
-
- metric = NewRelic::Agent.instance.stats_engine.lookup_stats('ClientApplication/1#234/all')
- assert_equal 1, metric.call_count
- end
-end
-
diff --git a/test/multiverse/suites/agent_only/http_response_code_test.rb b/test/multiverse/suites/agent_only/http_response_code_test.rb
index 962b7ba..1309506 100644
--- a/test/multiverse/suites/agent_only/http_response_code_test.rb
+++ b/test/multiverse/suites/agent_only/http_response_code_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://newrelic.atlassian.net/browse/RUBY-765
require 'fake_collector'
diff --git a/test/multiverse/suites/agent_only/key_transactions_test.rb b/test/multiverse/suites/agent_only/key_transactions_test.rb
new file mode 100644
index 0000000..31fbc41
--- /dev/null
+++ b/test/multiverse/suites/agent_only/key_transactions_test.rb
@@ -0,0 +1,80 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+class KeyTransactionsTest < Test::Unit::TestCase
+ class TestWidget
+ include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
+
+ def key_txn(t0)
+ Time.stubs(:now).returns(t0 + 5)
+ end
+ add_transaction_tracer :key_txn
+
+ def other_txn(t0)
+ Time.stubs(:now).returns(t0 + 5)
+ end
+ add_transaction_tracer :other_txn
+ end
+
+ def setup
+ $collector ||= NewRelic::FakeCollector.new
+ $collector.reset
+ key_apdex_config = { 'Controller/KeyTransactionsTest::TestWidget/key_txn' => 1 }
+ $collector.mock['connect'] = [200, {'return_value' => {
+ "agent_run_id" => 666,
+ 'web_transactions_apdex' => key_apdex_config,
+ 'apdex_t' => 10
+ }}]
+ $collector.run
+
+ NewRelic::Agent.manual_start(:sync_startup => true,
+ :force_reconnect => true)
+ end
+
+ def teardown
+ NewRelic::Agent.shutdown
+ $collector.reset
+ end
+
+ SATISFYING = 0
+ TOLERATING = 1
+ FAILING = 2
+
+ def test_applied_correct_apdex_t_to_key_txn
+ TestWidget.new.key_txn(stub_time_now)
+ NewRelic::Agent.instance.send(:harvest_and_send_timeslice_data)
+
+ stats = $collector.reported_stats_for_metric('Apdex')[0]
+ assert_equal(1.0, stats[FAILING],
+ "Expected stats (#{stats}) to be apdex failing")
+ end
+
+ def test_applied_correct_apdex_t_to_regular_txn
+ TestWidget.new.other_txn(stub_time_now)
+ NewRelic::Agent.instance.send(:harvest_and_send_timeslice_data)
+
+ stats = $collector.reported_stats_for_metric('Apdex')[0]
+ assert_equal(1.0, stats[SATISFYING],
+ "Expected stats (#{stats}) to be apdex satisfying")
+ end
+
+ def test_applied_correct_tt_theshold
+ now = stub_time_now
+ TestWidget.new.key_txn(now)
+ TestWidget.new.other_txn(now)
+
+ NewRelic::Agent.instance.send(:harvest_and_send_slowest_sample)
+
+ traces = $collector.calls_for('transaction_sample_data')
+ assert_equal 1, traces.size
+ assert_equal('Controller/KeyTransactionsTest::TestWidget/key_txn',
+ traces[0].metric_name)
+ end
+
+ def stub_time_now
+ now = Time.now
+ Time.stubs(:now).returns(now)
+ return now
+ end
+end
diff --git a/test/multiverse/suites/agent_only/logging_test.rb b/test/multiverse/suites/agent_only/logging_test.rb
index a307ea8..47fb32a 100644
--- a/test/multiverse/suites/agent_only/logging_test.rb
+++ b/test/multiverse/suites/agent_only/logging_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# RUBY-980 Improve logging
# This test suite is for checking IMPORTANT conditions that we log rather than
# trying to do this via brittle unit tests
diff --git a/test/multiverse/suites/agent_only/marshaling_test.rb b/test/multiverse/suites/agent_only/marshaling_test.rb
index c661e67..8488832 100644
--- a/test/multiverse/suites/agent_only/marshaling_test.rb
+++ b/test/multiverse/suites/agent_only/marshaling_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://newrelic.atlassian.net/wiki/display/eng/The+Terror+and+Glory+of+Transaction+Traces
# https://newrelic.atlassian.net/browse/RUBY-914
@@ -41,31 +45,23 @@ class MarshalingTest < Test::Unit::TestCase
marshaller = NewRelic::Agent::NewRelicService::PrubyMarshaller.new
end
- assert_equal(666,
- $collector.agent_data.select{|x| x.action == 'transaction_sample_data'}[0].body[0])
+ assert_equal('666', $collector.calls_for('transaction_sample_data')[0].run_id)
assert_equal(expected_sample.to_collector_array(marshaller.default_encoder),
- $collector.agent_data.select{|x| x.action == 'transaction_sample_data'}[0].body[1][0])
+ $collector.calls_for('transaction_sample_data')[0][1][0])
end
def test_metric_data_marshalling
stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope('Custom/test/method')
stats.record_data_point(1.0)
stats.record_data_point(2.0, 1.0)
- expected = [ [ {'name' => 'Custom/test/method', 'scope' => ''},
- [2, 3.0, 2.0, 1.0, 2.0, 5.0] ] ]
+ expected = [ 2, 3.0, 2.0, 1.0, 2.0, 5.0 ]
@agent.service.connect
@agent.send(:harvest_and_send_timeslice_data)
- assert_equal(666,
- $collector.agent_data.select{|x| x.action == 'metric_data'}[0].body[0])
-
- metric_data = $collector.agent_data \
- .select{|x| x.action == 'metric_data'}[0].body[3]
- assert metric_data
+ assert_equal('666', $collector.calls_for('metric_data')[0].run_id)
- custom_metric = metric_data \
- .select{|m| m[0]['name'] == 'Custom/test/method' }
+ custom_metric = $collector.reported_stats_for_metric('Custom/test/method')[0]
assert_equal(expected, custom_metric)
end
@@ -74,11 +70,9 @@ class MarshalingTest < Test::Unit::TestCase
@agent.service.connect
@agent.send(:harvest_and_send_errors)
- assert_equal(666,
- $collector.agent_data.select{|x| x.action == 'error_data'}[0].body[0])
+ assert_equal('666', $collector.calls_for('error_data')[0].run_id)
- error_data = $collector.agent_data \
- .select{|x| x.action == 'error_data'}[0].body[1][0]
+ error_data = $collector.calls_for('error_data')[0][1][0]
assert_equal('test error', error_data[2])
end
@@ -92,8 +86,7 @@ class MarshalingTest < Test::Unit::TestCase
@agent.service.connect
@agent.send(:harvest_and_send_slowest_sql)
- sql_data = $collector.agent_data \
- .select{|x| x.action == 'sql_trace_data'}[0].body[0]
+ sql_data = $collector.calls_for('sql_trace_data')[0][0]
assert_equal('select * from test', sql_data[0][3])
end
@@ -101,9 +94,7 @@ class MarshalingTest < Test::Unit::TestCase
@agent.service.connect('pid' => 1, 'agent_version' => '9000',
'app_name' => 'test')
- connect_data = $collector.agent_data \
- .select{|x| x.action == 'connect'}[0].body[0]
+ connect_data = $collector.calls_for('connect')[0]
assert_equal '9000', connect_data['agent_version']
end
end
-
diff --git a/test/multiverse/suites/agent_only/method_visibility_test.rb b/test/multiverse/suites/agent_only/method_visibility_test.rb
index ae49128..5b96fad 100644
--- a/test/multiverse/suites/agent_only/method_visibility_test.rb
+++ b/test/multiverse/suites/agent_only/method_visibility_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'multiverse/color'
class MethodVisibilityTest < Test::Unit::TestCase
diff --git a/test/multiverse/suites/agent_only/no_dns_resolv.rb b/test/multiverse/suites/agent_only/no_dns_resolv.rb
index bb19a3f..81e0e49 100644
--- a/test/multiverse/suites/agent_only/no_dns_resolv.rb
+++ b/test/multiverse/suites/agent_only/no_dns_resolv.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class NoDnsResolv < Test::Unit::TestCase
def test_should_no_resolve_hostname_when_agent_is_disabled
diff --git a/test/multiverse/suites/agent_only/pipe_manager_test.rb b/test/multiverse/suites/agent_only/pipe_manager_test.rb
index b2ab4b6..d168de3 100644
--- a/test/multiverse/suites/agent_only/pipe_manager_test.rb
+++ b/test/multiverse/suites/agent_only/pipe_manager_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://newrelic.atlassian.net/browse/RUBY-669
class PipeManagerTest < Test::Unit::TestCase
diff --git a/test/multiverse/suites/agent_only/rename_rule_test.rb b/test/multiverse/suites/agent_only/rename_rule_test.rb
new file mode 100644
index 0000000..321da25
--- /dev/null
+++ b/test/multiverse/suites/agent_only/rename_rule_test.rb
@@ -0,0 +1,61 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+class RenameRuleTest < Test::Unit::TestCase
+ class TestWidget
+ include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
+
+ def txn
+ mthd
+ end
+ add_transaction_tracer :txn
+
+ def mthd
+ 'doot de doo'
+ end
+ add_method_tracer :mthd
+ end
+
+ def setup
+ $collector ||= NewRelic::FakeCollector.new
+ $collector.reset
+ txn_rule_specs = [ { 'match_expression' => 'RenameRuleTest',
+ 'replacement' => 'Class' } ]
+ metric_rule_specs = [ { 'match_expression' => 'RenameRuleTest',
+ 'replacement' => 'Class' } ]
+ $collector.mock['connect'] = [200, {'return_value' => {
+ "agent_run_id" => 666,
+ 'transaction_name_rules' => txn_rule_specs,
+ 'metric_name_rules' => metric_rule_specs
+ }}]
+ $collector.run
+
+ NewRelic::Agent.manual_start(:sync_startup => true,
+ :force_reconnect => true)
+ TestWidget.new.txn
+ end
+
+ def teardown
+ NewRelic::Agent.shutdown
+ end
+
+ def test_transaction_name_rules
+ metric_names = ::NewRelic::Agent.instance.stats_engine.metrics
+ assert(metric_names.include?('Controller/Class::TestWidget/txn'),
+ "'Controller/Class::TestWidget/txn' not found in #{metric_names}")
+ assert(metric_names.include?('Apdex/Class::TestWidget/txn'),
+ "'Apdex/Class::TestWidget/txn' not found in #{metric_names}")
+ assert(!metric_names.include?('Controller/RenameRuleTest::TestWidget/txn'),
+ "'Controller/RenameRuleTest::TestWidget/txn' should not be in #{metric_names}")
+ end
+
+ def test_metric_name_rules
+ NewRelic::Agent.instance.send(:harvest_and_send_timeslice_data)
+ metric_names = $collector.calls_for('metric_data')[0].body[3].map{|m| m[0]['name']}
+ assert(metric_names.include?('Custom/Class::TestWidget/mthd'),
+ "'Custom/Class::TestWidget/mthd' not found in #{metric_names}")
+ assert(!metric_names.include?('Custom/RenameRuleTest::TestWidget/mthd'),
+ "'Custom/RenameRuleTest::TestWidget/mthd' should not be in #{metric_names}")
+ end
+end
diff --git a/test/multiverse/suites/agent_only/rum_instrumentation_test.rb b/test/multiverse/suites/agent_only/rum_instrumentation_test.rb
index cd04ad7..3d1d8b2 100644
--- a/test/multiverse/suites/agent_only/rum_instrumentation_test.rb
+++ b/test/multiverse/suites/agent_only/rum_instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'rack/test'
require 'new_relic/rack/browser_monitoring'
require './testing_app'
diff --git a/test/multiverse/suites/agent_only/service_timeout_test.rb b/test/multiverse/suites/agent_only/service_timeout_test.rb
index 347002f..f8aedd6 100644
--- a/test/multiverse/suites/agent_only/service_timeout_test.rb
+++ b/test/multiverse/suites/agent_only/service_timeout_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'socket'
class ServiceTimeoutTest < Test::Unit::TestCase
diff --git a/test/multiverse/suites/agent_only/ssl_test.rb b/test/multiverse/suites/agent_only/ssl_test.rb
index c393634..0899150 100644
--- a/test/multiverse/suites/agent_only/ssl_test.rb
+++ b/test/multiverse/suites/agent_only/ssl_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class SSLTest < Test::Unit::TestCase
def setup
diff --git a/test/multiverse/suites/agent_only/start_up_test.rb b/test/multiverse/suites/agent_only/start_up_test.rb
index 8993195..0663199 100644
--- a/test/multiverse/suites/agent_only/start_up_test.rb
+++ b/test/multiverse/suites/agent_only/start_up_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# RUBY-839 make sure there is no STDOUT chatter
require 'open3'
@@ -16,6 +20,6 @@ class StartUpTest < Test::Unit::TestCase
jruby_noise.each {|noise| output.gsub!(noise, "")}
- assert_equal '', output
+ assert_equal '', output.chomp
end
end
diff --git a/test/multiverse/suites/agent_only/test_trace_method_with_punctuation.rb b/test/multiverse/suites/agent_only/test_trace_method_with_punctuation.rb
index a181441..4ce3f7b 100644
--- a/test/multiverse/suites/agent_only/test_trace_method_with_punctuation.rb
+++ b/test/multiverse/suites/agent_only/test_trace_method_with_punctuation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
require 'newrelic_rpm'
diff --git a/test/multiverse/suites/agent_only/test_trace_transaction_with_punctuation.rb b/test/multiverse/suites/agent_only/test_trace_transaction_with_punctuation.rb
index 82a0951..5f2517b 100644
--- a/test/multiverse/suites/agent_only/test_trace_transaction_with_punctuation.rb
+++ b/test/multiverse/suites/agent_only/test_trace_transaction_with_punctuation.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
require 'newrelic_rpm'
diff --git a/test/multiverse/suites/agent_only/testing_app.rb b/test/multiverse/suites/agent_only/testing_app.rb
index e4b2af4..2b8714a 100644
--- a/test/multiverse/suites/agent_only/testing_app.rb
+++ b/test/multiverse/suites/agent_only/testing_app.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class TestingApp
attr_accessor :response, :headers
diff --git a/test/multiverse/suites/agent_only/thread_profiling_test.rb b/test/multiverse/suites/agent_only/thread_profiling_test.rb
index c244092..9b97a93 100644
--- a/test/multiverse/suites/agent_only/thread_profiling_test.rb
+++ b/test/multiverse/suites/agent_only/thread_profiling_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://newrelic.atlassian.net/wiki/display/eng/Agent+Thread+Profiling
# https://newrelic.atlassian.net/browse/RUBY-917
@@ -13,6 +17,7 @@ class ThreadProfilingTest < Test::Unit::TestCase
NewRelic::Agent::Agent.instance_variable_set(:@instance, nil)
NewRelic::Agent.manual_start(:'thread_profiler.enabled' => true)
+ NewRelic::Agent.instance.service.request_timeout = 0.5
@agent = NewRelic::Agent.instance
@thread_profiler = @agent.thread_profiler
@@ -51,11 +56,11 @@ class ThreadProfilingTest < Test::Unit::TestCase
def test_thread_profiling
@agent.send(:check_for_agent_commands)
- sleep(1)
- NewRelic::Agent.shutdown
+
+ let_it_finish
profile_data = $collector.calls_for('profile_data')[0]
- assert_equal(666, profile_data[0])
+ assert_equal('666', profile_data.run_id)
poll_count = profile_data[1][0][3]
assert poll_count > 25, "Expected poll_count > 25, but was #{poll_count}"
@@ -67,15 +72,25 @@ class ThreadProfilingTest < Test::Unit::TestCase
$collector.mock['get_agent_commands'] = [200, {'return_value' => STOP_COMMAND}]
@agent.send(:check_for_agent_commands)
- sleep(0.1)
- NewRelic::Agent.shutdown
+ let_it_finish
profile_data = $collector.calls_for('profile_data')[0]
- assert_equal(666, profile_data[0])
+ assert_equal('666', profile_data.run_id)
poll_count = profile_data[1][0][3]
assert poll_count < 10, "Expected poll_count < 10, but was #{poll_count}"
end
+
+
+ def let_it_finish
+ Timeout.timeout(2) do
+ until @thread_profiler.finished?
+ sleep(0.1)
+ end
+ end
+
+ NewRelic::Agent.shutdown
+ end
end
end
diff --git a/test/multiverse/suites/config_file_loading/config_file_loading_test.rb b/test/multiverse/suites/config_file_loading/config_file_loading_test.rb
index 74addef..5cce157 100644
--- a/test/multiverse/suites/config_file_loading/config_file_loading_test.rb
+++ b/test/multiverse/suites/config_file_loading/config_file_loading_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# Test the logic for loading the newrelic.yml config file.
#
# We will look at (in this priority):
diff --git a/test/multiverse/suites/datamapper/encoding_test.rb b/test/multiverse/suites/datamapper/encoding_test.rb
index a184335..5ee9c2b 100644
--- a/test/multiverse/suites/datamapper/encoding_test.rb
+++ b/test/multiverse/suites/datamapper/encoding_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://support.newrelic.com/tickets/2101
# https://github.com/newrelic/rpm/pull/42
# https://github.com/newrelic/rpm/pull/45
diff --git a/test/multiverse/suites/rails/Envfile b/test/multiverse/suites/rails/Envfile
index be92ec0..0e9f677 100644
--- a/test/multiverse/suites/rails/Envfile
+++ b/test/multiverse/suites/rails/Envfile
@@ -1,7 +1,14 @@
-suite_condition("Rails 3 does not support 1.8.6") do
+suite_condition("Rails 3+ do not support 1.8.6") do
RUBY_VERSION != '1.8.6'
end
+if RUBY_VERSION >= '1.9.3'
+ gemfile <<-RB
+ gem 'rails', '~>4.0.0.beta1'
+ gem 'haml'
+ RB
+end
+
gemfile <<-RB
gem 'rails', '~>3.2.0'
gem 'haml'
diff --git a/test/multiverse/suites/rails/app.rb b/test/multiverse/suites/rails/app.rb
index 9011b0b..2bd8981 100644
--- a/test/multiverse/suites/rails/app.rb
+++ b/test/multiverse/suites/rails/app.rb
@@ -1,4 +1,9 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'action_controller/railtie'
+require 'active_model'
# We define our single Rails application here, one time, upon the first inclusion
# Tests should feel free to define their own Controllers locally, but if they
@@ -15,11 +20,11 @@ if !defined?(MyApp)
MyApp.initialize!
MyApp.routes.draw do
- get('/bad_route' => 'Test#controller_error',
+ get('/bad_route' => 'test#controller_error',
:constraints => lambda do |_|
raise ActionController::RoutingError.new('this is an uncaught routing error')
end)
- match '/:controller(/:action(/:id))'
+ get '/:controller(/:action(/:id))'
end
class ApplicationController < ActionController::Base; end
@@ -31,6 +36,10 @@ if !defined?(MyApp)
self
end
+ def to_partial_path
+ 'foos/foo'
+ end
+
def valid?() true end
def new_record?() true end
def destroyed?() true end
diff --git a/test/multiverse/suites/rails/error_tracing_test.rb b/test/multiverse/suites/rails/error_tracing_test.rb
index 3771764..f83d77a 100644
--- a/test/multiverse/suites/rails/error_tracing_test.rb
+++ b/test/multiverse/suites/rails/error_tracing_test.rb
@@ -1,5 +1,8 @@
-# https://newrelic.atlassian.net/browse/RUBY-747
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+# https://newrelic.atlassian.net/browse/RUBY-747
require 'rails/test_help'
require 'fake_collector'
@@ -41,6 +44,8 @@ class IgnoredError < StandardError; end
class ServerIgnoredError < StandardError; end
class ErrorsWithoutSSCTest < ActionDispatch::IntegrationTest
+ extend Multiverse::Color
+
def setup
$collector ||= NewRelic::FakeCollector.new
$collector.reset
@@ -75,32 +80,39 @@ class ErrorsWithoutSSCTest < ActionDispatch::IntegrationTest
'no ignore error filter should be set')
end
-
- def test_error_collector_should_be_enabled
- assert NewRelic::Agent.config[:agent_enabled]
- assert NewRelic::Agent.config[:'error_collector.enabled']
- assert @error_collector.enabled?
- assert Rails.application.config.middleware.include?(NewRelic::Rack::ErrorCollector)
+ if Rails::VERSION::MAJOR != 4
+ def test_error_collector_should_be_enabled
+ assert NewRelic::Agent.config[:agent_enabled]
+ assert NewRelic::Agent.config[:'error_collector.enabled']
+ assert @error_collector.enabled?
+ assert Rails.application.config.middleware.include?(NewRelic::Rack::ErrorCollector)
+ end
+ else
+ puts yellow("SKIPPED test_error_collector_should_be_enabled : not working in Rails 4")
end
def test_should_capture_error_raised_in_view
get '/error/view_error'
- assert_error_reported_once('this is an uncaught view error')
+ assert_error_reported_once('this is an uncaught view error',
+ 'Controller/error/view_error')
end
def test_should_capture_error_raised_in_controller
get '/error/controller_error'
- assert_error_reported_once('this is an uncaught controller error')
+ assert_error_reported_once('this is an uncaught controller error',
+ 'Controller/error/controller_error')
end
def test_should_capture_error_raised_in_model
get '/error/model_error'
- assert_error_reported_once('this is an uncaught model error')
+ assert_error_reported_once('this is an uncaught model error',
+ 'Controller/error/model_error')
end
def test_should_capture_noticed_error_in_controller
get '/error/noticed_error'
- assert_error_reported_once('this error should be noticed')
+ assert_error_reported_once('this error should be noticed',
+ 'Controller/error/noticed_error')
end
# Important choice of controllor_error, since this goes through both the
@@ -118,17 +130,25 @@ class ErrorsWithoutSSCTest < ActionDispatch::IntegrationTest
assert_error_reported_once('this is a noticed error')
end
- def test_should_capture_routing_error
- get '/bad_route'
- assert_error_reported_once('this is an uncaught routing error')
+ if Rails::VERSION::MAJOR.to_i != 4
+ def test_should_capture_routing_error
+ get '/bad_route'
+ assert_error_reported_once('this is an uncaught routing error')
+ end
+ else
+ puts yellow("SKIPPED test_should_capture_routing_error : not working in Rails 4")
end
- def test_should_capture_request_uri_and_params
- get '/bad_route?eat=static'
- assert_equal('/bad_route',
- @error_collector.errors[0].params[:request_uri])
- assert_equal({'eat' => 'static'},
- @error_collector.errors[0].params[:request_params])
+ if Rails::VERSION::MAJOR.to_i != 4
+ def test_should_capture_request_uri_and_params
+ get '/bad_route?eat=static'
+ assert_equal('/bad_route',
+ @error_collector.errors[0].params[:request_uri])
+ assert_equal({'eat' => 'static'},
+ @error_collector.errors[0].params[:request_params])
+ end
+ else
+ puts yellow("SKIPPED test_should_capture_request_uri_and_params : not working in Rails 4")
end
def test_should_not_notice_errors_from_ignored_action
@@ -160,17 +180,25 @@ class ErrorsWithoutSSCTest < ActionDispatch::IntegrationTest
protected
- def assert_errors_reported(message, queued_count, total_count=queued_count)
+ def assert_errors_reported(message, queued_count, total_count=queued_count, txn_name=nil)
error_count = NewRelic::Agent::Agent.instance.stats_engine.get_stats("Errors/all")
- assert_equal total_count, error_count.call_count
+ assert_equal(total_count, error_count.call_count,
+ 'Incorrect call count on Errors/all')
+
+ if txn_name
+ error_count = NewRelic::Agent::Agent.instance.stats_engine \
+ .get_stats("Errors/#{txn_name}")
+ assert_equal(total_count, error_count.call_count,
+ "Incorrect call count on Errors/#{txn_name}")
+ end
assert_equal(queued_count,
@error_collector.errors.select{|error| error.message == message}.size,
"Wrong number of errors with message '#{message} found'")
end
- def assert_error_reported_once(message)
- assert_errors_reported(message, 1)
+ def assert_error_reported_once(message, txn_name=nil)
+ assert_errors_reported(message, 1, 1, txn_name)
end
end
diff --git a/test/multiverse/suites/rails/gc_instrumentation_test.rb b/test/multiverse/suites/rails/gc_instrumentation_test.rb
index 4f65cd9..f88ae5e 100644
--- a/test/multiverse/suites/rails/gc_instrumentation_test.rb
+++ b/test/multiverse/suites/rails/gc_instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require './app'
# GC instrumentation only works with REE or 1.9.x
diff --git a/test/multiverse/suites/rails/queue_time_test.rb b/test/multiverse/suites/rails/queue_time_test.rb
index 465bb5c..87b6e63 100644
--- a/test/multiverse/suites/rails/queue_time_test.rb
+++ b/test/multiverse/suites/rails/queue_time_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://newrelic.atlassian.net/browse/RUBY-927
require 'rails/test_help'
diff --git a/test/multiverse/suites/rails/view_instrumentation_test.rb b/test/multiverse/suites/rails/view_instrumentation_test.rb
index 3bdbb88..1366589 100644
--- a/test/multiverse/suites/rails/view_instrumentation_test.rb
+++ b/test/multiverse/suites/rails/view_instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require './app'
ActionController::Base.view_paths = ['app/views']
@@ -74,7 +78,13 @@ end
class ViewControllerTest < ActionController::TestCase
tests ViewsController
def setup
+ super
@controller = ViewsController.new
+ # ActiveSupport testing keeps blowing away my subscriber on
+ # teardown for some reason. Have to keep putting it back.
+ if Rails::VERSION::MAJOR.to_i == 4
+ NewRelic::Agent::Instrumentation::ActionViewSubscriber.subscribe
+ end
end
end
@@ -122,7 +132,7 @@ end
class TextRenderTest < ViewControllerTest
# it doesn't seem worth it to get consistent behavior here.
- if Rails::VERSION::MINOR.to_i == 0
+ if Rails::VERSION::MAJOR.to_i == 3 && Rails::VERSION::MINOR.to_i == 0
test "should not instrument rendering of text" do
get :text_render
sample = NewRelic::Agent.agent.transaction_sampler.samples.last
@@ -158,7 +168,7 @@ end
class MissingTemplateTest < ViewControllerTest
# Rails 3.0 has different behavior for rendering an empty array. We're okay with this.
- if Rails::VERSION::MINOR.to_i == 0
+ if Rails::VERSION::MAJOR.to_i == 3 && Rails::VERSION::MINOR.to_i == 0
test "should create an proper metric when the template is unknown" do
get :no_template
sample = NewRelic::Agent.agent.transaction_sampler.samples.last
diff --git a/test/multiverse/suites/resque/instrumentation_test.rb b/test/multiverse/suites/resque/instrumentation_test.rb
index c3ef21e..c59f99f 100644
--- a/test/multiverse/suites/resque/instrumentation_test.rb
+++ b/test/multiverse/suites/resque/instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# https://newrelic.atlassian.net/browse/RUBY-669
require 'resque'
@@ -17,6 +21,7 @@ class ResqueTest < Test::Unit::TestCase
$collector.run(COLLECTOR_PORT)
$redis.del('queue:resque_test')
$redis.set('index_key', 0)
+ Resque::Stat.clear('processed')
@pidfile = "resque_test.#{$$}.pid"
JOB_COUNT.times do |i|
Resque.enqueue(JobForTesting, 'index_key', i + 1)
@@ -40,7 +45,7 @@ class ResqueTest < Test::Unit::TestCase
end
def start_worker_child(env_vars=nil)
- worker_cmd = "NEWRELIC_DISPATCHER=resque #{env_vars} QUEUE=* bundle exec rake resque:work"
+ worker_cmd = "#{env_vars} QUEUE=* bundle exec rake resque:work"
@worker_pid = Process.fork
Process.exec(worker_cmd) if @worker_pid.nil?
end
@@ -52,7 +57,7 @@ class ResqueTest < Test::Unit::TestCase
def start_worker_background(env_vars=nil)
worker_cmd = "PIDFILE=#{@pidfile} TERM_CHILD=1 RESQUE_TERM_TIMEOUT=1 BACKGROUND=1 " +
- "NEWRELIC_DISPATCHER=resque #{env_vars} QUEUE=* bundle exec rake resque:work"
+ "#{env_vars} QUEUE=* bundle exec rake resque:work"
system(worker_cmd)
end
@@ -89,7 +94,7 @@ class ResqueTest < Test::Unit::TestCase
def wait_for_jobs
time_for_jobs = 5
begin
- Timeout.timeout(time_for_jobs) { sleep(0.1) until Resque.info[:pending].zero? }
+ Timeout.timeout(time_for_jobs) { sleep(0.1) until Resque.info[:processed] == JOB_COUNT }
rescue Timeout::Error => err
raise err.exception("waiting #{time_for_jobs}s for completion of #{JOB_COUNT} jobs")
end
@@ -133,7 +138,7 @@ class ResqueTest < Test::Unit::TestCase
run_worker(:env_vars => 'BROKEN_AFTER_FORK=true')
- assert File.read(log_path).include?('Unable to send data to parent process')
+ assert File.read(log_path).include?('No communication channel to parent process')
end
if RUBY_VERSION >= '1.9'
diff --git a/test/multiverse/suites/resque/resque_setup.rb b/test/multiverse/suites/resque/resque_setup.rb
index 9592cf6..54b2c13 100644
--- a/test/multiverse/suites/resque/resque_setup.rb
+++ b/test/multiverse/suites/resque/resque_setup.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'resque'
require 'newrelic_rpm'
diff --git a/test/multiverse/suites/sidekiq/Envfile b/test/multiverse/suites/sidekiq/Envfile
new file mode 100644
index 0000000..55673da
--- /dev/null
+++ b/test/multiverse/suites/sidekiq/Envfile
@@ -0,0 +1,21 @@
+suite_condition("SideKiq requires MRI 1.9.3 or JRuby 1.6 in 1.9 mode") do
+ RUBY_VERSION >= '1.9.3' || (RUBY_PLATFORM == 'java' && RUBY_VERSION >= '1.9')
+end
+
+gemfile <<-RB
+ gem 'json'
+ gem 'sidekiq'
+ gem 'rack'
+RB
+
+before_suite do
+ ENV["NEWRELIC_MULTIVERSE_REDIS_PORT"] = (20_000 + ($$ % 10_000)).to_s
+ ENV["NEWRELIC_MULTIVERSE_FAKE_COLLECTOR_PORT"] = (30_000 + ($$ % 10_000)).to_s
+ system("echo 'port #{ENV["NEWRELIC_MULTIVERSE_REDIS_PORT"]}' | redis-server - > /dev/null &")
+end
+
+after_suite do
+ system("redis-cli -p #{ENV["NEWRELIC_MULTIVERSE_REDIS_PORT"]} shutdown")
+end
+
+execute_mode 'spawn'
diff --git a/test/multiverse/suites/sidekiq/app.rb b/test/multiverse/suites/sidekiq/app.rb
new file mode 100644
index 0000000..40561e4
--- /dev/null
+++ b/test/multiverse/suites/sidekiq/app.rb
@@ -0,0 +1,25 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require "newrelic_rpm"
+
+REDIS_PORT = ENV['NEWRELIC_MULTIVERSE_REDIS_PORT']
+REDIS_URL = "redis://localhost:#{REDIS_PORT}/0"
+
+Sidekiq.configure_server do |config|
+ config.redis = { :url => REDIS_URL }
+end
+
+Sidekiq.configure_client do |config|
+ config.redis = { :url => REDIS_URL }
+end
+
+$redis = Redis.new(:port => REDIS_PORT)
+
+class TestWorker
+ include Sidekiq::Worker
+ def perform(key, val)
+ $redis.sadd(key, val)
+ end
+end
diff --git a/test/multiverse/suites/sidekiq/config/newrelic.yml b/test/multiverse/suites/sidekiq/config/newrelic.yml
new file mode 100644
index 0000000..8628075
--- /dev/null
+++ b/test/multiverse/suites/sidekiq/config/newrelic.yml
@@ -0,0 +1,22 @@
+---
+development:
+ error_collector:
+ capture_source: true
+ enabled: true
+ apdex_t: 0.5
+ ssl: false
+ monitor_mode: true
+ license_key: bootstrap_newrelic_admin_license_key_000
+ developer_mode: false
+ app_name: test
+ host: 127.0.0.1
+ api_host: 127.0.0.1
+ port: <%= ENV['NEWRELIC_MULTIVERSE_FAKE_COLLECTOR_PORT'] %>
+ transaction_tracer:
+ record_sql: obfuscated
+ enabled: true
+ stack_trace_threshold: 0.5
+ transaction_threshold: 1.0
+ capture_params: false
+ log_level: debug
+ log_file_path: log
diff --git a/test/multiverse/suites/sidekiq/log/.gitkeep b/test/multiverse/suites/sidekiq/log/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb b/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb
new file mode 100644
index 0000000..19af3d9
--- /dev/null
+++ b/test/multiverse/suites/sidekiq/sidekiq_instrumentation_test.rb
@@ -0,0 +1,137 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+# https://newrelic.atlassian.net/browse/RUBY-775
+
+require 'sidekiq'
+require 'test/unit'
+require 'newrelic_rpm'
+require 'fake_collector'
+
+class ResqueTest < Test::Unit::TestCase
+ JOB_COUNT = 5
+ COLLECTOR_PORT = ENV['NEWRELIC_MULTIVERSE_FAKE_COLLECTOR_PORT']
+
+ def setup
+ $collector ||= NewRelic::FakeCollector.new
+ $collector.reset
+ $collector.run(COLLECTOR_PORT)
+ @pidfile = "sidekiq.#{$$}.pid"
+ @sidekiq_log = "log/sidekiq.#{$$}.log"
+ JOB_COUNT.times do |i|
+ TestWorker.perform_async('jobs_completed', i + 1)
+ end
+ end
+
+ def teardown
+ $redis.flushall
+ File.unlink(@pidfile) if File.exist?(@pidfile)
+ File.unlink(@sidekiq_log) if File.exist?(@sidekiq_log)
+ end
+
+ def start_worker(opts={})
+ daemon_arg = opts[:daemonize] ? '-d' : ''
+ worker_cmd = "bundle exec sidekiq #{daemon_arg} -P #{@pidfile} -L #{@sidekiq_log} -r ./app.rb &"
+ system(worker_cmd)
+ end
+
+ def stop_worker
+ worker_pid = File.read(@pidfile).to_i
+ Process.kill("TERM", worker_pid)
+ begin
+ Timeout.timeout(5) { sleep(1) until !process_alive?(worker_pid) }
+ rescue Timeout::Error => e
+ raise e.exception("timed out waiting for sidekiq worker exit")
+ end
+ end
+
+ def process_alive?(pid)
+ Process.kill(0, pid)
+ return true
+ rescue Errno::ESRCH
+ return false
+ end
+
+ def wait_for_jobs
+ time_for_jobs = 5
+ begin
+ stats = Sidekiq::Stats.new
+ Timeout.timeout(time_for_jobs) { sleep(0.1) until stats.processed == JOB_COUNT }
+ rescue Timeout::Error => err
+ raise err.exception("waiting #{time_for_jobs}s for completion of #{JOB_COUNT} jobs")
+ end
+ end
+
+ def run_worker(opts={})
+ begin
+ start_worker(opts)
+ wait_for_jobs
+ ensure
+ stop_worker
+ end
+ end
+
+ # The point here is to supress Sidekiq log output for passing tests, but
+ # dump the log for failing tests.
+ def capture_sidekiq_log
+ File.unlink(@sidekiq_log) if File.exist?(@sidekiq_log)
+ @sidekiq_log = "log/sidekiq.#{$$}.log"
+ begin
+ yield
+ rescue Exception => e
+ if File.exist?(@sidekiq_log)
+ log_contents = File.read(@sidekiq_log)
+ $stderr.puts "Sidekiq log contents (#{@sidekiq_log}):"
+ $stderr.puts log_contents
+ end
+ raise e
+ end
+ File.unlink(@sidekiq_log) if File.exist?(@sidekiq_log)
+ end
+
+ METRIC_VALUES_POSITION = 3
+
+ def assert_metric_and_call_count(name, expected_call_count)
+ metric_data = $collector.calls_for('metric_data')
+ assert_equal(1, metric_data.size, "expected exactly one metric_data post from agent")
+
+ metric = metric_data.first[METRIC_VALUES_POSITION].find { |m| m[0]['name'] == name }
+ assert(metric, "could not find metric named #{name}")
+
+ call_count = metric[1][0]
+ assert_equal(expected_call_count, call_count)
+ end
+
+ def test_all_jobs_ran
+ capture_sidekiq_log do
+ run_worker
+ completed_jobs = Set.new($redis.smembers('jobs_completed').map(&:to_i))
+ expected_completed_jobs = Set.new((1..JOB_COUNT).to_a)
+ assert_equal(expected_completed_jobs, completed_jobs)
+ end
+ end
+
+ def test_agent_posts_correct_metric_data
+ capture_sidekiq_log do
+ run_worker
+ assert_metric_and_call_count('OtherTransaction/SidekiqJob/all', JOB_COUNT)
+ end
+ end
+
+ def test_all_jobs_ran_background
+ capture_sidekiq_log do
+ run_worker(:daemonize => true)
+ completed_jobs = Set.new($redis.smembers('jobs_completed').map(&:to_i))
+ expected_completed_jobs = Set.new((1..JOB_COUNT).to_a)
+ assert_equal(expected_completed_jobs, completed_jobs)
+ end
+ end
+
+ def test_agent_posts_correct_metric_data_background
+ capture_sidekiq_log do
+ run_worker(:daemonize => true)
+ assert_metric_and_call_count('OtherTransaction/SidekiqJob/all', JOB_COUNT)
+ end
+ end
+end
diff --git a/test/multiverse/suites/sinatra/Envfile b/test/multiverse/suites/sinatra/Envfile
index 91079d8..e3c6ac9 100644
--- a/test/multiverse/suites/sinatra/Envfile
+++ b/test/multiverse/suites/sinatra/Envfile
@@ -5,9 +5,11 @@ end
gemfile <<-RB
gem 'sinatra', '1.3.3'
gem 'rack-test', :require => 'rack/test'
+ gem 'mocha'
RB
gemfile <<-RB
gem 'sinatra', '1.2.8'
gem 'rack-test', :require => 'rack/test'
+ gem 'mocha'
RB
diff --git a/test/multiverse/suites/sinatra/config/newrelic.yml b/test/multiverse/suites/sinatra/config/newrelic.yml
index 0dd0a72..a417335 100644
--- a/test/multiverse/suites/sinatra/config/newrelic.yml
+++ b/test/multiverse/suites/sinatra/config/newrelic.yml
@@ -10,6 +10,7 @@ development:
license_key: bootstrap_newrelic_admin_license_key_000
developer_mode: false
app_name: test
+ sync_startup: true
host: 127.0.0.1
api_host: 127.0.0.1
port: <%= 30_000 + ($$ % 10_000) %>
diff --git a/test/multiverse/suites/sinatra/sinatra_error_tracing_test.rb b/test/multiverse/suites/sinatra/sinatra_error_tracing_test.rb
index cd16b82..70b8998 100644
--- a/test/multiverse/suites/sinatra/sinatra_error_tracing_test.rb
+++ b/test/multiverse/suites/sinatra/sinatra_error_tracing_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class SinatraErrorTracingTestApp < Sinatra::Base
configure do
set :show_exceptions, false
diff --git a/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb b/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb
index 2e2f91e..5366400 100644
--- a/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb
+++ b/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class SinatraTestApp < Sinatra::Base
get '/hello/:name' do |name|
@@ -42,14 +46,14 @@ class SinatraMetricExplosionTest < Test::Unit::TestCase
def test_transaction_name_from_route
get '/hello/world'
- metric_names = ::NewRelic::Agent.agent.stats_engine.stats_hash.keys.map{|k| k.name}
+ metric_names = ::NewRelic::Agent.agent.stats_engine.metrics
assert metric_names.include?('Controller/Sinatra/SinatraTestApp/GET hello/([^/?#]+)')
assert metric_names.include?('Apdex/Sinatra/SinatraTestApp/GET hello/([^/?#]+)')
end
def test_transaction_name_from_path
get '/wrong'
- metric_names = ::NewRelic::Agent.agent.stats_engine.stats_hash.keys.map{|k| k.name}
+ metric_names = ::NewRelic::Agent.agent.stats_engine.metrics
assert metric_names.include?('Controller/Sinatra/SinatraTestApp/GET (unknown)')
assert metric_names.include?('Apdex/Sinatra/SinatraTestApp/GET (unknown)')
end
@@ -61,8 +65,8 @@ class SinatraMetricExplosionTest < Test::Unit::TestCase
get '/hello/isitmeyourelookingfor?'
get '/another_controller'
- metric_names = ::NewRelic::Agent.agent.stats_engine.stats_hash.keys.
- map{|k| k.name} - ['CPU/User Time', "Middleware/all", "WebFrontend/QueueTime", "WebFrontend/WebServer/all"]
+ metric_names = ::NewRelic::Agent.agent.stats_engine.metrics
+ metric_names -= ['CPU/User Time', "Middleware/all", "WebFrontend/QueueTime", "WebFrontend/WebServer/all"]
assert_equal 6, metric_names.size, "Explosion detected in: #{metric_names.inspect}"
end
@@ -70,7 +74,7 @@ class SinatraMetricExplosionTest < Test::Unit::TestCase
assert_nothing_raised do
post '/some/garbage'
end
- metric_names = ::NewRelic::Agent.agent.stats_engine.stats_hash.keys.map{|k| k.name}
+ metric_names = ::NewRelic::Agent.agent.stats_engine.metrics
assert metric_names.include?('Controller/Sinatra/SinatraTestApp/POST (unknown)')
assert metric_names.include?('Apdex/Sinatra/SinatraTestApp/POST (unknown)')
end
diff --git a/test/multiverse/suites/sinatra/sinatra_routes_test.rb b/test/multiverse/suites/sinatra/sinatra_routes_test.rb
index b191efe..b5705b7 100644
--- a/test/multiverse/suites/sinatra/sinatra_routes_test.rb
+++ b/test/multiverse/suites/sinatra/sinatra_routes_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class SinatraRouteTestApp < Sinatra::Base
configure do
diff --git a/test/multiverse/suites/sinatra/sinatra_test.rb b/test/multiverse/suites/sinatra/sinatra_test.rb
index 104e62f..045f619 100644
--- a/test/multiverse/suites/sinatra/sinatra_test.rb
+++ b/test/multiverse/suites/sinatra/sinatra_test.rb
@@ -1,6 +1,14 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require 'mocha'
class SinatraRouteTestApp < Sinatra::Base
configure do
+ # display exceptions so we see what's going on
+ disable :show_exceptions
+
# create a condition (sintra's version of a before_filter) that returns the
# value that was passed into it.
set :my_condition do |boolean|
@@ -8,9 +16,6 @@ class SinatraRouteTestApp < Sinatra::Base
halt 404 unless boolean
end
end
-
- # treat errors like production for testing purposes
- set :show_exceptions, false
end
get '/user/login' do
@@ -22,10 +27,37 @@ class SinatraRouteTestApp < Sinatra::Base
"Welcome #{id}"
end
- get '/error' do
+ get '/raise' do
raise "Uh-oh"
end
+ # check that pass works properly
+ condition { pass { halt 418, "I'm a teapot." } }
+ get('/pass') { }
+
+ get '/pass' do
+ "I'm not a teapot."
+ end
+
+ class Error < StandardError; end
+ error(Error) { halt 200, 'nothing happened' }
+ condition { raise Error }
+ get('/error') { }
+
+ def perform_action_with_newrelic_trace(options)
+ $last_sinatra_route = options[:name]
+ super
+ end
+
+ get '/route/:name' do |name|
+ # usually this would be a db test or something
+ pass if name != 'match'
+ 'first route'
+ end
+
+ get '/route/no_match' do
+ 'second route'
+ end
end
class SinatraTest < Test::Unit::TestCase
@@ -62,7 +94,50 @@ class SinatraTest < Test::Unit::TestCase
end
def test_shown_errors_get_caught
+ get '/raise'
+ assert_equal 1, ::NewRelic::Agent.agent.error_collector.errors.size
+ end
+
+ def test_does_not_break_pass
+ get '/pass'
+ assert_equal 200, last_response.status
+ assert_equal "I'm not a teapot.", last_response.body
+ end
+
+ def test_does_not_break_error_handling
+ get '/error'
+ assert_equal 200, last_response.status
+ assert_equal "nothing happened", last_response.body
+ end
+
+ def test_sees_handled_error
get '/error'
assert_equal 1, ::NewRelic::Agent.agent.error_collector.errors.size
end
+
+ def test_correct_pattern
+ get '/route/match'
+ assert_equal 'first route', last_response.body
+ assert_equal 'GET route/([^/?#]+)', $last_sinatra_route
+
+ get '/route/no_match'
+ assert_equal 'second route', last_response.body
+
+ # Ideally we could handle this assert, but we can't rename transactions
+ # in flight at this point. Once we get that ability, consider patching
+ # process_route to notify of route name changes.
+
+ # assert_equal 'GET route/no_match', $last_sinatra_route
+ end
+
+ def test_set_unknown_transaction_name_if_error_in_routing
+ ::NewRelic::Agent::Instrumentation::Sinatra::NewRelic \
+ .stubs(:http_verb).raises(StandardError.new('madness'))
+
+ get '/user/login'
+
+ metric_names = ::NewRelic::Agent.agent.stats_engine.metrics
+ assert(metric_names.include?('Controller/Sinatra/SinatraRouteTestApp/(unknown)'),
+ "#{metric_names} should include 'Controller/Sinatra/SinatraRouteTestApp/(unknown)'")
+ end
end
diff --git a/test/multiverse/test/multiverse_test.rb b/test/multiverse/test/multiverse_test.rb
index b691e52..c57a2d8 100644
--- a/test/multiverse/test/multiverse_test.rb
+++ b/test/multiverse/test/multiverse_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
ENV['NEWRELIC_GEM_PATH'] = '../../../../../ruby_agent'
diff --git a/test/multiverse/test/suite_examples/one/a/a_test.rb b/test/multiverse/test/suite_examples/one/a/a_test.rb
index e8a224c..eb3d3bc 100644
--- a/test/multiverse/test/suite_examples/one/a/a_test.rb
+++ b/test/multiverse/test/suite_examples/one/a/a_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
class ATest < Test::Unit::TestCase
def test_timetrap_is_loaded
diff --git a/test/multiverse/test/suite_examples/one/b/b_test.rb b/test/multiverse/test/suite_examples/one/b/b_test.rb
index 7190609..ac4bccc 100644
--- a/test/multiverse/test/suite_examples/one/b/b_test.rb
+++ b/test/multiverse/test/suite_examples/one/b/b_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
class BTest < Test::Unit::TestCase
def test_timetrap_is_not_loaded
diff --git a/test/multiverse/test/suite_examples/three/a/fail_test.rb b/test/multiverse/test/suite_examples/three/a/fail_test.rb
index 9250cdb..8837753 100644
--- a/test/multiverse/test/suite_examples/three/a/fail_test.rb
+++ b/test/multiverse/test/suite_examples/three/a/fail_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
class ATest < Test::Unit::TestCase
def test_failure
diff --git a/test/multiverse/test/suite_examples/three/b/win_test.rb b/test/multiverse/test/suite_examples/three/b/win_test.rb
index e390b38..4bac152 100644
--- a/test/multiverse/test/suite_examples/three/b/win_test.rb
+++ b/test/multiverse/test/suite_examples/three/b/win_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
class ATest < Test::Unit::TestCase
def test_success
diff --git a/test/multiverse/test/suite_examples/two/a/fail_test.rb b/test/multiverse/test/suite_examples/two/a/fail_test.rb
index 9250cdb..8837753 100644
--- a/test/multiverse/test/suite_examples/two/a/fail_test.rb
+++ b/test/multiverse/test/suite_examples/two/a/fail_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'test/unit'
class ATest < Test::Unit::TestCase
def test_failure
diff --git a/test/new_relic/agent/agent/connect_test.rb b/test/new_relic/agent/agent/connect_test.rb
index 40bb083..1851086 100644
--- a/test/new_relic/agent/agent/connect_test.rb
+++ b/test/new_relic/agent/agent/connect_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..', '..','test_helper'))
require 'new_relic/agent/agent'
require 'ostruct'
@@ -78,18 +82,15 @@ class NewRelic::Agent::Agent::ConnectTest < Test::Unit::TestCase
handle_license_error(error)
end
- def test_environment_for_connect_positive
- fake_env = stub('local_env', :discovered_dispatcher => nil)
- fake_env.expects(:snapshot).returns("snapshot")
- NewRelic::Control.instance.expects(:local_env).at_least_once.returns(fake_env)
- with_config(:send_environment_info => true) do
- assert_equal 'snapshot', environment_for_connect
- end
+ def test_connect_settings_have_environment_report
+ assert NewRelic::Agent.agent.connect_settings[:environment].detect{ |(k, v)|
+ k == 'Gems'
+ }, "expected connect_settings to include gems from environment"
end
def test_environment_for_connect_negative
with_config(:send_environment_info => false) do
- assert_equal [], environment_for_connect
+ assert_equal [], NewRelic::Agent.agent.connect_settings[:environment]
end
end
@@ -181,7 +182,7 @@ class NewRelic::Agent::Agent::ConnectTest < Test::Unit::TestCase
query_server_for_configuration
end
- def test_connect_to_server_gets_config_from_collector
+ def test_connect_gets_config
NewRelic::Agent.manual_start
NewRelic::Agent::Agent.instance.service = default_service(
:connect => {'agent_run_id' => 23, 'config' => 'a lot'})
@@ -194,6 +195,50 @@ class NewRelic::Agent::Agent::ConnectTest < Test::Unit::TestCase
NewRelic::Agent.shutdown
end
+ def test_finish_setup_saves_transaction_name_rules
+ NewRelic::Agent.instance.instance_variable_set(:@transaction_rules,
+ NewRelic::Agent::RulesEngine.new)
+ config = {
+ 'transaction_name_rules' => [ { 'match_expression' => '88',
+ 'replacement' => '**' },
+ { 'match_expression' => 'xx',
+ 'replacement' => 'XX' } ]
+ }
+ finish_setup(config)
+
+ rules = NewRelic::Agent.instance.transaction_rules
+ assert_equal 2, rules.size
+ assert(rules.find{|r| r.match_expression == /88/ && r.replacement == '**' },
+ "rule not found among #{rules}")
+ assert(rules.find{|r| r.match_expression == /xx/ && r.replacement == 'XX' },
+ "rule not found among #{rules}")
+ ensure
+ NewRelic::Agent.instance.instance_variable_set(:@transaction_rules,
+ NewRelic::Agent::RulesEngine.new)
+ end
+
+ def test_finish_setup_saves_metric_name_rules
+ NewRelic::Agent.instance.instance_variable_set(:@metric_rules,
+ NewRelic::Agent::RulesEngine.new)
+ config = {
+ 'metric_name_rules' => [ { 'match_expression' => '77',
+ 'replacement' => '&&' },
+ { 'match_expression' => 'yy',
+ 'replacement' => 'YY' }]
+ }
+ finish_setup(config)
+
+ rules = NewRelic::Agent.instance.metric_rules
+ assert_equal 2, rules.size
+ assert(rules.find{|r| r.match_expression == /77/ && r.replacement == '&&' },
+ "rule not found among #{rules}")
+ assert(rules.find{|r| r.match_expression == /yy/ && r.replacement == 'YY' },
+ "rule not found among #{rules}")
+ ensure
+ NewRelic::Agent.instance.instance_variable_set(:@metric_rules,
+ NewRelic::Agent::RulesEngine.new)
+ end
+
def test_finish_setup
config = {
'agent_run_id' => 'fishsticks',
diff --git a/test/new_relic/agent/agent/start_test.rb b/test/new_relic/agent/agent/start_test.rb
index 0df40ef..84909e6 100644
--- a/test/new_relic/agent/agent/start_test.rb
+++ b/test/new_relic/agent/agent/start_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
class NewRelic::Agent::Agent::StartTest < Test::Unit::TestCase
require 'new_relic/agent/agent'
@@ -14,7 +18,9 @@ class NewRelic::Agent::Agent::StartTest < Test::Unit::TestCase
end
def test_already_started_positive
- ::Logger.any_instance.expects(:error).with("Agent Started Already!")
+ dummy_logger = mock
+ dummy_logger.expects(:error).with("Agent Started Already!")
+ NewRelic::Agent.stubs(:logger).returns(dummy_logger)
self.expects(:started?).returns(true)
assert already_started?, "should have already started"
end
diff --git a/test/new_relic/agent/agent/start_worker_thread_test.rb b/test/new_relic/agent/agent/start_worker_thread_test.rb
index 6352612..5e4a6af 100644
--- a/test/new_relic/agent/agent/start_worker_thread_test.rb
+++ b/test/new_relic/agent/agent/start_worker_thread_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
class NewRelic::Agent::Agent::StartWorkerThreadTest < Test::Unit::TestCase
require 'new_relic/agent/agent'
@@ -36,13 +40,10 @@ class NewRelic::Agent::Agent::StartWorkerThreadTest < Test::Unit::TestCase
self.expects(:reset_stats)
self.expects(:sleep).with(30)
-
- @metric_ids = 'this is not an empty hash'
@connected = true
handle_force_restart(error)
- assert_equal({}, @metric_ids)
assert_equal(:pending, @connect_state)
end
diff --git a/test/new_relic/agent/agent_logger_test.rb b/test/new_relic/agent/agent_logger_test.rb
index 01bea49..8bede17 100644
--- a/test/new_relic/agent/agent_logger_test.rb
+++ b/test/new_relic/agent/agent_logger_test.rb
@@ -1,5 +1,10 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/agent/agent_logger'
+require 'new_relic/agent/null_logger'
class AgentLoggerTest < Test::Unit::TestCase
def setup
@@ -63,18 +68,17 @@ class AgentLoggerTest < Test::Unit::TestCase
end
def test_wont_log_if_agent_not_enabled
- ::Logger.stubs(:new).with("/dev/null").returns(stub(:level=)).at_least_once
-
+ null_logger = NewRelic::Agent::NullLogger.new
+ NewRelic::Agent::NullLogger.expects(:new).returns(null_logger)
@config[:agent_enabled] = false
logger = NewRelic::Agent::AgentLogger.new(@config)
+ assert_nothing_raised do
+ logger.warn('hi there')
+ end
end
- def test_logs_to_nul_if_dev_null_not_there
- File.stubs(:exists?).with("/dev/null").returns(false)
- File.stubs(:exists?).with("NUL").returns(true)
-
- ::Logger.stubs(:new).with("NUL").returns(stub(:level=)).once
-
+ def test_does_not_touch_dev_null
+ Logger.expects(:new).with('/dev/null').never
@config[:agent_enabled] = false
logger = NewRelic::Agent::AgentLogger.new(@config)
end
diff --git a/test/new_relic/agent/agent_test.rb b/test/new_relic/agent/agent_test.rb
index a3d2a81..42b881b 100644
--- a/test/new_relic/agent/agent_test.rb
+++ b/test/new_relic/agent/agent_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/agent/thread_profiler'
@@ -39,8 +43,9 @@ module NewRelic
def test_after_fork_should_close_pipe_if_parent_not_connected
pipe = mock
- pipe.expects(:write).with('EOF')
+ pipe.expects(:after_fork_in_child)
pipe.expects(:close)
+ pipe.stubs(:parent_pid).returns(:digglewumpus)
dummy_channels = { 123 => pipe }
NewRelic::Agent::PipeChannelManager.stubs(:channels).returns(dummy_channels)
@@ -64,16 +69,6 @@ module NewRelic
@agent.instance_eval { transmit_data }
end
- def test_transmit_data_should_not_close_db_connections_if_forked
- NewRelic::Agent::Database.expects(:close_connections).never
- @agent.after_fork
- @agent.instance_eval { transmit_data }
- end
-
- def test_serialize
- assert_equal([{}, [], []], @agent.send(:serialize), "should return nil when shut down")
- end
-
def test_harvest_transaction_traces
assert_equal([], @agent.send(:harvest_transaction_traces), 'should return transaction traces')
end
@@ -121,23 +116,29 @@ module NewRelic
'should return timeslice data')
end
- def test_harvest_timelice_data_should_be_thread_safe
- 2000.times do |i|
- @agent.stats_engine.stats_hash[i.to_s] = NewRelic::StatsBase.new
- end
-
- harvest = Thread.new("Harvesting Test run timeslices") do
- @agent.send(:harvest_timeslice_data)
- end
+ # This test asserts nothing about correctness of logging data from multiple
+ # threads, since the get_stats + record_data_point combo is not designed
+ # to be thread-safe, but it does ensure that writes to the stats hash
+ # via this path that happen concurrently with harvests will not cause
+ # 'hash modified during iteration' errors.
+ def test_harvest_timeslice_data_should_be_thread_safe
+ threads = []
+ nthreads = 10
+ nmetrics = 100
- app = Thread.new("Harvesting Test Modify stats_hash") do
- 200.times do |i|
- @agent.stats_engine.stats_hash["a#{i}"] = NewRelic::StatsBase.new
+ assert_nothing_raised do
+ nthreads.times do |tid|
+ t = Thread.new do
+ nmetrics.times do |mid|
+ @agent.stats_engine.get_stats("m#{mid}").record_data_point(1)
+ end
+ end
+ t.abort_on_exception = true
+ threads << t
end
- end
- assert_nothing_raised do
- [app, harvest].each{|t| t.join}
+ 100.times { @agent.send(:harvest_timeslice_data) }
+ threads.each { |t| t.join }
end
end
@@ -228,28 +229,6 @@ module NewRelic
assert_equal 0, NewRelic::Agent.get_stats("Errors/all").call_count
end
- def test_fill_metric_id_cache_from_collect_response
- response = [[{"scope"=>"Controller/blogs/index", "name"=>"Database/SQL/other"}, 1328],
- [{"scope"=>"", "name"=>"WebFrontend/QueueTime"}, 10],
- [{"scope"=>"", "name"=>"ActiveRecord/Blog/find"}, 1017]]
-
- @agent.send(:fill_metric_id_cache, response)
- assert_equal 1328, @agent.metric_ids[MetricSpec.new('Database/SQL/other', 'Controller/blogs/index')]
- assert_equal 10, @agent.metric_ids[MetricSpec.new('WebFrontend/QueueTime')]
- assert_equal 1017, @agent.metric_ids[MetricSpec.new('ActiveRecord/Blog/find')]
- end
-
- def test_fill_metric_id_cache_from_collect_response
- response = [[{"scope"=>"Controller/blogs/index", "name"=>"Database/SQL/other"}, 1328],
- [{"scope"=>"", "name"=>"WebFrontend/QueueTime"}, 10],
- [{"scope"=>"", "name"=>"ActiveRecord/Blog/find"}, 1017]]
-
- @agent.send(:fill_metric_id_cache, response)
- assert_equal 1328, @agent.metric_ids[MetricSpec.new('Database/SQL/other', 'Controller/blogs/index')]
- assert_equal 10, @agent.metric_ids[MetricSpec.new('WebFrontend/QueueTime')]
- assert_equal 1017, @agent.metric_ids[MetricSpec.new('ActiveRecord/Blog/find')]
- end
-
def test_connect_retries_on_timeout
service = @agent.service
def service.connect(opts={})
diff --git a/test/new_relic/agent/agent_test_controller.rb b/test/new_relic/agent/agent_test_controller.rb
index c721e19..029bf0c 100644
--- a/test/new_relic/agent/agent_test_controller.rb
+++ b/test/new_relic/agent/agent_test_controller.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# Defining a test controller class with a superclass, used to
# verify correct attribute inheritence
class NewRelic::Agent::SuperclassController < ActionController::Base
diff --git a/test/new_relic/agent/agent_test_controller_test.rb b/test/new_relic/agent/agent_test_controller_test.rb
index eb259c5..9b25e44 100644
--- a/test/new_relic/agent/agent_test_controller_test.rb
+++ b/test/new_relic/agent/agent_test_controller_test.rb
@@ -1,5 +1,9 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'action_controller/test_case'
+
class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
require 'action_controller/base'
require 'new_relic/agent/agent_test_controller'
@@ -8,7 +12,7 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
attr_accessor :agent, :engine
- def test_initialization
+ def suite_initialization
# Suggested by cee-dub for merb tests. I'm actually amazed if our tests work with merb.
if defined?(Merb::Router)
Merb::Router.prepare do |r|
@@ -21,19 +25,19 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
end
else
Rails.application.routes.draw do
- match '/:controller/:action.:format'
- match '/:controller/:action'
+ get '/:controller/:action.:format'
+ get '/:controller/:action'
end
end
- if defined?(Rails) && Rails.respond_to?(:application) && Rails.application.respond_to?(:routes)
+ if defined?(Rails) && Rails.respond_to?(:application) &&
+ Rails.application.respond_to?(:routes)
@routes = Rails.application.routes
end
Thread.current[:newrelic_ignore_controller] = nil
NewRelic::Agent.manual_start
@agent = NewRelic::Agent.instance
- # @agent.instrument_app
agent.transaction_sampler.harvest
NewRelic::Agent::AgentTestController.class_eval do
newrelic_ignore :only => [:action_to_ignore, :entry_action, :base_action]
@@ -47,10 +51,10 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
if NewRelic::Control.instance.rails_version <= '2.1.0'
def initialize name
super name
- test_initialization
+ suite_initialization
end
else
- alias_method :setup, :test_initialization
+ alias_method :setup, :suite_initialization
end
def teardown
@@ -63,8 +67,11 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
def test_mongrel_queue
NewRelic::Agent::AgentTestController.clear_headers
engine.clear_stats
- NewRelic::Control.instance.local_env.stubs(:mongrel).returns( stub('mongrel', :workers => stub('workers', :list => stub('list', :length => '10'))))
-
+ NewRelic::Control.instance.local_env.stubs(:mongrel) \
+ .returns(stub('mongrel',
+ :workers => stub('workers',
+ :list => stub('list',
+ :length => '10'))))
get :index
assert_equal 1, stats('HttpDispatcher').call_count
assert_equal 1, engine.get_stats_no_scope('Mongrel/Queue Length').call_count
@@ -115,6 +122,7 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
assert_equal 'foofah', @response.body
compare_metrics %w[Controller/new_relic/agent/agent_test/action_inline], engine.metrics.grep(/^Controller/)
end
+
def test_metric__ignore
engine.clear_stats
compare_metrics [], engine.metrics
@@ -131,18 +139,19 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
'Apdex/new_relic/agent/agent_test/action_with_error',
'HttpDispatcher',
'Controller/new_relic/agent/agent_test/action_with_error',
- 'Errors/all']
+ 'Errors/all',
+ 'Errors/Controller/new_relic/agent/agent_test/action_with_error']
compare_metrics metrics, engine.metrics.reject{|m| m.index('Response')==0 || m.index('CPU')==0}
assert_equal 1, engine.get_stats_no_scope("Controller/new_relic/agent/agent_test/action_with_error").call_count
assert_equal 1, engine.get_stats_no_scope("Errors/all").call_count
apdex = engine.get_stats_no_scope("Apdex")
- score = apdex.get_apdex
- assert_equal 1, score[2], 'failing'
- assert_equal 0, score[1], 'tol'
- assert_equal 0, score[0], 'satisfied'
+ assert_equal 1, apdex.apdex_f, 'failing'
+ assert_equal 0, apdex.apdex_t, 'tol'
+ assert_equal 0, apdex.apdex_s, 'satisfied'
end
+
def test_controller_error
engine.clear_stats
assert_raise RuntimeError do
@@ -152,18 +161,19 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
'Apdex/new_relic/agent/agent_test/action_with_error',
'HttpDispatcher',
'Controller/new_relic/agent/agent_test/action_with_error',
- 'Errors/all']
+ 'Errors/all',
+ 'Errors/Controller/new_relic/agent/agent_test/action_with_error']
compare_metrics metrics, engine.metrics.reject{|m| m.index('Response')==0 || m.index('CPU')==0}
assert_equal 1, engine.get_stats_no_scope("Controller/new_relic/agent/agent_test/action_with_error").call_count
assert_equal 1, engine.get_stats_no_scope("Errors/all").call_count
apdex = engine.get_stats_no_scope("Apdex")
- score = apdex.get_apdex
- assert_equal 1, score[2], 'failing'
- assert_equal 0, score[1], 'tol'
- assert_equal 0, score[0], 'satisfied'
+ assert_equal 1, apdex.apdex_f, 'failing'
+ assert_equal 0, apdex.apdex_t, 'tol'
+ assert_equal 0, apdex.apdex_s, 'satisfied'
end
+
def test_filter_error
engine.clear_stats
assert_raise RuntimeError do
@@ -173,26 +183,29 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
'Apdex/new_relic/agent/agent_test/action_with_before_filter_error',
'HttpDispatcher',
'Controller/new_relic/agent/agent_test/action_with_before_filter_error',
- 'Errors/all']
+ 'Errors/all',
+ 'Errors/Controller/new_relic/agent/agent_test/action_with_before_filter_error']
compare_metrics metrics, engine.metrics.reject{|m| m.index('Response')==0 || m.index('CPU')==0 || m.index('GC')==0}
assert_equal 1, engine.get_stats_no_scope("Controller/new_relic/agent/agent_test/action_with_before_filter_error").call_count
assert_equal 1, engine.get_stats_no_scope("Errors/all").call_count
apdex = engine.get_stats_no_scope("Apdex")
- score = apdex.get_apdex
- assert_equal 1, score[2], 'failing'
- assert_equal 0, score[1], 'tol'
- assert_equal 0, score[0], 'satisfied'
+
+ assert_equal 1, apdex.apdex_f, 'failing'
+ assert_equal 0, apdex.apdex_t, 'tol'
+ assert_equal 0, apdex.apdex_s, 'satisfied'
end
+
def test_metric__ignore_base
engine.clear_stats
get :base_action
compare_metrics [], engine.metrics
end
+
def test_metric__no_ignore
path = 'new_relic/agent/agent_test/index'
index_stats = stats("Controller/#{path}")
- index_apdex_stats = engine.get_custom_stats("Apdex/#{path}", NewRelic::ApdexStats)
+ index_apdex_stats = engine.get_stats_no_scope("Apdex/#{path}")
assert_difference 'index_stats.call_count' do
assert_difference 'index_apdex_stats.call_count' do
get :index
@@ -200,20 +213,21 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
end
assert_nil Thread.current[:newrelic_ignore_controller]
end
+
def test_metric__ignore_apdex
engine = @agent.stats_engine
path = 'new_relic/agent/agent_test/action_to_ignore_apdex'
cpu_stats = stats("ControllerCPU/#{path}")
index_stats = stats("Controller/#{path}")
- index_apdex_stats = engine.get_custom_stats("Apdex/#{path}", NewRelic::ApdexStats)
+ index_apdex_stats = engine.get_stats_no_scope("Apdex/#{path}")
assert_difference 'index_stats.call_count' do
assert_no_difference 'index_apdex_stats.call_count' do
get :action_to_ignore_apdex
end
end
assert_nil Thread.current[:newrelic_ignore_controller]
-
end
+
def test_metric__dispatched
engine = @agent.stats_engine
get :entry_action
@@ -225,6 +239,7 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
assert_nil engine.lookup_stats('Controller/NewRelic::Agent::AgentTestController_controller/internal_action')
assert_not_nil engine.lookup_stats('Controller/NewRelic::Agent::AgentTestController/internal_traced_action')
end
+
def test_action_instrumentation
get :index, :foo => 'bar'
assert_match /bar/, @response.body
@@ -241,18 +256,16 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
end
def test_controller_params
- agent.transaction_sampler.reset!
- get :index, 'number' => "001-555-1212"
s = with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ agent.transaction_sampler.reset!
+ get :index, 'number' => "001-555-1212"
agent.transaction_sampler.harvest(nil)
end
assert_equal 1, s.size
assert_equal 5, s.first.params.size
end
-
def test_busy_calculation_correctly_calculates_based_acccumlator
-
# woah it's 1970
now = Time.at 0
@@ -323,6 +336,7 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
assert(queue_time_stat.total_call_time < 10, "Queue time should be under 10 seconds (sanity check)")
end
+
def test_queue_headers_heroku
# make this test deterministic
Time.stubs(:now => Time.at(1360973845))
@@ -373,4 +387,3 @@ class NewRelic::Agent::AgentTestControllerTest < ActionController::TestCase
end
end if defined? Rails
-
diff --git a/test/new_relic/agent/apdex_from_server_test.rb b/test/new_relic/agent/apdex_from_server_test.rb
index 95e880a..8cc2438 100644
--- a/test/new_relic/agent/apdex_from_server_test.rb
+++ b/test/new_relic/agent/apdex_from_server_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
class NewRelic::Agent::ApdexFromServerTest < Test::Unit::TestCase
diff --git a/test/new_relic/agent/audit_logger_test.rb b/test/new_relic/agent/audit_logger_test.rb
index e357116..9921fba 100644
--- a/test/new_relic/agent/audit_logger_test.rb
+++ b/test/new_relic/agent/audit_logger_test.rb
@@ -1,5 +1,10 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/agent/audit_logger'
+require 'new_relic/agent/null_logger'
class AuditLoggerTest < Test::Unit::TestCase
def setup
@@ -62,6 +67,17 @@ class AuditLoggerTest < Test::Unit::TestCase
assert_nil(logger.ensure_log_path)
end
+ def test_setup_logger_creates_null_logger_when_ensure_path_fails
+ null_logger = NewRelic::Agent::NullLogger.new
+ NewRelic::Agent::NullLogger.expects(:new).returns(null_logger)
+ logger = NewRelic::Agent::AuditLogger.new(@config)
+ logger.stubs(:ensure_log_path).returns(nil)
+ assert_nothing_raised do
+ logger.setup_logger
+ logger.log_request(@uri, 'whatever', @marshaller)
+ end
+ end
+
def test_log_request_captures_system_call_errors
logger = NewRelic::Agent::AuditLogger.new(@config)
dummy_sink = StringIO.new
diff --git a/test/new_relic/agent/beacon_configuration_test.rb b/test/new_relic/agent/beacon_configuration_test.rb
index e7b5ab9..6dc8a15 100644
--- a/test/new_relic/agent/beacon_configuration_test.rb
+++ b/test/new_relic/agent/beacon_configuration_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require "new_relic/agent/beacon_configuration"
class NewRelic::Agent::BeaconConfigurationTest < Test::Unit::TestCase
diff --git a/test/new_relic/agent/browser_monitoring_test.rb b/test/new_relic/agent/browser_monitoring_test.rb
index 9012db5..efa26bf 100644
--- a/test/new_relic/agent/browser_monitoring_test.rb
+++ b/test/new_relic/agent/browser_monitoring_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
ENV['SKIP_RAILS'] = 'true'
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require "new_relic/agent/browser_monitoring"
diff --git a/test/new_relic/agent/busy_calculator_test.rb b/test/new_relic/agent/busy_calculator_test.rb
index 1d756d7..accdf12 100644
--- a/test/new_relic/agent/busy_calculator_test.rb
+++ b/test/new_relic/agent/busy_calculator_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# Run faster standalone
ENV['SKIP_RAILS'] = 'true'
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
@@ -6,8 +10,8 @@ class NewRelic::Agent::BusyCalculatorTest < Test::Unit::TestCase
def setup
@now = Time.now.to_f
NewRelic::Agent::BusyCalculator.reset
- @instance_busy = NewRelic::MethodTraceStats.new
- NewRelic::Agent::BusyCalculator.stubs(:instance_busy_stats).returns(@instance_busy)
+ NewRelic::Agent.agent.stats_engine.clear_stats
+ @instance_busy = NewRelic::Agent.agent.stats_engine.get_stats_no_scope('Instance/Busy')
end
def test_normal
diff --git a/test/new_relic/agent/configuration/environment_source_test.rb b/test/new_relic/agent/configuration/environment_source_test.rb
index dcaf002..1b09ea9 100644
--- a/test/new_relic/agent/configuration/environment_source_test.rb
+++ b/test/new_relic/agent/configuration/environment_source_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
require 'new_relic/agent/configuration/environment_source'
diff --git a/test/new_relic/agent/configuration/manager_test.rb b/test/new_relic/agent/configuration/manager_test.rb
index b8f5414..2b2e772 100644
--- a/test/new_relic/agent/configuration/manager_test.rb
+++ b/test/new_relic/agent/configuration/manager_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
require 'new_relic/agent/configuration/manager'
require 'new_relic/agent/configuration/mask_defaults'
diff --git a/test/new_relic/agent/configuration/server_source_test.rb b/test/new_relic/agent/configuration/server_source_test.rb
index b2173f3..1514005 100644
--- a/test/new_relic/agent/configuration/server_source_test.rb
+++ b/test/new_relic/agent/configuration/server_source_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
require 'new_relic/agent/configuration/server_source'
@@ -11,9 +15,10 @@ module NewRelic::Agent::Configuration
'transaction_tracer.record_sql' => 'raw',
'error_collector.enabled' => true
},
- 'apdex_t' => 1.0,
- 'collect_errors' => false,
- 'collect_traces' => true
+ 'apdex_t' => 1.0,
+ 'collect_errors' => false,
+ 'collect_traces' => true,
+ 'web_transactions_apdex' => { 'Controller/some/txn' => 1.5 }
}
@source = ServerSource.new(config)
end
@@ -41,5 +46,57 @@ module NewRelic::Agent::Configuration
def test_should_ignore_apdex_f_setting_for_transaction_threshold
assert_equal nil, @source[:'transaction_tracer.transaction_threshold']
end
+
+ def test_should_not_dot_the_web_transactions_apdex_hash
+ assert_equal 1.5, @source[:web_transactions_apdex]['Controller/some/txn']
+ end
+
+ def test_should_disable_gated_features_when_server_says_to
+ rsp = {
+ 'collect_errors' => false,
+ 'collect_traces' => false
+ }
+ existing_config = {
+ :'error_collector.enabled' => true,
+ :'slow_sql.enabled' => true,
+ :'transaction_tracer.enabled' => true
+ }
+ @source = ServerSource.new(rsp, existing_config)
+ assert !@source[:'error_collector.enabled']
+ assert !@source[:'slow_sql.enabled']
+ assert !@source[:'transaction_tracer.enabled']
+ end
+
+ def test_should_enable_gated_features_when_server_says_to
+ rsp = {
+ 'collect_errors' => true,
+ 'collect_traces' => true
+ }
+ existing_config = {
+ :'error_collector.enabled' => true,
+ :'slow_sql.enabled' => true,
+ :'transaction_tracer.enabled' => true
+ }
+ @source = ServerSource.new(rsp, existing_config)
+ assert @source[:'error_collector.enabled']
+ assert @source[:'slow_sql.enabled']
+ assert @source[:'transaction_tracer.enabled']
+ end
+
+ def test_should_allow_manual_disable_of_gated_features
+ rsp = {
+ 'collect_errors' => true,
+ 'collect_traces' => true
+ }
+ existing_config = {
+ :'error_collector.enabled' => false,
+ :'slow_sql.enabled' => false,
+ :'transaction_tracer.enabled' => false
+ }
+ @source = ServerSource.new(rsp, existing_config)
+ assert !@source[:'error_collector.enabled']
+ assert !@source[:'slow_sql.enabled']
+ assert !@source[:'transaction_tracer.enabled']
+ end
end
end
diff --git a/test/new_relic/agent/configuration/yaml_source_test.rb b/test/new_relic/agent/configuration/yaml_source_test.rb
index f24bd69..8831e3c 100644
--- a/test/new_relic/agent/configuration/yaml_source_test.rb
+++ b/test/new_relic/agent/configuration/yaml_source_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
require 'new_relic/agent/configuration/yaml_source'
diff --git a/test/new_relic/agent/cross_app_monitor_test.rb b/test/new_relic/agent/cross_app_monitor_test.rb
new file mode 100644
index 0000000..da6d7b9
--- /dev/null
+++ b/test/new_relic/agent/cross_app_monitor_test.rb
@@ -0,0 +1,249 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
+
+module NewRelic::Agent
+ class CrossAppMonitorTest < Test::Unit::TestCase
+ NEWRELIC_ID_HEADER = NewRelic::Agent::CrossAppMonitor::NEWRELIC_ID_HEADER
+ NEWRELIC_TXN_HEADER = NewRelic::Agent::CrossAppMonitor::NEWRELIC_TXN_HEADER
+
+ AGENT_CROSS_APP_ID = "qwerty"
+ REQUEST_CROSS_APP_ID = "42#1234"
+ TRANSACTION_GUID = '941B0E8001E444E8'
+ REF_TRANSACTION_GUID = '830092CDE59421D4'
+
+ TRANSACTION_NAME = 'transaction'
+ QUEUE_TIME = 1000
+ APP_TIME = 2000
+
+ ENCODING_KEY_NOOP = "\0"
+ TRUSTED_ACCOUNT_IDS = [42,13]
+
+ CROSS_APP_ID_POSITION = 0
+ TRANSACTION_NAME_POSITION = 1
+ QUEUE_TIME_POSITION = 2
+ APP_TIME_POSITION = 3
+ CONTENT_LENGTH_POSITION = 4
+
+ def setup
+ NewRelic::Agent.reset_config
+ NewRelic::Agent.instance.events.clear
+ @response = {}
+
+ @monitor = NewRelic::Agent::CrossAppMonitor.new()
+ @config = {
+ :cross_process_id => AGENT_CROSS_APP_ID,
+ :encoding_key => ENCODING_KEY_NOOP,
+ :trusted_account_ids => TRUSTED_ACCOUNT_IDS
+ }
+
+ NewRelic::Agent.config.apply_config( @config )
+ @monitor.register_event_listeners
+ NewRelic::Agent::TransactionInfo.get.guid = TRANSACTION_GUID
+
+ # NewRelic::Agent.logger = NewRelic::Agent::AgentLogger.new( {:log_level => 'debug'}, '', Logger.new($stderr) )
+ end
+
+ def teardown
+ NewRelic::Agent.config.remove_config( @config )
+ NewRelic::Agent.instance.events.clear
+ end
+
+
+ #
+ # Tests
+ #
+
+ def test_adds_response_header
+ with_default_timings
+
+ when_request_runs
+
+ assert_equal 'WyJxd2VydHkiLCJ0cmFuc2FjdGlvbiIsMTAwMC4wLDIwMDAuMCwtMSwiOTQxQjBFODAwMUU0NDRFOCJd', response_app_data
+ assert_equal [AGENT_CROSS_APP_ID, TRANSACTION_NAME, QUEUE_TIME, APP_TIME, -1, TRANSACTION_GUID], unpacked_response
+ end
+
+ def test_encodes_transaction_name
+ NewRelic::Agent::BrowserMonitoring.stubs(:timings).returns(stub(
+ :transaction_name => "\"'goo",
+ :queue_time_in_seconds => QUEUE_TIME,
+ :app_time_in_seconds => APP_TIME))
+
+ when_request_runs
+
+ assert_equal "\"'goo", unpacked_response[TRANSACTION_NAME_POSITION]
+ end
+
+ def test_doesnt_write_response_header_if_id_blank
+ with_default_timings
+
+ when_request_runs(for_id(''))
+ assert_nil response_app_data
+ end
+
+ def test_doesnt_write_response_header_if_untrusted_id
+ with_default_timings
+
+ when_request_runs(for_id("4#1234"))
+ assert_nil response_app_data
+ end
+
+ def test_doesnt_write_response_header_if_improperly_formatted_id
+ with_default_timings
+
+ when_request_runs(for_id("42"))
+ assert_nil response_app_data
+ end
+
+ def test_doesnt_add_header_if_no_id_in_request
+ when_request_runs({})
+ assert_nil response_app_data
+ end
+
+ def test_doesnt_add_header_if_no_id_on_agent
+ # Since a +nil+ will make it fall back to the config installed in setup,
+ # we need to remove that first in order to test the no-id case
+ newconfig = @config.merge( :cross_process_id => nil )
+ NewRelic::Agent.config.remove_config( @config )
+
+ with_config( newconfig ) do
+ when_request_runs
+ assert_nil response_app_data
+ end
+ end
+
+ def test_doesnt_add_header_if_config_disabled
+ with_config(:"cross_application_tracer.enabled" => false) do
+ when_request_runs
+ assert_nil response_app_data
+ end
+ end
+
+ def test_includes_content_length
+ with_default_timings
+
+ when_request_runs(for_id(REQUEST_CROSS_APP_ID).merge("Content-Length" => 3000))
+ assert_equal 3000, unpacked_response[CONTENT_LENGTH_POSITION]
+ end
+
+ def test_finds_content_length_from_headers
+ %w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH cOnTeNt-LeNgTh}.each do |key|
+ request = { key => 42 }
+
+ assert_equal(42, @monitor.content_length_from_request(request), \
+ "Failed to find header on key #{key}")
+ end
+ end
+
+ def test_writes_custom_parameters
+ with_default_timings
+
+ NewRelic::Agent.expects(:add_custom_parameters).with(:client_cross_process_id => REQUEST_CROSS_APP_ID)
+ NewRelic::Agent.expects(:add_custom_parameters).with(:referring_transaction_guid => REF_TRANSACTION_GUID)
+
+ when_request_runs
+ end
+
+ def test_error_writes_custom_parameters
+ with_default_timings
+
+ options = when_request_has_error
+
+ assert_equal REQUEST_CROSS_APP_ID, options[:client_cross_process_id]
+ end
+
+ def test_error_doesnt_write_custom_parameters_if_no_id
+ with_default_timings
+
+ options = when_request_has_error(for_id(''))
+
+ assert_equal false, options.key?(:client_cross_process_id)
+ end
+
+ def test_writes_metric
+ with_default_timings
+
+ expected_metric_name = "ClientApplication/#{REQUEST_CROSS_APP_ID}/all"
+ NewRelic::Agent.instance.stats_engine.expects(:record_metrics). \
+ with(expected_metric_name, APP_TIME)
+
+ when_request_runs
+ end
+
+ def test_doesnt_write_metric_if_id_blank
+ with_default_timings
+
+ NewRelic::Agent.instance.stats_engine.expects(:record_metrics).never
+
+ when_request_runs(for_id(''))
+ end
+
+ def test_decoding_blank
+ assert_equal "",
+ NewRelic::Agent::CrossAppMonitor::EncodingFunctions.decode_with_key( 'querty', "" )
+ end
+
+ def test_encode_with_nil_uses_empty_key
+ assert_equal "querty",
+ NewRelic::Agent::CrossAppMonitor::EncodingFunctions.encode_with_key( nil, 'querty' )
+ end
+
+ def test_encoding_functions_can_roundtrip_utf8_text
+ str = 'Анастасі́я Олексі́ївна Каме́нських'
+ encoded = NewRelic::Agent::CrossAppMonitor::EncodingFunctions.obfuscate_with_key( 'potap', str )
+ decoded = NewRelic::Agent::CrossAppMonitor::EncodingFunctions.decode_with_key( 'potap', encoded )
+ decoded.force_encoding( 'utf-8' ) if decoded.respond_to?( :force_encoding )
+ assert_equal str, decoded
+ end
+
+ #
+ # Helpers
+ #
+
+ def when_request_runs(request=for_id(REQUEST_CROSS_APP_ID))
+ event_listener = NewRelic::Agent.instance.events
+ event_listener.notify(:before_call, request)
+ event_listener.notify(:start_transaction, 'a name')
+ event_listener.notify(:after_call, request, [200, @response, ''])
+ end
+
+ def when_request_has_error(request=for_id(REQUEST_CROSS_APP_ID))
+ options = {}
+ event_listener = NewRelic::Agent.instance.events
+ event_listener.notify(:before_call, request)
+ event_listener.notify(:notice_error, nil, options)
+ event_listener.notify(:after_call, request, [500, @response, ''])
+
+ options
+ end
+
+ def with_default_timings
+ NewRelic::Agent::BrowserMonitoring.stubs(:timings).returns(stub(
+ :transaction_name => TRANSACTION_NAME,
+ :queue_time_in_seconds => QUEUE_TIME,
+ :app_time_in_seconds => APP_TIME))
+ end
+
+ def for_id(id)
+ encoded_id = id == "" ? "" : Base64.encode64(id)
+ encoded_txn_info = Base64.encode64( NewRelic.json_dump([ REF_TRANSACTION_GUID, false ]) )
+
+ return {
+ NEWRELIC_ID_HEADER => encoded_id,
+ NEWRELIC_TXN_HEADER => encoded_txn_info,
+ }
+ end
+
+ def response_app_data
+ @response['X-NewRelic-App-Data']
+ end
+
+ def unpacked_response
+ return nil unless response_app_data
+ NewRelic.json_load(Base64.decode64(response_app_data))
+ end
+
+ end
+end
diff --git a/test/new_relic/agent/cross_process_monitoring_test.rb b/test/new_relic/agent/cross_process_monitoring_test.rb
deleted file mode 100644
index e98f418..0000000
--- a/test/new_relic/agent/cross_process_monitoring_test.rb
+++ /dev/null
@@ -1,190 +0,0 @@
-require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
-
-module NewRelic::Agent
- class CrossProcessMonitorTest < Test::Unit::TestCase
- AGENT_CROSS_PROCESS_ID = "qwerty"
- REQUEST_CROSS_PROCESS_ID = "42#1234"
-
- ENCODING_KEY_NOOP = "\0"
- TRUSTED_ACCOUNT_IDS = [42,13]
-
- CROSS_PROCESS_ID_POSITION = 0
- TRANSACTION_NAME_POSITION = 1
- QUEUE_TIME_POSITION = 2
- APP_TIME_POSITION = 3
- CONTENT_LENGTH_POSITION = 4
-
- def setup
- @response = {}
-
- @monitor = NewRelic::Agent::CrossProcessMonitor.new()
- @monitor.finish_setup(
- :cross_process_id => AGENT_CROSS_PROCESS_ID,
- :encoding_key => ENCODING_KEY_NOOP,
- :trusted_account_ids => TRUSTED_ACCOUNT_IDS)
- end
-
- def when_request_runs(request=for_id(REQUEST_CROSS_PROCESS_ID))
- @monitor.save_client_cross_process_id(request)
- @monitor.set_transaction_custom_parameters
- @monitor.insert_response_header(request, @response)
- end
-
- def when_request_has_error(request=for_id(REQUEST_CROSS_PROCESS_ID))
- options = {}
- @monitor.save_client_cross_process_id(request)
- @monitor.set_error_custom_parameters(options)
- @monitor.insert_response_header(request, @response)
-
- options
- end
-
- def test_adds_response_header
- with_default_timings
-
- when_request_runs
-
- assert_equal ["qwerty", "transaction", 1000, 2000, -1], unpacked_response
- end
-
- def test_strips_bad_characters_in_transaction_name
- NewRelic::Agent::BrowserMonitoring.stubs(:timings).returns(stub(
- :transaction_name => "\"'goo",
- :queue_time_in_seconds => 1000,
- :app_time_in_seconds => 2000))
-
- when_request_runs
-
- assert_equal "goo", unpacked_response[TRANSACTION_NAME_POSITION]
- end
-
- def test_doesnt_write_response_header_if_id_blank
- with_default_timings
-
- when_request_runs(for_id(''))
- assert_nil response_app_data
- end
-
- def test_doesnt_write_response_header_if_untrusted_id
- with_default_timings
-
- when_request_runs(for_id("4#1234"))
- assert_nil response_app_data
- end
-
- def test_doesnt_write_response_header_if_improperly_formatted_id
- with_default_timings
-
- when_request_runs(for_id("42"))
- assert_nil response_app_data
- end
-
- def test_doesnt_add_header_if_no_id_in_request
- when_request_runs({})
- assert_nil response_app_data
- end
-
- def test_doesnt_add_header_if_no_id_on_agent
- @monitor.finish_setup(
- :cross_process_id => nil,
- :encoding_key => ENCODING_KEY_NOOP,
- :trusted_account_ids => TRUSTED_ACCOUNT_IDS)
-
- when_request_runs
- assert_nil response_app_data
- end
-
- def test_doesnt_add_header_if_config_disabled
- with_config(:'cross_process.enabled' => false) do
- when_request_runs
- assert_nil response_app_data
- end
- end
-
- def test_includes_content_length
- with_default_timings
-
- when_request_runs(for_id(REQUEST_CROSS_PROCESS_ID).merge("Content-Length" => 3000))
- assert_equal 3000, unpacked_response[CONTENT_LENGTH_POSITION]
- end
-
- def test_finds_content_length_from_headers
- %w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH cOnTeNt-LeNgTh}.each do |key|
- request = { key => 42 }
-
- assert_equal(42, @monitor.content_length_from_request(request), \
- "Failed to find header on key #{key}")
- end
- end
-
- def test_writes_custom_parameters
- with_default_timings
-
- NewRelic::Agent.expects(:add_custom_parameters).once
-
- when_request_runs
- end
-
- def test_error_writes_custom_parameters
- with_default_timings
-
- options = when_request_has_error
-
- assert_equal REQUEST_CROSS_PROCESS_ID, options[:client_cross_process_id]
- end
-
- def test_error_doesnt_write_custom_parameters_if_no_id
- with_default_timings
-
- options = when_request_has_error(for_id(''))
-
- assert_equal false, options.key?(:client_cross_process_id)
- end
-
- def test_writes_metric
- with_default_timings
-
- metric = mock()
- metric.expects(:record_data_point).with(2000)
- NewRelic::Agent.instance.stats_engine.stubs(:get_stats_no_scope).returns(metric)
-
- when_request_runs
- end
-
- def test_doesnt_write_metric_if_id_blank
- with_default_timings
- NewRelic::Agent.instance.stats_engine.expects(:get_stats_no_scope).never
-
- when_request_runs(for_id(''))
- end
-
- def test_decoding_blank
- assert_equal "", @monitor.decode_with_key("")
- end
-
- def test_get_bytes_with_nil
- assert_equal [], @monitor.send(:get_bytes, nil)
- end
-
- def with_default_timings
- NewRelic::Agent::BrowserMonitoring.stubs(:timings).returns(stub(
- :transaction_name => "transaction",
- :queue_time_in_seconds => 1000,
- :app_time_in_seconds => 2000))
- end
-
- def for_id(id)
- encoded_id = id == "" ? "" : Base64.encode64(id)
- { 'X-NewRelic-ID' => encoded_id }
- end
-
- def response_app_data
- @response['X-NewRelic-App-Data']
- end
-
- def unpacked_response
- # Assumes array is valid JSON and Ruby, which is currently is
- eval(Base64.decode64(response_app_data))
- end
- end
-end
diff --git a/test/new_relic/agent/database_test.rb b/test/new_relic/agent/database_test.rb
index af2d414..579b3d1 100644
--- a/test/new_relic/agent/database_test.rb
+++ b/test/new_relic/agent/database_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..',
'test_helper'))
require 'new_relic/agent/database'
@@ -5,13 +9,13 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
def teardown
NewRelic::Agent::Database::Obfuscator.instance.reset
end
-
+
def test_process_resultset
resultset = [["column"]]
assert_equal([nil, [["column"]]],
NewRelic::Agent::Database.process_resultset(resultset))
end
-
+
def test_explain_sql_select_with_mysql_connection
config = {:adapter => 'mysql'}
config.default('val')
@@ -30,7 +34,7 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
result = NewRelic::Agent::Database.explain_sql(sql, config)
assert_equal(plan.keys.sort, result[0].sort)
- assert_equal(plan.values.compact.sort, result[1][0].compact.sort)
+ assert_equal(plan.values.compact.sort, result[1][0].compact.sort)
end
def test_explain_sql_one_select_with_pg_connection
@@ -50,6 +54,44 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
NewRelic::Agent::Database.explain_sql(sql, config))
end
+ def test_dont_collect_explain_for_parameterized_query
+ config = {:adapter => 'postgresql'}
+ config.default('val')
+ connection = mock('connection')
+ connection.expects(:execute).never
+ NewRelic::Agent::Database.stubs(:get_connection).with(config).returns(connection)
+ expects_logging(:debug, 'Unable to collect explain plan for parameterized query.')
+
+ sql = 'SELECT * FROM table WHERE id = $1'
+ assert_equal [], NewRelic::Agent::Database.explain_sql(sql, config)
+ end
+
+ def test_do_collect_explain_for_parameter_looking_literal
+ config = {:adapter => 'postgresql'}
+ config.default('val')
+ connection = mock('connection')
+ plan = [{"QUERY PLAN"=>"Some Jazz"}]
+ connection.stubs(:execute).returns(plan)
+ NewRelic::Agent::Database.stubs(:get_connection).with(config).returns(connection)
+
+ sql = "SELECT * FROM table WHERE id = 'noise $11'"
+ assert_equal([['QUERY PLAN'], [["Some Jazz"]]],
+ NewRelic::Agent::Database.explain_sql(sql, config))
+ end
+
+ def test_dont_collect_explain_for_truncated_query
+ config = {:adapter => 'postgresql'}
+ config.default('val')
+ connection = mock('connection')
+ connection.expects(:execute).never
+ NewRelic::Agent::Database.stubs(:get_connection).with(config).returns(connection)
+ expects_logging(:debug, 'Unable to collect explain plan for truncated query.')
+
+ sql = 'SELECT * FROM table WHERE id IN (1,2,3,4,5...'
+ assert_equal [], NewRelic::Agent::Database.explain_sql(sql, config)
+
+ end
+
def test_explain_sql_no_sql
assert_equal(nil, NewRelic::Agent::Database.explain_sql(nil, nil))
end
@@ -71,8 +113,8 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
config = mock('config')
config.stubs(:[]).returns(nil)
assert_equal([], NewRelic::Agent::Database.explain_sql('SELECT', config))
- end
-
+ end
+
def test_obfuscation_mysql_basic
insert = %q[INSERT INTO `X` values("test",0, 1 , 2, 'test')]
assert_equal("INSERT INTO `X` values(?,?, ? , ?, ?)",
@@ -90,9 +132,9 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
select = NewRelic::Agent::Database::Statement.new(%q[SELECT "table"."column" FROM "table" WHERE "table"."column" = 'value' AND "table"."other_column" = 'other value' LIMIT 1])
select.adapter = :postgresql
assert_equal(%q[SELECT "table"."column" FROM "table" WHERE "table"."column" = ? AND "table"."other_column" = ? LIMIT ?],
- NewRelic::Agent::Database.obfuscate_sql(select))
+ NewRelic::Agent::Database.obfuscate_sql(select))
end
-
+
def test_obfuscation_escaped_literals
insert = %q[INSERT INTO X values('', 'jim''s ssn',0, 1 , 'jim''s son''s son', """jim''s"" hat", "\"jim''s secret\"")]
assert_equal("INSERT INTO X values(?, ?,?, ? , ?, ?, ?)",
@@ -104,7 +146,7 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
assert_equal(%q[SELECT * FROM `table_007` LIMIT ?],
NewRelic::Agent::Database.obfuscate_sql(select))
end
-
+
def test_obfuscation_postgresql_integers_in_identifiers
select = NewRelic::Agent::Database::Statement.new(%q[SELECT * FROM "table_007" LIMIT 1])
select.adapter = :postgresql
@@ -112,25 +154,31 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
NewRelic::Agent::Database.obfuscate_sql(select))
end
+ def test_obfuscation_of_truncated_query
+ insert = "INSERT INTO data (blah) VALUES ('abcdefg..."
+ assert_equal("Query too large (over 16k characters) to safely obfuscate",
+ NewRelic::Agent::Database.obfuscate_sql(insert))
+ end
+
def test_sql_obfuscation_filters
NewRelic::Agent::Database.set_sql_obfuscator(:replace) do |string|
"1" + string
end
-
+
sql = "SELECT * FROM TABLE 123 'jim'"
-
+
assert_equal "1" + sql, NewRelic::Agent::Database.obfuscate_sql(sql)
-
+
NewRelic::Agent::Database.set_sql_obfuscator(:before) do |string|
"2" + string
end
-
+
assert_equal "12" + sql, NewRelic::Agent::Database.obfuscate_sql(sql)
-
+
NewRelic::Agent::Database.set_sql_obfuscator(:after) do |string|
string + "3"
end
-
+
assert_equal "12" + sql + "3", NewRelic::Agent::Database.obfuscate_sql(sql)
NewRelic::Agent::Database::Obfuscator.instance.reset
@@ -140,11 +188,11 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
foo_connection = mock('foo connection')
bar_connection = mock('bar connection')
NewRelic::Agent::Database::ConnectionManager.instance.instance_eval do
- @connections = { :foo => foo_connection, :bar => bar_connection }
+ @connections = { :foo => foo_connection, :bar => bar_connection }
end
foo_connection.expects(:disconnect!)
bar_connection.expects(:disconnect!)
-
+
NewRelic::Agent::Database.close_connections
end
end
diff --git a/test/new_relic/agent/error_collector/notice_error_test.rb b/test/new_relic/agent/error_collector/notice_error_test.rb
index 07b0bd4..623b61a 100644
--- a/test/new_relic/agent/error_collector/notice_error_test.rb
+++ b/test/new_relic/agent/error_collector/notice_error_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
class NewRelic::Agent::ErrorCollector::NoticeErrorTest < Test::Unit::TestCase
require 'new_relic/agent/error_collector'
diff --git a/test/new_relic/agent/error_collector_test.rb b/test/new_relic/agent/error_collector_test.rb
index f5e3a76..7a8095a 100644
--- a/test/new_relic/agent/error_collector_test.rb
+++ b/test/new_relic/agent/error_collector_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# Run faster standalone
ENV['SKIP_RAILS'] = 'true'
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
@@ -162,10 +166,10 @@ class NewRelic::Agent::ErrorCollectorTest < Test::Unit::TestCase
def test_exclude_block
@error_collector.ignore_error_filter &wrapped_filter_proc
-
+
@error_collector.notice_error(IOError.new("message"), :metric => 'path', :request_params => {:x => 'y'})
@error_collector.notice_error(StandardError.new("message"), :metric => 'path', :request_params => {:x => 'y'})
-
+
errors = @error_collector.harvest_errors([])
assert_equal 1, errors.length
@@ -192,6 +196,30 @@ class NewRelic::Agent::ErrorCollectorTest < Test::Unit::TestCase
end
end
+ def test_increment_error_count_record_summary_and_txn_metric
+ txn_info = NewRelic::Agent::TransactionInfo.new
+ txn_info.transaction_name = 'Controller/class/method'
+ NewRelic::Agent::TransactionInfo.stubs(:get).returns(txn_info)
+
+ stats_engine = NewRelic::Agent.instance.stats_engine
+ stats_engine.reset_stats
+ @error_collector.increment_error_count!(StandardError.new('Boo'))
+ assert_equal(1, stats_engine.get_stats('Errors/all').call_count,
+ 'Missing Errors/all metric')
+ assert_equal(1, stats_engine.get_stats('Errors/Controller/class/method').call_count,
+ 'Missing Errors/Controller/class/method metric')
+ end
+
+ def test_doesnt_increment_error_count_on_transaction_if_nameless
+ txn_info = NewRelic::Agent::TransactionInfo.new
+ NewRelic::Agent::TransactionInfo.stubs(:get).returns(txn_info)
+
+ stats_engine = NewRelic::Agent.instance.stats_engine
+ stats_engine.reset_stats
+ @error_collector.increment_error_count!(StandardError.new('Boo'))
+ assert_equal(0, stats_engine.get_stats('Errors/(unknown)').call_count)
+ end
+
private
def expects_error_count_increase(increase)
@@ -203,7 +231,7 @@ class NewRelic::Agent::ErrorCollectorTest < Test::Unit::TestCase
def get_error_stats
NewRelic::Agent.get_stats("Errors/all").call_count
end
-
+
def wrapped_filter_proc
Proc.new do |e|
if e.is_a? IOError
@@ -213,7 +241,7 @@ class NewRelic::Agent::ErrorCollectorTest < Test::Unit::TestCase
end
end
end
-
+
def silence_stream(*args)
super
rescue NoMethodError
diff --git a/test/new_relic/agent/event_listener_test.rb b/test/new_relic/agent/event_listener_test.rb
index f9b5a09..86ef857 100644
--- a/test/new_relic/agent/event_listener_test.rb
+++ b/test/new_relic/agent/event_listener_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
class EventListenerTest < Test::Unit::TestCase
@@ -14,6 +18,24 @@ class EventListenerTest < Test::Unit::TestCase
end
end
+
+ #
+ # Helpers
+ #
+
+ def assert_was_called
+ assert @called, "Event wasn't called"
+ end
+
+ def assert_was_not_called
+ assert !@called, "Event was called"
+ end
+
+
+ #
+ # Tests
+ #
+
def test_notifies
@events.subscribe(:before_call, &@check_method)
@events.notify(:before_call, :env => "env")
@@ -37,9 +59,12 @@ class EventListenerTest < Test::Unit::TestCase
@events.subscribe(:my_event) {}
end
+ def test_clear
+ @events.subscribe(:after_call, &@check_method)
+ @events.clear
+ @events.notify(:after_call)
- def assert_was_called
- assert @called, "Event wasn't called"
+ assert_was_not_called
end
end
diff --git a/test/new_relic/agent/instrumentation/action_view_subscriber_test.rb b/test/new_relic/agent/instrumentation/action_view_subscriber_test.rb
new file mode 100644
index 0000000..393a46e
--- /dev/null
+++ b/test/new_relic/agent/instrumentation/action_view_subscriber_test.rb
@@ -0,0 +1,254 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
+require 'new_relic/agent/instrumentation/rails4/action_view'
+
+class NewRelic::Agent::Instrumentation::ActionViewSubscriberTest < Test::Unit::TestCase
+ def setup
+ @subscriber = NewRelic::Agent::Instrumentation::ActionViewSubscriber.new
+ @stats_engine = NewRelic::Agent.instance.stats_engine
+ end
+
+ def teardown
+ NewRelic::Agent.instance.stats_engine.clear_stats
+ end
+
+ def test_records_metrics_for_simple_template
+ params = { :identifier => '/root/app/views/model/index.html.erb' }
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0, t0 + 2, t0 + 2)
+
+ @subscriber.start('render_template.action_view', :id, params)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.finish('render_template.action_view', :id, params)
+
+ metric = @stats_engine.lookup_stats('View/model/index.html.erb/Rendering')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_metrics_for_simple_file
+ params = { :identifier => '/root/something.txt' }
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0, t0 + 2, t0 + 2)
+
+ @subscriber.start('render_template.action_view', :id, params)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => nil)
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => nil)
+ @subscriber.finish('render_template.action_view', :id, params)
+
+ metric = @stats_engine.lookup_stats('View/file/Rendering')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_metrics_for_simple_inline
+ params = { :identifier => 'inline template' }
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0, t0 + 2, t0 + 2)
+
+ @subscriber.start('render_template.action_view', :id, params)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => nil)
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => nil)
+ @subscriber.finish('render_template.action_view', :id, params)
+
+ metric = @stats_engine.lookup_stats('View/inline template/Rendering')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_metrics_for_simple_text
+ params = { :identifier => 'text template' }
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0 + 2)
+
+ @subscriber.start('render_template.action_view', :id, params)
+ @subscriber.finish('render_template.action_view', :id, params)
+
+ metric = @stats_engine.lookup_stats('View/text template/Rendering')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_metrics_for_simple_partial
+ params = { :identifier => '/root/app/views/model/_form.html.erb' }
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0, t0 + 2, t0 + 2)
+
+ @subscriber.start('render_partial.action_view', :id, params)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/_form')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/_form')
+ @subscriber.finish('render_partial.action_view', :id, params)
+
+ metric = @stats_engine.lookup_stats('View/model/_form.html.erb/Partial')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_metrics_for_simple_collection
+ params = { :identifier => '/root/app/views/model/_user.html.erb' }
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0, t0 + 2, t0 + 2)
+
+ @subscriber.start('render_collection.action_view', :id, params)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/_user')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/_user')
+ @subscriber.finish('render_collection.action_view', :id, params)
+
+ metric = @stats_engine.lookup_stats('View/model/_user.html.erb/Partial')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_metrics_for_layout
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0 + 2)
+
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'layouts/application')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'layouts/application')
+
+ metric = @stats_engine.lookup_stats('View/layouts/application/Rendering')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_scoped_metric
+ params = { :identifier => '/root/app/views/model/index.html.erb' }
+ t0 = Time.now
+ Time.stubs(:now).returns(t0, t0, t0 + 2, t0 + 2)
+ @stats_engine.start_transaction('test_txn')
+
+ @subscriber.start('render_template.action_view', :id, params)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.finish('render_template.action_view', :id, params)
+
+ metric = @stats_engine.lookup_stats('View/model/index.html.erb/Rendering',
+ 'test_txn')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+
+ end
+
+ def test_records_nothing_if_tracing_disabled
+ params = { :identifier => '/root/app/views/model/_user.html.erb' }
+
+ NewRelic::Agent.disable_all_tracing do
+ @subscriber.start('render_collection.action_view', :id, params)
+ @subscriber.finish('render_collection.action_view', :id, params)
+ end
+
+ metric = @stats_engine.lookup_stats('View/model/_user.html.erb/Partial')
+ assert_nil metric
+ end
+
+ def test_creates_txn_segment_for_simple_render
+ params = { :identifier => '/root/app/views/model/index.html.erb' }
+
+ sampler = in_transaction do
+ @subscriber.start('render_template.action_view', :id, params)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.finish('render_template.action_view', :id, params)
+ end
+
+ last_segment = nil
+ sampler.last_sample.root_segment.each_segment{|s| last_segment = s }
+ NewRelic::Agent.shutdown
+
+ assert_equal('View/model/index.html.erb/Rendering',
+ last_segment.metric_name)
+ end
+
+ def test_creates_nested_partial_segment_within_render_segment
+ sampler = in_transaction do
+ @subscriber.start('render_template.action_view', :id,
+ :identifier => 'model/index.html.erb')
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.start('render_partial.action_view', :id,
+ :identifier => '/root/app/views/model/_list.html.erb')
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.finish('render_partial.action_view', :id,
+ :identifier => '/root/app/views/model/_list.html.erb')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/index')
+ @subscriber.finish('render_template.action_view', :id,
+ :identifier => 'model/index.html.erb')
+ end
+
+ template_segment = sampler.last_sample.root_segment.called_segments[0].called_segments[0]
+ partial_segment = template_segment.called_segments[0]
+
+ assert_equal('View/model/index.html.erb/Rendering',
+ template_segment.metric_name)
+ assert_equal('View/model/_list.html.erb/Partial',
+ partial_segment.metric_name)
+ end
+
+ def test_creates_nodes_for_each_in_a_collection_event
+ sampler = in_transaction do
+ @subscriber.start('render_collection.action_view', :id,
+ :identifier => '/root/app/views/model/_list.html.erb',
+ :count => 3)
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.start('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.finish('!render_template.action_view', :id,
+ :virtual_path => 'model/_list')
+ @subscriber.finish('render_collection.action_view', :id,
+ :identifier => '/root/app/views/model/_list.html.erb',
+ :count => 3)
+ end
+
+ template_segment = sampler.last_sample.root_segment.called_segments[0]
+ partial_segments = template_segment.called_segments
+
+ assert_equal 3, partial_segments.size
+ assert_equal('View/model/_list.html.erb/Partial',
+ partial_segments[0].metric_name)
+ end
+
+ def in_transaction
+ NewRelic::Agent.manual_start
+ NewRelic::Agent.instance.stats_engine.start_transaction('test')
+ sampler = NewRelic::Agent.instance.transaction_sampler
+ sampler.notice_first_scope_push(Time.now.to_f)
+ sampler.notice_transaction('/path', '/path', {})
+ sampler.notice_push_scope('Controller/sandwiches/index')
+ yield
+ sampler.notice_pop_scope('Controller/sandwiches/index')
+ sampler.notice_scope_empty
+ sampler
+ ensure
+ NewRelic::Agent.shutdown
+ end
+end if ::Rails::VERSION::MAJOR.to_i >= 4
diff --git a/test/new_relic/agent/instrumentation/active_record_helper_test.rb b/test/new_relic/agent/instrumentation/active_record_helper_test.rb
new file mode 100644
index 0000000..13418fb
--- /dev/null
+++ b/test/new_relic/agent/instrumentation/active_record_helper_test.rb
@@ -0,0 +1,52 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
+require 'new_relic/agent/instrumentation/active_record_helper'
+
+class NewRelic::Agent::Instrumentation::ActiveRecordHelperTest < Test::Unit::TestCase
+ include NewRelic::Agent::Instrumentation
+
+ def test_metric_for_name_find
+ metric_name = 'ActiveRecord/Model/find'
+ assert_equal metric_name, ActiveRecordHelper.metric_for_name('Model Find')
+ assert_equal metric_name, ActiveRecordHelper.metric_for_name('Model Load')
+ assert_equal metric_name, ActiveRecordHelper.metric_for_name('Model Count')
+ assert_equal metric_name, ActiveRecordHelper.metric_for_name('Model Exists')
+ end
+
+ def test_metric_for_name_with_namespace
+ assert_equal('ActiveRecord/Namespace::Model/find',
+ ActiveRecordHelper.metric_for_name('Namespace::Model Load'))
+ end
+
+ def test_metric_for_name_destroy
+ assert_equal('ActiveRecord/Model/destroy',
+ ActiveRecordHelper.metric_for_name('Model Destroy'))
+ end
+
+ def test_metric_for_name_create
+ assert_equal('ActiveRecord/Model/create',
+ ActiveRecordHelper.metric_for_name('Model Create'))
+ end
+
+ def test_metric_for_name_update
+ assert_equal('ActiveRecord/Model/save',
+ ActiveRecordHelper.metric_for_name('Model Update'))
+ end
+
+ def test_metric_for_name_columns
+ assert_nil ActiveRecordHelper.metric_for_name('Model Columns')
+ end
+
+ def test_rollup_metric_for_lists_rollups
+ rollup_metrics = ActiveRecordHelper.rollup_metrics_for('ActiveRecord/Model/find')
+ assert rollup_metrics.include?('ActiveRecord/find')
+ assert rollup_metrics.include?('ActiveRecord/all')
+ end
+
+ def test_remote_service_metric
+ assert_equal('RemoteService/sql/mysql/server',
+ ActiveRecordHelper.remote_service_metric('mysql', 'server'))
+ end
+end
diff --git a/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb b/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb
index c339320..f15fdcd 100644
--- a/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb
+++ b/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::Unit::TestCase
@@ -27,6 +31,8 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
def teardown
super
+ NewRelic::Agent::TransactionInfo.reset
+ Thread::current[:newrelic_scope_name] = nil
NewRelic::Agent.shutdown
end
@@ -70,28 +76,6 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
end
end
- # multiple duplicate find calls should only cause metric trigger on the first
- # call. the others are ignored.
- def test_query_cache
- # Not sure why we get a transaction error with sqlite
- return if isSqlite?
-
- find_metric = "ActiveRecord/ActiveRecordFixtures::Order/find"
- ActiveRecordFixtures::Order.cache do
- m = ActiveRecordFixtures::Order.create :id => 1, :name => 'jeff'
- assert_calls_metrics(find_metric) do
- all_finder(ActiveRecordFixtures::Order)
- end
-
- check_metric_count(find_metric, 1)
-
- assert_calls_metrics(find_metric) do
- 10.times { ActiveRecordFixtures::Order.find m.id }
- end
- check_metric_count(find_metric, 2)
- end
- end
-
def test_metric_names_jruby
# fails due to a bug in rails 3 - log does not provide the correct
# transaction type - it returns 'SQL' instead of 'Foo Create', for example.
@@ -348,7 +332,6 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
return if isPostgres?
expected_metrics = %W[ActiveRecord/all Database/SQL/show RemoteService/sql/#{adapter}/localhost]
-
assert_calls_metrics(*expected_metrics) do
ActiveRecordFixtures::Order.connection.execute "show tables"
end
@@ -368,7 +351,7 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
metrics = NewRelic::Agent.instance.stats_engine.metrics
compare_metrics [], metrics
end
-
+
def test_run_explains
perform_action_with_newrelic_trace :name => 'bogosity' do
ActiveRecordFixtures::Order.add_delay
@@ -376,12 +359,11 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
end
# that's a mouthful. perhaps we should ponder our API.
- segment = NewRelic::Agent.instance.transaction_sampler.last_sample \
- .root_segment.called_segments[0].called_segments[0].called_segments[0]
+ segment = last_segment(NewRelic::Agent.instance.transaction_sampler.last_sample)
regex = /^SELECT (["`]?#{ActiveRecordFixtures::Order.table_name}["`]?.)?\* FROM ["`]?#{ActiveRecordFixtures::Order.table_name}["`]?$/
assert_match regex, segment.params[:sql].strip
end
-
+
def test_prepare_to_send
perform_action_with_newrelic_trace :name => 'bogosity' do
ActiveRecordFixtures::Order.add_delay
@@ -393,14 +375,12 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
includes_gc = false
sample.each_segment {|s| includes_gc ||= s.metric_name =~ /GC/ }
- assert_equal (includes_gc ? 4 : 3), sample.count_segments, sample.to_s
-
- sql_segment = sample.root_segment.called_segments.first.called_segments.first.called_segments.first
+ sql_segment = last_segment(sample)
assert_not_nil sql_segment, sample.to_s
assert_match /^SELECT /, sql_segment.params[:sql]
assert sql_segment.duration > 0.0, "Segment duration must be greater than zero."
sample = sample.prepare_to_send(:record_sql => :raw, :explain_sql => 0.0)
- sql_segment = sample.root_segment.called_segments.first.called_segments.first.called_segments.first
+ sql_segment = last_segment(sample)
assert_match /^SELECT /, sql_segment.params[:sql]
explanations = sql_segment.params[:explain_plan]
if isMysql? || isPostgres?
@@ -412,6 +392,7 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
def test_transaction_mysql
return unless isMysql? && !defined?(JRuby)
+
ActiveRecordFixtures.setup
sample = NewRelic::Agent.instance.transaction_sampler.reset!
perform_action_with_newrelic_trace :name => 'bogosity' do
@@ -420,9 +401,8 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
end
sample = NewRelic::Agent.instance.transaction_sampler.last_sample
-
sample = sample.prepare_to_send(:record_sql => :obfuscated, :explain_sql => 0.0)
- segment = sample.root_segment.called_segments.first.called_segments.first.called_segments.first
+ segment = last_segment(sample)
explanation = segment.params[:explain_plan]
assert_not_nil explanation, "No explains in segment: #{segment}"
assert_equal 2, explanation.size,"No explains in segment: #{segment}"
@@ -452,7 +432,7 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
sample = NewRelic::Agent.instance.transaction_sampler.last_sample
sample = sample.prepare_to_send(:record_sql => :obfuscated, :explain_sql => 0.0)
- segment = sample.root_segment.called_segments.first.called_segments.first.called_segments.first
+ segment = last_segment(sample)
explanations = segment.params[:explain_plan]
assert_not_nil explanations, "No explains in segment: #{segment}"
@@ -477,7 +457,7 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
sample = NewRelic::Agent.instance.transaction_sampler.last_sample
sample = sample.prepare_to_send(:record_sql => :obfuscated, :explain_sql => 0.0)
- segment = sample.root_segment.called_segments.first.called_segments.first.called_segments.first
+ segment = last_segment(sample)
s = NewRelic::Agent.get_stats("ActiveRecord/ActiveRecordFixtures::Order/find")
assert_equal 1, s.call_count
@@ -492,7 +472,7 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
scope :jeffs, :conditions => { :name => 'Jeff' }
else
named_scope :jeffs, :conditions => { :name => 'Jeff' }
- end
+ end
end
def test_named_scope
ActiveRecordFixtures::Order.create :name => 'Jeff'
@@ -548,31 +528,24 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
assert_equal 'preserve-me!', e.message
end
end
-
+
def test_remote_service_metric_respects_dynamic_connection_config
return unless isMysql?
-# puts NewRelic::Agent::Database.config.inspect
-
ActiveRecordFixtures::Shipment.connection.execute('SHOW TABLES');
assert(NewRelic::Agent.get_stats("RemoteService/sql/#{adapter}/localhost").call_count != 0)
- config = ActiveRecordFixtures::Shipment.connection.instance_eval { @config }
+ config = ActiveRecordFixtures::Shipment.connection.instance_eval { @config }
config[:host] = '127.0.0.1'
connection = ActiveRecordFixtures::Shipment.establish_connection(config)
-
-# puts ActiveRecord::Base.connection.instance_eval { @config }.inspect
-# puts NewRelic::Agent::Database.config.inspect
-
+
ActiveRecordFixtures::Shipment.connection.execute('SHOW TABLES');
assert(NewRelic::Agent.get_stats("RemoteService/sql/#{adapter}/127.0.0.1").call_count != 0)
config[:host] = 'localhost'
ActiveRecordFixtures::Shipment.establish_connection(config)
-
-# raise NewRelic::Agent.instance.stats_engine.inspect
end
-
+
private
def rails3?
@@ -595,12 +568,24 @@ class NewRelic::Agent::Instrumentation::ActiveRecordInstrumentationTest < Test::
end
def all_finder(relation)
- if NewRelic::Control.instance.rails_version >= NewRelic::VersionNumber.new("4.0")
- relation.all.load
- elsif NewRelic::Control.instance.rails_version >= NewRelic::VersionNumber.new("3.0")
- relation.all
+ if defined?(::ActiveRecord::VERSION)
+ if ::ActiveRecord::VERSION::MAJOR.to_i >= 4
+ relation.all.load
+ elsif ::ActiveRecord::VERSION::MAJOR.to_i >= 3
+ relation.all
+ else
+ relation.find(:all)
+ end
else
relation.find(:all)
end
end
+
+ def last_segment(txn_sample)
+ last = nil
+ txn_sample.root_segment.each_segment do |segment|
+ last = segment
+ end
+ last
+ end
end
diff --git a/test/new_relic/agent/instrumentation/active_record_subscriber_test.rb b/test/new_relic/agent/instrumentation/active_record_subscriber_test.rb
new file mode 100644
index 0000000..f2c5721
--- /dev/null
+++ b/test/new_relic/agent/instrumentation/active_record_subscriber_test.rb
@@ -0,0 +1,132 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
+require 'new_relic/agent/instrumentation/rails4/active_record'
+
+if ::Rails::VERSION::MAJOR.to_i >= 4 && !NewRelic::LanguageSupport.using_engine?('jruby')
+class NewRelic::Agent::Instrumentation::ActiveRecordSubscriberTest < Test::Unit::TestCase
+ class Order; end
+
+ def setup
+ @config = { :adapter => 'mysql', :host => 'server' }
+ @connection = Object.new
+ @connection.instance_variable_set(:@config, @config)
+ Order.stubs(:connection_pool).returns(stub(:connections => [ @connection ]))
+
+ @params = {
+ :name => 'NewRelic::Agent::Instrumentation::ActiveRecordSubscriberTest::Order Load',
+ :sql => 'SELECT * FROM sandwiches',
+ :connection_id => @connection.object_id
+ }
+
+ @subscriber = NewRelic::Agent::Instrumentation::ActiveRecordSubscriber.new
+
+ @stats_engine = NewRelic::Agent.instance.stats_engine
+ @stats_engine.clear_stats
+ end
+
+ def test_records_metrics_for_simple_find
+ t1 = Time.now
+ t0 = t1 - 2
+ @subscriber.call('sql.active_record', t0, t1, :id, @params)
+
+ metric_name = 'ActiveRecord/NewRelic::Agent::Instrumentation::ActiveRecordSubscriberTest::Order/find'
+
+ metric = @stats_engine.lookup_stats(metric_name)
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_records_scoped_metrics
+ t1 = Time.now
+ t0 = t1 - 2
+
+ @stats_engine.start_transaction('test_txn')
+ @subscriber.call('sql.active_record', t0, t1, :id, @params)
+
+ metric_name = 'ActiveRecord/NewRelic::Agent::Instrumentation::ActiveRecordSubscriberTest::Order/find'
+
+ scoped_metric = @stats_engine.lookup_stats(metric_name, 'test_txn')
+ assert_equal(1, scoped_metric.call_count)
+ assert_equal(2.0, scoped_metric.total_call_time)
+ end
+
+ def test_records_nothing_if_tracing_disabled
+ t1 = Time.now
+ t0 = t1 - 2
+
+ NewRelic::Agent.disable_all_tracing do
+ @subscriber.call('sql.active_record', t0, t1, :id, @params)
+ end
+
+ metric = @stats_engine \
+ .lookup_stats('ActiveRecord/NewRelic::Agent::Instrumentation::ActiveRecordSubscriberTest::Order/find')
+ assert_nil metric
+ end
+
+ def test_records_rollup_metrics
+ t1 = Time.now
+ t0 = t1 - 2
+
+ @subscriber.call('sql.active_record', t0, t1, :id, @params)
+
+ ['ActiveRecord/find', 'ActiveRecord/all'].each do |metric_name|
+ metric = @stats_engine.lookup_stats(metric_name)
+ assert_equal(1, metric.call_count,
+ "Incorrect call count for #{metric_name}")
+ assert_equal(2.0, metric.total_call_time,
+ "Incorrect call time for #{metric_name}")
+ end
+ end
+
+ def test_records_remote_service_metric
+ t1 = Time.now
+ t0 = t1 - 2
+
+ @subscriber.call('sql.active_record', t0, t1, :id, @params)
+
+ metric = @stats_engine.lookup_stats('RemoteService/sql/mysql/server')
+ assert_equal(1, metric.call_count)
+ assert_equal(2.0, metric.total_call_time)
+ end
+
+ def test_creates_txn_segment
+ t1 = Time.now
+ t0 = t1 - 2
+
+ NewRelic::Agent.manual_start
+ @stats_engine.start_transaction('test')
+ sampler = NewRelic::Agent.instance.transaction_sampler
+ sampler.notice_first_scope_push(Time.now.to_f)
+ sampler.notice_transaction('/path', '/path', {})
+ sampler.notice_push_scope('Controller/sandwiches/index')
+ @subscriber.call('sql.active_record', t0, t1, :id, @params)
+ sampler.notice_pop_scope('Controller/sandwiches/index')
+ sampler.notice_scope_empty
+
+ last_segment = nil
+ sampler.last_sample.root_segment.each_segment{|s| last_segment = s }
+ NewRelic::Agent.shutdown
+
+ assert_equal('ActiveRecord/NewRelic::Agent::Instrumentation::ActiveRecordSubscriberTest::Order/find',
+ last_segment.metric_name)
+ assert_equal('SELECT * FROM sandwiches',
+ last_segment.params[:sql])
+ end
+
+ def test_creates_slow_sql_node
+ NewRelic::Agent.manual_start
+ sampler = NewRelic::Agent.instance.sql_sampler
+ sampler.notice_first_scope_push nil
+ t1 = Time.now
+ t0 = t1 - 2
+
+ @subscriber.call('sql.active_record', t0, t1, :id, @params)
+
+ assert_equal 'SELECT * FROM sandwiches', sampler.transaction_data.sql_data[0].sql
+ ensure
+ NewRelic::Agent.shutdown
+ end
+end
+end
diff --git a/test/new_relic/agent/instrumentation/browser_monitoring_timings_test.rb b/test/new_relic/agent/instrumentation/browser_monitoring_timings_test.rb
index b996748..399299f 100644
--- a/test/new_relic/agent/instrumentation/browser_monitoring_timings_test.rb
+++ b/test/new_relic/agent/instrumentation/browser_monitoring_timings_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
module NewRelic::Agent::Instrumentation
@@ -45,7 +49,7 @@ module NewRelic::Agent::Instrumentation
t = BrowserMonitoringTimings.new(1000, @transaction)
original = t.app_time_in_seconds
- Time.stubs(:now).returns(3000)
+ Time.stubs(:now).returns(Time.at(3000))
later = t.app_time_in_seconds
assert_equal original, later
diff --git a/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb b/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb
index f021c23..ac9e411 100644
--- a/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb
+++ b/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper'))
class NewRelic::Agent::Instrumentation::ControllerInstrumentationTest < Test::Unit::TestCase
require 'new_relic/agent/instrumentation/controller_instrumentation'
@@ -36,4 +40,72 @@ class NewRelic::Agent::Instrumentation::ControllerInstrumentationTest < Test::Un
.with({:request => 'headers'}, start_time).raises("an error")
assert_equal(start_time, @object.send(:_detect_upstream_wait, start_time))
end
+
+ def test_transaction_name_calls_newrelic_metric_path
+ @object.stubs(:newrelic_metric_path).returns('some/wacky/path')
+ assert_equal('Controller/some/wacky/path', @object.send(:transaction_name))
+ end
+
+ def test_transaction_name_applies_category_and_path
+ assert_equal('Controller/metric/path',
+ @object.send(:transaction_name,
+ :category => :controller,
+ :path => 'metric/path'))
+ assert_equal('OtherTransaction/Background/metric/path',
+ @object.send(:transaction_name,
+ :category => :task, :path => 'metric/path'))
+ assert_equal('Controller/Rack/metric/path',
+ @object.send(:transaction_name,
+ :category => :rack, :path => 'metric/path'))
+ assert_equal('Controller/metric/path',
+ @object.send(:transaction_name,
+ :category => :uri, :path => 'metric/path'))
+ assert_equal('Controller/Sinatra/metric/path',
+ @object.send(:transaction_name,
+ :category => :sinatra,
+ :path => 'metric/path'))
+ assert_equal('Blarg/metric/path',
+ @object.send(:transaction_name,
+ :category => 'Blarg', :path => 'metric/path'))
+
+ end
+
+ def test_transaction_name_uses_class_name_if_path_not_specified
+ assert_equal('Controller/NewRelic::Agent::Instrumentation::ControllerInstrumentationTest::TestObject',
+ @object.send(:transaction_name, :category => :controller))
+ end
+
+ def test_transaction_name_applies_action_name_if_specified_and_not_path
+ assert_equal('Controller/NewRelic::Agent::Instrumentation::ControllerInstrumentationTest::TestObject/action',
+ @object.send(:transaction_name, :category => :controller,
+ :name => 'action'))
+ end
+
+ def test_transaction_name_applies_name_rules
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '[0-9]+',
+ 'replacement' => '*',
+ 'replace_all' => true)
+ NewRelic::Agent.instance.transaction_rules << rule
+ assert_equal('foo/*/bar/*',
+ @object.send(:transaction_name, :category => 'foo',
+ :path => '1/bar/22'))
+ ensure
+ NewRelic::Agent.instance.instance_variable_set(:@transaction_rules,
+ NewRelic::Agent::RulesEngine.new)
+ end
+
+ def test_path_class_and_action
+ result = @object.send(:path_class_and_action, {})
+ assert_equal("NewRelic::Agent::Instrumentation::ControllerInstrumentationTest::TestObject", result)
+ end
+
+ def test_path_class_and_action_with_name
+ result = @object.send(:path_class_and_action, :name => "test")
+ assert_equal("NewRelic::Agent::Instrumentation::ControllerInstrumentationTest::TestObject/test", result)
+ end
+
+ def test_path_class_and_action_with_overridden_class_name
+ result = @object.send(:path_class_and_action, :name => "perform", :class_name => 'Resque')
+ assert_equal("Resque/perform", result)
+ end
end
diff --git a/test/new_relic/agent/instrumentation/instrumentation_test.rb b/test/new_relic/agent/instrumentation/instrumentation_test.rb
index 033dbea..a929c65 100644
--- a/test/new_relic/agent/instrumentation/instrumentation_test.rb
+++ b/test/new_relic/agent/instrumentation/instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper'))
class NewRelic::Agent::Instrumentation::InstrumentationTest < Test::Unit::TestCase
def test_load_all_instrumentation_files
diff --git a/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb b/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb
index 830ca6d..d4b25ea 100644
--- a/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb
+++ b/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', '..', 'test_helper'))
require 'new_relic/agent/instrumentation/metric_frame/pop'
class NewRelic::Agent::Instrumentation::MetricFrame::PopTest < Test::Unit::TestCase
diff --git a/test/new_relic/agent/instrumentation/metric_frame_test.rb b/test/new_relic/agent/instrumentation/metric_frame_test.rb
index 67e093a..15d8048 100644
--- a/test/new_relic/agent/instrumentation/metric_frame_test.rb
+++ b/test/new_relic/agent/instrumentation/metric_frame_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
class NewRelic::Agent::Instrumentation::MetricFrameTest < Test::Unit::TestCase
@@ -53,4 +57,99 @@ class NewRelic::Agent::Instrumentation::MetricFrameTest < Test::Unit::TestCase
f.start = 1500
assert_equal 500, f.queue_time
end
+
+ def test_update_apdex_records_failed_when_specified
+ stats = NewRelic::Agent::Stats.new
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 0.1, true)
+ assert_equal 0, stats.apdex_s
+ assert_equal 0, stats.apdex_t
+ assert_equal 1, stats.apdex_f
+ end
+
+ def test_update_apdex_records_satisfying
+ stats = NewRelic::Agent::Stats.new
+ with_config(:apdex_t => 1) do
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 0.5, false)
+ end
+ assert_equal 1, stats.apdex_s
+ assert_equal 0, stats.apdex_t
+ assert_equal 0, stats.apdex_f
+ end
+
+ def test_update_apdex_records_tolerating
+ stats = NewRelic::Agent::Stats.new
+ with_config(:apdex_t => 1) do
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 1.5, false)
+ end
+ assert_equal 0, stats.apdex_s
+ assert_equal 1, stats.apdex_t
+ assert_equal 0, stats.apdex_f
+ end
+
+ def test_update_apdex_records_failing
+ stats = NewRelic::Agent::Stats.new
+ with_config(:apdex_t => 1) do
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 4.5, false)
+ end
+ assert_equal 0, stats.apdex_s
+ assert_equal 0, stats.apdex_t
+ assert_equal 1, stats.apdex_f
+ end
+
+ def test_update_apdex_records_correct_apdex_for_key_transaction
+ txn_info = NewRelic::Agent::TransactionInfo.get
+ stats = NewRelic::Agent::Stats.new
+ config = {
+ :web_transactions_apdex => {
+ 'Controller/slow/txn' => 4,
+ 'Controller/fast/txn' => 0.1,
+ },
+ :apdex => 1
+ }
+
+ txn_info.transaction_name = 'Controller/slow/txn'
+ with_config(config, :do_not_cast => true) do
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 3.5, false)
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 5.5, false)
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 16.5, false)
+ end
+ assert_equal 1, stats.apdex_s
+ assert_equal 1, stats.apdex_t
+ assert_equal 1, stats.apdex_f
+
+ txn_info.transaction_name = 'Controller/fast/txn'
+ with_config(config, :do_not_cast => true) do
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 0.05, false)
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 0.2, false)
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 0.5, false)
+ end
+ assert_equal 2, stats.apdex_s
+ assert_equal 2, stats.apdex_t
+ assert_equal 2, stats.apdex_f
+
+ txn_info.transaction_name = 'Controller/other/txn'
+ with_config(config, :do_not_cast => true) do
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 0.5, false)
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 2, false)
+ NewRelic::Agent::Instrumentation::MetricFrame.update_apdex(stats, 5, false)
+ end
+ assert_equal 3, stats.apdex_s
+ assert_equal 3, stats.apdex_t
+ assert_equal 3, stats.apdex_f
+ end
+
+ def test_record_apdex_stores_apdex_t_in_min_and_max
+ stats_engine = NewRelic::Agent.instance.stats_engine
+ stats_engine.reset_stats
+ metric = stub(:apdex_metric_path => 'Apdex/Controller/some/txn')
+ NewRelic::Agent.instance.instance_variable_set(:@stats_engine, stats_engine)
+
+ with_config(:apdex_t => 2.5) do
+ NewRelic::Agent::Instrumentation::MetricFrame.record_apdex(metric, 1, 1, false)
+ end
+ assert_equal 2.5, stats_engine.lookup_stats('Apdex').min_call_time
+ assert_equal 2.5, stats_engine.lookup_stats('Apdex').max_call_time
+ assert_equal 2.5, stats_engine.lookup_stats('Apdex/Controller/some/txn').min_call_time
+ assert_equal 2.5, stats_engine.lookup_stats('Apdex/Controller/some/txn').max_call_time
+ end
end
diff --git a/test/new_relic/agent/instrumentation/net_instrumentation_test.rb b/test/new_relic/agent/instrumentation/net_instrumentation_test.rb
old mode 100644
new mode 100755
index 9d2aa45..3ee8d70
--- a/test/new_relic/agent/instrumentation/net_instrumentation_test.rb
+++ b/test/new_relic/agent/instrumentation/net_instrumentation_test.rb
@@ -1,84 +1,484 @@
-unless ENV['FAST_TESTS']
- require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
+#!/usr/bin/env ruby
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
- class NewRelic::Agent::Instrumentation::NetInstrumentationTest < Test::Unit::TestCase
- include NewRelic::Agent::Instrumentation::ControllerInstrumentation
- def setup
- NewRelic::Agent.manual_start
- @engine = NewRelic::Agent.instance.stats_engine
- @engine.clear_stats
+
+require 'net/http'
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
+require 'new_relic/agent/cross_app_tracing'
+
+# Add some stuff to Net::HTTP::HTTPResponse to facilitate building response data
+class Net::HTTPResponse
+ def to_s
+ buf = ''
+ buf << "HTTP/%s %d %s\r\n" % [ self.http_version, self.code, self.message ]
+ self.each_capitalized {|k,v| buf << k << ': ' << v << "\r\n" }
+ buf << "\r\n"
+ buf << @body if @body
+ return buf
+ end
+ def initialize_copy( original )
+ @http_version = @http_version.dup
+ @code = @code.dup
+ @message = @message.dup
+ @body = @body.dup
+ @read = false
+ @header = @header.dup
+ end
+
+ unless instance_methods.map {|name| name.to_sym }.include?( :body= )
+ def body=( newbody )
+ @body = newbody
end
+ end
+end
+
+class NewRelic::Agent::Instrumentation::NetInstrumentationTest < Test::Unit::TestCase
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation,
+ NewRelic::Agent::CrossAppMonitor::EncodingFunctions,
+ NewRelic::Agent::CrossAppTracing
+
+ CANNED_RESPONSE = Net::HTTPOK.new( '1.1', '200', 'OK' )
+ CANNED_RESPONSE.body =
+ '<html><head><title>Canned Response</title></head><body>Canned response.</body></html>'
+ CANNED_RESPONSE['content-type'] = 'text/html; charset=UTF-8'
+ CANNED_RESPONSE['date'] = 'Tue, 29 Jan 2013 21:52:04 GMT'
+ CANNED_RESPONSE['expires'] = '-1'
+ CANNED_RESPONSE['server'] = 'gws'
+ CANNED_RESPONSE.freeze
+
+ TRANSACTION_GUID = 'BEC1BC64675138B9'
+
+
+ def setup
+ NewRelic::Agent.manual_start(
+ :"cross_application_tracer.enabled" => false,
+ :"transaction_tracer.enabled" => true,
+ :cross_process_id => '269975#22824',
+ :encoding_key => 'gringletoes'
+ )
+
+ # $stderr.puts '', '---', ''
+ # NewRelic::Agent.logger = NewRelic::Agent::AgentLogger.new( {:log_level => 'debug'}, '', Logger.new($stderr) )
+
+ @engine = NewRelic::Agent.instance.stats_engine
+ @engine.clear_stats
+
+ @engine.start_transaction( 'test' )
+ NewRelic::Agent::TransactionInfo.get.guid = TRANSACTION_GUID
+
+ @sampler = NewRelic::Agent.instance.transaction_sampler
+ @sampler.notice_first_scope_push( Time.now.to_f )
+ @sampler.notice_transaction( '/path', '/path', {} )
+ @sampler.notice_push_scope "Controller/sandwiches/index"
+ @sampler.notice_sql("SELECT * FROM sandwiches WHERE bread = 'wheat'", nil, 0)
+ @sampler.notice_push_scope "ab"
+ @sampler.notice_sql("SELECT * FROM sandwiches WHERE bread = 'white'", nil, 0)
+ @sampler.notice_push_scope "fetch_external_service"
+
+ @response = CANNED_RESPONSE.clone
+ @socket = fixture_tcp_socket( @response )
+ end
+
+ def teardown
+ NewRelic::Agent.instance.transaction_sampler.reset!
+ Thread::current[:newrelic_scope_stack] = nil
+ NewRelic::Agent.instance.stats_engine.end_transaction
+ end
+
+
+ #
+ # Helpers
+ #
+
+ def fixture_tcp_socket( response )
+
+ # Don't actually talk to Google.
+ socket = stub("socket") do
+ stubs(:closed?).returns(false)
+ stubs(:close)
+
+ # Simulate a bunch of socket-ey stuff since Mocha doesn't really
+ # provide any other way to do it
+ class << self
+ attr_accessor :response, :write_checker
+ end
+
+ def self.check_write
+ self.write_checker = Proc.new
+ end
+
+ def self.write( buf )
+ self.write_checker.call( buf ) if self.write_checker
+ buf.length
+ end
+
+ def self.sysread( size, buf='' )
+ @data ||= response.to_s
+ raise EOFError if @data.empty?
+ buf.replace @data.slice!( 0, size )
+ buf
+ end
+ class << self
+ alias_method :read_nonblock, :sysread
+ end
- def metrics_without_gc
- @engine.metrics - ['GC/cumulative']
end
- private :metrics_without_gc
+ socket.response = response
+ TCPSocket.stubs( :open ).returns( socket )
+
+ return socket
+ end
+
+
+ def make_app_data_payload( *args )
+ return obfuscate_with_key( 'gringletoes', args.to_json ).gsub( /\n/, '' ) + "\n"
+ end
+
+
+ def find_last_segment
+ builder = NewRelic::Agent.agent.transaction_sampler.builder
+ last_segment = nil
+ builder.current_segment.each_segment {|s| last_segment = s }
+
+ return last_segment
+ end
- def test_get
+
+
+ #
+ # Tests
+ #
+
+ def test_get
+ url = URI.parse('http://www.google.com/index.html')
+ res = Net::HTTP.start(url.host, url.port) {|http|
+ http.get('/index.html')
+ }
+
+ assert_match %r/<head>/i, res.body
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+ def test_background
+ res = nil
+
+ perform_action_with_newrelic_trace("task", :category => :task) do
url = URI.parse('http://www.google.com/index.html')
res = Net::HTTP.start(url.host, url.port) {|http|
http.get('/index.html')
}
- assert_match /<head>/i, res.body
- assert_equal %w[External/all External/www.google.com/Net::HTTP/GET External/allOther External/www.google.com/all].sort,
- metrics_without_gc.sort
- end
-
- def test_background
- perform_action_with_newrelic_trace("task", :category => :task) do
- url = URI.parse('http://www.google.com/index.html')
- res = Net::HTTP.start(url.host, url.port) {|http|
- http.get('/index.html')
- }
- assert_match /<head>/i, res.body
- end
- assert_equal %w[External/all External/www.google.com/Net::HTTP/GET External/allOther External/www.google.com/all
- External/www.google.com/Net::HTTP/GET:OtherTransaction/Background/NewRelic::Agent::Instrumentation::NetInstrumentationTest/task].sort, metrics_without_gc.select{|m| m =~ /^External/}.sort
end
- def test_transactional
- perform_action_with_newrelic_trace("task") do
- url = URI.parse('http://www.google.com/index.html')
- res = Net::HTTP.start(url.host, url.port) {|http|
- http.get('/index.html')
- }
- assert_match /<head>/i, res.body
- end
- assert_equal %w[External/all External/www.google.com/Net::HTTP/GET External/allWeb External/www.google.com/all
- External/www.google.com/Net::HTTP/GET:Controller/NewRelic::Agent::Instrumentation::NetInstrumentationTest/task].sort, metrics_without_gc.select{|m| m =~ /^External/}.sort
- end
- def test_get__simple
- Net::HTTP.get URI.parse('http://www.google.com/index.html')
- assert_equal metrics_without_gc.sort,
- %w[External/all External/www.google.com/Net::HTTP/GET External/allOther External/www.google.com/all].sort
- end
- def test_ignore
- NewRelic::Agent.disable_all_tracing do
- url = URI.parse('http://www.google.com/index.html')
- res = Net::HTTP.start(url.host, url.port) {|http|
- http.post('/index.html','data')
- }
- end
- assert_equal 0, metrics_without_gc.size
- end
- def test_head
+ assert_match %r/<head>/i, res.body
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+ assert_includes @engine.metrics,
+ 'OtherTransaction/Background/NewRelic::Agent::Instrumentation::NetInstrumentationTest/task'
+
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+ def test_transactional
+ res = nil
+
+ perform_action_with_newrelic_trace("task") do
url = URI.parse('http://www.google.com/index.html')
res = Net::HTTP.start(url.host, url.port) {|http|
- http.head('/index.html')
+ http.get('/index.html')
}
- assert_equal %w[External/all External/www.google.com/Net::HTTP/HEAD External/allOther External/www.google.com/all].sort,
- metrics_without_gc.sort
end
- def test_post
+ assert_match %r/<head>/i, res.body
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_includes @engine.metrics, 'External/allWeb'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+ assert_includes @engine.metrics,
+ 'Controller/NewRelic::Agent::Instrumentation::NetInstrumentationTest/task'
+
+ assert_not_includes @engine.metrics, 'External/allOther'
+ end
+
+ def test_get__simple
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+ def test_ignore
+ NewRelic::Agent.disable_all_tracing do
url = URI.parse('http://www.google.com/index.html')
res = Net::HTTP.start(url.host, url.port) {|http|
http.post('/index.html','data')
}
- assert_equal %w[External/all External/www.google.com/Net::HTTP/POST External/allOther External/www.google.com/all].sort,
- metrics_without_gc.sort
end
+ assert_not_includes @engine.metrics, 'External/all'
+ assert_not_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_not_includes @engine.metrics, 'External/allOther'
+ assert_not_includes @engine.metrics, 'External/www.google.com/all'
+
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+ def test_head
+ url = URI.parse('http://www.google.com/index.html')
+ res = Net::HTTP.start(url.host, url.port) {|http|
+ http.head('/index.html')
+ }
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/HEAD'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+ def test_post
+ url = URI.parse('http://www.google.com/index.html')
+ res = Net::HTTP.start(url.host, url.port) {|http|
+ http.post('/index.html','data')
+ }
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/POST'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+ # When an http call is made, the agent should add a request header named
+ # X-NewRelic-ID with a value equal to the encoded cross_app_id.
+
+ def test_adds_a_request_header_to_outgoing_requests_if_xp_enabled
+ @socket.check_write do |data|
+
+ # assert_match /(?i:x-newrelic-id): VURQV1BZRkZdXUFT/, data
+ # The above assertion won't work in Ruby 2.0.0-p0 because of a bug in the
+ # regexp engine. Until that's fixed we'll check the header name case
+ # sensitively.
+ assert_match /X-Newrelic-Id: VURQV1BZRkZdXUFT/, data
+ end
+
+ with_config(:"cross_application_tracer.enabled" => true) do
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+ end
+
+ def test_adds_a_request_header_to_outgoing_requests_if_old_xp_config_is_present
+ @socket.check_write do |data|
+ # assert_match /(?i:x-newrelic-id): VURQV1BZRkZdXUFT/, data
+ # The above assertion won't work in Ruby 2.0.0-p0 because of a bug in the
+ # regexp engine. Until that's fixed we'll check the header name case
+ # sensitively.
+ assert_match /X-Newrelic-Id: VURQV1BZRkZdXUFT/, data
+ end
+
+ with_config(:cross_application_tracing => true) do
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+ end
+
+ def test_agent_doesnt_add_a_request_header_to_outgoing_requests_if_xp_disabled
+ @socket.check_write do |data|
+ # assert_no_match /(?i:x-newrelic-id): VURQV1BZRkZdXUFT/, data
+ # The above assertion won't work in Ruby 2.0.0-p0 because of a bug in the
+ # regexp engine. Until that's fixed we'll check the header name case
+ # sensitively.
+ assert_no_match /X-Newrelic-Id: VURQV1BZRkZdXUFT/, data
+ end
+
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+
+
+ def test_instrumentation_with_crossapp_enabled_records_normal_metrics_if_no_header_present
+ with_config(:"cross_application_tracer.enabled" => true) do
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+
+ assert_equal 5, @engine.metrics.length
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET:test'
+
+ assert_not_includes @engine.metrics, 'ExternalApp/www.google.com/18#1884/all'
+ assert_not_includes @engine.metrics, 'ExternalTransaction/www.google.com/18#1884/txn-name'
+ assert_not_includes @engine.metrics, 'External/allWeb'
end
+
+
+ def test_instrumentation_with_crossapp_disabled_records_normal_metrics_even_if_header_is_present
+ @response[ NR_APPDATA_HEADER ] =
+ make_app_data_payload( '18#1884', 'txn-name', 2, 8, 0, TRANSACTION_GUID )
+
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+
+ assert_equal 5, @engine.metrics.length
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET:test'
+
+ assert_not_includes @engine.metrics, 'ExternalApp/www.google.com/18#1884/all'
+ assert_not_includes @engine.metrics, 'ExternalTransaction/www.google.com/18#1884/txn-name'
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+
+ def test_instrumentation_with_crossapp_enabled_records_crossapp_metrics_if_header_present
+ @response[ NR_APPDATA_HEADER ] =
+ make_app_data_payload( '18#1884', 'txn-name', 2, 8, 0, TRANSACTION_GUID )
+
+ with_config(:"cross_application_tracer.enabled" => true) do
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+
+ assert_equal 6, @engine.metrics.length
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'ExternalApp/www.google.com/18#1884/all'
+ assert_includes @engine.metrics, 'ExternalTransaction/www.google.com/18#1884/txn-name'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+ assert_includes @engine.metrics, 'ExternalTransaction/www.google.com/18#1884/txn-name:test'
+
+ assert_not_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_not_includes @engine.metrics, 'External/allWeb'
+
+ last_segment = find_last_segment()
+
+ assert_includes last_segment.params.keys, :transaction_guid
+ assert_equal TRANSACTION_GUID, last_segment.params[:transaction_guid]
+ end
+
+ def test_crossapp_metrics_allow_valid_utf8_characters
+ @response[ NR_APPDATA_HEADER ] =
+ make_app_data_payload( '12#1114', '世界線航跡蔵', 18.0, 88.1, 4096, TRANSACTION_GUID )
+
+ with_config(:"cross_application_tracer.enabled" => true) do
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+
+ assert_equal 6, @engine.metrics.length
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'ExternalApp/www.google.com/12#1114/all'
+ assert_includes @engine.metrics, 'ExternalTransaction/www.google.com/12#1114/世界線航跡蔵'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+ assert_includes @engine.metrics, 'ExternalTransaction/www.google.com/12#1114/世界線航跡蔵:test'
+
+ assert_not_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_not_includes @engine.metrics, 'External/allWeb'
+
+ last_segment = find_last_segment()
+
+ assert_includes last_segment.params.keys, :transaction_guid
+ assert_equal TRANSACTION_GUID, last_segment.params[:transaction_guid]
+ end
+
+ def test_crossapp_metrics_ignores_crossapp_header_with_malformed_crossprocess_id
+ @response[ NR_APPDATA_HEADER ] =
+ make_app_data_payload( '88#88#88', 'invalid', 1, 2, 4096, TRANSACTION_GUID )
+
+ with_config(:"cross_application_tracer.enabled" => true) do
+ Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+
+ assert_equal 5, @engine.metrics.length
+
+ assert_includes @engine.metrics, 'External/all'
+ assert_includes @engine.metrics, 'External/allOther'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET'
+ assert_includes @engine.metrics, 'External/www.google.com/all'
+ assert_includes @engine.metrics, 'External/www.google.com/Net::HTTP/GET:test'
+
+ assert_not_includes @engine.metrics, 'ExternalApp/www.google.com/88#88#88/all'
+ assert_not_includes @engine.metrics, 'ExternalTransaction/www.google.com/88#88#88/invalid'
+ assert_not_includes @engine.metrics, 'External/allWeb'
+ end
+
+ def test_doesnt_affect_the_request_if_an_exception_is_raised_while_setting_up_tracing
+ res = nil
+ NewRelic::Agent.instance.stats_engine.stubs( :push_scope ).
+ raises( NoMethodError, "undefined method `push_scope'" )
+
+ with_config(:"cross_application_tracer.enabled" => true) do
+ assert_nothing_raised do
+ res = Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+ end
+
+ assert_equal res, CANNED_RESPONSE.instance_variable_get( :@body )
+ end
+
+ def test_doesnt_affect_the_request_if_an_exception_is_raised_while_finishing_tracing
+ res = nil
+ NewRelic::Agent.instance.stats_engine.stubs( :pop_scope ).
+ raises( NoMethodError, "undefined method `pop_scope'" )
+
+ with_config(:"cross_application_tracer.enabled" => true) do
+ assert_nothing_raised do
+ res = Net::HTTP.get URI.parse('http://www.google.com/index.html')
+ end
+ end
+
+ assert_equal res, CANNED_RESPONSE.instance_variable_get( :@body )
+ end
+
+ def test_scope_stack_integrity_maintained_on_request_failure
+ @socket.stubs(:write).raises('fake network error')
+ with_config(:"cross_application_tracer.enabled" => true) do
+ assert_nothing_raised do
+ expected = @engine.push_scope('dummy')
+ Net::HTTP.get(URI.parse('http://www.google.com/index.html')) rescue nil
+ @engine.pop_scope(expected, 42)
+ end
+ end
+ end
+
+ def test_doesnt_misbehave_when_transaction_tracing_is_disabled
+ @engine.transaction_sampler = nil
+
+ # The error should have any other consequence other than logging the error, so
+ # this will capture logs
+ logger = NewRelic::Agent::MemoryLogger.new
+ NewRelic::Agent.logger = logger
+
+ with_config(:"cross_application_tracer.enabled" => true) do
+ Net::HTTP.get(URI.parse('http://www.google.com/index.html'))
+ end
+
+ assert_no_match( /undefined method `rename_scope_segment' for nil:NilClass/i,
+ logger.messages.flatten.map {|log| log.to_s }.join(' ') )
+
+ ensure
+ @engine.transaction_sampler = NewRelic::Agent.agent.transaction_sampler
+ end
+
end
diff --git a/test/new_relic/agent/instrumentation/queue_time_test.rb b/test/new_relic/agent/instrumentation/queue_time_test.rb
index 061dc89..a5c1cea 100644
--- a/test/new_relic/agent/instrumentation/queue_time_test.rb
+++ b/test/new_relic/agent/instrumentation/queue_time_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper'))
class NewRelic::Agent::Instrumentation::QueueTimeTest < Test::Unit::TestCase
include NewRelic::Agent::Instrumentation
diff --git a/test/new_relic/agent/instrumentation/rack_test.rb b/test/new_relic/agent/instrumentation/rack_test.rb
index a3fcc6d..a119eb0 100644
--- a/test/new_relic/agent/instrumentation/rack_test.rb
+++ b/test/new_relic/agent/instrumentation/rack_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper'))
require 'new_relic/agent/instrumentation/rack'
diff --git a/test/new_relic/agent/instrumentation/sinatra_test.rb b/test/new_relic/agent/instrumentation/sinatra_test.rb
index a23c7ba..e81d1c6 100644
--- a/test/new_relic/agent/instrumentation/sinatra_test.rb
+++ b/test/new_relic/agent/instrumentation/sinatra_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper'))
require 'new_relic/agent/instrumentation/sinatra'
diff --git a/test/new_relic/agent/instrumentation/task_instrumentation_test.rb b/test/new_relic/agent/instrumentation/task_instrumentation_test.rb
index 3d181e9..2150b3d 100644
--- a/test/new_relic/agent/instrumentation/task_instrumentation_test.rb
+++ b/test/new_relic/agent/instrumentation/task_instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'test_helper'))
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'test_contexts'))
@@ -16,7 +20,7 @@ class NewRelic::Agent::Instrumentation::TaskInstrumentationTest < Test::Unit::Te
expected_but_missing = stat_names - @agent.stats_engine.metrics
assert_equal 0, expected_but_missing.size, @agent.stats_engine.metrics.map { |n|
stat = @agent.stats_engine.get_stats_no_scope(n)
- "#{'%-26s' % n}: #{stat.call_count} calls @ #{stat.average_call_time} sec/call"
+ "#{'%-26s' % n}: #{stat.call_count} calls"
}.join("\n ") + "\nmissing: #{expected_but_missing.inspect}"
assert_equal 0, @agent.stats_engine.get_stats_no_scope('Controller').call_count
assert_equal 1, @agent.stats_engine.get_stats_no_scope('Controller/NewRelic::Agent::Instrumentation::TaskInstrumentationTest/inner_task_0').call_count
@@ -44,7 +48,6 @@ class NewRelic::Agent::Instrumentation::TaskInstrumentationTest < Test::Unit::Te
run_task_outer(3)
@agent.stats_engine.metrics.sort.each do |n|
stat = @agent.stats_engine.get_stats_no_scope(n)
- # puts "#{'%-26s' % n}: #{stat.call_count} calls @ #{stat.average_call_time} sec/call"
end
assert_equal 1, @agent.stats_engine.get_stats_no_scope('Controller/NewRelic::Agent::Instrumentation::TaskInstrumentationTest/outer_task').call_count
assert_equal 2, @agent.stats_engine.get_stats_no_scope('Controller/NewRelic::Agent::Instrumentation::TaskInstrumentationTest/inner_task_0').call_count
@@ -70,7 +73,6 @@ class NewRelic::Agent::Instrumentation::TaskInstrumentationTest < Test::Unit::Te
assert_equal 0, @agent.transaction_sampler.scope_depth, "existing unfinished sample"
@agent.stats_engine.metrics.sort.each do |n|
stat = @agent.stats_engine.get_stats_no_scope(n)
- # puts "#{'%-26s' % n}: #{stat.call_count} calls @ #{stat.average_call_time} sec/call"
end
assert_equal 1, @agent.stats_engine.get_stats_no_scope('Controller/NewRelic::Agent::Instrumentation::TaskInstrumentationTest/outer_task').call_count
assert_equal 0, @agent.stats_engine.get_stats_no_scope('Controller').call_count
@@ -101,7 +103,6 @@ class NewRelic::Agent::Instrumentation::TaskInstrumentationTest < Test::Unit::Te
end
@agent.stats_engine.metrics.sort.each do |n|
stat = @agent.stats_engine.get_stats_no_scope(n)
- #puts "#{'%-26s' % n}: #{stat.call_count} calls @ #{stat.average_call_time} sec/call"
end
assert_equal @agent, NewRelic::Agent.instance
assert_equal 1, @agent.stats_engine.get_stats_no_scope('Controller/NewRelic::Agent::Instrumentation::TaskInstrumentationTest/hello').call_count
@@ -146,7 +147,7 @@ class NewRelic::Agent::Instrumentation::TaskInstrumentationTest < Test::Unit::Te
expected_but_missing = stat_names - @agent.stats_engine.metrics
assert_equal 0, expected_but_missing.size, @agent.stats_engine.metrics.map { |n|
stat = @agent.stats_engine.get_stats_no_scope(n)
- "#{'%-26s' % n}: #{stat.call_count} calls @ #{stat.average_call_time} sec/call"
+ "#{'%-26s' % n}: #{stat.call_count} calls"
}.join("\n ") + "\nmissing: #{expected_but_missing.inspect}"
assert_equal 1, @agent.stats_engine.get_stats_no_scope('OtherTransaction/all').call_count
assert_equal 1, @agent.stats_engine.get_stats_no_scope('OtherTransaction/Background/all').call_count
diff --git a/test/new_relic/agent/memcache_instrumentation_test.rb b/test/new_relic/agent/memcache_instrumentation_test.rb
index e05b833..7880721 100644
--- a/test/new_relic/agent/memcache_instrumentation_test.rb
+++ b/test/new_relic/agent/memcache_instrumentation_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
memcached_ready = false
diff --git a/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb b/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb
index d464fae..9962516 100644
--- a/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb
+++ b/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','..','test_helper'))
require 'set'
diff --git a/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb b/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb
index ede751f..647e0a4 100644
--- a/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb
+++ b/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','..','test_helper'))
class NewRelic::Agent::MethodTracer::InstanceMethods::TraceExecutionScopedTest < Test::Unit::TestCase
require 'new_relic/agent/method_tracer'
@@ -47,29 +51,36 @@ class NewRelic::Agent::MethodTracer::InstanceMethods::TraceExecutionScopedTest <
assert_equal NewRelic::Agent.instance, agent_instance
end
- def test_main_stat
- self.expects(:get_stats_scoped).with('hello', true)
- opts = {:scoped_metric_only => true}
- main_stat('hello', opts)
- end
-
- def test_get_metric_stats_metric
- metrics = ['foo', 'bar', 'baz']
+ def test_get_metric_specs_with_metric_option
+ first_name = 'foo'
+ other_names = ['bar', 'baz']
opts = {:metric => true}
- self.expects(:get_stats_unscoped).twice
- self.expects(:main_stat).with('foo', opts)
- first_name, stats = get_metric_stats(metrics, opts)
- assert_equal 'foo', first_name
- assert_equal 3, stats.length
+
+ specs = get_metric_specs(first_name, other_names, 'scope', opts)
+
+ expected_specs = [
+ NewRelic::MetricSpec.new('foo', 'scope'),
+ NewRelic::MetricSpec.new('foo'),
+ NewRelic::MetricSpec.new('bar'),
+ NewRelic::MetricSpec.new('baz')
+ ]
+
+ assert_equal(expected_specs.sort, specs.sort)
end
- def test_get_metric_stats_no_metric
- metrics = ['foo', 'bar', 'baz']
+ def test_get_metric_stats_without_metric_option
+ first_name = 'foo'
+ other_names = ['bar', 'baz']
opts = {:metric => false}
- self.expects(:get_stats_unscoped).twice
- first_name, stats = get_metric_stats(metrics, opts)
- assert_equal 'foo', first_name
- assert_equal 2, stats.length
+
+ specs = get_metric_specs(first_name, other_names, 'scope', opts)
+
+ expected_specs = [
+ NewRelic::MetricSpec.new('bar'),
+ NewRelic::MetricSpec.new('baz')
+ ]
+
+ assert_equal(expected_specs.sort, specs.sort)
end
def test_set_if_nil
@@ -145,17 +156,20 @@ class NewRelic::Agent::MethodTracer::InstanceMethods::TraceExecutionScopedTest <
t0 = 1.0
t1 = 2.0
metric = 'foo'
- metric_stats = [mock('fakestat')]
- metric_stats.first.expects(:trace_call).with(1.0, 0.5)
+ metric_specs = [NewRelic::MetricSpec.new('foo'), NewRelic::MetricSpec.new('bar')]
+ stats = [NewRelic::Agent::Stats.new, NewRelic::Agent::Stats.new]
expected_scope = 'an expected scope'
engine = mocked_object('stat_engine')
scope = mock('scope')
engine.expects(:pop_scope).with('an expected scope', 1.0, 2.0).returns(scope)
+ engine.expects(:record_metrics).with(metric_specs).multiple_yields(*stats)
+ stats[0].expects(:record_data_point).with(1.0, 0.5)
+ stats[1].expects(:record_data_point).with(1.0, 0.5)
scope.expects(:children_time).returns(0.5)
self.expects(:pop_flag!).with(false)
self.expects(:log_errors).with('trace_method_execution footer', 'foo').yields
- trace_execution_scoped_footer(t0, metric, metric_stats, expected_scope, false, t1)
+ trace_execution_scoped_footer(t0, metric, metric_specs, expected_scope, false, t1)
end
def test_trace_execution_scoped_disabled
@@ -175,10 +189,14 @@ class NewRelic::Agent::MethodTracer::InstanceMethods::TraceExecutionScopedTest <
def test_trace_execution_scoped_default
passed_in_opts = {}
opts_after_correction = {:metric => true, :deduct_call_time_from_parent => true}
+ specs = [
+ NewRelic::MetricSpec.new('metric'),
+ NewRelic::MetricSpec.new('array')
+ ]
self.expects(:trace_disabled?).returns(false)
- self.expects(:get_metric_stats).with(['metric', 'array'], opts_after_correction).returns(['metric', ['stats']])
+ self.expects(:get_metric_specs).with('metric', ['array'], nil, opts_after_correction).returns(specs)
self.expects(:trace_execution_scoped_header).with('metric', opts_after_correction).returns(['start_time', 'expected_scope'])
- self.expects(:trace_execution_scoped_footer).with('start_time', 'metric', ['stats'], 'expected_scope', nil)
+ self.expects(:trace_execution_scoped_footer).with('start_time', 'metric', specs, 'expected_scope', nil)
ran = false
value = trace_execution_scoped(['metric', 'array'], passed_in_opts) do
ran = true
@@ -192,10 +210,14 @@ class NewRelic::Agent::MethodTracer::InstanceMethods::TraceExecutionScopedTest <
def test_trace_execution_scoped_with_error
passed_in_opts = {}
opts_after_correction = {:metric => true, :deduct_call_time_from_parent => true}
+ specs = [
+ NewRelic::MetricSpec.new('metric'),
+ NewRelic::MetricSpec.new('array')
+ ]
self.expects(:trace_disabled?).returns(false)
- self.expects(:get_metric_stats).with(['metric', 'array'], opts_after_correction).returns(['metric', ['stats']])
+ self.expects(:get_metric_specs).with('metric', ['array'], nil, opts_after_correction).returns(specs)
self.expects(:trace_execution_scoped_header).with('metric', opts_after_correction).returns(['start_time', 'expected_scope'])
- self.expects(:trace_execution_scoped_footer).with('start_time', 'metric', ['stats'], 'expected_scope', nil)
+ self.expects(:trace_execution_scoped_footer).with('start_time', 'metric', specs, 'expected_scope', nil)
ran = false
assert_raises(RuntimeError) do
trace_execution_scoped(['metric', 'array'], passed_in_opts) do
diff --git a/test/new_relic/agent/method_tracer_test.rb b/test/new_relic/agent/method_tracer_test.rb
index c082072..7baf3bb 100644
--- a/test/new_relic/agent/method_tracer_test.rb
+++ b/test/new_relic/agent/method_tracer_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/agent/mock_scope_listener'
@@ -59,6 +63,8 @@ class NewRelic::Agent::MethodTracerTest < Test::Unit::TestCase
attr_reader :stats_engine
def setup
+ Thread::current[:newrelic_scope_stack] = nil
+
NewRelic::Agent.manual_start
@stats_engine = NewRelic::Agent.instance.stats_engine
@stats_engine.clear_stats
@@ -288,7 +294,7 @@ class NewRelic::Agent::MethodTracerTest < Test::Unit::TestCase
self.class.trace_execution_scoped metrics do
sleep 0.05
end
- elapsed = @stats_engine.get_stats('first').average_call_time
+ elapsed = @stats_engine.get_stats('first').total_call_time
metrics.map{|name| @stats_engine.get_stats name}.each do | m |
assert_equal 1, m.call_count
assert_equal elapsed, m.total_call_time
@@ -299,7 +305,7 @@ class NewRelic::Agent::MethodTracerTest < Test::Unit::TestCase
self.class.trace_execution_unscoped metrics do
sleep 0.05
end
- elapsed = @stats_engine.get_stats('first').average_call_time
+ elapsed = @stats_engine.get_stats('first').total_call_time
metrics.map{|name| @stats_engine.get_stats name}.each do | m |
assert_equal 1, m.call_count
assert_equal elapsed, m.total_call_time
diff --git a/test/new_relic/agent/mock_scope_listener.rb b/test/new_relic/agent/mock_scope_listener.rb
index cbd7031..94e87ff 100644
--- a/test/new_relic/agent/mock_scope_listener.rb
+++ b/test/new_relic/agent/mock_scope_listener.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class NewRelic::Agent::MockScopeListener
diff --git a/test/new_relic/agent/new_relic_service_test.rb b/test/new_relic/agent/new_relic_service_test.rb
index 8b4cf73..39a088e 100644
--- a/test/new_relic/agent/new_relic_service_test.rb
+++ b/test/new_relic/agent/new_relic_service_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
require 'new_relic/agent/thread_profiler'
@@ -197,9 +201,44 @@ class NewRelicServiceTest < Test::Unit::TestCase
end
def test_metric_data
- @http_handle.respond_to(:metric_data, 'met rick date uhhh')
- response = @service.metric_data((Time.now - 60).to_f, Time.now.to_f, {})
- assert_equal 'met rick date uhhh', response
+ dummy_rsp = 'met rick date uhh'
+ @http_handle.respond_to(:metric_data, dummy_rsp)
+ stats_hash = NewRelic::Agent::StatsHash.new
+ @service.expects(:fill_metric_id_cache).with(dummy_rsp)
+ response = @service.metric_data((Time.now - 60).to_f, Time.now.to_f, stats_hash)
+ assert_equal dummy_rsp, response
+ end
+
+ def test_fill_metric_id_cache_from_collect_response
+ response = [[{"scope"=>"Controller/blogs/index", "name"=>"Database/SQL/other"}, 1328],
+ [{"scope"=>"", "name"=>"WebFrontend/QueueTime"}, 10],
+ [{"scope"=>"", "name"=>"ActiveRecord/Blog/find"}, 1017]]
+
+ @service.send(:fill_metric_id_cache, response)
+
+ cache = @service.metric_id_cache
+ assert_equal 1328, cache[NewRelic::MetricSpec.new('Database/SQL/other', 'Controller/blogs/index')]
+ assert_equal 10, cache[NewRelic::MetricSpec.new('WebFrontend/QueueTime')]
+ assert_equal 1017, cache[NewRelic::MetricSpec.new('ActiveRecord/Blog/find')]
+ end
+
+ def test_caches_metric_ids_for_future_use
+ dummy_rsp = [[{ 'name' => 'a', 'scope' => '' }, 42]]
+ @http_handle.respond_to(:metric_data, dummy_rsp)
+
+ hash = NewRelic::Agent::StatsHash.new
+ hash.record(NewRelic::MetricSpec.new('a'), 1)
+
+ @service.metric_data((Time.now - 60).to_f, Time.now.to_f, hash)
+
+ hash = NewRelic::Agent::StatsHash.new
+ hash.record(NewRelic::MetricSpec.new('a'), 1)
+ stats = hash[NewRelic::MetricSpec.new('a')]
+
+ results = @service.build_metric_data_array(hash)
+ assert_nil(results.first.metric_spec)
+ assert_equal(stats, results.first.stats)
+ assert_equal(42, results.first.metric_id)
end
def test_error_data
@@ -276,7 +315,9 @@ end
@service.connect
@http_handle.respond_to(:metric_data, 0)
- @service.metric_data((Time.now - 60).to_f, Time.now.to_f, {})
+ @service.stubs(:fill_metric_id_cache)
+ stats_hash = NewRelic::Agent::StatsHash.new
+ @service.metric_data((Time.now - 60).to_f, Time.now.to_f, stats_hash)
@http_handle.respond_to(:transaction_sample_data, 1)
@service.transaction_sample_data([])
@@ -298,7 +339,8 @@ end
def test_should_raise_exception_on_413
@http_handle.respond_to(:metric_data, 'too big', :code => 413)
assert_raise NewRelic::Agent::UnrecoverableServerException do
- @service.metric_data((Time.now - 60).to_f, Time.now.to_f, {})
+ stats_hash = NewRelic::Agent::StatsHash.new
+ @service.metric_data((Time.now - 60).to_f, Time.now.to_f, stats_hash)
end
end
@@ -306,7 +348,8 @@ end
def test_should_raise_exception_on_415
@http_handle.respond_to(:metric_data, 'too big', :code => 415)
assert_raise NewRelic::Agent::UnrecoverableServerException do
- @service.metric_data((Time.now - 60).to_f, Time.now.to_f, {})
+ stats_hash = NewRelic::Agent::StatsHash.new
+ @service.metric_data((Time.now - 60).to_f, Time.now.to_f, stats_hash)
end
end
@@ -323,6 +366,18 @@ end
marshaller.load('{"exception": {"message": "error message", "error_type": "JavaCrash"}}')
end
end
+
+ def test_use_pruby_marshaller_if_error_using_json
+ json_marshaller = NewRelic::Agent::NewRelicService::JsonMarshaller.new
+ @service.instance_variable_set(:@marshaller, json_marshaller)
+ JSON.stubs(:dump).raises(RuntimeError.new('blah'))
+ @http_handle.respond_to(:transaction_sample_data, 'ok', :format => :pruby)
+
+ @service.transaction_sample_data([])
+
+ assert_equal('NewRelic::Agent::NewRelicService::PrubyMarshaller',
+ @service.marshaller.class.name)
+ end
end
def test_pruby_marshaller_handles_errors_from_collector
@@ -392,6 +447,57 @@ end
assert_equal 'OogBooga: test', error.message
end
+ def test_build_metric_data_array
+ hash = NewRelic::Agent::StatsHash.new
+
+ spec1 = NewRelic::MetricSpec.new('foo')
+ spec2 = NewRelic::MetricSpec.new('bar')
+ hash.record(spec1, 1)
+ hash.record(spec2, 2)
+
+ metric_data_array = @service.build_metric_data_array(hash)
+
+ assert_equal(2, metric_data_array.size)
+ metric_data_1 = metric_data_array.find { |md| md.metric_spec == spec1 }
+ metric_data_2 = metric_data_array.find { |md| md.metric_spec == spec2 }
+ assert_equal(hash[spec1], metric_data_1.stats)
+ assert_equal(hash[spec2], metric_data_2.stats)
+ end
+
+ def test_build_metric_data_array_uses_metric_id_cache_if_possible
+ hash = NewRelic::Agent::StatsHash.new
+
+ spec1 = NewRelic::MetricSpec.new('foo')
+ spec2 = NewRelic::MetricSpec.new('bar')
+ hash.record(spec1, 1)
+ hash.record(spec2, 1)
+
+ @service.stubs(:metric_id_cache).returns({ spec1 => 42 })
+ metric_data_array = @service.build_metric_data_array(hash)
+
+ assert_equal(2, metric_data_array.size)
+
+ metric_data1 = metric_data_array.find { |md| md.metric_id == 42 }
+ metric_data2 = metric_data_array.find { |md| md.metric_spec == spec2 }
+ assert_nil(metric_data1.metric_spec)
+ assert_nil(metric_data2.metric_id)
+ end
+
+ def test_build_metric_data_array_omits_empty_stats
+ hash = NewRelic::Agent::StatsHash.new
+
+ spec1 = NewRelic::MetricSpec.new('foo')
+ spec2 = NewRelic::MetricSpec.new('bar')
+ hash.record(spec1, 1)
+ hash[spec2] = NewRelic::Agent::Stats.new()
+
+ metric_data_array = @service.build_metric_data_array(hash)
+ assert_equal(1, metric_data_array.size)
+
+ metric_data = metric_data_array.first
+ assert_equal(spec1, metric_data.metric_spec)
+ end
+
class HTTPHandle
attr_accessor :read_timeout, :route_table
diff --git a/test/new_relic/agent/pipe_channel_manager_test.rb b/test/new_relic/agent/pipe_channel_manager_test.rb
index 235b7a0..b55e137 100644
--- a/test/new_relic/agent/pipe_channel_manager_test.rb
+++ b/test/new_relic/agent/pipe_channel_manager_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'timeout'
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
require 'new_relic/agent/pipe_channel_manager'
@@ -39,7 +43,7 @@ class NewRelic::Agent::PipeChannelManagerTest < Test::Unit::TestCase
NewRelic::Agent.after_fork
new_engine = NewRelic::Agent::StatsEngine.new
new_engine.get_stats_no_scope(metric).record_data_point(2.0)
- listener.pipes[666].write(:stats => new_engine.harvest_timeslice_data({}, {}))
+ listener.pipes[666].write(:stats => new_engine.harvest_timeslice_data({}))
end
Process.wait(pid)
listener.stop
@@ -97,17 +101,31 @@ class NewRelic::Agent::PipeChannelManagerTest < Test::Unit::TestCase
assert_equal(2, NewRelic::Agent.agent.error_collector.errors.size)
end
- def test_close_pipe_on_EOF_string
- listener = start_listener_with_pipe(669)
+ def assert_pipe_finished(id)
+ assert(!NewRelic::Agent::PipeChannelManager.channels[id] ||
+ NewRelic::Agent::PipeChannelManager.channels[id].closed?,
+ "Expected pipe with ID #{id} to be nil or closed")
+ end
+ def test_close_pipe_on_child_explicit_close
+ listener = start_listener_with_pipe(669)
pid = Process.fork do
- listener.pipes[669].write('EOF')
+ NewRelic::Agent::PipeService.new(669)
end
Process.wait(pid)
- listener.stop
+ listener.stop_listener_thread
+ assert_pipe_finished(669)
+ end
- assert(!NewRelic::Agent::PipeChannelManager.channels[669] ||
- NewRelic::Agent::PipeChannelManager.channels[669].closed?)
+ def test_close_pipe_on_child_exit
+ listener = start_listener_with_pipe(669)
+ pid = Process.fork do
+ NewRelic::Agent::PipeService.new(669)
+ exit!
+ end
+ Process.wait(pid)
+ listener.stop_listener_thread
+ assert_pipe_finished(669)
end
def test_manager_does_not_crash_when_given_bad_data
diff --git a/test/new_relic/agent/pipe_service_test.rb b/test/new_relic/agent/pipe_service_test.rb
index 0bbdfb9..2a057c7 100644
--- a/test/new_relic/agent/pipe_service_test.rb
+++ b/test/new_relic/agent/pipe_service_test.rb
@@ -1,16 +1,20 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
class PipeServiceTest < Test::Unit::TestCase
def setup
- NewRelic::Agent::PipeChannelManager.listener.stop
+ NewRelic::Agent::PipeChannelManager.listener.stop
NewRelic::Agent::PipeChannelManager.register_report_channel(:pipe_service_test)
@service = NewRelic::Agent::PipeService.new(:pipe_service_test)
end
-
+
def test_constructor
assert_equal :pipe_service_test, @service.channel_id
end
-
+
def test_connect_returns_nil
assert_nil @service.connect({})
end
@@ -26,10 +30,9 @@ class PipeServiceTest < Test::Unit::TestCase
end
def test_write_to_missing_pipe_logs_error
- service = NewRelic::Agent::PipeService.new(:non_existant)
::NewRelic::Agent.logger.expects(:error) \
- .with(regexp_matches(/Unable to send data to parent process/)).once
-
+ .with(regexp_matches(/No communication channel to parent process/)).once
+ service = NewRelic::Agent::PipeService.new(:non_existant)
assert_nothing_raised do
service.metric_data(Time.now, Time.now, {})
end
@@ -75,25 +78,18 @@ class PipeServiceTest < Test::Unit::TestCase
@service.metric_data(0.0, 0.1, metric_data0)
@service.transaction_sample_data(['txn0'])
@service.error_data(['err0'])
- @service.sql_trace_data(['sql0'])
+ @service.sql_trace_data(['sql0'])
@service.shutdown(Time.now)
end
Process.wait(pid)
-
+
received_data = read_from_pipe
-
+
assert_equal 'Custom/something', received_data[:stats].keys.sort[0].name
assert_equal ['txn0'], received_data[:transaction_traces]
assert_equal ['err0'], received_data[:error_traces].sort
end
- def test_shutdown_sends_EOF
- received_data = data_from_forked_process do
- @service.shutdown(Time.now)
- end
- assert_equal 'EOF', received_data[:EOF]
- end
-
def test_shutdown_closes_pipe
data_from_forked_process do
@service.shutdown(Time.now)
@@ -102,11 +98,11 @@ class PipeServiceTest < Test::Unit::TestCase
end
end
end
-
+
def generate_metric_data(metric_name, data=1.0)
engine = NewRelic::Agent::StatsEngine.new
engine.get_stats_no_scope(metric_name).record_data_point(data)
- engine.harvest_timeslice_data({}, {}).values
+ engine.harvest_timeslice_data({})
end
def read_from_pipe
diff --git a/test/new_relic/agent/rpm_agent_test.rb b/test/new_relic/agent/rpm_agent_test.rb
index 89243b5..f98437f 100644
--- a/test/new_relic/agent/rpm_agent_test.rb
+++ b/test/new_relic/agent/rpm_agent_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
ENV['SKIP_RAILS'] = 'true'
require File.expand_path('../../../test_helper', __FILE__)
require File.expand_path('../../../test_contexts', __FILE__)
@@ -83,17 +87,6 @@ class NewRelic::Agent::RpmAgentTest < Test::Unit::TestCase # ActiveSupport::Test
NewRelic::Agent.shutdown
end
- should "send_timeslice_data" do
- # this test fails due to a rubinius bug
- return if NewRelic::LanguageSupport.using_engine?('rbx')
- @agent.service.expects(:metric_data).returns([ [{'name' => '/A/b/c'}, 1],
- [{'name' => '/A/b/c', 'scope' => '/X'}, 2],
- [{'name' => '/A/b/d'}, 3] ])
- @agent.send :harvest_and_send_timeslice_data
- assert_equal 3, @agent.metric_ids.size
- assert_equal(3, @agent.metric_ids[NewRelic::MetricSpec.new('/A/b/d')],
- @agent.metric_ids.inspect)
- end
should "set_record_sql" do
@agent.set_record_sql(false)
assert !NewRelic::Agent.is_sql_recorded?
diff --git a/test/new_relic/agent/rules_engine_test.rb b/test/new_relic/agent/rules_engine_test.rb
new file mode 100644
index 0000000..7d7b720
--- /dev/null
+++ b/test/new_relic/agent/rules_engine_test.rb
@@ -0,0 +1,86 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
+
+class RulesEngineTest < Test::Unit::TestCase
+ def setup
+ @engine = NewRelic::Agent::RulesEngine.new
+ end
+
+ def test_rule_defaults
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '.*',
+ 'replacement' => '*')
+ assert !rule.terminate_chain
+ assert !rule.each_segment
+ assert !rule.ignore
+ assert !rule.replace_all
+ assert_equal 0, rule.eval_order
+ end
+
+ def test_rule_applies_regex_rename
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '[0-9]+',
+ 'replacement' => '*')
+ assert_equal(['foo/*/bar/22', true], rule.apply('foo/1/bar/22'))
+ end
+
+ def test_rule_applies_grouping_with_replacements
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '([0-9]+)',
+ 'replacement' => '\\1\\1')
+ assert_equal(['foo/11/bar/22', true], rule.apply('foo/1/bar/22'))
+ end
+
+ def test_rule_renames_all_matches_when_replace_all_is_true
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '[0-9]+',
+ 'replacement' => '*',
+ 'replace_all' => true)
+ assert_equal(['foo/*/bar/*', true], rule.apply('foo/1/bar/22'))
+ end
+
+ def test_rule_with_no_match
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => 'QQ',
+ 'replacement' => 'qq')
+ assert_equal(['foo/1/bar/22', false], rule.apply('foo/1/bar/22'))
+ end
+
+ def test_applies_rules_in_order
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '[0-9]+',
+ 'replacement' => '*',
+ 'replace_all' => true,
+ 'eval_order' => 0)
+ rerule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '\*',
+ 'replacement' => 'x',
+ 'replace_all' => true,
+ 'eval_order' => 1)
+ @engine << rerule
+ @engine << rule
+
+ assert_equal('foo/x/bar/x', @engine.rename('foo/1/bar/22'))
+ end
+
+ def test_can_apply_rules_to_all_segments
+ rule = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '[0-9]+.*',
+ 'replacement' => '*',
+ 'each_segment' => true)
+ @engine << rule
+
+ assert_equal('foo/*/bar/*', @engine.rename('foo/1a/bar/22b'))
+ end
+
+ def test_stops_after_terminate_chain
+ rule0 = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '[0-9]+',
+ 'replacement' => '*',
+ 'each_segment' => true,
+ 'eval_order' => 0,
+ 'terminate_chain' => true)
+ rule1 = NewRelic::Agent::RulesEngine::Rule.new('match_expression' => '.*',
+ 'replacement' => 'X',
+ 'replace_all' => true,
+ 'eval_order' => 1)
+ @engine << rule0
+ @engine << rule1
+
+ assert_equal('foo/*/bar/*', @engine.rename('foo/1/bar/22'))
+ end
+end
diff --git a/test/new_relic/agent/sampler_test.rb b/test/new_relic/agent/sampler_test.rb
index a56609b..bd7bc2f 100644
--- a/test/new_relic/agent/sampler_test.rb
+++ b/test/new_relic/agent/sampler_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
class NewRelic::Agent::SamplerTest < Test::Unit::TestCase
require 'new_relic/agent/sampler'
diff --git a/test/new_relic/agent/shim_agent_test.rb b/test/new_relic/agent/shim_agent_test.rb
index 030beb0..b621951 100644
--- a/test/new_relic/agent/shim_agent_test.rb
+++ b/test/new_relic/agent/shim_agent_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
module NewRelic
module Agent
@@ -8,10 +12,6 @@ module NewRelic
@agent = NewRelic::Agent::ShimAgent.new
end
- def test_serialize
- assert_equal(nil, @agent.serialize, "should return nil when shut down")
- end
-
def test_merge_data_from
assert_equal(nil, @agent.merge_data_from(mock('metric data')))
end
diff --git a/test/new_relic/agent/sql_sampler_test.rb b/test/new_relic/agent/sql_sampler_test.rb
index 8cebe97..05cd02b 100644
--- a/test/new_relic/agent/sql_sampler_test.rb
+++ b/test/new_relic/agent/sql_sampler_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
class NewRelic::Agent::SqlSamplerTest < Test::Unit::TestCase
@@ -33,6 +37,13 @@ class NewRelic::Agent::SqlSamplerTest < Test::Unit::TestCase
assert_equal 2, @sampler.transaction_data.sql_data.size
end
+ def test_notice_sql_truncates_query
+ @sampler.notice_first_scope_push nil
+ message = 'a' * 17_000
+ @sampler.notice_sql message, "Database/test/select", nil, 1.5
+ assert_equal('a' * 16_381 + '...', @sampler.transaction_data.sql_data[0].sql)
+ end
+
def test_harvest_slow_sql
data = NewRelic::Agent::TransactionSqlData.new
data.set_transaction_info("WebTransaction/Controller/c/a", "/c/a", {},
diff --git a/test/new_relic/agent/stats_engine/gc_profiler_test.rb b/test/new_relic/agent/stats_engine/gc_profiler_test.rb
new file mode 100644
index 0000000..8a9131b
--- /dev/null
+++ b/test/new_relic/agent/stats_engine/gc_profiler_test.rb
@@ -0,0 +1,89 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
+
+class NewRelic::Agent::StatsEngine::GCProfilerTest < Test::Unit::TestCase
+ def test_init_profiler_for_rails_bench
+ ::GC.stubs(:time)
+ ::GC.stubs(:collections)
+
+ assert_equal(NewRelic::Agent::StatsEngine::GCProfiler::RailsBench,
+ NewRelic::Agent::StatsEngine::GCProfiler.init.class)
+ end
+
+ def test_init_profiler_for_ruby_19
+ defined = defined?(::GC::Profiler)
+ if !defined
+ ::GC.const_set(:Profiler, Module.new)
+ end
+ ::GC::Profiler.stubs(:enabled?).returns(true)
+ ::GC::Profiler.stubs(:total_time).returns(0)
+ ::GC.stubs(:count).returns(0)
+
+ assert_equal(NewRelic::Agent::StatsEngine::GCProfiler::Ruby19,
+ NewRelic::Agent::StatsEngine::GCProfiler.init.class)
+ ensure
+ ::GC.send(:remove_const, :Profiler) unless defined
+ end
+
+ def test_init_profiler_for_rbx
+ defined = defined?(::Rubinius::GC)
+ if !defined
+ Object.const_set(:Rubinius, Module.new)
+ ::Rubinius.const_set(:GC, Module.new)
+ end
+ ::Rubinius::GC.stubs(:count).returns(0)
+ ::Rubinius::GC.stubs(:time).returns(0)
+
+ assert_equal(NewRelic::Agent::StatsEngine::GCProfiler::Rubinius,
+ NewRelic::Agent::StatsEngine::GCProfiler.init.class)
+ ensure
+ Object.send(:remove_const, :Rubinius) unless defined
+ end
+
+ def test_collect_gc_data
+ GC.disable unless NewRelic::LanguageSupport.using_engine?('jruby')
+ if NewRelic::LanguageSupport.using_engine?('rbx')
+ agent = ::Rubinius::Agent.loopback
+ agent.stubs(:get).with('system.gc.young.total_wallclock') \
+ .returns([:value, 1000], [:value, 2500])
+ agent.stubs(:get).with('system.gc.full.total_wallclock') \
+ .returns([:value, 2000], [:value, 3500])
+ agent.stubs(:get).with('system.gc.young.count') \
+ .returns([:value, 1], [:value, 2])
+ agent.stubs(:get).with('system.gc.full.count') \
+ .returns([:value, 1], [:value, 2])
+ ::Rubinius::Agent.stubs(:loopback).returns(agent)
+ elsif NewRelic::LanguageSupport.using_version?('1.9')
+ ::GC::Profiler.stubs(:enabled?).returns(true)
+ ::GC::Profiler.stubs(:total_time).returns(1.0, 4.0)
+ ::GC.stubs(:count).returns(1, 3)
+ ::GC::Profiler.stubs(:clear).returns(nil)
+ elsif NewRelic::LanguageSupport.using_version?('1.8.7') &&
+ RUBY_DESCRIPTION =~ /Ruby Enterprise Edition/
+ ::GC.stubs(:time).returns(1000000, 4000000)
+ ::GC.stubs(:collections).returns(1, 3)
+ else
+ return true # no need to test if we're not collecting GC metrics
+ end
+
+ engine = NewRelic::Agent.instance.stats_engine
+ tracer = NewRelic::Agent::TransactionSampler.new
+ tracer.instance_variable_set(:@last_sample,
+ NewRelic::TransactionSample.new)
+ engine.transaction_sampler = tracer
+ engine.start_transaction
+ scope = engine.push_scope "scope"
+ engine.pop_scope scope, 0.01
+ engine.end_transaction
+
+ gc_stats = engine.get_stats('GC/cumulative')
+ assert_equal 2, gc_stats.call_count
+ assert_equal 3.0, gc_stats.total_call_time
+ assert_equal(3.0, tracer.last_sample.params[:custom_params][:gc_time])
+ ensure
+ GC.enable unless NewRelic::LanguageSupport.using_engine?('jruby')
+ end
+end
diff --git a/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb b/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb
deleted file mode 100644
index 508fa1a..0000000
--- a/test/new_relic/agent/stats_engine/metric_stats/harvest_test.rb
+++ /dev/null
@@ -1,133 +0,0 @@
-require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','..','test_helper'))
-require 'new_relic/agent/stats_engine/metric_stats'
-
-class NewRelic::Agent::StatsEngine::MetricStats::HarvestTest < Test::Unit::TestCase
- include NewRelic::Agent::StatsEngine::MetricStats::Harvest
-
- attr_accessor :stats_hash
- def test_merge_stats_with_nil_stats
- self.stats_hash = NewRelic::Agent::StatsEngine::MetricStats::SynchronizedHash.new
- assert_equal({}, merge_stats({}, {}))
- end
-
-
- def test_get_stats_hash_from_hash
- assert_equal({}, get_stats_hash_from({}))
- end
-
- def test_get_stats_hash_from_engine
- assert_equal({}, get_stats_hash_from(NewRelic::Agent::StatsEngine.new))
- end
-
- def test_coerce_to_metric_spec_metric_spec
- assert_equal NewRelic::MetricSpec.new, coerce_to_metric_spec(NewRelic::MetricSpec.new)
- end
-
- def test_coerce_to_metric_spec_string
- assert_equal NewRelic::MetricSpec.new('foo'), coerce_to_metric_spec('foo')
- end
-
- def test_clone_and_reset_stats_nil
- spec = NewRelic::MetricSpec.new('foo', 'bar')
- stats = nil
- begin
- clone_and_reset_stats(spec, stats)
- rescue RuntimeError => e
- assert_equal("Nil stats for foo (bar)", e.message)
- end
- end
-
- def test_clone_and_reset_stats_present
- # spec is only used for debug output
- spec = nil
- stats = mock('stats')
- stats_clone = mock('stats_clone')
- stats.expects(:clone).returns(stats_clone)
- stats.expects(:reset)
- # should return a clone
- assert_equal stats_clone, clone_and_reset_stats(spec, stats)
- end
-
- def test_merge_old_data_present
- metric_spec = mock('metric_spec')
- stats = mock('stats obj')
- stats.expects(:merge!).with('some stats')
- old_data = mock('old data')
- old_data.expects(:stats).returns('some stats')
- old_data_hash = {metric_spec => old_data}
- merge_old_data!(metric_spec, stats, old_data_hash)
- end
-
- def test_merge_old_data_nil
- metric_spec = mock('metric_spec')
- stats = mock('stats') # doesn't matter
- old_data_hash = {metric_spec => nil}
- merge_old_data!(metric_spec, stats, old_data_hash)
- end
-
- def test_add_data_to_send_unless_empty_when_is_empty
- stats = mock('stats')
- stats.expects(:is_reset?).returns(true)
- assert_equal nil, add_data_to_send_unless_empty(nil, stats, nil, nil)
- end
-
- def test_add_data_to_send_unless_empty_main
- data = mock('data hash')
- stats = mock('stats')
- stats.expects(:is_reset?).returns(false)
- metric_spec = mock('spec')
-
- NewRelic::MetricData.expects(:new).with(metric_spec, stats, nil).returns('metric data')
- data.expects(:[]=).with(metric_spec, 'metric data')
- add_data_to_send_unless_empty(data, stats, metric_spec, nil)
- end
-
- def test_add_data_to_send_unless_empty_with_id
- data = mock('data hash')
- stats = mock('stats')
- stats.expects(:is_reset?).returns(false)
- metric_spec = mock('spec')
- id = mock('id')
-
- NewRelic::MetricData.expects(:new).with(nil, stats, id).returns('metric data')
- data.expects(:[]=).with(metric_spec, 'metric data')
- assert_equal 'metric data', add_data_to_send_unless_empty(data, stats, metric_spec, id)
- end
-
- def test_merge_data_basic
- mock_stats_hash = mock('stats hash')
- self.stats_hash = mock_stats_hash
- merge_data({})
- end
-
- def test_merge_data_new_and_old_data
- stats = NewRelic::MethodTraceStats.new
- stats.record_data_point(1.0)
- new_stats = NewRelic::MethodTraceStats.new
- new_stats.record_data_point(2.0)
- self.expects(:lookup_stats).with('Custom/test/method', '').returns(new_stats)
- assert_equal(2.0, new_stats.total_call_time)
-
- metric_spec = NewRelic::MetricSpec.new('Custom/test/method')
- mock_stats_hash = mock('stats_hash')
- self.stats_hash = mock_stats_hash
- merge_data({metric_spec => NewRelic::MetricData.new(metric_spec, stats, nil)})
- assert_equal(3.0, new_stats.total_call_time)
- end
-
- def test_merge_data_old_data
- stats = NewRelic::MethodTraceStats.new
- stats.record_data_point(1.0)
- self.expects(:lookup_stats).returns(nil)
-
- metric_spec = NewRelic::MetricSpec.new('Custom/test/method')
- mock_stats_hash = mock('stats_hash')
- mock_stats_hash.expects(:[]=).with(metric_spec, stats)
- self.stats_hash = mock_stats_hash
- merge_data({metric_spec => NewRelic::MetricData.new(metric_spec, stats, nil)})
- end
-
-end
-
-
-
diff --git a/test/new_relic/agent/stats_engine/metric_stats_test.rb b/test/new_relic/agent/stats_engine/metric_stats_test.rb
index c8a4ddb..0df18e4 100644
--- a/test/new_relic/agent/stats_engine/metric_stats_test.rb
+++ b/test/new_relic/agent/stats_engine/metric_stats_test.rb
@@ -1,5 +1,8 @@
-require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
class NewRelic::Agent::MetricStatsTest < Test::Unit::TestCase
def setup
@@ -9,9 +12,9 @@ class NewRelic::Agent::MetricStatsTest < Test::Unit::TestCase
puts e
puts e.backtrace.join("\n")
end
-
+
def teardown
- @engine.harvest_timeslice_data({},{})
+ @engine.harvest_timeslice_data({})
super
end
@@ -25,7 +28,7 @@ class NewRelic::Agent::MetricStatsTest < Test::Unit::TestCase
assert_not_nil s3
assert s1 == s2
- assert s1 != s3
+ assert_not_same(s1, s3)
end
def test_harvest
@@ -43,51 +46,139 @@ class NewRelic::Agent::MetricStatsTest < Test::Unit::TestCase
assert_equal 2, @engine.get_stats("c").call_count
assert_equal 4, @engine.get_stats("c").total_call_time
- metric_data = @engine.harvest_timeslice_data({}, {}).values
+ harvested = @engine.harvest_timeslice_data({})
# after harvest, all the metrics should be reset
assert_equal 0, @engine.get_stats("a").call_count
assert_equal 0, @engine.get_stats("a").total_call_time
-
assert_equal 0, @engine.get_stats("c").call_count
assert_equal 0, @engine.get_stats("c").total_call_time
- metric_data = metric_data.reverse if metric_data[0].metric_spec.name != "a"
-
- assert_equal 'a', metric_data[0].metric_spec.name
+ spec_a = NewRelic::MetricSpec.new('a')
+ assert(harvested.has_key?(spec_a))
+ assert_equal(1, harvested[spec_a].call_count)
+ assert_equal(10, harvested[spec_a].total_call_time)
+ end
- assert_equal 1, metric_data[0].stats.call_count
- assert_equal 10, metric_data[0].stats.total_call_time
+ def test_harvest_timeslice_data_applies_metric_rename_rules
+ rule = NewRelic::Agent::RulesEngine::Rule.new(
+ 'match_expression' => '[0-9]+',
+ 'replacement' => '*',
+ 'replace_all' => true
+ )
+ rules_engine = NewRelic::Agent::RulesEngine.new([rule])
+
+ @engine.get_stats_no_scope('Custom/foo/1/bar/22').record_data_point(1)
+ @engine.get_stats_no_scope('Custom/foo/3/bar/44').record_data_point(1)
+ @engine.get_stats_no_scope('Custom/foo/5/bar/66').record_data_point(1)
+
+ harvested = @engine.harvest_timeslice_data({}, rules_engine)
+
+ assert !harvested.has_key?(NewRelic::MetricSpec.new('Custom/foo/1/bar/22'))
+ assert !harvested.has_key?(NewRelic::MetricSpec.new('Custom/foo/3/bar/44'))
+ assert !harvested.has_key?(NewRelic::MetricSpec.new('Custom/foo/5/bar/66'))
+ merged = harvested[NewRelic::MetricSpec.new('Custom/foo/*/bar/*')]
+ assert_equal(3, merged.call_count)
end
def test_harvest_with_merge
s = @engine.get_stats "a"
s.trace_call 1
-
assert_equal 1, @engine.get_stats("a").call_count
- harvest = @engine.harvest_timeslice_data({}, {})
+ harvest = @engine.harvest_timeslice_data({})
+
+ s = @engine.get_stats "a"
assert_equal 0, s.call_count
s.trace_call 2
assert_equal 1, s.call_count
- # this calk should merge the contents of the previous harvest,
+ # this call should merge the contents of the previous harvest,
# so the stats for metric "a" should have 2 data points
- harvest = @engine.harvest_timeslice_data(harvest, {})
- stats = harvest.fetch(NewRelic::MetricSpec.new("a")).stats
+ harvest = @engine.harvest_timeslice_data(harvest)
+ stats = harvest.fetch(NewRelic::MetricSpec.new("a"))
assert_equal 2, stats.call_count
assert_equal 3, stats.total_call_time
end
- def test_rescues_from_synchronization_failure_on_write
- hash = NewRelic::Agent::StatsEngine::MetricStats::SynchronizedHash.new
- 10.times { |i| hash[i] = i }
+ def test_merge_merges
+ @engine.get_stats("foo").record_data_point(1)
+
+ other_stats_hash = NewRelic::Agent::StatsHash.new()
+ other_stats_hash.record(NewRelic::MetricSpec.new('foo'), 1)
+ other_stats_hash.record(NewRelic::MetricSpec.new('bar'), 1)
+
+ @engine.merge!(other_stats_hash)
- assert_nothing_raised do
- hash.each do |k, v|
- hash[11] = 1
+ foo_stats = @engine.get_stats('foo')
+ bar_stats = @engine.get_stats('bar')
+ assert_equal(2, foo_stats.call_count)
+ assert_equal(1, bar_stats.call_count)
+ end
+
+ def test_record_metrics_unscoped_metrics_only_by_default
+ @engine.stubs(:scope_name).returns('scopey')
+ @engine.record_metrics('foo', 42)
+ unscoped_stats = @engine.get_stats('foo', false)
+ scoped_stats = @engine.get_stats('foo', true, true, 'scopey')
+ assert_equal(1, unscoped_stats.call_count)
+ assert_equal(0, scoped_stats.call_count)
+ end
+
+ def test_record_metrics_records_to_scoped_metric_if_requested
+ @engine.stubs(:scope_name).returns('scopey')
+ @engine.record_metrics('foo', 42, :scoped => true)
+ unscoped_stats = @engine.get_stats('foo', false)
+ scoped_stats = @engine.get_stats('foo', true, true, 'scopey')
+ assert_equal(1, unscoped_stats.call_count)
+ assert_equal(1, scoped_stats.call_count)
+ end
+
+ def test_record_metrics_elides_scoped_metric_if_not_in_transaction
+ @engine.clear_stats
+ @engine.stubs(:scope_name).returns(nil)
+ @engine.record_metrics('foo', 42, :scoped => true)
+ unscoped_stats = @engine.get_stats('foo', false)
+ assert_equal(1, unscoped_stats.call_count)
+ assert_equal(1, @engine.metrics.size)
+ end
+
+ def test_record_metrics_accepts_explicit_scope
+ @engine.stubs(:scope_name).returns('scopey')
+ @engine.record_metrics('foo', 42, :scoped => true, :scope => 'not scopey')
+ unscoped_stats = @engine.get_stats('foo', false)
+ scoped_stats_scopey = @engine.get_stats('foo', true, true, 'scopey')
+ scoped_stats_not_scopey = @engine.get_stats('foo', true, true, 'not scopey')
+ assert_equal(1, unscoped_stats.call_count)
+ assert_equal(0, scoped_stats_scopey.call_count)
+ assert_equal(1, scoped_stats_not_scopey.call_count)
+ end
+
+ def test_record_metrics_accepts_block
+ @engine.record_metrics('foo') do |stats|
+ stats.call_count = 999
+ end
+ stats = @engine.get_stats_no_scope('foo')
+ assert_equal(999, stats.call_count)
+ end
+
+ def test_record_metrics_is_thread_safe
+ threads = []
+ nthreads = 25
+ iterations = 100
+ nthreads.times do |tid|
+ threads << Thread.new do
+ iterations.times do
+ @engine.record_metrics('m1', 1)
+ @engine.record_metrics('m2', 1)
+ end
end
end
+ threads.each { |t| t.join }
+
+ stats_m1 = @engine.get_stats_no_scope('m1')
+ stats_m2 = @engine.get_stats_no_scope('m2')
+ assert_equal(nthreads * iterations, stats_m1.call_count)
+ assert_equal(nthreads * iterations, stats_m2.call_count)
end
end
-
diff --git a/test/new_relic/agent/stats_engine/samplers_test.rb b/test/new_relic/agent/stats_engine/samplers_test.rb
index e138bb7..d9c97a3 100644
--- a/test/new_relic/agent/stats_engine/samplers_test.rb
+++ b/test/new_relic/agent/stats_engine/samplers_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..','..','test_helper'))
require 'new_relic/agent/samplers/cpu_sampler'
@@ -42,10 +46,13 @@ class NewRelic::Agent::StatsEngine::SamplersTest < Test::Unit::TestCase
s.poll
s.instance_eval { @last_time = Time.now - 1.1 }
s.poll
- assert_equal 2, s.systemtime_stats.call_count
- assert_equal 2, s.usertime_stats.call_count
- assert s.usertime_stats.total_call_time >= 0, "user cpu greater/equal to 0: #{s.usertime_stats.total_call_time}"
- assert s.systemtime_stats.total_call_time >= 0, "system cpu greater/equal to 0: #{s.systemtime_stats.total_call_time}"
+
+ systemtime_stats = s.stats_engine.get_stats_no_scope("CPU/System Time")
+ usertime_stats = s.stats_engine.get_stats_no_scope("CPU/User Time")
+ assert_equal 2, systemtime_stats.call_count
+ assert_equal 2, usertime_stats.call_count
+ assert usertime_stats.total_call_time >= 0, "user cpu greater/equal to 0: #{usertime_stats.total_call_time}"
+ assert systemtime_stats.total_call_time >= 0, "system cpu greater/equal to 0: #{systemtime_stats.total_call_time}"
end
def test_memory__default
s = NewRelic::Agent::Samplers::MemorySampler.new
@@ -53,8 +60,9 @@ class NewRelic::Agent::StatsEngine::SamplersTest < Test::Unit::TestCase
s.poll
s.poll
s.poll
- assert_equal 3, s.stats.call_count
- assert s.stats.total_call_time > 0.5, "cpu greater than 0.5 ms: #{s.stats.total_call_time}"
+ stats = @stats_engine.get_stats_no_scope("Memory/Physical")
+ assert_equal(3, stats.call_count)
+ assert stats.total_call_time > 0.5, "cpu greater than 0.5 ms: #{stats.total_call_time}"
end
def test_memory__linux
return if RUBY_PLATFORM =~ /darwin/
@@ -64,8 +72,9 @@ class NewRelic::Agent::StatsEngine::SamplersTest < Test::Unit::TestCase
s.poll
s.poll
s.poll
- assert_equal 3, s.stats.call_count
- assert s.stats.total_call_time > 0.5, "cpu greater than 0.5 ms: #{s.stats.total_call_time}"
+ stats = @stats_engine.get_stats_no_scope("Memory/Physical")
+ assert_equal 3, stats.call_count
+ assert stats.total_call_time > 0.5, "cpu greater than 0.5 ms: #{stats.total_call_time}"
end
def test_memory__solaris
return if defined? JRuby
@@ -74,8 +83,9 @@ class NewRelic::Agent::StatsEngine::SamplersTest < Test::Unit::TestCase
s = NewRelic::Agent::Samplers::MemorySampler.new
s.stats_engine = @stats_engine
s.poll
- assert_equal 1, s.stats.call_count
- assert_equal 999, s.stats.total_call_time
+ stats = s.stats_engine.get_stats_no_scope("Memory/Physical")
+ assert_equal 1, stats.call_count
+ assert_equal 999, stats.total_call_time
end
def test_memory__windows
return if defined? JRuby
diff --git a/test/new_relic/agent/stats_engine_test.rb b/test/new_relic/agent/stats_engine_test.rb
index 9b78c58..2e1d00a 100644
--- a/test/new_relic/agent/stats_engine_test.rb
+++ b/test/new_relic/agent/stats_engine_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..', 'test_helper'))
@@ -11,7 +15,7 @@ class NewRelic::Agent::StatsEngineTest < Test::Unit::TestCase
end
def teardown
- @engine.harvest_timeslice_data({},{})
+ @engine.harvest_timeslice_data({})
mocha_teardown
super
end
@@ -177,51 +181,6 @@ class NewRelic::Agent::StatsEngineTest < Test::Unit::TestCase
assert_equal 10, scope1.children_time.round
end
-
- def test_collect_gc_data
- GC.disable unless NewRelic::LanguageSupport.using_engine?('jruby')
- if NewRelic::LanguageSupport.using_engine?('rbx')
- agent = ::Rubinius::Agent.loopback
- agent.stubs(:get).with('system.gc.young.total_wallclock') \
- .returns([:value, 1000], [:value, 2500])
- agent.stubs(:get).with('system.gc.full.total_wallclock') \
- .returns([:value, 2000], [:value, 3500])
- agent.stubs(:get).with('system.gc.young.count') \
- .returns([:value, 1], [:value, 2])
- agent.stubs(:get).with('system.gc.full.count') \
- .returns([:value, 1], [:value, 2])
- ::Rubinius::Agent.stubs(:loopback).returns(agent)
- elsif NewRelic::LanguageSupport.using_version?('1.9')
- ::GC::Profiler.stubs(:enabled?).returns(true)
- ::GC::Profiler.stubs(:total_time).returns(1.0, 4.0)
- ::GC.stubs(:count).returns(1, 3)
- ::GC::Profiler.stubs(:clear).returns(nil)
- elsif NewRelic::LanguageSupport.using_version?('1.8.7') &&
- RUBY_DESCRIPTION =~ /Ruby Enterprise Edition/
- ::GC.stubs(:time).returns(1000000, 4000000)
- ::GC.stubs(:collections).returns(1, 3)
- else
- return true # no need to test if we're not collecting GC metrics
- end
-
- engine = NewRelic::Agent.instance.stats_engine
- tracer = NewRelic::Agent::TransactionSampler.new
- tracer.instance_variable_set(:@last_sample,
- NewRelic::TransactionSample.new)
- engine.transaction_sampler = tracer
- engine.start_transaction
- scope = engine.push_scope "scope"
- engine.pop_scope scope, 0.01
- engine.end_transaction
-
- gc_stats = engine.get_stats('GC/cumulative')
- assert_equal 2, gc_stats.call_count
- assert_equal 3.0, gc_stats.total_call_time
- assert_equal(3.0, tracer.last_sample.params[:custom_params][:gc_time])
- ensure
- GC.enable unless NewRelic::LanguageSupport.using_engine?('jruby')
- end
-
private
def check_time_approximate(expected, actual)
assert((expected - actual).abs < 0.1, "Expected between #{expected - 0.1} and #{expected + 0.1}, got #{actual}")
diff --git a/test/new_relic/agent/stats_hash_test.rb b/test/new_relic/agent/stats_hash_test.rb
new file mode 100644
index 0000000..647693d
--- /dev/null
+++ b/test/new_relic/agent/stats_hash_test.rb
@@ -0,0 +1,97 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require File.expand_path(File.join(File.dirname(__FILE__),'..', '..','test_helper'))
+
+class NewRelic::Agent::StatsHashTest < Test::Unit::TestCase
+ def setup
+ @hash = NewRelic::Agent::StatsHash.new
+ end
+
+ def test_creates_default_entries
+ stats = @hash['a/b/c/d']
+ assert_kind_of(NewRelic::Agent::Stats, stats)
+ end
+
+ def test_record_accpets_single_metric_spec
+ spec = NewRelic::MetricSpec.new('foo/bar')
+ stats = @hash[spec]
+ stats.expects(:record_data_point).with(42)
+ @hash.record(spec, 42)
+ end
+
+ def test_record_accepts_multiple_metric_specs
+ spec1 = NewRelic::MetricSpec.new('foo/bar', 'scope1')
+ spec2 = NewRelic::MetricSpec.new('foo/bar', 'scope2')
+ stats1 = @hash[spec1]
+ stats2 = @hash[spec2]
+ stats1.expects(:record_data_point).with(42)
+ stats2.expects(:record_data_point).with(42)
+ @hash.record([spec1, spec2], 42)
+ end
+
+ def test_record_accepts_single_metric_spec_with_block
+ spec = NewRelic::MetricSpec.new('foo')
+ stats = @hash[spec]
+ stats.expects(:do_stuff)
+ @hash.record(spec) do |s|
+ s.do_stuff
+ end
+ end
+
+ def test_record_accepts_multiple_metric_specs_with_block
+ specs = [
+ NewRelic::MetricSpec.new('foo'),
+ NewRelic::MetricSpec.new('bar')
+ ]
+ stats = specs.map { |spec| @hash[spec] }
+ stats.each { |stat| stat.expects(:do_stuff) }
+ @hash.record(specs) do |s|
+ s.do_stuff
+ end
+ end
+
+ def test_record_accepts_stats_value
+ spec = NewRelic::MetricSpec.new('foo')
+ other_stats = NewRelic::Agent::Stats.new
+ stats = @hash[spec]
+ stats.expects(:merge!).with(other_stats)
+ @hash.record(spec, other_stats)
+ end
+
+ def test_merge_merges
+ specs = [
+ NewRelic::MetricSpec.new('foo'),
+ NewRelic::MetricSpec.new('bar'),
+ NewRelic::MetricSpec.new('baz'),
+ NewRelic::MetricSpec.new('baz', 'a_scope')
+ ]
+
+ hash1 = NewRelic::Agent::StatsHash.new
+ hash1.record(specs[0], 1)
+ hash1.record(specs[1], 2)
+ hash1.record(specs[2], 3)
+
+ hash2 = NewRelic::Agent::StatsHash.new
+ hash2.record(specs[0], 1)
+ hash2.record(specs[1], 2)
+ hash2.record(specs[3], 3) # no scope
+
+ hash1.merge!(hash2)
+
+ assert_equal(4, hash1.keys.size)
+ assert_equal(2, hash1[specs[0]].call_count)
+ assert_equal(2, hash1[specs[1]].call_count)
+ assert_equal(1, hash1[specs[2]].call_count)
+ assert_equal(1, hash1[specs[3]].call_count)
+ end
+
+ def test_marshal_dump
+ hash = NewRelic::Agent::StatsHash.new()
+ hash.record('foo', 1)
+ hash.record('bar', 2)
+ copy = Marshal.load(Marshal.dump(hash))
+ assert_equal(hash, copy)
+ end
+end
diff --git a/test/new_relic/agent/stats_test.rb b/test/new_relic/agent/stats_test.rb
new file mode 100644
index 0000000..bbf384b
--- /dev/null
+++ b/test/new_relic/agent/stats_test.rb
@@ -0,0 +1,201 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+ENV['SKIP_RAILS'] = 'true'
+require File.expand_path(File.join(File.dirname(__FILE__),'..', '..', 'test_helper'))
+
+class NewRelic::Agent::StatsTest < Test::Unit::TestCase
+ def mock_plusequals(first, second, method, first_value, second_value)
+ first.expects(method).returns(first_value)
+ second.expects(method).returns(second_value)
+ first.expects("#{method}=".to_sym).with(first_value + second_value)
+ end
+
+ def test_update_totals
+ attrs = [:total_call_time, :total_exclusive_time, :sum_of_squares]
+ merged = setup_merge do |a, b|
+ attrs.each do |attr|
+ a.send("#{attr}=", 2)
+ b.send("#{attr}=", 3)
+ end
+ end
+ attrs.each do |attr|
+ assert_equal(5, merged.send(attr))
+ end
+ end
+
+ def setup_merge
+ a = NewRelic::Agent::Stats.new
+ b = NewRelic::Agent::Stats.new
+ a.reset
+ b.reset
+ yield a, b
+ a.merge(b)
+ end
+
+ def test_merge_expands_min_max_call_time
+ merged = setup_merge do |a, b|
+ a.call_count = 1
+ b.call_count = 1
+ a.min_call_time = 0.5
+ a.max_call_time = 3.0
+ b.min_call_time = 1.0
+ b.max_call_time = 4.0
+ end
+ assert_equal(0.5, merged.min_call_time)
+ assert_equal(4.0, merged.max_call_time)
+ end
+
+ def test_simple
+ stats = NewRelic::Agent::Stats.new
+ validate stats, 0, 0, 0, 0
+
+ assert_equal stats.call_count,0
+ stats.trace_call 10
+ stats.trace_call 20
+ stats.trace_call 30
+
+ validate stats, 3, (10+20+30), 10, 30
+ end
+
+ def test_to_s
+ s1 = NewRelic::Agent::Stats.new
+ s1.trace_call 10
+ assert_equal("[ 1 calls 10.0000s]", s1.to_s)
+ end
+
+ def test_apdex_recording
+ s = NewRelic::Agent::Stats.new
+
+ s.record_apdex_s
+ s.record_apdex_t
+
+ s.record_apdex_f
+ s.record_apdex_t
+
+ assert_equal(1, s.apdex_s)
+ assert_equal(1, s.apdex_f)
+ assert_equal(2, s.apdex_t)
+ end
+
+ def test_merge
+ s1 = NewRelic::Agent::Stats.new
+ s2 = NewRelic::Agent::Stats.new
+
+ s1.trace_call 10
+ s2.trace_call 20
+ s2.freeze
+
+ validate s2, 1, 20, 20, 20
+ s3 = s1.merge s2
+ validate s3, 2, (10+20), 10, 20
+ validate s1, 1, 10, 10, 10
+ validate s2, 1, 20, 20, 20
+
+ s1.merge! s2
+ validate s1, 2, (10+20), 10, 20
+ validate s2, 1, 20, 20, 20
+ end
+
+ def test_merge_with_exclusive
+ s1 = NewRelic::Agent::Stats.new
+
+ s2 = NewRelic::Agent::Stats.new
+
+ s1.trace_call 10, 5
+ s2.trace_call 20, 10
+ s2.freeze
+
+ validate s2, 1, 20, 20, 20, 10
+ s3 = s1.merge s2
+ validate s3, 2, (10+20), 10, 20, (10+5)
+ validate s1, 1, 10, 10, 10, 5
+ validate s2, 1, 20, 20, 20, 10
+
+ s1.merge! s2
+ validate s1, 2, (10+20), 10, 20, (5+10)
+ validate s2, 1, 20, 20, 20, 10
+ end
+
+ def test_merge_array
+ s1 = NewRelic::Agent::Stats.new
+ merges = []
+ merges << (NewRelic::Agent::Stats.new.trace_call 1)
+ merges << (NewRelic::Agent::Stats.new.trace_call 1)
+ merges << (NewRelic::Agent::Stats.new.trace_call 1)
+
+ s1.merge! merges
+ validate s1, 3, 3, 1, 1
+ end
+
+ def test_freeze
+ s1 = NewRelic::Agent::Stats.new
+
+ s1.trace_call 10
+ s1.freeze
+
+ begin
+ # the following should throw an exception because s1 is frozen
+ s1.trace_call 20
+ assert false
+ rescue StandardError
+ assert s1.frozen?
+ validate s1, 1, 10, 10, 10
+ end
+ end
+
+ def test_sum_of_squares_merge
+ s1 = NewRelic::Agent::Stats.new
+ s1.trace_call 4
+ s1.trace_call 7
+
+ s2 = NewRelic::Agent::Stats.new
+ s2.trace_call 13
+ s2.trace_call 16
+
+ s3 = s1.merge(s2)
+
+ assert_equal(s1.sum_of_squares, 4*4 + 7*7)
+ assert_equal(s3.sum_of_squares, 4*4 + 7*7 + 13*13 + 16*16, "check sum of squares")
+ end
+
+ def test_chained_stats_proxies_calls_to_scoped_and_unscoped
+ scoped_stats = NewRelic::Agent::Stats.new
+ unscoped_stats = NewRelic::Agent::Stats.new
+ chained_stats = NewRelic::Agent::ChainedStats.new(scoped_stats, unscoped_stats)
+ chained_stats.record_data_point(42)
+ assert_equal(1, scoped_stats.call_count)
+ assert_equal(1, unscoped_stats.call_count)
+ assert_equal(42, scoped_stats.total_call_time)
+ assert_equal(42, unscoped_stats.total_call_time)
+ end
+
+ def test_chained_stats_returns_values_from_scoped
+ scoped_stats = NewRelic::Agent::Stats.new
+ unscoped_stats = NewRelic::Agent::Stats.new
+ chained_stats = NewRelic::Agent::ChainedStats.new(scoped_stats, unscoped_stats)
+ scoped_stats.record_data_point(42)
+ assert_equal(1, chained_stats.call_count)
+ assert_equal(42, chained_stats.total_call_time)
+ end
+
+ if RUBY_VERSION >= '1.9'
+ def test_to_json_enforces_float_values
+ s1 = NewRelic::Agent::Stats.new
+ s1.trace_call 3.to_r
+ s1.trace_call 7.to_r
+
+ assert_equal 3.0, JSON.load(s1.to_json)['min_call_time']
+ end
+ end
+
+ private
+ def validate (stats, count, total, min, max, exclusive = nil)
+ assert_equal count, stats.call_count
+ assert_equal total, stats.total_call_time
+ assert_equal min, stats.min_call_time
+ assert_equal max, stats.max_call_time
+ assert_equal exclusive, stats.total_exclusive_time if exclusive
+ end
+end
diff --git a/test/new_relic/agent/thread_profiler_test.rb b/test/new_relic/agent/thread_profiler_test.rb
index 9aa6bad..478dfda 100644
--- a/test/new_relic/agent/thread_profiler_test.rb
+++ b/test/new_relic/agent/thread_profiler_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'base64'
require 'thread'
diff --git a/test/new_relic/agent/thread_test.rb b/test/new_relic/agent/thread_test.rb
index 3fc91b7..c245d06 100644
--- a/test/new_relic/agent/thread_test.rb
+++ b/test/new_relic/agent/thread_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/agent/thread'
diff --git a/test/new_relic/agent/threaded_test.rb b/test/new_relic/agent/threaded_test.rb
index 60aa635..32ca855 100644
--- a/test/new_relic/agent/threaded_test.rb
+++ b/test/new_relic/agent/threaded_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class ThreadedTest < Test::Unit::TestCase
def setup
@original_thread_class = NewRelic::Agent::AgentThread
diff --git a/test/new_relic/agent/transaction_info_test.rb b/test/new_relic/agent/transaction_info_test.rb
index 321a434..db524c6 100644
--- a/test/new_relic/agent/transaction_info_test.rb
+++ b/test/new_relic/agent/transaction_info_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'ostruct'
@@ -9,46 +13,98 @@ class NewRelic::Agent::TransactionInfoTest < Test::Unit::TestCase
@request_with_double_quotes = OpenStruct.new(:cookies => {'NRAGENT' => 'tk="""deadbeef"""'})
@request_with_single_quotes = OpenStruct.new(:cookies => {'NRAGENT' => "tk='''deadbeef'''"})
@request_with_multi_lt = OpenStruct.new(:cookies => {'NRAGENT' => 'tk=<<<deadbeef'})
- @request_with_multi_gt = OpenStruct.new(:cookies => {'NRAGENT' => 'tk=>>>deadbeef'})
+ @request_with_multi_gt = OpenStruct.new(:cookies => {'NRAGENT' => 'tk=>>>deadbeef'})
@request_with_bare_token = OpenStruct.new(:cookies => {'NRAGENT' => 0xdeadbeef})
@request_with_nil_token = OpenStruct.new(:cookies => {'NRAGENT' => nil})
end
-
+
def test_get_token_safe_token_returned_untouched
assert_equal("12345678", NewRelic::Agent::TransactionInfo.get_token(@request))
end
def test_get_token_with_embedded_tags_sanitized
- assert_equal("",NewRelic::Agent::TransactionInfo.get_token(@request_with_embedded_tag))
+ assert_equal("",NewRelic::Agent::TransactionInfo.get_token(@request_with_embedded_tag))
end
def test_get_token_with_embedded_utf8_js_sanitized
- assert_equal("1234&amp;#34&amp;#93&amp;#41&amp;#595678",
- NewRelic::Agent::TransactionInfo.get_token(@request_with_embedded_utf8_encoded_js))
+ assert_equal("1234&amp;#34&amp;#93&amp;#41&amp;#595678",
+ NewRelic::Agent::TransactionInfo.get_token(@request_with_embedded_utf8_encoded_js))
end
def test_get_token_replaces_double_quoted_token_with_empty_string
- assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_double_quotes))
+ assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_double_quotes))
end
def test_get_token_replaces_single_quoted_toket_with_empty_string
- assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_single_quotes))
+ assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_single_quotes))
end
def test_get_token_replaces_token_started_with_multiple_Lt_with_empty_string
- assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_multi_lt))
+ assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_multi_lt))
end
def test_get_token_replaces_token_started_with_multiple_gt_with_empty_string
- assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_multi_gt))
+ assert_equal("", NewRelic::Agent::TransactionInfo.get_token(@request_with_multi_gt))
end
def test_get_token_bare_value_replaced_with_nil
- assert_equal(nil,NewRelic::Agent::TransactionInfo.get_token(@request_with_bare_token))
+ assert_equal(nil,NewRelic::Agent::TransactionInfo.get_token(@request_with_bare_token))
end
def test_get_token_nil_token_returns_nil_token
- assert_equal(nil,NewRelic::Agent::TransactionInfo.get_token(@request_with_ni_token))
+ assert_equal(nil,NewRelic::Agent::TransactionInfo.get_token(@request_with_ni_token))
+ end
+
+ def test_has_correct_apdex_t_for_tansaction
+ txn_info = NewRelic::Agent::TransactionInfo.get
+ config = { :web_transactions_apdex => {'Controller/foo/bar' => 1.5},
+ :apdex_t => 2.0 }
+
+ with_config(config, :do_not_cast => true) do
+ txn_info.transaction_name = 'Controller/foo/bar'
+ assert_equal 1.5, txn_info.apdex_t
+ txn_info.transaction_name = 'Controller/some/other/txn'
+ assert_equal 2.0, txn_info.apdex_t
+ end
+ end
+
+ def test_has_correct_transaction_trace_threshold_when_default
+ txn_info = NewRelic::Agent::TransactionInfo.get
+ config = { :web_transactions_apdex => {'Controller/foo/bar' => 1.5},
+ :apdex_t => 2.0 }
+
+ with_config(config, :do_not_cast => true) do
+ txn_info.transaction_name = 'Controller/foo/bar'
+ assert_equal 6.0, txn_info.transaction_trace_threshold
+ txn_info.transaction_name = 'Controller/some/other/txn'
+ assert_equal 8.0, txn_info.transaction_trace_threshold
+ end
+ end
+
+ def test_has_correct_transaction_trace_threshold_when_specified
+ txn_info = NewRelic::Agent::TransactionInfo.get
+ config = {
+ :web_transactions_apdex => {'Controller/foo/bar' => 1.5},
+ :apdex_t => 2.0,
+ :'transaction_tracer.transaction_threshold' => 4.0
+ }
+
+ with_config(config, :do_not_cast => true) do
+ txn_info.transaction_name = 'Controller/foo/bar'
+ assert_equal 4.0, txn_info.transaction_trace_threshold
+ txn_info.transaction_name = 'Controller/some/other/txn'
+ assert_equal 4.0, txn_info.transaction_trace_threshold
+ end
end
+ def test_transaction_name
+ NewRelic::Agent::TransactionInfo.reset
+ txn = NewRelic::Agent::TransactionInfo.get
+ assert_equal(NewRelic::Agent::TransactionInfo::DEFAULT_TRANSACTION_NAME,
+ txn.transaction_name)
+ assert_equal(false, txn.transaction_name_set?)
+ txn.transaction_name = "foobar"
+ assert_equal("foobar", txn.transaction_name)
+ assert_equal(true, txn.transaction_name_set?)
+ end
end
diff --git a/test/new_relic/agent/transaction_sample_builder_test.rb b/test/new_relic/agent/transaction_sample_builder_test.rb
index f551f91..a2a07a3 100644
--- a/test/new_relic/agent/transaction_sample_builder_test.rb
+++ b/test/new_relic/agent/transaction_sample_builder_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
class NewRelic::Agent::TransationSampleBuilderTest < Test::Unit::TestCase
@@ -102,7 +106,7 @@ class NewRelic::Agent::TransationSampleBuilderTest < Test::Unit::TestCase
should_be_a_copy = sample.omit_segments_with('OMIT NOTHING')
validate_segment should_be_a_copy.root_segment, false
-
+
assert_equal sample.params, should_be_a_copy.params
assert_equal(sample.root_segment.to_debug_str(0),
should_be_a_copy.root_segment.to_debug_str(0))
@@ -169,7 +173,7 @@ class NewRelic::Agent::TransationSampleBuilderTest < Test::Unit::TestCase
@builder.finish_trace(Time.now.to_f)
validate_builder
end
-
+
def test_trace_should_not_record_more_than_segment_limit
with_config(:'transaction_tracer.limit_segments' => 3) do
8.times {|i| build_segment i.to_s }
@@ -177,6 +181,13 @@ class NewRelic::Agent::TransationSampleBuilderTest < Test::Unit::TestCase
end
end
+ def test_finish_trace_records_threshold
+ NewRelic::Agent::TransactionInfo.get.stubs(:transaction_trace_threshold) \
+ .returns(2.0)
+ @builder.finish_trace
+ assert_equal 2.0, @builder.sample.threshold
+ end
+
# regression
def test_trace_should_log_segment_reached_once
with_config(:'transaction_tracer.limit_segments' => 3) do
@@ -184,7 +195,7 @@ class NewRelic::Agent::TransationSampleBuilderTest < Test::Unit::TestCase
8.times {|i| build_segment i.to_s }
end
end
-
+
def validate_builder(check_names = true)
validate_segment @builder.sample.root_segment, check_names
end
diff --git a/test/new_relic/agent/transaction_sampler_test.rb b/test/new_relic/agent/transaction_sampler_test.rb
index 24e7547..860d467 100644
--- a/test/new_relic/agent/transaction_sampler_test.rb
+++ b/test/new_relic/agent/transaction_sampler_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
@@ -227,10 +231,9 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
end
def test_store_slowest_sample_new_is_slowest
- old_sample = mock('old_sample')
- new_sample = mock('new_sample')
+ old_sample = stub('old_sample', :duration => 3.0, :threshold => 1.0)
+ new_sample = stub('new_sample', :duration => 4.0, :threshold => 1.0)
@sampler.instance_eval { @slowest_sample = old_sample }
- @sampler.expects(:slowest_sample?).with(old_sample, new_sample).returns(true)
@sampler.store_slowest_sample(new_sample)
@@ -248,6 +251,15 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
assert_equal(old_sample, @sampler.instance_variable_get('@slowest_sample'))
end
+ def test_store_slowest_sample_does_not_store_if_faster_than_threshold
+ old_sample = stub('old_sample', :duration => 1.0, :threshold => 0.5)
+ new_sample = stub('new_sample', :duration => 2.0, :threshold => 4.0)
+ @sampler.instance_eval { @slowest_sample = old_sample }
+ @sampler.store_slowest_sample(new_sample)
+
+ assert_equal(old_sample, @sampler.instance_variable_get('@slowest_sample'))
+ end
+
def test_slowest_sample_no_sample
old_sample = nil
new_sample = mock('new_sample')
@@ -269,7 +281,7 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
new_sample.expects(:duration).returns(1.0)
assert_equal(true, @sampler.slowest_sample?(old_sample, new_sample))
end
-
+
def test_truncate_samples_no_samples
@sampler.instance_eval { @max_samples = 10 }
@sampler.instance_eval { @samples = [] }
@@ -373,7 +385,8 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
builder.expects(:current_segment).returns(segment)
segment.expects(:[]).with(key).returns(nil)
@sampler.expects(:append_new_message).with(nil, 'a message').returns('a message')
- @sampler.expects(:truncate_message).with('a message').returns('truncated_message')
+ NewRelic::Agent::TransactionSampler.expects(:truncate_message) \
+ .with('a message').returns('truncated_message')
segment.expects(:[]=).with(key, 'truncated_message')
@sampler.expects(:append_backtrace).with(segment, 1.0)
@sampler.send(:notice_extra_data, 'a message', 1.0, key)
@@ -381,12 +394,12 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
def test_truncate_message_short_message
message = 'a message'
- assert_equal(message, @sampler.truncate_message(message))
+ assert_equal(message, NewRelic::Agent::TransactionSampler.truncate_message(message))
end
def test_truncate_message_long_message
message = 'a' * 16384
- truncated_message = @sampler.truncate_message(message)
+ truncated_message = NewRelic::Agent::TransactionSampler.truncate_message(message)
assert_equal(16384, truncated_message.length)
assert_equal('a' * 16381 + '...', truncated_message)
end
@@ -558,17 +571,6 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
end
end
- def test_add_samples_to_under_threshold
- result = []
- sample = mock('sample')
- sample.expects(:duration).returns(1.0).at_least_once
- @sampler.instance_eval { @slowest_sample = sample }
- @sampler.expects(:add_random_sample_to).with([])
- with_config(:'transaction_tracer.transaction_threshold' => 2.0) do
- assert_equal([], @sampler.add_samples_to(result))
- end
- end
-
def test_add_samples_to_two_sample_enter_one_sample_leave
slower_sample = mock('slower')
slower_sample.expects(:duration).returns(10.0).at_least_once
@@ -667,23 +669,23 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
end
def test_sample_tree
- assert_equal 0, @sampler.scope_depth
+ with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ assert_equal 0, @sampler.scope_depth
- @sampler.notice_first_scope_push Time.now.to_f
- @sampler.notice_transaction "/path", nil, {}
- @sampler.notice_push_scope "a"
+ @sampler.notice_first_scope_push Time.now.to_f
+ @sampler.notice_transaction "/path", nil, {}
+ @sampler.notice_push_scope "a"
- @sampler.notice_push_scope "b"
- @sampler.notice_pop_scope "b"
+ @sampler.notice_push_scope "b"
+ @sampler.notice_pop_scope "b"
- @sampler.notice_push_scope "c"
- @sampler.notice_push_scope "d"
- @sampler.notice_pop_scope "d"
- @sampler.notice_pop_scope "c"
+ @sampler.notice_push_scope "c"
+ @sampler.notice_push_scope "d"
+ @sampler.notice_pop_scope "d"
+ @sampler.notice_pop_scope "c"
- @sampler.notice_pop_scope "a"
- @sampler.notice_scope_empty
- with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ @sampler.notice_pop_scope "a"
+ @sampler.notice_scope_empty
sample = @sampler.harvest([]).first
assert_equal "ROOT{a{b,c{d}}}", sample.to_s_compact
end
@@ -696,22 +698,22 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
MockGCStats.mock_values = [0,0,0,1,0,0,1,0,0,0,0,0,0,0,0]
assert_equal 0, @sampler.scope_depth
- @sampler.notice_first_scope_push Time.now.to_f
- @sampler.notice_transaction "/path", nil, {}
- @sampler.notice_push_scope "a"
+ with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ @sampler.notice_first_scope_push Time.now.to_f
+ @sampler.notice_transaction "/path", nil, {}
+ @sampler.notice_push_scope "a"
- @sampler.notice_push_scope "b"
- @sampler.notice_pop_scope "b"
+ @sampler.notice_push_scope "b"
+ @sampler.notice_pop_scope "b"
- @sampler.notice_push_scope "c"
- @sampler.notice_push_scope "d"
- @sampler.notice_pop_scope "d"
- @sampler.notice_pop_scope "c"
+ @sampler.notice_push_scope "c"
+ @sampler.notice_push_scope "d"
+ @sampler.notice_pop_scope "d"
+ @sampler.notice_pop_scope "c"
- @sampler.notice_pop_scope "a"
- @sampler.notice_scope_empty
+ @sampler.notice_pop_scope "a"
+ @sampler.notice_scope_empty
- with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
sample = @sampler.harvest([]).first
assert_equal "ROOT{a{b,c{d}}}", sample.to_s_compact
end
@@ -730,14 +732,13 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
# sample traces, for example. It's unfortunate, but we can't
# reliably turn off GC on all versions of ruby under test
def test_harvest_slowest
- run_sample_trace(0,0.1)
- run_sample_trace(0,0.1)
- # two second duration
- run_sample_trace(0,2)
- run_sample_trace(0,0.1)
- run_sample_trace(0,0.1)
-
with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ run_sample_trace(0,0.1)
+ run_sample_trace(0,0.1)
+ # two second duration
+ run_sample_trace(0,2)
+ run_sample_trace(0,0.1)
+ run_sample_trace(0,0.1)
slowest = @sampler.harvest(nil)[0]
first_duration = slowest.duration
@@ -758,8 +759,8 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
end
def test_prepare_to_send
- run_sample_trace { sleep 0.002 }
sample = with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ run_sample_trace { sleep 0.002 }
@sampler.harvest(nil)[0]
end
@@ -786,43 +787,43 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
end
def test_sample_with_parallel_paths
- assert_equal 0, @sampler.scope_depth
+ with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ assert_equal 0, @sampler.scope_depth
- @sampler.notice_first_scope_push Time.now.to_f
- @sampler.notice_transaction "/path", nil, {}
- @sampler.notice_push_scope "a"
+ @sampler.notice_first_scope_push Time.now.to_f
+ @sampler.notice_transaction "/path", nil, {}
+ @sampler.notice_push_scope "a"
- assert_equal 1, @sampler.scope_depth
+ assert_equal 1, @sampler.scope_depth
- @sampler.notice_pop_scope "a"
- @sampler.notice_scope_empty
+ @sampler.notice_pop_scope "a"
+ @sampler.notice_scope_empty
- assert_equal 0, @sampler.scope_depth
+ assert_equal 0, @sampler.scope_depth
- @sampler.notice_first_scope_push Time.now.to_f
- @sampler.notice_transaction "/path", nil, {}
- @sampler.notice_push_scope "a"
- @sampler.notice_pop_scope "a"
- @sampler.notice_scope_empty
+ @sampler.notice_first_scope_push Time.now.to_f
+ @sampler.notice_transaction "/path", nil, {}
+ @sampler.notice_push_scope "a"
+ @sampler.notice_pop_scope "a"
+ @sampler.notice_scope_empty
- assert_equal 0, @sampler.scope_depth
- with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ assert_equal 0, @sampler.scope_depth
sample = @sampler.harvest(nil).first
assert_equal "ROOT{a}", sample.to_s_compact
end
end
def test_double_scope_stack_empty
- @sampler.notice_first_scope_push Time.now.to_f
- @sampler.notice_transaction "/path", nil, {}
- @sampler.notice_push_scope "a"
- @sampler.notice_pop_scope "a"
- @sampler.notice_scope_empty
- @sampler.notice_scope_empty
- @sampler.notice_scope_empty
- @sampler.notice_scope_empty
-
with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ @sampler.notice_first_scope_push Time.now.to_f
+ @sampler.notice_transaction "/path", nil, {}
+ @sampler.notice_push_scope "a"
+ @sampler.notice_pop_scope "a"
+ @sampler.notice_scope_empty
+ @sampler.notice_scope_empty
+ @sampler.notice_scope_empty
+ @sampler.notice_scope_empty
+
assert_not_nil @sampler.harvest(nil)[0]
end
end
@@ -909,11 +910,10 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
def test_param_capture
[true, false].each do |capture|
with_config(:capture_params => capture) do
- @sampler.notice_first_scope_push Time.now.to_f
- @sampler.notice_transaction('/path', nil, {:param => 'hi'})
- @sampler.notice_scope_empty
-
tt = with_config(:'transaction_tracer.transaction_threshold' => 0.0) do
+ @sampler.notice_first_scope_push Time.now.to_f
+ @sampler.notice_transaction('/path', nil, {:param => 'hi'})
+ @sampler.notice_scope_empty
@sampler.harvest(nil)[0]
end
@@ -936,6 +936,22 @@ class NewRelic::Agent::TransactionSamplerTest < Test::Unit::TestCase
end
end
+ def test_renaming_current_segment_midflight
+ @sampler.start_builder
+ @sampler.notice_push_scope( 'External/www.google.com/all' )
+ @sampler.rename_scope_segment( 'External/www.google.com/Net::HTTP/GET' )
+ assert_nothing_raised do
+ @sampler.notice_pop_scope( 'External/www.google.com/Net::HTTP/GET' )
+ end
+ end
+
+ def test_adding_segment_parameters
+ @sampler.start_builder
+ @sampler.notice_push_scope( 'External/www.google.com/all' )
+ @sampler.add_segment_parameters( :transaction_guid => '97612F92E6194080' )
+ assert_equal '97612F92E6194080', @sampler.builder.current_segment[:transaction_guid]
+ end
+
private
def run_sample_trace(start = Time.now.to_f, stop = nil)
diff --git a/test/new_relic/agent/worker_loop_test.rb b/test/new_relic/agent/worker_loop_test.rb
index 9acbe7b..585d5ee 100644
--- a/test/new_relic/agent/worker_loop_test.rb
+++ b/test/new_relic/agent/worker_loop_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
ENV['SKIP_RAILS'] = 'true'
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
@@ -79,6 +83,6 @@ class NewRelic::Agent::WorkerLoopTest < Test::Unit::TestCase
end
def ticks(start, finish, step)
- (start..finish).step(step).to_a
+ (start..finish).step(step).map{|i| Time.at(i)}
end
end
diff --git a/test/new_relic/agent_test.rb b/test/new_relic/agent_test.rb
index a9800b6..e1bf50b 100644
--- a/test/new_relic/agent_test.rb
+++ b/test/new_relic/agent_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
require 'ostruct'
@@ -42,7 +46,7 @@ module NewRelic
def test_finish_setup_applied_server_side_config
with_config({ :'transction_tracer.enabled' => true,
'error_collector.enabled' => true,
- :log_level => 'info' }, 2) do
+ :log_level => 'info' }, :level => 2) do
NewRelic::Agent.instance.finish_setup('log_level' => 'debug',
'agent_config' => { 'transaction_tracer.enabled' => false },
'collect_errors' => false)
@@ -58,14 +62,6 @@ module NewRelic
NewRelic::Agent.after_fork
end
- def test_after_fork_sets_forked_flag
- agent = NewRelic::Agent::Agent.new
- assert !agent.forked?
- agent.after_fork
-
- assert agent.forked?
- end
-
if NewRelic::LanguageSupport.can_fork? &&
!NewRelic::LanguageSupport.using_version?('1.9.1')
def test_timeslice_harvest_with_after_fork_report_to_channel
@@ -78,10 +74,6 @@ module NewRelic
NewRelic::Agent.instance.stats_engine.get_stats_no_scope(metric) \
.record_data_point(1.0)
- # ensure that cached metric ids don't interfere with metric merging
- NewRelic::Agent.agent.instance_variable_set(:@metric_ids,
- { NewRelic::MetricSpec.new('Instance/Busy') => 1 })
-
NewRelic::Agent::PipeChannelManager.listener.close_all_pipes
NewRelic::Agent.register_report_channel(:agent_test) # before fork
pid = Process.fork do
@@ -237,6 +229,40 @@ module NewRelic
NewRelic::Agent::PipeChannelManager.listener.close_all_pipes
end
+ def test_record_metric
+ dummy_engine = NewRelic::Agent.agent.stats_engine
+ dummy_engine.expects(:record_metrics).with('foo', 12)
+ NewRelic::Agent.record_metric('foo', 12)
+ end
+
+ def test_record_metric_accepts_hash
+ dummy_engine = NewRelic::Agent.agent.stats_engine
+ stats_hash = {
+ :count => 12,
+ :total => 42,
+ :min => 1,
+ :max => 5,
+ :sum_of_squares => 999
+ }
+ expected_stats = NewRelic::Agent::Stats.new()
+ expected_stats.call_count = 12
+ expected_stats.total_call_time = 42
+ expected_stats.total_exclusive_time = 42
+ expected_stats.min_call_time = 1
+ expected_stats.max_call_time = 5
+ expected_stats.sum_of_squares = 999
+ dummy_engine.expects(:record_metrics).with('foo', expected_stats)
+ NewRelic::Agent.record_metric('foo', stats_hash)
+ end
+
+ def test_increment_metric
+ dummy_engine = NewRelic::Agent.agent.stats_engine
+ dummy_stats = mock
+ dummy_stats.expects(:increment_count).with(12)
+ dummy_engine.expects(:record_metrics).with('foo').yields(dummy_stats)
+ NewRelic::Agent.increment_metric('foo', 12)
+ end
+
private
def mocked_agent
diff --git a/test/new_relic/coerce_test.rb b/test/new_relic/coerce_test.rb
index e93903b..42d81cc 100644
--- a/test/new_relic/coerce_test.rb
+++ b/test/new_relic/coerce_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
require 'new_relic/coerce'
diff --git a/test/new_relic/collection_helper_test.rb b/test/new_relic/collection_helper_test.rb
index 156900c..a3ab296 100644
--- a/test/new_relic/collection_helper_test.rb
+++ b/test/new_relic/collection_helper_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
require 'ostruct'
require 'active_record_fixtures' if defined?(::ActiveRecord)
diff --git a/test/new_relic/command/deployments_test.rb b/test/new_relic/command/deployments_test.rb
index 5738e43..958505b 100644
--- a/test/new_relic/command/deployments_test.rb
+++ b/test/new_relic/command/deployments_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'/../../test_helper'))
require File.expand_path(File.join(File.dirname(__FILE__),'/../../../lib/new_relic/command'))
diff --git a/test/new_relic/control/class_methods_test.rb b/test/new_relic/control/class_methods_test.rb
index 22d039c..a2039e1 100644
--- a/test/new_relic/control/class_methods_test.rb
+++ b/test/new_relic/control/class_methods_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
require 'new_relic/control/class_methods'
diff --git a/test/new_relic/control/frameworks/rails_test.rb b/test/new_relic/control/frameworks/rails_test.rb
index 77155a2..e6c7d8d 100644
--- a/test/new_relic/control/frameworks/rails_test.rb
+++ b/test/new_relic/control/frameworks/rails_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'/../../../test_helper'))
class NewRelic::Control::Frameworks::RailsTest < Test::Unit::TestCase
diff --git a/test/new_relic/control_test.rb b/test/new_relic/control_test.rb
index fa5395c..9c94a11 100644
--- a/test/new_relic/control_test.rb
+++ b/test/new_relic/control_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'/../test_helper'))
class NewRelic::ControlTest < Test::Unit::TestCase
@@ -44,9 +48,8 @@ class NewRelic::ControlTest < Test::Unit::TestCase
def test_info
NewRelic::Agent.manual_start(:dispatcher_instance_id => 'test')
- props = NewRelic::Control.instance.local_env.snapshot
if defined?(Rails)
- assert_match /jdbc|postgres|mysql|sqlite/, props.assoc('Database adapter').last, props.inspect
+ assert_match /jdbc|postgres|mysql|sqlite/, NewRelic::EnvironmentReport.new["Database adapter"]
end
end
@@ -168,7 +171,7 @@ class NewRelic::ControlTest < Test::Unit::TestCase
def test_sql_tracer_disabled_when_tt_disabled_by_server
with_config({:'slow_sql.enabled' => true,
:'transaction_tracer.enabled' => true,
- :monitor_mode => true}, 2) do
+ :monitor_mode => true}, :level => 2) do
NewRelic::Agent.instance.finish_setup('collect_traces' => false)
assert(!NewRelic::Agent::Agent.instance.sql_sampler.enabled?,
diff --git a/test/new_relic/delayed_job_injection_test.rb b/test/new_relic/delayed_job_injection_test.rb
index c144103..d0ac80f 100644
--- a/test/new_relic/delayed_job_injection_test.rb
+++ b/test/new_relic/delayed_job_injection_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
class NewRelic::DelayedJobInstrumentationTest < Test::Unit::TestCase
diff --git a/test/new_relic/dispatcher_test.rb b/test/new_relic/dispatcher_test.rb
index fd4e971..3bdca55 100644
--- a/test/new_relic/dispatcher_test.rb
+++ b/test/new_relic/dispatcher_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
# Test logic around detecting or configuring dispatcher
@@ -9,11 +13,7 @@ class DispatcherTest < Test::Unit::TestCase
end
def assert_dispatcher_reported_to_environment_report(dispatcher)
- NewRelic::Control.instance.local_env.gather_environment_info
- key, value = NewRelic::Control.instance.local_env.snapshot.detect do |(k, v)|
- k == "Dispatcher"
- end
- assert_equal dispatcher.to_s, value
+ assert_equal dispatcher.to_s, NewRelic::EnvironmentReport.new["Dispatcher"]
end
def test_detects_dispatcher_via_loaded_libraries
diff --git a/test/new_relic/environment_report_test.rb b/test/new_relic/environment_report_test.rb
new file mode 100644
index 0000000..cab0b85
--- /dev/null
+++ b/test/new_relic/environment_report_test.rb
@@ -0,0 +1,89 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
+
+require 'new_relic/environment_report'
+
+class EnvironmentReportTest < Test::Unit::TestCase
+ def setup
+ @old_logic = ::NewRelic::EnvironmentReport.registered_reporters.dup
+ @report = ::NewRelic::EnvironmentReport.new
+ end
+
+ def teardown
+ ::NewRelic::EnvironmentReport.registered_reporters = @old_logic
+ end
+
+ def test_converts_to_array
+ ::NewRelic::EnvironmentReport.report_on("something"){"awesome"}
+ data = Array(::NewRelic::EnvironmentReport.new)
+ expected = ["something", "awesome"]
+ assert data.include?(expected), "expected to find #{expected} in #{data.inspect}"
+ end
+
+ def test_register_a_value_to_report_on
+ ::NewRelic::EnvironmentReport.report_on("What time is it?") do
+ "beer-o-clock"
+ end
+ assert_equal 'beer-o-clock', ::NewRelic::EnvironmentReport.new["What time is it?"]
+ end
+
+ def test_report_on_handles_errors_gracefully
+ assert_nothing_raised do
+ ::NewRelic::EnvironmentReport.report_on("What time is it?") do
+ raise ArgumentError, "woah! something blew up"
+ end
+ assert_nil ::NewRelic::EnvironmentReport.new["What time is it?"]
+ end
+ end
+
+ def test_it_does_not_set_keys_for_nil_values
+ ::NewRelic::EnvironmentReport.report_on("What time is it?") do
+ nil
+ end
+ assert ! NewRelic::EnvironmentReport.new.data.has_key?("What time is it?")
+ end
+
+ def test_can_set_an_environment_value_directly
+ @report['My Value'] = "so awesome!!"
+ assert_equal "so awesome!!", @report['My Value']
+ end
+
+ def test_it_knows_what_gems_are_in_the_environment
+ assert(@report['Gems'].size > 5, "Expected at least 5 gems in #{@report['Gems'].inspect}")
+ rake = @report['Gems'].detect{|s| s.include? 'rake'}
+ assert_match(/^rake\([\d\.]+\)$/, rake)
+ end
+
+ def test_gathers_ruby_version
+ assert_equal RUBY_VERSION, @report['Ruby version']
+ end
+
+ def test_has_logic_for_keys
+ [
+ "Gems",
+ "Plugin List",
+ "Ruby version",
+ "Ruby description",
+ "Ruby platform",
+ "Ruby patchlevel",
+ 'JRuby version',
+ 'Java VM version',
+ 'Processors',
+ 'Database adapter',
+ 'Framework',
+ 'Dispatcher',
+ 'Environment',
+ 'Arch',
+ 'OS version',
+ 'OS',
+ 'Rails Env',
+ 'Rails version',
+ 'Rails threadsafe',
+ ].each do |key|
+ assert NewRelic::EnvironmentReport.registered_reporters.has_key?(key), "Expected logic for #{key.inspect} in EnvironmentReport."
+ end
+ end
+end
diff --git a/test/new_relic/fake_collector.rb b/test/new_relic/fake_collector.rb
index 31fea47..dde18cf 100644
--- a/test/new_relic/fake_collector.rb
+++ b/test/new_relic/fake_collector.rb
@@ -1,10 +1,13 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'rubygems'
require 'rack'
require 'uri'
require 'socket'
require 'timeout'
require 'ostruct'
-require File.join(File.dirname(__FILE__), 'fakes_sending_data')
require 'json' if RUBY_VERSION >= '1.9'
@@ -12,8 +15,6 @@ module NewRelic
class FakeCollector
attr_accessor :agent_data, :mock
- include FakesSendingData
-
def initialize
@id_counter = 0
@base_expectations = {
@@ -53,16 +54,24 @@ module NewRelic
end
run_id = uri.query =~ /run_id=(\d+)/ ? $1 : nil
req.body.rewind
-
- body = if format == :json
- body = JSON.load(req.body.read)
- else
- body = Marshal.load(req.body.read)
+
+ begin
+ raw_body = req.body.read
+ raw_body = Zlib::Inflate.inflate(raw_body) if req.env["HTTP_CONTENT_ENCODING"] == "deflate"
+
+ body = if format == :json
+ body = JSON.load(raw_body)
+ else
+ body = Marshal.load(raw_body)
+ end
+ rescue => err
+ body = "UNABLE TO DECODE BODY: #{raw_body}"
end
- @agent_data << OpenStruct.new(:action => method,
- :body => body,
- :run_id => run_id,
- :format => format)
+
+ @agent_data << AgentPost.create(:action => method,
+ :body => body,
+ :run_id => run_id,
+ :format => format)
end
res.finish
end
@@ -151,6 +160,87 @@ module NewRelic
return true
end
+
+ def calls_for(method)
+ @agent_data.select {|d| d.action == method }
+ end
+
+ def reported_stats_for_metric(name, scope=nil)
+ calls_for('metric_data').map do |post|
+ post.body[3].find do |metric_record|
+ metric_record[0]['name'] == name &&
+ (!scope || metric_record[0]['scope'] == scope)
+ end
+ end.compact.map{|m| m[1]}
+ end
+
+ class AgentPost
+ attr_accessor :action, :body, :run_id, :format
+ def initialize(opts={})
+ @action = opts[:action]
+ @body = opts[:body]
+ @run_id = opts[:run_id]
+ @format = opts[:format]
+ end
+
+ def self.create(opts={})
+ case opts[:action]
+ when 'connect'
+ ConnectPost.new(opts)
+ when 'metric_data'
+ AgentPost.new(opts)
+ when 'profile_data'
+ ProfileDataPost.new(opts)
+ when 'sql_trace_data'
+ SqlTraceDataPost.new(opts)
+ when 'transaction_sample_data'
+ TransactionSampleDataPost.new(opts)
+ else
+ new(opts)
+ end
+ end
+
+ def [](key)
+ @body[key]
+ end
+
+ def unblob(blob)
+ return unless blob
+ JSON.load(Zlib::Inflate.inflate(Base64.decode64(blob)))
+ end
+ end
+
+ class ConnectPost < AgentPost
+ def initialize(opts={})
+ super
+ @body = @body[0]
+ end
+ end
+
+ class ProfileDataPost < AgentPost
+ def initialize(opts={})
+ super
+ @body[1][0][4] = unblob(@body[1][0][4]) if @format == :json
+ end
+ end
+
+ class SqlTraceDataPost < AgentPost
+ def initialize(opts={})
+ super
+ @body[0][0][9] = unblob(@body[0][0][9]) if @format == :json
+ end
+ end
+
+ class TransactionSampleDataPost < AgentPost
+ def initialize(opts={})
+ super
+ @body[4] = unblob(@body[4]) if @format == :json
+ end
+
+ def metric_name
+ @body[1][0][2]
+ end
+ end
end
# might we need this? I'll just leave it here for now
@@ -290,7 +380,7 @@ if $0 == __FILE__
end
def invoke(method, post={}, code=200)
- uri = URI.parse("http://127.0.0.1:#{determine_port}/agent_listener/8/12345/#{method}")
+ uri = URI.parse("http://127.0.0.1:#{@collector.determine_port}/agent_listener/8/12345/#{method}")
request = Net::HTTP::Post.new("#{uri.path}?#{uri.query}")
if uri.query && uri.query.include?('marshal_format=json')
request.body = JSON.dump(post)
diff --git a/test/new_relic/fakes_sending_data.rb b/test/new_relic/fakes_sending_data.rb
deleted file mode 100644
index 0353c31..0000000
--- a/test/new_relic/fakes_sending_data.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-module NewRelic
- module FakesSendingData
- def calls_for(method)
- @agent_data. \
- select { |d| d.action == method }. \
- map { |d| d.body }
- end
-
- # Unpeel the inner layers of encoding applied by the JSON marshaller.
- # I'm sorry.
- def unpack_inner_blobs(req)
- body = req.body
- if req.format == :json
- case req.action
- when 'profile_data' then
- body[0][4] = unpack(body[0][4])
- when 'sql_trace_data' then
- body[0][0][9] = unpack(body[0][0][9])
- when 'transaction_sample_data' then
- body[4] = unpack(body[4])
- end
- end
- body
- end
-
- def unpack(blob)
- JSON.load(Zlib::Inflate.inflate(Base64.decode64(blob)))
- end
- end
-end
diff --git a/test/new_relic/framework_test.rb b/test/new_relic/framework_test.rb
index f0df150..639b310 100644
--- a/test/new_relic/framework_test.rb
+++ b/test/new_relic/framework_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
# Test logic around detecting or configuring framework
diff --git a/test/new_relic/helper_test.rb b/test/new_relic/helper_test.rb
new file mode 100644
index 0000000..0454479
--- /dev/null
+++ b/test/new_relic/helper_test.rb
@@ -0,0 +1,27 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+
+#require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
+require 'test/unit'
+require 'newrelic_rpm'
+
+class HelperTest < Test::Unit::TestCase
+
+ def test_json_serializer_method
+ obj = [
+ 99, 'luftballons',
+ {
+ 'Hast du etwas' => 'Zeit für mich',
+ 'Dann singe ich' => {
+ 'ein lied' => 'für dich'
+ }
+ }
+ ]
+ copy = NewRelic.json_load( NewRelic.json_dump(obj) )
+
+ assert( obj == copy )
+ end
+
+end
diff --git a/test/new_relic/license_test.rb b/test/new_relic/license_test.rb
new file mode 100644
index 0000000..00bae18
--- /dev/null
+++ b/test/new_relic/license_test.rb
@@ -0,0 +1,125 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
+require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
+
+# look through the source code to enforce some simple rules that help us keep
+# our license data up to date.
+class LicenseTest < Test::Unit::TestCase
+
+ # A list of regexs that will likely match license info
+ LICENSE_TERMS = {
+ 'GPL' => /GPL/i,
+ '(c)' => /\(c\)/i,
+ 'Copyright' => /copyright/i,
+ 'BSD' => /\bBSD\b/i,
+ 'MIT' => /\bMIT\b/i,
+ 'Apache' => /\bapache\b/i,
+ 'rights reserved' => /rights reserved/i,
+ }
+
+ # Known occurrences of the above license terms
+ # format is:
+ # [ file, term ] => expected_number_of_occurances
+ # unless listed here the expectation is that these terms will not occur in
+ # the source code.
+ EXPECTED_LICENSE_OCCURRENCES = {
+ ['/lib/new_relic/okjson.rb', '(c)'] => 3, # methods arguments like (c)
+ ['/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb', '(c)'] => 2, # methods arguments like (c)
+ ['/lib/new_relic/okjson.rb', 'Copyright'] => 3, # okjson license info
+ ['/lib/new_relic/timer_lib.rb', '(c)'] => 1, # timer_lib license info
+ ['/lib/new_relic/timer_lib.rb', 'Copyright'] => 1, # timer_lib license info
+ ['/LICENSE', 'GPL'] => 1, # dual license info for system_timer
+ ['/LICENSE', 'MIT'] => 3,
+ ['/LICENSE', '(c)'] => 3,
+ ['/LICENSE', 'Copyright'] => 11,
+ ['/LICENSE', 'rights reserved'] => 1,
+ ['/ui/views/layouts/newrelic_default.rhtml', 'rights reserved'] => 1,
+ ['/ui/views/newrelic/file/javascript/jquery-1.4.2.js', 'GPL'] => 3,
+ ['/ui/views/newrelic/file/javascript/jquery-1.4.2.js', 'BSD'] => 2,
+ ['/ui/views/newrelic/file/javascript/jquery-1.4.2.js', 'Copyright'] => 3,
+ ['/ui/views/newrelic/file/javascript/jquery-1.4.2.js', 'MIT'] => 3,
+ ['/test/new_relic/agent/agent_test_controller_test.rb', 'Apache'] => 1, # apache header tests
+ ['/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/solr.rb', 'Apache'] => 2, # parse apache solr metrics
+ ['/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/solr_request_handler.rb', 'Apache'] => 1, # parse apache solr metrics
+ }
+
+ def all_rb_and_js_files
+ pattern = File.expand_path(gem_root + "/**/*.{rb,js}")
+ Dir[pattern]
+ end
+
+ def all_files
+ pattern = File.expand_path(gem_root + "/**/*")
+ Dir[pattern]
+ end
+
+ def gem_root
+ File.expand_path(File.dirname(__FILE__) + "/../../")
+ end
+
+ def shebang
+ /^#!/
+ end
+
+ def encoding
+ /^# ?(en)?coding: utf-8/
+ end
+
+ def syntax_mark
+ /^# -\*- ruby -\*-/
+ end
+
+ def should_skip?(path)
+ (
+ # skip directories
+ !File.file?(path) ||
+ # skip binary files
+ %w| .sqlite3 .log .png .ico .gif .gem |.include?(File.extname(path)) ||
+ # skip this file
+ File.expand_path(__FILE__) == path ||
+ # skip rpm_test_app and other stuff that ends up in tmp
+ path.include?(gem_root + '/tmp/') ||
+ # skip the auto-generated build.rb file
+ path =~ %r{lib/new_relic/build\.rb} ||
+ # skip tags file
+ path =~ %r{/tags$}i
+ )
+ end
+
+ def test_all_rb_and_js_files_have_license_header
+ all_rb_and_js_files.each do |filename|
+ next if should_skip?(filename)
+
+ first_four_lines = File.read(filename, 1000).split("\n")[0...4]
+ if first_four_lines.first =~ shebang
+ first_four_lines.shift # discard it
+ end
+ if first_four_lines.first =~ syntax_mark
+ first_four_lines.shift # discard it
+ end
+ if first_four_lines.first =~ encoding
+ first_four_lines.shift # discard it
+ end
+
+ assert_match(/This file is distributed under .+ license terms\./, first_four_lines[0], "#{filename} does not contain the proper license header.")
+ assert_match(%r"See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.", first_four_lines[1])
+ end
+ end
+
+ def test_for_scary_license_terms
+ files_to_check = all_files.reject { |f| should_skip?(f) }
+ files_to_check.each do |filename|
+ LICENSE_TERMS.each do |key, pattern|
+ # we're checking this one. We'll update the count of checked files below.
+ occurrences = File.readlines(filename).grep(pattern).size
+ expected = (EXPECTED_LICENSE_OCCURRENCES[[filename.sub(gem_root, ''), key]] || 0)
+ assert_equal expected, occurrences, "#{filename} contains #{key} #{occurrences} times. Should be #{expected}"
+ end
+ end
+ # sanity check that we are not skipping all the files.
+ checked_files = files_to_check.size
+ assert checked_files >= 390, "Somethings off. We only scanned #{checked_files} files for license info. There should be more."
+ end
+end
diff --git a/test/new_relic/load_test.rb b/test/new_relic/load_test.rb
index 977640a..27ebb42 100644
--- a/test/new_relic/load_test.rb
+++ b/test/new_relic/load_test.rb
@@ -1,7 +1,15 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
require 'test/unit'
require 'resolv'
-require 'mocha'
+begin
+ require 'mocha/setup'
+rescue LoadError
+ require 'mocha'
+end
class LoadTest < Test::Unit::TestCase
def test_loading_agent_when_disabled_does_not_resolv_addresses
diff --git a/test/new_relic/local_environment_test.rb b/test/new_relic/local_environment_test.rb
index 87b8b9f..747bc83 100644
--- a/test/new_relic/local_environment_test.rb
+++ b/test/new_relic/local_environment_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
class NewRelic::LocalEnvironmentTest < Test::Unit::TestCase
@@ -47,33 +51,6 @@ class NewRelic::LocalEnvironmentTest < Test::Unit::TestCase
Object.send(:remove_const, :PhusionPassenger)
end
- def test_snapshot
- e = NewRelic::LocalEnvironment.new
- s = e.snapshot
- assert_equal 0, s.size
- e.gather_environment_info
- s = e.snapshot
- assert_match /1\.8\.[67]|1\.9\.|2\.0/, s.assoc('Ruby version').last, s.inspect
- assert_equal 'test', s.assoc('Framework').last, s.inspect
- # Make sure the processor count is determined on linux systems
- if File.exists? '/proc/cpuinfo'
- assert s.assoc('Processors').last.to_i > 0
- end
- end
-
- def test_gather_cpu_info_successful
- e = NewRelic::LocalEnvironment.new
- e.gather_cpu_info(File.expand_path(File.join(File.dirname(__FILE__),'..', 'fixtures', 'proc_cpuinfo.txt')))
- s = e.snapshot
- assert_equal 24, s.assoc('Processors').last.to_i
- end
-
- def test_gather_cpu_info_failure
- e = NewRelic::LocalEnvironment.new
- e.gather_cpu_info(File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper.rb')))
- s = e.snapshot
- assert_equal 1, s.assoc('Processors').last.to_i
- end
def test_default_port
e = NewRelic::LocalEnvironment.new
diff --git a/test/new_relic/metric_data_test.rb b/test/new_relic/metric_data_test.rb
index a6b96e8..e730efa 100644
--- a/test/new_relic/metric_data_test.rb
+++ b/test/new_relic/metric_data_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
require 'new_relic/metric_data'
class NewRelic::MetricDataTest < Test::Unit::TestCase
@@ -101,29 +105,29 @@ class NewRelic::MetricDataTest < Test::Unit::TestCase
end
def test_to_json_no_metric_id
- md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', ''), NewRelic::MethodTraceStats.new, nil)
+ md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', ''), NewRelic::Agent::Stats.new, nil)
json = md.to_json
assert(json.include?('"Custom/test/method"'), "should include the metric spec in the json")
assert(json.include?('"metric_id":null}'), "should have a null metric_id")
end
def test_to_json_with_metric_id
- md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', ''), NewRelic::MethodTraceStats.new, 12345)
+ md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', ''), NewRelic::Agent::Stats.new, 12345)
assert_equal('{"metric_spec":null,"stats":{"total_exclusive_time":0.0,"min_call_time":0.0,"call_count":0,"sum_of_squares":0.0,"total_call_time":0.0,"max_call_time":0.0},"metric_id":12345}', md.to_json, "should not include the metric spec and should have a metric_id")
end
def test_to_s_with_metric_spec
- md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', ''), NewRelic::MethodTraceStats.new, 12345)
- assert_equal('Custom/test/method(): [01/01/70 12:00AM UTC, 0.000s; 0 calls 0s]', md.to_s, "should not include the metric id and should include the metric spec")
+ md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', ''), NewRelic::Agent::Stats.new, 12345)
+ assert_equal('Custom/test/method(): [ 0 calls 0.0000s]', md.to_s, "should not include the metric id and should include the metric spec")
end
def test_to_s_without_metric_spec
- md = NewRelic::MetricData.new(nil, NewRelic::MethodTraceStats.new, 12345)
- assert_equal('12345: [01/01/70 12:00AM UTC, 0.000s; 0 calls 0s]', md.to_s, "should include the metric id and not have a metric spec")
+ md = NewRelic::MetricData.new(nil, NewRelic::Agent::Stats.new, 12345)
+ assert_equal('12345: [ 0 calls 0.0000s]', md.to_s, "should include the metric id and not have a metric spec")
end
def test_to_collector_array_with_spec
- stats = NewRelic::MethodTraceStats.new
+ stats = NewRelic::Agent::Stats.new
stats.record_data_point(1.0)
stats.record_data_point(2.0, 1.0)
md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', 'scope'),
@@ -134,7 +138,7 @@ class NewRelic::MetricDataTest < Test::Unit::TestCase
end
def test_to_collector_array_with_spec_and_id
- stats = NewRelic::MethodTraceStats.new
+ stats = NewRelic::Agent::Stats.new
stats.record_data_point(1.0)
stats.record_data_point(2.0, 1.0)
md = NewRelic::MetricData.new(NewRelic::MetricSpec.new('Custom/test/method', 'scope'),
@@ -144,7 +148,7 @@ class NewRelic::MetricDataTest < Test::Unit::TestCase
end
def test_to_collector_array_with_id
- stats = NewRelic::MethodTraceStats.new
+ stats = NewRelic::Agent::Stats.new
stats.record_data_point(1.0)
stats.record_data_point(2.0, 1.0)
md = NewRelic::MetricData.new(nil, stats, 1234)
@@ -154,7 +158,7 @@ class NewRelic::MetricDataTest < Test::Unit::TestCase
# Rationals in metric data? -- https://support.newrelic.com/tickets/28053
def test_to_collector_array_with_rationals
- stats = NewRelic::MethodTraceStats.new
+ stats = NewRelic::Agent::Stats.new
stats.call_count = Rational(1, 1)
stats.total_call_time = Rational(2, 1)
stats.total_exclusive_time = Rational(3, 1)
@@ -168,7 +172,7 @@ class NewRelic::MetricDataTest < Test::Unit::TestCase
end
def test_to_collector_array_with_bad_values
- stats = NewRelic::MethodTraceStats.new
+ stats = NewRelic::Agent::Stats.new
stats.call_count = nil
stats.total_call_time = "junk"
stats.total_exclusive_time = Object.new
diff --git a/test/new_relic/metric_parser/metric_parser_test.rb b/test/new_relic/metric_parser/metric_parser_test.rb
index 1b921e3..5537949 100644
--- a/test/new_relic/metric_parser/metric_parser_test.rb
+++ b/test/new_relic/metric_parser/metric_parser_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..', 'test_helper'))
class NewRelic::MetricParser::MetricParserTest < Test::Unit::TestCase
class ::AnApplicationClass
diff --git a/test/new_relic/metric_spec_test.rb b/test/new_relic/metric_spec_test.rb
index 730d5c0..f92ce52 100644
--- a/test/new_relic/metric_spec_test.rb
+++ b/test/new_relic/metric_spec_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
class NewRelic::MetricSpecTest < Test::Unit::TestCase
@@ -46,7 +50,7 @@ class NewRelic::MetricSpecTest < Test::Unit::TestCase
compare_spec(spec, import)
- stats = NewRelic::MethodTraceStats.new
+ stats = NewRelic::Agent::Stats.new
import = ::ActiveSupport::JSON.decode(stats.to_json)
diff --git a/test/new_relic/noticed_error_test.rb b/test/new_relic/noticed_error_test.rb
index 6b69069..ad1c4b5 100644
--- a/test/new_relic/noticed_error_test.rb
+++ b/test/new_relic/noticed_error_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','test_helper'))
class NewRelic::Agent::NoticedErrorTest < Test::Unit::TestCase
diff --git a/test/new_relic/rack/agent_hooks_test.rb b/test/new_relic/rack/agent_hooks_test.rb
index 2fe858a..ddcf4db 100644
--- a/test/new_relic/rack/agent_hooks_test.rb
+++ b/test/new_relic/rack/agent_hooks_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/rack/agent_hooks'
diff --git a/test/new_relic/rack/all_test.rb b/test/new_relic/rack/all_test.rb
index 5b75241..e4ea79b 100644
--- a/test/new_relic/rack/all_test.rb
+++ b/test/new_relic/rack/all_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','test_helper'))
require 'new_relic/rack/browser_monitoring'
require 'new_relic/rack/developer_mode'
diff --git a/test/new_relic/rack/browser_monitoring_test.rb b/test/new_relic/rack/browser_monitoring_test.rb
index 5c032ef..3f2050f 100644
--- a/test/new_relic/rack/browser_monitoring_test.rb
+++ b/test/new_relic/rack/browser_monitoring_test.rb
@@ -1,6 +1,11 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..',
'test_helper'))
require 'rack/test'
+require 'new_relic/agent/instrumentation/rack'
require 'new_relic/rack/browser_monitoring'
ENV['RACK_ENV'] = 'test'
diff --git a/test/new_relic/rack/developer_mode_helper_test.rb b/test/new_relic/rack/developer_mode_helper_test.rb
index f42177b..53b1729 100644
--- a/test/new_relic/rack/developer_mode_helper_test.rb
+++ b/test/new_relic/rack/developer_mode_helper_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# ENV['SKIP_RAILS'] = 'true'
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..',
'test_helper'))
diff --git a/test/new_relic/rack/developer_mode_test.rb b/test/new_relic/rack/developer_mode_test.rb
index 5c338c0..f1226b9 100644
--- a/test/new_relic/rack/developer_mode_test.rb
+++ b/test/new_relic/rack/developer_mode_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
# ENV['SKIP_RAILS'] = 'true'
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..',
'test_helper'))
diff --git a/test/new_relic/rack/error_collector_test.rb b/test/new_relic/rack/error_collector_test.rb
index f2e70e6..d433fa3 100644
--- a/test/new_relic/rack/error_collector_test.rb
+++ b/test/new_relic/rack/error_collector_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..',
'test_helper'))
require 'rack/test'
diff --git a/test/new_relic/stats_test.rb b/test/new_relic/stats_test.rb
deleted file mode 100644
index e1aa4ed..0000000
--- a/test/new_relic/stats_test.rb
+++ /dev/null
@@ -1,421 +0,0 @@
-ENV['SKIP_RAILS'] = 'true'
-require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
-##require "new_relic/stats"
-
-module NewRelic; class TestObjectForStats
- include Stats
- attr_accessor :total_call_time
- attr_accessor :total_exclusive_time
- attr_accessor :begin_time
- attr_accessor :end_time
- attr_accessor :call_count
-end; end
-
-
-class NewRelic::StatsTest < Test::Unit::TestCase
-
- def test_checked_calculation_standard
- obj = NewRelic::TestObjectForStats.new
-
- assert_equal(1.0, obj.checked_calculation(1, 1))
-
- end
-
- def test_checked_calculation_with_zero
- obj = NewRelic::TestObjectForStats.new
- assert_equal(0.0, obj.checked_calculation(1, 0))
- end
-
- def test_checked_calculation_should_return_floats
- obj = NewRelic::TestObjectForStats.new
- assert_equal(0.5, obj.checked_calculation(1, 2))
- end
-
- def test_sum_attributes
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
- first.expects(:call_count).returns(6)
- second.expects(:call_count).returns(8)
- first.expects(:update_totals).with(second)
- first.expects(:stack_min_max_from).with(second)
- first.expects(:update_boundaries).with(second)
- first.expects(:call_count=).with(8)
- first.sum_attributes(second)
- end
-
- def mock_plusequals(first, second, method, first_value, second_value)
- first.expects(method).returns(first_value)
- second.expects(method).returns(second_value)
- first.expects("#{method}=".to_sym).with(first_value + second_value)
- end
-
- def test_stack_min_max_from
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
- mock_plusequals(first, second, :min_call_time, 1.5, 0.5)
- mock_plusequals(first, second, :max_call_time, 1.0, 3.0)
-
- first.stack_min_max_from(second)
- end
-
- def test_update_boundaries
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
-
- first.expects(:should_replace_begin_time?).with(second).returns(true)
- first.expects(:should_replace_end_time?).with(second).returns(true)
- first.expects(:begin_time=).with('other_begin_time')
- first.expects(:end_time=).with('other_end_time')
-
- second.expects(:begin_time).returns('other_begin_time')
- second.expects(:end_time).returns('other_end_time')
- first.update_boundaries(second)
- end
-
- def test_should_replace_end_time
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
-
- first.expects(:end_time).returns(Time.at(1))
- second.expects(:end_time).returns(Time.at(2))
- assert first.should_replace_end_time?(second), 'should replace end time when the other stat is larger'
- end
-
- def test_should_replace_begin_time_base
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
-
- first.expects(:begin_time).returns(Time.at(2))
- second.expects(:begin_time).returns(Time.at(1))
-
- assert first.should_replace_begin_time?(second), 'should replace the begin time when it is larger'
- end
-
- def test_should_replace_begin_time_empty
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
-
- first.expects(:begin_time).returns(Time.at(0))
- second.expects(:begin_time).returns(Time.at(2))
-
- first.expects(:begin_time).returns(Time.at(0))
-
- assert first.should_replace_begin_time?(second), "should replace the begin time if self.call_count == 0"
- end
-
- def test_update_totals
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
-
- [:total_call_time, :total_exclusive_time, :sum_of_squares].each do |method|
- mock_plusequals(first, second, method, 2.0, 3.0)
- end
-
- first.update_totals(second)
- end
-
- def test_min_time_less
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
-
- second.expects(:min_call_time).returns(1.0)
- first.expects(:min_call_time).returns(2.0)
- second.expects(:call_count).returns(1)
-
- first.min_time_less?(second)
- end
-
- def test_expand_min_max_to
- first = NewRelic::TestObjectForStats.new
- second = mock('other object')
-
- first.expects(:min_time_less?).with(second).returns(true)
- first.expects(:max_call_time).returns(3.0)
-
- second.expects(:min_call_time).returns(1.0)
- second.expects(:max_call_time).returns(4.0).twice
-
- first.expects(:min_call_time=).with(1.0)
- first.expects(:max_call_time=).with(4.0)
-
- first.expand_min_max_to(second)
- end
-
-
-
- def test_simple
- stats = NewRelic::MethodTraceStats.new
- validate stats, 0, 0, 0, 0
-
- assert_equal stats.call_count,0
- stats.trace_call 10
- stats.trace_call 20
- stats.trace_call 30
-
- validate stats, 3, (10+20+30), 10, 30
- end
-
- def test_to_s
- s1 = NewRelic::MethodTraceStats.new
- s1.trace_call 10
- assert_equal("[01/01/70 12:00AM UTC, 0.000s; 1 calls 10s]", s1.to_s)
- end
-
- def test_time_str
- s1 = NewRelic::MethodTraceStats.new
- assert_equal(s1.time_str(10), "10 ms")
- assert_equal(s1.time_str(4999), "4999 ms")
- assert_equal(s1.time_str(5000), "5.00 s")
- assert_equal(s1.time_str(5010), "5.01 s")
- assert_equal(s1.time_str(9999), "10.00 s")
- assert_equal(s1.time_str(10000), "10.0 s")
- assert_equal(s1.time_str(20000), "20.0 s")
- end
-
- def test_multiply_by
- s1 = NewRelic::MethodTraceStats.new
- s1.trace_call 10
- assert_equal("[01/01/70 12:00AM UTC, 0.000s; 10 calls 10s]", s1.multiply_by(10).to_s)
- end
-
- def test_get_apdex
- s1 = NewRelic::MethodTraceStats.new
- s1.trace_call 10
- assert_equal(s1.get_apdex, [1, 10, 10])
- end
-
- def test_apdex_score
- s1 = NewRelic::MethodTraceStats.new
- s1.trace_call 10
- assert_in_delta(s1.apdex_score, 0.285714285714286, 0.0000001)
- end
-
- def test_as_percentage
- s1 = NewRelic::MethodTraceStats.new
- s1.trace_call 10
- assert_equal(s1.as_percentage, 1000.0)
- end
-
- def test_calls_per_minute
-
- s1 = NewRelic::TestObjectForStats.new
- s1.call_count = 1
- s1.begin_time = Time.at(0)
- s1.end_time = Time.at(30)
- assert_equal(s1.calls_per_minute, 2)
- end
-
- def test_total_call_time_per_minute
- s1 = NewRelic::TestObjectForStats.new
- s1.begin_time = Time.at(0)
- s1.end_time = Time.at(0)
- assert_equal(0, s1.total_call_time_per_minute)
- s1.begin_time = Time.at(0)
- s1.end_time = Time.at(30)
- s1.total_call_time = 10
- assert_equal(20, s1.total_call_time_per_minute)
- end
-
- def test_time_percentage
- s1 = NewRelic::TestObjectForStats.new
- s1.begin_time = Time.at(0)
- s1.end_time = Time.at(0)
- assert_equal(0, s1.time_percentage)
- s1.total_call_time = 10
- s1.begin_time = Time.at(0)
- s1.end_time = Time.at(30)
- assert_equal((1.0 / 3.0), s1.time_percentage)
- s1.total_call_time = 20
- assert_equal((2.0 / 3.0), s1.time_percentage)
- end
-
- def test_exclusive_time_percentage
- s1 = NewRelic::TestObjectForStats.new
- s1.begin_time = Time.at(0)
- s1.end_time = Time.at(0)
- assert_equal(0, s1.exclusive_time_percentage)
- s1.total_exclusive_time = 10
- s1.begin_time = Time.at(0)
- s1.end_time = Time.at(30)
- assert_equal((1.0 / 3.0), s1.exclusive_time_percentage)
- s1.total_exclusive_time = 20
- assert_equal((2.0 / 3.0), s1.exclusive_time_percentage)
- end
-
- def test_sum_merge
- s1 = NewRelic::MethodTraceStats.new
- s2 = NewRelic::MethodTraceStats.new
- s1.trace_call 10
- s2.trace_call 20
- s2.freeze
-
- validate s1, 1, 10, 10, 10
- validate s2, 1, 20, 20, 20
- s1.sum_merge! s2
- validate s1, 1, (10+20), 10 + 20, 20 + 10
- validate s2, 1, 20, 20, 20
- end
-
- def test_sum_merge_with_exclusive
- s1 = NewRelic::MethodTraceStats.new
- s2 = NewRelic::MethodTraceStats.new
-
- s1.trace_call 10, 5
- s2.trace_call 20, 10
- s2.freeze
-
- validate s1, 1, 10, 10, 10, 5
- validate s2, 1, 20, 20, 20, 10
- s1.sum_merge! s2
- validate s1, 1, (10+20), 10 + 20, 20 + 10, (10+5)
- end
-
- def test_merge
- s1 = NewRelic::MethodTraceStats.new
- s2 = NewRelic::MethodTraceStats.new
-
- s1.trace_call 10
- s2.trace_call 20
- s2.freeze
-
- validate s2, 1, 20, 20, 20
- s3 = s1.merge s2
- validate s3, 2, (10+20), 10, 20
- validate s1, 1, 10, 10, 10
- validate s2, 1, 20, 20, 20
-
- s1.merge! s2
- validate s1, 2, (10+20), 10, 20
- validate s2, 1, 20, 20, 20
- end
-
- def test_merge_with_exclusive
- s1 = NewRelic::MethodTraceStats.new
-
- s2 = NewRelic::MethodTraceStats.new
-
- s1.trace_call 10, 5
- s2.trace_call 20, 10
- s2.freeze
-
- validate s2, 1, 20, 20, 20, 10
- s3 = s1.merge s2
- validate s3, 2, (10+20), 10, 20, (10+5)
- validate s1, 1, 10, 10, 10, 5
- validate s2, 1, 20, 20, 20, 10
-
- s1.merge! s2
- validate s1, 2, (10+20), 10, 20, (5+10)
- validate s2, 1, 20, 20, 20, 10
- end
-
- def test_merge_array
- s1 = NewRelic::MethodTraceStats.new
- merges = []
- merges << (NewRelic::MethodTraceStats.new.trace_call 1)
- merges << (NewRelic::MethodTraceStats.new.trace_call 1)
- merges << (NewRelic::MethodTraceStats.new.trace_call 1)
-
- s1.merge! merges
- validate s1, 3, 3, 1, 1
- end
-
- def test_freeze
- s1 = NewRelic::MethodTraceStats.new
-
- s1.trace_call 10
- s1.freeze
-
- begin
- # the following should throw an exception because s1 is frozen
- s1.trace_call 20
- assert false
- rescue StandardError
- assert s1.frozen?
- validate s1, 1, 10, 10, 10
- end
- end
-
- def test_std_dev
- s = NewRelic::MethodTraceStats.new
- s.trace_call 1
- assert s.standard_deviation == 0
-
- s = NewRelic::MethodTraceStats.new
- s.trace_call 10
- s.trace_call 10
- s.sum_of_squares = nil
- assert s.standard_deviation == 0
-
- s = NewRelic::MethodTraceStats.new
- s.trace_call 0.001
- s.trace_call 0.001
- assert s.standard_deviation == 0
-
-
- s = NewRelic::MethodTraceStats.new
- s.trace_call 10
- s.trace_call 10
- s.trace_call 10
- s.trace_call 10
- s.trace_call 10
- s.trace_call 10
- assert s.standard_deviation == 0
-
- s = NewRelic::MethodTraceStats.new
- s.trace_call 4
- s.trace_call 7
- s.trace_call 13
- s.trace_call 16
- s.trace_call 8
- s.trace_call 4
- assert_equal(s.sum_of_squares, 4**2 + 7**2 + 13**2 + 16**2 + 8**2 + 4**2)
-
- s.trace_call 9
- s.trace_call 3
- s.trace_call 1000
- s.trace_call 4
-
- # calculated stdev (population, not sample) from a spreadsheet.
- assert_in_delta(s.standard_deviation, 297.76, 0.01)
- end
-
- def test_std_dev_merge
- s1 = NewRelic::MethodTraceStats.new
- s1.trace_call 4
- s1.trace_call 7
-
- s2 = NewRelic::MethodTraceStats.new
- s2.trace_call 13
- s2.trace_call 16
-
- s3 = s1.merge(s2)
-
- assert_equal(s1.sum_of_squares, 4*4 + 7*7)
- assert_in_delta(s1.standard_deviation, 1.5, 0.01)
-
- assert_in_delta(s2.standard_deviation, 1.5, 0.01)
- assert_equal(s3.sum_of_squares, 4*4 + 7*7 + 13*13 + 16*16, "check sum of squares")
- assert_in_delta(s3.standard_deviation, 4.743, 0.01)
- end
-
- if RUBY_VERSION >= '1.9'
- def test_to_json_enforces_float_values
- s1 = NewRelic::MethodTraceStats.new
- s1.trace_call 3.to_r
- s1.trace_call 7.to_r
-
- assert_equal 3.0, JSON.load(s1.to_json)['min_call_time']
- end
- end
-
- private
- def validate (stats, count, total, min, max, exclusive = nil)
- assert_equal stats.call_count, count
- assert_equal stats.total_call_time, total
- assert_equal stats.average_call_time, (count > 0 ? total / count : 0)
- assert_equal stats.min_call_time, min
- assert_equal stats.max_call_time, max
- assert_equal stats.total_exclusive_time, exclusive if exclusive
- end
-end
diff --git a/test/new_relic/transaction_analysis/segment_summary_test.rb b/test/new_relic/transaction_analysis/segment_summary_test.rb
index adae366..9e17162 100644
--- a/test/new_relic/transaction_analysis/segment_summary_test.rb
+++ b/test/new_relic/transaction_analysis/segment_summary_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', '..','test_helper'))
require 'new_relic/transaction_analysis/segment_summary'
class NewRelic::TransactionAnalysis::SegmentSummaryTest < Test::Unit::TestCase
diff --git a/test/new_relic/transaction_analysis_test.rb b/test/new_relic/transaction_analysis_test.rb
index 151487b..8bd41f5 100644
--- a/test/new_relic/transaction_analysis_test.rb
+++ b/test/new_relic/transaction_analysis_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
require 'new_relic/transaction_analysis'
class NewRelic::TransactionAnalysisTest < Test::Unit::TestCase
diff --git a/test/new_relic/transaction_sample/composite_segment_test.rb b/test/new_relic/transaction_sample/composite_segment_test.rb
index 94640b8..35ce3c6 100644
--- a/test/new_relic/transaction_sample/composite_segment_test.rb
+++ b/test/new_relic/transaction_sample/composite_segment_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
require 'new_relic/transaction_sample/composite_segment'
class NewRelic::TransactionSample::CompositeSegmentTest < Test::Unit::TestCase
diff --git a/test/new_relic/transaction_sample/fake_segment_test.rb b/test/new_relic/transaction_sample/fake_segment_test.rb
index 542ef1f..02039d5 100644
--- a/test/new_relic/transaction_sample/fake_segment_test.rb
+++ b/test/new_relic/transaction_sample/fake_segment_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
require 'new_relic/transaction_sample/fake_segment'
class NewRelic::TransactionSample::FakeSegmentTest < Test::Unit::TestCase
diff --git a/test/new_relic/transaction_sample/segment_test.rb b/test/new_relic/transaction_sample/segment_test.rb
index 38ce1d5..b2dbff4 100644
--- a/test/new_relic/transaction_sample/segment_test.rb
+++ b/test/new_relic/transaction_sample/segment_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
require 'new_relic/transaction_sample/segment'
class NewRelic::TransactionSample::SegmentTest < Test::Unit::TestCase
diff --git a/test/new_relic/transaction_sample/summary_segment_test.rb b/test/new_relic/transaction_sample/summary_segment_test.rb
index f8785f3..d7271c4 100644
--- a/test/new_relic/transaction_sample/summary_segment_test.rb
+++ b/test/new_relic/transaction_sample/summary_segment_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'test_helper'))
require 'new_relic/transaction_sample/summary_segment'
class NewRelic::TransactionSample::SummarySegmentTest < Test::Unit::TestCase
diff --git a/test/new_relic/transaction_sample_subtest_test.rb b/test/new_relic/transaction_sample_subtest_test.rb
index 56dcc4e..dad700e 100644
--- a/test/new_relic/transaction_sample_subtest_test.rb
+++ b/test/new_relic/transaction_sample_subtest_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
class NewRelic::TransactionSample::SubTest < Test::Unit::TestCase
def setup
@t = NewRelic::TransactionSample.new
diff --git a/test/new_relic/transaction_sample_test.rb b/test/new_relic/transaction_sample_test.rb
index c1908d1..0b3467f 100644
--- a/test/new_relic/transaction_sample_test.rb
+++ b/test/new_relic/transaction_sample_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path('../../test_helper.rb', __FILE__)
class NewRelic::TransactionSampleTest < Test::Unit::TestCase
diff --git a/test/new_relic/version_number_test.rb b/test/new_relic/version_number_test.rb
index 03ae01f..28269ce 100644
--- a/test/new_relic/version_number_test.rb
+++ b/test/new_relic/version_number_test.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require File.expand_path(File.join(File.dirname(__FILE__),'..', 'test_helper'))
class NewRelic::VersionNumberTest < Test::Unit::TestCase
diff --git a/test/script/ci.sh b/test/script/ci.sh
index 104845d..3eaaaf8 100755
--- a/test/script/ci.sh
+++ b/test/script/ci.sh
@@ -6,16 +6,16 @@
#
# It relies on 2 environment variables:
#
-# RUBY - The rbenv ruby you want to use (e.g. 1.8.7, ree, jruby)
+# RUBY_VERSION - The rbenv ruby you want to use (e.g. 1.8.7, ree, jruby)
#
# BRANCH - The rpm_test_app branch you want to use (e.g. rails20, rails31)
#
# Example usage:
-# RUBY=ree BRANCH=rails20 test/script/ci.sh
+# RUBY_VERSION=ree BRANCH=rails20 test/script/ci.sh
#
-# RUBY=ree BRANCH=rails20 test/script/ci.sh
+# RUBY_VERSION=ree BRANCH=rails20 test/script/ci.sh
#
-# RUBY=jruby BRANCH=rails22 test/script/ci.sh
+# RUBY_VERSION=jruby BRANCH=rails22 test/script/ci.sh
echo "Executing $0"
echo "Running in $(pwd)"
@@ -26,10 +26,10 @@ echo "Running in $(pwd)"
set -e
# check for require environment variables
-if [ "x$RUBY" == "x" ]; then
- echo '$RUBY is undefined'
+if [ "x$RUBY_VERSION" == "x" ]; then
+ echo '$RUBY_VERSION is undefined'
echo 'defaulting to 1.9.3'
- export RUBY=1.9.3-p286
+ export RUBY_VERSION=1.9.3-p374
fi
if [ "x$BRANCH" == "x" ]; then
echo '$BRANCH is undefined'
@@ -42,22 +42,22 @@ if [ "x$JOB_NAME" == "x" ]; then
echo 'defaulting to clrun'
export PROJECT_NAME=clrun
else
- CLEANSED_NAME=`echo $JOB_NAME | sed "s/label//" | sed "s/Portland//" | sed "s/BRANCH//" | sed "s/RUBY//" | sed "s/[=\/,\._]//g" | sed "s/ReleaseCandidate/RC/"`
+ CLEANSED_NAME=`echo $JOB_NAME | sed "s/label//" | sed "s/Portland//" | sed "s/BRANCH//" | sed "s/RUBY_VERSION//" | sed "s/[=\/,\._]//g" | sed "s/ReleaseCandidate/RC/"`
echo "setting PROJECT_NAME to $CLEANSED_NAME"
export PROJECT_NAME="$CLEANSED_NAME"
fi
eval "$(rbenv init -)" || true
-rbenv shell $RUBY
-if [ "x$(rbenv version-name)" = "x$RUBY" ]; then
- echo "switched to ruby $RUBY"
+rbenv shell $RUBY_VERSION
+if [ "x$(rbenv version-name)" = "x$RUBY_VERSION" ]; then
+ echo "switched to ruby $RUBY_VERSION"
else
- rbenv install $RUBY
- rbenv shell $RUBY
- if [ "x$(rbenv version-name)" = "x$RUBY" ]; then
- echo "switched to ruby $RUBY"
+ rbenv install $RUBY_VERSION
+ rbenv shell $RUBY_VERSION
+ if [ "x$(rbenv version-name)" = "x$RUBY_VERSION" ]; then
+ echo "switched to ruby $RUBY_VERSION"
else
- echo "failed to install ruby $RUBY"
+ echo "failed to install ruby $RUBY_VERSION"
exit 1
fi
fi
@@ -93,7 +93,7 @@ cd rpm_test_app
git fetch || true
git checkout -t origin/$BRANCH || git checkout $BRANCH
if [ -x $HOME/.rbenv/plugins/rbenv-gemsets ]; then
- echo "$RUBY-$BRANCH" > .rbenv-gemsets
+ echo "$RUBY_VERSION-$BRANCH" > .rbenv-gemsets
fi
# Re-write database.yml to this here doc
@@ -144,13 +144,14 @@ else
perl -p -i'.bak' -e 's#gem .newrelic_rpm.*$#gem "newrelic_rpm", :path => "\.\.\/\.\.\/"#' Gemfile
fi
-if [ "x$RUBY" == "x1.8.6" ]; then
+if [ "x$RUBY_VERSION" == "x1.8.6" ]; then
# Bundler 1.1 dropped support for ruby 1.8.6
bundle -h > /dev/null || gem install bundler -v'~>1.0.0' --no-rdoc --no-ri
else
bundle -h > /dev/null || gem install bundler --no-rdoc --no-ri
fi
+bundle -v
export RAILS_ENV=test
bundle --local || bundle
diff --git a/test/test_contexts.rb b/test/test_contexts.rb
index 1e49e1b..eb94b6e 100644
--- a/test/test_contexts.rb
+++ b/test/test_contexts.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module TestContexts
def with_running_agent
@@ -10,7 +14,6 @@ module TestContexts
NewRelic::Agent::Agent.instance.service = default_service
NewRelic::Agent.manual_start :log => @log
@agent = NewRelic::Agent.instance
- @agent.metric_ids.clear
@agent.transaction_sampler.send :clear_builder
@agent.transaction_sampler.reset!
@agent.stats_engine.clear_stats
diff --git a/test/test_helper.rb b/test/test_helper.rb
index 7b85ebf..86306a7 100644
--- a/test/test_helper.rb
+++ b/test/test_helper.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
module NewRelic; TEST = true; end unless defined? NewRelic::TEST
ENV['RAILS_ENV'] = 'test'
NEWRELIC_PLUGIN_DIR = File.expand_path(File.join(File.dirname(__FILE__),".."))
@@ -17,10 +21,18 @@ require 'rake'
begin
require 'config/environment'
# require File.join(File.dirname(__FILE__),'..','..','rpm_test_app','config','environment')
+
+ # we need 'rails/test_help' for Rails 4
+ # we need 'test_help' for Rails 2
+ # we need neither for Rails 3
begin
- require 'test_help'
+ require 'rails/test_help'
rescue LoadError
- # ignore load problems on test help - it doesn't exist in rails 3
+ begin
+ require 'test_help'
+ rescue LoadError
+ # ignore load problems on test help - it doesn't exist in rails 3
+ end
end
require 'newrelic_rpm'
rescue LoadError => e
@@ -44,7 +56,11 @@ end
require 'test/unit'
require 'shoulda'
-require 'mocha'
+begin
+ require 'mocha/setup'
+rescue LoadError
+ require 'mocha'
+end
begin # 1.8.6
require 'mocha/integration/test_unit'
@@ -140,15 +156,33 @@ def assert_calls_unscoped_metrics(*metrics)
assert_not_equal first_metrics, last_metrics, "should have changed these metrics"
end
+unless defined?( assert_includes )
+ def assert_includes( collection, member, msg=nil )
+ msg = build_message( msg, "Expected ? to include ?", collection, member )
+ assert_block( msg ) { collection.include?(member) }
+ end
+end
+
+unless defined?( assert_not_includes )
+ def assert_not_includes( collection, member, msg=nil )
+ msg = build_message( msg, "Expected ? not to include ?", collection, member )
+ assert_block( msg ) { !collection.include?(member) }
+ end
+end
def compare_metrics(expected, actual)
actual.delete_if {|a| a.include?('GC/cumulative') } # in case we are in REE
assert_equal(expected.to_a.sort, actual.to_a.sort, "extra: #{(actual - expected).to_a.inspect}; missing: #{(expected - actual).to_a.inspect}")
end
-def with_config(config_hash, level=0)
- config = NewRelic::Agent::Configuration::DottedHash.new(config_hash)
- NewRelic::Agent.config.apply_config(config, level)
+def with_config(config_hash, opts={})
+ opts = { :level => 0, :do_not_cast => false }.merge(opts)
+ if opts[:do_not_cast]
+ config = config_hash
+ else
+ config = NewRelic::Agent::Configuration::DottedHash.new(config_hash)
+ end
+ NewRelic::Agent.config.apply_config(config, opts[:level])
begin
yield
ensure
diff --git a/ui/helpers/developer_mode_helper.rb b/ui/helpers/developer_mode_helper.rb
index 7a3cf5c..46a7df0 100644
--- a/ui/helpers/developer_mode_helper.rb
+++ b/ui/helpers/developer_mode_helper.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'pathname'
require File.expand_path('../google_pie_chart', __FILE__)
require 'new_relic/collection_helper'
diff --git a/ui/helpers/google_pie_chart.rb b/ui/helpers/google_pie_chart.rb
index 7ac1607..d7e6243 100644
--- a/ui/helpers/google_pie_chart.rb
+++ b/ui/helpers/google_pie_chart.rb
@@ -1,3 +1,7 @@
+# encoding: utf-8
+# This file is distributed under New Relic's license terms.
+# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
require 'cgi'
# A wrapper around the google charts service.
diff --git a/ui/views/newrelic/file/javascript/jquery-1.4.2.js b/ui/views/newrelic/file/javascript/jquery-1.4.2.js
index fff6776..386c83e 100644
--- a/ui/views/newrelic/file/javascript/jquery-1.4.2.js
+++ b/ui/views/newrelic/file/javascript/jquery-1.4.2.js
@@ -1,3 +1,6 @@
+// This file is distributed under jquery's license terms.
+// See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
/*!
* jQuery JavaScript Library v1.4.2
* http://jquery.com/
diff --git a/ui/views/newrelic/file/javascript/transaction_sample.js b/ui/views/newrelic/file/javascript/transaction_sample.js
index 7456725..b9a4dfb 100644
--- a/ui/views/newrelic/file/javascript/transaction_sample.js
+++ b/ui/views/newrelic/file/javascript/transaction_sample.js
@@ -1,3 +1,6 @@
+// This file is distributed under New Relic's license terms.
+// See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
+
function show_request_params()
{
$('#params_link').hide();
diff --git a/ui/views/newrelic/index.rhtml b/ui/views/newrelic/index.rhtml
index c7c56d2..0b12bb9 100644
--- a/ui/views/newrelic/index.rhtml
+++ b/ui/views/newrelic/index.rhtml
@@ -21,10 +21,10 @@
</table>
</td>
<td valign=top>
- <% if defined?(Unicorn) || defined?(Passenger) %>
+ <% if defined?(Rainbows) || defined?(Unicorn) || defined?(Passenger) %>
<div class="flash">
<p id="errorExplanation">
- We've noticed you're running a forking web server, like Unicorn or Passenger. See Below</p><p> Developer mode may be unpredictable,
+ We've noticed you're running a forking web server, like Rainbows, Unicorn, or Passenger. See Below</p><p> Developer mode may be unpredictable,
since those servers run many processes and developer mode is entirely in the process.</p><p> We suggest you try Thin,
Mongrel, or another single-process web server for development mode.</p><p>
(You can ignore this message if you're sure that you're running only one process, and it's not being reaped when inactive.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment