scout_apm 4.0.4 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +4 -0
  3. data/CHANGELOG.markdown +33 -0
  4. data/LICENSE.md +21 -28
  5. data/gems/typhoeus.gemfile +3 -0
  6. data/lib/scout_apm/agent/preconditions.rb +3 -3
  7. data/lib/scout_apm/auto_instrument/rails.rb +0 -1
  8. data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
  9. data/lib/scout_apm/background_job_integrations/sidekiq.rb +13 -2
  10. data/lib/scout_apm/config.rb +10 -0
  11. data/lib/scout_apm/error_service/middleware.rb +2 -2
  12. data/lib/scout_apm/error_service/payload.rb +1 -1
  13. data/lib/scout_apm/error_service.rb +3 -1
  14. data/lib/scout_apm/external_service_metric_set.rb +97 -0
  15. data/lib/scout_apm/external_service_metric_stats.rb +85 -0
  16. data/lib/scout_apm/fake_store.rb +3 -0
  17. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +5 -1
  18. data/lib/scout_apm/git_revision.rb +9 -0
  19. data/lib/scout_apm/ignored_uris.rb +3 -1
  20. data/lib/scout_apm/instant/middleware.rb +2 -0
  21. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +5 -2
  22. data/lib/scout_apm/instruments/action_view.rb +13 -5
  23. data/lib/scout_apm/instruments/active_record.rb +2 -0
  24. data/lib/scout_apm/instruments/elasticsearch.rb +2 -0
  25. data/lib/scout_apm/instruments/sinatra.rb +2 -0
  26. data/lib/scout_apm/instruments/typhoeus.rb +8 -6
  27. data/lib/scout_apm/layer_converters/external_service_converter.rb +65 -0
  28. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
  29. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +2 -0
  30. data/lib/scout_apm/logger.rb +2 -2
  31. data/lib/scout_apm/reporting.rb +2 -1
  32. data/lib/scout_apm/serializers/external_service_serializer_to_json.rb +15 -0
  33. data/lib/scout_apm/serializers/payload_serializer.rb +4 -3
  34. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +4 -1
  35. data/lib/scout_apm/store.rb +21 -1
  36. data/lib/scout_apm/tracked_request.rb +7 -1
  37. data/lib/scout_apm/utils/sql_sanitizer.rb +32 -6
  38. data/lib/scout_apm/version.rb +1 -1
  39. data/lib/scout_apm.rb +5 -0
  40. data/scout_apm.gemspec +0 -2
  41. data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
  42. data/test/unit/auto_instrument/hanging_method.rb +6 -0
  43. data/test/unit/auto_instrument_test.rb +8 -0
  44. data/test/unit/external_service_metric_set_test.rb +67 -0
  45. data/test/unit/external_service_metric_stats_test.rb +106 -0
  46. data/test/unit/ignored_uris_test.rb +6 -0
  47. data/test/unit/instruments/typhoeus_test.rb +42 -0
  48. data/test/unit/remote/{test_message.rb → message_test.rb} +0 -0
  49. data/test/unit/remote/{test_router.rb → route_test.rb} +0 -0
  50. data/test/unit/remote/{test_server.rb → server_test.rb} +4 -1
  51. data/test/unit/serializers/payload_serializer_test.rb +3 -3
  52. data/test/unit/sql_sanitizer_test.rb +19 -2
  53. metadata +16 -9
  54. data/.travis.yml +0 -22
  55. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -32
  56. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -32
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 16b10ca7e3c7474546feb2b367438e9fabeb2c799e85608b96afb3ddea37b647
4
- data.tar.gz: 044ced73909cff452e59bccf14e7dfcf344fd8f6262c5b633b0d3239995609fd
3
+ metadata.gz: f9a94a38d6b06a02e34ce7923e8084976739cc8d0fba1adfbc606f88b0bdf4d7
4
+ data.tar.gz: aebe73f5379911e8691be2771b3a3fc3f5fbdd4779f2038f6a6f6e0ed38e1e20
5
5
  SHA512:
