statsd-instrument 2.1.4 → 2.2.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/CHANGELOG.md +4 -0
- data/CONTRIBUTING.md +9 -2
- data/README.md +28 -2
- data/lib/statsd/instrument/backends/udp_backend.rb +92 -33
- data/lib/statsd/instrument/metric.rb +11 -1
- data/lib/statsd/instrument/version.rb +1 -1
- data/lib/statsd/instrument.rb +30 -0
- data/test/metric_test.rb +9 -0
- data/test/udp_backend_test.rb +44 -7
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1888f7964454431b2167c0c253ea7c53de3c1afb
|
4
|
+
data.tar.gz: 74c6d0f9f44eb989bcfdc44cfea912fb07c1447a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b2faeaa7145f89905c966f08b044bb90b544f53f9bc5de0b1aa29bcf7731e3807f70ad114692a31481e19693d761f74253702791c24212f173c3254dc466f9dc
|
7
|
+
data.tar.gz: 8f7d60c2a4144afb033adbb18435373d7bba35702782e835642923a09dff7ec8028fbf7de655c7031aa6102a8df698685af0b71b6bca4195ca17e86cb20d01fa
|
data/CHANGELOG.md
CHANGED
@@ -5,6 +5,10 @@ please at an entry to the "unreleased changes" section below.
|
|
5
5
|
|
6
6
|
### Unreleased changes
|
7
7
|
|
8
|
+
### Version 2.2.0
|
9
|
+
|
10
|
+
- Add support for two new datadog specific metric types: events and service checks.
|
11
|
+
|
8
12
|
### Version 2.1.3
|
9
13
|
|
10
14
|
- The `assert_statsd_calls` test helper will now raise an exception whenever a block isn't passed.
|
data/CONTRIBUTING.md
CHANGED
@@ -1,6 +1,11 @@
|
|
1
1
|
# Contributing
|
2
2
|
|
3
|
-
This project is MIT licensed
|
3
|
+
This project is MIT licensed.
|
4
|
+
|
5
|
+
> **Note**: this project is currently not actively maintained, but is heavily used in production.
|
6
|
+
> As a result, pull requests and issues may not be responded to. Also, due to the limited time we have
|
7
|
+
> avaibale to work on this library, we cannot accept PRs that do not maintain backwards compatibility,
|
8
|
+
> or PRs that would affect the performance of the hot code paths.
|
4
9
|
|
5
10
|
## Reporting issues
|
6
11
|
|
@@ -17,7 +22,6 @@ When reporting issues, please incldue the following information:
|
|
17
22
|
1. Fork the repository, and create a branch.
|
18
23
|
2. Implement the feature or bugfix, and add tests that cover the changed functionality.
|
19
24
|
3. Create a pull request. Make sure that you get Travis CI passes.
|
20
|
-
4. Ping **@jstorimer** and/or **@wvanbergen** for a code review.
|
21
25
|
|
22
26
|
Some notes:
|
23
27
|
|
@@ -26,6 +30,9 @@ Some notes:
|
|
26
30
|
- Add an entry to the "unreleased changes" section of [CHANGELOG.md](./CHANGELOG.md).
|
27
31
|
- **Do not** update `StatsD::Instrument::VERSION`. This will be done during the release prodecure.
|
28
32
|
|
33
|
+
> **Important:** if you change anything in the hot code path (sending a StatsD metric), please
|
34
|
+
> include benchmarks to show the performance impact of your changes.
|
35
|
+
|
29
36
|
## Release procedure
|
30
37
|
|
31
38
|
1. Update the version number in `lib/statsd/instrument/version.rb`.
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# StatsD client for Ruby apps
|
2
2
|
|
3
|
-
[](https://secure.travis-ci.org/Shopify/statsd-instrument)
|
4
4
|
|
5
5
|
This is a ruby client for statsd (http://github.com/etsy/statsd). It provides a lightweight way to track and measure metrics in your application.
|
6
6
|
|
@@ -106,6 +106,32 @@ StatsD.set('GoogleBase.customers', "12345", sample_rate: 1.0)
|
|
106
106
|
|
107
107
|
Because you are counting unique values, the results of using a sampling value less than 1.0 can lead to unexpected, hard to interpret results.
|
108
108
|
|
109
|
+
#### StatsD.event
|
110
|
+
|
111
|
+
An event is a (title, text) tuple that can be used to correlate metrics with something that occured within the system.
|
112
|
+
This is a good fit for instance to correlate response time variation with a deploy of the new code.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
StatsD.event('shipit.deploy', 'started', sample_rate: 1.0)
|
116
|
+
```
|
117
|
+
|
118
|
+
*Note: This is only supported by the [datadog implementatation](https://docs.datadoghq.com/guides/dogstatsd/#events).*
|
119
|
+
|
120
|
+
Events support additional metadata such as `date_happened`, `hostname`, `aggregation_key`, `priority`, `source_type_name`, `alert_type`.
|
121
|
+
|
122
|
+
#### StatsD.service_check
|
123
|
+
|
124
|
+
An event is a (check_name, status) tuple that can be used to monitor the status of services your application depends on.
|
125
|
+
|
126
|
+
```ruby
|
127
|
+
StatsD.service_check('shipit.redis_connection', 'ok')
|
128
|
+
```
|
129
|
+
|
130
|
+
*Note: This is only supported by the [datadog implementatation](https://docs.datadoghq.com/guides/dogstatsd/#service-checks).*
|
131
|
+
|
132
|
+
Service checks support additional metadata such as `timestamp`, `hostname`, `message`.
|
133
|
+
|
134
|
+
|
109
135
|
### Metaprogramming Methods
|
110
136
|
|
111
137
|
As mentioned, it's most common to use the provided metaprogramming methods. This lets you define all of your instrumentation in one file and not litter your code with instrumentation details. You should enable a class for instrumentation by extending it with the `StatsD::Instrument` class.
|
@@ -191,7 +217,7 @@ GoogleBase.statsd_count :insert, lambda{|object, args| object.class.to_s.downcas
|
|
191
217
|
|
192
218
|
### Tags
|
193
219
|
|
194
|
-
The Datadog implementation
|
220
|
+
The Datadog implementation supports tags, which you can use to slice and dice metrics in their UI. You can specify a list of tags as an option, either standalone tag (e.g. `"mytag"`), or key value based, separated by a colon: `"env:production"`.
|
195
221
|
|
196
222
|
``` ruby
|
197
223
|
StatsD.increment('my.counter', tags: ['env:production', 'unicorn'])
|
@@ -3,12 +3,86 @@ require 'monitor'
|
|
3
3
|
module StatsD::Instrument::Backends
|
4
4
|
class UDPBackend < StatsD::Instrument::Backend
|
5
5
|
|
6
|
+
class DogStatsDProtocol
|
7
|
+
EVENT_OPTIONS = {
|
8
|
+
date_happened: 'd',
|
9
|
+
hostname: 'h',
|
10
|
+
aggregation_key: 'k',
|
11
|
+
priority: 'p',
|
12
|
+
source_type_name: 's',
|
13
|
+
alert_type: 't',
|
14
|
+
}
|
15
|
+
|
16
|
+
SERVICE_CHECK_OPTIONS = {
|
17
|
+
timestamp: 'd',
|
18
|
+
hostname: 'h',
|
19
|
+
message: 'm',
|
20
|
+
}
|
21
|
+
|
22
|
+
def supported?(metric)
|
23
|
+
[:c, :ms, :g, :h, :s, :_e, :_sc].include?(metric.type)
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate_packet(metric)
|
27
|
+
packet = ""
|
28
|
+
|
29
|
+
if metric.type == :_e
|
30
|
+
escaped_title = metric.name.tr('\n', '\\n')
|
31
|
+
escaped_text = metric.value.tr('\n', '\\n')
|
32
|
+
|
33
|
+
packet << "_e{#{escaped_title.size},#{escaped_text.size}}:#{escaped_title}|#{escaped_text}"
|
34
|
+
packet << generate_metadata(metric, EVENT_OPTIONS)
|
35
|
+
elsif metric.type == :_sc
|
36
|
+
packet << "_sc|#{metric.name}|#{metric.value}"
|
37
|
+
packet << generate_metadata(metric, SERVICE_CHECK_OPTIONS)
|
38
|
+
else
|
39
|
+
packet << "#{metric.name}:#{metric.value}|#{metric.type}"
|
40
|
+
end
|
41
|
+
|
42
|
+
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
43
|
+
packet << "|##{metric.tags.join(',')}" if metric.tags
|
44
|
+
packet
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def generate_metadata(metric, options)
|
50
|
+
(metric.metadata.keys & options.keys).map do |key|
|
51
|
+
"|#{options[key]}:#{metric.metadata[key]}"
|
52
|
+
end.join
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class StatsiteStatsDProtocol
|
57
|
+
def supported?(metric)
|
58
|
+
[:c, :ms, :g, :s, :kv].include?(metric.type)
|
59
|
+
end
|
60
|
+
|
61
|
+
def generate_packet(metric)
|
62
|
+
packet = "#{metric.name}:#{metric.value}|#{metric.type}"
|
63
|
+
packet << "|@#{metric.sample_rate}" unless metric.sample_rate == 1
|
64
|
+
packet << "\n"
|
65
|
+
packet
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
class StatsDProtocol
|
70
|
+
def supported?(metric)
|
71
|
+
[:c, :ms, :g, :s].include?(metric.type)
|
72
|
+
end
|
73
|
+
|
74
|
+
def generate_packet(metric)
|
75
|
+
packet = "#{metric.name}:#{metric.value}|#{metric.type}"
|
76
|
+
packet << "|@#{metric.sample_rate}" if metric.sample_rate < 1
|
77
|
+
packet
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
6
81
|
DEFAULT_IMPLEMENTATION = :statsd
|
7
82
|
|
8
83
|
include MonitorMixin
|
9
84
|
|
10
|
-
attr_reader :host, :port
|
11
|
-
attr_accessor :implementation
|
85
|
+
attr_reader :host, :port, :implementation
|
12
86
|
|
13
87
|
def initialize(server = nil, implementation = nil)
|
14
88
|
super()
|
@@ -16,8 +90,20 @@ module StatsD::Instrument::Backends
|
|
16
90
|
self.implementation = (implementation || DEFAULT_IMPLEMENTATION).to_sym
|
17
91
|
end
|
18
92
|
|
93
|
+
def implementation=(value)
|
94
|
+
@packet_factory = case value
|
95
|
+
when :datadog
|
96
|
+
DogStatsDProtocol.new
|
97
|
+
when :statsite
|
98
|
+
StatsiteStatsDProtocol.new
|
99
|
+
else
|
100
|
+
StatsDProtocol.new
|
101
|
+
end
|
102
|
+
@implementation = value
|
103
|
+
end
|
104
|
+
|
19
105
|
def collect_metric(metric)
|
20
|
-
unless
|
106
|
+
unless @packet_factory.supported?(metric)
|
21
107
|
StatsD.logger.warn("[StatsD] Metric type #{metric.type.inspect} not supported on #{implementation} implementation.")
|
22
108
|
return false
|
23
109
|
end
|
@@ -26,20 +112,12 @@ module StatsD::Instrument::Backends
|
|
26
112
|
return false
|
27
113
|
end
|
28
114
|
|
29
|
-
write_packet(generate_packet(metric))
|
30
|
-
end
|
31
|
-
|
32
|
-
def implementation_supports_metric_type?(type)
|
33
|
-
case type
|
34
|
-
when :h; implementation == :datadog
|
35
|
-
when :kv; implementation == :statsite
|
36
|
-
else true
|
37
|
-
end
|
115
|
+
write_packet(@packet_factory.generate_packet(metric))
|
38
116
|
end
|
39
117
|
|
40
118
|
def server=(connection_string)
|
41
|
-
|
42
|
-
|
119
|
+
@host, @port = connection_string.split(':', 2)
|
120
|
+
@port = @port.to_i
|
43
121
|
invalidate_socket
|
44
122
|
end
|
45
123
|
|
@@ -61,25 +139,6 @@ module StatsD::Instrument::Backends
|
|
61
139
|
@socket
|
62
140
|
end
|
63
141
|
|
64
|
-
def generate_packet(metric)
|
65
|
-
command = "#{metric.name}:#{metric.value}|#{metric.type}"
|
66
|
-
command << "|@#{metric.sample_rate}" if metric.sample_rate < 1 || (implementation == :statsite && metric.sample_rate > 1)
|
67
|
-
if metric.tags
|
68
|
-
if tags_supported?
|
69
|
-
command << "|##{metric.tags.join(',')}"
|
70
|
-
else
|
71
|
-
StatsD.logger.warn("[StatsD] Tags are only supported on Datadog implementation.")
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
command << "\n" if implementation == :statsite
|
76
|
-
command
|
77
|
-
end
|
78
|
-
|
79
|
-
def tags_supported?
|
80
|
-
implementation == :datadog
|
81
|
-
end
|
82
|
-
|
83
142
|
def write_packet(command)
|
84
143
|
synchronize do
|
85
144
|
socket.send(command, 0) > 0
|
@@ -28,7 +28,7 @@
|
|
28
28
|
#
|
29
29
|
class StatsD::Instrument::Metric
|
30
30
|
|
31
|
-
attr_accessor :type, :name, :value, :sample_rate, :tags
|
31
|
+
attr_accessor :type, :name, :value, :sample_rate, :tags, :metadata
|
32
32
|
|
33
33
|
# Initializes a new metric instance.
|
34
34
|
# Normally, you don't want to call this method directly, but use one of the metric collection
|
@@ -46,11 +46,13 @@ class StatsD::Instrument::Metric
|
|
46
46
|
def initialize(options = {})
|
47
47
|
@type = options[:type] or raise ArgumentError, "Metric :type is required."
|
48
48
|
@name = options[:name] or raise ArgumentError, "Metric :name is required."
|
49
|
+
@name = normalize_name(@name)
|
49
50
|
@name = StatsD.prefix ? "#{StatsD.prefix}.#{@name}" : @name unless options[:no_prefix]
|
50
51
|
|
51
52
|
@value = options[:value] || default_value
|
52
53
|
@sample_rate = options[:sample_rate] || StatsD.default_sample_rate
|
53
54
|
@tags = StatsD::Instrument::Metric.normalize_tags(options[:tags])
|
55
|
+
@metadata = options.reject { |k, _| [:type, :name, :value, :sample_rate, :tags].include?(k) }
|
54
56
|
end
|
55
57
|
|
56
58
|
# The default value for this metric, which will be used if it is not set.
|
@@ -93,6 +95,14 @@ class StatsD::Instrument::Metric
|
|
93
95
|
s: 'set',
|
94
96
|
}
|
95
97
|
|
98
|
+
# Strip metric names of special characters used by StatsD line protocol, replace with underscore
|
99
|
+
#
|
100
|
+
# @param name [String]
|
101
|
+
# @return [String]
|
102
|
+
def normalize_name(name)
|
103
|
+
name.tr(':|@'.freeze, '_')
|
104
|
+
end
|
105
|
+
|
96
106
|
# Utility function to convert tags to the canonical form.
|
97
107
|
#
|
98
108
|
# - Tags specified as key value pairs will be converted into an array
|
data/lib/statsd/instrument.rb
CHANGED
@@ -369,6 +369,36 @@ module StatsD
|
|
369
369
|
collect_metric(hash_argument(metric_options).merge(type: :s, name: key, value: value))
|
370
370
|
end
|
371
371
|
|
372
|
+
# Emits an event metric.
|
373
|
+
# @param title [String] Title of the event.
|
374
|
+
# @param text [String] Body of the event.
|
375
|
+
# @param metric_options [Hash] (default: {}) Metric options
|
376
|
+
# @return (see #collect_metric)
|
377
|
+
# @note Supported by the datadog implementation only.
|
378
|
+
def event(title, text, *metric_options)
|
379
|
+
if text.is_a?(Hash) && metric_options.empty?
|
380
|
+
metric_options = [text]
|
381
|
+
text = text.fetch(:text, nil)
|
382
|
+
end
|
383
|
+
|
384
|
+
collect_metric(hash_argument(metric_options).merge(type: :_e, name: title, value: text))
|
385
|
+
end
|
386
|
+
|
387
|
+
# Emits a service check metric.
|
388
|
+
# @param title [String] Title of the event.
|
389
|
+
# @param text [String] Body of the event.
|
390
|
+
# @param metric_options [Hash] (default: {}) Metric options
|
391
|
+
# @return (see #collect_metric)
|
392
|
+
# @note Supported by the datadog implementation only.
|
393
|
+
def service_check(name, status, *metric_options)
|
394
|
+
if status.is_a?(Hash) && metric_options.empty?
|
395
|
+
metric_options = [status]
|
396
|
+
status = status.fetch(:status, nil)
|
397
|
+
end
|
398
|
+
|
399
|
+
collect_metric(hash_argument(metric_options).merge(type: :_sc, name: name, value: status))
|
400
|
+
end
|
401
|
+
|
372
402
|
private
|
373
403
|
|
374
404
|
# Converts old-style ordered arguments in an argument hash for backwards compatibility.
|
data/test/metric_test.rb
CHANGED
@@ -24,6 +24,15 @@ class MetricTest < Minitest::Test
|
|
24
24
|
assert_equal 'counter', m.name
|
25
25
|
end
|
26
26
|
|
27
|
+
def test_bad_metric_name
|
28
|
+
m = StatsD::Instrument::Metric.new(type: :c, name: 'my:metric', no_prefix: true)
|
29
|
+
assert_equal 'my_metric', m.name
|
30
|
+
m = StatsD::Instrument::Metric.new(type: :c, name: 'my|metric', no_prefix: true)
|
31
|
+
assert_equal 'my_metric', m.name
|
32
|
+
m = StatsD::Instrument::Metric.new(type: :c, name: 'my@metric', no_prefix: true)
|
33
|
+
assert_equal 'my_metric', m.name
|
34
|
+
end
|
35
|
+
|
27
36
|
def test_handle_bad_tags
|
28
37
|
assert_equal ['ignored'], StatsD::Instrument::Metric.normalize_tags(['igno|red'])
|
29
38
|
assert_equal ['lol::class:omg::lol'], StatsD::Instrument::Metric.normalize_tags({ :"lol::class" => "omg::lol" })
|
data/test/udp_backend_test.rb
CHANGED
@@ -76,6 +76,50 @@ class UDPBackendTest < Minitest::Test
|
|
76
76
|
StatsD.histogram('fooh', 42.4)
|
77
77
|
end
|
78
78
|
|
79
|
+
def test_event_on_datadog
|
80
|
+
@backend.implementation = :datadog
|
81
|
+
@backend.expects(:write_packet).with('_e{4,3}:fooh|baz|h:localhost:3000|@0.01|#foo')
|
82
|
+
StatsD.event('fooh', 'baz', hostname: 'localhost:3000', sample_rate: 0.01, tags: ["foo"])
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_event_on_datadog_escapes_newlines
|
86
|
+
@backend.implementation = :datadog
|
87
|
+
@backend.expects(:write_packet).with('_e{8,5}:fooh\\n\\n|baz\\n')
|
88
|
+
StatsD.event('fooh\n\n', 'baz\n')
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_event_on_datadog_ignores_invalid_metadata
|
92
|
+
@backend.implementation = :datadog
|
93
|
+
@backend.expects(:write_packet).with('_e{4,3}:fooh|baz')
|
94
|
+
StatsD.event('fooh', 'baz', i_am_not_supported: 'not-supported')
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_event_warns_when_not_using_datadog
|
98
|
+
@backend.implementation = :other
|
99
|
+
@backend.expects(:write_packet).never
|
100
|
+
@logger.expects(:warn)
|
101
|
+
StatsD.event('fooh', 'bar')
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_service_check_on_datadog
|
105
|
+
@backend.implementation = :datadog
|
106
|
+
@backend.expects(:write_packet).with('_sc|fooh|baz|h:localhost:3000|@0.01|#foo')
|
107
|
+
StatsD.service_check('fooh', 'baz', hostname: 'localhost:3000', sample_rate: 0.01, tags: ["foo"])
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_service_check_on_datadog_ignores_invalid_metadata
|
111
|
+
@backend.implementation = :datadog
|
112
|
+
@backend.expects(:write_packet).with('_sc|fooh|baz')
|
113
|
+
StatsD.service_check('fooh', 'baz', i_am_not_supported: 'not-supported')
|
114
|
+
end
|
115
|
+
|
116
|
+
def test_service_check_warns_when_not_using_datadog
|
117
|
+
@backend.implementation = :other
|
118
|
+
@backend.expects(:write_packet).never
|
119
|
+
@logger.expects(:warn)
|
120
|
+
StatsD.service_check('fooh', 'bar')
|
121
|
+
end
|
122
|
+
|
79
123
|
def test_histogram_warns_if_not_using_datadog
|
80
124
|
@backend.implementation = :other
|
81
125
|
@backend.expects(:write_packet).never
|
@@ -108,13 +152,6 @@ class UDPBackendTest < Minitest::Test
|
|
108
152
|
StatsD.increment('fooc', 3, tags: ['topic:foo', 'bar'])
|
109
153
|
end
|
110
154
|
|
111
|
-
def test_warn_when_using_tags_and_not_on_datadog
|
112
|
-
@backend.implementation = :other
|
113
|
-
@backend.expects(:write_packet).with("fooc:1|c")
|
114
|
-
@logger.expects(:warn)
|
115
|
-
StatsD.increment('fooc', tags: ['ignored'])
|
116
|
-
end
|
117
|
-
|
118
155
|
def test_socket_error_should_not_raise_but_log
|
119
156
|
@socket.stubs(:connect).raises(SocketError)
|
120
157
|
@logger.expects(:error)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: statsd-instrument
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jesse Storimer
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2018-05-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
@@ -162,7 +162,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
162
|
version: '0'
|
163
163
|
requirements: []
|
164
164
|
rubyforge_project:
|
165
|
-
rubygems_version: 2.
|
165
|
+
rubygems_version: 2.6.14
|
166
166
|
signing_key:
|
167
167
|
specification_version: 4
|
168
168
|
summary: A StatsD client for Ruby apps
|