dogstatsd-ruby 4.0.0 → 5.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|