newrelic-infinite_tracing 6.11.0.365
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +12 -0
- data/Rakefile +24 -0
- data/lib/infinite_tracing.rb +40 -0
- data/lib/infinite_tracing/agent_integrations.rb +20 -0
- data/lib/infinite_tracing/agent_integrations/agent.rb +33 -0
- data/lib/infinite_tracing/agent_integrations/datastore_segment.rb +17 -0
- data/lib/infinite_tracing/agent_integrations/external_request_segment.rb +17 -0
- data/lib/infinite_tracing/agent_integrations/segment.rb +22 -0
- data/lib/infinite_tracing/channel.rb +45 -0
- data/lib/infinite_tracing/client.rb +138 -0
- data/lib/infinite_tracing/config.rb +115 -0
- data/lib/infinite_tracing/connection.rb +171 -0
- data/lib/infinite_tracing/constants.rb +20 -0
- data/lib/infinite_tracing/proto.rb +18 -0
- data/lib/infinite_tracing/proto/infinite_tracing_pb.rb +44 -0
- data/lib/infinite_tracing/proto/infinite_tracing_services_pb.rb +42 -0
- data/lib/infinite_tracing/record_status_handler.rb +49 -0
- data/lib/infinite_tracing/streaming_buffer.rb +162 -0
- data/lib/infinite_tracing/suspended_streaming_buffer.rb +39 -0
- data/lib/infinite_tracing/transformer.rb +54 -0
- data/lib/infinite_tracing/version.rb +12 -0
- data/lib/infinite_tracing/worker.rb +74 -0
- data/lib/new_relic/infinite_tracing.rb +40 -0
- data/lib/newrelic/infinite_tracing.rb +8 -0
- data/lib/proto/infinite_tracing.proto +42 -0
- data/newrelic-infinite_tracing.gemspec +91 -0
- data/tasks/all.rb +8 -0
- data/tasks/proto.rake +51 -0
- metadata +290 -0
@@ -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
|