scout_apm 2.6.10 → 3.0.0.pre0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (233) hide show
  1. checksums.yaml +5 -5
  2. data/.gitignore +1 -2
  3. data/.rubocop.yml +3 -11
  4. data/CHANGELOG.markdown +4 -362
  5. data/Gemfile +1 -14
  6. data/README.markdown +7 -52
  7. data/Rakefile +1 -0
  8. data/ext/allocations/allocations.c +1 -7
  9. data/ext/allocations/extconf.rb +0 -1
  10. data/ext/rusage/rusage.c +0 -26
  11. data/ext/stacks/extconf.rb +37 -0
  12. data/ext/stacks/scout_atomics.h +86 -0
  13. data/ext/stacks/stacks.c +811 -0
  14. data/lib/scout_apm/agent/logging.rb +69 -0
  15. data/lib/scout_apm/agent/reporting.rb +126 -0
  16. data/lib/scout_apm/agent.rb +259 -138
  17. data/lib/scout_apm/app_server_load.rb +15 -41
  18. data/lib/scout_apm/attribute_arranger.rb +3 -14
  19. data/lib/scout_apm/background_job_integrations/delayed_job.rb +1 -70
  20. data/lib/scout_apm/background_job_integrations/sidekiq.rb +24 -31
  21. data/lib/scout_apm/background_worker.rb +12 -23
  22. data/lib/scout_apm/capacity.rb +57 -0
  23. data/lib/scout_apm/config.rb +37 -206
  24. data/lib/scout_apm/context.rb +4 -20
  25. data/lib/scout_apm/deploy_integrations/capistrano_2.cap +12 -0
  26. data/lib/scout_apm/deploy_integrations/capistrano_2.rb +83 -0
  27. data/lib/scout_apm/deploy_integrations/capistrano_3.cap +12 -0
  28. data/lib/scout_apm/deploy_integrations/capistrano_3.rb +88 -0
  29. data/lib/scout_apm/environment.rb +28 -42
  30. data/lib/scout_apm/fake_store.rb +0 -12
  31. data/lib/scout_apm/framework_integrations/rails_2.rb +1 -2
  32. data/lib/scout_apm/framework_integrations/rails_3_or_4.rb +6 -17
  33. data/lib/scout_apm/framework_integrations/sinatra.rb +1 -1
  34. data/lib/scout_apm/histogram.rb +3 -12
  35. data/lib/scout_apm/instant/assets/xmlhttp_instrumentation.html +2 -2
  36. data/lib/scout_apm/instant/middleware.rb +54 -202
  37. data/lib/scout_apm/instant_reporting.rb +7 -7
  38. data/lib/scout_apm/instruments/.DS_Store +0 -0
  39. data/lib/scout_apm/instruments/action_controller_rails_2.rb +9 -15
  40. data/lib/scout_apm/instruments/action_controller_rails_3_rails4.rb +76 -124
  41. data/lib/scout_apm/instruments/active_record.rb +29 -324
  42. data/lib/scout_apm/instruments/delayed_job.rb +57 -0
  43. data/lib/scout_apm/instruments/elasticsearch.rb +6 -10
  44. data/lib/scout_apm/instruments/grape.rb +9 -12
  45. data/lib/scout_apm/instruments/http_client.rb +7 -14
  46. data/lib/scout_apm/instruments/influxdb.rb +6 -10
  47. data/lib/scout_apm/instruments/middleware_detailed.rb +11 -15
  48. data/lib/scout_apm/instruments/middleware_summary.rb +5 -11
  49. data/lib/scout_apm/instruments/mongoid.rb +8 -39
  50. data/lib/scout_apm/instruments/moped.rb +6 -11
  51. data/lib/scout_apm/instruments/net_http.rb +9 -27
  52. data/lib/scout_apm/instruments/percentile_sampler.rb +23 -42
  53. data/lib/scout_apm/instruments/process/process_cpu.rb +6 -11
  54. data/lib/scout_apm/instruments/process/process_memory.rb +12 -17
  55. data/lib/scout_apm/instruments/rails_router.rb +6 -12
  56. data/lib/scout_apm/instruments/redis.rb +6 -10
  57. data/lib/scout_apm/instruments/sinatra.rb +4 -5
  58. data/lib/scout_apm/job_record.rb +2 -4
  59. data/lib/scout_apm/layaway.rb +34 -88
  60. data/lib/scout_apm/layaway_file.rb +3 -13
  61. data/lib/scout_apm/layer.rb +60 -25
  62. data/lib/scout_apm/layer_converters/allocation_metric_converter.rb +6 -7
  63. data/lib/scout_apm/layer_converters/converter_base.rb +14 -203
  64. data/lib/scout_apm/layer_converters/depth_first_walker.rb +10 -22
  65. data/lib/scout_apm/layer_converters/error_converter.rb +8 -8
  66. data/lib/scout_apm/layer_converters/job_converter.rb +50 -37
  67. data/lib/scout_apm/layer_converters/metric_converter.rb +19 -18
  68. data/lib/scout_apm/layer_converters/request_queue_time_converter.rb +13 -13
  69. data/lib/scout_apm/layer_converters/slow_job_converter.rb +116 -52
  70. data/lib/scout_apm/layer_converters/slow_request_converter.rb +120 -51
  71. data/lib/scout_apm/metric_meta.rb +5 -0
  72. data/lib/scout_apm/metric_set.rb +1 -9
  73. data/lib/scout_apm/metric_stats.rb +8 -7
  74. data/lib/scout_apm/middleware.rb +9 -7
  75. data/lib/scout_apm/reporter.rb +24 -71
  76. data/lib/scout_apm/request_histograms.rb +0 -12
  77. data/lib/scout_apm/request_manager.rb +7 -5
  78. data/lib/scout_apm/scored_item_set.rb +0 -7
  79. data/lib/scout_apm/serializers/app_server_load_serializer.rb +0 -4
  80. data/lib/scout_apm/serializers/deploy_serializer.rb +16 -0
  81. data/lib/scout_apm/serializers/directive_serializer.rb +0 -4
  82. data/lib/scout_apm/serializers/payload_serializer.rb +4 -11
  83. data/lib/scout_apm/serializers/payload_serializer_to_json.rb +16 -35
  84. data/lib/scout_apm/serializers/slow_jobs_serializer_to_json.rb +1 -2
  85. data/lib/scout_apm/server_integrations/passenger.rb +1 -1
  86. data/lib/scout_apm/server_integrations/puma.rb +2 -5
  87. data/lib/scout_apm/slow_job_policy.rb +13 -25
  88. data/lib/scout_apm/slow_job_record.rb +4 -13
  89. data/lib/scout_apm/slow_request_policy.rb +13 -25
  90. data/lib/scout_apm/slow_transaction.rb +5 -25
  91. data/lib/scout_apm/store.rb +32 -99
  92. data/lib/scout_apm/trace_compactor.rb +312 -0
  93. data/lib/scout_apm/tracer.rb +31 -35
  94. data/lib/scout_apm/tracked_request.rb +95 -262
  95. data/lib/scout_apm/utils/active_record_metric_name.rb +13 -88
  96. data/lib/scout_apm/utils/backtrace_parser.rb +4 -7
  97. data/lib/scout_apm/utils/fake_stacks.rb +87 -0
  98. data/lib/scout_apm/utils/installed_gems.rb +3 -7
  99. data/lib/scout_apm/utils/klass_helper.rb +2 -8
  100. data/lib/scout_apm/utils/null_logger.rb +13 -0
  101. data/lib/scout_apm/utils/sql_sanitizer.rb +5 -16
  102. data/lib/scout_apm/utils/sql_sanitizer_regex.rb +0 -7
  103. data/lib/scout_apm/utils/sql_sanitizer_regex_1_8_7.rb +0 -6
  104. data/lib/scout_apm/utils/unique_id.rb +0 -27
  105. data/lib/scout_apm/version.rb +2 -1
  106. data/lib/scout_apm.rb +25 -84
  107. data/scout_apm.gemspec +3 -17
  108. data/test/test_helper.rb +3 -57
  109. data/test/unit/agent_test.rb +54 -1
  110. data/test/unit/background_job_integrations/sidekiq_test.rb +3 -0
  111. data/test/unit/config_test.rb +12 -25
  112. data/test/unit/context_test.rb +4 -4
  113. data/test/unit/histogram_test.rb +4 -25
  114. data/test/unit/ignored_uris_test.rb +1 -1
  115. data/test/unit/instruments/active_record_instruments_test.rb +5 -0
  116. data/test/unit/layaway_test.rb +2 -62
  117. data/test/unit/serializers/payload_serializer_test.rb +15 -43
  118. data/test/unit/slow_request_policy_test.rb +6 -15
  119. data/test/unit/sql_sanitizer_test.rb +6 -53
  120. data/test/unit/store_test.rb +4 -73
  121. data/test/unit/utils/active_record_metric_name_test.rb +5 -59
  122. data/test/unit/utils/backtrace_parser_test.rb +1 -6
  123. data/tester.rb +53 -0
  124. metadata +28 -229
  125. data/.travis.yml +0 -26
  126. data/Guardfile +0 -43
  127. data/gems/README.md +0 -28
  128. data/gems/octoshark.gemfile +0 -4
  129. data/gems/rails3.gemfile +0 -5
  130. data/gems/rails4.gemfile +0 -4
  131. data/gems/rails5.gemfile +0 -4
  132. data/gems/rails6.gemfile +0 -4
  133. data/lib/scout_apm/agent/exit_handler.rb +0 -65
  134. data/lib/scout_apm/agent/preconditions.rb +0 -81
  135. data/lib/scout_apm/agent_context.rb +0 -261
  136. data/lib/scout_apm/auto_instrument/instruction_sequence.rb +0 -31
  137. data/lib/scout_apm/auto_instrument/layer.rb +0 -23
  138. data/lib/scout_apm/auto_instrument/parser.rb +0 -27
  139. data/lib/scout_apm/auto_instrument/rails.rb +0 -175
  140. data/lib/scout_apm/auto_instrument.rb +0 -5
  141. data/lib/scout_apm/background_job_integrations/legacy_sneakers.rb +0 -55
  142. data/lib/scout_apm/background_job_integrations/que.rb +0 -134
  143. data/lib/scout_apm/background_job_integrations/resque.rb +0 -88
  144. data/lib/scout_apm/background_job_integrations/shoryuken.rb +0 -124
  145. data/lib/scout_apm/background_job_integrations/sneakers.rb +0 -87
  146. data/lib/scout_apm/background_recorder.rb +0 -48
  147. data/lib/scout_apm/db_query_metric_set.rb +0 -97
  148. data/lib/scout_apm/db_query_metric_stats.rb +0 -102
  149. data/lib/scout_apm/debug.rb +0 -37
  150. data/lib/scout_apm/detailed_trace.rb +0 -217
  151. data/lib/scout_apm/error.rb +0 -27
  152. data/lib/scout_apm/error_service/error_buffer.rb +0 -39
  153. data/lib/scout_apm/error_service/error_record.rb +0 -211
  154. data/lib/scout_apm/error_service/ignored_exceptions.rb +0 -66
  155. data/lib/scout_apm/error_service/middleware.rb +0 -32
  156. data/lib/scout_apm/error_service/notifier.rb +0 -33
  157. data/lib/scout_apm/error_service/payload.rb +0 -47
  158. data/lib/scout_apm/error_service/periodic_work.rb +0 -17
  159. data/lib/scout_apm/error_service/railtie.rb +0 -11
  160. data/lib/scout_apm/error_service/sidekiq.rb +0 -80
  161. data/lib/scout_apm/error_service.rb +0 -32
  162. data/lib/scout_apm/extensions/config.rb +0 -87
  163. data/lib/scout_apm/extensions/transaction_callback_payload.rb +0 -74
  164. data/lib/scout_apm/git_revision.rb +0 -59
  165. data/lib/scout_apm/instrument_manager.rb +0 -88
  166. data/lib/scout_apm/instruments/action_view.rb +0 -141
  167. data/lib/scout_apm/instruments/http.rb +0 -48
  168. data/lib/scout_apm/instruments/memcached.rb +0 -43
  169. data/lib/scout_apm/instruments/resque.rb +0 -39
  170. data/lib/scout_apm/instruments/samplers.rb +0 -11
  171. data/lib/scout_apm/layer_children_set.rb +0 -86
  172. data/lib/scout_apm/layer_converters/database_converter.rb +0 -70
  173. data/lib/scout_apm/layer_converters/find_layer_by_type.rb +0 -38
  174. data/lib/scout_apm/layer_converters/histograms.rb +0 -15
  175. data/lib/scout_apm/layer_converters/trace_converter.rb +0 -184
  176. data/lib/scout_apm/limited_layer.rb +0 -126
  177. data/lib/scout_apm/logger.rb +0 -158
  178. data/lib/scout_apm/periodic_work.rb +0 -47
  179. data/lib/scout_apm/rack.rb +0 -26
  180. data/lib/scout_apm/remote/message.rb +0 -27
  181. data/lib/scout_apm/remote/recorder.rb +0 -57
  182. data/lib/scout_apm/remote/router.rb +0 -49
  183. data/lib/scout_apm/remote/server.rb +0 -60
  184. data/lib/scout_apm/reporting.rb +0 -143
  185. data/lib/scout_apm/serializers/db_query_serializer_to_json.rb +0 -15
  186. data/lib/scout_apm/serializers/histograms_serializer_to_json.rb +0 -21
  187. data/lib/scout_apm/synchronous_recorder.rb +0 -30
  188. data/lib/scout_apm/tasks/doctor.rb +0 -75
  189. data/lib/scout_apm/tasks/support.rb +0 -22
  190. data/lib/scout_apm/transaction.rb +0 -13
  191. data/lib/scout_apm/transaction_time_consumed.rb +0 -51
  192. data/lib/scout_apm/utils/gzip_helper.rb +0 -24
  193. data/lib/scout_apm/utils/marshal_logging.rb +0 -90
  194. data/lib/scout_apm/utils/numbers.rb +0 -14
  195. data/lib/scout_apm/utils/scm.rb +0 -14
  196. data/lib/tasks/doctor.rake +0 -11
  197. data/test/tmp/README.md +0 -17
  198. data/test/unit/agent_context_test.rb +0 -15
  199. data/test/unit/auto_instrument/assignments-instrumented.rb +0 -31
  200. data/test/unit/auto_instrument/assignments.rb +0 -31
  201. data/test/unit/auto_instrument/controller-ast.txt +0 -57
  202. data/test/unit/auto_instrument/controller-instrumented.rb +0 -49
  203. data/test/unit/auto_instrument/controller.rb +0 -49
  204. data/test/unit/auto_instrument/rescue_from-instrumented.rb +0 -13
  205. data/test/unit/auto_instrument/rescue_from.rb +0 -13
  206. data/test/unit/auto_instrument_test.rb +0 -54
  207. data/test/unit/db_query_metric_set_test.rb +0 -67
  208. data/test/unit/db_query_metric_stats_test.rb +0 -113
  209. data/test/unit/error_service/error_buffer_test.rb +0 -25
  210. data/test/unit/error_service/ignored_exceptions_test.rb +0 -49
  211. data/test/unit/extensions/periodic_callbacks_test.rb +0 -58
  212. data/test/unit/extensions/transaction_callbacks_test.rb +0 -58
  213. data/test/unit/fake_store_test.rb +0 -10
  214. data/test/unit/git_revision_test.rb +0 -15
  215. data/test/unit/instruments/active_record_test.rb +0 -40
  216. data/test/unit/instruments/net_http_test.rb +0 -27
  217. data/test/unit/instruments/percentile_sampler_test.rb +0 -133
  218. data/test/unit/layer_children_set_test.rb +0 -97
  219. data/test/unit/layer_converters/depth_first_walker_test.rb +0 -70
  220. data/test/unit/layer_converters/metric_converter_test.rb +0 -22
  221. data/test/unit/layer_converters/stubs.rb +0 -33
  222. data/test/unit/limited_layer_test.rb +0 -53
  223. data/test/unit/logger_test.rb +0 -69
  224. data/test/unit/remote/test_message.rb +0 -13
  225. data/test/unit/remote/test_router.rb +0 -33
  226. data/test/unit/remote/test_server.rb +0 -15
  227. data/test/unit/request_histograms_test.rb +0 -17
  228. data/test/unit/tracer_test.rb +0 -76
  229. data/test/unit/tracked_request_test.rb +0 -71
  230. data/test/unit/transaction_test.rb +0 -14
  231. data/test/unit/transaction_time_consumed_test.rb +0 -46
  232. data/test/unit/utils/numbers_test.rb +0 -15
  233. data/test/unit/utils/scm.rb +0 -17
