dogstatsd-ruby 3.3.0 → 5.6.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,48 @@
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
+ family = Addrinfo.udp(host, port).afamily
33
+
34
+ @socket = UDPSocket.new(family)
35
+ @socket.connect(host, port)
36
+ end
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.
42
+ def send_message(message)
43
+ connect unless @socket
44
+ @socket.send(message, 0)
45
+ end
46
+ end
47
+ end
48
+ 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'connection'
4
+
5
+ module Datadog
6
+ class Statsd
7
+ VERSION = '5.6.1'
8
+ end
9
+ end