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,181 @@
1
+ # encoding: utf-8
2
+
3
+ require 'digest'
4
+
5
+ require 'one_apm/agent/inbound_request_monitor'
6
+ require 'one_apm/agent/threading/agent_thread'
7
+ require 'one_apm/transaction/transaction_state'
8
+
9
+ module OneApm
10
+ module Agent
11
+ class CrossAppMonitor < InboundRequestMonitor
12
+
13
+ ONEAPM_ID_HEADER = 'X-OneApm-ID'
14
+ ONEAPM_TXN_HEADER = 'X-OneApm-Transaction'
15
+ ONEAPM_APPDATA_HEADER = 'X-OneApm-App-Data'
16
+
17
+ ONEAPM_ID_HEADER_KEY = 'HTTP_X_ONEAPM_ID'.freeze
18
+ ONEAPM_TXN_HEADER_KEY = 'HTTP_X_ONEAPM_TRANSACTION'.freeze
19
+ CONTENT_LENGTH_HEADER_KEY = 'HTTP_CONTENT_LENGTH'.freeze
20
+
21
+ def on_finished_configuring(events)
22
+ register_event_listeners(events)
23
+ end
24
+
25
+ # Expected sequence of events:
26
+ # :before_call will save our cross application request id to the thread
27
+ # :after_call will write our response headers/metrics and clean up the thread
28
+ def register_event_listeners(events)
29
+ OneApm::Agent.logger.
30
+ debug("Wiring up Cross Application Tracing to events after finished configuring")
31
+
32
+ events.subscribe(:before_call) do |env| #THREAD_LOCAL_ACCESS
33
+ if should_process_request(env)
34
+ state = OneApm::TransactionState.tl_get
35
+
36
+ save_client_cross_app_id(state, env)
37
+ save_referring_transaction_info(state, env)
38
+ set_transaction_custom_parameters(state)
39
+ end
40
+ end
41
+
42
+ events.subscribe(:after_call) do |env, (_status_code, headers, _body)| #THREAD_LOCAL_ACCESS
43
+ state = OneApm::TransactionState.tl_get
44
+
45
+ insert_response_header(state, env, headers)
46
+ end
47
+
48
+ events.subscribe(:notice_error) do |_, options| #THREAD_LOCAL_ACCESS
49
+ state = OneApm::TransactionState.tl_get
50
+
51
+ set_error_custom_parameters(state, options)
52
+ end
53
+ end
54
+
55
+ def save_client_cross_app_id(state, request_headers)
56
+ state.client_cross_app_id = decoded_id(request_headers)
57
+ end
58
+
59
+ def clear_client_cross_app_id(state)
60
+ state.client_cross_app_id = nil
61
+ end
62
+
63
+ def save_referring_transaction_info(state, request_headers)
64
+ txn_header = request_headers[ONEAPM_TXN_HEADER_KEY] or return
65
+ txn_info = deserialize_header(txn_header, ONEAPM_TXN_HEADER)
66
+ state.referring_transaction_info = txn_info
67
+ end
68
+
69
+ def client_referring_transaction_guid(state)
70
+ info = state.referring_transaction_info or return nil
71
+ return info[0]
72
+ end
73
+
74
+ def client_referring_transaction_record_flag(state)
75
+ info = state.referring_transaction_info or return nil
76
+ return info[1]
77
+ end
78
+
79
+ def client_referring_transaction_trip_id(state)
80
+ info = state.referring_transaction_info or return nil
81
+ return info[2].is_a?(String) && info[2]
82
+ end
83
+
84
+ def client_referring_transaction_path_hash(state)
85
+ info = state.referring_transaction_info or return nil
86
+ return info[3].is_a?(String) && info[3]
87
+ end
88
+
89
+ def insert_response_header(state, request_headers, response_headers)
90
+ unless state.client_cross_app_id.nil?
91
+ txn = state.current_transaction
92
+ unless txn.nil?
93
+ txn.freeze_name_and_execute_if_not_ignored do
94
+ timings = state.timings
95
+ content_length = content_length_from_request(request_headers)
96
+
97
+ set_response_headers(state, response_headers, timings, content_length)
98
+ set_metrics(state.client_cross_app_id, timings)
99
+ end
100
+ end
101
+ clear_client_cross_app_id(state)
102
+ end
103
+ end
104
+
105
+ def should_process_request(request_headers)
106
+ return cross_app_enabled? && trusts?(request_headers)
107
+ end
108
+
109
+ def cross_app_enabled?
110
+ OneApm::Agent::CrossAppTracing.cross_app_enabled?
111
+ end
112
+
113
+ # Expects an ID of format "12#345", and will only accept that!
114
+ def trusts?(request)
115
+ id = decoded_id(request)
116
+ split_id = id.match(/(\d+)#\d+/)
117
+ return false if split_id.nil?
118
+
119
+ OneApm::Agent.config[:trusted_account_ids].include?(split_id.captures.first.to_i)
120
+ end
121
+
122
+ def set_response_headers(state, response_headers, timings, content_length)
123
+ response_headers[ONEAPM_APPDATA_HEADER] = build_payload(state, timings, content_length)
124
+ end
125
+
126
+ def build_payload(state, timings, content_length)
127
+ payload = [
128
+ OneApm::Agent.config[:cross_process_id],
129
+ timings.transaction_name,
130
+ timings.queue_time_in_seconds.to_f,
131
+ timings.app_time_in_seconds.to_f,
132
+ content_length,
133
+ state.request_guid
134
+ ]
135
+ payload = obfuscator.obfuscate(OneApm::JSONWrapper.dump(payload))
136
+ end
137
+
138
+ def set_transaction_custom_parameters(state)
139
+ # We expect to get the before call to set the id (if we have it) before
140
+ # this, and then write our custom parameter when the transaction starts
141
+ OneApm::Agent.add_custom_parameters(:client_cross_process_id => state.client_cross_app_id) if state.client_cross_app_id
142
+
143
+ referring_guid = client_referring_transaction_guid(state)
144
+ if referring_guid
145
+ OneApm::Agent.add_custom_parameters(:referring_transaction_guid => referring_guid)
146
+ end
147
+ end
148
+
149
+ def set_error_custom_parameters(state, options)
150
+ options[:client_cross_process_id] = state.client_cross_app_id if state.client_cross_app_id
151
+ end
152
+
153
+ def set_metrics(id, timings)
154
+ metric_name = "ClientApplication/#{id}/all"
155
+ OneApm::Agent.record_metric(metric_name, timings.app_time_in_seconds)
156
+ end
157
+
158
+ def decoded_id(request)
159
+ encoded_id = request[ONEAPM_ID_HEADER_KEY]
160
+ return "" if encoded_id.nil?
161
+
162
+ obfuscator.deobfuscate(encoded_id)
163
+ end
164
+
165
+ def content_length_from_request(request)
166
+ request[CONTENT_LENGTH_HEADER_KEY] || -1
167
+ end
168
+
169
+ def hash_transaction_name(identifier)
170
+ Digest::MD5.digest(identifier).unpack("@12N").first & 0xffffffff
171
+ end
172
+
173
+ def path_hash(txn_name, seed)
174
+ rotated = ((seed << 1) | (seed >> 31)) & 0xffffffff
175
+ app_name = OneApm::Agent.config.app_names.first
176
+ identifier = "#{app_name};#{txn_name}"
177
+ sprintf("%08x", rotated ^ hash_transaction_name(identifier))
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,336 @@
1
+ # encoding: utf-8
2
+
3
+ module OneApm
4
+ module Agent
5
+ module CrossAppTracing
6
+
7
+ # Exception raised if there is a problem with cross app transactions.
8
+ class Error < RuntimeError; end
9
+
10
+ # The cross app response header for "outgoing" calls
11
+ OA_APPDATA_HEADER = 'X-OneApm-App-Data'
12
+
13
+ # The cross app id header for "outgoing" calls
14
+ OA_ID_HEADER = 'X-OneApm-ID'
15
+
16
+ # The cross app transaction header for "outgoing" calls
17
+ OA_TXN_HEADER = 'X-OneApm-Transaction'
18
+
19
+ # The cross app synthetics header
20
+ OA_SYNTHETICS_HEADER = 'X-OneApm-Synthetics'
21
+
22
+ # The index of the transaction GUID in the appdata header of responses
23
+ APPDATA_TXN_GUID_INDEX = 5
24
+
25
+
26
+ ###############
27
+ module_function
28
+ ###############
29
+
30
+ # Send the given +request+, adding metrics appropriate to the
31
+ # response when it comes back.
32
+ #
33
+ # See the documentation for +start_trace+ for an explanation of what
34
+ # +request+ should look like.
35
+ #
36
+ def tl_trace_http_request(request)
37
+ state = OneApm::TransactionState.tl_get
38
+ return yield unless state.is_execution_traced?
39
+
40
+ # It's important to set t0 outside the ensured block, otherwise there's
41
+ # a race condition if we raise after begin but before t0's set.
42
+ t0 = Time.now
43
+ begin
44
+ segment = start_trace(state, t0, request)
45
+ response = yield
46
+ ensure
47
+ finish_trace(state, t0, segment, request, response)
48
+ end
49
+
50
+ return response
51
+ end
52
+
53
+ # Set up the necessary state for cross-application tracing before the
54
+ # given +request+ goes out.
55
+ #
56
+ # The +request+ object passed in must respond to the following methods:
57
+ #
58
+ # * type - Return a String describing the underlying library being used
59
+ # to make the request (e.g. 'Net::HTTP' or 'Typhoeus')
60
+ # * host - Return a String with the hostname or IP of the host being
61
+ # communicated with.
62
+ # * method - Return a String with the HTTP method name for this request
63
+ # * [](key) - Lookup an HTTP request header by name
64
+ # * []=(key, val) - Set an HTTP request header by name
65
+ # * uri - Full URI of the request
66
+ #
67
+ # This method returns the transaction segment if it was sucessfully pushed.
68
+ def start_trace(state, t0, request)
69
+ inject_request_headers(state, request) if cross_app_enabled?
70
+ stack = state.traced_method_stack
71
+ segment = stack.push_frame(state, :http_request, t0)
72
+
73
+ return segment
74
+ rescue => err
75
+ OneApm::Agent.logger.error "Uncaught exception while tracing HTTP request", err
76
+ return nil
77
+ rescue Exception => e
78
+ OneApm::Agent.logger.debug "Unexpected exception raised while tracing HTTP request", e
79
+
80
+ raise e
81
+ end
82
+
83
+
84
+ # Finish tracing the HTTP +request+ that started at +t0+ with the information in
85
+ # +response+ and the given +http+ connection.
86
+ #
87
+ # The +request+ must conform to the same interface described in the documentation
88
+ # for +start_trace+.
89
+ #
90
+ # The +response+ must respond to the following methods:
91
+ #
92
+ # * [](key) - Reads response headers.
93
+ # * to_hash - Converts response headers to a Hash
94
+ #
95
+ def finish_trace(state, t0, segment, request, response)
96
+ unless t0
97
+ OneApm::Agent.logger.error("HTTP request trace finished without start time. This is probably an agent bug.")
98
+ return
99
+ end
100
+
101
+ t1 = Time.now
102
+ duration = t1.to_f - t0.to_f
103
+
104
+ begin
105
+ if request
106
+ # Figure out which metrics we need to report based on the request and response
107
+ # The last (most-specific) one is scoped.
108
+ metrics = metrics_for(request, response)
109
+ scoped_metric = metrics.pop
110
+
111
+ stats_engine.record_scoped_and_unscoped_metrics(
112
+ state, scoped_metric, metrics, duration)
113
+
114
+ # If we don't have segment, something failed during start_trace so
115
+ # the current segment isn't the HTTP call it should have been.
116
+ if segment
117
+ segment.name = scoped_metric
118
+ add_transaction_trace_parameters(request, response)
119
+ end
120
+ end
121
+ ensure
122
+ # If we have a segment, always pop the traced method stack to avoid
123
+ # an inconsistent state, which prevents tracing of whole transaction.
124
+ if segment
125
+ stack = state.traced_method_stack
126
+ stack.pop_frame(state, segment, scoped_metric, t1)
127
+ end
128
+ end
129
+ rescue OneApm::Agent::CrossAppTracing::Error => err
130
+ OneApm::Agent.logger.debug "while cross app tracing", err
131
+ rescue => err
132
+ OneApm::Agent.logger.error "Uncaught exception while finishing an HTTP request trace", err
133
+ end
134
+
135
+ # Return +true+ if cross app tracing is enabled in the config.
136
+ def cross_app_enabled?
137
+ valid_cross_process_id? &&
138
+ valid_encoding_key? &&
139
+ cross_application_tracer_enabled?
140
+ end
141
+
142
+ def valid_cross_process_id?
143
+ OneApm::Agent.config[:cross_process_id] && OneApm::Agent.config[:cross_process_id].length > 0
144
+ end
145
+
146
+ def valid_encoding_key?
147
+ OneApm::Agent.config[:encoding_key] && OneApm::Agent.config[:encoding_key].length > 0
148
+ end
149
+
150
+ def cross_application_tracer_enabled?
151
+ OneApm::Agent.config[:"cross_application_tracer.enabled"]
152
+ end
153
+
154
+ # Fetcher for the cross app encoding key. Raises a
155
+ # OneApm::Agent::CrossAppTracing::Error if the key isn't configured.
156
+ def cross_app_encoding_key
157
+ OneApm::Agent.config[:encoding_key] or
158
+ raise OneApm::Agent::CrossAppTracing::Error, "No encoding_key set."
159
+ end
160
+
161
+ def obfuscator
162
+ @obfuscator ||= OneApm::Agent::Obfuscator.new(cross_app_encoding_key)
163
+ end
164
+
165
+ # Inject the X-Process header into the outgoing +request+.
166
+ def inject_request_headers(state, request)
167
+ cross_app_id = OneApm::Agent.config[:cross_process_id] or
168
+ raise OneApm::Agent::CrossAppTracing::Error, "no cross app ID configured"
169
+
170
+ state.is_cross_app_caller = true
171
+ txn_guid = state.request_guid
172
+ txn = state.current_transaction
173
+ if txn
174
+ trip_id = txn.cat_trip_id(state)
175
+ path_hash = txn.cat_path_hash(state)
176
+
177
+ if txn.raw_synthetics_header
178
+ request[OA_SYNTHETICS_HEADER] = txn.raw_synthetics_header
179
+ end
180
+ end
181
+ txn_data = OneApm::JSONWrapper.dump([txn_guid, false, trip_id, path_hash])
182
+
183
+ request[OA_ID_HEADER] = obfuscator.obfuscate(cross_app_id)
184
+ request[OA_TXN_HEADER] = obfuscator.obfuscate(txn_data)
185
+
186
+ rescue OneApm::Agent::CrossAppTracing::Error => err
187
+ OneApm::Agent.logger.debug "Not injecting x-process header", err
188
+ end
189
+
190
+ def add_transaction_trace_parameters(request, response)
191
+ filtered_uri = ::OneApm::Support::HTTPClients::URIUtil.filter_uri(request.uri)
192
+ transaction_sampler.add_segment_parameters(:uri => filtered_uri)
193
+ if response && response_is_crossapp?(response)
194
+ add_cat_transaction_trace_parameters(response)
195
+ end
196
+ end
197
+
198
+
199
+ # Extract any custom parameters from +response+ if it's cross-application and
200
+ # add them to the current TT node.
201
+ def add_cat_transaction_trace_parameters( response )
202
+ appdata = extract_appdata( response )
203
+ transaction_sampler.add_segment_parameters( \
204
+ :transaction_guid => appdata[APPDATA_TXN_GUID_INDEX] )
205
+ end
206
+
207
+
208
+ # Return the set of metric names that correspond to
209
+ # the given +request+ and +response+.
210
+ # +response+ may be nil in the case that the request produced an error
211
+ # without ever receiving an HTTP response.
212
+ def metrics_for( request, response )
213
+ metrics = common_metrics( request )
214
+
215
+ if response && response_is_crossapp?( response )
216
+ begin
217
+ metrics.concat metrics_for_crossapp_response( request, response )
218
+ rescue => err
219
+ # Fall back to regular metrics if there's a problem with x-process metrics
220
+ OneApm::Agent.logger.debug "%p while fetching x-process metrics: %s" %
221
+ [ err.class, err.message ]
222
+ metrics.concat metrics_for_regular_request( request )
223
+ end
224
+ else
225
+ metrics.concat metrics_for_regular_request( request )
226
+ end
227
+
228
+ return metrics
229
+ end
230
+
231
+
232
+ # Return an Array of metrics used for every response.
233
+ def common_metrics( request )
234
+ metrics = [ "External/all" ]
235
+ metrics << "External/#{request.host}/all"
236
+
237
+ if OneApm::Transaction.recording_web_transaction?
238
+ metrics << "External/allWeb"
239
+ else
240
+ metrics << "External/allOther"
241
+ end
242
+
243
+ return metrics
244
+ end
245
+
246
+
247
+ # Returns +true+ if Cross Application Tracing is enabled, and the given +response+
248
+ # has the appropriate headers.
249
+ def response_is_crossapp?( response )
250
+ return false unless cross_app_enabled?
251
+ unless response[OA_APPDATA_HEADER]
252
+ return false
253
+ end
254
+
255
+ return true
256
+ end
257
+
258
+
259
+ # Return the set of metric objects appropriate for the given cross app
260
+ # +response+.
261
+ def metrics_for_crossapp_response( request, response )
262
+ xp_id, txn_name, _q_time, _r_time, _req_len, _ = extract_appdata( response )
263
+
264
+ check_crossapp_id( xp_id )
265
+ check_transaction_name( txn_name )
266
+
267
+ metrics = []
268
+ metrics << "ExternalApp/#{request.host}/#{xp_id}/all"
269
+ metrics << "ExternalTransaction/#{request.host}/#{xp_id}/#{txn_name}"
270
+
271
+ return metrics
272
+ end
273
+
274
+
275
+ # Extract x-process application data from the specified +response+ and return
276
+ # it as an array of the form:
277
+ #
278
+ # [
279
+ # <cross app ID>,
280
+ # <transaction name>,
281
+ # <queue time in seconds>,
282
+ # <response time in seconds>,
283
+ # <request content length in bytes>,
284
+ # <transaction GUID>
285
+ # ]
286
+ def extract_appdata( response )
287
+ appdata = response[OA_APPDATA_HEADER] or
288
+ raise OneApm::Agent::CrossAppTracing::Error,
289
+ "Can't derive metrics for response: no #{OA_APPDATA_HEADER} header!"
290
+
291
+ decoded_appdata = obfuscator.deobfuscate( appdata )
292
+ decoded_appdata.set_encoding( ::Encoding::UTF_8 ) if
293
+ decoded_appdata.respond_to?( :set_encoding )
294
+
295
+ return OneApm::JSONWrapper.load( decoded_appdata )
296
+ end
297
+
298
+
299
+ # Return the set of metric objects appropriate for the given (non-cross app)
300
+ # +request+.
301
+ def metrics_for_regular_request( request )
302
+ metrics = []
303
+ metrics << "External/#{request.host}/#{request.type}/#{request.method}"
304
+
305
+ return metrics
306
+ end
307
+
308
+
309
+ # Fetch a reference to the stats engine.
310
+ def stats_engine
311
+ OneApm::Agent.instance.stats_engine
312
+ end
313
+
314
+ def transaction_sampler
315
+ OneApm::Agent.instance.transaction_sampler
316
+ end
317
+
318
+ # Check the given +id+ to ensure it conforms to the format of a cross-application
319
+ # ID. Raises an OneApm::Agent::CrossAppTracing::Error if it doesn't.
320
+ def check_crossapp_id( id )
321
+ id =~ /\A\d+#\d+\z/ or
322
+ raise OneApm::Agent::CrossAppTracing::Error,
323
+ "malformed cross application ID %p" % [ id ]
324
+ end
325
+
326
+
327
+ # Check the given +name+ to ensure it conforms to the format of a valid transaction
328
+ # name.
329
+ def check_transaction_name( name )
330
+ # No-op -- apparently absolutely anything is a valid transaction name?
331
+ # This is here for when that inevitably comes back to haunt us.
332
+ end
333
+
334
+ end
335
+ end
336
+ end