@@ -7,24 +7,23 @@
7
7
  #
8
8
  module ScoutApm
9
9
  class Layaway
10
+ # How old a file needs to be in Seconds before it gets reported.
11
+ REPORTING_AGE = 120
12
+
10
13
  # How long to let a stale file sit before deleting it.
11
14
  # Letting it sit a bit may be useful for debugging
12
15
  STALE_AGE = 10 * 60
13
16
 
14
- # Failsafe to prevent writing layaway files if for some reason they are not being cleaned up
15
- MAX_FILES_LIMIT = 5000
16
-
17
17
  # A strftime format string for how we render timestamps in filenames.
18
18
  # Must be sortable as an integer
19
19
  TIME_FORMAT = "%Y%m%d%H%M"
20
20
 
21
- attr_reader :context
22
- def initialize(context)
23
- @context = context
24
- end
21
+ attr_accessor :config
22
+ attr_reader :environment
25
23
 
26
- def logger
27
- context.logger
24
+ def initialize(config, environment)
25
+ @config = config
26
+ @environment = environment
28
27
  end
29
28
 
30
29
  # Returns a Pathname object with the fully qualified directory where the layaway files can be placed.
@@ -35,54 +34,33 @@ module ScoutApm
35
34
  def directory
36
35
  return @directory if @directory
