oneapm_rpm 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (234) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +30 -0
  3. data/.rubocop.yml +725 -0
  4. data/Gemfile +3 -0
  5. data/Guardfile +7 -0
  6. data/LICENSE +1 -0
  7. data/README.md +3 -0
  8. data/config/cert/cacert.pem +1177 -0
  9. data/config/database.yml +5 -0
  10. data/lib/initializers/goliath.rb +11 -0
  11. data/lib/initializers/other.rb +1 -0
  12. data/lib/initializers/rails.rb +15 -0
  13. data/lib/one_apm/agent.rb +253 -0
  14. data/lib/one_apm/agent/agent.rb +283 -0
  15. data/lib/one_apm/agent/agent/connect.rb +175 -0
  16. data/lib/one_apm/agent/agent/container_data_manager.rb +218 -0
  17. data/lib/one_apm/agent/agent/forkable_dispatcher_functions.rb +96 -0
  18. data/lib/one_apm/agent/agent/helpers.rb +45 -0
  19. data/lib/one_apm/agent/agent/start.rb +226 -0
  20. data/lib/one_apm/agent/agent/start_worker_thread.rb +148 -0
  21. data/lib/one_apm/agent/busy_calculator.rb +115 -0
  22. data/lib/one_apm/agent/cross_app/cross_app_monitor.rb +181 -0
  23. data/lib/one_apm/agent/cross_app/cross_app_tracing.rb +336 -0
  24. data/lib/one_apm/agent/database.rb +308 -0
  25. data/lib/one_apm/agent/database/active_record_helper.rb +80 -0
  26. data/lib/one_apm/agent/database/obfuscation_helpers.rb +76 -0
  27. data/lib/one_apm/agent/database/obfuscator.rb +78 -0
  28. data/lib/one_apm/agent/database/postgres_explain_obfuscator.rb +45 -0
  29. data/lib/one_apm/agent/datastores.rb +175 -0
  30. data/lib/one_apm/agent/datastores/metric_helper.rb +83 -0
  31. data/lib/one_apm/agent/datastores/mongo.rb +27 -0
  32. data/lib/one_apm/agent/datastores/mongo/metric_translator.rb +189 -0
  33. data/lib/one_apm/agent/datastores/mongo/obfuscator.rb +37 -0
  34. data/lib/one_apm/agent/datastores/mongo/statement_formatter.rb +51 -0
  35. data/lib/one_apm/agent/event/event_listener.rb +40 -0
  36. data/lib/one_apm/agent/event/event_loop.rb +191 -0
  37. data/lib/one_apm/agent/event/worker_loop.rb +97 -0
  38. data/lib/one_apm/agent/harvester.rb +48 -0
  39. data/lib/one_apm/agent/inbound_request_monitor.rb +30 -0
  40. data/lib/one_apm/agent/javascript_instrumentor.rb +186 -0
  41. data/lib/one_apm/agent/pipe/pipe_channel_manager.rb +275 -0
  42. data/lib/one_apm/agent/pipe/pipe_service.rb +81 -0
  43. data/lib/one_apm/agent/sampler.rb +55 -0
  44. data/lib/one_apm/agent/sampler_collection.rb +65 -0
  45. data/lib/one_apm/agent/samplers/cpu_sampler.rb +49 -0
  46. data/lib/one_apm/agent/samplers/delayed_job_sampler.rb +109 -0
  47. data/lib/one_apm/agent/samplers/memory_sampler.rb +144 -0
  48. data/lib/one_apm/agent/samplers/object_sampler.rb +22 -0
  49. data/lib/one_apm/agent/samplers/vm_sampler.rb +124 -0
  50. data/lib/one_apm/agent/synthetics_monitor.rb +48 -0
  51. data/lib/one_apm/agent/threading/agent_thread.rb +74 -0
  52. data/lib/one_apm/agent/threading/backtrace_node.rb +133 -0
  53. data/lib/one_apm/agent/threading/backtrace_service.rb +259 -0
  54. data/lib/one_apm/agent/threading/thread_profile.rb +155 -0
  55. data/lib/one_apm/collector/collector/helper.rb +139 -0
  56. data/lib/one_apm/collector/collector/http_connection.rb +254 -0
  57. data/lib/one_apm/collector/collector/server_methods.rb +71 -0
  58. data/lib/one_apm/collector/collector_service.rb +123 -0
  59. data/lib/one_apm/collector/commands/agent_command.rb +17 -0
  60. data/lib/one_apm/collector/commands/thread_profiler_session.rb +108 -0
  61. data/lib/one_apm/collector/commands/xray_session.rb +53 -0
  62. data/lib/one_apm/collector/commands/xray_session_collection.rb +156 -0
  63. data/lib/one_apm/collector/containers/agent_command_router.rb +153 -0
  64. data/lib/one_apm/collector/containers/custom_event_aggregator.rb +94 -0
  65. data/lib/one_apm/collector/containers/error_collector.rb +349 -0
  66. data/lib/one_apm/collector/containers/sql_sampler.rb +331 -0
  67. data/lib/one_apm/collector/containers/stats_engine.rb +34 -0
  68. data/lib/one_apm/collector/containers/transaction_event_aggregator.rb +249 -0
  69. data/lib/one_apm/collector/containers/transaction_sampler.rb +352 -0
  70. data/lib/one_apm/collector/containers/utilization_data.rb +36 -0
  71. data/lib/one_apm/collector/stats_engine/gc_profiler.rb +106 -0
  72. data/lib/one_apm/collector/stats_engine/metric_stats.rb +243 -0
  73. data/lib/one_apm/collector/stats_engine/stats_hash.rb +105 -0
  74. data/lib/one_apm/configuration.rb +429 -0
  75. data/lib/one_apm/configuration/autostart.rb +41 -0
  76. data/lib/one_apm/configuration/default_source.rb +1026 -0
  77. data/lib/one_apm/configuration/environment_source.rb +113 -0
  78. data/lib/one_apm/configuration/high_security_source.rb +56 -0
  79. data/lib/one_apm/configuration/manual_source.rb +13 -0
  80. data/lib/one_apm/configuration/server_source.rb +60 -0
  81. data/lib/one_apm/configuration/yaml_source.rb +134 -0
  82. data/lib/one_apm/errors/agent_errors.rb +26 -0
  83. data/lib/one_apm/errors/internal_agent_error.rb +16 -0
  84. data/lib/one_apm/errors/noticed_error.rb +79 -0
  85. data/lib/one_apm/frameworks/external.rb +15 -0
  86. data/lib/one_apm/frameworks/rails.rb +103 -0
  87. data/lib/one_apm/frameworks/rails3.rb +37 -0
  88. data/lib/one_apm/frameworks/rails4.rb +21 -0
  89. data/lib/one_apm/frameworks/ruby.rb +21 -0
  90. data/lib/one_apm/frameworks/sinatra.rb +12 -0
  91. data/lib/one_apm/inst/3rd/active_merchant.rb +35 -0
  92. data/lib/one_apm/inst/3rd/acts_as_solr.rb +70 -0
  93. data/lib/one_apm/inst/3rd/authlogic.rb +23 -0
  94. data/lib/one_apm/inst/3rd/sunspot.rb +31 -0
  95. data/lib/one_apm/inst/background_job/active_job.rb +88 -0
  96. data/lib/one_apm/inst/background_job/delayed_job.rb +52 -0
  97. data/lib/one_apm/inst/background_job/delayed_job_injection.rb +8 -0
  98. data/lib/one_apm/inst/background_job/resque.rb +107 -0
  99. data/lib/one_apm/inst/background_job/sidekiq.rb +64 -0
  100. data/lib/one_apm/inst/dispatcher/passenger.rb +25 -0
  101. data/lib/one_apm/inst/dispatcher/rainbows.rb +23 -0
  102. data/lib/one_apm/inst/framework/grape.rb +94 -0
  103. data/lib/one_apm/inst/framework/padrino.rb +30 -0
  104. data/lib/one_apm/inst/framework/sinatra.rb +185 -0
  105. data/lib/one_apm/inst/framework/sinatra/ignorer.rb +50 -0
  106. data/lib/one_apm/inst/framework/sinatra/transaction_namer.rb +54 -0
  107. data/lib/one_apm/inst/http_clients/curb.rb +189 -0
  108. data/lib/one_apm/inst/http_clients/excon.rb +70 -0
  109. data/lib/one_apm/inst/http_clients/excon/connection.rb +31 -0
  110. data/lib/one_apm/inst/http_clients/excon/middleware.rb +55 -0
  111. data/lib/one_apm/inst/http_clients/httpclient.rb +44 -0
  112. data/lib/one_apm/inst/http_clients/net.rb +34 -0
  113. data/lib/one_apm/inst/http_clients/typhoeus.rb +76 -0
  114. data/lib/one_apm/inst/nosql/memcache.rb +134 -0
  115. data/lib/one_apm/inst/nosql/mongo.rb +126 -0
  116. data/lib/one_apm/inst/nosql/mongo_moped.rb +85 -0
  117. data/lib/one_apm/inst/nosql/redis.rb +83 -0
  118. data/lib/one_apm/inst/orm/active_record.rb +99 -0
  119. data/lib/one_apm/inst/orm/active_record_4.rb +28 -0
  120. data/lib/one_apm/inst/orm/data_mapper.rb +180 -0
  121. data/lib/one_apm/inst/orm/sequel.rb +47 -0
  122. data/lib/one_apm/inst/rack.rb +38 -0
  123. data/lib/one_apm/inst/rack/rack.rb +44 -0
  124. data/lib/one_apm/inst/rack/rack_builder.rb +51 -0
  125. data/lib/one_apm/inst/rails/action_controller.rb +118 -0
  126. data/lib/one_apm/inst/rails/action_web_service.rb +44 -0
  127. data/lib/one_apm/inst/rails/errors.rb +43 -0
  128. data/lib/one_apm/inst/rails3/action_controller.rb +172 -0
  129. data/lib/one_apm/inst/rails3/errors.rb +43 -0
  130. data/lib/one_apm/inst/rails4/action_controller.rb +27 -0
  131. data/lib/one_apm/inst/rails4/action_controller_subscriber.rb +121 -0
  132. data/lib/one_apm/inst/rails4/action_view.rb +23 -0
  133. data/lib/one_apm/inst/rails4/action_view_subscriber.rb +93 -0
  134. data/lib/one_apm/inst/rails4/active_record_subscriber.rb +96 -0
  135. data/lib/one_apm/inst/rails4/errors.rb +42 -0
  136. data/lib/one_apm/inst/rails_middleware.rb +40 -0
  137. data/lib/one_apm/inst/support/evented_subscriber.rb +98 -0
  138. data/lib/one_apm/inst/support/ignore_actions.rb +39 -0
  139. data/lib/one_apm/inst/support/queue_time.rb +76 -0
  140. data/lib/one_apm/inst/transaction_base.rb +405 -0
  141. data/lib/one_apm/logger/agent_logger.rb +206 -0
  142. data/lib/one_apm/logger/audit_logger.rb +78 -0
  143. data/lib/one_apm/logger/memory_logger.rb +50 -0
  144. data/lib/one_apm/logger/null_logger.rb +19 -0
  145. data/lib/one_apm/metrics/metric_data.rb +72 -0
  146. data/lib/one_apm/metrics/metric_spec.rb +82 -0
  147. data/lib/one_apm/metrics/stats.rb +173 -0
  148. data/lib/one_apm/probe.rb +16 -0
  149. data/lib/one_apm/probe/framework_loader.rb +53 -0
  150. data/lib/one_apm/probe/instance_methods.rb +105 -0
  151. data/lib/one_apm/probe/instrumentation.rb +60 -0
  152. data/lib/one_apm/rack/browser_monitoring.rb +144 -0
  153. data/lib/one_apm/rack/middleware_base.rb +27 -0
  154. data/lib/one_apm/rack/middleware_hooks.rb +17 -0
  155. data/lib/one_apm/rack/middleware_tracing.rb +81 -0
  156. data/lib/one_apm/rack/middleware_wrapper.rb +86 -0
  157. data/lib/one_apm/support/chained_call.rb +15 -0
  158. data/lib/one_apm/support/coerce.rb +81 -0
  159. data/lib/one_apm/support/collection_helper.rb +79 -0
  160. data/lib/one_apm/support/dotted_hash.rb +45 -0
  161. data/lib/one_apm/support/encoders.rb +34 -0
  162. data/lib/one_apm/support/environment_report.rb +127 -0
  163. data/lib/one_apm/support/event_buffer.rb +82 -0
  164. data/lib/one_apm/support/event_buffer/sampled_buffer.rb +45 -0
  165. data/lib/one_apm/support/event_buffer/sized_buffer.rb +21 -0
  166. data/lib/one_apm/support/event_buffer/synthetics_event_buffer.rb +40 -0
  167. data/lib/one_apm/support/helper.rb +49 -0
  168. data/lib/one_apm/support/hostname.rb +13 -0
  169. data/lib/one_apm/support/http_clients/curb_wrappers.rb +65 -0
  170. data/lib/one_apm/support/http_clients/excon_wrappers.rb +63 -0
  171. data/lib/one_apm/support/http_clients/httpclient_wrappers.rb +61 -0
  172. data/lib/one_apm/support/http_clients/net_http_wrappers.rb +48 -0
  173. data/lib/one_apm/support/http_clients/typhoeus_wrappers.rb +73 -0
  174. data/lib/one_apm/support/http_clients/uri_util.rb +39 -0
  175. data/lib/one_apm/support/json_marshaller.rb +68 -0
  176. data/lib/one_apm/support/json_wrapper.rb +130 -0
  177. data/lib/one_apm/support/language_support.rb +142 -0
  178. data/lib/one_apm/support/library_detection.rb +119 -0
  179. data/lib/one_apm/support/local_environment.rb +196 -0
  180. data/lib/one_apm/support/marshaller.rb +62 -0
  181. data/lib/one_apm/support/method_tracer.rb +334 -0
  182. data/lib/one_apm/support/method_tracer/helpers.rb +92 -0
  183. data/lib/one_apm/support/method_tracer/traced_method_stack.rb +103 -0
  184. data/lib/one_apm/support/obfuscator.rb +47 -0
  185. data/lib/one_apm/support/okjson.rb +601 -0
  186. data/lib/one_apm/support/parameter_filtering.rb +35 -0
  187. data/lib/one_apm/support/rules_engine.rb +56 -0
  188. data/lib/one_apm/support/rules_engine/replacement_rule.rb +80 -0
  189. data/lib/one_apm/support/rules_engine/segment_terms_rule.rb +46 -0
  190. data/lib/one_apm/support/server.rb +11 -0
  191. data/lib/one_apm/support/supported_versions.rb +257 -0
  192. data/lib/one_apm/support/system_info.rb +211 -0
  193. data/lib/one_apm/support/timer_lib.rb +29 -0
  194. data/lib/one_apm/support/version_number.rb +51 -0
  195. data/lib/one_apm/support/vm.rb +30 -0
  196. data/lib/one_apm/support/vm/jruby_vm.rb +38 -0
  197. data/lib/one_apm/support/vm/monotonic_gc_profiler.rb +43 -0
  198. data/lib/one_apm/support/vm/mri_vm.rb +85 -0
  199. data/lib/one_apm/support/vm/rubinius_vm.rb +129 -0
  200. data/lib/one_apm/support/vm/snapshot.rb +18 -0
  201. data/lib/one_apm/transaction.rb +336 -0
  202. data/lib/one_apm/transaction/class_methods.rb +132 -0
  203. data/lib/one_apm/transaction/instance_helpers.rb +82 -0
  204. data/lib/one_apm/transaction/metric_constants.rb +42 -0
  205. data/lib/one_apm/transaction/sample_buffer/force_persist_sample_buffer.rb +21 -0
  206. data/lib/one_apm/transaction/sample_buffer/slowest_sample_buffer.rb +21 -0
  207. data/lib/one_apm/transaction/sample_buffer/synthetics_sample_buffer.rb +21 -0
  208. data/lib/one_apm/transaction/sample_buffer/transaction_sample_buffer.rb +101 -0
  209. data/lib/one_apm/transaction/sample_buffer/xray_sample_buffer.rb +60 -0
  210. data/lib/one_apm/transaction/segment.rb +193 -0
  211. data/lib/one_apm/transaction/segment_summary.rb +51 -0
  212. data/lib/one_apm/transaction/thread_local_access.rb +73 -0
  213. data/lib/one_apm/transaction/transaction_analysis.rb +78 -0
  214. data/lib/one_apm/transaction/transaction_apdex.rb +20 -0
  215. data/lib/one_apm/transaction/transaction_cpu.rb +22 -0
  216. data/lib/one_apm/transaction/transaction_finish_append.rb +67 -0
  217. data/lib/one_apm/transaction/transaction_ignore.rb +33 -0
  218. data/lib/one_apm/transaction/transaction_jruby_functions.rb +40 -0
  219. data/lib/one_apm/transaction/transaction_metrics.rb +53 -0
  220. data/lib/one_apm/transaction/transaction_name.rb +90 -0
  221. data/lib/one_apm/transaction/transaction_namer.rb +49 -0
  222. data/lib/one_apm/transaction/transaction_sample.rb +204 -0
  223. data/lib/one_apm/transaction/transaction_sample_builder.rb +168 -0
  224. data/lib/one_apm/transaction/transaction_state.rb +149 -0
  225. data/lib/one_apm/transaction/transaction_summary.rb +28 -0
  226. data/lib/one_apm/transaction/transaction_synthetics.rb +40 -0
  227. data/lib/one_apm/transaction/transaction_timings.rb +54 -0
  228. data/lib/one_apm/version.rb +13 -0
  229. data/lib/oneapm_rpm.rb +16 -0
  230. data/lib/sequel/extensions/oneapm_instrumentation.rb +84 -0
  231. data/lib/sequel/plugins/oneapm_instrumentation.rb +66 -0
  232. data/oneapm.yml +135 -0
  233. data/oneapm_rpm.gemspec +58 -0
  234. metadata +474 -0
