instana 1.197.0.pre1 → 1.199.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/instana/backend/agent.rb +9 -1
  3. data/lib/instana/backend/host_agent.rb +27 -10
  4. data/lib/instana/backend/host_agent_activation_observer.rb +17 -7
  5. data/lib/instana/backend/request_client.rb +6 -16
  6. data/lib/instana/backend/serverless_agent.rb +13 -18
  7. data/lib/instana/base.rb +2 -0
  8. data/lib/instana/config.rb +1 -1
  9. data/lib/instana/instrumentation/excon.rb +7 -5
  10. data/lib/instana/instrumentation/instrumented_request.rb +62 -7
  11. data/lib/instana/instrumentation/net-http.rb +7 -5
  12. data/lib/instana/instrumentation/rack.rb +12 -7
  13. data/lib/instana/serverless.rb +139 -0
  14. data/lib/instana/setup.rb +5 -0
  15. data/lib/instana/snapshot/google_cloud_run_instance.rb +69 -0
  16. data/lib/instana/snapshot/google_cloud_run_process.rb +58 -0
  17. data/lib/instana/snapshot/lambda_function.rb +39 -0
  18. data/lib/instana/tracing/processor.rb +11 -1
  19. data/lib/instana/tracing/span.rb +10 -4
  20. data/lib/instana/tracing/span_context.rb +14 -9
  21. data/lib/instana/util.rb +4 -2
  22. data/lib/instana/version.rb +1 -1
  23. data/test/backend/agent_test.rb +26 -0
  24. data/test/backend/host_agent_activation_observer_test.rb +16 -9
  25. data/test/backend/host_agent_test.rb +17 -2
  26. data/test/backend/request_client_test.rb +0 -22
  27. data/test/instrumentation/rack_instrumented_request_test.rb +2 -0
  28. data/test/serverless_test.rb +323 -0
  29. data/test/snapshot/google_cloud_run_instance_test.rb +74 -0
  30. data/test/snapshot/google_cloud_run_process_test.rb +33 -0
  31. data/test/snapshot/lambda_function_test.rb +37 -0
  32. data/test/test_helper.rb +1 -1
  33. data/test/tracing/id_management_test.rb +4 -0
  34. data/test/tracing/span_context_test.rb +3 -3
  35. data/test/tracing/span_test.rb +9 -0
  36. metadata +16 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 58219b994af0387dcc4c672577b19c9570e487998be17d315a92902d6e1d86e6
4
- data.tar.gz: 60e586b644f60545ebe6c7690d3ba673354d2133937b3b2bf95db04fbe8f58c4
3
+ metadata.gz: 86a3e023781d52d3b5efd25c9b5d304520dd3c88aecde954c326bd736abae787
4
+ data.tar.gz: b6c7bf11431925b9a48b827b5adbb52d93cb1f1f44bde1ebef98bd89f2188cb0
5
5
  SHA512:
6
- metadata.gz: 4d4ca8c71a1613c05e5f1fec02e5857c544013f0043d4b95e31fc900b81903ba5f3c08abd075afc4643e7761cd54a7595939ad9149575589b44b8e43722ea89a
7
- data.tar.gz: 737447ad9f8ed3c6aef72bead3e66128b83ac1d346bc35f00ad1bd3e0b485f4cd8080faabf8817da23a788f229d32db1739e98484637fd8e70b852b415447d97
6
+ metadata.gz: e2045faa632b6edc00e4dba26feb69d5249c755698a0dac2adeaa0e2b1cbe65f7f37682f7b59a1d3d000f8908dbee85c4f0356da333f02e874361b5861cfcc3f
7
+ data.tar.gz: e1b3983ace1be9653732b033902c7f59a74dce020ebfb585e02cf3dfdd1c2e7eb50df4cff9ff4eb8467df3a57b3208165efe659d97073bd6bc97f9e2bdd62fa0
@@ -15,7 +15,15 @@ module Instana
15
15
  end
16
16
 
17
17
  def setup
18
- @delegate = if @fargate_metadata_uri && ENV.key?('INSTANA_ENDPOINT_URL')
18
+ @delegate = if ENV.key?('_HANDLER')
19
+ ServerlessAgent.new([Snapshot::LambdaFunction.new])
20
+ elsif ENV.key?('K_REVISION') && ENV.key?('INSTANA_ENDPOINT_URL')
21
+ ServerlessAgent.new([
22
+ Snapshot::GoogleCloudRunProcess.new,
23
+ Snapshot::GoogleCloudRunInstance.new,
24
+ Snapshot::RubyProcess.new
25
+ ])
26
+ elsif @fargate_metadata_uri && ENV.key?('INSTANA_ENDPOINT_URL')
19
27
  ServerlessAgent.new(fargate_snapshots)
