scout_apm 2.5.1 → 5.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (156) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/test.yml +68 -0
  3. data/.gitignore +1 -0
  4. data/.rubocop.yml +5 -5
  5. data/CHANGELOG.markdown +176 -3
  6. data/Gemfile +1 -7
  7. data/LICENSE.md +21 -28
  8. data/gems/README.md +28 -0
  9. data/gems/instruments.gemfile +6 -0
  10. data/gems/octoshark.gemfile +4 -0
  11. data/gems/rails3.gemfile +5 -0
  12. data/gems/rails4.gemfile +4 -0
  13. data/gems/rails5.gemfile +4 -0
  14. data/gems/rails6.gemfile +4 -0
  15. data/gems/sidekiq.gemfile +4 -0
  16. data/gems/typhoeus.gemfile +3 -0
  17. data/lib/scout_apm/agent/preconditions.rb +3 -3
  18. data/lib/scout_apm/agent.rb +22 -0
  19. data/lib/scout_apm/agent_context.rb +21 -2
  20. data/lib/scout_apm/app_server_load.rb +7 -2
  21. data/lib/scout_apm/auto_instrument/instruction_sequence.rb +31 -0
  22. data/lib/scout_apm/auto_instrument/layer.rb +23 -0
  23. data/lib/scout_apm/auto_instrument/parser.rb +27 -0
  24. data/lib/scout_apm/auto_instrument/rails.rb +174 -0
  25. data/lib/scout_apm/auto_instrument.rb +5 -0
  26. data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -1
  27. data/lib/scout_apm/background_job_integrations/faktory.rb +103 -0
  28. data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +55 -0
  29. data/lib/scout_apm/background_job_integrations/que.rb +134 -0
  30. data/lib/scout_apm/background_job_integrations/shoryuken.rb +2 -0
  31. data/lib/scout_apm/background_job_integrations/sidekiq.rb +15 -10
  32. data/lib/scout_apm/background_job_integrations/sneakers.rb +11 -11
  33. data/lib/scout_apm/config.rb +54 -6
  34. data/lib/scout_apm/detailed_trace.rb +3 -2
  35. data/lib/scout_apm/environment.rb +18 -1
  36. data/lib/scout_apm/error.rb +27 -0
  37. data/lib/scout_apm/error_service/error_buffer.rb +39 -0
  38. data/lib/scout_apm/error_service/error_record.rb +211 -0
  39. data/lib/scout_apm/error_service/ignored_exceptions.rb +66 -0
  40. data/lib/scout_apm/error_service/middleware.rb +32 -0
  41. data/lib/scout_apm/error_service/notifier.rb +33 -0
  42. data/lib/scout_apm/error_service/payload.rb +47 -0
  43. data/lib/scout_apm/error_service/periodic_work.rb +17 -0
  44. data/lib/scout_apm/error_service/railtie.rb +11 -0
  45. data/lib/scout_apm/error_service/sidekiq.rb +80 -0
  46. data/lib/scout_apm/error_service.rb +34 -0
  47. data/lib/scout_apm/exceptions.rb +12 -0
  48. data/lib/scout_apm/extensions/transaction_callback_payload.rb +1 -1
  49. data/lib/scout_apm/external_service_metric_set.rb +97 -0
  50. data/lib/scout_apm/external_service_metric_stats.rb +85 -0
  51. data/lib/scout_apm/fake_store.rb +3 -0
  52. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +7 -2
  53. data/lib/scout_apm/git_revision.rb +9 -0
  54. data/lib/scout_apm/ignored_uris.rb +3 -1
  55. data/lib/scout_apm/instant/middleware.rb +4 -1
  56. data/lib/scout_apm/instrument_manager.rb +22 -1
  57. data/lib/scout_apm/instruments/action_controller_rails_2.rb +1 -1
  58. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +53 -29
  59. data/lib/scout_apm/instruments/action_view.rb +30 -9
  60. data/lib/scout_apm/instruments/active_record.rb +69 -19
  61. data/lib/scout_apm/instruments/elasticsearch.rb +93 -42
  62. data/lib/scout_apm/instruments/grape.rb +1 -1
  63. data/lib/scout_apm/instruments/http.rb +68 -0
  64. data/lib/scout_apm/instruments/http_client.rb +33 -14
  65. data/lib/scout_apm/instruments/influxdb.rb +2 -2
  66. data/lib/scout_apm/instruments/memcached.rb +58 -0
  67. data/lib/scout_apm/instruments/middleware_detailed.rb +1 -1
  68. data/lib/scout_apm/instruments/middleware_summary.rb +1 -1
  69. data/lib/scout_apm/instruments/mongoid.rb +10 -5
  70. data/lib/scout_apm/instruments/moped.rb +44 -19
  71. data/lib/scout_apm/instruments/net_http.rb +51 -16
  72. data/lib/scout_apm/instruments/rails_router.rb +1 -1
  73. data/lib/scout_apm/instruments/redis.rb +27 -12
  74. data/lib/scout_apm/instruments/redis5.rb +59 -0
  75. data/lib/scout_apm/instruments/sinatra.rb +3 -1
  76. data/lib/scout_apm/instruments/typhoeus.rb +90 -0
  77. data/lib/scout_apm/job_record.rb +4 -2
  78. data/lib/scout_apm/layaway_file.rb +4 -0
  79. data/lib/scout_apm/layer.rb +5 -2
  80. data/lib/scout_apm/layer_children_set.rb +9 -8
  81. data/lib/scout_apm/layer_converters/external_service_converter.rb +65 -0
  82. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +4 -0
  83. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +2 -0
  84. data/lib/scout_apm/layer_converters/trace_converter.rb +7 -4
  85. data/lib/scout_apm/logger.rb +5 -1
  86. data/lib/scout_apm/middleware.rb +1 -1
  87. data/lib/scout_apm/periodic_work.rb +19 -0
  88. data/lib/scout_apm/remote/message.rb +4 -0
  89. data/lib/scout_apm/remote/server.rb +13 -1
  90. data/lib/scout_apm/reporter.rb +8 -3
  91. data/lib/scout_apm/reporting.rb +2 -1
  92. data/lib/scout_apm/request_histograms.rb +8 -0
  93. data/lib/scout_apm/serializers/app_server_load_serializer.rb +4 -0
  94. data/lib/scout_apm/serializers/directive_serializer.rb +4 -0
  95. data/lib/scout_apm/serializers/external_service_serializer_to_json.rb +15 -0
  96. data/lib/scout_apm/serializers/payload_serializer.rb +4 -3
  97. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +10 -3
  98. data/lib/scout_apm/slow_policy/age_policy.rb +33 -0
  99. data/lib/scout_apm/slow_policy/percent_policy.rb +22 -0
  100. data/lib/scout_apm/slow_policy/percentile_policy.rb +24 -0
  101. data/lib/scout_apm/slow_policy/policy.rb +21 -0
  102. data/lib/scout_apm/slow_policy/speed_policy.rb +16 -0
  103. data/lib/scout_apm/slow_request_policy.rb +18 -77
  104. data/lib/scout_apm/store.rb +31 -1
  105. data/lib/scout_apm/tracer.rb +2 -2
  106. data/lib/scout_apm/tracked_request.rb +35 -4
  107. data/lib/scout_apm/utils/backtrace_parser.rb +3 -0
  108. data/lib/scout_apm/utils/marshal_logging.rb +90 -0
  109. data/lib/scout_apm/utils/sql_sanitizer.rb +47 -7
  110. data/lib/scout_apm/version.rb +1 -1
  111. data/lib/scout_apm.rb +46 -1
  112. data/scout_apm.gemspec +14 -9
  113. data/test/test_helper.rb +2 -2
  114. data/test/tmp/README.md +17 -0
  115. data/test/unit/agent_context_test.rb +29 -0
  116. data/test/unit/auto_instrument/anonymous_block_value.rb +7 -0
  117. data/test/unit/auto_instrument/assignments-instrumented.rb +31 -0
  118. data/test/unit/auto_instrument/assignments.rb +31 -0
  119. data/test/unit/auto_instrument/controller-ast.txt +57 -0
  120. data/test/unit/auto_instrument/controller-instrumented.rb +49 -0
  121. data/test/unit/auto_instrument/controller.rb +49 -0
  122. data/test/unit/auto_instrument/hanging_method.rb +6 -0
  123. data/test/unit/auto_instrument/rescue_from-instrumented.rb +13 -0
  124. data/test/unit/auto_instrument/rescue_from.rb +13 -0
  125. data/test/unit/auto_instrument_test.rb +62 -0
  126. data/test/unit/background_job_integrations/sidekiq_test.rb +17 -0
  127. data/test/unit/environment_test.rb +2 -2
  128. data/test/unit/error_service/error_buffer_test.rb +25 -0
  129. data/test/unit/error_service/ignored_exceptions_test.rb +49 -0
  130. data/test/unit/external_service_metric_set_test.rb +67 -0
  131. data/test/unit/external_service_metric_stats_test.rb +106 -0
  132. data/test/unit/ignored_uris_test.rb +6 -0
  133. data/test/unit/instruments/active_record_test.rb +40 -0
  134. data/test/unit/instruments/http_client_test.rb +24 -0
  135. data/test/unit/instruments/http_test.rb +24 -0
  136. data/test/unit/instruments/moped_test.rb +24 -0
  137. data/test/unit/instruments/net_http_test.rb +11 -1
  138. data/test/unit/instruments/redis_test.rb +24 -0
  139. data/test/unit/instruments/typhoeus_test.rb +42 -0
  140. data/test/unit/layer_children_set_test.rb +9 -0
  141. data/test/unit/remote/{test_message.rb → message_test.rb} +0 -0
  142. data/test/unit/remote/{test_router.rb → route_test.rb} +0 -0
  143. data/test/unit/remote/{test_server.rb → server_test.rb} +4 -1
  144. data/test/unit/request_histograms_test.rb +17 -0
  145. data/test/unit/serializers/payload_serializer_test.rb +39 -3
  146. data/test/unit/slow_request_policy_test.rb +41 -13
  147. data/test/unit/sql_sanitizer_test.rb +106 -0
  148. data/test/unit/tracer_test.rb +25 -0
  149. metadata +118 -60
  150. data/.travis.yml +0 -25
  151. data/lib/scout_apm/instruments/.DS_Store +0 -0
  152. data/lib/scout_apm/slow_job_policy.rb +0 -111
  153. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -25
  154. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -26
  155. data/test/unit/instruments/active_record_instruments_test.rb +0 -5
  156. data/test/unit/slow_job_policy_test.rb +0 -6
