appoptics_apm 4.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.codeclimate.yml +43 -0
- data/.dockerignore +5 -0
- data/.gitignore +23 -0
- data/.rubocop.yml +5 -0
- data/.travis.yml +82 -0
- data/CHANGELOG.md +769 -0
- data/CONFIG.md +33 -0
- data/Dockerfile +41 -0
- data/Dockerfile_test +66 -0
- data/Gemfile +41 -0
- data/LICENSE +193 -0
- data/README.md +351 -0
- data/Rakefile +202 -0
- data/Vagrantfile +67 -0
- data/appoptics_apm.gemspec +55 -0
- data/build_gems.sh +15 -0
- data/docker-compose.yml +73 -0
- data/examples/DNT.md +35 -0
- data/examples/carrying_context.rb +220 -0
- data/examples/instrumenting_metal_controller.rb +8 -0
- data/examples/puma_on_heroku_config.rb +17 -0
- data/examples/tracing_async_threads.rb +124 -0
- data/examples/tracing_background_jobs.rb +53 -0
- data/examples/tracing_forked_processes.rb +99 -0
- data/examples/unicorn_on_heroku_config.rb +28 -0
- data/ext/oboe_metal/extconf.rb +54 -0
- data/ext/oboe_metal/lib/.keep +0 -0
- data/ext/oboe_metal/lib/liboboe-1.0.so.0.0.0 +0 -0
- data/ext/oboe_metal/noop/noop.c +7 -0
- data/ext/oboe_metal/src/VERSION +1 -0
- data/ext/oboe_metal/src/bson/bson.h +221 -0
- data/ext/oboe_metal/src/bson/platform_hacks.h +91 -0
- data/ext/oboe_metal/src/oboe.h +883 -0
- data/ext/oboe_metal/src/oboe.hpp +793 -0
- data/ext/oboe_metal/src/oboe_debug.h +50 -0
- data/ext/oboe_metal/src/oboe_wrap.cxx +6088 -0
- data/ext/oboe_metal/tests/test.rb +11 -0
- data/gemfiles/delayed_job.gemfile +36 -0
- data/gemfiles/frameworks.gemfile +44 -0
- data/gemfiles/instrumentation_mocked.gemfile +29 -0
- data/gemfiles/libraries.gemfile +85 -0
- data/gemfiles/rails23.gemfile +39 -0
- data/gemfiles/rails30.gemfile +42 -0
- data/gemfiles/rails31.gemfile +44 -0
- data/gemfiles/rails32.gemfile +54 -0
- data/gemfiles/rails40.gemfile +27 -0
- data/gemfiles/rails41.gemfile +27 -0
- data/gemfiles/rails42.gemfile +35 -0
- data/gemfiles/rails50.gemfile +44 -0
- data/gemfiles/rails51.gemfile +44 -0
- data/get_version.rb +5 -0
- data/init.rb +4 -0
- data/lib/appoptics_apm/api/layerinit.rb +39 -0
- data/lib/appoptics_apm/api/logging.rb +359 -0
- data/lib/appoptics_apm/api/memcache.rb +34 -0
- data/lib/appoptics_apm/api/profiling.rb +201 -0
- data/lib/appoptics_apm/api/tracing.rb +152 -0
- data/lib/appoptics_apm/api/util.rb +128 -0
- data/lib/appoptics_apm/api.rb +18 -0
- data/lib/appoptics_apm/base.rb +252 -0
- data/lib/appoptics_apm/config.rb +281 -0
- data/lib/appoptics_apm/frameworks/grape.rb +93 -0
- data/lib/appoptics_apm/frameworks/padrino/templates.rb +58 -0
- data/lib/appoptics_apm/frameworks/padrino.rb +52 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller.rb +106 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller2.rb +61 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller3.rb +58 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller4.rb +48 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller5.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_controller_api.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_view.rb +58 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_view_2x.rb +56 -0
- data/lib/appoptics_apm/frameworks/rails/inst/action_view_30.rb +50 -0
- data/lib/appoptics_apm/frameworks/rails/inst/active_record.rb +27 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql.rb +43 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/mysql2.rb +28 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/postgresql.rb +30 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils.rb +120 -0
- data/lib/appoptics_apm/frameworks/rails/inst/connection_adapters/utils5x.rb +101 -0
- data/lib/appoptics_apm/frameworks/rails.rb +116 -0
- data/lib/appoptics_apm/frameworks/sinatra/templates.rb +56 -0
- data/lib/appoptics_apm/frameworks/sinatra.rb +71 -0
- data/lib/appoptics_apm/inst/bunny-client.rb +148 -0
- data/lib/appoptics_apm/inst/bunny-consumer.rb +92 -0
- data/lib/appoptics_apm/inst/curb.rb +329 -0
- data/lib/appoptics_apm/inst/dalli.rb +85 -0
- data/lib/appoptics_apm/inst/delayed_job.rb +92 -0
- data/lib/appoptics_apm/inst/em-http-request.rb +105 -0
- data/lib/appoptics_apm/inst/excon.rb +130 -0
- data/lib/appoptics_apm/inst/faraday.rb +77 -0
- data/lib/appoptics_apm/inst/http.rb +83 -0
- data/lib/appoptics_apm/inst/httpclient.rb +176 -0
- data/lib/appoptics_apm/inst/memcache.rb +102 -0
- data/lib/appoptics_apm/inst/memcached.rb +94 -0
- data/lib/appoptics_apm/inst/mongo.rb +242 -0
- data/lib/appoptics_apm/inst/mongo2.rb +225 -0
- data/lib/appoptics_apm/inst/moped.rb +466 -0
- data/lib/appoptics_apm/inst/rack.rb +146 -0
- data/lib/appoptics_apm/inst/redis.rb +275 -0
- data/lib/appoptics_apm/inst/resque.rb +151 -0
- data/lib/appoptics_apm/inst/rest-client.rb +50 -0
- data/lib/appoptics_apm/inst/sequel.rb +178 -0
- data/lib/appoptics_apm/inst/sidekiq-client.rb +53 -0
- data/lib/appoptics_apm/inst/sidekiq-worker.rb +67 -0
- data/lib/appoptics_apm/inst/twitter-cassandra.rb +294 -0
- data/lib/appoptics_apm/inst/typhoeus.rb +113 -0
- data/lib/appoptics_apm/instrumentation.rb +22 -0
- data/lib/appoptics_apm/legacy_method_profiling.rb +97 -0
- data/lib/appoptics_apm/loading.rb +66 -0
- data/lib/appoptics_apm/logger.rb +41 -0
- data/lib/appoptics_apm/method_profiling.rb +33 -0
- data/lib/appoptics_apm/ruby.rb +35 -0
- data/lib/appoptics_apm/support.rb +135 -0
- data/lib/appoptics_apm/test.rb +94 -0
- data/lib/appoptics_apm/thread_local.rb +26 -0
- data/lib/appoptics_apm/util.rb +312 -0
- data/lib/appoptics_apm/version.rb +15 -0
- data/lib/appoptics_apm/xtrace.rb +103 -0
- data/lib/appoptics_apm.rb +72 -0
- data/lib/joboe_metal.rb +214 -0
- data/lib/oboe/README +2 -0
- data/lib/oboe/backward_compatibility.rb +80 -0
- data/lib/oboe/inst/rack.rb +11 -0
- data/lib/oboe.rb +7 -0
- data/lib/oboe_metal.rb +187 -0
- data/lib/rails/generators/appoptics_apm/install_generator.rb +45 -0
- data/lib/rails/generators/appoptics_apm/templates/appoptics_apm_initializer.rb +222 -0
- data/ruby_setup.sh +47 -0
- data/run_docker_build_gem_upload_to_packagecloud.sh +20 -0
- data/run_tests_docker.rb +32 -0
- data/test/benchmark/README.md +65 -0
- data/test/benchmark/logging_bench.rb +54 -0
- data/test/benchmark/with_libraries_gemfile/bunny_bench.rb +69 -0
- data/test/benchmark/with_rails5x_gemfile/action_controller5x_bench.rb +43 -0
- data/test/frameworks/apps/grape_nested.rb +33 -0
- data/test/frameworks/apps/grape_simple.rb +80 -0
- data/test/frameworks/apps/padrino_simple.rb +80 -0
- data/test/frameworks/apps/sinatra_simple.rb +55 -0
- data/test/frameworks/grape_test.rb +286 -0
- data/test/frameworks/padrino_test.rb +222 -0
- data/test/frameworks/rails3x_test.rb +554 -0
- data/test/frameworks/rails4x_test.rb +570 -0
- data/test/frameworks/rails5x_api_test.rb +210 -0
- data/test/frameworks/rails5x_test.rb +376 -0
- data/test/frameworks/rails_shared_tests.rb +172 -0
- data/test/frameworks/sinatra_test.rb +140 -0
- data/test/instrumentation/bunny_client_test.rb +276 -0
- data/test/instrumentation/bunny_consumer_test.rb +204 -0
- data/test/instrumentation/curb_test.rb +398 -0
- data/test/instrumentation/dalli_test.rb +177 -0
- data/test/instrumentation/em_http_request_test.rb +89 -0
- data/test/instrumentation/excon_test.rb +231 -0
- data/test/instrumentation/faraday_test.rb +228 -0
- data/test/instrumentation/http_test.rb +143 -0
- data/test/instrumentation/httpclient_test.rb +320 -0
- data/test/instrumentation/memcache_test.rb +260 -0
- data/test/instrumentation/memcached_test.rb +229 -0
- data/test/instrumentation/mongo_v1_test.rb +479 -0
- data/test/instrumentation/mongo_v2_index_test.rb +124 -0
- data/test/instrumentation/mongo_v2_test.rb +584 -0
- data/test/instrumentation/mongo_v2_view_test.rb +435 -0
- data/test/instrumentation/moped_test.rb +517 -0
- data/test/instrumentation/rack_test.rb +165 -0
- data/test/instrumentation/redis_hashes_test.rb +268 -0
- data/test/instrumentation/redis_keys_test.rb +321 -0
- data/test/instrumentation/redis_lists_test.rb +310 -0
- data/test/instrumentation/redis_misc_test.rb +163 -0
- data/test/instrumentation/redis_sets_test.rb +296 -0
- data/test/instrumentation/redis_sortedsets_test.rb +328 -0
- data/test/instrumentation/redis_strings_test.rb +349 -0
- data/test/instrumentation/resque_test.rb +185 -0
- data/test/instrumentation/rest-client_test.rb +288 -0
- data/test/instrumentation/sequel_mysql2_test.rb +353 -0
- data/test/instrumentation/sequel_mysql_test.rb +334 -0
- data/test/instrumentation/sequel_pg_test.rb +336 -0
- data/test/instrumentation/sidekiq-client_test.rb +159 -0
- data/test/instrumentation/sidekiq-worker_test.rb +180 -0
- data/test/instrumentation/twitter-cassandra_test.rb +424 -0
- data/test/instrumentation/typhoeus_test.rb +284 -0
- data/test/jobs/delayed_job/db_worker_job.rb +29 -0
- data/test/jobs/delayed_job/error_worker_job.rb +10 -0
- data/test/jobs/delayed_job/remote_call_worker_job.rb +20 -0
- data/test/jobs/resque/db_worker_job.rb +29 -0
- data/test/jobs/resque/error_worker_job.rb +10 -0
- data/test/jobs/resque/remote_call_worker_job.rb +20 -0
- data/test/jobs/sidekiq/db_worker_job.rb +29 -0
- data/test/jobs/sidekiq/error_worker_job.rb +10 -0
- data/test/jobs/sidekiq/remote_call_worker_job.rb +20 -0
- data/test/minitest_helper.rb +276 -0
- data/test/mocked/curb_mocked_test.rb +311 -0
- data/test/mocked/excon_mocked_test.rb +166 -0
- data/test/mocked/faraday_mocked_test.rb +93 -0
- data/test/mocked/http_mocked_test.rb +129 -0
- data/test/mocked/httpclient_mocked_test.rb +245 -0
- data/test/mocked/rest_client_mocked_test.rb +103 -0
- data/test/mocked/typhoeus_mocked_test.rb +192 -0
- data/test/models/widget.rb +36 -0
- data/test/profiling/legacy_method_profiling_test.rb +201 -0
- data/test/profiling/method_profiling_test.rb +631 -0
- data/test/queues/delayed_job-client_test.rb +95 -0
- data/test/queues/delayed_job-worker_test.rb +91 -0
- data/test/reporter/reporter_test.rb +14 -0
- data/test/servers/delayed_job.rb +107 -0
- data/test/servers/rackapp_8101.rb +29 -0
- data/test/servers/rails3x_8140.rb +96 -0
- data/test/servers/rails4x_8140.rb +96 -0
- data/test/servers/rails5x_8140.rb +95 -0
- data/test/servers/rails5x_api_8150.rb +78 -0
- data/test/servers/sidekiq.rb +29 -0
- data/test/servers/sidekiq.yml +7 -0
- data/test/servers/sidekiq_initializer.rb +25 -0
- data/test/settings +0 -0
- data/test/support/auto_tracing_test.rb +50 -0
- data/test/support/backcompat_test.rb +276 -0
- data/test/support/config_test.rb +149 -0
- data/test/support/dnt_test.rb +98 -0
- data/test/support/init_report_test.rb +25 -0
- data/test/support/liboboe_settings_test.rb +110 -0
- data/test/support/logging_test.rb +130 -0
- data/test/support/noop_test.rb +88 -0
- data/test/support/sql_sanitize_test.rb +55 -0
- data/test/support/tracing_mode_test.rb +33 -0
- data/test/support/tvalias_test.rb +15 -0
- data/test/support/xtrace_test.rb +41 -0
- metadata +475 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
3
|
+
# All rights reserved.
|
|
4
|
+
|
|
5
|
+
# This is a Rails API stack that launches on a background
|
|
6
|
+
# thread and listens on port 8150.
|
|
7
|
+
#
|
|
8
|
+
if ENV['DBTYPE'] == 'mysql2'
|
|
9
|
+
AppOpticsAPM::Test.set_mysql2_env
|
|
10
|
+
elsif ENV['DBTYPE'] == 'postgresql'
|
|
11
|
+
AppOpticsAPM::Test.set_postgresql_env
|
|
12
|
+
else
|
|
13
|
+
AppOpticsAPM.logger.warn "Unidentified DBTYPE: #{ENV['DBTYPE']}" unless ENV['DBTYPE'] == "postgresql"
|
|
14
|
+
AppOpticsAPM.logger.debug "Defaulting to postgres DB for background Rails server."
|
|
15
|
+
AppOpticsAPM::Test.set_postgresql_env
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
require "rails"
|
|
19
|
+
require "active_model/railtie"
|
|
20
|
+
require "active_job/railtie"
|
|
21
|
+
require "active_record/railtie"
|
|
22
|
+
require "action_controller/railtie"
|
|
23
|
+
require "action_mailer/railtie"
|
|
24
|
+
require "action_view/railtie"
|
|
25
|
+
require "action_cable/engine"
|
|
26
|
+
require "rails/test_unit/railtie"
|
|
27
|
+
|
|
28
|
+
require 'rack/handler/puma'
|
|
29
|
+
require File.expand_path(File.dirname(__FILE__) + '/../models/widget')
|
|
30
|
+
|
|
31
|
+
AppOpticsAPM.logger.info "[appoptics_apm/info] Starting background utility rails app on localhost:8150."
|
|
32
|
+
|
|
33
|
+
ActiveRecord::Base.establish_connection(ENV['DATABASE_URL'])
|
|
34
|
+
|
|
35
|
+
unless ActiveRecord::Base.connection.table_exists? 'widgets'
|
|
36
|
+
ActiveRecord::Migration.run(CreateWidgets)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module Rails50APIStack
|
|
40
|
+
class Application < Rails::Application
|
|
41
|
+
config.api_only = true
|
|
42
|
+
|
|
43
|
+
routes.append do
|
|
44
|
+
get "/monkey/hello" => "monkey#hello"
|
|
45
|
+
get "/monkey/error" => "monkey#error"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
config.cache_classes = true
|
|
49
|
+
config.eager_load = false
|
|
50
|
+
config.active_support.deprecation = :stderr
|
|
51
|
+
config.middleware.delete Rack::Lock
|
|
52
|
+
config.middleware.delete ActionDispatch::Flash
|
|
53
|
+
config.secret_token = "48837489qkuweoiuoqwehisuakshdjksadhaisdy78o34y138974xyqp9rmye8yrpiokeuioqwzyoiuxftoyqiuxrhm3iou1hrzmjk"
|
|
54
|
+
config.secret_key_base = "2049671-96803948"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
#################################################
|
|
59
|
+
# Controllers
|
|
60
|
+
#################################################
|
|
61
|
+
|
|
62
|
+
class MonkeyController < ActionController::API
|
|
63
|
+
def hello
|
|
64
|
+
render :plain => {:Response => "Hello API!"}.to_json, content_type: 'application/json'
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def error
|
|
68
|
+
raise "Rails API fake error from controller"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
Rails50APIStack::Application.initialize!
|
|
73
|
+
|
|
74
|
+
Thread.new do
|
|
75
|
+
Rack::Handler::Puma.run(Rails50APIStack::Application.to_app, {:Host => '127.0.0.1', :Port => 8150})
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
sleep(2)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
# We configure and launch Sidekiq in a background
|
|
5
|
+
# thread here.
|
|
6
|
+
#
|
|
7
|
+
require 'sidekiq/cli'
|
|
8
|
+
|
|
9
|
+
AppOpticsAPM.logger.info "[appoptics_apm/servers] Starting up background Sidekiq."
|
|
10
|
+
|
|
11
|
+
options = []
|
|
12
|
+
arguments = ""
|
|
13
|
+
options << ["-r", Dir.pwd + "/test/servers/sidekiq_initializer.rb"]
|
|
14
|
+
options << ["-q", "critical,20", "-q", "default"]
|
|
15
|
+
options << ["-c", "10"]
|
|
16
|
+
options << ["-P", "/tmp/sidekiq_#{Process.pid}.pid"]
|
|
17
|
+
|
|
18
|
+
options.flatten.each do |x|
|
|
19
|
+
arguments += " #{x}"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
AppOpticsAPM.logger.debug "[appoptics_apm/servers] sidekiq #{arguments}"
|
|
23
|
+
|
|
24
|
+
Thread.new do
|
|
25
|
+
system("OBOE_GEM_TEST=true sidekiq #{arguments}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Allow Sidekiq to boot up
|
|
29
|
+
sleep 10
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
# This file is used to initialize the background Sidekiq
|
|
5
|
+
# process launched in our test suite.
|
|
6
|
+
#
|
|
7
|
+
ENV['BUNDLE_GEMFILE'] = Dir.pwd + "/gemfiles/libraries.gemfile"
|
|
8
|
+
|
|
9
|
+
require 'rubygems'
|
|
10
|
+
require 'bundler/setup'
|
|
11
|
+
require_relative '../jobs/sidekiq/db_worker_job'
|
|
12
|
+
require_relative '../jobs/sidekiq/remote_call_worker_job'
|
|
13
|
+
require_relative '../jobs/sidekiq/error_worker_job'
|
|
14
|
+
|
|
15
|
+
ENV["RACK_ENV"] = "test"
|
|
16
|
+
ENV["APPOPTICS_GEM_TEST"] = "true"
|
|
17
|
+
ENV["APPOPTICS_GEM_VERBOSE"] = "true"
|
|
18
|
+
|
|
19
|
+
Bundler.require(:default, :test)
|
|
20
|
+
|
|
21
|
+
# Configure AppOpticsAPM
|
|
22
|
+
AppOpticsAPM::Config[:tracing_mode] = "always"
|
|
23
|
+
AppOpticsAPM::Config[:sample_rate] = 1000000
|
|
24
|
+
AppOpticsAPM.logger.level = Logger::DEBUG
|
|
25
|
+
|
data/test/settings
ADDED
|
Binary file
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
require 'minitest_helper'
|
|
5
|
+
|
|
6
|
+
class AutoTraceTest < Minitest::Test
|
|
7
|
+
def setup
|
|
8
|
+
@tm = AppOpticsAPM::Config[:tracing_mode]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def teardown
|
|
12
|
+
AppOpticsAPM::Config[:tracing_mode] = @tm
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def test_trace_when_default_tm_dj
|
|
16
|
+
AppOpticsAPM::API.start_trace('delayed_job-worker') do
|
|
17
|
+
AppOpticsAPM.tracing?.must_equal true
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def test_trace_when_default_tm_sidekiq
|
|
22
|
+
AppOpticsAPM::API.start_trace('sidekiq-worker') do
|
|
23
|
+
AppOpticsAPM.tracing?.must_equal true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def test_trace_when_default_tm_resque
|
|
28
|
+
AppOpticsAPM::API.start_trace('resque-worker') do
|
|
29
|
+
AppOpticsAPM.tracing?.must_equal true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def test_trace_when_default_tm_rabbitmq
|
|
34
|
+
AppOpticsAPM::API.start_trace('rabbitmq-consumer') do
|
|
35
|
+
AppOpticsAPM.tracing?.must_equal true
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def test_dont_trace_when_never
|
|
40
|
+
AppOpticsAPM::Config[:tracing_mode] = :never
|
|
41
|
+
|
|
42
|
+
AppOpticsAPM::API.start_trace('delayed_job-worker') do
|
|
43
|
+
AppOpticsAPM.tracing?.must_equal false
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
AppOpticsAPM::API.start_trace('asdf') do
|
|
47
|
+
AppOpticsAPM.tracing?.must_equal false
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
require 'minitest_helper'
|
|
5
|
+
|
|
6
|
+
describe "BackwardCompatibility" do
|
|
7
|
+
|
|
8
|
+
it 'should still export to Oboe::Ruby' do
|
|
9
|
+
defined?(::Oboe::Ruby).must_equal "constant"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'should still respond to Oboe::Config' do
|
|
13
|
+
@verbose = Oboe::Config[:verbose]
|
|
14
|
+
@dalli_enabled = Oboe::Config[:dalli][:enabled]
|
|
15
|
+
@tm = Oboe::Config[:tracing_mode]
|
|
16
|
+
@sr = Oboe::Config[:sample_rate]
|
|
17
|
+
|
|
18
|
+
Oboe::Config[:verbose] = true
|
|
19
|
+
Oboe::Config[:verbose].must_equal true
|
|
20
|
+
Oboe::Config.verbose.must_equal true
|
|
21
|
+
AppOpticsAPM::Config[:verbose].must_equal true
|
|
22
|
+
AppOpticsAPM::Config.verbose.must_equal true
|
|
23
|
+
|
|
24
|
+
Oboe::Config[:dalli][:enabled] = false
|
|
25
|
+
Oboe::Config[:dalli][:enabled].must_equal false
|
|
26
|
+
AppOpticsAPM::Config[:dalli][:enabled].must_equal false
|
|
27
|
+
|
|
28
|
+
Oboe::Config[:sample_rate] = 8e5
|
|
29
|
+
Oboe::Config.sample_rate.must_equal 8e5
|
|
30
|
+
AppOpticsAPM::Config.sample_rate.must_equal 8e5
|
|
31
|
+
|
|
32
|
+
Oboe::Config[:tracing_mode] = 'always'
|
|
33
|
+
Oboe::Config.tracing_mode.must_equal :always
|
|
34
|
+
AppOpticsAPM::Config.tracing_mode.must_equal :always
|
|
35
|
+
|
|
36
|
+
Oboe::Config[:sample_rate] = @sr
|
|
37
|
+
Oboe::Config[:tracing_mode] = @tm
|
|
38
|
+
Oboe::Config[:dalli][:enabled] = @dalli_enabled
|
|
39
|
+
Oboe::Config[:verbose] = @verbose
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'should still support Oboe::API.log calls' do
|
|
43
|
+
clear_all_traces
|
|
44
|
+
|
|
45
|
+
Oboe::API.log_start('rack', nil, {})
|
|
46
|
+
Oboe::API.log_end('rack')
|
|
47
|
+
|
|
48
|
+
traces = get_all_traces
|
|
49
|
+
traces.count.must_equal 2
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'should still support Oboe::API.trace calls' do
|
|
53
|
+
clear_all_traces
|
|
54
|
+
|
|
55
|
+
Oboe::API.start_trace('api_test', '', {}) do
|
|
56
|
+
sleep 1
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
traces = get_all_traces
|
|
60
|
+
traces.count.must_equal 2
|
|
61
|
+
|
|
62
|
+
validate_outer_layers(traces, 'api_test')
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'should still support Oboe::API.profile'do
|
|
66
|
+
clear_all_traces
|
|
67
|
+
|
|
68
|
+
Oboe::API.start_trace('outer_profile_test') do
|
|
69
|
+
Oboe::API.profile('profile_test', {}, false) do
|
|
70
|
+
sleep 1
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
traces = get_all_traces
|
|
75
|
+
traces.count.must_equal 4
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Pasted in from test/profiling/method_test.rb
|
|
79
|
+
# Modified to use OboeMethodProfiling
|
|
80
|
+
describe "OboeMethodProfiling" do
|
|
81
|
+
before do
|
|
82
|
+
clear_all_traces
|
|
83
|
+
# Conditionally Undefine TestWorker
|
|
84
|
+
# http://stackoverflow.com/questions/11503558/how-to-undefine-class-in-ruby
|
|
85
|
+
Object.send(:remove_const, :TestWorker) if defined?(TestWorker)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it 'should be loaded, defined and ready' do
|
|
89
|
+
defined?(::OboeMethodProfiling).wont_match nil
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'should trace Class methods' do
|
|
93
|
+
class TestWorker
|
|
94
|
+
def self.do_work
|
|
95
|
+
sleep 1
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
class << self
|
|
99
|
+
include OboeMethodProfiling
|
|
100
|
+
profile_method :do_work, 'do_work'
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
::Oboe::API.start_trace('method_profiling', '', {}) do
|
|
105
|
+
# Call the profiled class method
|
|
106
|
+
TestWorker.do_work
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
traces = get_all_traces
|
|
110
|
+
traces.count.must_equal 4
|
|
111
|
+
|
|
112
|
+
validate_outer_layers(traces, 'method_profiling')
|
|
113
|
+
|
|
114
|
+
kvs = {}
|
|
115
|
+
kvs["Label"] = 'profile_entry'
|
|
116
|
+
kvs["Language"] = "ruby"
|
|
117
|
+
kvs["ProfileName"] = "do_work"
|
|
118
|
+
kvs["FunctionName"] = "do_work"
|
|
119
|
+
kvs["Class"] = "TestWorker"
|
|
120
|
+
|
|
121
|
+
validate_event_keys(traces[1], kvs)
|
|
122
|
+
|
|
123
|
+
traces[1].has_key?("Layer").must_equal false
|
|
124
|
+
traces[1].has_key?("File").must_equal true
|
|
125
|
+
traces[1].has_key?("LineNumber").must_equal true
|
|
126
|
+
|
|
127
|
+
kvs.clear
|
|
128
|
+
kvs["Label"] = "profile_exit"
|
|
129
|
+
kvs["Language"] = "ruby"
|
|
130
|
+
kvs["ProfileName"] = "do_work"
|
|
131
|
+
|
|
132
|
+
validate_event_keys(traces[2], kvs)
|
|
133
|
+
traces[2].has_key?("Layer").must_equal false
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
it 'should trace Instance methods' do
|
|
137
|
+
class TestWorker
|
|
138
|
+
def do_work
|
|
139
|
+
sleep 1
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
include OboeMethodProfiling
|
|
143
|
+
profile_method :do_work, 'do_work'
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
::Oboe::API.start_trace('method_profiling', '', {}) do
|
|
147
|
+
# Call the profiled class method
|
|
148
|
+
tw = TestWorker.new
|
|
149
|
+
tw.do_work
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
traces = get_all_traces
|
|
153
|
+
traces.count.must_equal 4
|
|
154
|
+
|
|
155
|
+
validate_outer_layers(traces, 'method_profiling')
|
|
156
|
+
|
|
157
|
+
kvs = {}
|
|
158
|
+
kvs["Label"] = 'profile_entry'
|
|
159
|
+
kvs["Language"] = "ruby"
|
|
160
|
+
kvs["ProfileName"] = "do_work"
|
|
161
|
+
kvs["FunctionName"] = "do_work"
|
|
162
|
+
kvs["Class"] = "TestWorker"
|
|
163
|
+
|
|
164
|
+
validate_event_keys(traces[1], kvs)
|
|
165
|
+
|
|
166
|
+
traces[1].has_key?("Layer").must_equal false
|
|
167
|
+
traces[1].has_key?("File").must_equal true
|
|
168
|
+
traces[1].has_key?("LineNumber").must_equal true
|
|
169
|
+
|
|
170
|
+
kvs.clear
|
|
171
|
+
kvs["Label"] = "profile_exit"
|
|
172
|
+
kvs["Language"] = "ruby"
|
|
173
|
+
kvs["ProfileName"] = "do_work"
|
|
174
|
+
|
|
175
|
+
validate_event_keys(traces[2], kvs)
|
|
176
|
+
traces[2].has_key?("Layer").must_equal false
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'should trace Module class methods' do
|
|
180
|
+
module TestWorker
|
|
181
|
+
def self.do_work
|
|
182
|
+
sleep 1
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
class << self
|
|
186
|
+
include OboeMethodProfiling
|
|
187
|
+
profile_method :do_work, 'do_work'
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
::Oboe::API.start_trace('method_profiling', '', {}) do
|
|
192
|
+
# Call the profiled class method
|
|
193
|
+
TestWorker.do_work
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
traces = get_all_traces
|
|
197
|
+
traces.count.must_equal 4
|
|
198
|
+
validate_outer_layers(traces, 'method_profiling')
|
|
199
|
+
|
|
200
|
+
kvs = {}
|
|
201
|
+
kvs["Label"] = 'profile_entry'
|
|
202
|
+
kvs["Language"] = "ruby"
|
|
203
|
+
kvs["ProfileName"] = "do_work"
|
|
204
|
+
kvs["FunctionName"] = "do_work"
|
|
205
|
+
kvs["Module"] = "TestWorker"
|
|
206
|
+
|
|
207
|
+
validate_event_keys(traces[1], kvs)
|
|
208
|
+
|
|
209
|
+
traces[1].has_key?("Layer").must_equal false
|
|
210
|
+
traces[1].has_key?("File").must_equal true
|
|
211
|
+
traces[1].has_key?("LineNumber").must_equal true
|
|
212
|
+
|
|
213
|
+
kvs.clear
|
|
214
|
+
kvs["Label"] = "profile_exit"
|
|
215
|
+
kvs["Language"] = "ruby"
|
|
216
|
+
kvs["ProfileName"] = "do_work"
|
|
217
|
+
|
|
218
|
+
validate_event_keys(traces[2], kvs)
|
|
219
|
+
traces[2].has_key?("Layer").must_equal false
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
it 'should not store arguments and return value by default' do
|
|
223
|
+
class TestWorker
|
|
224
|
+
def self.do_work(_s, _i, _a, _h)
|
|
225
|
+
sleep 1
|
|
226
|
+
return "the zebra is loose"
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
class << self
|
|
230
|
+
include OboeMethodProfiling
|
|
231
|
+
# Default call method
|
|
232
|
+
profile_method :do_work, 'do_work'
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
::Oboe::API.start_trace('method_profiling', '', {}) do
|
|
237
|
+
# Call the profiled class method
|
|
238
|
+
TestWorker.do_work('String Argument', 203984, ["1", "2", 3], { :color => :black })
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
traces = get_all_traces
|
|
242
|
+
traces.count.must_equal 4
|
|
243
|
+
|
|
244
|
+
traces[1].has_key?("Args").must_equal false
|
|
245
|
+
traces[2].has_key?("ReturnValue").must_equal false
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
it 'should store arguments and return value when asked' do
|
|
249
|
+
class TestWorker
|
|
250
|
+
def self.do_work(_s, _i, _a, _h)
|
|
251
|
+
sleep 1
|
|
252
|
+
return "the zebra is loose"
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
class << self
|
|
256
|
+
include OboeMethodProfiling
|
|
257
|
+
profile_method :do_work, 'do_work', true, true
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
::Oboe::API.start_trace('method_profiling', '', {}) do
|
|
262
|
+
# Call the profiled class method
|
|
263
|
+
TestWorker.do_work('String Argument', 203984, ["1", "2", 3], { :color => :black })
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
traces = get_all_traces
|
|
267
|
+
traces.count.must_equal 4
|
|
268
|
+
|
|
269
|
+
traces[1].has_key?("Args").must_equal true
|
|
270
|
+
traces[1]["Args"].must_equal "\"String Argument\"\n203984\n[\"1\", \"2\", 3]\n{:color=>:black}\n"
|
|
271
|
+
|
|
272
|
+
traces[2].has_key?("ReturnValue").must_equal true
|
|
273
|
+
traces[2]["ReturnValue"].must_equal "\"the zebra is loose\"\n"
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Copyright (c) 2016 SolarWinds, LLC.
|
|
2
|
+
# All rights reserved.
|
|
3
|
+
|
|
4
|
+
require 'minitest_helper'
|
|
5
|
+
|
|
6
|
+
describe "AppOpticsAPM::Config" do
|
|
7
|
+
after do
|
|
8
|
+
# Set back to always trace mode
|
|
9
|
+
AppOpticsAPM::Config[:tracing_mode] = "always"
|
|
10
|
+
AppOpticsAPM::Config[:sample_rate] = 1000000
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
it 'should have the correct default values' do
|
|
14
|
+
# Reset AppOpticsAPM::Config to defaults
|
|
15
|
+
AppOpticsAPM::Config.initialize
|
|
16
|
+
|
|
17
|
+
# FIXME: We set the APPOPTICS_GEM_VERBOSE env for the
|
|
18
|
+
# ____ test suite so this assertion is not going to fly
|
|
19
|
+
#
|
|
20
|
+
# AppOpticsAPM::Config[:verbose].must_equal false
|
|
21
|
+
|
|
22
|
+
# TODO: Is there anything we should test here?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'should have the correct instrumentation defaults' do
|
|
26
|
+
# Reset AppOpticsAPM::Config to defaults
|
|
27
|
+
AppOpticsAPM::Config.initialize
|
|
28
|
+
|
|
29
|
+
instrumentation = AppOpticsAPM::Config.instrumentation
|
|
30
|
+
|
|
31
|
+
# Verify the number of individual instrumentations
|
|
32
|
+
instrumentation.count.must_equal 30
|
|
33
|
+
|
|
34
|
+
AppOpticsAPM::Config[:action_controller][:enabled].must_equal true
|
|
35
|
+
AppOpticsAPM::Config[:action_controller_api][:enabled].must_equal true
|
|
36
|
+
AppOpticsAPM::Config[:action_view][:enabled].must_equal true
|
|
37
|
+
AppOpticsAPM::Config[:active_record][:enabled].must_equal true
|
|
38
|
+
AppOpticsAPM::Config[:bunnyclient][:enabled].must_equal true
|
|
39
|
+
AppOpticsAPM::Config[:bunnyconsumer][:enabled].must_equal true
|
|
40
|
+
AppOpticsAPM::Config[:cassandra][:enabled].must_equal true
|
|
41
|
+
AppOpticsAPM::Config[:curb][:enabled].must_equal true
|
|
42
|
+
AppOpticsAPM::Config[:dalli][:enabled].must_equal true
|
|
43
|
+
AppOpticsAPM::Config[:delayed_jobclient][:enabled].must_equal true
|
|
44
|
+
AppOpticsAPM::Config[:delayed_jobworker][:enabled].must_equal true
|
|
45
|
+
AppOpticsAPM::Config[:em_http_request][:enabled].must_equal false
|
|
46
|
+
AppOpticsAPM::Config[:excon][:enabled].must_equal true
|
|
47
|
+
AppOpticsAPM::Config[:faraday][:enabled].must_equal true
|
|
48
|
+
AppOpticsAPM::Config[:grape][:enabled].must_equal true
|
|
49
|
+
AppOpticsAPM::Config[:httpclient][:enabled].must_equal true
|
|
50
|
+
AppOpticsAPM::Config[:nethttp][:enabled].must_equal true
|
|
51
|
+
AppOpticsAPM::Config[:memcached][:enabled].must_equal true
|
|
52
|
+
AppOpticsAPM::Config[:memcache][:enabled].must_equal true
|
|
53
|
+
AppOpticsAPM::Config[:mongo][:enabled].must_equal true
|
|
54
|
+
AppOpticsAPM::Config[:moped][:enabled].must_equal true
|
|
55
|
+
AppOpticsAPM::Config[:rack][:enabled].must_equal true
|
|
56
|
+
AppOpticsAPM::Config[:redis][:enabled].must_equal true
|
|
57
|
+
AppOpticsAPM::Config[:resqueclient][:enabled].must_equal true
|
|
58
|
+
AppOpticsAPM::Config[:resqueworker][:enabled].must_equal true
|
|
59
|
+
AppOpticsAPM::Config[:rest_client][:enabled].must_equal true
|
|
60
|
+
AppOpticsAPM::Config[:sequel][:enabled].must_equal true
|
|
61
|
+
AppOpticsAPM::Config[:sidekiqclient][:enabled].must_equal true
|
|
62
|
+
AppOpticsAPM::Config[:sidekiqworker][:enabled].must_equal true
|
|
63
|
+
AppOpticsAPM::Config[:typhoeus][:enabled].must_equal true
|
|
64
|
+
|
|
65
|
+
AppOpticsAPM::Config[:action_controller][:log_args].must_equal true
|
|
66
|
+
AppOpticsAPM::Config[:action_controller_api][:log_args].must_equal true
|
|
67
|
+
AppOpticsAPM::Config[:action_view][:log_args].must_equal true
|
|
68
|
+
AppOpticsAPM::Config[:active_record][:log_args].must_equal true
|
|
69
|
+
AppOpticsAPM::Config[:bunnyclient][:log_args].must_equal true
|
|
70
|
+
AppOpticsAPM::Config[:bunnyconsumer][:log_args].must_equal true
|
|
71
|
+
AppOpticsAPM::Config[:cassandra][:log_args].must_equal true
|
|
72
|
+
AppOpticsAPM::Config[:curb][:log_args].must_equal true
|
|
73
|
+
AppOpticsAPM::Config[:dalli][:log_args].must_equal true
|
|
74
|
+
AppOpticsAPM::Config[:delayed_jobclient][:log_args].must_equal true
|
|
75
|
+
AppOpticsAPM::Config[:delayed_jobworker][:log_args].must_equal true
|
|
76
|
+
AppOpticsAPM::Config[:em_http_request][:log_args].must_equal true
|
|
77
|
+
AppOpticsAPM::Config[:excon][:log_args].must_equal true
|
|
78
|
+
AppOpticsAPM::Config[:faraday][:log_args].must_equal true
|
|
79
|
+
AppOpticsAPM::Config[:grape][:log_args].must_equal true
|
|
80
|
+
AppOpticsAPM::Config[:httpclient][:log_args].must_equal true
|
|
81
|
+
AppOpticsAPM::Config[:nethttp][:log_args].must_equal true
|
|
82
|
+
AppOpticsAPM::Config[:memcached][:log_args].must_equal true
|
|
83
|
+
AppOpticsAPM::Config[:memcache][:log_args].must_equal true
|
|
84
|
+
AppOpticsAPM::Config[:mongo][:log_args].must_equal true
|
|
85
|
+
AppOpticsAPM::Config[:moped][:log_args].must_equal true
|
|
86
|
+
AppOpticsAPM::Config[:rack][:log_args].must_equal true
|
|
87
|
+
AppOpticsAPM::Config[:redis][:log_args].must_equal true
|
|
88
|
+
AppOpticsAPM::Config[:resqueclient][:log_args].must_equal true
|
|
89
|
+
AppOpticsAPM::Config[:resqueworker][:log_args].must_equal true
|
|
90
|
+
AppOpticsAPM::Config[:rest_client][:log_args].must_equal true
|
|
91
|
+
AppOpticsAPM::Config[:sequel][:log_args].must_equal true
|
|
92
|
+
AppOpticsAPM::Config[:sidekiqclient][:log_args].must_equal true
|
|
93
|
+
AppOpticsAPM::Config[:sidekiqworker][:log_args].must_equal true
|
|
94
|
+
AppOpticsAPM::Config[:typhoeus][:log_args].must_equal true
|
|
95
|
+
|
|
96
|
+
AppOpticsAPM::Config[:blacklist].is_a?(Array).must_equal true
|
|
97
|
+
|
|
98
|
+
AppOpticsAPM::Config[:dnt_regexp].must_equal '\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|pdf|txt|tar|wav|bmp|rtf|js|flv|swf|otf|eot|ttf|woff|woff2|svg|less)(\?.+){0,1}$'
|
|
99
|
+
AppOpticsAPM::Config[:dnt_opts].must_equal Regexp::IGNORECASE
|
|
100
|
+
|
|
101
|
+
AppOpticsAPM::Config[:sanitize_sql].must_equal true
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def test_should_obey_globals
|
|
105
|
+
# Reset AppOpticsAPM::Config to defaults
|
|
106
|
+
AppOpticsAPM::Config.initialize
|
|
107
|
+
|
|
108
|
+
http_clients = AppOpticsAPM::Config.http_clients
|
|
109
|
+
|
|
110
|
+
# Restore these at the end
|
|
111
|
+
@url_query_params = AppOpticsAPM::Config[:include_url_query_params]
|
|
112
|
+
@remote_url_params = AppOpticsAPM::Config[:include_remote_url_params]
|
|
113
|
+
|
|
114
|
+
# After setting global options, the per instrumentation
|
|
115
|
+
# equivalents should follow suit.
|
|
116
|
+
|
|
117
|
+
#
|
|
118
|
+
# :include_remote_url_params
|
|
119
|
+
#
|
|
120
|
+
|
|
121
|
+
# Check defaults
|
|
122
|
+
AppOpticsAPM::Config[:include_remote_url_params].must_equal true
|
|
123
|
+
http_clients.each do |i|
|
|
124
|
+
AppOpticsAPM::Config[i][:log_args].must_equal true
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Check obedience
|
|
128
|
+
AppOpticsAPM::Config[:include_remote_url_params] = false
|
|
129
|
+
http_clients.each do |i|
|
|
130
|
+
AppOpticsAPM::Config[i][:log_args].must_equal false
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
#
|
|
134
|
+
# :include_url_query_params
|
|
135
|
+
#
|
|
136
|
+
|
|
137
|
+
# Check default
|
|
138
|
+
AppOpticsAPM::Config[:include_url_query_params].must_equal true
|
|
139
|
+
AppOpticsAPM::Config[:rack][:log_args].must_equal true
|
|
140
|
+
|
|
141
|
+
# Check obedience
|
|
142
|
+
AppOpticsAPM::Config[:include_url_query_params] = false
|
|
143
|
+
AppOpticsAPM::Config[:rack][:log_args].must_equal false
|
|
144
|
+
|
|
145
|
+
# Restore the previous values
|
|
146
|
+
AppOpticsAPM::Config[:include_url_query_params] = @url_query_params
|
|
147
|
+
AppOpticsAPM::Config[:include_remote_url_params] = @remote_url_params
|
|
148
|
+
end
|
|
149
|
+
end
|