37
36
 
38
- data_file = context.config.value("data_file")
37
+ data_file = config.value("data_file")
39
38
  data_file = File.dirname(data_file) if data_file && !File.directory?(data_file)
40
39
 
41
40
  candidates = [
42
41
  data_file,
43
- "#{context.environment.root}/tmp",
42
+ "#{environment.root}/tmp",
44
43
  "/tmp"
45
44
  ].compact
46
45
 
47
46
  found = candidates.detect { |dir| File.writable?(dir) }
48
- logger.debug("Storing Layaway Files in #{found}")
47
+ ScoutApm::Agent.instance.logger.debug("Storing Layaway Files in #{found}")
49
48
  @directory = Pathname.new(found)
50
49
  end
51
50
 
52
- def write_reporting_period(reporting_period, files_limit = MAX_FILES_LIMIT)
53
- if at_layaway_file_limit?(files_limit)
54
- # This will happen constantly once we hit this case, so only log the first time
55
- @wrote_layaway_limit_error_message ||= logger.error("Layaway: Hit layaway file limit. Not writing to layaway file")
56
- return false
57
- end
58
- logger.debug("Layaway: wrote time period: #{reporting_period.timestamp}")
51
+ def write_reporting_period(reporting_period)
59
52
  filename = file_for(reporting_period.timestamp)