@@ -0,0 +1,90 @@
1
+ module ScoutApm
2
+ module Utils
3
+ class Error < StandardError; end
4
+
5
+ class InstanceVar
6
+ attr_reader :name
7
+ attr_reader :obj
8
+
9
+ def initialize(name, obj, parent)
10
+ @name = name
11
+ @obj = obj
12
+ @parent = parent
13
+ end
14
+
15
+ def to_s
16
+ "#{@name} - #{obj.class}"
17
+ end
18
+
19
+ def history
20
+ (@parent.nil? ? [] : @parent.history) + [to_s]
21
+ end
22
+ end
23
+
24
+ class MarshalLogging
25
+ def initialize(base_obj)
26
+ @base_obj = base_obj
27
+ end
28
+
29
+ def dive
30
+ to_investigate = [InstanceVar.new('Root', @base_obj, nil)]
31
+ max_to_check = 10000
32
+ checked = 0
33
+
34
+ while (var = to_investigate.shift)
35
+ checked += 1
36
+ if checked > max_to_check
37
+ return "Limiting Checks (max = #{max_to_check})"
38
+ end
39
+
40
+ obj = var.obj
41
+
42
+ if offending_hash?(obj)
43
+ return "Found undumpable object: #{var.history}"
44
+ end
45
+
46
+ if !dumps?(obj)
47
+ if obj.is_a? Hash
48
+ keys = obj.keys
49
+ keys.each do |key|
50
+ to_investigate.push(
51
+ InstanceVar.new(key.to_s, obj[key], var)
52
+ )
53
+ end
54
+ elsif obj.is_a? Array
55
+ obj.each_with_index do |value, idx|
56
+ to_investigate.push(
57
+ InstanceVar.new("Index #{idx}", value, var)
58
+ )
59
+ end
60
+ else
61
+ symbols = obj.instance_variables
62
+ if !symbols.any?
63
+ return "Found undumpable object: #{var.history}"
64
+ end
65
+
66
+ symbols.each do |sym|
67
+ to_investigate.push(
68
+ InstanceVar.new(sym, obj.instance_variable_get(sym), var)
69
+ )
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ true
76
+ end
77
+
78
+ def dumps?(obj)
79
+ Marshal.dump(obj)
80
+ true
81
+ rescue TypeError
82
+ false
83
+ end
84
+
85
+ def offending_hash?(obj)
86
+ obj.is_a?(Hash) && !obj.default_proc.nil?
87
+ end
88
+ end
89
+ end
90
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: false
2
+
1
3
  require 'scout_apm/environment'
