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,40 @@
1
+ # encoding: utf-8
2
+
3
+ require 'one_apm/rack/middleware_wrapper'
4
+
5
+ LibraryDetection.defer do
6
+ named :rails_middleware
7
+
8
+ depends_on do
9
+ defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i >= 3
10
+ end
11
+
12
+ depends_on do
13
+ !::OneApm::Agent.config[:disable_rack_middleware]
14
+ end
15
+
16
+ executes do
17
+ ::OneApm::Agent.logger.info("Installing Rails 3+ middleware instrumentation")
18
+
19
+ module ActionDispatch
20
+ class MiddlewareStack
21
+ class Middleware
22
+
23
+ def build_with_one_apm(app)
24
+ # MiddlewareWrapper.wrap guards against double-wrapping here.
25
+ # We need to instrument the innermost app (usually a RouteSet),
26
+ # which will never itself be the return value from #build, but will
27
+ # instead be the initial value of the app argument.
28
+ wrapped_app = ::OneApm::Rack::MiddlewareWrapper.wrap(app)
29
+ result = build_without_one_apm(wrapped_app)
30
+ ::OneApm::Rack::MiddlewareWrapper.wrap(result)
31
+ end
32
+
33
+ alias_method :build_without_one_apm, :build
34
+ alias_method :build, :build_with_one_apm
35
+ end
36
+ end
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,98 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Agent
5
+ module Instrumentation
6
+ class EventedSubscriber
7
+ def initialize
8
+ @queue_key = ['OneApm', self.class.name, object_id].join('-')
9
+ end
10
+
11
+ def self.subscribed?
12
+ # TODO: need to talk to Rails core about an API for this,
13
+ # rather than digging through Listener ivars
14
+ ActiveSupport::Notifications.notifier.instance_variable_get(:@subscribers) \
15
+ .find{|s| s.instance_variable_get(:@delegate).class == self }
16
+ end
17
+
18
+ def self.subscribe(pattern)
19
+ if !subscribed?
20
+ ActiveSupport::Notifications.subscribe(pattern, new)
21
+ end
22
+ end
23
+
24
+ def start(name, id, payload)
25
+ event = ActiveSupport::Notifications::Event.new(name, Time.now, nil, id, payload)
26
+ push_event(event)
27
+ return event
28
+ end
29
+
30
+ def finish(name, id, payload)
31
+ pop_event(id)
32
+ end
33
+
34
+ def log_notification_error(error, name, event_type)
35
+ # These are important enough failures that we want the backtraces
36
+ # logged at error level, hence the explicit log_exception call.
37
+ OneApm::Agent.logger.error("Error during #{event_type} callback for event '#{name}':")
38
+ OneApm::Agent.logger.log_exception(:error, error)
39
+ end
40
+
41
+ def push_event(event)
42
+ parent = event_stack[event.transaction_id].last
43
+ if parent && event.respond_to?(:parent=)
44
+ event.parent = parent
45
+ parent << event
46
+ end
47
+ event_stack[event.transaction_id].push event
48
+ end
49
+
50
+ def pop_event(transaction_id)
51
+ event = event_stack[transaction_id].pop
52
+ event.end = Time.now
53
+ return event
54
+ end
55
+
56
+ def event_stack
57
+ Thread.current[@queue_key] ||= Hash.new {|h,id| h[id] = [] }
58
+ end
59
+ end
60
+
61
+ # Taken from ActiveSupport::Notifications::Event, pasted here
62
+ # with a couple minor additions so we don't have a hard
63
+ # dependency on ActiveSupport::Notifications.
64
+ #
65
+ # Represents an intrumentation event, provides timing and metric
66
+ # name information useful when recording metrics.
67
+ class Event
68
+ attr_reader :name, :time, :transaction_id, :payload, :children
69
+ attr_accessor :end, :parent, :frame
70
+
71
+ def initialize(name, start, ending, transaction_id, payload)
72
+ @name = name
73
+ @payload = payload.dup
74
+ @time = start
75
+ @transaction_id = transaction_id
76
+ @end = ending
77
+ @children = []
78
+ end
79
+
80
+ def metric_name
81
+ raise NotImplementedError
82
+ end
83
+
84
+ def duration
85
+ self.end - time
86
+ end
87
+
88
+ def <<(event)
89
+ @children << event
90
+ end
91
+
92
+ def parent_of?(event)
93
+ @children.include? event
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,39 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Agent
5
+ module Instrumentation
6
+ module IgnoreActions
7
+ def self.is_filtered?(key, klass, action_name)
8
+ # We'll walk the superclass chain and see if
9
+ # any class says 'yes, filter this one'.
10
+
11
+ while klass.respond_to? :oneapm_read_attr
12
+ ignore_actions = klass.oneapm_read_attr(key)
13
+
14
+ should_filter = case ignore_actions
15
+ when Hash
16
+ only_actions = Array(ignore_actions[:only])
17
+ except_actions = Array(ignore_actions[:except])
18
+ action_name = action_name.to_sym
19
+
20
+ only_actions.include?(action_name) || (!except_actions.empty? && !except_actions.include?(action_name))
21
+ else
22
+ !!ignore_actions
23
+ end
24
+
25
+ return true if should_filter
26
+
27
+ # Nothing so far says we should filter,
28
+ # so keep checking up the superclass chain.
29
+ klass = klass.superclass
30
+ end
31
+
32
+ # Getting here means that no class filtered this.
33
+ false
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -0,0 +1,76 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Agent
5
+ module Instrumentation
6
+ # https://oneapm.com/docs/features/tracking-front-end-time
7
+ # Record queue time metrics based on any of three headers
8
+ # which can be set on the request.
9
+ module QueueTime
10
+ unless defined?(REQUEST_START_HEADER)
11
+ REQUEST_START_HEADER = 'HTTP_X_REQUEST_START'.freeze
12
+ QUEUE_START_HEADER = 'HTTP_X_QUEUE_START'.freeze
13
+ MIDDLEWARE_START_HEADER = 'HTTP_X_MIDDLEWARE_START'.freeze
14
+ ALL_QUEUE_METRIC = 'WebFrontend/QueueTime'.freeze
15
+ # any timestamps before this are thrown out and the parser
16
+ # will try again with a larger unit (2000/1/1 UTC)
17
+ EARLIEST_ACCEPTABLE_TIME = Time.at(946684800)
18
+
19
+ CANDIDATE_HEADERS = [
20
+ REQUEST_START_HEADER,
21
+ QUEUE_START_HEADER,
22
+ MIDDLEWARE_START_HEADER
23
+ ].freeze
24
+
25
+ DIVISORS = [1_000_000, 1_000, 1]
26
+ end
27
+
28
+ module_function
29
+
30
+ def parse_frontend_timestamp(headers, now=Time.now)
31
+ earliest = nil
32
+
33
+ CANDIDATE_HEADERS.each do |header|
34
+ if headers[header]
35
+ parsed = parse_timestamp(timestamp_string_from_header_value(headers[header]))
36
+ if parsed && (!earliest || parsed < earliest)
37
+ earliest = parsed
38
+ end
39
+ end
40
+ end
41
+
42
+ if earliest && earliest > now
43
+ OneApm::Agent.logger.debug("Negative queue time detected, treating as zero: start=#{earliest.to_f} > now=#{now.to_f}")
44
+ earliest = now
45
+ end
46
+
47
+ earliest
48
+ end
49
+
50
+ def timestamp_string_from_header_value(value)
51
+ case value
52
+ when /^\s*([\d+\.]+)\s*$/ then $1
53
+ # following regexp intentionally unanchored to handle
54
+ # (ie ignore) leading server names
55
+ when /t=([\d+\.]+)/ then $1
56
+ end
57
+ end
58
+
59
+ def parse_timestamp(string)
60
+ DIVISORS.each do |divisor|
61
+ begin
62
+ t = Time.at(string.to_f / divisor)
63
+ return t if t > EARLIEST_ACCEPTABLE_TIME
64
+ rescue RangeError
65
+ # On Ruby versions built with a 32-bit time_t, attempting to
66
+ # instantiate a Time object in the far future raises a RangeError,
67
+ # in which case we know we've chosen the wrong divisor.
68
+ end
69
+ end
70
+
71
+ nil
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,405 @@
1
+ # encoding: utf-8
2
+
3
+ require 'one_apm/transaction'
4
+ require 'one_apm/transaction/transaction_namer'
5
+ require 'one_apm/inst/support/queue_time'
6
+ require 'one_apm/inst/support/ignore_actions'
7
+
8
+ module OneApm
9
+ module Agent
10
+ module Instrumentation
11
+ #
12
+ # OneApm instrumentation for transactions
13
+ #
14
+ # * controller actions
15
+ # * background tasks
16
+ # * external web calls
17
+ #
18
+ # see
19
+ # * add_transaction_tracer
20
+ # * perform_action_with_oneapm_trace
21
+ #
22
+ module TransactionBase
23
+
24
+ def self.included(clazz)
25
+ clazz.extend(ClassMethods)
26
+ end
27
+
28
+ module Shim
29
+ module ClassMethodsShim
30
+ def oneapm_ignore(*args); end
31
+ def oneapm_ignore_apdex(*args); end
32
+ def oneapm_ignore_enduser(*args); end
33
+ end
34
+
35
+ def self.included(clazz)
36
+ clazz.extend(ClassMethodsShim)
37
+ end
38
+ def oneapm_notice_error(*args); end
39
+ def one_apm_trace_controller_action(*args); yield; end
40
+ def perform_action_with_oneapm_trace(*args); yield; end
41
+ end
42
+
43
+ OA_DO_NOT_TRACE_KEY = :'@do_not_trace' unless defined?(OA_DO_NOT_TRACE_KEY )
44
+ OA_IGNORE_APDEX_KEY = :'@ignore_apdex' unless defined?(OA_IGNORE_APDEX_KEY )
45
+ OA_IGNORE_ENDUSER_KEY = :'@ignore_enduser' unless defined?(OA_IGNORE_ENDUSER_KEY)
46
+ OA_DEFAULT_OPTIONS = {}.freeze unless defined?(OA_DEFAULT_OPTIONS )
47
+
48
+ module ClassMethods
49
+
50
+ # Have OneApm ignore actions in this controller.
51
+ # Specify the actions as hash options using :except and :only.
52
+ # If no actions are specified, all actions are ignored.
53
+ #
54
+ # @api public
55
+ #
56
+ def oneapm_ignore(options = {})
57
+ oneapm_ignore_aspect(OA_DO_NOT_TRACE_KEY, options)
58
+ end
59
+
60
+ # Have OneApm omit apdex measurements on the given actions.
61
+ # Typically used for actions that are not user facing or that skew your overall apdex measurement.
62
+ # Accepts :except and :only options.
63
+ #
64
+ # @api public
65
+ #
66
+ def oneapm_ignore_apdex(options = {})
67
+ oneapm_ignore_aspect(OA_IGNORE_APDEX_KEY, options)
68
+ end
69
+
70
+ # Have OneApm skip install javascript_instrumentation
71
+ #
72
+ # @api public
73
+ #
74
+ def oneapm_ignore_enduser(options = {})
75
+ oneapm_ignore_aspect(OA_IGNORE_ENDUSER_KEY, options)
76
+ end
77
+
78
+ def oneapm_ignore_aspect(property, options = {})
79
+ if options.empty?
80
+ oneapm_write_attr property, true
81
+ elsif !options.is_a?(Hash)
82
+ ::OneApm::Agent.logger.error "oneapm_#{property} takes an optional hash with :only and :except lists of actions (illegal argument type '#{options.class}')"
83
+ else
84
+ oneapm_write_attr property, options
85
+ end
86
+ end
87
+
88
+ def oneapm_write_attr(attr_name, value)
89
+ instance_variable_set(attr_name, value)
90
+ end
91
+
92
+ def oneapm_read_attr(attr_name)
93
+ instance_variable_get(attr_name)
94
+ end
95
+
96
+ # Add transaction tracing to the given method.
97
+ # This will treat the given method as a main entrypoint for instrumentation,
98
+ # just like controller actions are treated by default.
99
+ # Useful especially for background tasks.
100
+ #
101
+ # Example for background job:
102
+ # class Job
103
+ # include OneApm::Agent::Instrumentation::TransactionBase
104
+ # def run(task)
105
+ # ...
106
+ # end
107
+ #
108
+ # # Instrument run so tasks show up under task.name.
109
+ # # Note single quoting to defer eval to runtime.
110
+ # add_transaction_tracer :run, :name => '#{args[0].name}'
111
+ # end
112
+ #
113
+ # Here's an example of a controller that uses a dispatcher
114
+ # action to invoke operations which you want treated as top
115
+ # level actions, so they aren't all lumped into the invoker
116
+ # action.
117
+ #
118
+ # MyController < ActionController::Base
119
+ # include OneApm::Agent::Instrumentation::TransactionBase
120
+ # # dispatch the given op to the method given by the service parameter.
121
+ # def invoke_operation
122
+ # op = params['operation']
123
+ # send op
124
+ # end
125
+ # # Ignore the invoker to avoid double counting
126
+ # oneapm_ignore :only => 'invoke_operation'
127
+ # # Instrument the operations:
128
+ # add_transaction_tracer :print
129
+ # add_transaction_tracer :show
130
+ # add_transaction_tracer :forward
131
+ # end
132
+ #
133
+ # Here's an example of how to pass contextual information into the transaction
134
+ # so it will appear in transaction traces:
135
+ #
136
+ # class Job
137
+ # include OneApm::Agent::Instrumentation::TransactionBase
138
+ # def process(account)
139
+ # ...
140
+ # end
141
+ # # Include the account name in the transaction details. Note the single
142
+ # # quotes to defer eval until call time.
143
+ # add_transaction_tracer :process, :params => '{ :account_name => args[0].name }'
144
+ # end
145
+ #
146
+ # See OneApm::Agent::Instrumentation::TransactionBase#perform_action_with_oneapm_trace
147
+ # for the full list of available options.
148
+ #
149
+ # @api public
150
+ #
151
+ def add_transaction_tracer(method, options = {})
152
+ options[:name] ||= method.to_s
153
+
154
+ argument_list = generate_argument_list(options)
155
+ traced_method, punctuation = parse_punctuation(method)
156
+ with_method_name, without_method_name = build_method_names(traced_method, punctuation)
157
+
158
+ if already_added_transaction_tracer?(self, with_method_name)
159
+ ::OneApm::Agent.logger.warn("Transaction tracer already in place for class = #{self.name}, method = #{method.to_s}, skipping")
160
+ return
161
+ end
162
+
163
+ class_eval <<-EOC
164
+ def #{with_method_name}(*args, &block)
165
+ perform_action_with_oneapm_trace(#{argument_list.join(',')}) do
166
+ #{without_method_name}(*args, &block)
167
+ end
168
+ end
169
+ EOC
170
+
171
+ visibility = OneApm::Helper.instance_method_visibility self, method
172
+
173
+ alias_method without_method_name, method.to_s
174
+ alias_method method.to_s, with_method_name
175
+
176
+ send visibility, method
177
+ send visibility, with_method_name
178
+
179
+ ::OneApm::Agent.logger.debug("Traced transaction: class = #{self.name}, method = #{method.to_s}, options = #{options.inspect}")
180
+ end
181
+
182
+ def parse_punctuation(method)
183
+ [method.to_s.sub(/([?!=])$/, ''), $1]
184
+ end
185
+
186
+ def generate_argument_list(options)
187
+ options.map do |key, value|
188
+ value = if value.is_a?(Symbol)
189
+ value.inspect
190
+ elsif key == :params
191
+ value.to_s
192
+ else
193
+ %Q["#{value.to_s}"]
194
+ end
195
+
196
+ %Q[:#{key} => #{value}]
197
+ end
198
+ end
199
+
200
+ def build_method_names(traced_method, punctuation)
201
+ [
202
+ "#{traced_method.to_s}_with_oneapm_transaction_trace#{punctuation}",
203
+ "#{traced_method.to_s}_without_oneapm_transaction_trace#{punctuation}"
204
+ ]
205
+ end
206
+
207
+ def already_added_transaction_tracer?(target, with_method_name)
208
+ if OneApm::Helper.instance_methods_include?(target, with_method_name)
209
+ true
210
+ else
211
+ false
212
+ end
213
+ end
214
+ end
215
+
216
+ # Yield to the given block with OneApm tracing. Used by
217
+ # default instrumentation on controller actions in Rails.
218
+ # But it can also be used in custom instrumentation of controller
219
+ # methods and background tasks.
220
+ #
221
+ # This is the method invoked by instrumentation added by the
222
+ # <tt>ClassMethods#add_transaction_tracer</tt>.
223
+ #
224
+ # Here's a more verbose version of the example shown in
225
+ # <tt>ClassMethods#add_transaction_tracer</tt> using this method instead of
226
+ # #add_transaction_tracer.
227
+ #
228
+ # Below is a controller with an +invoke_operation+ action which
229
+ # dispatches to more specific operation methods based on a
230
+ # parameter (very dangerous, btw!). With this instrumentation,
231
+ # the +invoke_operation+ action is ignored but the operation
232
+ # methods show up in OneApm as if they were first class controller
233
+ # actions
234
+ #
235
+ # MyController < ActionController::Base
236
+ # include OneApm::Agent::Instrumentation::TransactionBase
237
+ # # dispatch the given op to the method given by the service parameter.
238
+ # def invoke_operation
239
+ # op = params['operation']
240
+ # perform_action_with_oneapm_trace(:name => op) do
241
+ # send op, params['message']
242
+ # end
243
+ # end
244
+ # # Ignore the invoker to avoid double counting
245
+ # oneapm_ignore :only => 'invoke_operation'
246
+ # end
247
+ #
248
+ #
249
+ # When invoking this method explicitly as in the example above, pass in a
250
+ # block to measure with some combination of options:
251
+ #
252
+ # * <tt>:category => :controller</tt> indicates that this is a
253
+ # controller action and will appear with all the other actions. This
254
+ # is the default.
255
+ # * <tt>:category => :task</tt> indicates that this is a
256
+ # background task and will show up in OneApm with other background
257
+ # tasks instead of in the controllers list
258
+ # * <tt>:category => :middleware</tt> if you are instrumenting a rack
259
+ # middleware call. The <tt>:name</tt> is optional, useful if you
260
+ # have more than one potential transaction in the #call.
261
+ # * <tt>:category => :uri</tt> indicates that this is a
262
+ # web transaction whose name is a normalized URI, where 'normalized'
263
+ # means the URI does not have any elements with data in them such
264
+ # as in many REST URIs.
265
+ # * <tt>:name => action_name</tt> is used to specify the action
266
+ # name used as part of the metric name
267
+ # * <tt>:params => {...}</tt> to provide information about the context
268
+ # of the call, used in transaction trace display, for example:
269
+ # <tt>:params => { :account => @account.name, :file => file.name }</tt>
270
+ # These are treated similarly to request parameters in web transactions.
271
+ #
272
+ # Seldomly used options:
273
+ #
274
+ # * <tt>:class_name => aClass.name</tt> is used to override the name
275
+ # of the class when used inside the metric name. Default is the
276
+ # current class.
277
+ # * <tt>:path => metric_path</tt> is *deprecated* in the public API. It
278
+ # allows you to set the entire metric after the category part. Overrides
279
+ # all the other options.
280
+ # * <tt>:request => Rack::Request#new(env)</tt> is used to pass in a
281
+ # request object that may respond to uri and referer.
282
+ #
283
+ # @api public
284
+ #
285
+ def perform_action_with_oneapm_trace(*args, &block) #THREAD_LOCAL_ACCESS
286
+ state = OneApm::TransactionState.tl_get
287
+ state.request = oneapm_request(args)
288
+
289
+ skip_tracing = do_not_trace? || !state.is_execution_traced?
290
+
291
+ if skip_tracing
292
+ state.current_transaction.ignore! if state.current_transaction
293
+ OneApm::Agent.disable_all_tracing { return yield }
294
+ end
295
+
296
+ # This method has traditionally taken a variable number of arguments, but the
297
+ # only one that is expected / used is a single options hash. We are preserving
298
+ # the *args method signature to ensure backwards compatibility.
299
+
300
+ trace_options = args.last.is_a?(Hash) ? args.last : OA_DEFAULT_OPTIONS
301
+ category = trace_options[:category] || :controller
302
+ txn_options = create_transaction_options(trace_options, category, state)
303
+
304
+ begin
305
+ txn = Transaction.start(state, category, txn_options)
306
+
307
+ begin
308
+ yield
309
+ rescue => e
310
+ OneApm::Agent.notice_error(e)
311
+ raise
312
+ end
313
+
314
+ ensure
315
+ if txn
316
+ txn.ignore_apdex! if ignore_apdex?
317
+ txn.ignore_enduser! if ignore_enduser?
318
+ end
319
+ Transaction.stop(state)
320
+ end
321
+ end
322
+
323
+ protected
324
+
325
+ def oneapm_request(args)
326
+ opts = args.first
327
+ # passed as a parameter to add_transaction_tracer
328
+ if opts.respond_to?(:keys) && opts.respond_to?(:[]) && opts[:request]
329
+ opts[:request]
330
+ # in a Rails app
331
+ elsif self.respond_to?(:request)
332
+ self.request
333
+ end
334
+ end
335
+
336
+ # Should be implemented in the dispatcher class
337
+ def oneapm_response_code; end
338
+
339
+ def oneapm_request_headers(state)
340
+ request = state.request
341
+ if request
342
+ if request.respond_to?(:headers)
343
+ request.headers
344
+ elsif request.respond_to?(:env)
345
+ request.env
346
+ end
347
+ end
348
+ end
349
+
350
+ # overrideable method to determine whether to trace an action
351
+ # or not - you may override this in your controller and supply
352
+ # your own logic for ignoring transactions.
353
+ def do_not_trace?
354
+ _is_filtered?(OA_DO_NOT_TRACE_KEY)
355
+ end
356
+
357
+ # overrideable method to determine whether to trace an action
358
+ # for purposes of apdex measurement - you can use this to
359
+ # ignore things like api calls or other fast non-user-facing
360
+ # actions
361
+ def ignore_apdex?
362
+ _is_filtered?(OA_IGNORE_APDEX_KEY)
363
+ end
364
+
365
+ def ignore_enduser?
366
+ _is_filtered?(OA_IGNORE_ENDUSER_KEY)
367
+ end
368
+
369
+ private
370
+
371
+ def create_transaction_options(trace_options, category, state)
372
+ txn_options = {}
373
+ txn_options[:request] = trace_options[:request]
374
+ txn_options[:request] ||= request if respond_to?(:request)
375
+ # params should have been filtered before calling perform_action_with_oneapm_trace
376
+ txn_options[:filtered_params] = trace_options[:params]
377
+ txn_options[:transaction_name] = OneApm::TransactionNamer.name_for(nil, self, category, trace_options)
378
+ txn_options[:apdex_start_time] = detect_queue_start_time(state)
379
+ txn_options
380
+ end
381
+
382
+ # Filter out a request if it matches one of our parameters for
383
+ # ignoring it - the key is either OA_DO_NOT_TRACE_KEY or OA_IGNORE_APDEX_KEY
384
+ def _is_filtered?(key)
385
+ name = if respond_to?(:action_name)
386
+ action_name
387
+ else
388
+ :'[action_name_missing]'
389
+ end
390
+
391
+ OneApm::Agent::Instrumentation::IgnoreActions.is_filtered?(
392
+ key,
393
+ self.class,
394
+ name)
395
+ end
396
+
397
+ def detect_queue_start_time(state)
398
+ headers = oneapm_request_headers(state)
399
+
400
+ QueueTime.parse_frontend_timestamp(headers) if headers
401
+ end
402
+ end
403
+ end
404
+ end
405
+ end