@@ -0,0 +1,62 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Support
5
+ class Marshaller
6
+
7
+ # Used to wrap errors reported to agent by the collector
8
+ class CollectorError < StandardError; end
9
+
10
+ def parsed_error(error)
11
+ error_type = error['error_type']
12
+ error_message = error['message']
13
+
14
+ exception = case error_type
15
+ when 'OneApm::LicenseException'
16
+ OneApm::LicenseException.new(error_message)
17
+ when 'OneApm::ForceRestartException'
18
+ OneApm::ForceRestartException.new(error_message)
19
+ when 'OneApm::ForceDisconnectException'
20
+ OneApm::ForceDisconnectException.new(error_message)
21
+ else
22
+ OneApm::Support::Marshaller::CollectorError.new("#{error['error_type']}: #{error['message']}")
23
+ end
24
+
25
+ exception
26
+ end
27
+
28
+ def prepare(data, options={})
29
+ encoder = options[:encoder] || default_encoder
30
+ if data.respond_to?(:to_collector_array)
31
+ data.to_collector_array(encoder)
32
+ elsif data.kind_of?(Array)
33
+ data.map { |element| prepare(element, options) }
34
+ else
35
+ data
36
+ end
37
+ end
38
+
39
+ def default_encoder
40
+ Encoders::Identity
41
+ end
42
+
43
+ def self.human_readable?
44
+ false
45
+ end
46
+
47
+ protected
48
+
49
+ def return_value(data)
50
+ if data.respond_to?(:has_key?)
51
+ if data.has_key?('exception')
52
+ raise parsed_error(data['exception'])
53
+ elsif data.has_key?('return_value')
54
+ return data['return_value']
55
+ end
56
+ end
57
+ ::OneApm::Agent.logger.debug("Unexpected response from collector: #{data}")
58
+ nil
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,334 @@
1
+ # encoding: utf-8
2
+
3
+ require 'one_apm/probe'
4
+ require 'one_apm/support/method_tracer/helpers'
5
+
6
+ module OneApm
7
+ module Support
8
+ # This module contains class methods added to support installing custom
9
+ # metric tracers and executing for individual metrics.
10
+ #
11
+ # == Examples
12
+ #
13
+ # When the agent initializes, it extends Module with these methods.
14
+ # However if you want to use the API in code that might get loaded
15
+ # before the agent is initialized you will need to require
16
+ # this file:
17
+ #
18
+ # require 'one_apm/support/method_tracer'
19
+ # class A
20
+ # include OneApm::Support::MethodTracer
21
+ # def process
22
+ # ...
23
+ # end
24
+ # add_method_tracer :process
25
+ # end
26
+ #
27
+ # To instrument a class method:
28
+ #
29
+ # require 'one_apm/support/method_tracer'
30
+ # class An
31
+ # def self.process
32
+ # ...
33
+ # end
34
+ # class << self
35
+ # include OneApm::Support::MethodTracer
36
+ # add_method_tracer :process
37
+ # end
38
+ # end
39
+ #
40
+ # @api public
41
+ #
42
+
43
+ module MethodTracer
44
+ def self.included clazz
45
+ clazz.extend ClassMethods
46
+ end
47
+
48
+ def self.extended clazz
49
+ clazz.extend ClassMethods
50
+ end
51
+
52
+ # Trace a given block with stats and keep track of the caller.
53
+ # See OneApm::Support::MethodTracer::ClassMethods#add_method_tracer for a description of the arguments.
54
+ # +metric_names+ is either a single name or an array of metric names.
55
+ # If more than one metric is passed, the +produce_metric+ option only applies to the first. The
56
+ # others are always recorded. Only the first metric is pushed onto the scope stack.
57
+ #
58
+ # Generally you pass an array of metric names if you want to record the metric under additional
59
+ # categories, but generally this *should never ever be done*. Most of the time you can aggregate
60
+ # on the server.
61
+ #
62
+ # @api public
63
+ #
64
+ def trace_execution_scoped(metric_names, options={}) #THREAD_LOCAL_ACCESS
65
+ OneApm::Support::MethodTracer::Helpers.trace_execution_scoped(metric_names, options) do
66
+ # Using an implicit block avoids object allocation for a &block param
67
+ yield
68
+ end
69
+ end
70
+
71
+ # Trace a given block with stats assigned to the given metric_name. It does not
72
+ # provide scoped measurements, meaning whatever is being traced will not 'blame the
73
+ # Controller'--that is to say appear in the breakdown chart.
74
+ # This is code is inlined in #add_method_tracer.
75
+ # * <tt>metric_names</tt> is a single name or an array of names of metrics
76
+ #
77
+ # @api public
78
+ #
79
+ def trace_execution_unscoped(metric_names, options={}) #THREAD_LOCAL_ACCESS
80
+ return yield unless OneApm::Agent.tl_is_execution_traced?
81
+ t0 = Time.now
82
+ begin
83
+ yield
84
+ ensure
85
+ duration = (Time.now - t0).to_f # for some reason this is 3 usec faster than Time - Time
86
+ OneApm::Agent.instance.stats_engine.tl_record_unscoped_metrics(metric_names, duration)
87
+ end
88
+ end
89
+
90
+ # Defines methods used at the class level, for adding instrumentation
91
+ # @api public
92
+ module ClassMethods
93
+ # contains methods refactored out of the #add_method_tracer method
94
+ module AddMethodTracer
95
+ ALLOWED_KEYS = [:force, :metric, :push_scope, :code_header, :code_footer].freeze
96
+
97
+ DEPRECATED_KEYS = [:force, :scoped_metric_only, :deduct_call_time_from_parent].freeze
98
+
99
+ # raises an error when the
100
+ # OneApm::Support::MethodTracer::ClassMethods#add_method_tracer
101
+ # method is called with improper keys. This aids in
102
+ # debugging new instrumentation by failing fast
103
+ def check_for_illegal_keys!(method_name, options)
104
+ unrecognized_keys = options.keys - ALLOWED_KEYS
105
+ deprecated_keys = options.keys & DEPRECATED_KEYS
106
+
107
+ if unrecognized_keys.any?
108
+ raise "Unrecognized options when adding method tracer to #{method_name}: " +
109
+ unrecognized_keys.join(', ')
110
+ end
111
+
112
+ if deprecated_keys.any?
113
+ OneApm::Agent.logger.warn("Deprecated options when adding method tracer to #{method_name}: "+
114
+ deprecated_keys.join(', '))
115
+ end
116
+ end
117
+
118
+ # validity checking - add_method_tracer must receive either
119
+ # push scope or metric, or else it would record no
120
+ # data. Raises an error if this is the case
121
+ def check_for_push_scope_and_metric(options)
122
+ unless options[:push_scope] || options[:metric]
123
+ raise "Can't add a tracer where push_scope is false and metric is false"
124
+ end
125
+ end
126
+
127
+ DEFAULT_SETTINGS = {:push_scope => true, :metric => true, :code_header => "", :code_footer => "" }.freeze
128
+
129
+ # Checks the provided options to make sure that they make
130
+ # sense. Raises an error if the options are incorrect to
131
+ # assist with debugging, so that errors occur at class
132
+ # construction time rather than instrumentation run time
133
+ def validate_options(method_name, options)
134
+ unless options.is_a?(Hash)
135
+ raise TypeError.new("Error adding method tracer to #{method_name}: provided options must be a Hash")
136
+ end
137
+ check_for_illegal_keys!(method_name, options)
138
+ options = DEFAULT_SETTINGS.merge(options)
139
+ check_for_push_scope_and_metric(options)
140
+ options
141
+ end
142
+
143
+ # Default to the class where the method is defined.
144
+ #
145
+ # Example:
146
+ # Foo.default_metric_name_code('bar') #=> "Custom/#{Foo.name}/bar"
147
+ def default_metric_name_code(method_name)
148
+ "Custom/#{self.name}/#{method_name.to_s}"
149
+ end
150
+
151
+ # Checks to see if the method we are attempting to trace
152
+ # actually exists or not. #add_method_tracer can't do
153
+ # anything if the method doesn't exist.
154
+ def oneapm_method_exists?(method_name)
155
+ exists = method_defined?(method_name) || private_method_defined?(method_name)
156
+ ::OneApm::Agent.logger.error("Did not trace #{self.name}##{method_name} because that method does not exist") unless exists
157
+ exists
158
+ end
159
+
160
+ # Checks to see if we have already traced a method with a
161
+ # given metric by checking to see if the traced method
162
+ # exists. Warns the user if methods are being double-traced
163
+ # to help with debugging custom instrumentation.
164
+ def traced_method_exists?(method_name, metric_name_code)
165
+ exists = method_defined?(_traced_method_name(method_name, metric_name_code))
166
+ ::OneApm::Agent.logger.error("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}") if exists
167
+ exists
168
+ end
169
+
170
+ # Returns a code snippet to be eval'd that skips tracing
171
+ # when the agent is not tracing execution. turns
172
+ # instrumentation into effectively one method call overhead
173
+ # when the agent is disabled
174
+ def assemble_code_header(method_name, metric_name_code, options)
175
+ header = "return #{_untraced_method_name(method_name, metric_name_code)}(*args, &block) unless OneApm::Agent.tl_is_execution_traced?\n"
176
+ header += options[:code_header].to_s
177
+ header
178
+ end
179
+
180
+ # returns an eval-able string that contains the traced
181
+ # method code used if the agent is not creating a scope for
182
+ # use in scoped metrics.
183
+ def method_without_push_scope(method_name, metric_name_code, options)
184
+ "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
185
+ #{assemble_code_header(method_name, metric_name_code, options)}
186
+ t0 = Time.now
187
+ begin
188
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)\n
189
+ ensure
190
+ duration = (Time.now - t0).to_f
191
+ OneApm::Agent.record_metric(\"#{metric_name_code}\", duration)
192
+ #{options[:code_footer]}
193
+ end
194
+ end"
195
+ end
196
+
197
+ # returns an eval-able string that contains the tracing code
198
+ # for a fully traced metric including scoping
199
+ def method_with_push_scope(method_name, metric_name_code, options)
200
+ "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
201
+ #{options[:code_header]}
202
+ result = ::OneApm::Support::MethodTracer::Helpers.trace_execution_scoped(\"#{metric_name_code}\",
203
+ :metric => #{options[:metric]}) do
204
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
205
+ end
206
+ #{options[:code_footer]}
207
+ result
208
+ end"
209
+ end
210
+
211
+ # Decides which code snippet we should be eval'ing in this
212
+ # context, based on the options.
213
+ def code_to_eval(method_name, metric_name_code, options)
214
+ options = validate_options(method_name, options)
215
+ if options[:push_scope]
216
+ method_with_push_scope(method_name, metric_name_code, options)
217
+ else
218
+ method_without_push_scope(method_name, metric_name_code, options)
219
+ end
220
+ end
221
+ end
222
+ include AddMethodTracer
223
+
224
+ # Add a method tracer to the specified method.
225
+ #
226
+ # By default, this will cause invocations of the traced method to be
227
+ # recorded in transaction traces, and in a metric named after the class
228
+ # and method. It will also make the method show up in transaction-level
229
+ # breakdown charts and tables.
230
+ #
231
+ # === Overriding the metric name
232
+ #
233
+ # +metric_name_code+ is a string that is eval'd to get the name of the
234
+ # metric associated with the call, so if you want to use interpolation
235
+ # evaluated at call time, then single quote the value like this:
236
+ #
237
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
238
+ #
239
+ # This would name the metric according to the class of the runtime
240
+ # intance, as opposed to the class where +foo+ is defined.
241
+ #
242
+ # If not provided, the metric name will be <tt>Custom/ClassName/method_name</tt>.
243
+ #
244
+ # @param [Symbol] method_name the name of the method to trace
245
+ # @param [String] metric_name_code the metric name to record calls to
246
+ # the traced method under. This may be either a static string, or Ruby
247
+ # code to be evaluated at call-time in order to determine the metric
248
+ # name dynamically.
249
+ # @param [Hash] options additional options controlling how the method is
250
+ # traced.
251
+ # @option options [Boolean] :push_scope (true) If false, the traced method will
252
+ # not appear in transaction traces or breakdown charts, and it will
253
+ # only be visible in custom dashboards.
254
+ # @option options [Boolean] :metric (true) If false, the traced method will
255
+ # only appear in transaction traces, but no metrics will be recorded
256
+ # for it.
257
+ # @option options [String] :code_header ('') Ruby code to be inserted and run
258
+ # before the tracer begins timing.
259
+ # @option options [String] :code_footer ('') Ruby code to be inserted and run
260
+ # after the tracer stops timing.
261
+ #
262
+ # @example
263
+ # add_method_tracer :foo
264
+ #
265
+ # # With a custom metric name
266
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
267
+ #
268
+ # # Instrument foo only for custom dashboards (not in transaction
269
+ # # traces or breakdown charts)
270
+ # add_method_tracer :foo, 'Custom/foo', :push_scope => false
271
+ #
272
+ # # Instrument foo in transaction traces only
273
+ # add_method_tracer :foo, 'Custom/foo', :metric => false
274
+ #
275
+ # @api public
276
+ #
277
+ def add_method_tracer(method_name, metric_name_code=nil, options = {})
278
+ return unless oneapm_method_exists?(method_name)
279
+ metric_name_code ||= default_metric_name_code(method_name)
280
+ return if traced_method_exists?(method_name, metric_name_code)
281
+
282
+ traced_method = code_to_eval(method_name, metric_name_code, options)
283
+
284
+ visibility = OneApm::Helper.instance_method_visibility self, method_name
285
+
286
+ class_eval traced_method, __FILE__, __LINE__
287
+ alias_method _untraced_method_name(method_name, metric_name_code), method_name
288
+ alias_method method_name, _traced_method_name(method_name, metric_name_code)
289
+ send visibility, method_name
290
+ send visibility, _traced_method_name(method_name, metric_name_code)
291
+ ::OneApm::Agent.logger.debug("Traced method: class = #{self.name},"+
292
+ "method = #{method_name}, "+
293
+ "metric = '#{metric_name_code}'")
294
+ end
295
+
296
+ # For tests only because tracers must be removed in reverse-order
297
+ # from when they were added, or else other tracers that were added to the same method
298
+ # may get removed as well.
299
+ def remove_method_tracer(method_name, metric_name_code) # :nodoc:
300
+ return unless Agent.config[:agent_enabled]
301
+ if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
302
+ alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
303
+ undef_method "#{_traced_method_name(method_name, metric_name_code)}"
304
+ ::OneApm::Agent.logger.debug("removed method tracer #{method_name} #{metric_name_code}\n")
305
+ else
306
+ raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
307
+ end
308
+ end
309
+ private
310
+
311
+ # given a method and a metric, this method returns the
312
+ # untraced alias of the method name
313
+ def _untraced_method_name(method_name, metric_name)
314
+ "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
315
+ end
316
+
317
+ # given a method and a metric, this method returns the traced
318
+ # alias of the method name
319
+ def _traced_method_name(method_name, metric_name)
320
+ "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
321
+ end
322
+
323
+ # makes sure that method names do not contain characters that
324
+ # might break the interpreter, for example ! or ? characters
325
+ # that are not allowed in the middle of method names
326
+ def _sanitize_name(name)
327
+ name.to_s.tr_s('^a-zA-Z0-9', '_')
328
+ end
329
+ end
330
+
331
+ # @!parse extend ClassMethods
332
+ end
333
+ end
334
+ end
@@ -0,0 +1,92 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Support
5
+ module MethodTracer
6
+ module Helpers
7
+ MAX_ALLOWED_METRIC_DURATION = 1_000_000_000 # roughly 31 years
8
+
9
+ extend self
10
+
11
+ # helper for logging errors to the oneapm_agent.log
12
+ # properly. Logs the error at error level
13
+ def log_errors(code_area)
14
+ yield
15
+ rescue => e
16
+ ::OneApm::Agent.logger.error("Caught exception in #{code_area}.", e)
17
+ end
18
+
19
+ def trace_execution_scoped_header(state, t0)
20
+ log_errors(:trace_execution_scoped_header) do
21
+ stack = state.traced_method_stack
22
+ stack.push_frame(state, :method_tracer, t0)
23
+ end
24
+ end
25
+
26
+ def record_metrics(state, first_name, other_names, duration, exclusive, options)
27
+ record_scoped_metric = options.has_key?(:scoped_metric) ? options[:scoped_metric] : true
28
+ stat_engine = OneApm::Agent.instance.stats_engine
29
+ if record_scoped_metric
30
+ stat_engine.record_scoped_and_unscoped_metrics(state, first_name, other_names, duration, exclusive)
31
+ else
32
+ metrics = [first_name].concat(other_names)
33
+ stat_engine.record_unscoped_metrics(state, metrics, duration, exclusive)
34
+ end
35
+ end
36
+
37
+ def trace_execution_scoped_footer(state, t0, first_name, metric_names, expected_frame, options, t1=Time.now.to_f)
38
+ log_errors(:trace_method_execution_footer) do
39
+ if expected_frame
40
+ stack = state.traced_method_stack
41
+ create_metrics = options.has_key?(:metric) ? options[:metric] : true
42
+ frame = stack.pop_frame(state, expected_frame, first_name, t1, create_metrics)
43
+ if create_metrics
44
+ duration = t1 - t0
45
+ exclusive = duration - frame.children_time
46
+
47
+ if duration < MAX_ALLOWED_METRIC_DURATION
48
+ if duration < 0
49
+ ::OneApm::Agent.logger.log_once(:warn, "metric_duration_negative:#{first_name}",
50
+ "Metric #{first_name} has negative duration: #{duration} s")
51
+ end
52
+
53
+ if exclusive < 0
54
+ ::OneApm::Agent.logger.log_once(:warn, "metric_exclusive_negative:#{first_name}",
55
+ "Metric #{first_name} has negative exclusive time: duration = #{duration} s, child_time = #{frame.children_time}")
56
+ end
57
+
58
+ record_metrics(state, first_name, metric_names, duration, exclusive, options)
59
+ else
60
+ ::OneApm::Agent.logger.log_once(:warn, "too_huge_metric:#{first_name}",
61
+ "Ignoring metric #{first_name} with unacceptably large duration: #{duration} s")
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+
68
+ def trace_execution_scoped(metric_names, options={}) #THREAD_LOCAL_ACCESS
69
+ state = OneApm::TransactionState.tl_get
70
+ return yield unless state.is_execution_traced?
71
+
72
+ metric_names = Array(metric_names)
73
+ first_name = metric_names.shift
74
+ return yield unless first_name
75
+
76
+ additional_metrics_callback = options[:additional_metrics_callback]
77
+ start_time = Time.now.to_f
78
+ expected_scope = trace_execution_scoped_header(state, start_time)
79
+
80
+ begin
81
+ result = yield
82
+ metric_names += Array(additional_metrics_callback.call) if additional_metrics_callback
83
+ result
84
+ ensure
85
+ trace_execution_scoped_footer(state, start_time, first_name, metric_names, expected_scope, options)
86
+ end
87
+ end
88
+
89
+ end
90
+ end
91
+ end
92
+ end