dogstatsd-ruby 4.0.0 → 5.5.0
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.
- checksums.yaml +4 -4
- data/README.md +186 -57
- data/lib/datadog/statsd/connection.rb +60 -0
- data/lib/datadog/statsd/connection_cfg.rb +76 -0
- data/lib/datadog/statsd/forwarder.rb +133 -0
- data/lib/datadog/statsd/message_buffer.rb +97 -0
- data/lib/datadog/statsd/sender.rb +181 -0
- data/lib/datadog/statsd/serialization/event_serializer.rb +71 -0
- data/lib/datadog/statsd/serialization/serializer.rb +41 -0
- data/lib/datadog/statsd/serialization/service_check_serializer.rb +60 -0
- data/lib/datadog/statsd/serialization/stat_serializer.rb +55 -0
- data/lib/datadog/statsd/serialization/tag_serializer.rb +96 -0
- data/lib/datadog/statsd/serialization.rb +15 -0
- data/lib/datadog/statsd/single_thread_sender.rb +68 -0
- data/lib/datadog/statsd/telemetry.rb +117 -0
- data/lib/datadog/statsd/timer.rb +61 -0
- data/lib/datadog/statsd/udp_connection.rb +46 -0
- data/lib/datadog/statsd/uds_connection.rb +49 -0
- data/lib/datadog/statsd/version.rb +9 -0
- data/lib/datadog/statsd.rb +209 -333
- metadata +38 -11
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'time'
|
3
|
+
|
4
|
+
module Datadog
|
5
|
+
class Statsd
|
6
|
+
class Telemetry
|
7
|
+
attr_reader :metrics
|
8
|
+
attr_reader :events
|
9
|
+
attr_reader :service_checks
|
10
|
+
attr_reader :bytes_sent
|
11
|
+
attr_reader :bytes_dropped
|
12
|
+
attr_reader :bytes_dropped_queue
|
13
|
+
attr_reader :bytes_dropped_writer
|
14
|
+
attr_reader :packets_sent
|
15
|
+
attr_reader :packets_dropped
|
16
|
+
attr_reader :packets_dropped_queue
|
17
|
+
attr_reader :packets_dropped_writer
|
18
|
+
|
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)
|
23
|
+
@flush_interval = flush_interval
|
24
|
+
@global_tags = global_tags
|
25
|
+
@transport_type = transport_type
|
26
|
+
reset
|
27
|
+
|
28
|
+
# TODO: Karim: I don't know why but telemetry tags are serialized
|
29
|
+
# before global tags so by refactoring this, I am keeping the same behavior
|
30
|
+
@serialized_tags = Serialization::TagSerializer.new(
|
31
|
+
client: 'ruby',
|
32
|
+
client_version: VERSION,
|
33
|
+
client_transport: transport_type,
|
34
|
+
).format(global_tags)
|
35
|
+
end
|
36
|
+
|
37
|
+
def would_fit_in?(max_buffer_payload_size)
|
38
|
+
MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS + serialized_tags.size < max_buffer_payload_size
|
39
|
+
end
|
40
|
+
|
41
|
+
def reset
|
42
|
+
@metrics = 0
|
43
|
+
@events = 0
|
44
|
+
@service_checks = 0
|
45
|
+
@bytes_sent = 0
|
46
|
+
@bytes_dropped = 0
|
47
|
+
@bytes_dropped_queue = 0
|
48
|
+
@bytes_dropped_writer = 0
|
49
|
+
@packets_sent = 0
|
50
|
+
@packets_dropped = 0
|
51
|
+
@packets_dropped_queue = 0
|
52
|
+
@packets_dropped_writer = 0
|
53
|
+
@next_flush_time = now_in_s + @flush_interval
|
54
|
+
end
|
55
|
+
|
56
|
+
def sent(metrics: 0, events: 0, service_checks: 0, bytes: 0, packets: 0)
|
57
|
+
@metrics += metrics
|
58
|
+
@events += events
|
59
|
+
@service_checks += service_checks
|
60
|
+
|
61
|
+
@bytes_sent += bytes
|
62
|
+
@packets_sent += packets
|
63
|
+
end
|
64
|
+
|
65
|
+
def dropped_queue(bytes: 0, packets: 0)
|
66
|
+
@bytes_dropped += bytes
|
67
|
+
@bytes_dropped_queue += bytes
|
68
|
+
@packets_dropped += packets
|
69
|
+
@packets_dropped_queue += packets
|
70
|
+
end
|
71
|
+
|
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?
|
80
|
+
@next_flush_time < now_in_s
|
81
|
+
end
|
82
|
+
|
83
|
+
def flush
|
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
|
+
]
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
attr_reader :serialized_tags
|
101
|
+
|
102
|
+
def pattern
|
103
|
+
@pattern ||= "datadog.dogstatsd.client.%s:%d|#{COUNTER_TYPE}|##{serialized_tags}"
|
104
|
+
end
|
105
|
+
|
106
|
+
if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
|
107
|
+
def now_in_s
|
108
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
109
|
+
end
|
110
|
+
else
|
111
|
+
def now_in_s
|
112
|
+
Time.now.to_i
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -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
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'connection'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
class Statsd
|
7
|
+
class UDPConnection < Connection
|
8
|
+
# StatsD host.
|
9
|
+
attr_reader :host
|
10
|
+
|
11
|
+
# StatsD port.
|
12
|
+
attr_reader :port
|
13
|
+
|
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
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def connect
|
30
|
+
close if @socket
|
31
|
+
|
32
|
+
@socket = UDPSocket.new
|
33
|
+
@socket.connect(host, port)
|
34
|
+
end
|
35
|
+
|
36
|
+
# send_message is writing the message in the socket, it may create the socket if nil
|
37
|
+
# It is not thread-safe but since it is called by either the Sender bg thread or the
|
38
|
+
# SingleThreadSender (which is using a mutex while Flushing), only one thread must call
|
39
|
+
# it at a time.
|
40
|
+
def send_message(message)
|
41
|
+
connect unless @socket
|
42
|
+
@socket.send(message, 0)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'connection'
|
4
|
+
|
5
|
+
module Datadog
|
6
|
+
class Statsd
|
7
|
+
class UDSConnection < Connection
|
8
|
+
class BadSocketError < StandardError; end
|
9
|
+
|
10
|
+
# DogStatsd unix socket path
|
11
|
+
attr_reader :socket_path
|
12
|
+
|
13
|
+
def initialize(socket_path, **kwargs)
|
14
|
+
super(**kwargs)
|
15
|
+
|
16
|
+
@socket_path = socket_path
|
17
|
+
@socket = nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def close
|
21
|
+
@socket.close if @socket
|
22
|
+
@socket = nil
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def connect
|
28
|
+
close if @socket
|
29
|
+
|
30
|
+
@socket = Socket.new(Socket::AF_UNIX, Socket::SOCK_DGRAM)
|
31
|
+
@socket.connect(Socket.pack_sockaddr_un(@socket_path))
|
32
|
+
end
|
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.
|
38
|
+
def send_message(message)
|
39
|
+
connect unless @socket
|
40
|
+
@socket.sendmsg_nonblock(message)
|
41
|
+
rescue Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ENOENT => e
|
42
|
+
# TODO: FIXME: This error should be considered as a retryable error in the
|
43
|
+
# Connection class. An even better solution would be to make BadSocketError inherit
|
44
|
+
# from a specific retryable error class in the Connection class.
|
45
|
+
raise BadSocketError, "#{e.class}: #{e}"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|