instana 1.195.4 → 1.197.0.pre1

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -2
  3. data/Rakefile +1 -1
  4. data/instana.gemspec +3 -7
  5. data/lib/instana.rb +3 -0
  6. data/lib/instana/activator.rb +2 -0
  7. data/lib/instana/backend/agent.rb +60 -0
  8. data/lib/instana/backend/gc_snapshot.rb +41 -0
  9. data/lib/instana/backend/host_agent.rb +57 -0
  10. data/lib/instana/backend/host_agent_activation_observer.rb +87 -0
  11. data/lib/instana/backend/host_agent_lookup.rb +57 -0
  12. data/lib/instana/backend/host_agent_reporting_observer.rb +106 -0
  13. data/lib/instana/backend/process_info.rb +64 -0
  14. data/lib/instana/backend/request_client.rb +84 -0
  15. data/lib/instana/backend/serverless_agent.rb +118 -0
  16. data/lib/instana/base.rb +8 -27
  17. data/lib/instana/config.rb +7 -21
  18. data/lib/instana/logger_delegator.rb +31 -0
  19. data/lib/instana/{opentracing → open_tracing}/carrier.rb +0 -0
  20. data/lib/instana/open_tracing/instana_tracer.rb +99 -0
  21. data/lib/instana/secrets.rb +6 -2
  22. data/lib/instana/setup.rb +20 -11
  23. data/lib/instana/snapshot/deltable.rb +25 -0
  24. data/lib/instana/snapshot/docker_container.rb +151 -0
  25. data/lib/instana/snapshot/fargate_container.rb +88 -0
  26. data/lib/instana/snapshot/fargate_process.rb +67 -0
  27. data/lib/instana/snapshot/fargate_task.rb +72 -0
  28. data/lib/instana/snapshot/ruby_process.rb +48 -0
  29. data/lib/instana/tracer.rb +25 -143
  30. data/lib/instana/tracing/processor.rb +4 -22
  31. data/lib/instana/tracing/span.rb +26 -35
  32. data/lib/instana/tracing/span_context.rb +1 -1
  33. data/lib/instana/util.rb +4 -67
  34. data/lib/instana/version.rb +1 -1
  35. data/lib/opentracing.rb +26 -3
  36. data/test/backend/agent_test.rb +54 -0
  37. data/test/backend/gc_snapshot_test.rb +11 -0
  38. data/test/backend/host_agent_activation_observer_test.rb +65 -0
  39. data/test/backend/host_agent_lookup_test.rb +78 -0
  40. data/test/backend/host_agent_reporting_observer_test.rb +192 -0
  41. data/test/backend/host_agent_test.rb +32 -0
  42. data/test/backend/process_info_test.rb +63 -0
  43. data/test/backend/request_client_test.rb +61 -0
  44. data/test/backend/serverless_agent_test.rb +73 -0
  45. data/test/config_test.rb +10 -0
  46. data/test/instana_test.rb +11 -4
  47. data/test/instrumentation/rack_instrumented_request_test.rb +3 -2
  48. data/test/instrumentation/rack_test.rb +2 -14
  49. data/test/secrets_test.rb +41 -22
  50. data/test/snapshot/deltable_test.rb +17 -0
  51. data/test/snapshot/docker_container_test.rb +82 -0
  52. data/test/snapshot/fargate_container_test.rb +82 -0
  53. data/test/snapshot/fargate_process_test.rb +35 -0
  54. data/test/snapshot/fargate_task_test.rb +49 -0
  55. data/test/snapshot/ruby_process_test.rb +14 -0
  56. data/test/support/mock_timer.rb +20 -0
  57. data/test/test_helper.rb +15 -3
  58. data/test/tracing/custom_test.rb +1 -3
  59. data/test/tracing/id_management_test.rb +4 -0
  60. data/test/tracing/opentracing_test.rb +15 -2
  61. data/test/tracing/processor_test.rb +58 -0
  62. data/test/tracing/span_context_test.rb +22 -0
  63. data/test/tracing/span_test.rb +127 -0
  64. data/test/tracing/tracer_async_test.rb +29 -0
  65. data/test/tracing/tracer_test.rb +82 -16
  66. data/test/util_test.rb +10 -0
  67. metadata +72 -45
  68. data/lib/instana/agent.rb +0 -508
  69. data/lib/instana/agent/helpers.rb +0 -87
  70. data/lib/instana/agent/hooks.rb +0 -44
  71. data/lib/instana/agent/tasks.rb +0 -51
  72. data/lib/instana/collector.rb +0 -119
  73. data/lib/instana/collectors/gc.rb +0 -60
  74. data/lib/instana/collectors/memory.rb +0 -37
  75. data/lib/instana/collectors/thread.rb +0 -33
  76. data/lib/instana/eum/eum-test.js.erb +0 -17
  77. data/lib/instana/eum/eum.js.erb +0 -17
  78. data/lib/instana/helpers.rb +0 -47
  79. data/lib/instana/opentracing/tracer.rb +0 -21
  80. data/lib/instana/thread_local.rb +0 -18
  81. data/lib/oj_check.rb +0 -19
  82. data/test/agent/agent_test.rb +0 -151