60
- layaway_file = LayawayFile.new(context, filename)
53
+ layaway_file = LayawayFile.new(filename)
61
54
  layaway_file.write(reporting_period)
62
- rescue => e
63
- logger.debug("Layaway: error writing: #{e.message}, #{e.backtrace.inspect}")
64
- raise e
65
55
  end
66
56
 
67
- # Claims a given timestamp by getting an exclusive lock on a timestamped
68
- # coordinator file. The coordinator file never contains data, it's just a
69
- # syncronization mechanism.
70
- #
71
- # Once the 'claim' is obtained:
72
- # * load and yield each ReportingPeriod from the layaway files.
73
- # * if there are reporting periods:
74
- # * yields any ReportingPeriods collected up from all the files.
75
- # * deletes all of the layaway files (including the coordinator) for the timestamp
76
- # * if not
77
- # * delete the coordinator
78
- # * remove any stale layaway files that may be hanging around.
79
- # * Finally unlock and ensure the coordinator file is cleared.
80
- #
81
- # If a claim file can't be obtained, return false without doing any work
82
- # Another process is handling the reporting.
57
+ # Claims a given timestamp (getting a lock on a particular filename),
58
+ # then yields ReportingPeriods collected up from all the files.
59
+ # If the yield returns truthy, delete the layaway files that made it up.
83
60
  def with_claim(timestamp)
84
61
  coordinator_file = glob_pattern(timestamp, :coordinator)
85
62
 
63
+
86
64
  begin
87
65
  # This file gets deleted only by a process that successfully created and obtained the exclusive lock
88
66
  f = File.open(coordinator_file, File::RDWR | File::CREAT | File::EXCL | File::NONBLOCK)
@@ -92,35 +70,25 @@ module ScoutApm
92
70
 
93
71
  begin
94
72
  if f
95
- begin
96
- logger.debug("Obtained Reporting Lock")
97
-
98
- log_layaway_file_information
99
73
 
