dogstatsd-ruby 4.8.1 → 5.6.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +172 -15
- data/lib/datadog/statsd/connection.rb +16 -18
- data/lib/datadog/statsd/connection_cfg.rb +125 -0
- data/lib/datadog/statsd/forwarder.rb +138 -0
- data/lib/datadog/statsd/message_buffer.rb +105 -0
- data/lib/datadog/statsd/sender.rb +184 -0
- data/lib/datadog/statsd/serialization/event_serializer.rb +5 -1
- data/lib/datadog/statsd/serialization/stat_serializer.rb +22 -29
- data/lib/datadog/statsd/serialization/tag_serializer.rb +20 -15
- data/lib/datadog/statsd/single_thread_sender.rb +82 -0
- data/lib/datadog/statsd/telemetry.rb +43 -24
- data/lib/datadog/statsd/timer.rb +61 -0
- data/lib/datadog/statsd/udp_connection.rb +25 -14
- data/lib/datadog/statsd/uds_connection.rb +19 -8
- data/lib/datadog/statsd/version.rb +1 -1
- data/lib/datadog/statsd.rb +154 -69
- metadata +23 -12
- data/lib/datadog/statsd/batch.rb +0 -56
@@ -0,0 +1,184 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
# Sender is using a companion thread to flush and pack messages
|
6
|
+
# in a `MessageBuffer`.
|
7
|
+
# The communication with this thread is done using a `Queue`.
|
8
|
+
# If the thread is dead, it is starting a new one to avoid having a blocked
|
9
|
+
# Sender with no companion thread to communicate with (most of the time, having
|
10
|
+
# a dead companion thread means that a fork just happened and that we are
|
11
|
+
# running in the child process).
|
12
|
+
class Sender
|
13
|
+
CLOSEABLE_QUEUES = Queue.instance_methods.include?(:close)
|
14
|
+
|
15
|
+
def initialize(message_buffer, telemetry: nil, queue_size: UDP_DEFAULT_BUFFER_SIZE, logger: nil, flush_interval: nil, queue_class: Queue, thread_class: Thread)
|
16
|
+
@message_buffer = message_buffer
|
17
|
+
@telemetry = telemetry
|
18
|
+
@queue_size = queue_size
|
19
|
+
@logger = logger
|
20
|
+
@mx = Mutex.new
|
21
|
+
@queue_class = queue_class
|
22
|
+
@thread_class = thread_class
|
23
|
+
@flush_timer = if flush_interval
|
24
|
+
Datadog::Statsd::Timer.new(flush_interval) { flush(sync: true) }
|
25
|
+
else
|
26
|
+
nil
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def flush(sync: false)
|
31
|
+
# keep a copy around in case another thread is calling #stop while this method is running
|
32
|
+
current_message_queue = message_queue
|
33
|
+
|
34
|
+
# don't try to flush if there is no message_queue instantiated or
|
35
|
+
# no companion thread running
|
36
|
+
if !current_message_queue
|
37
|
+
@logger.debug { "Statsd: can't flush: no message queue ready" } if @logger
|
38
|
+
return
|
39
|
+
end
|
40
|
+
if !sender_thread.alive?
|
41
|
+
@logger.debug { "Statsd: can't flush: no sender_thread alive" } if @logger
|
42
|
+
return
|
43
|
+
end
|
44
|
+
|
45
|
+
current_message_queue.push(:flush)
|
46
|
+
rendez_vous if sync
|
47
|
+
end
|
48
|
+
|
49
|
+
def rendez_vous
|
50
|
+
# could happen if #start hasn't be called
|
51
|
+
return unless message_queue
|
52
|
+
|
53
|
+
# Initialize and get the thread's sync queue
|
54
|
+
queue = (@thread_class.current[:statsd_sync_queue] ||= @queue_class.new)
|
55
|
+
# tell sender-thread to notify us in the current
|
56
|
+
# thread's queue
|
57
|
+
message_queue.push(queue)
|
58
|
+
# wait for the sender thread to send a message
|
59
|
+
# once the flush is done
|
60
|
+
queue.pop
|
61
|
+
end
|
62
|
+
|
63
|
+
def add(message)
|
64
|
+
raise ArgumentError, 'Start sender first' unless message_queue
|
65
|
+
|
66
|
+
# if the thread does not exist, we assume we are running in a forked process,
|
67
|
+
# empty the message queue and message buffers (these messages belong to
|
68
|
+
# the parent process) and spawn a new companion thread.
|
69
|
+
if !sender_thread.alive?
|
70
|
+
@mx.synchronize {
|
71
|
+
# a call from another thread has already re-created
|
72
|
+
# the companion thread before this one acquired the lock
|
73
|
+
break if sender_thread.alive?
|
74
|
+
@logger.debug { "Statsd: companion thread is dead, re-creating one" } if @logger
|
75
|
+
|
76
|
+
message_queue.close if CLOSEABLE_QUEUES
|
77
|
+
@message_queue = nil
|
78
|
+
message_buffer.reset
|
79
|
+
start
|
80
|
+
@flush_timer.start if @flush_timer && @flush_timer.stop?
|
81
|
+
}
|
82
|
+
end
|
83
|
+
|
84
|
+
if message_queue.length <= @queue_size
|
85
|
+
message_queue << message
|
86
|
+
else
|
87
|
+
if @telemetry
|
88
|
+
bytesize = message.respond_to?(:bytesize) ? message.bytesize : 0
|
89
|
+
@telemetry.dropped_queue(packets: 1, bytes: bytesize)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def start
|
95
|
+
raise ArgumentError, 'Sender already started' if message_queue
|
96
|
+
|
97
|
+
# initialize a new message queue for the background thread
|
98
|
+
@message_queue = @queue_class.new
|
99
|
+
# start background thread
|
100
|
+
@sender_thread = @thread_class.new(&method(:send_loop))
|
101
|
+
@sender_thread.name = "Statsd Sender" unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
|
102
|
+
@flush_timer.start if @flush_timer
|
103
|
+
end
|
104
|
+
|
105
|
+
if CLOSEABLE_QUEUES
|
106
|
+
# when calling stop, make sure that no other threads is trying
|
107
|
+
# to close the sender nor trying to continue to `#add` more message
|
108
|
+
# into the sender.
|
109
|
+
def stop(join_worker: true)
|
110
|
+
@flush_timer.stop if @flush_timer
|
111
|
+
|
112
|
+
message_queue = @message_queue
|
113
|
+
message_queue.close if message_queue
|
114
|
+
|
115
|
+
sender_thread = @sender_thread
|
116
|
+
sender_thread.join if sender_thread && join_worker
|
117
|
+
end
|
118
|
+
else
|
119
|
+
# when calling stop, make sure that no other threads is trying
|
120
|
+
# to close the sender nor trying to continue to `#add` more message
|
121
|
+
# into the sender.
|
122
|
+
def stop(join_worker: true)
|
123
|
+
@flush_timer.stop if @flush_timer
|
124
|
+
|
125
|
+
message_queue = @message_queue
|
126
|
+
message_queue << :close if message_queue
|
127
|
+
|
128
|
+
sender_thread = @sender_thread
|
129
|
+
sender_thread.join if sender_thread && join_worker
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
private
|
134
|
+
|
135
|
+
attr_reader :message_buffer
|
136
|
+
attr_reader :message_queue
|
137
|
+
attr_reader :sender_thread
|
138
|
+
|
139
|
+
if CLOSEABLE_QUEUES
|
140
|
+
def send_loop
|
141
|
+
until (message = message_queue.pop).nil? && message_queue.closed?
|
142
|
+
# skip if message is nil, e.g. when message_queue
|
143
|
+
# is empty and closed
|
144
|
+
next unless message
|
145
|
+
|
146
|
+
case message
|
147
|
+
when :flush
|
148
|
+
message_buffer.flush
|
149
|
+
when @queue_class
|
150
|
+
message.push(:go_on)
|
151
|
+
else
|
152
|
+
message_buffer.add(message)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
@message_queue = nil
|
157
|
+
@sender_thread = nil
|
158
|
+
end
|
159
|
+
else
|
160
|
+
def send_loop
|
161
|
+
loop do
|
162
|
+
message = message_queue.pop
|
163
|
+
|
164
|
+
next unless message
|
165
|
+
|
166
|
+
case message
|
167
|
+
when :close
|
168
|
+
break
|
169
|
+
when :flush
|
170
|
+
message_buffer.flush
|
171
|
+
when @queue_class
|
172
|
+
message.push(:go_on)
|
173
|
+
else
|
174
|
+
message_buffer.add(message)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
@message_queue = nil
|
179
|
+
@sender_thread = nil
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -48,7 +48,11 @@ module Datadog
|
|
48
48
|
end
|
49
49
|
|
50
50
|
if event.bytesize > MAX_EVENT_SIZE
|
51
|
-
|
51
|
+
if options[:truncate_if_too_long]
|
52
|
+
event.slice!(MAX_EVENT_SIZE..event.length)
|
53
|
+
else
|
54
|
+
raise "Event #{title} payload is too big (more that 8KB), event discarded"
|
55
|
+
end
|
52
56
|
end
|
53
57
|
end
|
54
58
|
end
|
@@ -6,34 +6,24 @@ module Datadog
|
|
6
6
|
class StatSerializer
|
7
7
|
def initialize(prefix, global_tags: [])
|
8
8
|
@prefix = prefix
|
9
|
+
@prefix_str = prefix.to_s
|
9
10
|
@tag_serializer = TagSerializer.new(global_tags)
|
10
11
|
end
|
11
12
|
|
12
13
|
def format(name, delta, type, tags: [], sample_rate: 1)
|
13
|
-
|
14
|
-
stat << prefix if prefix
|
15
|
-
|
16
|
-
# stat value
|
17
|
-
stat << formated_name(name)
|
18
|
-
stat << ':'
|
19
|
-
stat << delta.to_s
|
20
|
-
|
21
|
-
# stat type
|
22
|
-
stat << '|'
|
23
|
-
stat << type
|
24
|
-
|
25
|
-
# sample_rate
|
26
|
-
if sample_rate != 1
|
27
|
-
stat << '|'
|
28
|
-
stat << '@'
|
29
|
-
stat << sample_rate.to_s
|
30
|
-
end
|
14
|
+
name = formated_name(name)
|
31
15
|
|
32
|
-
|
16
|
+
if sample_rate != 1
|
17
|
+
if tags_list = tag_serializer.format(tags)
|
18
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}|##{tags_list}"
|
19
|
+
else
|
20
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}|@#{sample_rate}"
|
21
|
+
end
|
22
|
+
else
|
33
23
|
if tags_list = tag_serializer.format(tags)
|
34
|
-
|
35
|
-
|
36
|
-
|
24
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}|##{tags_list}"
|
25
|
+
else
|
26
|
+
"#{@prefix_str}#{name}:#{delta}|#{type}"
|
37
27
|
end
|
38
28
|
end
|
39
29
|
end
|
@@ -43,18 +33,21 @@ module Datadog
|
|
43
33
|
end
|
44
34
|
|
45
35
|
private
|
36
|
+
|
46
37
|
attr_reader :prefix
|
47
38
|
attr_reader :tag_serializer
|
48
39
|
|
49
40
|
def formated_name(name)
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
f.tr!(':|@', '_')
|
41
|
+
if name.is_a?(String)
|
42
|
+
# DEV: gsub is faster than dup.gsub!
|
43
|
+
formated = name.gsub('::', '.')
|
44
|
+
else
|
45
|
+
formated = name.to_s
|
46
|
+
formated.gsub!('::', '.')
|
57
47
|
end
|
48
|
+
|
49
|
+
formated.tr!(':|@', '_')
|
50
|
+
formated
|
58
51
|
end
|
59
52
|
end
|
60
53
|
end
|
@@ -13,18 +13,25 @@ module Datadog
|
|
13
13
|
|
14
14
|
# Convert to tag list and set
|
15
15
|
@global_tags = to_tags_list(global_tags)
|
16
|
+
if @global_tags.any?
|
17
|
+
@global_tags_formatted = @global_tags.join(',')
|
18
|
+
else
|
19
|
+
@global_tags_formatted = nil
|
20
|
+
end
|
16
21
|
end
|
17
22
|
|
18
23
|
def format(message_tags)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
global_tags + to_tags_list(message_tags)
|
23
|
-
else
|
24
|
-
global_tags
|
25
|
-
end
|
24
|
+
if !message_tags || message_tags.empty?
|
25
|
+
return @global_tags_formatted
|
26
|
+
end
|
26
27
|
|
27
|
-
|
28
|
+
tags = if @global_tags_formatted
|
29
|
+
[@global_tags_formatted, to_tags_list(message_tags)]
|
30
|
+
else
|
31
|
+
to_tags_list(message_tags)
|
32
|
+
end
|
33
|
+
|
34
|
+
tags.join(',')
|
28
35
|
end
|
29
36
|
|
30
37
|
attr_reader :global_tags
|
@@ -51,19 +58,17 @@ module Datadog
|
|
51
58
|
def to_tags_list(tags)
|
52
59
|
case tags
|
53
60
|
when Hash
|
54
|
-
tags.
|
55
|
-
if
|
56
|
-
|
61
|
+
tags.map do |name, value|
|
62
|
+
if value
|
63
|
+
escape_tag_content("#{name}:#{value}")
|
57
64
|
else
|
58
|
-
|
65
|
+
escape_tag_content(name)
|
59
66
|
end
|
60
67
|
end
|
61
68
|
when Array
|
62
|
-
tags.
|
69
|
+
tags.map { |tag| escape_tag_content(tag) }
|
63
70
|
else
|
64
71
|
[]
|
65
|
-
end.map! do |tag|
|
66
|
-
escape_tag_content(tag)
|
67
72
|
end
|
68
73
|
end
|
69
74
|
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
# The SingleThreadSender is a sender synchronously buffering messages
|
6
|
+
# in a `MessageBuffer`.
|
7
|
+
# It is using current Process.PID to check it is the result of a recent fork
|
8
|
+
# and it is reseting the MessageBuffer if that's the case.
|
9
|
+
class SingleThreadSender
|
10
|
+
def initialize(message_buffer, logger: nil, flush_interval: nil, queue_size: 1)
|
11
|
+
@message_buffer = message_buffer
|
12
|
+
@logger = logger
|
13
|
+
@mx = Mutex.new
|
14
|
+
@message_queue_size = queue_size
|
15
|
+
@message_queue = []
|
16
|
+
@flush_timer = if flush_interval
|
17
|
+
Datadog::Statsd::Timer.new(flush_interval) { flush }
|
18
|
+
else
|
19
|
+
nil
|
20
|
+
end
|
21
|
+
# store the pid for which this sender has been created
|
22
|
+
update_fork_pid
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(message)
|
26
|
+
@mx.synchronize {
|
27
|
+
# we have just forked, meaning we have messages in the buffer that we should
|
28
|
+
# not send, they belong to the parent process, let's clear the buffer.
|
29
|
+
if forked?
|
30
|
+
@message_buffer.reset
|
31
|
+
@message_queue.clear
|
32
|
+
@flush_timer.start if @flush_timer && @flush_timer.stop?
|
33
|
+
update_fork_pid
|
34
|
+
end
|
35
|
+
|
36
|
+
@message_queue << message
|
37
|
+
if @message_queue.size >= @message_queue_size
|
38
|
+
drain_message_queue
|
39
|
+
end
|
40
|
+
}
|
41
|
+
end
|
42
|
+
|
43
|
+
def flush(*)
|
44
|
+
@mx.synchronize {
|
45
|
+
drain_message_queue
|
46
|
+
@message_buffer.flush()
|
47
|
+
}
|
48
|
+
end
|
49
|
+
|
50
|
+
def start()
|
51
|
+
@flush_timer.start if @flush_timer
|
52
|
+
end
|
53
|
+
|
54
|
+
def stop()
|
55
|
+
@flush_timer.stop if @flush_timer
|
56
|
+
end
|
57
|
+
|
58
|
+
# Compatibility with `Sender`
|
59
|
+
def rendez_vous()
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def drain_message_queue
|
65
|
+
while msg = @message_queue.shift
|
66
|
+
@message_buffer.add(msg)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# below are "fork management" methods to be able to clean the MessageBuffer
|
71
|
+
# if it detects that it is running in a unknown PID.
|
72
|
+
|
73
|
+
def forked?
|
74
|
+
Process.pid != @fork_pid
|
75
|
+
end
|
76
|
+
|
77
|
+
def update_fork_pid
|
78
|
+
@fork_pid = Process.pid
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -9,12 +9,17 @@ module Datadog
|
|
9
9
|
attr_reader :service_checks
|
10
10
|
attr_reader :bytes_sent
|
11
11
|
attr_reader :bytes_dropped
|
12
|
+
attr_reader :bytes_dropped_queue
|
13
|
+
attr_reader :bytes_dropped_writer
|
12
14
|
attr_reader :packets_sent
|
13
15
|
attr_reader :packets_dropped
|
14
|
-
attr_reader :
|
16
|
+
attr_reader :packets_dropped_queue
|
17
|
+
attr_reader :packets_dropped_writer
|
15
18
|
|
16
|
-
|
17
|
-
|
19
|
+
# Rough estimation of maximum telemetry message size without tags
|
20
|
+
MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS = 50 # bytes
|
21
|
+
|
22
|
+
def initialize(flush_interval, global_tags: [], transport_type: :udp)
|
18
23
|
@flush_interval = flush_interval
|
19
24
|
@global_tags = global_tags
|
20
25
|
@transport_type = transport_type
|
@@ -27,15 +32,10 @@ module Datadog
|
|
27
32
|
client_version: VERSION,
|
28
33
|
client_transport: transport_type,
|
29
34
|
).format(global_tags)
|
35
|
+
end
|
30
36
|
|
31
|
-
|
32
|
-
|
33
|
-
# 'max_buffer_bytes', we have to adjust with the size of the telemetry
|
34
|
-
# (and any tags used). The telemetry payload size will change depending
|
35
|
-
# on the actual value of metrics: metrics received, packet dropped,
|
36
|
-
# etc. This is why we add a 63bytes margin: 9 bytes for each of the 7
|
37
|
-
# telemetry metrics.
|
38
|
-
@estimate_max_size = disabled ? 0 : flush.length + 9 * 7
|
37
|
+
def would_fit_in?(max_buffer_payload_size)
|
38
|
+
MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS + serialized_tags.size < max_buffer_payload_size
|
39
39
|
end
|
40
40
|
|
41
41
|
def reset
|
@@ -44,8 +44,12 @@ module Datadog
|
|
44
44
|
@service_checks = 0
|
45
45
|
@bytes_sent = 0
|
46
46
|
@bytes_dropped = 0
|
47
|
+
@bytes_dropped_queue = 0
|
48
|
+
@bytes_dropped_writer = 0
|
47
49
|
@packets_sent = 0
|
48
50
|
@packets_dropped = 0
|
51
|
+
@packets_dropped_queue = 0
|
52
|
+
@packets_dropped_writer = 0
|
49
53
|
@next_flush_time = now_in_s + @flush_interval
|
50
54
|
end
|
51
55
|
|
@@ -58,32 +62,47 @@ module Datadog
|
|
58
62
|
@packets_sent += packets
|
59
63
|
end
|
60
64
|
|
61
|
-
def
|
65
|
+
def dropped_queue(bytes: 0, packets: 0)
|
62
66
|
@bytes_dropped += bytes
|
67
|
+
@bytes_dropped_queue += bytes
|
63
68
|
@packets_dropped += packets
|
69
|
+
@packets_dropped_queue += packets
|
64
70
|
end
|
65
71
|
|
66
|
-
def
|
72
|
+
def dropped_writer(bytes: 0, packets: 0)
|
73
|
+
@bytes_dropped += bytes
|
74
|
+
@bytes_dropped_writer += bytes
|
75
|
+
@packets_dropped += packets
|
76
|
+
@packets_dropped_writer += packets
|
77
|
+
end
|
78
|
+
|
79
|
+
def should_flush?
|
67
80
|
@next_flush_time < now_in_s
|
68
81
|
end
|
69
82
|
|
70
83
|
def flush
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
84
|
+
[
|
85
|
+
sprintf(pattern, 'metrics', @metrics),
|
86
|
+
sprintf(pattern, 'events', @events),
|
87
|
+
sprintf(pattern, 'service_checks', @service_checks),
|
88
|
+
sprintf(pattern, 'bytes_sent', @bytes_sent),
|
89
|
+
sprintf(pattern, 'bytes_dropped', @bytes_dropped),
|
90
|
+
sprintf(pattern, 'bytes_dropped_queue', @bytes_dropped_queue),
|
91
|
+
sprintf(pattern, 'bytes_dropped_writer', @bytes_dropped_writer),
|
92
|
+
sprintf(pattern, 'packets_sent', @packets_sent),
|
93
|
+
sprintf(pattern, 'packets_dropped', @packets_dropped),
|
94
|
+
sprintf(pattern, 'packets_dropped_queue', @packets_dropped_queue),
|
95
|
+
sprintf(pattern, 'packets_dropped_writer', @packets_dropped_writer),
|
96
|
+
]
|
82
97
|
end
|
83
98
|
|
84
99
|
private
|
85
100
|
attr_reader :serialized_tags
|
86
101
|
|
102
|
+
def pattern
|
103
|
+
@pattern ||= "datadog.dogstatsd.client.%s:%d|#{COUNTER_TYPE}|##{serialized_tags}"
|
104
|
+
end
|
105
|
+
|
87
106
|
if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
|
88
107
|
def now_in_s
|
89
108
|
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class Timer
|
6
|
+
def initialize(interval, &callback)
|
7
|
+
@mx = Mutex.new
|
8
|
+
@cv = ConditionVariable.new
|
9
|
+
@interval = interval
|
10
|
+
@callback = callback
|
11
|
+
@stop = true
|
12
|
+
@thread = nil
|
13
|
+
end
|
14
|
+
|
15
|
+
def start
|
16
|
+
return unless stop?
|
17
|
+
|
18
|
+
@stop = false
|
19
|
+
@thread = Thread.new do
|
20
|
+
last_execution_time = current_time
|
21
|
+
@mx.synchronize do
|
22
|
+
until @stop
|
23
|
+
timeout = @interval - (current_time - last_execution_time)
|
24
|
+
@cv.wait(@mx, timeout > 0 ? timeout : 0)
|
25
|
+
last_execution_time = current_time
|
26
|
+
@callback.call
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
@thread.name = 'Statsd Timer' unless Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.3')
|
31
|
+
end
|
32
|
+
|
33
|
+
def stop
|
34
|
+
return if @thread.nil?
|
35
|
+
|
36
|
+
@stop = true
|
37
|
+
@mx.synchronize do
|
38
|
+
@cv.signal
|
39
|
+
end
|
40
|
+
@thread.join
|
41
|
+
@thread = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def stop?
|
45
|
+
@thread.nil? || @thread.stop?
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
if Process.const_defined?(:CLOCK_MONOTONIC)
|
51
|
+
def current_time
|
52
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
53
|
+
end
|
54
|
+
else
|
55
|
+
def current_time
|
56
|
+
Time.now
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -5,32 +5,43 @@ require_relative 'connection'
|
|
5
5
|
module Datadog
|
6
6
|
class Statsd
|
7
7
|
class UDPConnection < Connection
|
8
|
-
|
9
|
-
DEFAULT_PORT = 8125
|
10
|
-
|
11
|
-
# StatsD host. Defaults to 127.0.0.1.
|
8
|
+
# StatsD host.
|
12
9
|
attr_reader :host
|
13
10
|
|
14
|
-
# StatsD port.
|
11
|
+
# StatsD port.
|
15
12
|
attr_reader :port
|
16
13
|
|
17
|
-
def initialize(host, port,
|
18
|
-
super(
|
19
|
-
|
20
|
-
@
|
21
|
-
@
|
14
|
+
def initialize(host, port, **kwargs)
|
15
|
+
super(**kwargs)
|
16
|
+
|
17
|
+
@host = host
|
18
|
+
@port = port
|
19
|
+
@socket = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def close
|
23
|
+
@socket.close if @socket
|
24
|
+
@socket = nil
|
22
25
|
end
|
23
26
|
|
24
27
|
private
|
25
28
|
|
26
29
|
def connect
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
+
close if @socket
|
31
|
+
|
32
|
+
family = Addrinfo.udp(host, port).afamily
|
33
|
+
|
34
|
+
@socket = UDPSocket.new(family)
|
35
|
+
@socket.connect(host, port)
|
30
36
|
end
|
31
37
|
|
38
|
+
# send_message is writing the message in the socket, it may create the socket if nil
|
39
|
+
# It is not thread-safe but since it is called by either the Sender bg thread or the
|
40
|
+
# SingleThreadSender (which is using a mutex while Flushing), only one thread must call
|
41
|
+
# it at a time.
|
32
42
|
def send_message(message)
|
33
|
-
socket
|
43
|
+
connect unless @socket
|
44
|
+
@socket.send(message, 0)
|
34
45
|
end
|
35
46
|
end
|
36
47
|
end
|
@@ -10,24 +10,35 @@ module Datadog
|
|
10
10
|
# DogStatsd unix socket path
|
11
11
|
attr_reader :socket_path
|
12
12
|
|
13
|
-
def initialize(socket_path,
|
14
|
-
super(
|
13
|
+
def initialize(socket_path, **kwargs)
|
14
|
+
super(**kwargs)
|
15
|
+
|
15
16
|
@socket_path = socket_path
|
16
|
-
@
|
17
|
+
@socket = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
@socket.close if @socket
|
22
|
+
@socket = nil
|
17
23
|
end
|
18
24
|
|
19
25
|
private
|
20
26
|
|
21
27
|
def connect
|
22
|
-
|
23
|
-
|
24
|
-
socket
|
28
|
+
close if @socket
|
29
|
+
|
30
|
+
@socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
|
31
|
+
@socket.connect(Socket.pack_sockaddr_un(@socket_path))
|
25
32
|
end
|
26
33
|
|
34
|
+
# send_message is writing the message in the socket, it may create the socket if nil
|
35
|
+
# It is not thread-safe but since it is called by either the Sender bg thread or the
|
36
|
+
# SingleThreadSender (which is using a mutex while Flushing), only one thread must call
|
37
|
+
# it at a time.
|
27
38
|
def send_message(message)
|
28
|
-
socket
|
39
|
+
connect unless @socket
|
40
|
+
@socket.sendmsg_nonblock(message)
|
29
41
|
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
|
30
|
-
@socket = nil
|
31
42
|
# TODO: FIXME: This error should be considered as a retryable error in the
|
32
43
|
# Connection class. An even better solution would be to make BadSocketError inherit
|
33
44
|
# from a specific retryable error class in the Connection class.
|