@@ -0,0 +1,64 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Backend
6
+ # Wrapper around {Sys::ProcTable} that adds support for reading the /proc
7
+ # file system for extra information around containers
8
+ # @since 1.197.0
9
+ class ProcessInfo < SimpleDelegator
10
+ def name
11
+ cmdline
12
+ .split(' ').first
13
+ end
14
+
15
+ def arguments
16
+ _, *arguments = cmdline.split(' ')
17
+ clean_arguments(arguments)
18
+ end
19
+
20
+ def parent_pid
21
+ if in_container? && sched_pid != pid
22
+ sched_pid
23
+ else
24
+ pid
25
+ end
26
+ end
27
+
28
+ def from_parent_namespace
29
+ !in_container? || in_container? && sched_pid != pid
30
+ end
31
+
32
+ def cpuset
33
+ path = "/proc/#{pid}/cpuset"
34
+ return unless File.exist?(path)
35
+
36
+ File.read(path).strip
37
+ end
38
+
39
+ def in_container?
40
+ !cpuset.nil? && cpuset != '/'
41
+ end
42
+
43
+ def sched_pid
44
+ path = '/proc/self/sched'
45
+ return unless File.exist?(path)
46
+
47
+ File.read(path).match(/\d+/).to_s.to_i
48
+ end
49
+
50
+ private
51
+
52
+ def clean_arguments(arguments)
53
+ return arguments unless RbConfig::CONFIG['host_os'].include?('darwin')
54
+
55
+ arguments.reject do |a|
56
+ if a.include?('=')
57
+ k, = a.split('=', 2)
58
+ ENV[k]
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,84 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ require 'net/http'
5
+ require 'delegate'
6
+ require 'json'
7
+
8
+ # :nocov:
9
+ begin
10
+ require 'oj'
11
+ rescue LoadError => _e
12
+ Instana.logger.warn("Unable to load Oj.")
13
+ end
14
+ # :nocov:
15
+
16
+ module Instana
17
+ module Backend
18
+ # Convince wrapper around {Net::HTTP}.
19
+ # @since 1.197.0
20
+ class RequestClient
21
+ class Response < SimpleDelegator
22
+ # @return [Hash] the decoded json response
23
+ def json
24
+ JSON.parse(body)
25
+ end
26
+
27
+ # @return [Boolean] true if the request was successful
28
+ def ok?
29
+ __getobj__.is_a?(Net::HTTPSuccess)
30
+ end
31
+ end
32
+
33
+ def initialize(host, port, use_ssl: false)
34
+ @client = Net::HTTP.start(host, port, use_ssl: use_ssl)
35
+ end
36
+
37
+ # Send a request to the backend. If data is a {Hash},
38
+ # encode the object as JSON and set the proper headers.
39
+ #
40
+ # @param [String] method request method
41
+ # @param [String] path request path
42
+ # @param [Hash, String] data request body
43
+ # @param [Hash] headers extra request headers to send
44
+ def send_request(method, path, data = nil, headers = {})
45
+ body = if data.is_a?(Hash) || data.is_a?(Array)
46
+ headers['Content-Type'] = 'application/json'
47
+ headers['Accept'] = 'application/json'
48
+
49
+ encode_body(data)
50
+ else
51
+ headers['Content-Type'] = 'application/octet-stream'
52
+
53
+ data
54
+ end
55
+
56
+ response = @client.send_request(method, path, body, headers)
57
+ Response.new(response)
58
+ end
59
+
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
+ private
76
+
77
+ def encode_body(data)
78
+ # :nocov:
79
+ defined?(Oj) ? Oj.dump(data, mode: :strict) : JSON.dump(data)
80
+ # :nocov:
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,118 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ module Backend
6
+ # @since 1.197.0
7
+ class ServerlessAgent
8
+ DEFAULT_SECRETS = 'contains-ignore-case:key,password,secret'.freeze
9
+ attr_reader :timer
10
+
11
+ # rubocop:disable Metrics/ParameterLists
12
+ def initialize(snapshots,
13
+ timer_class: Concurrent::TimerTask,
14
+ processor: ::Instana.processor,
15
+ logger: ::Instana.logger,
16
+ backend_uri: ENV['INSTANA_ENDPOINT_URL'],
17
+ secrets: ENV.fetch('INSTANA_SECRETS', DEFAULT_SECRETS), headers: ENV.fetch('INSTANA_EXTRA_HTTP_HEADERS', ''))
18
+ @snapshots = snapshots
19
+ @processor = processor
20
+ @logger = logger
21
+ @timer = timer_class.new(execution_interval: 1, run_now: true) { send_bundle }
22
+ @backend_uri = URI(backend_uri)
23
+ @client = Backend::RequestClient.new(@backend_uri.host, @backend_uri.port, use_ssl: @backend_uri.scheme == "https")
24
+ @secrets = secrets
25
+ @headers = headers
26
+ end
27
+ # rubocop:enable Metrics/ParameterLists
28
+
29
+ def setup; end
30
+
31
+ def spawn_background_thread
32
+ @timer.execute
33
+ end
34
+
35
+ # @return [Boolean] true if the agent able to send spans to the backend
36
+ def ready?
37
+ true
38
+ end
39
+
40
+ # @return [Hash, NilClass] the backend friendly description of the current in process collector
41
+ def source
42
+ return @source if @source
43
+
44
+ snapshot = @snapshots.detect { |s| s.respond_to?(:source) }
45
+
46
+ if snapshot
47
+ @source = snapshot.source
48
+ else
49
+ @logger.warn('Unable to find a snapshot which provides a source.')
50
+ {}
51
+ end
52
+ end
53
+
54
+ # @return [Array] extra headers to include in the trace
55
+ def extra_headers
56
+ @headers.split(';')
57
+ end
58
+
59
+ # @return [Hash] values which are removed from urls sent to the backend
60
+ def secret_values
61
+ # TODO: Parse from env
62
+ matcher, *keys = @secrets.split(/[:,]/)
63
+ {'matcher' => matcher, 'list' => keys}
64
+ end
65
+
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
+ def send_bundle
77
+ spans = @processor.queued_spans
78
+ bundle = {
79
+ spans: spans,
80
+ metrics: {
81
+ plugins: agent_snapshots
82
+ }
83
+ }
84
+
85
+ path = "#{@backend_uri.path}/bundle"
86
+ response = @client.send_request('POST', path, bundle, request_headers)
87
+
88
+ return if response.ok?
89
+
90
+ @logger.warn("Recived a `#{response.code}` when sending data.")
91
+ end
92
+
93
+ def agent_snapshots
94
+ @snapshots.map do |snapshot|
95
+ begin
96
+ snapshot.snapshot
97
+ rescue StandardError => e
98
+ @logger.error(e.message)
99
+ nil
100
+ end
101
+ end.compact
102
+ end
103
+
104
+ def host_name
105
+ return @host_name if @host_name
106
+
107
+ snapshot = @snapshots.detect { |s| s.respond_to?(:host_name) }
108
+
109
+ if snapshot
110
+ @host_name = snapshot.host_name
111
+ else
112
+ @logger.warn('Unable to find a snapshot which provides a host_name.')
113
+ ''
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
data/lib/instana/base.rb CHANGED
@@ -4,16 +4,13 @@
4
4
  require "logger"