20
28
  else
21
29
  HostAgent.new
@@ -5,22 +5,29 @@ module Instana
5
5
  module Backend
6
6
  # @since 1.197.0
7
7
  class HostAgent
8
- def initialize(discovery: Concurrent::Atom.new(nil))
8
+ attr_reader :future
9
+
10
+ def initialize(discovery: Concurrent::Atom.new(nil), logger: ::Instana.logger)
9
11
  @discovery = discovery
10
- @client = nil
12
+ @logger = logger
13
+ @future = nil
11
14
  end
12
15
 
13
- def setup
16
+ def setup; end
17
+
18
+ def spawn_background_thread
14
19
  return if ENV.key?('INSTANA_TEST')
15
20
 
16
- @client = HostAgentLookup.new.call
17
- @discovery
18
- .with_observer(HostAgentActivationObserver.new(@client, @discovery))
19
- .with_observer(HostAgentReportingObserver.new(@client, @discovery))
20
- end
21
+ @future = Concurrent::Promises.future do
22
+ client = until_not_nil { HostAgentLookup.new.call }
23
+ @discovery.delete_observers
24
+ @discovery
25
+ .with_observer(HostAgentActivationObserver.new(client, @discovery))
26
+ .with_observer(HostAgentReportingObserver.new(client, @discovery))
21
27
 
22
- def spawn_background_thread
23
- @discovery.swap { nil }
28
+ @discovery.swap { nil }
29
+ client
30
+ end
24
31
  end
25
32
 
26
33
  # @return [Boolean] true if the agent able to send spans to the backend
@@ -48,6 +55,16 @@ module Instana
48
55
 
49
56
  private
50
57
 
58
+ def until_not_nil
59
+ loop do
60
+ result = yield
61
+ return result unless result.nil?
62
+
63
+ @logger.debug("Waiting on a connection to the agent.")
64
+ sleep(1)
65
+ end
66
+ end
67
+
51
68
  def discovery_value
52
69
  v = @discovery.value
53
70
  v || {}
@@ -12,20 +12,23 @@ module Instana
12
12
 
13
13
  # @param [RequestClient] client used to make requests to the backend
14
14
  # @param [Concurrent::Atom] discovery object used to store discovery response in
15
- def initialize(client, discovery, wait_time: 60, logger: ::Instana.logger, max_wait_tries: 60, proc_table: Sys::ProcTable) # rubocop:disable Metrics/ParameterLists
15
+ def initialize(client, discovery, wait_time: 1, logger: ::Instana.logger, max_wait_tries: 60, proc_table: Sys::ProcTable, socket_proc: default_socket_proc) # rubocop:disable Metrics/ParameterLists
16
16
  @client = client
17
17
  @discovery = discovery
18
18
  @wait_time = wait_time
19
19
  @logger = logger
20
20
  @max_wait_tries = max_wait_tries
21
21
  @proc_table = proc_table
22
+ @socket_proc = socket_proc
22
23
  end
23
24
 
24
25
  def update(_time, _old_version, new_version)
25
26
  return unless new_version.nil?
26
27
 
28
+ socket = @socket_proc.call(@client)
29
+
27
30
  try_forever_with_backoff do
28
- payload = discovery_payload
31
+ payload = discovery_payload(socket)
29
32
  discovery_response = @client.send_request('PUT', DISCOVERY_URL, payload)
30
33
 
31
34
  raise DiscoveryError, "Discovery response was #{discovery_response.code} with `#{payload}`." unless discovery_response.ok?
@@ -36,11 +39,13 @@ module Instana
36
39
  @logger.debug("Agent ready.")
37
40
  @discovery.swap { discovery }
38
41
  end
42
+
43
+ socket.close
39
44
  end
40
45
 
41
46
  private
42
47
 
43
- def discovery_payload
48
+ def discovery_payload(socket)
44
49
  proc_table = @proc_table.ps(pid: Process.pid)
45
50
  process = ProcessInfo.new(proc_table)
46
51
 
@@ -52,9 +57,10 @@ module Instana
52
57
  cpuSetFileContent: process.cpuset
53
58
  }
54
59
 