100
- files = all_files_for(timestamp).reject{|l| l.to_s == coordinator_file.to_s }
101
- rps = files.map{ |layaway| LayawayFile.new(context, layaway).load }.compact
102
- if rps.any?
103
- yield rps
74
+ ScoutApm::Agent.instance.logger.debug("Obtained Reporting Lock")
104
75
 
105
- logger.debug("Layaway: Deleting the now-reported files for #{timestamp.to_s}")
106
- delete_files_for(timestamp) # also removes the coodinator_file
107
- else
108
- File.unlink(coordinator_file)
109
- logger.debug("Layaway: No files to report")
110
- end
76
+ files = all_files_for(timestamp).reject{|l| l.to_s == coordinator_file.to_s }
77
+ rps = files.map{ |layaway| LayawayFile.new(layaway).load }.compact
78
+ if rps.any?
79
+ yield rps
111
80
 
112
- logger.debug("Layaway: Checking for any stale files")
81
+ delete_files_for(timestamp) # also removes the coodinator_file
113
82
  delete_stale_files(timestamp.to_time - STALE_AGE)
114
-
115
- true
116
- rescue Exception => e
117
- logger.debug("Layaway: Caught an exception in with_claim, with the coordination file locked: #{e.message}, #{e.backtrace.inspect}")
118
- raise
119
- ensure
120
- # Unlock the file when done!
121
- f.flock(File::LOCK_UN | File::LOCK_NB)
122
- f.close
83
+ else
84
+ File.unlink(coordinator_file)
85
+ ScoutApm::Agent.instance.logger.debug("No layaway files to report")
123
86
  end
87
+
88
+ # Unlock the file when done!
89
+ f.flock(File::LOCK_UN | File::LOCK_NB)
90
+ f.close
91
+ true
124
92
  else
125
93
  # Didn't obtain lock, another process is reporting. Return false from this function, but otherwise no work
126
94
  false
@@ -129,10 +97,7 @@ module ScoutApm
129
97
  end
130
98
 
131
99
  def delete_files_for(timestamp)
132
- all_files_for(timestamp).each { |layaway|
133
- logger.debug("Layaway: Deleting file: #{layaway}")
134
- File.unlink(layaway)
135
- }
100
+ all_files_for(timestamp).each { |layaway| File.unlink(layaway) }
136
101
  end
137
102
 
138
103
  def delete_stale_files(older_than)
@@ -141,10 +106,8 @@ module ScoutApm
141
106
  compact.
142
107
  uniq.
143
108
  select { |timestamp| timestamp.to_i < older_than.strftime(TIME_FORMAT).to_i }.
144
- tap { |timestamps| logger.debug("Layaway: Deleting stale files with timestamps: #{timestamps.inspect}") }.
109
+ tap { |timestamps| ScoutApm::Agent.instance.logger.debug("Deleting stale layaway files with timestamps: #{timestamps.inspect}") }.
145
110
  map { |timestamp| delete_files_for(timestamp) }
146
- rescue => e
147
- logger.debug("Layaway: Problem deleting stale files: #{e.message}, #{e.backtrace.inspect}")
148
111
  end
149
112
 
150
113
  private
@@ -188,30 +151,13 @@ module ScoutApm
188
151
  end
189
152
 
190
153
  def timestamp_from_filename(filename)
191
- match = filename.match(%r{scout_(\d+)_\d+\.data\z})
154
+ match = filename.match(%r{scout_(.*)_.*\.data})
192
155
  if match
193
156
  match[1]
194
157
  else
195
158
  nil
196
159
  end
197
160
  end
198
-
199
- def at_layaway_file_limit?(files_limit = MAX_FILES_LIMIT)
200
- all_files_for(:all).count >= files_limit
201
- end
202
-
203
- def log_layaway_file_information
204
- files_in_temp = Dir["#{directory}/*"].count
205
-
206
- all_filenames = all_files_for(:all)
207
- count_per_timestamp = Hash[
208
- all_filenames.
209
- group_by {|f| timestamp_from_filename(f) }.
210
- map{ |timestamp, list| [timestamp, list.length] }
211
- ]
212
-
213
- logger.debug("Layaway: Total Files in #{directory}: #{files_in_temp}. Total Layaway Files: #{all_filenames.size}. By Timestamp: #{count_per_timestamp.inspect}")
214
- end
215
161
  end
216
162
  end
217
163
 
@@ -2,15 +2,9 @@
2
2
  module ScoutApm
3
3
  class LayawayFile
4
4
  attr_reader :path
5
- attr_reader :context
6
5
 
7
- def initialize(context, path)
6
+ def initialize(path)
8
7
  @path = path
9
- @context = context
10
- end
11
-
12
- def logger
13
- context.logger
14
8
  end
15
9
 
16
10
  def load
@@ -18,8 +12,8 @@ module ScoutApm
18
12
  deserialize(data)
19
13
  rescue NameError, ArgumentError, TypeError => e
20
14
  # Marshal error
21
- logger.info("LayawayFile: Unable to load data")
22
- logger.debug("#{e.message}, #{e.backtrace.join("\n\t")}")
15
+ ScoutApm::Agent.instance.logger.info("Unable to load data from Layaway file, resetting.")
16
+ ScoutApm::Agent.instance.logger.debug("#{e.message}, #{e.backtrace.join("\n\t")}")
23
17
  nil
24
18
  end
25
19
 
@@ -30,10 +24,6 @@ module ScoutApm
30
24
 
31
25
  def serialize(data)
32
26
  Marshal.dump(data)
