instana 1.10.1-java

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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +23 -0
  3. data/.gitignore +16 -0
  4. data/.rubocop.yml +1156 -0
  5. data/.travis.yml +43 -0
  6. data/Configuration.md +149 -0
  7. data/Dockerfile +13 -0
  8. data/Gemfile +41 -0
  9. data/LICENSE +21 -0
  10. data/README.md +102 -0
  11. data/Rakefile +56 -0
  12. data/Tracing.md +145 -0
  13. data/Troubleshooting.md +32 -0
  14. data/benchmarks/Gemfile +7 -0
  15. data/benchmarks/id_generation.rb +12 -0
  16. data/benchmarks/opentracing.rb +26 -0
  17. data/benchmarks/rack_vanilla_vs_traced.rb +80 -0
  18. data/benchmarks/stackprof_rack_tracing.rb +77 -0
  19. data/benchmarks/time_processing.rb +12 -0
  20. data/bin/console +7 -0
  21. data/bin/setup +8 -0
  22. data/examples/opentracing.rb +31 -0
  23. data/examples/tracing.rb +80 -0
  24. data/gemfiles/libraries.gemfile +71 -0
  25. data/gemfiles/rails32.gemfile +51 -0
  26. data/gemfiles/rails42.gemfile +50 -0
  27. data/gemfiles/rails50.gemfile +52 -0
  28. data/instana.gemspec +46 -0
  29. data/lib/instana.rb +12 -0
  30. data/lib/instana/agent.rb +441 -0
  31. data/lib/instana/agent/helpers.rb +61 -0
  32. data/lib/instana/agent/hooks.rb +37 -0
  33. data/lib/instana/agent/tasks.rb +48 -0
  34. data/lib/instana/base.rb +54 -0
  35. data/lib/instana/collector.rb +116 -0
  36. data/lib/instana/collectors/gc.rb +57 -0
  37. data/lib/instana/collectors/memory.rb +34 -0
  38. data/lib/instana/collectors/thread.rb +30 -0
  39. data/lib/instana/config.rb +79 -0
  40. data/lib/instana/eum/eum-test.js.erb +16 -0
  41. data/lib/instana/eum/eum.js.erb +14 -0
  42. data/lib/instana/frameworks/cuba.rb +6 -0
  43. data/lib/instana/frameworks/instrumentation/abstract_mysql_adapter.rb +58 -0
  44. data/lib/instana/frameworks/instrumentation/action_controller.rb +183 -0
  45. data/lib/instana/frameworks/instrumentation/action_view.rb +43 -0
  46. data/lib/instana/frameworks/instrumentation/active_record.rb +27 -0
  47. data/lib/instana/frameworks/instrumentation/mysql2_adapter.rb +81 -0
  48. data/lib/instana/frameworks/instrumentation/mysql_adapter.rb +56 -0
  49. data/lib/instana/frameworks/instrumentation/postgresql_adapter.rb +71 -0
  50. data/lib/instana/frameworks/rails.rb +42 -0
  51. data/lib/instana/frameworks/roda.rb +6 -0
  52. data/lib/instana/frameworks/sinatra.rb +9 -0
  53. data/lib/instana/helpers.rb +40 -0
  54. data/lib/instana/instrumentation.rb +21 -0
  55. data/lib/instana/instrumentation/dalli.rb +78 -0
  56. data/lib/instana/instrumentation/excon.rb +74 -0
  57. data/lib/instana/instrumentation/grpc.rb +84 -0
  58. data/lib/instana/instrumentation/net-http.rb +66 -0
  59. data/lib/instana/instrumentation/rack.rb +77 -0
  60. data/lib/instana/instrumentation/redis.rb +82 -0
  61. data/lib/instana/instrumentation/resque.rb +131 -0
  62. data/lib/instana/instrumentation/rest-client.rb +34 -0
  63. data/lib/instana/instrumentation/sidekiq-client.rb +45 -0
  64. data/lib/instana/instrumentation/sidekiq-worker.rb +54 -0
  65. data/lib/instana/opentracing/carrier.rb +4 -0
  66. data/lib/instana/opentracing/tracer.rb +18 -0
  67. data/lib/instana/rack.rb +10 -0
  68. data/lib/instana/setup.rb +36 -0
  69. data/lib/instana/test.rb +40 -0
  70. data/lib/instana/thread_local.rb +15 -0
  71. data/lib/instana/tracer.rb +392 -0
  72. data/lib/instana/tracing/processor.rb +92 -0
  73. data/lib/instana/tracing/span.rb +401 -0
  74. data/lib/instana/tracing/span_context.rb +33 -0
  75. data/lib/instana/util.rb +261 -0
  76. data/lib/instana/version.rb +4 -0
  77. data/lib/oj_check.rb +16 -0
  78. data/lib/opentracing.rb +6 -0
  79. data/test/agent/agent_test.rb +143 -0
  80. data/test/apps/cuba.rb +15 -0
  81. data/test/apps/grpc_server.rb +81 -0
  82. data/test/apps/roda.rb +10 -0
  83. data/test/apps/sinatra.rb +5 -0
  84. data/test/benchmarks/bench_id_generation.rb +12 -0
  85. data/test/benchmarks/bench_opentracing.rb +13 -0
  86. data/test/config_test.rb +37 -0
  87. data/test/frameworks/cuba_test.rb +44 -0
  88. data/test/frameworks/rack_test.rb +167 -0
  89. data/test/frameworks/rails/actioncontroller_test.rb +93 -0
  90. data/test/frameworks/rails/actionview3_test.rb +255 -0
  91. data/test/frameworks/rails/actionview4_test.rb +254 -0
  92. data/test/frameworks/rails/actionview5_test.rb +221 -0
  93. data/test/frameworks/rails/activerecord3_test.rb +134 -0
  94. data/test/frameworks/rails/activerecord4_test.rb +134 -0
  95. data/test/frameworks/rails/activerecord5_test.rb +87 -0
  96. data/test/frameworks/roda_test.rb +44 -0
  97. data/test/frameworks/sinatra_test.rb +44 -0
  98. data/test/instana_test.rb +27 -0
  99. data/test/instrumentation/dalli_test.rb +253 -0
  100. data/test/instrumentation/excon_test.rb +147 -0
  101. data/test/instrumentation/grpc_test.rb +377 -0
  102. data/test/instrumentation/net-http_test.rb +160 -0
  103. data/test/instrumentation/redis_test.rb +119 -0
  104. data/test/instrumentation/resque_test.rb +128 -0
  105. data/test/instrumentation/rest-client_test.rb +55 -0
  106. data/test/instrumentation/sidekiq-client_test.rb +125 -0
  107. data/test/instrumentation/sidekiq-worker_test.rb +173 -0
  108. data/test/jobs/resque_error_job.rb +22 -0
  109. data/test/jobs/resque_fast_job.rb +20 -0
  110. data/test/jobs/sidekiq_job_1.rb +6 -0
  111. data/test/jobs/sidekiq_job_2.rb +7 -0
  112. data/test/models/block.rb +18 -0
  113. data/test/servers/grpc_50051.rb +20 -0
  114. data/test/servers/helpers/sidekiq_worker_initializer.rb +27 -0
  115. data/test/servers/rackapp_6511.rb +25 -0
  116. data/test/servers/rails_3205.rb +167 -0
  117. data/test/servers/sidekiq/worker.rb +27 -0
  118. data/test/test_helper.rb +145 -0
  119. data/test/tracing/custom_test.rb +158 -0
  120. data/test/tracing/id_management_test.rb +130 -0
  121. data/test/tracing/opentracing_test.rb +335 -0
  122. data/test/tracing/trace_test.rb +67 -0
  123. data/test/tracing/tracer_async_test.rb +198 -0
  124. data/test/tracing/tracer_test.rb +223 -0
  125. metadata +327 -0