5
5
  require "instana/version"
6
6
  require "instana/util"
7
- require "instana/helpers"
8
7
 
9
8
  module Instana
10
9
  class << self
11
10
  attr_accessor :agent
12
- attr_accessor :collector
13
11
  attr_accessor :tracer
14
12
  attr_accessor :processor
15
13
  attr_accessor :config
16
- attr_accessor :logger
17
14
  attr_accessor :pid
18
15
  attr_reader :secrets
19
16
 
@@ -24,36 +21,20 @@ module Instana
24
21
  # to run" state.
25
22
  #
26
23
  def setup
27
- @agent = ::Instana::Agent.new
24
+ @agent = ::Instana::Backend::Agent.new
28
25
  @tracer = ::Instana::Tracer.new
29
26
  @processor = ::Instana::Processor.new
30
- @collector = ::Instana::Collector.new
31
27
  @secrets = ::Instana::Secrets.new
32
28
  end
33
- end
34
- end
35
-
36
- # Setup the logger as early as possible
37
-
38
- # Default Logger outputs to STDOUT
39
- ::Instana.logger = Logger.new(STDOUT)
40
-
41
- # Can instead log to a file that is rotated every 10M
42
- # ::Instana.logger = Logger.new("instana.log", 10, 1073741824)
43
29
 