33
- rescue
34
- ScoutApm::Agent.instance.logger.info("Failed Marshalling LayawayFile")
35
- ScoutApm::Agent.instance.logger.info(ScoutApm::Utils::MarshalLogging.new(data).dive) rescue nil
36
- raise
37
27
  end
38
28
 
39
29
  def deserialize(data)
@@ -13,17 +13,13 @@ module ScoutApm
13
13
  # instrumentation for an example of how this is useful
14
14
  attr_accessor :name
15
15
 
16
- # An array of children layers
16
+ # An array of children layers, in call order.
17
17
  # For instance, if we are in a middleware, there will likely be only a single
18
18
  # child, which is another middleware. In a Controller, we may have a handful
19
19
  # of children: [ActiveRecord, ActiveRecord, View, HTTP Call].
20
20
  #
21
21
  # This useful to get actual time spent in this layer vs. children time
22
- #
23
- # TODO: Check callers for compatibility w/ nil to avoid making an empty array
24
- def children
25
- @children || LayerChildrenSet.new
26
- end
22
+ attr_reader :children
27
23
 
28
24
  # Time objects recording the start & stop times of this layer
29
25
  attr_reader :start_time, :stop_time
@@ -36,42 +32,47 @@ module ScoutApm
36
32
 
37
33
  # If this layer took longer than a fixed amount of time, store the
38
34
  # backtrace of where it occurred.
39
- attr_accessor :backtrace
40
-
41
- # The file name associated with the layer. Only used for autoinstruments overhead logging.
42
- attr_accessor :file_name
35
+ attr_reader :backtrace
43
36
 
44
37
  # As we go through a part of a request, instrumentation can store additional data
45
38
  # Known Keys:
46
39
  # :record_count - The number of rows returned by an AR query (From notification instantiation.active_record)
47
40
  # :class_name - The ActiveRecord class name (From notification instantiation.active_record)
48
- #
49
- # If no annotations are ever set, this will return nil
50
41
  attr_reader :annotations
51
42
 
52
- attr_reader :allocations_start, :allocations_stop
43
+ # ScoutProf - trace_index is an index into the Stack structure in the C
44
+ # code, used to store captured traces.
45
+ attr_reader :trace_index
46
+
47
+ # ScoutProf - frame_index is an optimization to not capture a few frames
48
+ # during scoutprof instrumentation
49
+ attr_reader :frame_index
50
+
51
+ # Captured backtraces from ScoutProf. This is distinct from the backtrace
52
+ # attribute, which gets the ruby backtrace of any given layer. StackProf
53
+ # focuses on Controller layers, and requires a native extension and a
54
+ # reasonably recent Ruby.
55
+ attr_reader :traces
53
56
 
54
57
  BACKTRACE_CALLER_LIMIT = 50 # maximum number of lines to send thru for backtrace analysis
55
58
 
56
59
  def initialize(type, name, start_time = Time.now)
57
60
  @type = type
58
61
  @name = name
62
+ @annotations = {}
59
63
  @start_time = start_time
60
64
  @allocations_start = ScoutApm::Instruments::Allocations.count
61
65
  @allocations_stop = 0
62
-
63
- # initialize these only on first use
64
- @children = nil
65
- @annotations = nil
66
+ @children = [] # In order of calls
66
67
  @desc = nil
67
- end
68
68
 
69
- def limited?
70
- false
69
+ @traces = ScoutApm::TraceSet.new
70
+ @raw_frames = []
71
+ @frame_index = ScoutApm::Instruments::Stacks.current_frame_index # For efficiency sake, try to skip the bottom X frames when collecting traces
72
+ @trace_index = ScoutApm::Instruments::Stacks.current_trace_index
71
73
  end
72
74
 
73
75
  def add_child(child)
74
- @children ||= LayerChildrenSet.new
75
76
  @children << child
76
77
  end
77
78
 
@@ -90,7 +91,6 @@ module ScoutApm
90
91
 
91
92
  # This data is internal to ScoutApm, to add custom information, use the Context api.
92
93
  def annotate_layer(hsh)
93
- @annotations ||= {}
94
94
  @annotations.merge!(hsh)
95
95
  end
96
96
 
@@ -102,6 +102,14 @@ module ScoutApm
102
102
  @subscopable
103
103
  end
104
104
 
105
+ def traced!
106
+ @traced = true
107
+ end
108
+
109
+ def traced?
110
+ @traced
111
+ end
112
+
105
113
  # This is the old style name. This function is used for now, but should be
106
114
  # removed, and the new type & name split should be enforced through the
107
115
  # app.
@@ -116,13 +124,42 @@ module ScoutApm
116
124
  # In Ruby 2.0+, we can pass the range directly to the caller to reduce the memory footprint.
117
125
  def caller_array
118
126
  # omits the first several callers which are in the ScoutAPM stack.
119
- if ScoutApm::Agent.instance.context.environment.ruby_2?
127
+ if ScoutApm::Environment.instance.ruby_2?
120
128
  caller(3...BACKTRACE_CALLER_LIMIT)
121
129
  else
122
130
  caller[3...BACKTRACE_CALLER_LIMIT]
123
131
  end
124
132
  end
125
133
 