2
4
 
3
5
  # Removes actual values from SQL. Used to both obfuscate the SQL and group
@@ -5,12 +7,37 @@ require 'scout_apm/environment'
5
7
  module ScoutApm
6
8
  module Utils
7
9
  class SqlSanitizer
8
- if ScoutApm::Environment.instance.ruby_187?
9
- require 'scout_apm/utils/sql_sanitizer_regex_1_8_7'
10
- else
11
- require 'scout_apm/utils/sql_sanitizer_regex'
12
- end
13
- include ScoutApm::Utils::SqlRegex
10
+ MULTIPLE_SPACES = %r|\s+|.freeze
11
+ MULTIPLE_QUESTIONS = /\?(,\?)+/.freeze
12
+
13
+ PSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*\z|.freeze
14
+ PSQL_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
15
+ PSQL_REMOVE_JSON_STRINGS = /:"(?:[^"]|"")*"/.freeze
16
+ PSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
17
+ PSQL_AFTER_SELECT = /(?:SELECT\s+).*?(?:WHERE|FROM\z)/im.freeze # Should be everything between a FROM and a WHERE
18
+ PSQL_PLACEHOLDER = /\$\d+/.freeze
19
+ PSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
20
+ PSQL_AFTER_FROM = /(?:FROM\s+).*?(?:WHERE|\z)/im.freeze # Should be everything between a FROM and a WHERE
21
+ PSQL_AFTER_FROM_AS = /(?:FROM\s+).*?(?:AS|\z)/im.freeze # Should be everything between a FROM and AS without WHERE
22
+ PSQL_AFTER_JOIN = /(?:JOIN\s+).*?\z/im.freeze
23
+ PSQL_AFTER_WHERE = /(?:WHERE\s+).*?(?:SELECT|\z)/im.freeze
24
+ PSQL_AFTER_SET = /(?:SET\s+).*?(?:WHERE|\z)/im.freeze
25
+
26
+ MYSQL_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
27
+ MYSQL_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
28
+ MYSQL_REMOVE_SINGLE_QUOTE_STRINGS = %r{'(?:\\'|[^']|'')*'}.freeze
29
+ MYSQL_REMOVE_DOUBLE_QUOTE_STRINGS = %r{"(?:\\"|[^"]|"")*"}.freeze
30
+ MYSQL_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
31
+
32
+ SQLITE_VAR_INTERPOLATION = %r|\[\[.*\]\]\s*$|.freeze
33
+ SQLITE_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
34
+ SQLITE_REMOVE_INTEGERS = /(?<!LIMIT )\b\d+\b/.freeze
35
+
36
+ # => "EXEC sp_executesql N'SELECT [users].* FROM [users] WHERE (age > 50) ORDER BY [users].[id] ASC OFFSET 0 ROWS FETCH NEXT @0 ROWS ONLY', N'@0 int', @0 = 10"
37
+ SQLSERVER_REMOVE_EXECUTESQL = /EXEC sp_executesql (N')?/.freeze
38
+ SQLSERVER_REMOVE_STRINGS = /'(?:[^']|'')*'/.freeze
39
+ SQLSERVER_REMOVE_INTEGERS = /(?<!LIMIT )\b(?<!@)\d+\b/.freeze
40
+ SQLSERVER_IN_CLAUSE = /IN\s+\(\?[^\)]*\)/.freeze
14
41
 