@@ -0,0 +1,51 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'rake'
5
+ gem 'minitest', '5.9.1'
6
+ gem 'minitest-reporters'
7
+ gem 'minitest-debugger', :require => false
8
+ gem 'rack-test'
9
+ gem 'webmock'
10
+ gem 'puma'
11
+ # public_suffix dropped support for Ruby 2.1 and earlier.
12
+ gem 'public_suffix', '< 3.0'
13
+ end
14
+
15
+ group :development do
16
+ gem 'ruby-debug', :platforms => [:mri_18, :jruby]
17
+ gem 'debugger', :platform => :mri_19
18
+
19
+ if RUBY_VERSION > '1.8.7'
20
+ gem 'pry'
21
+
22
+ if RUBY_VERSION < '2.2'
23
+ gem 'byebug', '< 9.1.0'
24
+ gem 'pry-byebug'
25
+ else
26
+ gem 'pry-byebug'
27
+ end
28
+ else
29
+ gem 'pry', '0.9.12.4'
30
+ end
31
+ end
32
+
33
+ gem 'rails', '3.2.22.5'
34
+ gem 'pg'
35
+ gem 'mysql2', '~> 0.3.10'
36
+
37
+ # Gems used only for assets and not required
38
+ # in production environments by default.
39
+ group :assets do
40
+ gem 'sass-rails', '~> 3.2.3'
41
+ gem 'coffee-rails', '~> 3.2.1'
42
+ gem 'therubyracer', :platforms => :ruby
43
+ gem 'uglifier', '>= 1.0.3'
44
+ end
45
+
46
+ gem 'jquery-rails'
47
+
48
+ # Include the Instana Ruby gem's base set of gems
49
+ gemspec :path => File.expand_path(File.dirname(__FILE__) + '/../')
50
+
51
+ # vim:syntax=ruby
@@ -0,0 +1,50 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'rake'
5
+ gem 'minitest', '5.9.1'
6
+ gem 'minitest-reporters'
7
+ gem 'minitest-debugger', :require => false
8
+ gem 'rack-test'
9
+ gem 'webmock'
10
+ gem 'puma'
11
+ # public_suffix dropped support for Ruby 2.1 and earlier.
12
+ gem 'public_suffix', '< 3.0'
13
+ end
14
+
15
+ group :development do
16
+ gem 'ruby-debug', :platforms => [:mri_18, :jruby]
17
+ gem 'debugger', :platform => :mri_19
18
+
19
+ if RUBY_VERSION > '1.8.7'
20
+ gem 'pry'
21
+
22
+ if RUBY_VERSION < '2.2'
23
+ gem 'byebug', '< 9.1.0'
24
+ gem 'pry-byebug'
25
+ else
26
+ gem 'pry-byebug'
27
+ end
28
+ else
29
+ gem 'pry', '0.9.12.4'
30
+ end
31
+ end
32
+
33
+ gem 'rails', '4.2.9'
34
+ gem 'sqlite3'
35
+ gem 'sass-rails', '~> 5.0'
36
+ gem 'uglifier', '>= 1.3.0'
37
+ gem 'coffee-rails', '~> 4.1.0'
38
+ gem 'therubyracer', platforms: :ruby
39
+
40
+ gem 'jquery-rails'
41
+ gem 'turbolinks'
42
+ gem 'jbuilder', '~> 2.0'
43
+ gem 'sdoc', '~> 0.4.0', group: :doc
44
+ gem "pg"
45
+ gem "mysql2", '0.4.10'
46
+
47
+ # Include the Instana Ruby gem's base set of gems
48
+ gemspec :path => File.expand_path(File.dirname(__FILE__) + '/../')
49
+
50
+ # vim:syntax=ruby
@@ -0,0 +1,52 @@
1
+ source 'https://rubygems.org'
2
+
3
+ group :development, :test do
4
+ gem 'rake'
5
+ gem 'minitest', '5.9.1'
6
+ gem 'minitest-reporters'
7
+ gem 'minitest-debugger', :require => false
8
+ gem 'rack-test'
9
+ gem 'webmock'
10
+ gem 'puma'
11
+ # public_suffix dropped support for Ruby 2.1 and earlier.
12
+ gem 'public_suffix', '< 3.0'
13
+ end
14
+
15
+ group :development do
16
+ gem 'ruby-debug', :platforms => [:mri_18, :jruby]
17
+ gem 'debugger', :platform => :mri_19
18
+
19
+ if RUBY_VERSION > '1.8.7'
20
+ gem 'pry'
21
+
22
+ if RUBY_VERSION < '2.2'
23
+ gem 'byebug', '< 9.1.0'
24
+ gem 'pry-byebug'
25
+ else
26
+ gem 'pry-byebug'
27
+ end
28
+ else
29
+ gem 'pry', '0.9.12.4'
30
+ end
31
+ end
32
+
33
+ gem 'rails', '~> 5.0.0', '>= 5.0.0.1'
34
+ gem 'sass-rails', '~> 5.0'
35
+ gem 'uglifier', '>= 1.3.0'
36
+ gem 'coffee-rails', '~> 4.2'
37
+ gem 'therubyracer', platforms: :ruby
38
+
39
+ gem 'jquery-rails'
40
+ gem 'turbolinks', '~> 5'
41
+ gem 'jbuilder', '~> 2.5'
42
+
43
+ gem 'pg'
44
+ gem 'mysql2', '0.4.10'
45
+
46
+ # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
47
+ gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
48
+
49
+ # Include the Instana Ruby gem's base set of gems
50
+ gemspec :path => File.expand_path(File.dirname(__FILE__) + '/../')
51
+
52
+ # vim:syntax=ruby
data/instana.gemspec ADDED
@@ -0,0 +1,46 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'instana/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "instana"
8
+ spec.version = Instana::VERSION
9
+ spec.authors = ["Peter Giacomo Lombardo"]
10
+ spec.email = ["pglombardo@gmail.com"]
11
+
12
+ spec.summary = %q{Ruby Distributed Tracing & Metrics Sensor for Instana}
13
+ spec.description = %q{The Instana gem collects and reports Ruby metrics and distibuted traces to your Instana dashboard.}
14
+ spec.homepage = "https://www.instana.com/"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+ spec.test_files = Dir.glob("{test}/**/*.rb")
21
+
22
+ spec.required_ruby_version = '>= 2.1'
23
+ spec.platform = defined?(JRUBY_VERSION) ? 'java' : Gem::Platform::RUBY
24
+
25
+ spec.licenses = ['MIT']
26
+
27
+ spec.add_development_dependency "bundler", "~> 2.0"
28
+ spec.add_development_dependency "rake", "~> 10.0"
29
+ spec.add_development_dependency "minitest", "~> 5.0"
30
+ spec.add_development_dependency('pry', '>= 0.10.0')
31
+
32
+ # Development debugging
33
+ unless RUBY_PLATFORM =~ /java/i
34
+ spec.add_development_dependency('byebug', '>= 8.0.0')
35
+ spec.add_development_dependency('pry-byebug', '>= 3.0.0')
36
+ end
37
+
38
+ spec.add_runtime_dependency('sys-proctable', '>= 1.2.2')
39
+ spec.add_runtime_dependency('get_process_mem', '>= 0.2.1')
40
+ spec.add_runtime_dependency('timers', '>= 4.0.0')
41
+ spec.add_runtime_dependency('oj', '>=3.0.11') unless RUBY_PLATFORM =~ /java/i
42
+
43
+ # Indirect dependency
44
+ # https://github.com/instana/ruby-sensor/issues/10
45
+ spec.add_runtime_dependency('ffi', '>=1.0.11')
46
+ end
data/lib/instana.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "instana/setup"
2
+
3
+ # Boot the instana agent background thread. If you wish to have greater
4
+ # control on the where and which thread this is run in, instead use
5
+ #
6
+ # gem "instana", :require => "instana/setup"
7
+ #
8
+ # ...and override ::Instana::Agent.spawn_background_thread to boot
9
+ # the thread of your choice.
10
+ #
11
+
12
+ ::Instana.agent.spawn_background_thread
@@ -0,0 +1,441 @@
1
+ require 'net/http'
2
+ require 'socket'
3
+ require 'sys/proctable'
4
+ require 'timers'
5
+ require 'uri'
6
+ require 'thread'
7
+
8
+ require 'instana/agent/helpers'
9
+ require 'instana/agent/hooks'
10
+ require 'instana/agent/tasks'
11
+
12
+ include Sys
13
+
14
+ module Instana
15
+ OJ_OPTIONS = {:mode => :strict}
16
+
17
+ class Agent
18
+ include AgentHelpers
19
+ include AgentHooks
20
+ include AgentTasks
21
+
22
+ attr_accessor :state
23
+ attr_accessor :agent_uuid
24
+ attr_accessor :process
25
+ attr_accessor :collect_thread
26
+ attr_accessor :thread_spawn_lock
27
+ attr_accessor :extra_headers
28
+
29
+ LOCALHOST = '127.0.0.1'.freeze
30
+ MIME_JSON = 'application/json'.freeze
31
+ DISCOVERY_PATH = 'com.instana.plugin.ruby.discovery'.freeze
32
+
33
+ def initialize
34
+ # Supported two states (unannounced & announced)
35
+ @state = :unannounced
36
+
37
+ # Timestamp of the last successful response from
38
+ # entity data reporting.
39
+ @entity_last_seen = Time.now
40
+
41
+ # Used to track the last time the collect timer was run.
42
+ @last_collect_run = Time.now
43
+
44
+ # Two timers, one for each state (unannounced & announced)
45
+ @timers = ::Timers::Group.new
46
+ @announce_timer = nil
47
+ @collect_timer = nil
48
+
49
+ @thread_spawn_lock = Mutex.new
50
+
51
+ # Detect platform flags
52
+ @is_linux = (RbConfig::CONFIG['host_os'] =~ /linux/i) ? true : false
53
+ @is_osx = (RUBY_PLATFORM =~ /darwin/i) ? true : false
54
+
55
+ # In case we're running in Docker, have the default gateway available
56
+ # to check in case we're running in bridged network mode
57
+ if @is_linux && File.exist?("/sbin/ip")
58
+ @default_gateway = `/sbin/ip route | awk '/default/ { print $3 }'`.chomp
59
+ else
60
+ @default_gateway = nil
61
+ end
62
+
63
+ # Re-useable HTTP client for communication with
64
+ # the host agent.
65
+ @httpclient = nil
66
+
67
+ # Collect initial process info - repeat prior to announce
68
+ # in `announce_sensor` in case of process rename, after fork etc.
69
+ @process = ::Instana::Util.collect_process_info
70
+
71
+ # The agent UUID returned from the host agent
72
+ @agent_uuid = nil
73
+
74
+ # This will hold info on the discovered agent host
75
+ @discovered = nil
76
+
77
+ # The agent may pass down custom headers for this sensor to capture
78
+ @extra_headers = nil
79
+ end
80
+
81
+ # Spawns the background thread and calls start. This method is separated
82
+ # out for those who wish to control which thread the background agent will
83
+ # run in.
84
+ #
85
+ # This method can be overridden with the following:
86
+ #
87
+ # module Instana
88
+ # class Agent
89
+ # def spawn_background_thread
90
+ # # start thread
91
+ # start
92
+ # end
93
+ # end
94
+ # end
95
+ #
96
+ def spawn_background_thread
97
+ @thread_spawn_lock.synchronize {
98
+ if @collect_thread && @collect_thread.alive?
99
+ ::Instana.logger.info "[instana] Collect thread already started & alive. Not spawning another."
100
+ else
101
+ @collect_thread = Thread.new do
102
+ start
103
+ end
104
+ end
105
+ }
106
+ end
107
+
108
+ # Sets up periodic timers and starts the agent in a background thread.
109
+ #
110
+ def setup
111
+ # The announce timer
112
+ # We attempt to announce this ruby sensor to the host agent.
113
+ # In case of failure, we try again in 30 seconds.
114
+ @announce_timer = @timers.every(30) do
115
+ if @state == :unannounced
116
+ if host_agent_available? && announce_sensor
117
+ transition_to(:announced)
118
+ ::Instana.logger.info "Host agent available. We're in business. (#{@state} pid:#{Process.pid} #{@process[:name]})"
119
+ end
120
+ end
121
+ end
122
+
123
+ # The collect timer
124
+ # If we are in announced state, send metric data (only delta reporting)
125
+ # every ::Instana.config[:collector][:interval] seconds.
126
+ @collect_timer = @timers.every(::Instana.config[:collector][:interval]) do
127
+ # Make sure that this block doesn't get called more often than the interval. This can
128
+ # happen on high CPU load and a back up of timer runs. If we are called before `interval`
129
+ # then we just skip.
130
+ unless (Time.now - @last_collect_run) < ::Instana.config[:collector][:interval]
131
+ @last_collect_run = Time.now
132
+ if @state == :announced
133
+ if !::Instana.collector.collect_and_report
134
+ # If report has been failing for more than 1 minute,
135
+ # fall back to unannounced state
136
+ if (Time.now - @entity_last_seen) > 60
137
+ ::Instana.logger.warn "Host agent offline for >1 min. Going to sit in a corner..."
138
+ transition_to(:unannounced)
139
+ end
140
+ end
141
+ ::Instana.processor.send
142
+ end
143
+ end
144
+ end
145
+ end
146
+
147
+ # Starts the timer loop for the timers that were initialized
148
+ # in the setup method. This is blocking and should only be
149
+ # called from an already initialized background thread.
150
+ #
151
+ def start
152
+ if !host_agent_available?
153
+ if !ENV.key?("INSTANA_QUIET")
154
+ ::Instana.logger.warn "Host agent not available. Will retry periodically. (Set env INSTANA_QUIET=1 to shut these messages off)"
155
+ end
156
+ end
157
+
158
+ while true
159
+ if @state == :unannounced
160
+ @collect_timer.pause
161
+ @announce_timer.resume
162
+ else
163
+ @announce_timer.pause
164
+ @collect_timer.resume
165
+ end
166
+ @timers.wait
167
+ end
168
+ rescue Exception => e
169
+ ::Instana.logger.warn { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" }
170
+ ::Instana.logger.debug { e.backtrace.join("\r\n") }
171
+ ensure
172
+ if @state == :announced
173
+ # Pause the timers so they don't fire while we are
174
+ # reporting traces
175
+ @collect_timer.pause
176
+ @announce_timer.pause
177
+
178
+ ::Instana.logger.debug { "#{Thread.current}: Agent exiting. Reporting final spans (if any)." }
179
+ ::Instana.processor.send
180
+ end
181
+ end
182
+
183
+ # Collect process ID, name and arguments to notify
184
+ # the host agent.
185
+ #
186
+ def announce_sensor
187
+ unless @discovered
188
+ ::Instana.logger.debug("#{__method__} called but discovery hasn't run yet!")
189
+ return false
190
+ end
191
+
192
+ # Always re-collect process info before announce in case the process name has been
193
+ # re-written (looking at you puma!)
194
+ @process = ::Instana::Util.collect_process_info
195
+
196
+ announce_payload = {}
197
+ announce_payload[:pid] = pid_namespace? ? get_real_pid : Process.pid
198
+ announce_payload[:name] = @process[:name]
199
+ announce_payload[:args] = @process[:arguments]
200
+
201
+ if @is_linux && !ENV.key?('INSTANA_TEST')
202
+ # We create an open socket to the host agent in case we are running in a container
203
+ # and the real pid needs to be detected.
204
+ socket = TCPSocket.new @discovered[:agent_host], @discovered[:agent_port]
205
+ announce_payload[:fd] = socket.fileno
206
+ announce_payload[:inode] = File.readlink("/proc/#{Process.pid}/fd/#{socket.fileno}")
207
+ end
208
+
209
+ uri = URI.parse("http://#{@discovered[:agent_host]}:#{@discovered[:agent_port]}/#{DISCOVERY_PATH}")
210
+ req = Net::HTTP::Put.new(uri)
211
+ req.body = Oj.dump(announce_payload, OJ_OPTIONS)
212
+
213
+ ::Instana.logger.debug { "Announce: http://#{@discovered[:agent_host]}:#{@discovered[:agent_port]}/#{DISCOVERY_PATH} - payload: #{req.body}" }
214
+
215
+ response = make_host_agent_request(req)
216
+
217
+ if response && (response.code.to_i == 200)
218
+ data = Oj.load(response.body, OJ_OPTIONS)
219
+ @process[:report_pid] = data['pid']
220
+ @agent_uuid = data['agentUuid']
221
+
222
+ if data.key?('extraHeaders')
223
+ @extra_headers = data['extraHeaders']
224
+ end
225
+ true
226
+ else
227
+ false
228
+ end
229
+ rescue => e
230
+ Instana.logger.info { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" }
231
+ Instana.logger.debug { e.backtrace.join("\r\n") }
232
+ return false
233
+ ensure
234
+ socket.close if socket && !socket.closed?
235
+ end
236
+
237
+ # Method to report metrics data to the host agent.
238
+ #
239
+ # @param paylod [Hash] The collection of metrics to report.
240
+ #
241
+ # @return [Boolean] true on success, false otherwise
242
+ #
243
+ def report_metrics(payload)
244
+ unless @discovered
245
+ ::Instana.logger.debug("#{__method__} called but discovery hasn't run yet!")
246
+ return false
247
+ end
248
+
249
+ path = "com.instana.plugin.ruby.#{@process[:report_pid]}"
250
+ uri = URI.parse("http://#{@discovered[:agent_host]}:#{@discovered[:agent_port]}/#{path}")
251
+ req = Net::HTTP::Post.new(uri)
252
+
253
+ req.body = Oj.dump(payload, OJ_OPTIONS)
254
+ response = make_host_agent_request(req)
255
+
256
+ if response
257
+ if response.body && response.body.length > 2
258
+ # The host agent returned something indicating that is has a request for us that we
259
+ # need to process.
260
+ handle_agent_tasks(response.body)
261
+ end
262
+
263
+ if response.code.to_i == 200
264
+ @entity_last_seen = Time.now
265
+ return true
266
+ end
267
+
268
+ end
269
+ false
270
+ rescue => e
271
+ Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" }
272
+ Instana.logger.debug { e.backtrace.join("\r\n") }
273
+ end
274
+
275
+ # Accept and report spans to the host agent.
276
+ #
277
+ # @param traces [Array] An array of [Span]
278
+ # @return [Boolean]
279
+ #
280
+ def report_spans(spans)
281
+ return unless @state == :announced
282
+
283
+ unless @discovered
284
+ ::Instana.logger.debug("#{__method__} called but discovery hasn't run yet!")
285
+ return false
286
+ end
287
+
288
+ path = "com.instana.plugin.ruby/traces.#{@process[:report_pid]}"
289
+ uri = URI.parse("http://#{@discovered[:agent_host]}:#{@discovered[:agent_port]}/#{path}")
290
+ req = Net::HTTP::Post.new(uri)
291
+
292
+ opts = OJ_OPTIONS.merge({omit_nil: true})
293
+
294
+ req.body = Oj.dump(spans, opts)
295
+ response = make_host_agent_request(req)
296
+
297
+ if response
298
+ last_trace_response = response.code.to_i
299
+
300
+ if [200, 204].include?(last_trace_response)
301
+ return true
302
+ end
303
+ end
304
+ false
305
+ rescue => e
306
+ Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" }
307
+ Instana.logger.debug { e.backtrace.join("\r\n") }
308
+ end
309
+
310
+ # Check that the host agent is available and can be contacted. This will
311
+ # first check localhost and if not, then attempt on the default gateway
312
+ # for docker in bridged mode.
313
+ #
314
+ def host_agent_available?
315
+ @discovered ||= run_discovery
316
+
317
+ if @discovered
318
+ # Try default location or manually configured (if so)
319
+ uri = URI.parse("http://#{@discovered[:agent_host]}:#{@discovered[:agent_port]}/")
320
+ req = Net::HTTP::Get.new(uri)
321
+
322
+ response = make_host_agent_request(req)
323
+
324
+ if response && (response.code.to_i == 200)
325
+ return true
326
+ end
327
+ end
328
+ false
329
+ rescue => e
330
+ Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" }
331
+ Instana.logger.debug { e.backtrace.join("\r\n") } unless ENV.key?('INSTANA_TEST')
332
+ return false
333
+ end
334
+
335
+ # Runs a discovery process to determine where we can contact the host agent. This is usually just
336
+ # localhost but in docker can be found on the default gateway. Another option is the INSTANA_AGENT_HOST
337
+ # environment variable. This also allows for manual configuration via ::Instana.config[:agent_host/port].
338
+ #
339
+ # @return [Hash] a hash with :agent_host, :agent_port values or empty hash
340
+ #
341
+ def run_discovery
342
+ discovered = {}
343
+
344
+ ::Instana.logger.debug { "#{__method__}: Running agent discovery..." }
345
+
346
+ # Try default location or manually configured (if so)
347
+ uri = URI.parse("http://#{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}/")
348
+ req = Net::HTTP::Get.new(uri)
349
+
350
+ ::Instana.logger.debug { "#{__method__}: Trying #{::Instana.config[:agent_host]}:#{::Instana.config[:agent_port]}" }
351
+
352
+ response = make_host_agent_request(req)
353
+
354
+ if response && (response.code.to_i == 200)
355
+ discovered[:agent_host] = ::Instana.config[:agent_host]
356
+ discovered[:agent_port] = ::Instana.config[:agent_port]
357
+ ::Instana.logger.debug { "#{__method__}: Found #{discovered[:agent_host]}:#{discovered[:agent_port]}" }
358
+ return discovered
359
+ end
360
+
361
+ return nil unless @is_linux
362
+
363
+ # We are potentially running on Docker in bridged networking mode.
364
+ # Attempt to contact default gateway
365
+ uri = URI.parse("http://#{@default_gateway}:#{::Instana.config[:agent_port]}/")
366
+ req = Net::HTTP::Get.new(uri)
367
+
368
+ ::Instana.logger.debug { "#{__method__}: Trying default gateway #{@default_gateway}:#{::Instana.config[:agent_port]}" }
369
+
370
+ response = make_host_agent_request(req)
371
+
372
+ if response && (response.code.to_i == 200)
373
+ discovered[:agent_host] = @default_gateway
374
+ discovered[:agent_port] = ::Instana.config[:agent_port]
375
+ ::Instana.logger.debug { "#{__method__}: Found #{discovered[:agent_host]}:#{discovered[:agent_port]}" }
376
+ return discovered
377
+ end
378
+
379
+ nil
380
+ end
381
+
382
+ private
383
+
384
+ # Handles any/all steps required in the transtion
385
+ # between states.
386
+ #
387
+ # @param state [Symbol] Can be 1 of 2 possible states:
388
+ # `:announced`, `:unannounced`
389
+ #
390
+ def transition_to(state)
391
+ ::Instana.logger.debug("Transitioning to #{state}")
392
+ case state
393
+ when :announced
394
+ # announce successful; set state
395
+ @state = :announced
396
+
397
+ # Reset the entity timer
398
+ @entity_last_seen = Time.now
399
+
400
+ when :unannounced
401
+ @state = :unannounced
402
+ # Reset our HTTP client
403
+ @httpclient = nil
404
+
405
+ else
406
+ ::Instana.logger.debug "Uknown agent state: #{state}"
407
+ end
408
+ ::Instana.collector.reset_timer!
409
+ true
410
+ end
411
+
412
+ # Centralization of the net/http communications
413
+ # with the host agent. Pass in a prepared <req>
414
+ # of type Net::HTTP::Get|Put|Head
415
+ #
416
+ # @param req [Net::HTTP::Req] A prepared Net::HTTP request object of the type
417
+ # you wish to make (Get, Put, Post etc.)
418
+ #
419
+ def make_host_agent_request(req)
420
+ req['Accept'] = MIME_JSON
421
+ req['Content-Type'] = MIME_JSON
422
+
423
+ if @state == :unannounced
424
+ @httpclient = Net::HTTP.new(req.uri.hostname, req.uri.port)
425
+ @httpclient.open_timeout = 1
426
+ @httpclient.read_timeout = 1
427
+ end
428
+
429
+ response = @httpclient.request(req)
430
+ # ::Instana.logger.debug "#{req.method}->#{req.uri} body:(#{req.body}) Response:#{response} body:(#{response.body})"
431
+
432
+ response
433
+ rescue Errno::ECONNREFUSED
434
+ return nil
435
+ rescue => e
436
+ Instana.logger.debug { "#{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" }
437
+ Instana.logger.debug { e.backtrace.join("\r\n") } unless ENV.key?('INSTANA_TEST')
438
+ return nil
439
+ end
440
+ end
441
+ end