newrelic-infinite_tracing 6.11.0.365

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.
@@ -0,0 +1,171 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ # The connection class manages the channel and connection to the gRPC server.
7
+ #
8
+ # Calls to the gRPC server are blocked until the agent connects to the collector
9
+ # and obtains a license_key and agent_run_token from the server side configuration.
10
+ #
11
+ # If the agent is instructed to reconnect by the collector, that event triggers
12
+ # server_source_configuration_added, which this connection is subscribed to and will
13
+ # also notify the client to restart and re-establish its bi-directional streaming
14
+ # with the gRPC server.
15
+ #
16
+ # NOTE: Connection is implemented as a Singleton and it also only ever expects *one*
17
+ # client instance by design.
18
+ module NewRelic::Agent
19
+ module InfiniteTracing
20
+ class Connection
21
+
22
+
23
+ # listens for server side configurations added to the agent. When a new config is
24
+ # added, we have a new agent run token and need to restart the client's RPC stream
25
+ # with the new metadata information.
26
+ NewRelic::Agent.agent.events.subscribe(:server_source_configuration_added) do
27
+ begin
28
+ Connection.instance.notify_agent_started
29
+ rescue => error
30
+ NewRelic::Agent.logger.error \
31
+ "Error during notify :server_source_configuration_added event",
32
+ error
33
+ end
34
+ end
35
+
36
+ class << self
37
+
38
+ def instance
39
+ @@instance ||= new
40
+ end
41
+
42
+ def reset
43
+ @@instance = new
44
+ end
45
+
46
+ # RPC calls will pass the calling client instance in. We track this
47
+ # so we're able to signal the client to restart when connectivity to the
48
+ # server is disrupted.
49
+ def record_spans client, enumerator, exponential_backoff
50
+ instance.record_spans client, enumerator, exponential_backoff
51
+ end
52
+
53
+ # RPC calls will pass the calling client instance in. We track this
54
+ # so we're able to signal the client to restart when connectivity to the
55
+ # server is disrupted.
56
+ def record_span_batches client, enumerator, exponential_backoff
57
+ instance.record_span_batch client, enumerator, exponential_backoff
58
+ end
59
+
60
+ def metadata
61
+ instance.metadata
62
+ end
63
+ end
64
+
65
+ # We attempt to connect and record spans with reconnection backoff in order to deal with
66
+ # unavailable errors coming from the stub being created and record_span call
67
+ def record_spans client, enumerator, exponential_backoff
68
+ @active_clients[client] = client
69
+ with_reconnection_backoff(exponential_backoff) { rpc.record_span enumerator, metadata: metadata }
70
+ end
71
+
72
+ # RPC calls will pass the calling client instance in. We track this
73
+ # so we're able to signal the client to restart when connectivity to the
74
+ # server is disrupted.
75
+ def record_span_batches client, enumerator, exponential_backoff
76
+ @active_clients[client] = client
77
+ with_reconnection_backoff(exponential_backoff) { rpc.record_span_batch enumerator, metadata: metadata }
78
+ end
79
+
80
+ # Acquires the new channel stub for the RPC calls.
81
+ # We attempt to connect and record spans with reconnection backoff in order to deal with
82
+ # unavailable errors coming from the stub being created and record_span call
83
+ def rpc
84
+ @rpc ||= Channel.new.stub
85
+ end
86
+
87
+ # The metadata for the RPC calls is a blocking call waiting for the Agent to
88
+ # connect and receive the server side configuration, which contains the license_key
89
+ # as well as the agent_id (agent_run_token).
90
+ def metadata
91
+ return @metadata if @metadata
92
+
93
+ @lock.synchronize do
94
+ @agent_started.wait(@lock) if !@agent_connected
95
+
96
+ @metadata = {
97
+ "license_key" => license_key,
98
+ "agent_run_token" => agent_id
99
+ }
100
+ end
101
+ end
102
+
103
+ # Initializes rpc so we can get a Channel and Stub (connect to gRPC server)
104
+ # Initializes metadata so we use newest values in establishing channel
105
+ # Sets the agent_connected flag and signals the agent started so any
106
+ # waiting locks (rpc calls ahead of the agent connecting) can proceed.
107
+ def notify_agent_started
108
+ @lock.synchronize do
109
+ @rpc = nil
110
+ @metadata = nil
111
+ @agent_connected = true
112
+ @agent_started.signal
113
+ end
114
+ @active_clients.each_value(&:restart)
115
+ end
116
+
117
+ private
118
+
119
+ # prepares the connection to wait for the agent to connect and have an
120
+ # agent_run_token ready for metadata on rpc calls.
121
+ def initialize
122
+ @active_clients = {}
123
+ @rpc = nil
124
+ @metadata = nil
125
+ @connection_attempts = 0
126
+ @agent_connected = NewRelic::Agent.agent.connected?
127
+ @agent_started = ConditionVariable.new
128
+ @lock = Mutex.new
129
+ end
130
+
131
+ # The agent run token, which is only available after a server source configuration has
132
+ # been added to the agent's config stack.
133
+ def agent_id
134
+ NewRelic::Agent.agent.service.agent_id.to_s
135
+ end
136
+
137
+ def license_key
138
+ NewRelic::Agent.config[:license_key]
139
+ end
140
+
141
+ # Continues retrying the connection at backoff intervals until a successful connection is made
142
+ def with_reconnection_backoff exponential_backoff=true, &block
143
+ @connection_attempts = 0
144
+ begin
145
+ yield
146
+ rescue => exception
147
+ retry_connection_period = retry_connection_period(exponential_backoff)
148
+ ::NewRelic::Agent.logger.error "Error establishing connection with infinite tracing service:", exception
149
+ ::NewRelic::Agent.logger.info "Will re-attempt infinte tracing connection in #{retry_connection_period} seconds"
150
+ sleep retry_connection_period
151
+ note_connect_failure
152
+ retry
153
+ end
154
+ end
155
+
156
+ def retry_connection_period exponential_backoff=true
157
+ if exponential_backoff
158
+ NewRelic::CONNECT_RETRY_PERIODS[@connection_attempts] || NewRelic::MAX_RETRY_PERIOD
159
+ else
160
+ NewRelic::MIN_RETRY_PERIOD
161
+ end
162
+ end
163
+
164
+ # broken out to help for testing
165
+ def note_connect_failure
166
+ @connection_attempts += 1
167
+ end
168
+
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,20 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ module NewRelic::Agent
7
+ module InfiniteTracing
8
+ module Constants
9
+ SUPPORTABILITY_PREFIX = "Supportability/InfiniteTracing/Span"
10
+
11
+ SPANS_SEEN_METRIC = "#{SUPPORTABILITY_PREFIX}/Seen"
12
+ SPANS_SENT_METRIC = "#{SUPPORTABILITY_PREFIX}/Sent"
13
+ QUEUE_DUMPED_METRIC = "#{SUPPORTABILITY_PREFIX}/AgentQueueDumped"
14
+ RESPONSE_ERROR_METRIC = "#{SUPPORTABILITY_PREFIX}/Response/Error"
15
+
16
+ GRPC_ERROR_NAME_METRIC = "#{SUPPORTABILITY_PREFIX}/gRPC/%s"
17
+ GRPC_OTHER_ERROR_METRIC = GRPC_ERROR_NAME_METRIC % "Other"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ require 'grpc'
7
+ require 'google/protobuf'
8
+
9
+ require_relative 'proto/infinite_tracing_pb'
10
+ require_relative 'proto/infinite_tracing_services_pb'
11
+
12
+ # Mapping gRPC namespaced classes into New Relic's
13
+ module NewRelic::Agent::InfiniteTracing
14
+ Span = Com::Newrelic::Trace::V1::Span
15
+ SpanBatch = Com::Newrelic::Trace::V1::SpanBatch
16
+ AttributeValue = Com::Newrelic::Trace::V1::AttributeValue
17
+ RecordStatus = Com::Newrelic::Trace::V1::RecordStatus
18
+ end
@@ -0,0 +1,44 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
7
+ # source: infinite_tracing.proto
8
+
9
+
10
+ Google::Protobuf::DescriptorPool.generated_pool.build do
11
+ add_message "com.newrelic.trace.v1.SpanBatch" do
12
+ repeated :spans, :message, 1, "com.newrelic.trace.v1.Span"
13
+ end
14
+ add_message "com.newrelic.trace.v1.Span" do
15
+ optional :trace_id, :string, 1
16
+ map :intrinsics, :string, :message, 2, "com.newrelic.trace.v1.AttributeValue"
17
+ map :user_attributes, :string, :message, 3, "com.newrelic.trace.v1.AttributeValue"
18
+ map :agent_attributes, :string, :message, 4, "com.newrelic.trace.v1.AttributeValue"
19
+ end
20
+ add_message "com.newrelic.trace.v1.AttributeValue" do
21
+ oneof :value do
22
+ optional :string_value, :string, 1
23
+ optional :bool_value, :bool, 2
24
+ optional :int_value, :int64, 3
25
+ optional :double_value, :double, 4
26
+ end
27
+ end
28
+ add_message "com.newrelic.trace.v1.RecordStatus" do
29
+ optional :messages_seen, :uint64, 1
30
+ end
31
+ end
32
+
33
+ module Com
34
+ module Newrelic
35
+ module Trace
36
+ module V1
37
+ SpanBatch = Google::Protobuf::DescriptorPool.generated_pool.lookup("com.newrelic.trace.v1.SpanBatch").msgclass
38
+ Span = Google::Protobuf::DescriptorPool.generated_pool.lookup("com.newrelic.trace.v1.Span").msgclass
39
+ AttributeValue = Google::Protobuf::DescriptorPool.generated_pool.lookup("com.newrelic.trace.v1.AttributeValue").msgclass
40
+ RecordStatus = Google::Protobuf::DescriptorPool.generated_pool.lookup("com.newrelic.trace.v1.RecordStatus").msgclass
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
7
+ # Source: infinite_tracing.proto for package 'com.newrelic.trace.v1'
8
+ # Original file comments:
9
+ # encoding: utf-8
10
+ # This file is distributed under New Relic's license terms.
11
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
12
+ #
13
+
14
+
15
+ module Com
16
+ module Newrelic
17
+ module Trace
18
+ module V1
19
+ module IngestService
20
+ class Service
21
+
22
+ include GRPC::GenericService
23
+
24
+ self.marshal_class_method = :encode
25
+ self.unmarshal_class_method = :decode
26
+ self.service_name = 'com.newrelic.trace.v1.IngestService'
27
+
28
+ # Accepts a stream of Span messages, and returns an irregular stream of
29
+ # RecordStatus messages.
30
+ rpc :RecordSpan, stream(Span), stream(RecordStatus)
31
+ # Accepts a stream of SpanBatch messages, and returns an irregular
32
+ # stream of RecordStatus messages. This endpoint can be used to improve
33
+ # throughput when Span messages are small
34
+ rpc :RecordSpanBatch, stream(SpanBatch), stream(RecordStatus)
35
+ end
36
+
37
+ Stub = Service.rpc_stub_class
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,49 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ module NewRelic::Agent
7
+ module InfiniteTracing
8
+ class RecordStatusHandler
9
+ def initialize client, enumerator
10
+ @client = client
11
+ @enumerator = enumerator
12
+ @messages_seen = nil
13
+ @lock = Mutex.new
14
+ @lock.synchronize { @worker = start_handler }
15
+ end
16
+
17
+ def messages_seen
18
+ @messages_seen ? @messages_seen.messages_seen : 0
19
+ end
20
+
21
+ def start_handler
22
+ Worker.new self.class.name do
23
+ begin
24
+ @enumerator.each do |response|
25
+ break if response.nil? || response.is_a?(Exception)
26
+ @lock.synchronize do
27
+ @messages_seen = response
28
+ NewRelic::Agent.logger.debug "gRPC Infinite Tracer Observer saw #{messages_seen} messages"
29
+ end
30
+ end
31
+ rescue => error
32
+ @lock.synchronize { @client.handle_error error }
33
+ end
34
+ end
35
+ rescue => error
36
+ NewRelic::Agent.logger.error "gRPC Worker Error", error
37
+ end
38
+
39
+ def stop
40
+ return if @worker.nil?
41
+ @lock.synchronize do
42
+ NewRelic::Agent.logger.debug "gRPC Stopping Response Handler"
43
+ @worker.stop
44
+ @worker = nil
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,162 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+ # frozen_string_literal: true
5
+
6
+ # The StreamingBuffer class provides an Enumerator to the standard Ruby Queue
7
+ # class. The enumerator is blocking while the queue is empty.
8
+ module NewRelic::Agent
9
+ module InfiniteTracing
10
+
11
+ BATCH_SIZE = 100
12
+
13
+ class StreamingBuffer
14
+ include Constants
15
+ include Enumerable
16
+ extend Forwardable
17
+ def_delegators :@queue, :empty?, :num_waiting, :push
18
+
19
+ DEFAULT_QUEUE_SIZE = 10_000
20
+ FLUSH_DELAY = 0.005
21
+ MAX_FLUSH_WAIT = 3 # three seconds
22
+
23
+ attr_reader :queue
24
+
25
+ def initialize max_size = DEFAULT_QUEUE_SIZE
26
+ @max_size = max_size
27
+ @lock = Mutex.new
28
+ @queue = Queue.new
29
+ @batch = Array.new
30
+ end
31
+
32
+ # Dumps the contents of this streaming buffer onto
33
+ # the given buffer and closes the queue
34
+ def transfer new_buffer
35
+ @lock.synchronize do
36
+ until @queue.empty? do new_buffer.push @queue.pop end
37
+ @queue.close
38
+ end
39
+ end
40
+
41
+ # Pushes the segment given onto the queue.
42
+ #
43
+ # If the queue is at capacity, it is dumped and a
44
+ # supportability metric is recorded for the event.
45
+ #
46
+ # When a restart signal is received, the queue is
47
+ # locked with a mutex, blocking the push until
48
+ # the queue has restarted.
49
+ def << segment
50
+ @lock.synchronize do
51
+ clear_queue if @queue.size >= @max_size
52
+ NewRelic::Agent.increment_metric SPANS_SEEN_METRIC
53
+ @queue.push segment
54
+ end
55
+ end
56
+
57
+ # Drops all segments from the queue and records a
58
+ # supportability metric for the event.
59
+ def clear_queue
60
+ @queue.clear
61
+ NewRelic::Agent.increment_metric QUEUE_DUMPED_METRIC
62
+ end
63
+
64
+ # # Waits for the queue to be fully consumed or for the
65
+ # # waiting consumers to release.
66
+ # def flush_queue
67
+ # @queue.num_waiting.times { @queue.push nil }
68
+ # close_queue
69
+ # until @queue.empty? do sleep(FLUSH_DELAY) end
70
+ # end
71
+
72
+ # Waits for the queue to be fully consumed or for the
73
+ # waiting consumers to release.
74
+ def flush_queue
75
+ @queue.num_waiting.times { @queue.push nil }
76
+ close_queue
77
+
78
+ # Logs if we're throwing away spans because nothing's
79
+ # waiting to take them off the queue.
80
+ if @queue.num_waiting == 0 && !@queue.empty?
81
+ NewRelic::Agent.logger.warn "Discarding #{@queue.size} segments on Streaming Buffer"
82
+ return
83
+ end
84
+
85
+ # Only wait a short while for queue to flush
86
+ cutoff = Time.now + MAX_FLUSH_WAIT
87
+ until @queue.empty? || Time.now >= cutoff do sleep(FLUSH_DELAY) end
88
+ end
89
+
90
+ def close_queue
91
+ @lock.synchronize { @queue.close }
92
+ end
93
+
94
+ # Returns the blocking enumerator that will pop
95
+ # items off the queue while any items are present
96
+ # If +nil+ is popped, the queue is closing.
97
+ #
98
+ # The segment is transformed into a serializable
99
+ # span here so processing is taking place within
100
+ # the gRPC call's thread rather than in the main
101
+ # application thread.
102
+ def enumerator
103
+ return enum_for(:enumerator) unless block_given?
104
+ loop do
105
+ if segment = @queue.pop(false)
106
+ NewRelic::Agent.increment_metric SPANS_SENT_METRIC
107
+ yield transform(segment)
108
+
109
+ else
110
+ raise ClosedQueueError
111
+ end
112
+ end
113
+ end
114
+
115
+ # Returns the blocking enumerator that will pop
116
+ # items off the queue while any items are present
117
+ #
118
+ # yielding is deferred until batch_size spans is
119
+ # reached.
120
+ #
121
+ # If +nil+ is popped, the queue is closing. A
122
+ # final yield on non-empty batch is fired.
123
+ #
124
+ # The segment is transformed into a serializable
125
+ # span here so processing is taking place within
126
+ # the gRPC call's thread rather than in the main
127
+ # application thread.
128
+ def batch_enumerator
129
+ return enum_for(:enumerator) unless block_given?
130
+ loop do
131
+ if proc_or_segment = @queue.pop(false)
132
+ NewRelic::Agent.increment_metric SPANS_SENT_METRIC
133
+ @batch << transform(proc_or_segment)
134
+ if @batch.size >= BATCH_SIZE
135
+ yield SpanBatch.new(spans: @batch)
136
+ @batch.clear
137
+ end
138
+
139
+ else
140
+ yield SpanBatch.new(spans: @batch) unless @batch.empty?
141
+ raise ClosedQueueError
142
+ end
143
+ end
144
+ end
145
+
146
+ private
147
+
148
+ def span_event proc_or_segment
149
+ if proc_or_segment.is_a?(Proc)
150
+ proc_or_segment.call
151
+ else
152
+ SpanEventPrimitive.for_segment(proc_or_segment)
153
+ end
154
+ end
155
+
156
+ def transform proc_or_segment
157
+ Span.new Transformer.transform(span_event proc_or_segment)
158
+ end
159
+
160
+ end
161
+ end
162
+ end