15
42
  attr_accessor :database_engine
16
43
 
@@ -34,15 +61,28 @@ module ScoutApm
34
61
  when :postgres then to_s_postgres
35
62
  when :mysql then to_s_mysql
36
63
  when :sqlite then to_s_sqlite
64
+ when :sqlserver then to_s_sqlserver
37
65
  end
38
66
  end
39
67
 
40
68
  private
41
69
 
70
+ def to_s_sqlserver
71
+ sql.gsub!(SQLSERVER_REMOVE_EXECUTESQL, '')
72
+ sql.gsub!(SQLSERVER_REMOVE_STRINGS, '?')
73
+ sql.gsub!(SQLSERVER_REMOVE_INTEGERS, '?')
74
+ sql.gsub!(SQLSERVER_IN_CLAUSE, 'IN (?)')
75
+ sql
76
+ end
77
+
42
78
  def to_s_postgres
43
79
  sql.gsub!(PSQL_PLACEHOLDER, '?')
44
80
  sql.gsub!(PSQL_VAR_INTERPOLATION, '')
45
- sql.gsub!(PSQL_REMOVE_STRINGS, '?')
81
+ # sql.gsub!(PSQL_REMOVE_STRINGS, '?')
82
+ sql.gsub!(PSQL_AFTER_WHERE) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
83
+ sql.gsub!(PSQL_AFTER_FROM_AS) {|c| c.gsub(PSQL_REMOVE_JSON_STRINGS, ':"?"')}
84
+ sql.gsub!(PSQL_AFTER_JOIN) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
85
+ sql.gsub!(PSQL_AFTER_SET) {|c| c.gsub(PSQL_REMOVE_STRINGS, '?')}
46
86
  sql.gsub!(PSQL_REMOVE_INTEGERS, '?')
47
87
  sql.gsub!(PSQL_IN_CLAUSE, 'IN (?)')
48
88
  sql.gsub!(MULTIPLE_SPACES, ' ')
@@ -1,3 +1,3 @@
1
1
  module ScoutApm
2
- VERSION = "2.5.1"
2
+ VERSION = "5.3.2"
3
3
  end
data/lib/scout_apm.rb CHANGED
@@ -27,6 +27,7 @@ require 'rusage'
27
27
  #####################################
28
28
  require 'scout_apm/version'
29
29
 
30
+ require 'scout_apm/exceptions'
30
31
  require 'scout_apm/debug'
31
32
  require 'scout_apm/tracked_request'
32
33
  require 'scout_apm/layer'
@@ -42,6 +43,7 @@ require 'scout_apm/layer_converters/job_converter'
42
43
  require 'scout_apm/layer_converters/slow_job_converter'
43
44
  require 'scout_apm/layer_converters/metric_converter'
44
45
  require 'scout_apm/layer_converters/database_converter'
46
+ require 'scout_apm/layer_converters/external_service_converter'
45
47
  require 'scout_apm/layer_converters/slow_request_converter'
46
48
  require 'scout_apm/layer_converters/request_queue_time_converter'
47
49
  require 'scout_apm/layer_converters/allocation_metric_converter'
@@ -58,10 +60,13 @@ require 'scout_apm/server_integrations/webrick'
58
60
  require 'scout_apm/server_integrations/null'
59
61
 
60
62
  require 'scout_apm/background_job_integrations/sidekiq'
63
+ require 'scout_apm/background_job_integrations/faktory'
61
64
  require 'scout_apm/background_job_integrations/delayed_job'
62
65
  require 'scout_apm/background_job_integrations/resque'
63
66
  require 'scout_apm/background_job_integrations/shoryuken'
64
67
  require 'scout_apm/background_job_integrations/sneakers'
68
+ require 'scout_apm/background_job_integrations/que'
69
+ require 'scout_apm/background_job_integrations/legacy_sneakers'
65
70
 
66
71
  require 'scout_apm/framework_integrations/rails_2'
67
72
  require 'scout_apm/framework_integrations/rails_3_or_4'
@@ -76,9 +81,13 @@ require 'scout_apm/histogram'
76
81
 
77
82
  require 'scout_apm/instruments/net_http'
78
83
  require 'scout_apm/instruments/http_client'
84
+ require 'scout_apm/instruments/http'
85
+ require 'scout_apm/instruments/typhoeus'
79
86
  require 'scout_apm/instruments/moped'
80
87
  require 'scout_apm/instruments/mongoid'
88
+ require 'scout_apm/instruments/memcached'
81
89
  require 'scout_apm/instruments/redis'
90
+ require 'scout_apm/instruments/redis5'
82
91
  require 'scout_apm/instruments/influxdb'
83
92
  require 'scout_apm/instruments/elasticsearch'
84
93
  require 'scout_apm/instruments/active_record'
@@ -110,6 +119,7 @@ require 'scout_apm/utils/time'
110
119
  require 'scout_apm/utils/unique_id'
111
120
  require 'scout_apm/utils/numbers'
112
121
  require 'scout_apm/utils/gzip_helper'
122
+ require 'scout_apm/utils/marshal_logging'
113
123
 
114
124
  require 'scout_apm/config'