55
- if @client.fileno && @client.inode
56
- payload[:fd] = @client.fileno
57
- payload[:inode] = @client.inode
60
+ inode_path = "/proc/self/fd/#{socket.fileno}"
61
+ if socket.fileno && File.exist?(inode_path)
62
+ payload[:fd] = socket.fileno
63
+ payload[:inode] = File.readlink(inode_path)
58
64
  end
59
65
 
60
66
  payload.compact
@@ -76,12 +82,16 @@ module Instana
76
82
  def try_forever_with_backoff
77
83
  yield
78
84
  rescue DiscoveryError, Net::OpenTimeout => e
79
- @logger.error(e)
85
+ @logger.warn(e)
80
86
  sleep(@wait_time)
81
87
  retry
82
88
  rescue StandardError => e
83
89
  @logger.error(%(#{e}\n#{e.backtrace.join("\n")}))
84
90
  end
91
+
92
+ def default_socket_proc
93
+ ->(c) { TCPSocket.new(c.host, c.port) }
94
+ end
85
95
  end
86
96
  end
87
97
  end
@@ -18,6 +18,8 @@ module Instana
18
18
  # Convince wrapper around {Net::HTTP}.
19
19
  # @since 1.197.0
20
20
  class RequestClient
21
+ attr_reader :host, :port
22
+
21
23
  class Response < SimpleDelegator
22
24
  # @return [Hash] the decoded json response
23
25
  def json
@@ -31,7 +33,10 @@ module Instana
31
33
  end
32
34
 
33
35
  def initialize(host, port, use_ssl: false)
34
- @client = Net::HTTP.start(host, port, use_ssl: use_ssl)
36
+ timeout = Integer(ENV.fetch('INSTANA_TIMEOUT', 500))
37
+ @host = host
38
+ @port = port
39
+ @client = Net::HTTP.start(host, port, use_ssl: use_ssl, read_timeout: timeout)
35
40
  end
36
41
 
37
42
  # Send a request to the backend. If data is a {Hash},
@@ -57,21 +62,6 @@ module Instana
57
62
  Response.new(response)
58
63
  end
59
64
 
60
- # @return [Integer, NilClass] the fileno of the Net::HTTP socket or nil if it can't be identified
61
- def fileno
62
- socket = @client.instance_variable_get('@socket')
63
- io = socket && socket.instance_variable_get('@io')
64
- io && io.fileno
65
- end
66
-
67
- # @return [String] the inode asscoated with the Net::HTTP socket or nil if it can't be identified
68
- def inode
69
- path = "/proc/self/fd/#{fileno}"
70
- return unless File.exist?(path) && fileno
71
-
72
- File.readlink(path)
73
- end
74
-
75
65
  private
76
66
 
77
67
  def encode_body(data)
@@ -39,12 +39,10 @@ module Instana
39
39
 
40
40
  # @return [Hash, NilClass] the backend friendly description of the current in process collector
41
41
  def source
42
- return @source if @source
43
-
44
42
  snapshot = @snapshots.detect { |s| s.respond_to?(:source) }
45
43
 
46
44
  if snapshot
47
- @source = snapshot.source
45
+ snapshot.source
48
46
  else
49
47
  @logger.warn('Unable to find a snapshot which provides a source.')
50
48
  {}
@@ -58,21 +56,10 @@ module Instana
58
56
 
59
57
  # @return [Hash] values which are removed from urls sent to the backend
60
58
  def secret_values
61
- # TODO: Parse from env
62
59
  matcher, *keys = @secrets.split(/[:,]/)
63
60
  {'matcher' => matcher, 'list' => keys}
64
61
  end
65
62
 
66
- private
67
-
68
- def request_headers
69
- {
70
- 'X-Instana-Host' => host_name,
71
- 'X-Instana-Key' => ENV['INSTANA_AGENT_KEY'],
72
- 'X-Instana-Time' => (Time.now.to_i * 1000).to_s
73
- }
74
- end
75
-
76
63
  def send_bundle
77
64
  spans = @processor.queued_spans
78
65
  bundle = {
@@ -90,9 +77,19 @@ module Instana
90
77
  @logger.warn("Recived a `#{response.code}` when sending data.")
91
78
  end
92
79
 
80
+ private
81
+
82
+ def request_headers
83
+ {
84
+ 'X-Instana-Host' => host_name,
85
+ 'X-Instana-Key' => ENV['INSTANA_AGENT_KEY'],
86
+ 'X-Instana-Time' => (Time.now.to_i * 1000).to_s
87
+ }
88
+ end
89
+
93
90
  def agent_snapshots
94
91
  @snapshots.map do |snapshot|
95
- begin
92
+ begin # rubocop:disable Style/RedundantBegin, Lint/RedundantCopDisableDirective
96
93
  snapshot.snapshot
97
94
  rescue StandardError => e
98
95
  @logger.error(e.message)
@@ -102,12 +99,10 @@ module Instana
102
99
  end
103
100
 
104
101
  def host_name
105
- return @host_name if @host_name
106
-
107
102
  snapshot = @snapshots.detect { |s| s.respond_to?(:host_name) }
108
103
 
109
104
  if snapshot
110
- @host_name = snapshot.host_name
105
+ snapshot.host_name
111
106
  else
112
107
  @logger.warn('Unable to find a snapshot which provides a host_name.')
113
108
  ''
data/lib/instana/base.rb CHANGED
@@ -13,6 +13,7 @@ module Instana
13
13
  attr_accessor :config
14
14
  attr_accessor :pid
15
15
  attr_reader :secrets
16
+ attr_reader :serverless
16
17
 
17
18
  ##
18
19
  # setup
@@ -25,6 +26,7 @@ module Instana
25
26
  @tracer = ::Instana::Tracer.new
26
27
  @processor = ::Instana::Processor.new
27
28
  @secrets = ::Instana::Secrets.new
29
+ @serverless = ::Instana::Serverless.new
28
30
  end
29
31
 
30
32
  def logger
@@ -51,7 +51,7 @@ module Instana
51
51
  @config[:sanitize_sql] = true
52
52
 
53
53
  # W3 Trace Context Support
54
- @config[:w3_trace_correlation] = ENV.fetch('INSTANA_W3C_TRACE_CORRELATION', 'true').eql?('true')
54
+ @config[:w3_trace_correlation] = ENV['INSTANA_DISABLE_W3C_TRACE_CORRELATION'].nil?
55
55
 
56
56
  @config[:action_controller] = { :enabled => true }
57
57
  @config[:action_view] = { :enabled => true }
@@ -24,14 +24,16 @@ module Instana
24
24
  end
25
25
 
26
26
  # Set request headers; encode IDs as hexadecimal strings
27
- datum[:headers]['X-Instana-T'] = t_context.trace_id_header
28
- datum[:headers]['X-Instana-S'] = t_context.span_id_header
27
+ datum[:headers]['X-Instana-L'] = t_context.level.to_s
29
28
 
30
- if ::Instana.config[:w3_trace_correlation]
31
- datum[:headers]['Traceparent'] = t_context.trace_parent_header
32
- datum[:headers]['Tracestate'] = t_context.trace_state_header
29
+ if t_context.active?
30
+ datum[:headers]['X-Instana-T'] = t_context.trace_id_header
31
+ datum[:headers]['X-Instana-S'] = t_context.span_id_header
33
32
  end
34
33
 
34
+ datum[:headers]['Traceparent'] = t_context.trace_parent_header
35
+ datum[:headers]['Tracestate'] = t_context.trace_state_header unless t_context.trace_state_header.empty?
36
+
35
37
  @stack.request_call(datum)
36
38
  end
37
39
 
@@ -18,16 +18,33 @@ module Instana
18
18
  end
19
19
 
20
20
  def incoming_context
21
- context = if @env['HTTP_X_INSTANA_T']
21
+ context = if !correlation_data.empty?
22
+ {}
23
+ elsif @env['HTTP_X_INSTANA_T']
22
24
  context_from_instana_headers
23
- elsif @env['HTTP_TRACEPARENT'] && ::Instana.config[:w3_trace_correlation]
25
+ elsif @env['HTTP_TRACEPARENT']
24
26
  context_from_trace_parent
27
+ elsif @env['HTTP_TRACESTATE']
28
+ context_from_trace_state
25
29
  else
26
30
  {}
27
31
  end
28
32
 
29
33
  context[:level] = @env['HTTP_X_INSTANA_L'][0] if @env['HTTP_X_INSTANA_L']
30
34
 
35
+ unless ::Instana.config[:w3_trace_correlation]
36
+ trace_state = parse_trace_state
37
+
38
+ if context[:from_w3] && trace_state.empty?
39
+ context.delete(:span_id)
40
+ context[:from_w3] = false
41
+ elsif context[:from_w3] && !trace_state.empty?
42
+ context[:trace_id] = trace_state[:t]
43
+ context[:span_id] = trace_state[:p]
44
+ context[:from_w3] = false
45
+ end
46
+ end
47
+
31
48
  context
32
49
  end
33
50
 
@@ -69,19 +86,43 @@ module Instana
69
86
  end
70
87
 
71
88
  def continuing_from_trace_parent?
72
- incoming_context.include?(:external_trace_id)
89
+ incoming_context[:from_w3]
73
90
  end
74
91
 
75
92
  def synthetic?
76
93
  @env.has_key?('HTTP_X_INSTANA_SYNTHETIC') && @env['HTTP_X_INSTANA_SYNTHETIC'].eql?('1')
77
94
  end
78
95
 
96
+ def long_instana_id?
97
+ ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_T']).length == 32
98
+ end
99
+
100
+ def external_trace_id?
101
+ continuing_from_trace_parent? || long_instana_id?
102
+ end
103
+
104
+ def external_trace_id
105
+ incoming_context[:long_instana_id] || incoming_context[:external_trace_id]
106
+ end
107
+
79
108
  private
80
109
 
81
110
  def context_from_instana_headers
111
+ sanitized_t = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_T'])
112
+ sanitized_s = ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_S'])
113
+ external_trace_id = if @env['HTTP_TRACEPARENT']
114
+ context_from_trace_parent[:external_trace_id]
115
+ elsif long_instana_id?
116
+ sanitized_t
117
+ end
118
+
82
119
  {
83
- trace_id: ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_T']),
84
- span_id: ::Instana::Util.header_to_id(@env['HTTP_X_INSTANA_S'])
120
+ span_id: sanitized_s,
121
+ trace_id: long_instana_id? ? sanitized_t[16..-1] : sanitized_t, # rubocop:disable Style/SlicingWithRange, Lint/RedundantCopDisableDirective
122
+ long_instana_id: long_instana_id? ? sanitized_t : nil,
123
+ external_trace_id: external_trace_id,
124
+ external_state: @env['HTTP_TRACESTATE'],
125
+ from_w3: false
85
126
  }.compact
86
127
  end
87
128
 
@@ -90,14 +131,28 @@ module Instana
90
131
  matches = @env['HTTP_TRACEPARENT'].match(W3_TRACE_PARENT_FORMAT)
91
132
  return {} unless matches
92
133
 
134
+ trace_id = ::Instana::Util.header_to_id(matches['trace'][16..-1]) # rubocop:disable Style/SlicingWithRange, Lint/RedundantCopDisableDirective
135
+ span_id = ::Instana::Util.header_to_id(matches['parent'])
136
+
93
137
  {
94
138
  external_trace_id: matches['trace'],
95
139
  external_state: @env['HTTP_TRACESTATE'],
96
- trace_id: ::Instana::Util.header_to_id(matches['trace'][16..-1]), # rubocop:disable Style/SlicingWithRange, Lint/RedundantCopDisableDirective
97
- span_id: ::Instana::Util.header_to_id(matches['parent'])
140
+ trace_id: trace_id,
141
+ span_id: span_id,
142
+ from_w3: true
98
143
  }
99
144
  end
100
145
 
146
+ def context_from_trace_state
147
+ state = parse_trace_state
148
+
149
+ {
150
+ trace_id: state[:t],
151
+ span_id: state[:p],
152
+ from_w3: false
153
+ }.compact
154
+ end
155
+
101
156
  def parse_trace_state
102
157
  return {} unless @env.has_key?('HTTP_TRACESTATE')
103
158
  token = @env['HTTP_TRACESTATE']
@@ -19,14 +19,16 @@ module Instana
19
19
 
20
20
  # Set request headers; encode IDs as hexadecimal strings
21
21
  t_context = ::Instana.tracer.context
22
- request['X-Instana-T'] = t_context.trace_id_header
23
- request['X-Instana-S'] = t_context.span_id_header
22
+ request['X-Instana-L'] = t_context.level.to_s
24
23
 
25
- if ::Instana.config[:w3_trace_correlation]
26
- request['Traceparent'] = t_context.trace_parent_header
27
- request['Tracestate'] = t_context.trace_state_header
24
+ if t_context.active?
25
+ request['X-Instana-T'] = t_context.trace_id_header
26
+ request['X-Instana-S'] = t_context.span_id_header
28
27
  end
29
28
 
29
+ request['Traceparent'] = t_context.trace_parent_header
30
+ request['Tracestate'] = t_context.trace_state_header unless t_context.trace_state_header.empty?
31
+
30
32
  # Collect up KV info now in case any exception is raised
31
33
  kv_payload = { :http => {} }
32
34
  kv_payload[:http][:method] = request.method