44
- if ENV.key?('INSTANA_GEM_TEST')
45
- ::Instana.logger.level = Logger::DEBUG
46
- elsif ENV.key?('INSTANA_GEM_DEV') || ENV.key?('INSTANA_DEBUG')
47
- ::Instana.logger.level = Logger::DEBUG
48
- elsif ENV.key?('INSTANA_QUIET')
49
- ::Instana.logger.level = Logger::FATAL
50
- else
51
- ::Instana.logger.level = Logger::WARN
52
- end
30
+ def logger
31
+ @logger ||= ::Instana::LoggerDelegator.new(Logger.new(STDOUT))
32
+ end
53
33
 
54
- ::Instana.logger.formatter = proc do |severity, datetime, progname, msg|
55
- "#{datetime}: #{severity.rjust(5)} Instana: #{progname} #{msg}\n"
34
+ def logger=(val)
35
+ @logger.__setobj__(val)
36
+ end
37
+ end
56
38
  end
57
39
 
58
-
59
40
  ::Instana.logger.info "Stan is on the scene. Starting Instana instrumentation version #{::Instana::VERSION}"
@@ -3,26 +3,21 @@
3
3
 
4
4
  module Instana
5
5
  class Config
6
-
7
- def initialize
6
+ def initialize(logger: ::Instana.logger, agent_host: ENV['INSTANA_AGENT_HOST'], agent_port: ENV['INSTANA_AGENT_PORT'])
8
7
  @config = {}
9
- if ENV.key?('INSTANA_AGENT_HOST')
10
- ::Instana.logger.debug "Using custom agent host location specified in INSTANA_AGENT_HOST (#{ENV['INSTANA_AGENT_HOST']})"
11
- @config[:agent_host] = ENV['INSTANA_AGENT_HOST']
8
+ if agent_host
9
+ logger.debug "Using custom agent host location specified in INSTANA_AGENT_HOST (#{ENV['INSTANA_AGENT_HOST']})"
10
+ @config[:agent_host] = agent_host
12
11
  else
13
12
  @config[:agent_host] = '127.0.0.1'
14
13
  end
15
- if ENV.key?('INSTANA_AGENT_PORT')
16
- ::Instana.logger.debug "Using custom agent port specified in INSTANA_AGENT_PORT (#{ENV['INSTANA_AGENT_PORT']})"
17
- @config[:agent_port] = ENV['INSTANA_AGENT_PORT']
14
+ if agent_port
15
+ logger.debug "Using custom agent port specified in INSTANA_AGENT_PORT (#{ENV['INSTANA_AGENT_PORT']})"
16
+ @config[:agent_port] = agent_port
18
17
  else
19
18
  @config[:agent_port] = 42699
20
19
  end
21
20
 
22
- # This option has been DEPRECATED. Use the INSTANA_DISABLE environment variable instead.
23
- # https://docs.instana.io/ecosystem/ruby/configuration/
24
- @config[:enabled] = true
25
-
26
21
  # Enable/disable metrics globally or individually (default: all enabled)
27
22
  @config[:metrics] = { :enabled => true }
28
23
  @config[:metrics][:gc] = { :enabled => true }
@@ -80,15 +75,6 @@ module Instana
80
75
 
81
76
  def []=(key, value)
82
77
  @config[key.to_sym] = value
83
-
84
- if key == :enabled
85
- # Configuring global enable/disable flag, then set the
86
- # appropriate children flags.
87
- @config[:metrics][:enabled] = value
88
- @config[:tracing][:enabled] = value
89
-
90
- ::Instana.logger.warn "::Instana.config[:enabled] has been deprecated. Set INSTANA_DISABLE environment variable to any value instead."
91
- end
92
78
  end
93
79
  end
94
80
  end