115
125
  require 'scout_apm/environment'
@@ -124,6 +134,7 @@ require 'scout_apm/bucket_name_splitter'
124
134
  require 'scout_apm/stack_item'
125
135
  require 'scout_apm/metric_set'
126
136
  require 'scout_apm/db_query_metric_set'
137
+ require 'scout_apm/external_service_metric_set'
127
138
  require 'scout_apm/store'
128
139
  require 'scout_apm/fake_store'
129
140
  require 'scout_apm/tracer'
@@ -136,12 +147,18 @@ require 'scout_apm/synchronous_recorder'
136
147
  require 'scout_apm/metric_meta'
137
148
  require 'scout_apm/metric_stats'
138
149
  require 'scout_apm/db_query_metric_stats'
150
+ require 'scout_apm/external_service_metric_stats'
139
151
  require 'scout_apm/slow_transaction'
140
152
  require 'scout_apm/slow_job_record'
141
153
  require 'scout_apm/detailed_trace'
142
154
  require 'scout_apm/scored_item_set'
155
+
143
156
  require 'scout_apm/slow_request_policy'
144
- require 'scout_apm/slow_job_policy'
157
+ require 'scout_apm/slow_policy/age_policy'
158
+ require 'scout_apm/slow_policy/speed_policy'
159
+ require 'scout_apm/slow_policy/percent_policy'
160
+ require 'scout_apm/slow_policy/percentile_policy'
161
+
145
162
  require 'scout_apm/job_record'
146
163
  require 'scout_apm/request_histograms'
147
164
  require 'scout_apm/transaction_time_consumed'
@@ -156,6 +173,7 @@ require 'scout_apm/serializers/slow_jobs_serializer_to_json'
156
173
  require 'scout_apm/serializers/metrics_to_json_serializer'
157
174
  require 'scout_apm/serializers/histograms_serializer_to_json'
158
175
  require 'scout_apm/serializers/db_query_serializer_to_json'
176
+ require 'scout_apm/serializers/external_service_serializer_to_json'
159
177
  require 'scout_apm/serializers/directive_serializer'
160
178
  require 'scout_apm/serializers/app_server_load_serializer'
161
179
 
@@ -182,6 +200,17 @@ require 'scout_apm/tasks/support'
182
200
  require 'scout_apm/extensions/config'
183
201
  require 'scout_apm/extensions/transaction_callback_payload'
184
202
 
203
+ require 'scout_apm/error'
204
+ require 'scout_apm/error_service'
205
+ require 'scout_apm/error_service/middleware'
206
+ require 'scout_apm/error_service/notifier'
207
+ require 'scout_apm/error_service/sidekiq'
208
+ require 'scout_apm/error_service/ignored_exceptions'
209
+ require 'scout_apm/error_service/error_buffer'
210
+ require 'scout_apm/error_service/error_record'
211
+ require 'scout_apm/error_service/periodic_work'
212
+ require 'scout_apm/error_service/payload'
213
+
185
214
  if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR) && Rails::VERSION::MAJOR >= 3 && defined?(Rails::Railtie)
186
215
  module ScoutApm
187
216
  class Railtie < Rails::Railtie
@@ -194,6 +223,22 @@ if defined?(Rails) && defined?(Rails::VERSION) && defined?(Rails::VERSION::MAJOR
194
223
  # Attempt to start right away, this will work best for preloading apps, Unicorn & Puma & similar
195
224
  ScoutApm::Agent.instance.install
196
225
 
226
+ if ScoutApm::Agent.instance.context.config.value("auto_instruments")
227
+ if defined?(Parser::TreeRewriter)
228
+ ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled.")
229
+ require 'scout_apm/auto_instrument'
230
+ else # AutoInstruments is turned on, but we don't he the prerequisites to use it
231
+ ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is enabled, but Parser::TreeRewriter is missing. Update 'parser' gem to >= 2.5.0.")
232
+ end
233
+ else
234
+ ScoutApm::Agent.instance.context.logger.debug("AutoInstruments is disabled.")
235
+ end
236
+
237
+ if ScoutApm::Agent.instance.context.config.value("errors_enabled")
238
+ app.config.middleware.insert_after ActionDispatch::DebugExceptions, ScoutApm::ErrorService::Middleware
239
+ ScoutApm::ErrorService::Sidekiq.new.install
240
+ end
241
+
197
242
  # Install the middleware every time in development mode.
198
243
  # The middleware is a noop if dev_trace is not enabled in config
199
244
  if Rails.env.development?
data/scout_apm.gemspec CHANGED
@@ -10,9 +10,7 @@ Gem::Specification.new do |s|
10
10
  s.homepage = "https://github.com/scoutapp/scout_apm_ruby"
11
11
  s.summary = "Ruby application performance monitoring"
12
12
  s.description = "Monitors Ruby apps and reports detailed metrics on performance to Scout."
13
- s.license = "Proprietary (See LICENSE.md)"
14
-
15
- s.rubyforge_project = "scout_apm"
13
+ s.license = "MIT"
16
14
 
17
15
  s.files = `git ls-files`.split("\n")
18
16
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
@@ -21,6 +19,8 @@ Gem::Specification.new do |s|
21
19
  s.extensions << 'ext/allocations/extconf.rb'
22
20
  s.extensions << 'ext/rusage/extconf.rb'
23
21
 
22
+ s.required_ruby_version = '>= 2.1'
23
+
24
24
  s.add_development_dependency "minitest"
25
25
  s.add_development_dependency "mocha"
26
26
  s.add_development_dependency "pry"
@@ -28,11 +28,16 @@ Gem::Specification.new do |s|
28
28
  s.add_development_dependency "rake-compiler"
29
29
  s.add_development_dependency "addressable"
30
30
  s.add_development_dependency "activesupport"
31
+ s.add_runtime_dependency "parser"
32
+
33
+ # These are general development dependencies which are used in instrumentation
34
+ # tests. Specific versions are pulled in using specific gemfiles, e.g.
35
+ # `gems/rails3.gemfile`.
36
+ s.add_development_dependency "activerecord"
37
+ s.add_development_dependency "sqlite3"
31
38
 
32
- if RUBY_VERSION >= "1.9.3"
33
- s.add_development_dependency "rubocop"
34
- s.add_development_dependency "guard"
35
- s.add_development_dependency "guard-minitest"
36
- s.add_development_dependency "m"
37
- end
39
+ s.add_development_dependency "rubocop"
40
+ s.add_development_dependency "guard"
41
+ s.add_development_dependency "guard-minitest"
42
+ s.add_development_dependency "m"
38
43
  end
data/test/test_helper.rb CHANGED
@@ -5,7 +5,7 @@ SimpleCov.start
5
5
  require 'minitest/autorun'
6
6
  require 'minitest/unit'
7
7
  require 'minitest/pride'
8
- require 'mocha/mini_test'
8
+ require 'mocha/minitest'
9
9
  require 'pry'
10
10
 
11
11
 
@@ -100,7 +100,7 @@ class Minitest::Test
100
100
  end
101
101
 
102
102
  def agent_context
103
- ScoutApm::AgentContext.new
103
+ ScoutApm::Agent.instance.context
104
104
  end
105
105
 
106
106
  DATA_FILE_DIR = File.dirname(__FILE__) + '/tmp'
@@ -0,0 +1,17 @@
1
+ # Temporary Data
2
+
3
+ Use this directory for temporary test files.
4
+
5
+ ## Usage
6
+
7
+ The `DATA_FILE_DIR` constant points at this directory:
8
+
9
+ ```ruby
10
+ class MyTest < Minitest::Test
11
+ def database_path
12
+ File.expand_path('test.sqlite3', DATA_FILE_DIR)
13
+ end
14
+
15
+ # ... tests
16
+ end
17
+ ```
@@ -0,0 +1,29 @@
1
+ require "test_helper"
2
+
3
+ require "scout_apm/agent_context"
4
+
5
+ class AgentContextTest < Minitest::Test
6
+ def test_has_error_service_ignored_exceptions
7
+ context = ScoutApm::AgentContext.new
8
+ assert ScoutApm::ErrorService::IgnoredExceptions, context.ignored_exceptions.class
9
+ end
10
+
11
+ def test_has_error_buffer
12
+ context = ScoutApm::AgentContext.new
13
+ assert ScoutApm::ErrorService::ErrorBuffer, context.error_buffer.class
14
+ end
15
+
16
+
17
+ class TestPolicy
18
+ def call(req); 1; end
19
+ def stored!(req); end
20
+ end
21
+
22
+ def test_customize_slow_request_policy
23
+ context = ScoutApm::AgentContext.new
24
+ assert 4, context.slow_request_policy.policies
25
+
26
+ context.slow_request_policy.add(TestPolicy.new)
27
+ assert 5, context.slow_request_policy.policies
28
+ end
29
+ end
@@ -0,0 +1,7 @@
1
+ class TestController < ApplicationController
2
+ def index
3
+ quests = policy_scope(Quest.open)
4
+ quests.each { _1.current_user = current_user }
5
+ respond_with_proto quests
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+
2
+ class Assignments
3
+ def test_op_asgn
4
+ foo.bar += ::ScoutApm::AutoInstrument("User.size",["ROOT/test/unit/auto_instrument/assignments.rb:4:in `test_op_asgn'"]){User.size}
5
+ foo.bar -= ::ScoutApm::AutoInstrument("User.size",["ROOT/test/unit/auto_instrument/assignments.rb:5:in `test_op_asgn'"]){User.size}
6
+ end
7
+
8
+ def nested_assignment
9
+ @email ||= if (email = ::ScoutApm::AutoInstrument("session[\"email\"]",["ROOT/test/unit/auto_instrument/assignments.rb:9:in `nested_assignment'"]){session["email"]}).present?
10
+ ::ScoutApm::AutoInstrument("User.where(email: email).first",["ROOT/test/unit/auto_instrument/assignments.rb:10:in `nested_assignment'"]){User.where(email: email).first}
11
+ else
12
+ nil
13
+ end
14
+ end
15
+
16
+ def paginate_collection(coll)
17
+ page = (::ScoutApm::AutoInstrument("params[:page].present?",["ROOT/test/unit/auto_instrument/assignments.rb:17:in `paginate_collection'"]){params[:page].present?} ? ::ScoutApm::AutoInstrument("params[:page].to_i",["ROOT/test/unit/auto_instrument/assignments.rb:17:in `paginate_collection'"]){params[:page].to_i} : 1)
18
+ per_page = (::ScoutApm::AutoInstrument("params[:per_page].present?",["ROOT/test/unit/auto_instrument/assignments.rb:18:in `paginate_collection'"]){params[:per_page].present?} ? ::ScoutApm::AutoInstrument("params[:per_page].to_i",["ROOT/test/unit/auto_instrument/assignments.rb:18:in `paginate_collection'"]){params[:per_page].to_i} : 20)
19
+ pagination, self.collection = ::ScoutApm::AutoInstrument("pagy(...",["ROOT/test/unit/auto_instrument/assignments.rb:19:in `paginate_collection'"]){pagy(
20
+ coll,
21
+ items: per_page,
22
+ page: page
23
+ )}
24
+ ::ScoutApm::AutoInstrument("headers[PAGINATION_TOTAL_HEADER] = pagination.count.to_s",["ROOT/test/unit/auto_instrument/assignments.rb:24:in `paginate_collection'"]){headers[PAGINATION_TOTAL_HEADER] = pagination.count.to_s}
25
+ ::ScoutApm::AutoInstrument("headers[PAGINATION_TOTAL_PAGES_HEADER] = pagination.pages.to_s",["ROOT/test/unit/auto_instrument/assignments.rb:25:in `paginate_collection'"]){headers[PAGINATION_TOTAL_PAGES_HEADER] = pagination.pages.to_s}
26
+ ::ScoutApm::AutoInstrument("headers[PAGINATION_PER_PAGE_HEADER] = per_page.to_s",["ROOT/test/unit/auto_instrument/assignments.rb:26:in `paginate_collection'"]){headers[PAGINATION_PER_PAGE_HEADER] = per_page.to_s}
27
+ ::ScoutApm::AutoInstrument("headers[PAGINATION_PAGE_HEADER] = pagination.page.to_s",["ROOT/test/unit/auto_instrument/assignments.rb:27:in `paginate_collection'"]){headers[PAGINATION_PAGE_HEADER] = pagination.page.to_s}
28
+ ::ScoutApm::AutoInstrument("headers[PAGINATION_NEXT_PAGE_HEADER] = pagination.next.to_s",["ROOT/test/unit/auto_instrument/assignments.rb:28:in `paginate_collection'"]){headers[PAGINATION_NEXT_PAGE_HEADER] = pagination.next.to_s}
29
+ ::ScoutApm::AutoInstrument("collection",["ROOT/test/unit/auto_instrument/assignments.rb:29:in `paginate_collection'"]){collection}
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+
2
+ class Assignments
3
+ def test_op_asgn
4
+ foo.bar += User.size
5
+ foo.bar -= User.size
6
+ end
7
+
8
+ def nested_assignment
9
+ @email ||= if (email = session["email"]).present?
10
+ User.where(email: email).first
11
+ else
12
+ nil
13
+ end
14
+ end
15
+
16
+ def paginate_collection(coll)
17
+ page = (params[:page].present? ? params[:page].to_i : 1)
18
+ per_page = (params[:per_page].present? ? params[:per_page].to_i : 20)
19
+ pagination, self.collection = pagy(
20
+ coll,
21
+ items: per_page,
22
+ page: page
23
+ )
24
+ headers[PAGINATION_TOTAL_HEADER] = pagination.count.to_s
25
+ headers[PAGINATION_TOTAL_PAGES_HEADER] = pagination.pages.to_s
26
+ headers[PAGINATION_PER_PAGE_HEADER] = per_page.to_s
27
+ headers[PAGINATION_PAGE_HEADER] = pagination.page.to_s
28
+ headers[PAGINATION_NEXT_PAGE_HEADER] = pagination.next.to_s
29
+ collection
30
+ end
31
+ end
@@ -0,0 +1,57 @@
1
+ s(:class,
2
+ s(:const, nil, :ClientsController),
3
+ s(:const, nil, :ApplicationController),
4
+ s(:begin,
5
+ s(:send, nil, :before_action,
6
+ s(:sym, :check_authorization)),
7
+ s(:def, :index,
8
+ s(:args),
9
+ s(:if,
10
+ s(:send,
11
+ s(:send,
12
+ s(:send, nil, :params), :[],
13
+ s(:sym, :status)), :==,
14
+ s(:str, "activated")),
15
+ s(:ivasgn, :@clients,
16
+ s(:send,
17
+ s(:const, nil, :Client), :activated)),
18
+ s(:ivasgn, :@clients,
19
+ s(:send,
20
+ s(:const, nil, :Client), :inactivated)))),
21
+ s(:def, :create,
22
+ s(:args),
23
+ s(:begin,
24
+ s(:ivasgn, :@client,
25
+ s(:send,
26
+ s(:const, nil, :Client), :new,
27
+ s(:send,
28
+ s(:send, nil, :params), :[],
29
+ s(:sym, :client)))),
30
+ s(:if,
31
+ s(:send,
32
+ s(:ivar, :@client), :save),
33
+ s(:send, nil, :redirect_to,
34
+ s(:ivar, :@client)),
35
+ s(:send, nil, :render,
36
+ s(:str, "new"))))),
37
+ s(:def, :edit,
38
+ s(:args),
39
+ s(:begin,
40
+ s(:ivasgn, :@client,
41
+ s(:send,
42
+ s(:const, nil, :Client), :new,
43
+ s(:send,
44
+ s(:send, nil, :params), :[],
45
+ s(:sym, :client)))),
46
+ s(:if,
47
+ s(:send,
48
+ s(:send, nil, :request), :post?),
49
+ s(:block,
50
+ s(:send,
51
+ s(:ivar, :@client), :transaction),
52
+ s(:args),
53
+ s(:send,
54
+ s(:ivar, :@client), :update_attributes,
55
+ s(:send,
56
+ s(:send, nil, :params), :[],
57
+ s(:sym, :client)))), nil)))))
@@ -0,0 +1,49 @@
1
+
2
+ class ClientsController < ApplicationController
3
+ before_action :check_authorization
4
+
5
+ def index
6
+ if ::ScoutApm::AutoInstrument("params[:status] == \"activated\"",["ROOT/test/unit/auto_instrument/controller.rb:6:in `index'"]){params[:status] == "activated"}
7
+ @clients = ::ScoutApm::AutoInstrument("Client.activated",["ROOT/test/unit/auto_instrument/controller.rb:7:in `index'"]){Client.activated}
8
+ else
9
+ @clients = ::ScoutApm::AutoInstrument("Client.inactivated",["ROOT/test/unit/auto_instrument/controller.rb:9:in `index'"]){Client.inactivated}
10
+ end
11
+ end
12
+
13
+ def create
14
+ @client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["ROOT/test/unit/auto_instrument/controller.rb:14:in `create'"]){Client.new(params[:client])}
15
+ if ::ScoutApm::AutoInstrument("@client.save",["ROOT/test/unit/auto_instrument/controller.rb:15:in `create'"]){@client.save}
16
+ ::ScoutApm::AutoInstrument("redirect_to @client",["ROOT/test/unit/auto_instrument/controller.rb:16:in `create'"]){redirect_to @client}
17
+ else
18
+ # This line overrides the default rendering behavior, which
19
+ # would have been to render the "create" view.
20
+ ::ScoutApm::AutoInstrument("render \"new\"",["ROOT/test/unit/auto_instrument/controller.rb:20:in `create'"]){render "new"}
21
+ end
22
+ end
23
+
24
+ def edit
25
+ @client = ::ScoutApm::AutoInstrument("Client.new(params[:client])",["ROOT/test/unit/auto_instrument/controller.rb:25:in `edit'"]){Client.new(params[:client])}
26
+
27
+ if ::ScoutApm::AutoInstrument("request.post?",["ROOT/test/unit/auto_instrument/controller.rb:27:in `edit'"]){request.post?}
28
+ ::ScoutApm::AutoInstrument("@client.transaction do...",["ROOT/test/unit/auto_instrument/controller.rb:28:in `edit'"]){@client.transaction do
29
+ @client.update_attributes(params[:client])
30
+ end}
31
+ end
32
+ end
33
+
34
+ def data
35
+ @clients = ::ScoutApm::AutoInstrument("Client.all",["ROOT/test/unit/auto_instrument/controller.rb:35:in `data'"]){Client.all}
36
+
37
+ formatter = ::ScoutApm::AutoInstrument("proc do |row|...",["ROOT/test/unit/auto_instrument/controller.rb:37:in `data'"]){proc do |row|
38
+ row.to_json
39
+ end}
40
+
41
+ ::ScoutApm::AutoInstrument("respond_with @clients.each(&formatter).join(\"\\n\"), :content_type => 'application/json; boundary=NL'",["ROOT/test/unit/auto_instrument/controller.rb:41:in `data'"]){respond_with @clients.each(&formatter).join("\n"), :content_type => 'application/json; boundary=NL'}
42
+ end
43
+
44
+ def things
45
+ x = {}
46
+ x[:this] ||= 'foo'
47
+ x[:that] &&= ::ScoutApm::AutoInstrument("'foo'.size",["ROOT/test/unit/auto_instrument/controller.rb:47:in `things'"]){'foo'.size}
48
+ end
49
+ end
@@ -0,0 +1,49 @@
1
+
2
+ class ClientsController < ApplicationController
3
+ before_action :check_authorization
4
+
5
+ def index
6
+ if params[:status] == "activated"
7
+ @clients = Client.activated
8
+ else
9
+ @clients = Client.inactivated
10
+ end
11
+ end
12
+
13
+ def create
14
+ @client = Client.new(params[:client])
15
+ if @client.save
16
+ redirect_to @client
17
+ else
18
+ # This line overrides the default rendering behavior, which
19
+ # would have been to render the "create" view.
20
+ render "new"
21
+ end
22
+ end
23
+
24
+ def edit
25
+ @client = Client.new(params[:client])
26
+
27
+ if request.post?
28
+ @client.transaction do
29
+ @client.update_attributes(params[:client])
30
+ end
31
+ end
32
+ end
33
+
34
+ def data
35
+ @clients = Client.all
36
+
37
+ formatter = proc do |row|
38
+ row.to_json
39
+ end
40
+
41
+ respond_with @clients.each(&formatter).join("\n"), :content_type => 'application/json; boundary=NL'
42
+ end
43
+
44
+ def things
45
+ x = {}
46
+ x[:this] ||= 'foo'
47
+ x[:that] &&= 'foo'.size
48
+ end
49
+ end
@@ -0,0 +1,6 @@
1
+ class TestController < ApplicationController
2
+ end
3
+
4
+ def hanging_method
5
+ Test.first
6
+ end
@@ -0,0 +1,13 @@
1
+
2
+ class BrokenController < ApplicationController
3
+ rescue_from Exception do |e|
4
+ if e.is_a? Pundit::NotAuthorizedError
5
+ unauthorized_error
6
+ elsif e.is_a? ActionController::ParameterMissing
7
+ error(status: 422, message: e.message)
8
+ else
9
+ log_error(e)
10
+ error message: 'Internal error', exception: e
11
+ end
12
+ end
13
+ end