instana 1.197.0.pre1 → 1.199.0

Sign up to get free protection for your applications and to get access to all the features.
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