134
+ # Set the name of the file that this action is coming from.
135
+ # TraceSet uses this to more accurately filter backtraces
136
+ def set_root_class(klass_name)
137
+ @traces.set_root_class(klass_name)
138
+ end
139
+
140
+ def start_sampling
141
+ if ScoutApm::Agent.instance.config.value('profile') && traced?
142
+ ScoutApm::Instruments::Stacks.update_indexes(frame_index, trace_index)
143
+ ScoutApm::Instruments::Stacks.start_sampling
144
+ else
145
+ ScoutApm::Instruments::Stacks.stop_sampling(false)
146
+ end
147
+ end
148
+
149
+ def record_traces!
150
+ if ScoutApm::Agent.instance.config.value('profile')
151
+ ScoutApm::Instruments::Stacks.stop_sampling(false)
152
+ if traced?
153
+ traces.raw_traces = ScoutApm::Instruments::Stacks.profile_frames
154
+ traces.skipped_in_gc = ScoutApm::Instruments::Stacks.skipped_in_gc
155
+ traces.skipped_in_handler = ScoutApm::Instruments::Stacks.skipped_in_handler
156
+ traces.skipped_in_job_registered = ScoutApm::Instruments::Stacks.skipped_in_job_registered
157
+ traces.skipped_in_not_running = ScoutApm::Instruments::Stacks.skipped_in_not_running
158
+ end
159
+ end
160
+ end
161
+
162
+
126
163
  ######################################
127
164
  # Debugging Helpers
128
165
  ######################################
@@ -163,7 +200,6 @@ module ScoutApm
163
200
  map { |child| child.total_call_time }.
164
201
  inject(0) { |sum, time| sum + time }
165
202
  end
166
- private :child_time
167
203
 
168
204
  ######################################
169
205
  # Allocation Calculations
@@ -189,6 +225,5 @@ module ScoutApm
189
225
  map { |child| child.total_allocations }.
190
226
  inject(0) { |sum, obj| sum + obj }
191
227
  end
192
- private :child_allocations
193
228
  end
194
229
  end
@@ -1,17 +1,16 @@
1
1
  module ScoutApm
2
2
  module LayerConverters
3
3
  class AllocationMetricConverter < ConverterBase
4
- def record!
5
- return unless scope_layer
6
- return unless ScoutApm::Instruments::Allocations::ENABLED
4
+ def call
5
+ scope = scope_layer
6
+ return {} unless scope
7
+ return {} unless ScoutApm::Instruments::Allocations::ENABLED
7
8
 
8
- meta = MetricMeta.new("ObjectAllocations", {:scope => scope_layer.legacy_metric_name})
9
+ meta = MetricMeta.new("ObjectAllocations", {:scope => scope.legacy_metric_name})
9
10
  stat = MetricStats.new
10
11
  stat.update!(root_layer.total_allocations)
11
- metrics = { meta => stat }
12
12
 
13
- @store.track!(metrics)
14
- nil # not returning anything in the layer results ... not used
13
+ { meta => stat }
15
14
  end
16
15
  end
17
16
  end
@@ -1,220 +1,31 @@
1
1
  module ScoutApm
2
2
  module LayerConverters
3
3
  class ConverterBase
4
-
5
- attr_reader :context
4
+ attr_reader :walker
6
5
  attr_reader :request
7
6
  attr_reader :root_layer
8
- attr_reader :layer_finder
9
7
 
10
- def initialize(context, request, layer_finder, store=nil)
11
- @context = context
8
+ def initialize(request)
12
9
  @request = request
13
- @layer_finder = layer_finder
14
- @store = store
15
-
16
10
  @root_layer = request.root_layer
17
- @backtraces = []
18
- @limited = false
11
+ @walker = DepthFirstWalker.new(root_layer)
19
12
  end
20
13
 
14
+ # Scope is determined by the first Controller we hit. Most of the time
15
+ # there will only be 1 anyway. But if you have a controller that calls
16
+ # another controller method, we may pick that up:
17
+ # def update
18
+ # show
19
+ # render :update
20
+ # end
21
21
  def scope_layer
22
- layer_finder.scope
23
- end
24
-
25
- ################################################################################
26
- # Subscoping
27
- ################################################################################
28
- #
29
- # Keep a list of subscopes, but only ever use the front one. The rest
30
- # get pushed/popped in cases when we have many levels of subscopable
31
- # layers. This lets us push/pop without otherwise keeping track very closely.
32
- def register_hooks(walker)
33
- @subscope_layers = []
34
-
35
- walker.before do |layer|
36
- if layer.subscopable?
37
- @subscope_layers.push(layer)
38
- end
39
- end
40
-
41
- walker.after do |layer|
42
- if layer.subscopable?
43
- @subscope_layers.pop
44
- end
45
- end
46
- end
47
-
48
- def subscoped?(layer)
49
- @subscope_layers.first && layer != @subscope_layers.first # Don't scope under ourself.
50
- end
51
-
52
- def subscope_name
53
- @subscope_layers.first.legacy_metric_name
54
- end
55
-
56
-
57
- ################################################################################
58
- # Backtrace Handling
59
- ################################################################################
60
- #
61
- # Because we get several layers for the same thing if you call an
62
- # instrumented thing repeatedly, and only some of them may have
63
- # backtraces captured, we store the backtraces off into another spot
64
- # during processing, then at the end, we loop over those saved
65
- # backtraces, putting them back into the metrics hash.
66
- #
67
- # This comes up most often when capturing n+1 backtraces. Because the
68
- # query may be fast enough to evade our time-limit based backtrace
69
- # capture, only the Nth item (see TrackedRequest for more detail) has a
70
- # backtrack captured. This sequence makes sure that we report up that
71
- # backtrace in the aggregated set of metrics around that call.
72
-
73
- # Call this as you are processing each layer. It will store off backtraces
74
- def store_backtrace(layer, meta)
75
- return unless layer.backtrace
76
-
77
- bt = ScoutApm::Utils::BacktraceParser.new(layer.backtrace).call
78
- if bt.any?
79
- meta.backtrace = bt
80
- @backtraces << meta
81
- end
22
+ @scope_layer ||= find_first_layer_of_type("Controller") || find_first_layer_of_type("Job")
82
23
  end
