dogstatsd-ruby 4.8.2 → 5.2.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 +126 -1
- data/lib/datadog/statsd.rb +91 -55
- data/lib/datadog/statsd/connection.rb +5 -8
- data/lib/datadog/statsd/forwarder.rb +122 -0
- data/lib/datadog/statsd/message_buffer.rb +88 -0
- data/lib/datadog/statsd/sender.rb +117 -0
- data/lib/datadog/statsd/serialization/event_serializer.rb +5 -1
- data/lib/datadog/statsd/serialization/tag_serializer.rb +5 -1
- data/lib/datadog/statsd/single_thread_sender.rb +31 -0
- data/lib/datadog/statsd/telemetry.rb +21 -23
- data/lib/datadog/statsd/udp_connection.rb +3 -3
- data/lib/datadog/statsd/uds_connection.rb +3 -3
- data/lib/datadog/statsd/version.rb +1 -1
- metadata +11 -7
- data/lib/datadog/statsd/batch.rb +0 -56
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9abfaabd9be58a320dd8cc8d1d4998ac287daa08e68ab894054cf2910f5f3133
|
4
|
+
data.tar.gz: 88ebf4ef472f7663897c06fa3e5753a17c12722bc1a58f8aa4e9fb0a3ace07c6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 199527bd5e9d39d94be3cabf28dc5f4d8913c4d3f746a6018a22c9ed13440c34d77fc11b363f47f9e2fd325974005fdc3567a223de4669b388ced99c3cd0750f
|
7
|
+
data.tar.gz: 0da25bafef540e4d36be8cae9c1fbb1f11c6aea94415828941aa9c34877db75166574f41b5e1a0829c71285f958162133d0f592a34619ce066e14c97ea86fd4d
|
data/README.md
CHANGED
@@ -24,15 +24,88 @@ require 'datadog/statsd'
|
|
24
24
|
|
25
25
|
# Create a DogStatsD client instance.
|
26
26
|
statsd = Datadog::Statsd.new('localhost', 8125)
|
27
|
+
...
|
28
|
+
# release resources used by the client instance
|
29
|
+
statsd.close()
|
27
30
|
```
|
28
31
|
Or if you want to connect over Unix Domain Socket:
|
29
32
|
```ruby
|
30
33
|
# Connection over Unix Domain Socket
|
31
34
|
statsd = Datadog::Statsd.new(socket_path: '/path/to/socket/file')
|
35
|
+
...
|
36
|
+
# release resources used by the client instance
|
37
|
+
statsd.close()
|
32
38
|
```
|
33
39
|
|
34
40
|
Find a list of all the available options for your DogStatsD Client in the [DogStatsD-ruby rubydoc](https://www.rubydoc.info/github/DataDog/dogstatsd-ruby/master/Datadog/Statsd) or in the [Datadog public DogStatsD documentation](https://docs.datadoghq.com/developers/dogstatsd/?tab=ruby#client-instantiation-parameters).
|
35
41
|
|
42
|
+
### Migrating from v4.x to v5.x
|
43
|
+
|
44
|
+
If you are already using DogStatsD-ruby v4.x and you want to migrate to a version v5.x, the major
|
45
|
+
change concerning you is the new threading model (please see section Threading model):
|
46
|
+
|
47
|
+
In practice, it means two things:
|
48
|
+
|
49
|
+
1. Now that the client is buffering metrics before sending them, you have to manually
|
50
|
+
call the method `Datadog::Statsd#flush` if you want to force the sending of metrics. Note that the companion thread will automatically flush the buffered metrics if the buffer gets full or when you are closing the instance.
|
51
|
+
|
52
|
+
2. You have to make sure you are either:
|
53
|
+
|
54
|
+
* using singletons instances of the DogStatsD client and not allocating one each time you need one, letting the buffering mechanism flush metrics, it's still a bad solution if the process later forks (see related section below). Or,
|
55
|
+
* properly closing your DogStatsD client instance when it is not needed anymore using the method `Datadog::Statsd#close` to release the resources used by the instance and to close the socket
|
56
|
+
|
57
|
+
If you have issues with the companion thread or the buffering mode, you can instantiate a client that behaves exactly as in v4.x (i.e. no companion thread and flush on every metric submission):
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
# Import the library
|
61
|
+
require 'datadog/statsd'
|
62
|
+
|
63
|
+
# Create a DogStatsD client instance using UDP
|
64
|
+
statsd = Datadog::Statsd.new('localhost', 8125, single_thread: true, buffer_max_payload_size: 1)
|
65
|
+
...
|
66
|
+
# to close the instance is not necessary in this case since metrics are flushed on submission
|
67
|
+
# but it is still a good practice and it explicitely closes the socket
|
68
|
+
statsd.close()
|
69
|
+
```
|
70
|
+
|
71
|
+
or
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# Import the library
|
75
|
+
require 'datadog/statsd'
|
76
|
+
|
77
|
+
# Create a DogStatsD client instance using UDS
|
78
|
+
statsd = Datadog::Statsd.new(socket_path: '/path/to/socket/file', single_thread: true, buffer_max_payload_size: 1)
|
79
|
+
...
|
80
|
+
# to close the instance is not necessary in this case since metrics are flushed on submission
|
81
|
+
# but it is still a good practice and it explicitely closes the socket
|
82
|
+
statsd.close()
|
83
|
+
```
|
84
|
+
|
85
|
+
### v5.x Common Pitfalls
|
86
|
+
|
87
|
+
Version v5.x of `dogstatsd-ruby` is using a companion thread for preemptive flushing, it brings better performances for application having a high-throughput of statsd metrics, but it comes with new pitfalls:
|
88
|
+
|
89
|
+
* Applications forking after having created the dogstatsd instance: forking a process can't duplicate the existing threads, meaning that one of the processes won't have a companion thread to flush the metrics and will lead to missing metrics.
|
90
|
+
* Applications creating a lot of different instances of the client without closing them: it is important to close the instance to free the thread and the socket it is using or it will lead to thread leaks.
|
91
|
+
|
92
|
+
If you are using [Sidekiq](https://github.com/mperham/sidekiq), please make sure to close the client instances that are instantiated. [See this example on using DogStatsD-ruby v5.x with Sidekiq](https://github.com/DataDog/dogstatsd-ruby/blob/master/examples/sidekiq_example.rb).
|
93
|
+
|
94
|
+
If you are using [Puma](https://github.com/puma/puma) or [Unicorn](https://yhbt.net/unicorn.git), please make sure to create the instance of DogStatsD in the workers, not in the main process before it forks to create its workers. See [this comment for more details](https://github.com/DataDog/dogstatsd-ruby/issues/179#issuecomment-845570345).
|
95
|
+
|
96
|
+
Applications that are in these situations but can't apply these recommendations should enable the `single_thread` mode which does not use a companion thread. Here is how to instantiate a client in this mode:
|
97
|
+
|
98
|
+
```ruby
|
99
|
+
# Import the library
|
100
|
+
require 'datadog/statsd'
|
101
|
+
|
102
|
+
# Create a DogStatsD client instance.
|
103
|
+
statsd = Datadog::Statsd.new('localhost', 8125, single_thread: true)
|
104
|
+
...
|
105
|
+
# release resources used by the client instance and flush last metrics
|
106
|
+
statsd.close()
|
107
|
+
```
|
108
|
+
|
36
109
|
### Origin detection over UDP
|
37
110
|
|
38
111
|
Origin detection is a method to detect which pod DogStatsD packets are coming from in order to add the pod's tags to the tag list.
|
@@ -84,9 +157,61 @@ size should be used, you can set it with the parameter `buffer_max_payload_size`
|
|
84
157
|
statsd = Datadog::Statsd.new('localhost', 8125, buffer_max_payload_size: 4096)
|
85
158
|
```
|
86
159
|
|
160
|
+
## Threading model
|
161
|
+
|
162
|
+
On versions greater than 5.0, we changed the threading model of the library so that one instance of `Datadog::Statsd` could be shared between threads and so that the writes in the socket are non blocking.
|
163
|
+
|
164
|
+
When you instantiate a `Datadog::Statsd`, a companion thread is spawned. This thread will be called the Sender thread, as it is modeled by the [Sender](../lib/datadog/statsd/sender.rb) class. Please use `single_thread: true` while creating an instance if you don't want to or can't use a companion thread.
|
165
|
+
|
166
|
+
This thread is stopped when you close the statsd client (`Datadog::Statsd#close`). It also means that allocating a lot of statsd clients without closing them properly when not used anymore
|
167
|
+
could lead to a thread leak (even though they will be sleeping, blocked on IO).
|
168
|
+
The communication between the current thread is managed through a standard Ruby Queue.
|
169
|
+
|
170
|
+
The sender thread has the following logic (Code present in the method `Datadog::Statsd::Sender#send_loop`):
|
171
|
+
|
172
|
+
```
|
173
|
+
while the sender message queue is not closed do
|
174
|
+
read message from sender message queue
|
175
|
+
|
176
|
+
if message is a Control message to flush
|
177
|
+
flush buffer in connection
|
178
|
+
else if message is a Control message to synchronize
|
179
|
+
synchronize with calling thread
|
180
|
+
else
|
181
|
+
add message to the buffer
|
182
|
+
end
|
183
|
+
end while
|
184
|
+
```
|
185
|
+
|
186
|
+
Most of the time, the sender thread is blocked and sleeping when doing a blocking read from the sender message queue.
|
187
|
+
|
188
|
+
We can see that there is 3 different kind of messages:
|
189
|
+
|
190
|
+
* a control message to flush the buffer in the connection
|
191
|
+
* a control message to synchronize any thread with the sender thread
|
192
|
+
* a message to append to the buffer
|
193
|
+
|
194
|
+
There is also an implicit message which is closing the queue as it will stop blocking read from the message queue (if happening) and thus, stop the sender thread.
|
195
|
+
|
196
|
+
### Usual workflow
|
197
|
+
|
198
|
+
You push metrics to the statsd client which writes them quickly to the sender message queue. The sender thread receives those message, buffers them and flushes them to the connection when the buffer limit is reached.
|
199
|
+
|
200
|
+
### Flushing
|
201
|
+
|
202
|
+
When calling a flush, a specific control message (the `:flush` symbol) is sent to the sender thread. When finding it, it flushes its internal buffer into the connection.
|
203
|
+
|
204
|
+
### Rendez-vous
|
205
|
+
|
206
|
+
It is possible to ensure a message has been consumed by the sender thread and written to the buffer by simply calling a rendez-vous right after. This is done when you are doing a synchronized flush (calling `Datadog::Statsd#flush` with the `sync: true` option).
|
207
|
+
|
208
|
+
This means the current thread is going to sleep and wait for a Queue which is given to the sender thread. When the sender thread reads this queue from its own message queue, it puts a placeholder message in it so that it wakes up the calling thread.
|
209
|
+
|
210
|
+
This is useful when closing the application or when checking unit tests.
|
211
|
+
|
87
212
|
## Credits
|
88
213
|
|
89
|
-
dogstatsd-ruby is forked from
|
214
|
+
dogstatsd-ruby is forked from Rein Henrichs [original Statsd
|
90
215
|
client](https://github.com/reinh/statsd).
|
91
216
|
|
92
217
|
Copyright (c) 2011 Rein Henrichs. See LICENSE.txt for
|
data/lib/datadog/statsd.rb
CHANGED
@@ -5,8 +5,11 @@ require_relative 'statsd/version'
|
|
5
5
|
require_relative 'statsd/telemetry'
|
6
6
|
require_relative 'statsd/udp_connection'
|
7
7
|
require_relative 'statsd/uds_connection'
|
8
|
-
require_relative 'statsd/
|
8
|
+
require_relative 'statsd/message_buffer'
|
9
9
|
require_relative 'statsd/serialization'
|
10
|
+
require_relative 'statsd/sender'
|
11
|
+
require_relative 'statsd/single_thread_sender'
|
12
|
+
require_relative 'statsd/forwarder'
|
10
13
|
|
11
14
|
# = Datadog::Statsd: A DogStatsd client (https://www.datadoghq.com)
|
12
15
|
#
|
@@ -26,12 +29,17 @@ require_relative 'statsd/serialization'
|
|
26
29
|
# statsd = Datadog::Statsd.new 'localhost', 8125, tags: 'tag1:true'
|
27
30
|
module Datadog
|
28
31
|
class Statsd
|
32
|
+
class Error < StandardError
|
33
|
+
end
|
34
|
+
|
29
35
|
OK = 0
|
30
36
|
WARNING = 1
|
31
37
|
CRITICAL = 2
|
32
38
|
UNKNOWN = 3
|
33
39
|
|
34
|
-
|
40
|
+
UDP_DEFAULT_BUFFER_SIZE = 1_432
|
41
|
+
UDS_DEFAULT_BUFFER_SIZE = 8_192
|
42
|
+
DEFAULT_BUFFER_POOL_SIZE = Float::INFINITY
|
35
43
|
MAX_EVENT_SIZE = 8 * 1_024
|
36
44
|
# minimum flush interval for the telemetry in seconds
|
37
45
|
DEFAULT_TELEMETRY_FLUSH_INTERVAL = 10
|
@@ -51,67 +59,64 @@ module Datadog
|
|
51
59
|
serializer.global_tags
|
52
60
|
end
|
53
61
|
|
54
|
-
# Buffer containing the statsd message before they are sent in batch
|
55
|
-
attr_reader :buffer
|
56
|
-
|
57
|
-
# Maximum buffer size in bytes before it is flushed
|
58
|
-
attr_reader :max_buffer_bytes
|
59
|
-
|
60
62
|
# Default sample rate
|
61
63
|
attr_reader :sample_rate
|
62
64
|
|
63
|
-
# Connection
|
64
|
-
attr_reader :connection
|
65
|
-
|
66
65
|
# @param [String] host your statsd host
|
67
66
|
# @param [Integer] port your statsd port
|
68
67
|
# @option [String] namespace set a namespace to be prepended to every metric name
|
69
68
|
# @option [Array<String>|Hash] tags tags to be added to every metric
|
70
69
|
# @option [Logger] logger for debugging
|
71
|
-
# @option [Integer]
|
70
|
+
# @option [Integer] buffer_max_payload_size max bytes to buffer
|
71
|
+
# @option [Integer] buffer_max_pool_size max messages to buffer
|
72
72
|
# @option [String] socket_path unix socket path
|
73
73
|
# @option [Float] default sample rate if not overridden
|
74
|
+
# @option [Boolean] single_thread flushes the metrics on the main thread instead of in a companion thread
|
74
75
|
def initialize(
|
75
76
|
host = nil,
|
76
77
|
port = nil,
|
78
|
+
socket_path: nil,
|
79
|
+
|
77
80
|
namespace: nil,
|
78
81
|
tags: nil,
|
79
|
-
max_buffer_bytes: DEFAULT_BUFFER_SIZE,
|
80
|
-
socket_path: nil,
|
81
|
-
logger: nil,
|
82
82
|
sample_rate: nil,
|
83
|
-
|
83
|
+
|
84
|
+
buffer_max_payload_size: nil,
|
85
|
+
buffer_max_pool_size: nil,
|
86
|
+
buffer_overflowing_stategy: :drop,
|
87
|
+
|
88
|
+
logger: nil,
|
89
|
+
|
90
|
+
single_thread: false,
|
91
|
+
|
92
|
+
telemetry_enable: true,
|
84
93
|
telemetry_flush_interval: DEFAULT_TELEMETRY_FLUSH_INTERVAL
|
85
94
|
)
|
86
95
|
unless tags.nil? || tags.is_a?(Array) || tags.is_a?(Hash)
|
87
|
-
raise ArgumentError, 'tags must be
|
96
|
+
raise ArgumentError, 'tags must be an array of string tags or a Hash'
|
88
97
|
end
|
89
98
|
|
90
99
|
@namespace = namespace
|
91
100
|
@prefix = @namespace ? "#{@namespace}.".freeze : nil
|
92
|
-
|
93
101
|
@serializer = Serialization::Serializer.new(prefix: @prefix, global_tags: tags)
|
102
|
+
@sample_rate = sample_rate
|
94
103
|
|
95
|
-
|
104
|
+
@forwarder = Forwarder.new(
|
105
|
+
host: host,
|
106
|
+
port: port,
|
107
|
+
socket_path: socket_path,
|
96
108
|
|
97
|
-
@telemetry = Telemetry.new(disable_telemetry, telemetry_flush_interval,
|
98
109
|
global_tags: tags,
|
99
|
-
|
100
|
-
)
|
101
|
-
|
102
|
-
@connection = case transport_type
|
103
|
-
when :udp
|
104
|
-
UDPConnection.new(host, port, logger, telemetry)
|
105
|
-
when :uds
|
106
|
-
UDSConnection.new(socket_path, logger, telemetry)
|
107
|
-
end
|
110
|
+
logger: logger,
|
108
111
|
|
109
|
-
|
112
|
+
single_thread: single_thread,
|
110
113
|
|
111
|
-
|
114
|
+
buffer_max_payload_size: buffer_max_payload_size,
|
115
|
+
buffer_max_pool_size: buffer_max_pool_size,
|
116
|
+
buffer_overflowing_stategy: buffer_overflowing_stategy,
|
112
117
|
|
113
|
-
|
114
|
-
|
118
|
+
telemetry_flush_interval: telemetry_enable ? telemetry_flush_interval : nil,
|
119
|
+
)
|
115
120
|
end
|
116
121
|
|
117
122
|
# yield a new instance to a block and close it when done
|
@@ -270,9 +275,9 @@ module Datadog
|
|
270
275
|
# @example Report a critical service check status
|
271
276
|
# $statsd.service_check('my.service.check', Statsd::CRITICAL, :tags=>['urgent'])
|
272
277
|
def service_check(name, status, opts = EMPTY_OPTIONS)
|
273
|
-
telemetry.sent(service_checks: 1)
|
278
|
+
telemetry.sent(service_checks: 1) if telemetry
|
274
279
|
|
275
|
-
|
280
|
+
forwarder.send_message(serializer.to_service_check(name, status, opts))
|
276
281
|
end
|
277
282
|
|
278
283
|
# This end point allows you to post events to the stream. You can tag them, set priority and even aggregate them with other events.
|
@@ -290,17 +295,25 @@ module Datadog
|
|
290
295
|
# @option opts [String, nil] :priority ('normal') Can be "normal" or "low"
|
291
296
|
# @option opts [String, nil] :source_type_name (nil) Assign a source type to the event
|
292
297
|
# @option opts [String, nil] :alert_type ('info') Can be "error", "warning", "info" or "success".
|
298
|
+
# @option opts [Boolean, false] :truncate_if_too_long (false) Truncate the event if it is too long
|
293
299
|
# @option opts [Array<String>] :tags tags to be added to every metric
|
294
300
|
# @example Report an awful event:
|
295
301
|
# $statsd.event('Something terrible happened', 'The end is near if we do nothing', :alert_type=>'warning', :tags=>['end_of_times','urgent'])
|
296
302
|
def event(title, text, opts = EMPTY_OPTIONS)
|
297
|
-
telemetry.sent(events: 1)
|
303
|
+
telemetry.sent(events: 1) if telemetry
|
298
304
|
|
299
|
-
|
305
|
+
forwarder.send_message(serializer.to_event(title, text, opts))
|
300
306
|
end
|
301
307
|
|
302
|
-
# Send several metrics in the same
|
303
|
-
# They will be buffered and flushed when the block finishes
|
308
|
+
# Send several metrics in the same packet.
|
309
|
+
# They will be buffered and flushed when the block finishes.
|
310
|
+
#
|
311
|
+
# This method exists for compatibility with v4.x versions, it is not needed
|
312
|
+
# anymore since the batching is now automatically done internally.
|
313
|
+
# It also means that an automatic flush could occur if the buffer is filled
|
314
|
+
# during the execution of the batch block.
|
315
|
+
#
|
316
|
+
# This method is DEPRECATED and will be removed in future v6.x API.
|
304
317
|
#
|
305
318
|
# @example Send several metrics in one packet:
|
306
319
|
# $statsd.batch do |s|
|
@@ -308,19 +321,50 @@ module Datadog
|
|
308
321
|
# s.increment('page.views')
|
309
322
|
# end
|
310
323
|
def batch
|
311
|
-
|
312
|
-
|
313
|
-
end
|
324
|
+
yield self
|
325
|
+
flush(sync: true)
|
314
326
|
end
|
315
327
|
|
316
328
|
# Close the underlying socket
|
317
|
-
|
318
|
-
|
329
|
+
#
|
330
|
+
# @param [Boolean, true] flush Should we flush the metrics before closing
|
331
|
+
def close(flush: true)
|
332
|
+
flush(sync: true) if flush
|
333
|
+
forwarder.close
|
334
|
+
end
|
335
|
+
|
336
|
+
def sync_with_outbound_io
|
337
|
+
forwarder.sync_with_outbound_io
|
338
|
+
end
|
339
|
+
|
340
|
+
# Flush the buffer into the connection
|
341
|
+
def flush(flush_telemetry: false, sync: false)
|
342
|
+
forwarder.flush(flush_telemetry: flush_telemetry, sync: sync)
|
343
|
+
end
|
344
|
+
|
345
|
+
def telemetry
|
346
|
+
forwarder.telemetry
|
347
|
+
end
|
348
|
+
|
349
|
+
def host
|
350
|
+
forwarder.host
|
351
|
+
end
|
352
|
+
|
353
|
+
def port
|
354
|
+
forwarder.port
|
355
|
+
end
|
356
|
+
|
357
|
+
def socket_path
|
358
|
+
forwarder.socket_path
|
359
|
+
end
|
360
|
+
|
361
|
+
def transport_type
|
362
|
+
forwarder.transport_type
|
319
363
|
end
|
320
364
|
|
321
365
|
private
|
322
366
|
attr_reader :serializer
|
323
|
-
attr_reader :
|
367
|
+
attr_reader :forwarder
|
324
368
|
|
325
369
|
PROCESS_TIME_SUPPORTED = (RUBY_VERSION >= '2.1.0')
|
326
370
|
EMPTY_OPTIONS = {}.freeze
|
@@ -336,22 +380,14 @@ module Datadog
|
|
336
380
|
end
|
337
381
|
|
338
382
|
def send_stats(stat, delta, type, opts = EMPTY_OPTIONS)
|
339
|
-
telemetry.sent(metrics: 1)
|
383
|
+
telemetry.sent(metrics: 1) if telemetry
|
340
384
|
|
341
385
|
sample_rate = opts[:sample_rate] || @sample_rate || 1
|
342
386
|
|
343
387
|
if sample_rate == 1 || rand <= sample_rate
|
344
388
|
full_stat = serializer.to_stat(stat, delta, type, tags: opts[:tags], sample_rate: sample_rate)
|
345
389
|
|
346
|
-
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
def send_stat(message)
|
351
|
-
if @batch.open?
|
352
|
-
@batch.add(message)
|
353
|
-
else
|
354
|
-
@connection.write(message)
|
390
|
+
forwarder.send_message(full_stat)
|
355
391
|
end
|
356
392
|
end
|
357
393
|
end
|
@@ -3,8 +3,9 @@
|
|
3
3
|
module Datadog
|
4
4
|
class Statsd
|
5
5
|
class Connection
|
6
|
-
def initialize(telemetry)
|
6
|
+
def initialize(telemetry: nil, logger: nil)
|
7
7
|
@telemetry = telemetry
|
8
|
+
@logger = logger
|
8
9
|
end
|
9
10
|
|
10
11
|
# Close the underlying socket
|
@@ -20,15 +21,11 @@ module Datadog
|
|
20
21
|
def write(payload)
|
21
22
|
logger.debug { "Statsd: #{payload}" } if logger
|
22
23
|
|
23
|
-
flush_telemetry = telemetry.flush?
|
24
|
-
|
25
|
-
payload += telemetry.flush if flush_telemetry
|
26
|
-
|
27
24
|
send_message(payload)
|
28
25
|
|
29
|
-
telemetry.
|
26
|
+
telemetry.sent(packets: 1, bytes: payload.length) if telemetry
|
30
27
|
|
31
|
-
|
28
|
+
true
|
32
29
|
rescue StandardError => boom
|
33
30
|
# Try once to reconnect if the socket has been closed
|
34
31
|
retries ||= 1
|
@@ -45,7 +42,7 @@ module Datadog
|
|
45
42
|
end
|
46
43
|
end
|
47
44
|
|
48
|
-
telemetry.dropped(packets: 1, bytes: payload.length)
|
45
|
+
telemetry.dropped(packets: 1, bytes: payload.length) if telemetry
|
49
46
|
logger.error { "Statsd: #{boom.class} #{boom}" } if logger
|
50
47
|
nil
|
51
48
|
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class Forwarder
|
6
|
+
attr_reader :telemetry
|
7
|
+
attr_reader :transport_type
|
8
|
+
|
9
|
+
def initialize(
|
10
|
+
host: nil,
|
11
|
+
port: nil,
|
12
|
+
socket_path: nil,
|
13
|
+
|
14
|
+
buffer_max_payload_size: nil,
|
15
|
+
buffer_max_pool_size: nil,
|
16
|
+
buffer_overflowing_stategy: :drop,
|
17
|
+
|
18
|
+
telemetry_flush_interval: nil,
|
19
|
+
global_tags: [],
|
20
|
+
|
21
|
+
single_thread: false,
|
22
|
+
|
23
|
+
logger: nil
|
24
|
+
)
|
25
|
+
@transport_type = socket_path.nil? ? :udp : :uds
|
26
|
+
|
27
|
+
if telemetry_flush_interval
|
28
|
+
@telemetry = Telemetry.new(telemetry_flush_interval,
|
29
|
+
global_tags: global_tags,
|
30
|
+
transport_type: transport_type
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
@connection = case transport_type
|
35
|
+
when :udp
|
36
|
+
UDPConnection.new(host, port, logger: logger, telemetry: telemetry)
|
37
|
+
when :uds
|
38
|
+
UDSConnection.new(socket_path, logger: logger, telemetry: telemetry)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Initialize buffer
|
42
|
+
buffer_max_payload_size ||= (transport_type == :udp ? UDP_DEFAULT_BUFFER_SIZE : UDS_DEFAULT_BUFFER_SIZE)
|
43
|
+
|
44
|
+
if buffer_max_payload_size <= 0
|
45
|
+
raise ArgumentError, 'buffer_max_payload_size cannot be <= 0'
|
46
|
+
end
|
47
|
+
|
48
|
+
unless telemetry.nil? || telemetry.would_fit_in?(buffer_max_payload_size)
|
49
|
+
raise ArgumentError, "buffer_max_payload_size is not high enough to use telemetry (tags=(#{global_tags.inspect}))"
|
50
|
+
end
|
51
|
+
|
52
|
+
@buffer = MessageBuffer.new(@connection,
|
53
|
+
max_payload_size: buffer_max_payload_size,
|
54
|
+
max_pool_size: buffer_max_pool_size || DEFAULT_BUFFER_POOL_SIZE,
|
55
|
+
overflowing_stategy: buffer_overflowing_stategy,
|
56
|
+
)
|
57
|
+
|
58
|
+
@sender = single_thread ? SingleThreadSender.new(buffer) : Sender.new(buffer)
|
59
|
+
@sender.start
|
60
|
+
end
|
61
|
+
|
62
|
+
def send_message(message)
|
63
|
+
sender.add(message)
|
64
|
+
|
65
|
+
tick_telemetry
|
66
|
+
end
|
67
|
+
|
68
|
+
def sync_with_outbound_io
|
69
|
+
sender.rendez_vous
|
70
|
+
end
|
71
|
+
|
72
|
+
def flush(flush_telemetry: false, sync: false)
|
73
|
+
do_flush_telemetry if telemetry && flush_telemetry
|
74
|
+
|
75
|
+
sender.flush(sync: sync)
|
76
|
+
end
|
77
|
+
|
78
|
+
def host
|
79
|
+
return nil unless transport_type == :udp
|
80
|
+
|
81
|
+
connection.host
|
82
|
+
end
|
83
|
+
|
84
|
+
def port
|
85
|
+
return nil unless transport_type == :udp
|
86
|
+
|
87
|
+
connection.port
|
88
|
+
end
|
89
|
+
|
90
|
+
def socket_path
|
91
|
+
return nil unless transport_type == :uds
|
92
|
+
|
93
|
+
connection.socket_path
|
94
|
+
end
|
95
|
+
|
96
|
+
def close
|
97
|
+
sender.stop
|
98
|
+
connection.close
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
attr_reader :buffer
|
103
|
+
attr_reader :sender
|
104
|
+
attr_reader :connection
|
105
|
+
|
106
|
+
def do_flush_telemetry
|
107
|
+
telemetry_snapshot = telemetry.flush
|
108
|
+
telemetry.reset
|
109
|
+
|
110
|
+
telemetry_snapshot.each do |message|
|
111
|
+
sender.add(message)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def tick_telemetry
|
116
|
+
return nil unless telemetry
|
117
|
+
|
118
|
+
do_flush_telemetry if telemetry.should_flush?
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class MessageBuffer
|
6
|
+
PAYLOAD_SIZE_TOLERANCE = 0.05
|
7
|
+
|
8
|
+
def initialize(connection,
|
9
|
+
max_payload_size: nil,
|
10
|
+
max_pool_size: DEFAULT_BUFFER_POOL_SIZE,
|
11
|
+
overflowing_stategy: :drop
|
12
|
+
)
|
13
|
+
raise ArgumentError, 'max_payload_size keyword argument must be provided' unless max_payload_size
|
14
|
+
raise ArgumentError, 'max_pool_size keyword argument must be provided' unless max_pool_size
|
15
|
+
|
16
|
+
@connection = connection
|
17
|
+
@max_payload_size = max_payload_size
|
18
|
+
@max_pool_size = max_pool_size
|
19
|
+
@overflowing_stategy = overflowing_stategy
|
20
|
+
|
21
|
+
@buffer = String.new
|
22
|
+
@message_count = 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def add(message)
|
26
|
+
message_size = message.bytesize
|
27
|
+
|
28
|
+
return nil unless message_size > 0 # to avoid adding empty messages to the buffer
|
29
|
+
return nil unless ensure_sendable!(message_size)
|
30
|
+
|
31
|
+
flush if should_flush?(message_size)
|
32
|
+
|
33
|
+
buffer << "\n" unless buffer.empty?
|
34
|
+
buffer << message
|
35
|
+
|
36
|
+
@message_count += 1
|
37
|
+
|
38
|
+
# flush when we're pretty sure that we won't be able
|
39
|
+
# to add another message to the buffer
|
40
|
+
flush if preemptive_flush?
|
41
|
+
|
42
|
+
true
|
43
|
+
end
|
44
|
+
|
45
|
+
def flush
|
46
|
+
return if buffer.empty?
|
47
|
+
|
48
|
+
connection.write(buffer)
|
49
|
+
|
50
|
+
buffer.clear
|
51
|
+
@message_count = 0
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
attr :max_payload_size
|
56
|
+
attr :max_pool_size
|
57
|
+
|
58
|
+
attr :overflowing_stategy
|
59
|
+
|
60
|
+
attr :connection
|
61
|
+
attr :buffer
|
62
|
+
|
63
|
+
def should_flush?(message_size)
|
64
|
+
return true if buffer.bytesize + 1 + message_size >= max_payload_size
|
65
|
+
|
66
|
+
false
|
67
|
+
end
|
68
|
+
|
69
|
+
def preemptive_flush?
|
70
|
+
@message_count == max_pool_size || buffer.bytesize > bytesize_threshold
|
71
|
+
end
|
72
|
+
|
73
|
+
def ensure_sendable!(message_size)
|
74
|
+
return true if message_size <= max_payload_size
|
75
|
+
|
76
|
+
if overflowing_stategy == :raise
|
77
|
+
raise Error, 'Message too big for payload limit'
|
78
|
+
end
|
79
|
+
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
def bytesize_threshold
|
84
|
+
@bytesize_threshold ||= (max_payload_size - PAYLOAD_SIZE_TOLERANCE * max_payload_size).to_i
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class Sender
|
6
|
+
CLOSEABLE_QUEUES = Queue.instance_methods.include?(:close)
|
7
|
+
|
8
|
+
def initialize(message_buffer)
|
9
|
+
@message_buffer = message_buffer
|
10
|
+
end
|
11
|
+
|
12
|
+
def flush(sync: false)
|
13
|
+
# don't try to flush if there is no message_queue instantiated
|
14
|
+
return unless message_queue
|
15
|
+
|
16
|
+
message_queue.push(:flush)
|
17
|
+
|
18
|
+
rendez_vous if sync
|
19
|
+
end
|
20
|
+
|
21
|
+
def rendez_vous
|
22
|
+
# Initialize and get the thread's sync queue
|
23
|
+
queue = (Thread.current[:statsd_sync_queue] ||= Queue.new)
|
24
|
+
# tell sender-thread to notify us in the current
|
25
|
+
# thread's queue
|
26
|
+
message_queue.push(queue)
|
27
|
+
# wait for the sender thread to send a message
|
28
|
+
# once the flush is done
|
29
|
+
queue.pop
|
30
|
+
end
|
31
|
+
|
32
|
+
def add(message)
|
33
|
+
raise ArgumentError, 'Start sender first' unless message_queue
|
34
|
+
|
35
|
+
message_queue << message
|
36
|
+
end
|
37
|
+
|
38
|
+
def start
|
39
|
+
raise ArgumentError, 'Sender already started' if message_queue
|
40
|
+
|
41
|
+
# initialize message queue for background thread
|
42
|
+
@message_queue = Queue.new
|
43
|
+
# start background thread
|
44
|
+
@sender_thread = Thread.new(&method(:send_loop))
|
45
|
+
end
|
46
|
+
|
47
|
+
if CLOSEABLE_QUEUES
|
48
|
+
def stop(join_worker: true)
|
49
|
+
message_queue = @message_queue
|
50
|
+
message_queue.close if message_queue
|
51
|
+
|
52
|
+
sender_thread = @sender_thread
|
53
|
+
sender_thread.join if sender_thread && join_worker
|
54
|
+
end
|
55
|
+
else
|
56
|
+
def stop(join_worker: true)
|
57
|
+
message_queue = @message_queue
|
58
|
+
message_queue << :close if message_queue
|
59
|
+
|
60
|
+
sender_thread = @sender_thread
|
61
|
+
sender_thread.join if sender_thread && join_worker
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :message_buffer
|
68
|
+
|
69
|
+
attr_reader :message_queue
|
70
|
+
attr_reader :sender_thread
|
71
|
+
|
72
|
+
if CLOSEABLE_QUEUES
|
73
|
+
def send_loop
|
74
|
+
until (message = message_queue.pop).nil? && message_queue.closed?
|
75
|
+
# skip if message is nil, e.g. when message_queue
|
76
|
+
# is empty and closed
|
77
|
+
next unless message
|
78
|
+
|
79
|
+
case message
|
80
|
+
when :flush
|
81
|
+
message_buffer.flush
|
82
|
+
when Queue
|
83
|
+
message.push(:go_on)
|
84
|
+
else
|
85
|
+
message_buffer.add(message)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
@message_queue = nil
|
90
|
+
@sender_thread = nil
|
91
|
+
end
|
92
|
+
else
|
93
|
+
def send_loop
|
94
|
+
loop do
|
95
|
+
message = message_queue.pop
|
96
|
+
|
97
|
+
next unless message
|
98
|
+
|
99
|
+
case message
|
100
|
+
when :close
|
101
|
+
break
|
102
|
+
when :flush
|
103
|
+
message_buffer.flush
|
104
|
+
when Queue
|
105
|
+
message.push(:go_on)
|
106
|
+
else
|
107
|
+
message_buffer.add(message)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
@message_queue = nil
|
112
|
+
@sender_thread = nil
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
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
|
@@ -13,7 +13,11 @@ module Datadog
|
|
13
13
|
|
14
14
|
# Convert to tag list and set
|
15
15
|
@global_tags = to_tags_list(global_tags)
|
16
|
-
|
16
|
+
if @global_tags.any?
|
17
|
+
@global_tags_formatted = @global_tags.join(',')
|
18
|
+
else
|
19
|
+
@global_tags_formatted = nil
|
20
|
+
end
|
17
21
|
end
|
18
22
|
|
19
23
|
def format(message_tags)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Datadog
|
4
|
+
class Statsd
|
5
|
+
class SingleThreadSender
|
6
|
+
def initialize(message_buffer)
|
7
|
+
@message_buffer = message_buffer
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(message)
|
11
|
+
@message_buffer.add(message)
|
12
|
+
end
|
13
|
+
|
14
|
+
def flush(*)
|
15
|
+
@message_buffer.flush()
|
16
|
+
end
|
17
|
+
|
18
|
+
# Compatibility with `Sender`
|
19
|
+
def start()
|
20
|
+
end
|
21
|
+
|
22
|
+
# Compatibility with `Sender`
|
23
|
+
def stop()
|
24
|
+
end
|
25
|
+
|
26
|
+
# Compatibility with `Sender`
|
27
|
+
def rendez_vous()
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -11,10 +11,11 @@ module Datadog
|
|
11
11
|
attr_reader :bytes_dropped
|
12
12
|
attr_reader :packets_sent
|
13
13
|
attr_reader :packets_dropped
|
14
|
-
attr_reader :estimate_max_size
|
15
14
|
|
16
|
-
|
17
|
-
|
15
|
+
# Rough estimation of maximum telemetry message size without tags
|
16
|
+
MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS = 50 # bytes
|
17
|
+
|
18
|
+
def initialize(flush_interval, global_tags: [], transport_type: :udp)
|
18
19
|
@flush_interval = flush_interval
|
19
20
|
@global_tags = global_tags
|
20
21
|
@transport_type = transport_type
|
@@ -27,15 +28,10 @@ module Datadog
|
|
27
28
|
client_version: VERSION,
|
28
29
|
client_transport: transport_type,
|
29
30
|
).format(global_tags)
|
31
|
+
end
|
30
32
|
|
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
|
33
|
+
def would_fit_in?(max_buffer_payload_size)
|
34
|
+
MAX_TELEMETRY_MESSAGE_SIZE_WT_TAGS + serialized_tags.size < max_buffer_payload_size
|
39
35
|
end
|
40
36
|
|
41
37
|
def reset
|
@@ -63,27 +59,29 @@ module Datadog
|
|
63
59
|
@packets_dropped += packets
|
64
60
|
end
|
65
61
|
|
66
|
-
def
|
62
|
+
def should_flush?
|
67
63
|
@next_flush_time < now_in_s
|
68
64
|
end
|
69
65
|
|
70
66
|
def flush
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
datadog.dogstatsd.client.packets_sent:#{@packets_sent}|#{COUNTER_TYPE}|##{serialized_tags}
|
81
|
-
datadog.dogstatsd.client.packets_dropped:#{@packets_dropped}|#{COUNTER_TYPE}|##{serialized_tags})
|
67
|
+
[
|
68
|
+
sprintf(pattern, 'metrics', @metrics),
|
69
|
+
sprintf(pattern, 'events', @events),
|
70
|
+
sprintf(pattern, 'service_checks', @service_checks),
|
71
|
+
sprintf(pattern, 'bytes_sent', @bytes_sent),
|
72
|
+
sprintf(pattern, 'bytes_dropped', @bytes_dropped),
|
73
|
+
sprintf(pattern, 'packets_sent', @packets_sent),
|
74
|
+
sprintf(pattern, 'packets_dropped', @packets_dropped),
|
75
|
+
]
|
82
76
|
end
|
83
77
|
|
84
78
|
private
|
85
79
|
attr_reader :serialized_tags
|
86
80
|
|
81
|
+
def pattern
|
82
|
+
@pattern ||= "datadog.dogstatsd.client.%s:%d|#{COUNTER_TYPE}|##{serialized_tags}"
|
83
|
+
end
|
84
|
+
|
87
85
|
if Kernel.const_defined?('Process') && Process.respond_to?(:clock_gettime)
|
88
86
|
def now_in_s
|
89
87
|
Process.clock_gettime(Process::CLOCK_MONOTONIC, :second)
|
@@ -14,11 +14,11 @@ module Datadog
|
|
14
14
|
# StatsD port. Defaults to 8125.
|
15
15
|
attr_reader :port
|
16
16
|
|
17
|
-
def initialize(host, port,
|
18
|
-
super(
|
17
|
+
def initialize(host, port, **kwargs)
|
18
|
+
super(**kwargs)
|
19
|
+
|
19
20
|
@host = host || ENV.fetch('DD_AGENT_HOST', DEFAULT_HOST)
|
20
21
|
@port = port || ENV.fetch('DD_DOGSTATSD_PORT', DEFAULT_PORT).to_i
|
21
|
-
@logger = logger
|
22
22
|
end
|
23
23
|
|
24
24
|
private
|
@@ -10,10 +10,10 @@ 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
|
-
@logger = logger
|
17
17
|
end
|
18
18
|
|
19
19
|
private
|
metadata
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dogstatsd-ruby
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rein Henrichs
|
8
|
+
- Karim Bogtob
|
8
9
|
autorequire:
|
9
10
|
bindir: bin
|
10
11
|
cert_chain: []
|
11
|
-
date:
|
12
|
+
date: 2021-07-01 00:00:00.000000000 Z
|
12
13
|
dependencies: []
|
13
|
-
description: A Ruby
|
14
|
+
description: A Ruby DogStatsd client
|
14
15
|
email: code@datadoghq.com
|
15
16
|
executables: []
|
16
17
|
extensions: []
|
@@ -21,14 +22,17 @@ files:
|
|
21
22
|
- LICENSE.txt
|
22
23
|
- README.md
|
23
24
|
- lib/datadog/statsd.rb
|
24
|
-
- lib/datadog/statsd/batch.rb
|
25
25
|
- lib/datadog/statsd/connection.rb
|
26
|
+
- lib/datadog/statsd/forwarder.rb
|
27
|
+
- lib/datadog/statsd/message_buffer.rb
|
28
|
+
- lib/datadog/statsd/sender.rb
|
26
29
|
- lib/datadog/statsd/serialization.rb
|
27
30
|
- lib/datadog/statsd/serialization/event_serializer.rb
|
28
31
|
- lib/datadog/statsd/serialization/serializer.rb
|
29
32
|
- lib/datadog/statsd/serialization/service_check_serializer.rb
|
30
33
|
- lib/datadog/statsd/serialization/stat_serializer.rb
|
31
34
|
- lib/datadog/statsd/serialization/tag_serializer.rb
|
35
|
+
- lib/datadog/statsd/single_thread_sender.rb
|
32
36
|
- lib/datadog/statsd/telemetry.rb
|
33
37
|
- lib/datadog/statsd/udp_connection.rb
|
34
38
|
- lib/datadog/statsd/uds_connection.rb
|
@@ -38,9 +42,9 @@ licenses:
|
|
38
42
|
- MIT
|
39
43
|
metadata:
|
40
44
|
bug_tracker_uri: https://github.com/DataDog/dogstatsd-ruby/issues
|
41
|
-
changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/
|
42
|
-
documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/
|
43
|
-
source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/
|
45
|
+
changelog_uri: https://github.com/DataDog/dogstatsd-ruby/blob/v5.2.0/CHANGELOG.md
|
46
|
+
documentation_uri: https://www.rubydoc.info/gems/dogstatsd-ruby/5.2.0
|
47
|
+
source_code_uri: https://github.com/DataDog/dogstatsd-ruby/tree/v5.2.0
|
44
48
|
post_install_message:
|
45
49
|
rdoc_options: []
|
46
50
|
require_paths:
|
data/lib/datadog/statsd/batch.rb
DELETED
@@ -1,56 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Datadog
|
4
|
-
class Statsd
|
5
|
-
class Batch
|
6
|
-
def initialize(connection, max_buffer_bytes)
|
7
|
-
@connection = connection
|
8
|
-
@max_buffer_bytes = max_buffer_bytes
|
9
|
-
@depth = 0
|
10
|
-
reset
|
11
|
-
end
|
12
|
-
|
13
|
-
def open
|
14
|
-
@depth += 1
|
15
|
-
|
16
|
-
yield
|
17
|
-
ensure
|
18
|
-
@depth -= 1
|
19
|
-
flush if !open?
|
20
|
-
end
|
21
|
-
|
22
|
-
def open?
|
23
|
-
@depth > 0
|
24
|
-
end
|
25
|
-
|
26
|
-
def add(message)
|
27
|
-
message_bytes = message.bytesize
|
28
|
-
|
29
|
-
unless @buffer_bytes == 0
|
30
|
-
if @buffer_bytes + 1 + message_bytes >= @max_buffer_bytes
|
31
|
-
flush
|
32
|
-
else
|
33
|
-
@buffer << "\n"
|
34
|
-
@buffer_bytes += 1
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
@buffer << message
|
39
|
-
@buffer_bytes += message_bytes
|
40
|
-
end
|
41
|
-
|
42
|
-
def flush
|
43
|
-
return if @buffer_bytes == 0
|
44
|
-
@connection.write(@buffer)
|
45
|
-
reset
|
46
|
-
end
|
47
|
-
|
48
|
-
private
|
49
|
-
|
50
|
-
def reset
|
51
|
-
@buffer = String.new
|
52
|
-
@buffer_bytes = 0
|
53
|
-
end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
end
|