6
- metadata.gz: c2ae77ef620e77bce7b267fe8c39ff2f91945dfffc3ad008dd01cbd3b45268a8b44624713652d1df2bdeb2789bedb1fdb98aa58742b48326b1821ea01482132d
7
- data.tar.gz: c533bf6cf2822f7688b4377e9f69e005eb1255c0c03cd000dd5d4edaef74b7761b4b8af9b8d39d1229480fd7f2cd0b0f6bcaf1890be5b3f6a9540bf7f8e03f64
6
+ metadata.gz: d8850b1365747879effcfe383d0b438070069d2b625365dc8ea61db7c72cf6840cdf73675c95aea7a7688750dbcb41efec1b7320232312172bc49be9b207d421
7
+ data.tar.gz: f3d389aa73f8b4090627843410c610edc1c5fa88740587136dbd8277d3ddfa5360e61d0d256ae23db7cf53f20b482d3f283541c077efcd2d12032e5f3f72f4e2
@@ -26,6 +26,9 @@ jobs:
26
26
  - ruby: 2.4
27
27
  - ruby: 2.5
28
28
  - ruby: 2.6
29
+ - ruby: 2.6
30
+ gemfile: gems/typhoeus.gemfile
31
+ test_features: "typhoeus"
29
32
  - ruby: 2.6
30
33
  gemfile: gems/octoshark.gemfile
31
34
  - ruby: 2.6
@@ -36,6 +39,7 @@ jobs:
36
39
 
37
40
  env:
38
41
  BUNDLE_GEMFILE: ${{ matrix.gemfile }}
42
+ SCOUT_TEST_FEATURES: ${{ matrix.test_features }}
39
43
 
40
44
  runs-on: ubuntu-latest
41
45
 