83
24
 
84
- # Call this after you finish walking the layers, and want to take the
85
- # set-aside backtraces and place them into the metas they match
86
- def attach_backtraces(metric_hash)
87
- @backtraces.each do |meta_with_backtrace|
88
- metric_hash.keys.find { |k| k == meta_with_backtrace }.backtrace = meta_with_backtrace.backtrace
25
+ def find_first_layer_of_type(layer_type)
26
+ walker.walk do |layer|
27
+ return layer if layer.type == layer_type
89
28
  end
90
- metric_hash
91
- end
92
-
93
-
94
- ################################################################################
95
- # Limit Handling
96
- ################################################################################
97
-
98
- # To prevent huge traces from being generated, we should stop collecting
99
- # detailed metrics as we go beyond some reasonably large count.
100
- #
101
- # We should still add up the /all aggregates.
102
-
103
- MAX_METRICS = 500
104
-
105
- def over_metric_limit?(metric_hash)
106
- if metric_hash.size > MAX_METRICS
107
- @limited = true
108
- else
109
- false
110
- end
111
- end
112
-
113
- def limited?
114
- !! @limited
115
- end
116
-
117
- ################################################################################
118
- # Meta Scope
119
- ################################################################################
120
-
121
- # When we make MetricMeta records, we need to determine a few things from layer.
122
- def make_meta_options(layer)
123
- scope_hash = make_meta_options_scope(layer)
124
- desc_hash = make_meta_options_desc_hash(layer)
125
-
126
- scope_hash.merge(desc_hash)
127
- end
128
-
129
- def make_meta_options_scope(layer)
130
- # This layer is scoped under another thing. Typically that means this is a layer under a view.
131
- # Like: Controller -> View/users/show -> ActiveRecord/user/find
132
- # in that example, the scope is the View/users/show
133
- if subscoped?(layer)
134
- {:scope => subscope_name}
135
-
136
- # We don't scope the controller under itself
137
- elsif layer == scope_layer
138
- {}
139
-
140
- # This layer is a top level metric ("ActiveRecord", or "HTTP" or
141
- # whatever, directly under the controller), so scope to the
142
- # Controller
143
- else
144
- {:scope => scope_layer.legacy_metric_name}
145
- end
146
- end
147
-
148
- def make_meta_options_desc_hash(layer, max_desc_length=32768)
149
- if layer.desc
150
- desc_s = layer.desc.to_s
151
- trimmed_desc = desc_s[0 .. max_desc_length]
152
- {:desc => trimmed_desc}
153
- else
154
- {}
155
- end
156
- end
157
-
158
-
159
- ################################################################################
160
- # Storing metrics into the hashes
161
- ################################################################################
162
-
163
- # This is the detailed metric - type, name, backtrace, annotations, etc.
164
- def store_specific_metric(layer, metric_hash, allocation_metric_hash)
165
- return false if over_metric_limit?(metric_hash)
166
-
167
- meta_options = make_meta_options(layer)
168
-
169
- meta = MetricMeta.new(layer.legacy_metric_name, meta_options)
170
- meta.extra.merge!(layer.annotations) if layer.annotations
171
-
172
- store_backtrace(layer, meta)
173
-
174
- metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
175
- allocation_metric_hash[meta] ||= MetricStats.new(meta_options.has_key?(:scope))
176
-
177
- # timing
178
- stat = metric_hash[meta]
179
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
180
-
181
- # allocations
182
- stat = allocation_metric_hash[meta]
183
- stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
184
-
185
- if LimitedLayer === layer
186
- metric_hash[meta].call_count = layer.count
187
- allocation_metric_hash[meta].call_count = layer.count
188
- end
189
- end
190
-
191
- # Merged Metric - no specifics, just sum up by type (ActiveRecord, View, HTTP, etc)
192
- def store_aggregate_metric(layer, metric_hash, allocation_metric_hash)
193
- meta = MetricMeta.new("#{layer.type}/all")
194
-
195
- metric_hash[meta] ||= MetricStats.new(false)
196
- allocation_metric_hash[meta] ||= MetricStats.new(false)
197
-
198
- # timing
199
- stat = metric_hash[meta]
200
- stat.update!(layer.total_call_time, layer.total_exclusive_time)
201
-
202
- # allocations
203
- stat = allocation_metric_hash[meta]
204
- stat.update!(layer.total_allocations, layer.total_exclusive_allocations)
205
- end
206
-
207
- ################################################################################
208
- # Misc Helpers
209
- ################################################################################
210
-
211
- # Sometimes we start capturing a layer without knowing if we really
212
- # want to make an entry for it. See ActiveRecord instrumentation for
213
- # an example. We start capturing before we know if a query is cached
214
- # or not, and want to skip any cached queries.
215
- def skip_layer?(layer)
216
- return false if layer.annotations.nil?
217
- return true if layer.annotations[:ignorable]
218
29
  end
219
30
  end
220
31
  end