@@ -0,0 +1,31 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ module Instana
5
+ class LoggerDelegator < SimpleDelegator
6
+ def initialize(obj)
7
+ obj.level = level_from_environment
8
+ super(obj)
9
+ end
10
+
11
+ private
12
+
13
+ def level_from_environment
14
+ # :nocov:
15
+ return Logger::FATAL if ENV.key?('INSTANA_TEST') || ENV.key?('RACK_TEST')
16
+ return Logger::DEBUG if ENV.key?('INSTANA_DEBUG')
17
+
18
+ case ENV['INSTANA_LOG_LEVEL']
19
+ when 'debug'
20
+ Logger::DEBUG
21
+ when 'warn'
22
+ Logger::WARN
23
+ when 'error'
24
+ Logger::ERROR
25
+ else
26
+ Logger::INFO
27
+ end
28
+ # :nocov:
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,99 @@
1
+ # (c) Copyright IBM Corp. 2021
2
+ # (c) Copyright Instana Inc. 2021
3
+
4
+ require 'delegate'
5
+ # :nocov:
6
+ module OpenTracing
7
+ class InstanaTracer < SimpleDelegator
8
+ Span = ::Instana::Span
9
+
10
+ # Start a new span
11
+ #
12
+ # @param operation_name [String] The name of the operation represented by the span
13
+ # @param child_of [Span] A span to be used as the ChildOf reference
14
+ # @param start_time [Time] the start time of the span
15
+ # @param tags [Hash] Starting tags for the span
16
+ #
17
+ # @return [Span]
18
+ #
19
+ def start_span(operation_name, child_of: nil, start_time: ::Instana::Util.now_in_ms, tags: nil)
20
+ new_span = if child_of && (child_of.is_a?(::Instana::Span) || child_of.is_a?(::Instana::SpanContext))
21
+ Span.new(operation_name, parent_ctx: child_of, start_time: start_time)
22
+ else
23
+ Span.new(operation_name, start_time: start_time)
24
+ end
25
+ new_span.set_tags(tags) if tags
26
+ new_span
27
+ end
28
+
29
+ # Start a new span which is the child of the current span
30
+ #
31
+ # @param operation_name [String] The name of the operation represented by the span
32
+ # @param child_of [Span] A span to be used as the ChildOf reference
33
+ # @param start_time [Time] the start time of the span
34
+ # @param tags [Hash] Starting tags for the span
35
+ #
36
+ # @return [Span]
37
+ #
38
+ def start_active_span(operation_name, child_of: active_span, start_time: ::Instana::Util.now_in_ms, tags: nil)
39
+ ::Instana.tracer.current_span = start_span(operation_name, child_of: child_of, start_time: start_time, tags: tags)
40
+ block_given? ? yield(::Instana.tracer.current_span) : ::Instana.tracer.current_span
41
+ end
42
+
43
+ # Returns the currently active span
44
+ #
45
+ # @return [Span]
46
+ #
47
+ def active_span
48
+ ::Instana.tracer.current_span
49
+ end
50
+
51
+ # Inject a span into the given carrier
52
+ #
53
+ # @param span_context [SpanContext]
54
+ # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK]
55
+ # @param carrier [Carrier]
56
+ #
57
+ def inject(span_context, format, carrier)
58
+ case format
59
+ when OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY
60
+ ::Instana.logger.debug 'Unsupported inject format'
61
+ when OpenTracing::FORMAT_RACK
62
+ carrier['X-Instana-T'] = ::Instana::Util.id_to_header(span_context.trace_id)
63
+ carrier['X-Instana-S'] = ::Instana::Util.id_to_header(span_context.span_id)
64
+ else
65
+ ::Instana.logger.debug 'Unknown inject format'
66
+ end
67
+ end
68
+
69
+ # Extract a span from a carrier
70
+ #
71
+ # @param format [OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY, OpenTracing::FORMAT_RACK]
72
+ # @param carrier [Carrier]
73
+ #
74
+ # @return [SpanContext]
75
+ #
76
+ def extract(format, carrier)
77
+ case format
78
+ when OpenTracing::FORMAT_TEXT_MAP, OpenTracing::FORMAT_BINARY
79
+ ::Instana.logger.debug 'Unsupported extract format'
80
+ when OpenTracing::FORMAT_RACK
81
+ ::Instana::SpanContext.new(::Instana::Util.header_to_id(carrier['HTTP_X_INSTANA_T']),
82
+ ::Instana::Util.header_to_id(carrier['HTTP_X_INSTANA_S']))
83
+ else
84
+ ::Instana.logger.debug 'Unknown inject format'
85
+ nil
86
+ end
87
+ end
88
+
89
+ def method_missing(method, *args, &block)
90
+ ::Instana.logger.warn { "You are invoking `#{m}` on Instana::Tracer via OpenTracing." }
91
+ super(method, *args, &block)
92
+ end
93
+
94
+ def respond_to_missing?(*)
95
+ super(method)
96
+ end
97
+ end
98
+ end
99
+ # :nocov: