newrelic-infinite_tracing 6.11.0.365

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