statsd-instrument 2.5.1 → 2.6.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/.rubocop-https---shopify-github-io-ruby-style-guide-rubocop-yml +1 -1
- data/.rubocop.yml +11 -6
- data/.yardopts +5 -0
- data/CHANGELOG.md +75 -6
- data/README.md +54 -46
- data/benchmark/datagram-client +41 -0
- data/lib/statsd/instrument/assertions.rb +168 -57
- data/lib/statsd/instrument/backends/udp_backend.rb +20 -29
- data/lib/statsd/instrument/capture_sink.rb +27 -0
- data/lib/statsd/instrument/client.rb +313 -0
- data/lib/statsd/instrument/datagram.rb +75 -0
- data/lib/statsd/instrument/datagram_builder.rb +101 -0
- data/lib/statsd/instrument/dogstatsd_datagram_builder.rb +71 -0
- data/lib/statsd/instrument/environment.rb +106 -29
- data/lib/statsd/instrument/log_sink.rb +24 -0
- data/lib/statsd/instrument/null_sink.rb +13 -0
- data/lib/statsd/instrument/rubocop/measure_as_dist_argument.rb +39 -0
- data/lib/statsd/instrument/rubocop/metaprogramming_positional_arguments.rb +6 -10
- data/lib/statsd/instrument/rubocop/metric_prefix_argument.rb +37 -0
- data/lib/statsd/instrument/rubocop/metric_return_value.rb +7 -6
- data/lib/statsd/instrument/rubocop/metric_value_keyword_argument.rb +11 -20
- data/lib/statsd/instrument/rubocop/positional_arguments.rb +13 -13
- data/lib/statsd/instrument/rubocop/splat_arguments.rb +8 -14
- data/lib/statsd/instrument/rubocop.rb +64 -0
- data/lib/statsd/instrument/statsd_datagram_builder.rb +14 -0
- data/lib/statsd/instrument/strict.rb +112 -22
- data/lib/statsd/instrument/udp_sink.rb +62 -0
- data/lib/statsd/instrument/version.rb +1 -1
- data/lib/statsd/instrument.rb +191 -100
- data/test/assertions_test.rb +139 -176
- data/test/capture_sink_test.rb +44 -0
- data/test/client_test.rb +164 -0
- data/test/compatibility/dogstatsd_datagram_compatibility_test.rb +162 -0
- data/test/datagram_builder_test.rb +120 -0
- data/test/deprecations_test.rb +56 -10
- data/test/dogstatsd_datagram_builder_test.rb +32 -0
- data/test/environment_test.rb +73 -7
- data/test/log_sink_test.rb +37 -0
- data/test/null_sink_test.rb +13 -0
- data/test/rubocop/measure_as_dist_argument_test.rb +44 -0
- data/test/rubocop/metaprogramming_positional_arguments_test.rb +1 -1
- data/test/rubocop/metric_prefix_argument_test.rb +38 -0
- data/test/rubocop/metric_return_value_test.rb +1 -1
- data/test/rubocop/metric_value_keyword_argument_test.rb +1 -1
- data/test/rubocop/positional_arguments_test.rb +1 -1
- data/test/rubocop/splat_arguments_test.rb +1 -1
- data/test/statsd_datagram_builder_test.rb +22 -0
- data/test/statsd_instrumentation_test.rb +0 -24
- data/test/statsd_test.rb +0 -23
- data/test/test_helper.rb +0 -2
- data/test/udp_backend_test.rb +25 -8
- data/test/udp_sink_test.rb +85 -0
- metadata +38 -2
@@ -7,23 +7,10 @@ module StatsD::Instrument::Backends
|
|
7
7
|
BASE_SUPPORTED_METRIC_TYPES = { c: true, ms: true, g: true, s: true }
|
8
8
|
|
9
9
|
class DogStatsDProtocol
|
10
|
-
EVENT_OPTIONS = {
|
11
|
-
date_happened: 'd',
|
12
|
-
hostname: 'h',
|
13
|
-
aggregation_key: 'k',
|
14
|
-
priority: 'p',
|
15
|
-
source_type_name: 's',
|
16
|
-
alert_type: 't',
|
17
|
-
}
|
18
|
-
|
19
|
-
SERVICE_CHECK_OPTIONS = {
|
20
|
-
timestamp: 'd',
|
21
|
-
hostname: 'h',
|
22
|
-
message: 'm',
|
23
|
-
}
|
24
|
-
|
25
10
|
SUPPORTED_METRIC_TYPES = BASE_SUPPORTED_METRIC_TYPES.merge(h: true, _e: true, _sc: true, d: true)
|
26
11
|
|
12
|
+
SERVICE_CHECK_STATUSES = { ok: 0, warning: 1, critical: 2, unknown: 3 }
|
13
|
+
|
27
14
|
def generate_packet(metric)
|
28
15
|
packet = +""
|
29
16
|
|
@@ -32,27 +19,31 @@ module StatsD::Instrument::Backends
|
|
32
19
|
escaped_text = metric.value.gsub("\n", "\\n")
|
33
20
|
|
34
21
|
packet << "_e{#{escaped_title.size},#{escaped_text.size}}:#{escaped_title}|#{escaped_text}"
|
35
|
-
packet <<
|
22
|
+
packet << "|h:#{metric.metadata[:hostname]}" if metric.metadata[:hostname]
|
23
|
+
packet << "|d:#{metric.metadata[:timestamp].to_i}" if metric.metadata[:timestamp]
|
24
|
+
packet << "|k:#{metric.metadata[:aggregation_key]}" if metric.metadata[:aggregation_key]
|
25
|
+
packet << "|p:#{metric.metadata[:priority]}" if metric.metadata[:priority]
|
26
|
+
packet << "|s:#{metric.metadata[:source_type_name]}" if metric.metadata[:source_type_name]
|
27
|
+
packet << "|t:#{metric.metadata[:alert_type]}" if metric.metadata[:alert_type]
|
28
|
+
packet << "|##{metric.tags.join(',')}" if metric.tags
|
29
|
+
|
36
30
|
elsif metric.type == :_sc
|
37
|
-
|
38
|
-
|
31
|
+
status = metric.value.is_a?(Integer) ? metric.value : SERVICE_CHECK_STATUSES.fetch(metric.value.to_sym)
|
32
|
+
|
33
|
+
packet << "_sc|#{metric.name}|#{status}"
|
34
|
+
packet << "|h:#{metric.metadata[:hostname]}" if metric.metadata[:hostname]
|
35
|
+
packet << "|d:#{metric.metadata[:timestamp].to_i}" if metric.metadata[:timestamp]
|
36
|
+
packet << "|##{metric.tags.join(',')}" if metric.tags
|
37
|
+
packet << "|m:#{metric.metadata[:message]}" if metric.metadata[:message]
|
38
|
+
|
39
39
|
else
|
40
40
|
packet << "#{metric.name}:#{metric.value}|#{metric.type}"
|
41
|
+
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
42
|
+
packet << "|##{metric.tags.join(',')}" if metric.tags
|
41
43
|
end
|
42
44
|
|
43
|
-
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
44
|
-
packet << "|##{metric.tags.join(',')}" if metric.tags
|
45
|
-
|
46
45
|
packet
|
47
46
|
end
|
48
|
-
|
49
|
-
private
|
50
|
-
|
51
|
-
def generate_metadata(metric, options)
|
52
|
-
(metric.metadata.keys & options.keys).map do |key|
|
53
|
-
"|#{options[key]}:#{metric.metadata[key]}"
|
54
|
-
end.join
|
55
|
-
end
|
56
47
|
end
|
57
48
|
|
58
49
|
class StatsiteStatsDProtocol
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @note This class is part of the new Client implementation that is intended
|
4
|
+
# to become the new default in the next major release of this library.
|
5
|
+
class StatsD::Instrument::CaptureSink
|
6
|
+
attr_reader :parent, :datagrams, :datagram_class
|
7
|
+
|
8
|
+
def initialize(parent:, datagram_class: StatsD::Instrument::Datagram)
|
9
|
+
@parent = parent
|
10
|
+
@datagram_class = datagram_class
|
11
|
+
@datagrams = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def sample?(_sample_rate)
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
def <<(datagram)
|
19
|
+
@datagrams << datagram_class.new(datagram)
|
20
|
+
parent << datagram
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
def clear
|
25
|
+
@datagrams.clear
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,313 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'statsd/instrument/datagram'
|
4
|
+
require 'statsd/instrument/datagram_builder'
|
5
|
+
require 'statsd/instrument/statsd_datagram_builder'
|
6
|
+
require 'statsd/instrument/dogstatsd_datagram_builder'
|
7
|
+
require 'statsd/instrument/null_sink'
|
8
|
+
require 'statsd/instrument/udp_sink'
|
9
|
+
require 'statsd/instrument/capture_sink'
|
10
|
+
require 'statsd/instrument/log_sink'
|
11
|
+
|
12
|
+
# The Client is the main interface for using StatsD.
|
13
|
+
#
|
14
|
+
# @note This new new Client implementation that is intended to become the new default in
|
15
|
+
# the next major release of this library. While this class may already be functional,
|
16
|
+
# we provide no guarantees about the API and the behavior may change.
|
17
|
+
class StatsD::Instrument::Client
|
18
|
+
attr_reader :sink, :datagram_builder_class, :prefix, :default_tags, :default_sample_rate
|
19
|
+
|
20
|
+
def initialize(
|
21
|
+
sink: StatsD::Instrument::NullSink.new,
|
22
|
+
prefix: nil,
|
23
|
+
default_sample_rate: 1,
|
24
|
+
default_tags: nil,
|
25
|
+
datagram_builder_class: StatsD::Instrument::StatsDDatagramBuilder
|
26
|
+
)
|
27
|
+
@sink = sink
|
28
|
+
@datagram_builder_class = datagram_builder_class
|
29
|
+
|
30
|
+
@prefix = prefix
|
31
|
+
@default_tags = default_tags
|
32
|
+
@default_sample_rate = default_sample_rate
|
33
|
+
|
34
|
+
@datagram_builder = { false => nil, true => nil }
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!group Metric Methods
|
38
|
+
|
39
|
+
# Emits a counter metric.
|
40
|
+
#
|
41
|
+
# You should use a counter metric to count the frequency of something happening. As a
|
42
|
+
# result, the value should generally be set to 1 (the default), unless you reporting
|
43
|
+
# about a batch of activity. E.g. `increment('messages.processed', messages.size)`
|
44
|
+
# For values that are not frequencies, you should use another metric type, e.g.
|
45
|
+
# {#histogram} or {#distribution}.
|
46
|
+
#
|
47
|
+
# @param name [String] The name of the metric.
|
48
|
+
#
|
49
|
+
# - We recommend using `snake_case.metric_names` as naming scheme.
|
50
|
+
# - A `.` should be used for namespacing, e.g. `foo.bar.baz`
|
51
|
+
# - A metric name should not include the following characters: `|`, `@`, and `:`.
|
52
|
+
# The library will convert these characters to `_`.
|
53
|
+
#
|
54
|
+
# @param value [Integer] (default: 1) The value to increment the counter by.
|
55
|
+
#
|
56
|
+
# You should not compensate for the sample rate using the counter increment. E.g., if
|
57
|
+
# your sample rate is set to `0.01`, you should not use 100 as increment to compensate
|
58
|
+
# for it. The sample rate is part of the packet that is being sent to the server, and
|
59
|
+
# the server should know how to compensate for it.
|
60
|
+
#
|
61
|
+
# @param [Float] sample_rate (default: `#default_sample_rate`) The rate at which to sample
|
62
|
+
# this metric call. This value should be between 0 and 1. This value can be used to reduce
|
63
|
+
# the amount of network I/O (and CPU cycles) is being used for very frequent metrics.
|
64
|
+
#
|
65
|
+
# - A value of `0.1` means that only 1 out of 10 calls will be emitted; the other 9 will
|
66
|
+
# be short-circuited.
|
67
|
+
# - When set to `1`, every metric will be emitted.
|
68
|
+
# - If this parameter is not set, the default sample rate for this client will be used.
|
69
|
+
#
|
70
|
+
# @param [Hash<Symbol, String>, Array<String>] tags (default: nil)
|
71
|
+
# @return [void]
|
72
|
+
def increment(name, value = 1, sample_rate: nil, tags: nil, no_prefix: false)
|
73
|
+
sample_rate ||= @default_sample_rate
|
74
|
+
return unless sample?(sample_rate)
|
75
|
+
emit(datagram_builder(no_prefix: no_prefix).c(name, value, sample_rate, tags))
|
76
|
+
end
|
77
|
+
|
78
|
+
# Emits a timing metric.
|
79
|
+
#
|
80
|
+
# @param name (see #increment)
|
81
|
+
# @param [Numeric] value The duration to record, in milliseconds.
|
82
|
+
# @param sample_rate (see #increment)
|
83
|
+
# @param tags (see #increment)
|
84
|
+
# @return [void]
|
85
|
+
def measure(name, value = nil, sample_rate: nil, tags: nil, no_prefix: false, &block)
|
86
|
+
if block_given?
|
87
|
+
return latency(name, sample_rate: sample_rate, tags: tags, metric_type: :ms, no_prefix: no_prefix, &block)
|
88
|
+
end
|
89
|
+
|
90
|
+
sample_rate ||= @default_sample_rate
|
91
|
+
return unless sample?(sample_rate)
|
92
|
+
emit(datagram_builder(no_prefix: no_prefix).ms(name, value, sample_rate, tags))
|
93
|
+
end
|
94
|
+
|
95
|
+
# Emits a gauge metric.
|
96
|
+
#
|
97
|
+
# You should use a gauge if you are reporting the current value of
|
98
|
+
# something that can only have one value at the time. E.g., the
|
99
|
+
# speed of your car. A newly reported value will repla e the previously
|
100
|
+
# reported value.
|
101
|
+
#
|
102
|
+
#
|
103
|
+
# @param name (see #increment)
|
104
|
+
# @param [Numeric] value The gauged value.
|
105
|
+
# @param sample_rate (see #increment)
|
106
|
+
# @param tags (see #increment)
|
107
|
+
# @return [void]
|
108
|
+
def gauge(name, value, sample_rate: nil, tags: nil, no_prefix: false)
|
109
|
+
sample_rate ||= @default_sample_rate
|
110
|
+
return unless sample?(sample_rate)
|
111
|
+
emit(datagram_builder(no_prefix: no_prefix).g(name, value, sample_rate, tags))
|
112
|
+
end
|
113
|
+
|
114
|
+
# Emits a set metric, which counts distinct values.
|
115
|
+
#
|
116
|
+
# @param name (see #increment)
|
117
|
+
# @param [Numeric, String] value The value to count for distinct occurrences.
|
118
|
+
# @param sample_rate (see #increment)
|
119
|
+
# @param tags (see #increment)
|
120
|
+
# @return [void]
|
121
|
+
def set(name, value, sample_rate: nil, tags: nil, no_prefix: false)
|
122
|
+
sample_rate ||= @default_sample_rate
|
123
|
+
return unless sample?(sample_rate)
|
124
|
+
emit(datagram_builder(no_prefix: no_prefix).s(name, value, sample_rate, tags))
|
125
|
+
end
|
126
|
+
|
127
|
+
# Emits a distribution metric, which builds a histogram of the reported
|
128
|
+
# values.
|
129
|
+
#
|
130
|
+
# @note The distribution metric type is not available on all implementations.
|
131
|
+
# A `NotImplemetedError` will be raised if you call this method, but
|
132
|
+
# the active implementation does not support it.
|
133
|
+
#
|
134
|
+
# @param name (see #increment)
|
135
|
+
# @param [Numeric] value The value to include in the distribution histogram.
|
136
|
+
# @param sample_rate (see #increment)
|
137
|
+
# @param tags (see #increment)
|
138
|
+
# @return [void]
|
139
|
+
def distribution(name, value = nil, sample_rate: nil, tags: nil, no_prefix: false, &block)
|
140
|
+
if block_given?
|
141
|
+
return latency(name, sample_rate: sample_rate, tags: tags, metric_type: :d, no_prefix: no_prefix, &block)
|
142
|
+
end
|
143
|
+
|
144
|
+
sample_rate ||= @default_sample_rate
|
145
|
+
return unless sample?(sample_rate)
|
146
|
+
emit(datagram_builder(no_prefix: no_prefix).d(name, value, sample_rate, tags))
|
147
|
+
end
|
148
|
+
|
149
|
+
# Emits a histogram metric, which builds a histogram of the reported values.
|
150
|
+
#
|
151
|
+
# @note The histogram metric type is not available on all implementations.
|
152
|
+
# A `NotImplemetedError` will be raised if you call this method, but
|
153
|
+
# the active implementation does not support it.
|
154
|
+
#
|
155
|
+
# @param name (see #increment)
|
156
|
+
# @param [Numeric] value The value to include in the histogram.
|
157
|
+
# @param sample_rate (see #increment)
|
158
|
+
# @param tags (see #increment)
|
159
|
+
# @return [void]
|
160
|
+
def histogram(name, value, sample_rate: nil, tags: nil, no_prefix: false)
|
161
|
+
sample_rate ||= @default_sample_rate
|
162
|
+
return unless sample?(sample_rate)
|
163
|
+
emit(datagram_builder(no_prefix: no_prefix).h(name, value, sample_rate, tags))
|
164
|
+
end
|
165
|
+
|
166
|
+
# @!endgroup
|
167
|
+
|
168
|
+
# Measures the latency of the given block in milliseconds, and emits it as a metric.
|
169
|
+
#
|
170
|
+
# @param name (see #increment)
|
171
|
+
# @param sample_rate (see #increment)
|
172
|
+
# @param tags (see #increment)
|
173
|
+
# @param [Symbol] metric_type The metric type to use. If not specified, we will
|
174
|
+
# use the preferred metric type of the implementation. The default is `:ms`.
|
175
|
+
# Generally, you should not have to set this.
|
176
|
+
# @yield The latency (execution time) of the block
|
177
|
+
# @return The return value of the proivded block will be passed through.
|
178
|
+
def latency(name, sample_rate: nil, tags: nil, metric_type: nil, no_prefix: false)
|
179
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
180
|
+
begin
|
181
|
+
yield
|
182
|
+
ensure
|
183
|
+
stop = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
184
|
+
|
185
|
+
sample_rate ||= @default_sample_rate
|
186
|
+
if sample?(sample_rate)
|
187
|
+
metric_type ||= datagram_builder(no_prefix: no_prefix).latency_metric_type
|
188
|
+
latency_in_ms = 1000.0 * (stop - start)
|
189
|
+
emit(datagram_builder(no_prefix: no_prefix).send(metric_type, name, latency_in_ms, sample_rate, tags))
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# Emits a service check.
|
195
|
+
#
|
196
|
+
# @param [String] title Event title.
|
197
|
+
# @param [String] text Event description. Newlines are allowed.
|
198
|
+
# @param [Time] timestamp The of the event. If not provided,
|
199
|
+
# Datadog will interpret it as the current timestamp.
|
200
|
+
# @param [String] hostname A hostname to associate with the event.
|
201
|
+
# @param [String] aggregation_key An aggregation key to group events with the same key.
|
202
|
+
# @param [String] priority Priority of the event. Either "normal" (default) or "low".
|
203
|
+
# @param [String] source_type_name The source type of the event.
|
204
|
+
# @param [String] alert_type Either "error", "warning", "info" (default) or "success".
|
205
|
+
# @param [Array, Hash] tags Tags to associate with the event.
|
206
|
+
# @return [void]
|
207
|
+
#
|
208
|
+
# @note Supported by the Datadog implementation only.
|
209
|
+
def service_check(name, status, timestamp: nil, hostname: nil, tags: nil, message: nil, no_prefix: false)
|
210
|
+
emit(datagram_builder(no_prefix: no_prefix)._sc(name, status,
|
211
|
+
timestamp: timestamp, hostname: hostname, tags: tags, message: message))
|
212
|
+
end
|
213
|
+
|
214
|
+
# Emits an event.
|
215
|
+
#
|
216
|
+
# @param [String] name Name of the service
|
217
|
+
# @param [Symbol] status Either `:ok`, `:warning`, `:critical` or `:unknown`
|
218
|
+
# @param [Time] timestamp The moment when the service was checked. If not provided,
|
219
|
+
# Datadog will interpret it as the current timestamp.
|
220
|
+
# @param [String] hostname A hostname to associate with the check.
|
221
|
+
# @param [Array, Hash] tags Tags to associate with the check.
|
222
|
+
# @param [String] message A message describing the current state of the service check.
|
223
|
+
# @return [void]
|
224
|
+
#
|
225
|
+
# @note Supported by the Datadog implementation only.
|
226
|
+
def event(title, text, timestamp: nil, hostname: nil, aggregation_key: nil, priority: nil,
|
227
|
+
source_type_name: nil, alert_type: nil, tags: nil, no_prefix: false)
|
228
|
+
|
229
|
+
emit(datagram_builder(no_prefix: no_prefix)._e(title, text, timestamp: timestamp,
|
230
|
+
hostname: hostname, tags: tags, aggregation_key: aggregation_key, priority: priority,
|
231
|
+
source_type_name: source_type_name, alert_type: alert_type))
|
232
|
+
end
|
233
|
+
|
234
|
+
# Instantiates a new StatsD client that uses the settings of the current client,
|
235
|
+
# except for the provided overrides.
|
236
|
+
#
|
237
|
+
# @yield [client] A new client will be constructed with the altered settings, and
|
238
|
+
# yielded to the block. The original client will not be affected. The new client
|
239
|
+
# will be disposed after the block returns
|
240
|
+
# @return The return value of the block will be passed on as return value.
|
241
|
+
def with_options(
|
242
|
+
sink: nil,
|
243
|
+
prefix: nil,
|
244
|
+
default_sample_rate: nil,
|
245
|
+
default_tags: nil,
|
246
|
+
datagram_builder_class: nil
|
247
|
+
)
|
248
|
+
client = clone_with_options(sink: sink, prefix: prefix,
|
249
|
+
default_sample_rate: default_sample_rate, default_tags: default_tags,
|
250
|
+
datagram_builder_class: datagram_builder_class)
|
251
|
+
|
252
|
+
yield(client)
|
253
|
+
end
|
254
|
+
|
255
|
+
def clone_with_options(
|
256
|
+
sink: nil,
|
257
|
+
prefix: nil,
|
258
|
+
default_sample_rate: nil,
|
259
|
+
default_tags: nil,
|
260
|
+
datagram_builder_class: nil
|
261
|
+
)
|
262
|
+
self.class.new(
|
263
|
+
sink: sink || @sink,
|
264
|
+
prefix: prefix || @prefix,
|
265
|
+
default_sample_rate: default_sample_rate || @default_sample_rate,
|
266
|
+
default_tags: default_tags || @default_tags,
|
267
|
+
datagram_builder_class: datagram_builder_class || @datagram_builder_class,
|
268
|
+
)
|
269
|
+
end
|
270
|
+
|
271
|
+
def capture_sink
|
272
|
+
StatsD::Instrument::CaptureSink.new(
|
273
|
+
parent: @sink,
|
274
|
+
datagram_class: datagram_builder(no_prefix: false).datagram_class,
|
275
|
+
)
|
276
|
+
end
|
277
|
+
|
278
|
+
def with_capture_sink(capture_sink)
|
279
|
+
@sink = capture_sink
|
280
|
+
yield
|
281
|
+
ensure
|
282
|
+
@sink = @sink.parent
|
283
|
+
end
|
284
|
+
|
285
|
+
# Captures metrics that were emitted during the provided block.
|
286
|
+
#
|
287
|
+
# @yield During the execution of the provided block, metrics will be captured.
|
288
|
+
# @return [Array<StatsD::Instagram::Datagram>] The list of metrics that were
|
289
|
+
# emitted during the block, in the same order in which they were emitted.
|
290
|
+
def capture(&block)
|
291
|
+
sink = capture_sink
|
292
|
+
with_capture_sink(sink, &block)
|
293
|
+
sink.datagrams
|
294
|
+
end
|
295
|
+
|
296
|
+
protected
|
297
|
+
|
298
|
+
def datagram_builder(no_prefix:)
|
299
|
+
@datagram_builder[no_prefix] ||= @datagram_builder_class.new(
|
300
|
+
prefix: no_prefix ? nil : prefix,
|
301
|
+
default_tags: default_tags,
|
302
|
+
)
|
303
|
+
end
|
304
|
+
|
305
|
+
def sample?(sample_rate)
|
306
|
+
@sink.sample?(sample_rate)
|
307
|
+
end
|
308
|
+
|
309
|
+
def emit(datagram)
|
310
|
+
@sink << datagram
|
311
|
+
nil
|
312
|
+
end
|
313
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# The Datagram class parses and inspects a StatsD datagrans
|
4
|
+
#
|
5
|
+
# @note This class is part of the new Client implementation that is intended
|
6
|
+
# to become the new default in the next major release of this library.
|
7
|
+
class StatsD::Instrument::Datagram
|
8
|
+
attr_reader :source
|
9
|
+
|
10
|
+
def initialize(source)
|
11
|
+
@source = source
|
12
|
+
end
|
13
|
+
|
14
|
+
# @return [Float] The sample rate at which this datagram was emitted, between 0 and 1.
|
15
|
+
def sample_rate
|
16
|
+
parsed_datagram[:sample_rate] ? Float(parsed_datagram[:sample_rate]) : 1.0
|
17
|
+
end
|
18
|
+
|
19
|
+
def type
|
20
|
+
parsed_datagram[:type]
|
21
|
+
end
|
22
|
+
|
23
|
+
def name
|
24
|
+
parsed_datagram[:name]
|
25
|
+
end
|
26
|
+
|
27
|
+
def value
|
28
|
+
parsed_datagram[:value]
|
29
|
+
end
|
30
|
+
|
31
|
+
def tags
|
32
|
+
@tags ||= parsed_datagram[:tags] ? parsed_datagram[:tags].split(',') : nil
|
33
|
+
end
|
34
|
+
|
35
|
+
def inspect
|
36
|
+
"#<#{self.class.name}:\"#{@source}\">"
|
37
|
+
end
|
38
|
+
|
39
|
+
def hash
|
40
|
+
source.hash
|
41
|
+
end
|
42
|
+
|
43
|
+
def eql?(other)
|
44
|
+
case other
|
45
|
+
when StatsD::Instrument::Datagram
|
46
|
+
source == other.source
|
47
|
+
when String
|
48
|
+
source == other
|
49
|
+
else
|
50
|
+
false
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
alias_method :==, :eql?
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
PARSER = %r{
|
59
|
+
\A
|
60
|
+
(?<name>[^\:\|\@]+)\:(?<value>[^\:\|\@]+)\|(?<type>c|ms|g|s|h|d)
|
61
|
+
(?:\|\@(?<sample_rate>\d*(?:\.\d*)?))?
|
62
|
+
(?:\|\#(?<tags>(?:[^\|\#,]+(?:,[^\|\#,]+)*)))?
|
63
|
+
\n? # In some implementations, the datagram may include a trailing newline.
|
64
|
+
\z
|
65
|
+
}x
|
66
|
+
private_constant :PARSER
|
67
|
+
|
68
|
+
def parsed_datagram
|
69
|
+
@parsed ||= if (match_info = PARSER.match(@source))
|
70
|
+
match_info
|
71
|
+
else
|
72
|
+
raise ArgumentError, "Invalid StatsD datagram: #{@source}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @note This class is part of the new Client implementation that is intended
|
4
|
+
# to become the new default in the next major release of this library.
|
5
|
+
class StatsD::Instrument::DatagramBuilder
|
6
|
+
unless Regexp.method_defined?(:match?) # for ruby 2.3
|
7
|
+
module RubyBackports
|
8
|
+
refine Regexp do
|
9
|
+
def match?(str)
|
10
|
+
match(str) != nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
using RubyBackports
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.unsupported_datagram_types(*types)
|
19
|
+
types.each do |type|
|
20
|
+
define_method(type) do |_, _, _, _|
|
21
|
+
raise NotImplementedError, "Type #{type} metrics are not suppered by #{self.class.name}."
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(prefix: nil, default_tags: nil)
|
27
|
+
@prefix = prefix.nil? ? "" : "#{normalize_name(prefix)}."
|
28
|
+
@default_tags = normalize_tags(default_tags)
|
29
|
+
end
|
30
|
+
|
31
|
+
def c(name, value, sample_rate, tags)
|
32
|
+
generate_generic_datagram(name, value, 'c', sample_rate, tags)
|
33
|
+
end
|
34
|
+
|
35
|
+
def g(name, value, sample_rate, tags)
|
36
|
+
generate_generic_datagram(name, value, 'g', sample_rate, tags)
|
37
|
+
end
|
38
|
+
|
39
|
+
def ms(name, value, sample_rate, tags)
|
40
|
+
generate_generic_datagram(name, value, 'ms', sample_rate, tags)
|
41
|
+
end
|
42
|
+
|
43
|
+
def s(name, value, sample_rate, tags)
|
44
|
+
generate_generic_datagram(name, value, 's', sample_rate, tags)
|
45
|
+
end
|
46
|
+
|
47
|
+
def h(name, value, sample_rate, tags)
|
48
|
+
generate_generic_datagram(name, value, 'h', sample_rate, tags)
|
49
|
+
end
|
50
|
+
|
51
|
+
def d(name, value, sample_rate, tags)
|
52
|
+
generate_generic_datagram(name, value, 'd', sample_rate, tags)
|
53
|
+
end
|
54
|
+
|
55
|
+
def kv(name, value, sample_rate, tags)
|
56
|
+
generate_generic_datagram(name, value, 'kv', sample_rate, tags)
|
57
|
+
end
|
58
|
+
|
59
|
+
def datagram_class
|
60
|
+
StatsD::Instrument::Datagram
|
61
|
+
end
|
62
|
+
|
63
|
+
def latency_metric_type
|
64
|
+
:ms
|
65
|
+
end
|
66
|
+
|
67
|
+
protected
|
68
|
+
|
69
|
+
attr_reader :prefix, :default_tags
|
70
|
+
|
71
|
+
# Utility function to convert tags to the canonical form.
|
72
|
+
#
|
73
|
+
# - Tags specified as key value pairs will be converted into an array
|
74
|
+
# - Tags are normalized to remove unsupported characters
|
75
|
+
#
|
76
|
+
# @param tags [Array<String>, Hash<String, String>, nil] Tags specified in any form.
|
77
|
+
# @return [Array<String>, nil] the list of tags in canonical form.
|
78
|
+
def normalize_tags(tags)
|
79
|
+
return [] unless tags
|
80
|
+
tags = tags.map { |k, v| "#{k}:#{v}" } if tags.is_a?(Hash)
|
81
|
+
|
82
|
+
# Fast path when no string replacement is needed
|
83
|
+
return tags unless tags.any? { |tag| /[|,]/.match?(tag) }
|
84
|
+
tags.map { |tag| tag.tr('|,', '') }
|
85
|
+
end
|
86
|
+
|
87
|
+
# Utility function to remove invalid characters from a StatsD metric name
|
88
|
+
def normalize_name(name)
|
89
|
+
# Fast path when no normalization is needed to avoid copying the string
|
90
|
+
return name unless /[:|@]/.match?(name)
|
91
|
+
name.tr(':|@', '_')
|
92
|
+
end
|
93
|
+
|
94
|
+
def generate_generic_datagram(name, value, type, sample_rate, tags)
|
95
|
+
tags = normalize_tags(tags) + default_tags
|
96
|
+
datagram = +"#{@prefix}#{normalize_name(name)}:#{value}|#{type}"
|
97
|
+
datagram << "|@#{sample_rate}" if sample_rate && sample_rate < 1
|
98
|
+
datagram << "|##{tags.join(',')}" unless tags.empty?
|
99
|
+
datagram
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# @note This class is part of the new Client implementation that is intended
|
4
|
+
# to become the new default in the next major release of this library.
|
5
|
+
class StatsD::Instrument::DogStatsDDatagramBuilder < StatsD::Instrument::DatagramBuilder
|
6
|
+
unsupported_datagram_types :kv
|
7
|
+
|
8
|
+
def latency_metric_type
|
9
|
+
:d
|
10
|
+
end
|
11
|
+
|
12
|
+
# Constricts an event datagram.
|
13
|
+
#
|
14
|
+
# @param [String] title Event title.
|
15
|
+
# @param [String] text Event description. Newlines are allowed.
|
16
|
+
# @param [Time] timestamp The of the event. If not provided,
|
17
|
+
# Datadog will interpret it as the current timestamp.
|
18
|
+
# @param [String] hostname A hostname to associate with the event.
|
19
|
+
# @param [String] aggregation_key An aggregation key to group events with the same key.
|
20
|
+
# @param [String] priority Priority of the event. Either "normal" (default) or "low".
|
21
|
+
# @param [String] source_type_name The source type of the event.
|
22
|
+
# @param [String] alert_type Either "error", "warning", "info" (default) or "success".
|
23
|
+
# @param [Array, Hash] tags Tags to associate with the event.
|
24
|
+
# @return [String] The correctly formatted service check datagram
|
25
|
+
#
|
26
|
+
# @see https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/#events
|
27
|
+
def _e(title, text, timestamp: nil, hostname: nil, aggregation_key: nil, priority: nil,
|
28
|
+
source_type_name: nil, alert_type: nil, tags: nil)
|
29
|
+
|
30
|
+
escaped_title = "#{@prefix}#{title}".gsub("\n", '\n')
|
31
|
+
escaped_text = text.gsub("\n", '\n')
|
32
|
+
tags = normalize_tags(tags) + default_tags
|
33
|
+
|
34
|
+
datagram = +"_e{#{escaped_title.length},#{escaped_text.length}}:#{escaped_title}|#{escaped_text}"
|
35
|
+
datagram << "|h:#{hostname}" if hostname
|
36
|
+
datagram << "|d:#{timestamp.to_i}" if timestamp
|
37
|
+
datagram << "|k:#{aggregation_key}" if aggregation_key
|
38
|
+
datagram << "|p:#{priority}" if priority
|
39
|
+
datagram << "|s:#{source_type_name}" if source_type_name
|
40
|
+
datagram << "|t:#{alert_type}" if alert_type
|
41
|
+
datagram << "|##{tags.join(',')}" unless tags.empty?
|
42
|
+
datagram
|
43
|
+
end
|
44
|
+
|
45
|
+
# Constricts a service check datagram.
|
46
|
+
#
|
47
|
+
# @param [String] name Name of the service
|
48
|
+
# @param [Symbol] status Either `:ok`, `:warning`, `:critical` or `:unknown`
|
49
|
+
# @param [Time] timestamp The moment when the service was checked. If not provided,
|
50
|
+
# Datadog will interpret it as the current timestamp.
|
51
|
+
# @param [String] hostname A hostname to associate with the check.
|
52
|
+
# @param [Array, Hash] tags Tags to associate with the check.
|
53
|
+
# @param [String] message A message describing the current state of the service check.
|
54
|
+
# @return [String] The correctly formatted service check datagram
|
55
|
+
#
|
56
|
+
# @see https://docs.datadoghq.com/developers/dogstatsd/datagram_shell/#service-checks
|
57
|
+
def _sc(name, status, timestamp: nil, hostname: nil, tags: nil, message: nil)
|
58
|
+
status_number = status.is_a?(Integer) ? status : SERVICE_CHECK_STATUS_VALUES.fetch(status.to_sym)
|
59
|
+
tags = normalize_tags(tags) + default_tags
|
60
|
+
|
61
|
+
datagram = +"_sc|#{@prefix}#{normalize_name(name)}|#{status_number}"
|
62
|
+
datagram << "|h:#{hostname}" if hostname
|
63
|
+
datagram << "|d:#{timestamp.to_i}" if timestamp
|
64
|
+
datagram << "|##{tags.join(',')}" unless tags.empty?
|
65
|
+
datagram << "|m:#{normalize_name(message)}" if message
|
66
|
+
datagram
|
67
|
+
end
|
68
|
+
|
69
|
+
SERVICE_CHECK_STATUS_VALUES = { ok: 0, warning: 1, critical: 2, unknown: 3 }.freeze
|
70
|
+
private_constant :SERVICE_CHECK_STATUS_VALUES
|
71
|
+
end
|