data/CHANGELOG.markdown CHANGED
@@ -1,9 +1,42 @@
1
+ # 5.0.0
2
+
3
+ * Add External Service metrics reporting (#403)
4
+ * Relicense to MIT (#429)
5
+ * Opt out of frozen string literals in select files (#427)
6
+ * Fall back when logger can't write to destination (#423)
7
+ * Avoid exception on race condition (#407)
8
+ * Add Mina deploy tracking support (#327)
9
+
10
+ # 4.1.2
11
+
12
+ * Add record_queue_time configuration (PR #422)
13
+
14
+ # 4.1.1
15
+
16
+ * Fix issue with Typheous Hydra instrument (#418)
17
+
18
+ # 4.1.0
19
+
20
+ * Preload Celluloid in Shoryuken instrumentation (#331)
21
+ * Fix deprecation warning in Rails 6.1+ (#365)
22
+ * Set Typheous's desc more directly (#392)
23
+ * Delegate to ActiveRecord #log more intelligently (#394)
24
+ * Don't delay starting agent when possible (#397)
25
+ * Fix template naming issue in Rails 6+ (#399)
26
+ * Avoid double-counting issue with AutoInstruments (#405)
27
+ * Renaming test files for Remote::{Server|Route|Message} to be included in test run (#409)
28
+ * More robust naming of Sidekiq jobs (#412)
29
+ * Allow render_template instruments to work with older Rails (#413)
30
+ * Fix function to manually capture exceptions (#415)
31
+ * Enhance SQL Sanitization (#417)
32
+
1
33
  # 4.0.4
2
34
 
3
35
  * Add Faktory Support (#385)
4
36
  * Remove Regexp hack for 1.8.7 (no longer supported) (#384)
5
37
  * More robust DelayedJob detection (#382)
6
38
  * Fix kwargs handling in Tracing module (#381)
39
+
7
40
  # 4.0.3
8
41
 
9
42
  * Handle edge case with nil Typhoeus current-layer (#380)
data/LICENSE.md CHANGED
@@ -1,31 +1,24 @@
1
- # Scout Software Agent License
2
-
3
- Subject to and conditioned upon your continued compliance with the terms and conditions of this license, Zimuth, Inc. grants you a non-exclusive, non-sublicensable and non-transferable, limited license to install, use and run one copy of this software on each of your and your affiliate’s computers. This license also grants you the limited right to distribute verbatim copies of this software and documentation to third parties provided the software and documentation will (a) remain the exclusive property of Zimuth; (b) be subject to the terms and conditions of this license; and (c) include a complete and unaltered copy of this license and all other copyright or other intellectual property rights notices contained in the original.
4
-
5
- The software includes the open-source components listed below. Any use of the open-source components by you shall be governed by, and subject to, the terms and conditions of the applicable open-source licenses.
6
-
7
- Except as this license expressly permits, you may not:
8
-
9
- * copy the software, in whole or in part;
10
- * modify, correct, adapt, translate, enhance or otherwise prepare derivative works or improvements of the software;
11
- * sell, sublicense, assign, distribute, publish, transfer or otherwise make available the software to any person or entity;
12
- * remove, delete, efface, alter, obscure, translate, combine, supplement or otherwise change any trademarks, terms of the documentation, warranties, disclaimers, or intellectual property rights, or other symbols, notices, marks or serial numbers on or relating to any copy of the software or documentation; or
13
- use the software in any manner or for any purpose that infringes, misappropriates or otherwise violates any intellectual property right or other right of any person or entity, or that violates any applicable law;
14
-
15
- By using the software, you acknowledge and agree that:
16
-
17
- * the software and documentation are licensed, not sold, to you by Zimuth and you do not and will not have or acquire under or in connection with this license any ownership interest in the software or documentation, or in any related intellectual property rights; and
18
- * Zimuth will remain the sole and exclusive owner of all right, title and interest in and to the software and documentation, including all related intellectual property rights, subject only to the rights of third parties in open-source components and the limited license granted to you under this license; and
19
-
20
- Except for the limited rights and licenses expressly granted to you under this agreement, nothing in this license grants, by implication, waiver, estoppel or otherwise, to you or any third party any intellectual property rights or other right, title, or interest in or to any of the software or documentation.
21
-
22
- This license shall be automatically terminated and revoked if you exceed the scope or violate any terms and conditions of this license.
23
-
24
- ALL LICENSED SOFTWARE, DOCUMENTATION AND OTHER PRODUCTS, INFORMATION, MATERIALS AND SERVICES PROVIDED BY ZIMUTH ARE PROVIDED HERE “AS IS.” ZIMUTH DISCLAIMS ALL WARRANTIES, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHER (INCLUDING ALL WARRANTIES ARISING FROM COURSE OF DEALING, USAGE OR TRADE PRACTICE), AND SPECIFICALLY DISCLAIMS ALL IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. WITHOUT LIMITING THE FOREGOING, ZIMUTH MAKES NO WARRANTY OF ANY KIND THAT THE LICENSED SOFTWARE OR DOCUMENTATION, OR ANY OTHER LICENSOR OR THIRD-PARTY GOODS, SERVICES, TECHNOLOGIES OR MATERIALS, WILL MEET YOUR REQUIREMENTS, OPERATE WITHOUT INTERRUPTION, ACHIEVE ANY INTENDED RESULT, BE COMPATIBLE OR WORK WITH ANY OTHER GOODS, SERVICES, TECHNOLOGIES OR MATERIALS (INCLUDING ANY SOFTWARE, HARDWARE, SYSTEM OR NETWORK) EXCEPT IF AND TO THE EXTENT EXPRESSLY SET FORTH IN THE DOCUMENTATION, OR BE SECURE, ACCURATE, COMPLETE, FREE OF HARMFUL CODE OR ERROR FREE. ALL OPEN-SOURCE COMPONENTS AND OTHER THIRD-PARTY MATERIALS ARE PROVIDED “AS IS” AND ANY REPRESENTATION OR WARRANTY OF OR CONCERNING ANY OF THEM IS STRICTLY BETWEEN LICENSEE AND THE THIRD-PARTY OWNER OR DISTRIBUTOR OF SUCH OPEN-SOURCE COMPONENTS AND THIRD-PARTY MATERIALS.
25
-
26
- IN NO EVENT WILL ZIMUTH BE LIABLE UNDER OR IN CONNECTION WITH THIS LICENSE OR ITS SUBJECT MATTER UNDER ANY LEGAL OR EQUITABLE THEORY, INCLUDING BREACH OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY AND OTHERWISE, FOR ANY CONSEQUENTIAL, INCIDENTAL, INDIRECT, EXEMPLARY, SPECIAL, ENHANCED OR PUNITIVE DAMAGES, IN EACH CASE REGARDLESS OF WHETHER SUCH PERSONS WERE ADVISED OF THE POSSIBILITY OF SUCH LOSSES OR DAMAGES OR SUCH LOSSES OR DAMAGES WERE OTHERWISE FORESEEABLE, AND NOTWITHSTANDING THE FAILURE OF ANY AGREED OR OTHER REMEDY OF ITS ESSENTIAL PURPOSE.
27
-
28
- ## OPEN SOURCE COMPONENTS
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-2021 Zimuth, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
29
22
 
30
23
  This product includes `rusage`, which inherits the Artistic License 2.0 from proc/wait3.
31
24
  See http://www.rubydoc.info/gems/rusage/0.2.0.
@@ -0,0 +1,3 @@
1
+ eval_gemfile("../Gemfile")
2
+
3
+ gem 'typhoeus'
@@ -27,10 +27,10 @@ module ScoutApm
27
27
  PRECONDITION_DETECTED_SERVER = {
28
28
  :message => proc {|environ| "Deferring agent start. Standing by for first request" },
29
29
  :check => proc { |context|
30
- app_server_missing = !context.environment.app_server_integration(true).found?
31
- background_job_missing = context.environment.background_job_integrations.length == 0
30
+ app_server_found = context.environment.app_server_integration(true).found?
31
+ background_job_integration_found = context.environment.background_job_integrations.length > 0
32
32
 
33
- !app_server_missing && !background_job_missing
33
+ app_server_found || background_job_integration_found
34
34
  },
35
35
  :severity => :info,
36
36
  },
@@ -69,7 +69,6 @@ module ScoutApm
69
69
  end
70
70
 
71
71
  method_name = @method.last.children[0]
72
- class_name = @scope.last.children[1]
73
72
  bt = ["#{file_name}:#{line}:in `#{method_name}'"]
74
73
 
75
74
  return [
@@ -37,6 +37,8 @@ module ScoutApm
37
37
  end
38
38
 
39
39
  def install_processor
40
+ # celluloid has not loaded by this point and older versions of `shorykuen/processor` assume that it did
41
+ require 'celluloid' if defined?(::Shoryuken::VERSION) && ::Shoryuken::VERSION < '3'
40
42
  require 'shoryuken/processor' # sidekiq v4 has not loaded this file by this point
41
43
 
42
44
  ::Shoryuken::Processor.class_eval do
@@ -80,12 +80,23 @@ module ScoutApm
80
80
  DELAYED_WRAPPER_KLASS = 'Sidekiq::Extensions::DelayedClass'.freeze
81
81
 
82
82
 
83
+ # Capturing the class name is a little tricky, since we need to handle several cases:
84
+ # 1. ActiveJob, with the class in the key 'wrapped'
85
+ # 2. ActiveJob, but the 'wrapped' key is wrong (due to YAJL serializing weirdly), find it in args.job_class
86
+ # 3. DelayedJob wrapper, deserializing using YAML into the real object, which can be introspected
87
+ # 4. No wrapper, just sidekiq's class
83
88
  def job_class(msg)
84
89
  job_class = msg.fetch('class', UNKNOWN_CLASS_PLACEHOLDER)
85
90
 
86
- if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped')
91
+ if job_class == ACTIVE_JOB_KLASS && msg.key?('wrapped') && msg['wrapped'].is_a?(String)
87
92
  begin
88
- job_class = msg['wrapped']
93
+ job_class = msg['wrapped'].to_s
94
+ rescue
95
+ ACTIVE_JOB_KLASS
96
+ end
97
+ elsif job_class == ACTIVE_JOB_KLASS && msg.try(:[], 'args').try(:[], 'job_class')
98
+ begin
99
+ job_class = msg['args']['job_class'].to_s
89
100
  rescue
90
101
  ACTIVE_JOB_KLASS
91
102
  end
@@ -29,6 +29,7 @@ require 'scout_apm/environment'
29
29
  # report_format - 'json' or 'marshal'. Marshal is legacy and will be removed.
30
30
  # scm_subdirectory - if the app root lives in source management in a subdirectory. E.g. #{SCM_ROOT}/src
31
31
  # uri_reporting - 'path' or 'full_path' default is 'full_path', which reports URL params as well as the path.
32
+ # record_queue_time - true/false to enable recording of queuetime.
32
33
  # remote_agent_host - Internal: What host to bind to, and also send messages to for remote. Default: 127.0.0.1.
33
34
  # remote_agent_port - What port to bind the remote webserver to
34
35
  # start_resque_server_instrument - Used in special situations with certain Resque installs
@@ -55,6 +56,8 @@ module ScoutApm
55
56
  'direct_host',
56
57
  'disabled_instruments',
57
58
  'enable_background_jobs',
59
+ 'external_service_metric_limit',
60
+ 'external_service_metric_report_limit',
58
61
  'host',
59
62
  'hostname',
60
63
  'ignore',
@@ -69,6 +72,7 @@ module ScoutApm
69
72
  'name',
70
73
  'profile',
71
74
  'proxy',
75
+ 'record_queue_time',
72
76
  'remote_agent_host',
73
77
  'remote_agent_port',
74
78
  'report_format',
@@ -177,7 +181,10 @@ module ScoutApm
177
181
  'compress_payload' => BooleanCoercion.new,
178
182
  'database_metric_limit' => IntegerCoercion.new,
179
183
  'database_metric_report_limit' => IntegerCoercion.new,
184
+ 'external_service_metric_limit' => IntegerCoercion.new,
185
+ 'external_service_metric_report_limit' => IntegerCoercion.new,
180
186
  'instrument_http_url_length' => IntegerCoercion.new,
187
+ 'record_queue_time' => BooleanCoercion.new,
181
188
  'start_resque_server_instrument' => BooleanCoercion.new,
182
189
  'timeline_traces' => BooleanCoercion.new,
183
190
  'auto_instruments' => BooleanCoercion.new,
@@ -289,9 +296,12 @@ module ScoutApm
289
296
  'remote_agent_port' => 7721, # picked at random
290
297
  'database_metric_limit' => 5000, # The hard limit on db metrics
291
298
  'database_metric_report_limit' => 1000,
299
+ 'external_service_metric_limit' => 5000, # The hard limit on external service metrics
300
+ 'external_service_metric_report_limit' => 1000,
292
301
  'instrument_http_url_length' => 300,
293
302
  'start_resque_server_instrument' => true, # still only starts if Resque is detected
294
303
  'collect_remote_ip' => true,
304
+ 'record_queue_time' => true,
295
305
  'timeline_traces' => true,
296
306
  'auto_instruments' => false,
297
307
  'auto_instruments_ignore' => [],
@@ -9,10 +9,10 @@ module ScoutApm
9
9
  begin
10
10
  response = @app.call(env)
11
11
  rescue Exception => exception
12
- puts "[Scout Error Service] Caught Exception: #{exception.class.name}"
13
-
14
12
  context = ScoutApm::Agent.instance.context
15
13
 
14
+ context.logger.debug "[Scout Error Service] Caught Exception: #{exception.class.name}"
15
+
16
16
  # Bail out early, and reraise if the error is not interesting.
17
17
  if context.ignored_exceptions.ignored?(exception)
18
18
  raise
@@ -12,7 +12,7 @@ module ScoutApm
12
12
  # TODO: Don't use to_json since it isn't supported in Ruby 1.8.7
13
13
  def serialize
14
14
  payload = as_json.to_json
15
- context.logger.info(payload)
15
+ context.logger.debug(payload)
16
16
  payload
17
17
  end
18
18
 
@@ -16,7 +16,9 @@ module ScoutApm
16
16
  # Used internally by SidekiqException
17
17
  def self.capture(exception, params = {})
18
18
  return if disabled?
19
- return if ScoutApm::Agent.instance.context.ignored_exceptions.ignore?(exception)
19
+
20
+ context = ScoutApm::Agent.instance.context
21
+ return if context.ignored_exceptions.ignore?(exception)
20
22
 
21
23
  context.errors_buffer.capture(exception, env)
22
24
  end
@@ -0,0 +1,97 @@
1
+ # Note, this class must be Marshal Dumpable
2
+ module ScoutApm
3
+ class ExternalServiceMetricSet
4
+ include Enumerable
5
+
6
+ attr_reader :metrics # the raw metrics. You probably want #metrics_to_report
7
+
8
+ def marshal_dump
9
+ [ @metrics ]
10
+ end
11
+
12
+ def marshal_load(array)
13
+ @metrics = array.first
14
+ @context = ScoutApm::Agent.instance.context
15
+ end
16
+
17
+ def initialize(context)
18
+ @context = context
19
+
20
+ # A hash of ExternalServiceMetricStats values, keyed by ExternalServiceMetricStats.key
21
+ @metrics = Hash.new
22
+ end
23
+
24
+ # Need to look this up again if we end up as nil. Which I guess can happen
25
+ # after a Marshal load?
26
+ def context
27
+ @context ||= ScoutApm::Agent.instance.context
28
+ end
29
+
30
+ def each
31
+ metrics.each do |_key, external_service_metric_stat|
32
+ yield external_service_metric_stat
33
+ end
34
+ end
35
+
36
+ # Looks up a ExternalServiceMetricStats instance in the +@metrics+ hash. Sets the value to +other+ if no key
37
+ # Returns a ExternalServiceMetricStats instance
38
+ def lookup(other)
39
+ metrics[other.key] ||= other
40
+ end
41
+
42
+ # Take another set, and merge it with this one
43
+ def combine!(other)
44
+ other.each do |metric|
45
+ self << metric
46
+ end
47
+ self
48
+ end
49
+
50
+ # Add a single ExternalServiceMetricStats object to this set.
51
+ #
52
+ # Looks up an existing one under this key and merges, or just saves a new
53
+ # one under the key
54
+ def <<(stat)
55
+ existing_stat = metrics[stat.key]
56
+ if existing_stat
57
+ existing_stat.combine!(stat)
58
+ elsif at_limit?
59
+ # We're full up, can't add any more.
60
+ # Should I log this? It may get super noisy?
61
+ else
62
+ metrics[stat.key] = stat
63
+ end
64
+ end
65
+
66
+ def increment_transaction_count!
67
+ metrics.each do |_key, external_service_metric_stat|
68
+ external_service_metric_stat.increment_transaction_count!
69
+ end
70
+ end
71
+
72
+ def metrics_to_report
73
+ report_limit = context.config.value('external_service_metric_report_limit')
74
+ if metrics.size > report_limit
75
+ metrics.
76
+ values.
77
+ sort_by {|stat| stat.call_time }.
78
+ reverse.
79
+ take(report_limit)
80
+ else
81
+ metrics.values
82
+ end
83
+ end
84
+
85
+ def inspect
86
+ metrics.map {|key, metric|
87
+ "#{key.inspect} - Count: #{metric.call_count}, Total Time: #{"%.2f" % metric.call_time}"
88
+ }.join("\n")
89
+ end
90
+
91
+ def at_limit?
92
+ @limit ||= context.config.value('external_service_metric_limit')
93
+ metrics.size >= @limit
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,85 @@
1
+ module ScoutApm
2
+ class ExternalServiceMetricStats
3
+
4
+ DEFAULT_HISTOGRAM_SIZE = 50
5
+
6
+ attr_reader :domain_name
7
+ attr_reader :operation
8
+ attr_reader :scope
9
+
10
+ attr_reader :transaction_count
11
+
12
+ attr_reader :call_count
13
+ attr_reader :call_time
14
+
15
+ attr_reader :min_call_time
16
+ attr_reader :max_call_time
17
+
18
+ attr_reader :histogram
19
+
20
+ def initialize(domain_name, operation, scope, call_count, call_time)
21
+ @domain_name = domain_name
22
+ @operation = operation
23
+
24
+ @call_count = call_count
25
+
26
+ @call_time = call_time
27
+ @min_call_time = call_time
28
+ @max_call_time = call_time
29
+
30
+ # This histogram is for call_time
31
+ @histogram = NumericHistogram.new(DEFAULT_HISTOGRAM_SIZE)
32
+ @histogram.add(call_time)
33
+
34
+ @transaction_count = 0
35
+
36
+ @scope = scope
37
+ end
38
+
39
+ # Merge data in this scope. Used in ExternalServiceMetricSet
40
+ def key
41
+ @key ||= [domain_name, operation, scope]
42
+ end
43
+
44
+ # Combine data from another ExternalServiceMetricStats into +self+. Modifies and returns +self+
45
+ def combine!(other)
46
+ return self if other == self
47
+
48
+ @transaction_count += other.transaction_count
49
+ @call_count += other.call_count
50
+ @call_time += other.call_time
51
+
52
+ @min_call_time = other.min_call_time if @min_call_time.zero? or other.min_call_time < @min_call_time
53
+ @max_call_time = other.max_call_time if other.max_call_time > @max_call_time
54
+
55
+ @histogram.combine!(other.histogram)
56
+ self
57
+ end
58
+
59
+ def as_json
60
+ json_attributes = [
61
+ :domain_name,
62
+ :operation,
63
+ :scope,
64
+
65
+ :transaction_count,
66
+ :call_count,
67
+
68
+ :histogram,
69
+ :call_time,
70
+ :max_call_time,
71
+ :min_call_time,
72
+ ]
73
+
74
+ ScoutApm::AttributeArranger.call(self, json_attributes)
75
+ end
76
+
77
+ # Called by the Set on each ExternalServiceMetricStats object that it holds, only
78
+ # once during the recording of a transaction.
79
+ #
80
+ # Don't call elsewhere, and don't set to 1 in the initializer.
81
+ def increment_transaction_count!
82
+ @transaction_count += 1
83
+ end
84
+ end
85
+ end
@@ -26,6 +26,9 @@ module ScoutApm
26
26
  def track_db_query_metrics!(db_query_metric_set, options={})
27
27
  end
28
28
 
29
+ def track_external_service_metrics!(external_service_metric_set, options={})
30
+ end
31
+
29
32
  def track_slow_transaction!(slow_transaction)
30
33
  end
31
34
 
@@ -71,7 +71,11 @@ module ScoutApm
71
71
  #
72
72
  # We avoid this issue by not calling .respond_to? here, and instead using the less optimal `rescue nil` approach
73
73
  def raw_database_adapter
74
- adapter = ActiveRecord::Base.connection_config[:adapter].to_s rescue nil
74
+ adapter = ActiveRecord::Base.connection_db_config.configuration_hash[:adapter] rescue nil
75
+
76
+ if adapter.nil?
77
+ adapter = ActiveRecord::Base.connection_config[:adapter].to_s rescue nil
78
+ end
75
79
 
76
80
  if adapter.nil?
77
81
  adapter = ActiveRecord::Base.configurations[env]["adapter"]
@@ -20,6 +20,7 @@ module ScoutApm
20
20
  detect_from_config ||
21
21
  detect_from_heroku ||
22
22
  detect_from_capistrano ||
23
+ detect_from_mina ||
23
24
  detect_from_git
24
25
  end
25
26
 
@@ -43,6 +44,14 @@ module ScoutApm
43
44
  nil
44
45
  end
45
46
 
47
+ # https://github.com/mina-deploy/mina
48
+ def detect_from_mina
49
+ File.read(File.join(app_root, '.mina_git_revision')).strip
50
+ rescue
51
+ logger.debug "Unable to detect Git Revision from Mina: #{$!.message}"
52
+ nil
53
+ end
54
+
46
55
  def detect_from_git
47
56
  if File.directory?(".git")
48
57
  `git rev-parse --short HEAD`.strip
@@ -4,7 +4,9 @@ module ScoutApm
4
4
  attr_reader :regex
5
5
 
6
6
  def initialize(prefixes)
7
- regexes = Array(prefixes).map {|prefix| %r{\A#{prefix}} }
7
+ regexes = Array(prefixes).
8
+ reject{|prefix| prefix == ""}.
9
+ map {|prefix| %r{\A#{prefix}} }
8
10
  @regex = Regexp.union(*regexes)
9
11
  end
10
12
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  module ScoutApm
2
4
  module Instant
3
5
 
@@ -95,8 +95,11 @@ module ScoutApm
95
95
  req.instant_key = instant_key
96
96
  end
97
97
 
98
- if current_layer && current_layer.type == "Controller"
99
- # Don't start a new layer if ActionController::API or ActionController::Base handled it already.
98
+ # Don't start a new layer if ActionController::API or
99
+ # ActionController::Base handled it already. Needs to account for
100
+ # any layers started during a around_action (most likely
101
+ # AutoInstrument, but could be another custom instrument)
102
+ if current_layer && (current_layer.type == "Controller" || current_layer.type == "AutoInstrument" || req.web?)
100
103
  super
101
104
  else
102
105
  begin
@@ -83,7 +83,7 @@ module ScoutApm
83
83
  maybe_template = args[1]
84
84
 
85
85
  template_name = @template.virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
86
- template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3
86
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3.5
87
87
  template_name ||= "Unknown Partial"
88
88
 
89
89
  layer_name = template_name + "/Rendering"
@@ -105,7 +105,10 @@ module ScoutApm
105
105
  def collection_with_template(*args, **kwargs)
106
106
  req = ScoutApm::RequestManager.lookup
107
107
 
108
- template_name = @template.virtual_path rescue "Unknown Collection"
108
+ maybe_template = args[1]
109
+
110
+ template_name = @template.virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
111
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.0.3.5
109
112
  template_name ||= "Unknown Collection"
110
113
  layer_name = template_name + "/Rendering"
111
114
 
@@ -126,10 +129,15 @@ module ScoutApm
126
129
  end
127
130
 
128
131
  module ActionViewTemplateRendererInstruments
129
- def render_template(*args, **kwargs)
132
+ # Don't forward kwargs here, since Rails 3, 4, 5, 6 don't use them, and
133
+ # it causes annoyances in the instrumentation
134
+ def render_template(*args)
130
135
  req = ScoutApm::RequestManager.lookup
131
136
 
132
- template_name = args[0].virtual_path rescue "Unknown"
137
+ maybe_template = args[1]
138
+
139
+ template_name = args[0].virtual_path rescue nil # Works on Rails 3.2 -> end of Rails 5 series
140
+ template_name ||= maybe_template.virtual_path rescue nil # Works on Rails 6 -> 6.1.3
133
141
  template_name ||= "Unknown"
134
142
  layer_name = template_name + "/Rendering"
135
143
 
@@ -138,7 +146,7 @@ module ScoutApm
138
146
 
139
147
  begin
140
148
  req.start_layer(layer)
141
- super(*args, **kwargs)
149
+ super(*args)
142
150
  ensure
143
151
  req.stop_layer
144
152
  end
@@ -215,6 +215,7 @@ module ScoutApm
215
215
  end
216
216
  end
217
217
  end
218
+ ruby2_keywords :log if respond_to?(:ruby2_keywords, true)
218
219
  end
219
220
 
220
221
  module ActiveRecordInstruments
@@ -267,6 +268,7 @@ module ScoutApm
267
268
  end
268
269
  end
269
270
  end
271
+ ruby2_keywords :log if respond_to?(:ruby2_keywords, true)
270
272
  end
271
273
 
272
274
  ################################################################################
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  module ScoutApm
2
4
  module Instruments
3
5
  class Elasticsearch
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  # XXX: Is this file used?
2
4
  module ScoutApm
3
5
  module Instruments
@@ -29,10 +29,11 @@ module ScoutApm
29
29
 
30
30
  module TyphoeusHydraInstrumentation
31
31
  def run(*args, &block)
32
+ layer = ScoutApm::Layer.new("HTTP", "Hydra")
33
+ layer.desc = scout_desc
34
+
32
35
  req = ScoutApm::RequestManager.lookup
33
- req.start_layer(ScoutApm::Layer.new("HTTP", "Hydra"))
34
- current_layer = req.current_layer
35
- current_layer.desc = scout_desc if current_layer
36
+ req.start_layer(layer)
36
37
 
37
38
  begin
38
39
  super(*args, &block)
@@ -50,10 +51,11 @@ module ScoutApm
50
51
 
51
52
  module TyphoeusInstrumentation
52
53
  def run(*args, &block)
54
+ layer = ScoutApm::Layer.new("HTTP", scout_request_verb)
55
+ layer.desc = scout_desc(scout_request_verb, scout_request_url)
56
+
53
57
  req = ScoutApm::RequestManager.lookup
54
- req.start_layer(ScoutApm::Layer.new("HTTP", scout_request_verb))
55
- current_layer = req.current_layer
56
- current_layer.desc = scout_desc(scout_request_verb, scout_request_url) if current_layer
58
+ req.start_layer(layer)
57
59
 
58
60
  begin
59
61
  super(*args, &block)