statsd-instrument 3.11.2 → 4.0.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/lib/statsd/instrument/aggregator.rb +3 -3
- data/lib/statsd/instrument/client.rb +36 -22
- data/lib/statsd/instrument/version.rb +1 -1
- data/test/aggregator_test.rb +68 -100
- data/test/client_test.rb +64 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5ba68947d3fa5cec0f604135dbf0fdda92ba4d056262ae2864e3144f8ad395ba
|
|
4
|
+
data.tar.gz: 63200c83b270a8d840eb88ba3d58a8b615e3f1d4c20aaacb15946ce745f42a90
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: cf8edd51de1582c7b90e9ae07d74277ff093a6ba6d9e896f502bdfab6f94abe1c5d9fb1ed019a8cdf0a700774606595182086dee310659512ac18c17ff8efd16
|
|
7
|
+
data.tar.gz: de8619df52375bf5aa0a544ee18f5849b892ce82f2ae3ebac77c30ca62c5cb9851ff267aff286e0f8eff2b2ce9f225af925d1d6d8a56cb5a7a8fd4ff9371e744
|
data/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,10 @@ section below.
|
|
|
6
6
|
|
|
7
7
|
## Unreleased changes
|
|
8
8
|
|
|
9
|
+
## Version 4.0.0
|
|
10
|
+
|
|
11
|
+
- Make the internal aggregator interface positional-only and injectable through `Client`, with fixed-arity `increment`, `gauge`, and `aggregate_timing` entrypoints for native aggregation backends. Public Client metric methods retain their keyword API.
|
|
12
|
+
|
|
9
13
|
## Version 3.11.2
|
|
10
14
|
|
|
11
15
|
- Widen the `CompiledMetric` tag-combination cache key from 32 to 57 bits, eliminating
|
|
@@ -148,7 +148,7 @@ module StatsD
|
|
|
148
148
|
# @param tags [Hash{String, Symbol => String},Array<String>] The tags to attach to the counter.
|
|
149
149
|
# @param no_prefix [Boolean] If true, the metric will not be prefixed.
|
|
150
150
|
# @return [void]
|
|
151
|
-
def increment(name, value
|
|
151
|
+
def increment(name, value, tags, no_prefix, sample_rate)
|
|
152
152
|
unless thread_healthcheck
|
|
153
153
|
@sink << datagram_builder(no_prefix: no_prefix).c(name, value, sample_rate, tags)
|
|
154
154
|
return
|
|
@@ -224,7 +224,7 @@ module StatsD
|
|
|
224
224
|
end
|
|
225
225
|
end
|
|
226
226
|
|
|
227
|
-
def aggregate_timing(name, value, tags
|
|
227
|
+
def aggregate_timing(name, value, tags, no_prefix, type, sample_rate)
|
|
228
228
|
unless thread_healthcheck
|
|
229
229
|
@sink << datagram_builder(no_prefix: no_prefix).timing_value_packed(
|
|
230
230
|
name, type.to_s, [value], sample_rate, tags
|
|
@@ -249,7 +249,7 @@ module StatsD
|
|
|
249
249
|
do_flush(aggregation_state) if aggregation_state
|
|
250
250
|
end
|
|
251
251
|
|
|
252
|
-
def gauge(name, value, tags
|
|
252
|
+
def gauge(name, value, tags, no_prefix)
|
|
253
253
|
unless thread_healthcheck
|
|
254
254
|
@sink << datagram_builder(no_prefix: no_prefix).g(name, value, CONST_SAMPLE_RATE, tags)
|
|
255
255
|
return
|
|
@@ -19,6 +19,9 @@ module StatsD
|
|
|
19
19
|
# @see StatsD.singleton_client
|
|
20
20
|
# @see #clone_with_options
|
|
21
21
|
class Client
|
|
22
|
+
EMPTY_TAGS = [].freeze
|
|
23
|
+
private_constant :EMPTY_TAGS
|
|
24
|
+
|
|
22
25
|
class << self
|
|
23
26
|
# Instantiates a StatsD::Instrument::Client using configuration values provided in
|
|
24
27
|
# environment variables.
|
|
@@ -31,7 +34,8 @@ module StatsD
|
|
|
31
34
|
default_tags: env.statsd_default_tags,
|
|
32
35
|
implementation: env.statsd_implementation,
|
|
33
36
|
sink: env.default_sink_for_environment,
|
|
34
|
-
datagram_builder_class: datagram_builder_class_for_implementation(implementation)
|
|
37
|
+
datagram_builder_class: datagram_builder_class_for_implementation(implementation),
|
|
38
|
+
aggregator: nil
|
|
35
39
|
)
|
|
36
40
|
new(
|
|
37
41
|
prefix: prefix,
|
|
@@ -42,6 +46,7 @@ module StatsD
|
|
|
42
46
|
datagram_builder_class: datagram_builder_class,
|
|
43
47
|
enable_aggregation: env.experimental_aggregation_enabled?,
|
|
44
48
|
aggregation_flush_interval: env.aggregation_interval,
|
|
49
|
+
aggregator: aggregator,
|
|
45
50
|
)
|
|
46
51
|
end
|
|
47
52
|
|
|
@@ -147,6 +152,16 @@ module StatsD
|
|
|
147
152
|
end
|
|
148
153
|
|
|
149
154
|
# Instantiates a new client.
|
|
155
|
+
#
|
|
156
|
+
# Aggregators use fixed-arity positional +increment+, +gauge+, and
|
|
157
|
+
# +aggregate_timing+ methods. This keeps the aggregation hot path simple
|
|
158
|
+
# and avoids keyword argument forwarding. An injected aggregator is also
|
|
159
|
+
# expected to implement the existing precompiled aggregation methods and
|
|
160
|
+
# +flush+. Calls without tags receive a shared frozen empty array. Other tag
|
|
161
|
+
# values are forwarded as-is; custom backends may require arrays.
|
|
162
|
+
#
|
|
163
|
+
# @param aggregator [#increment, #gauge, #aggregate_timing, nil]
|
|
164
|
+
# Optional external aggregation backend.
|
|
150
165
|
# @see .from_env to instantiate a client using environment variables.
|
|
151
166
|
def initialize(
|
|
152
167
|
prefix: nil,
|
|
@@ -157,7 +172,8 @@ module StatsD
|
|
|
157
172
|
datagram_builder_class: self.class.datagram_builder_class_for_implementation(implementation),
|
|
158
173
|
enable_aggregation: false,
|
|
159
174
|
aggregation_flush_interval: 2.0,
|
|
160
|
-
aggregation_max_context_size: StatsD::Instrument::Aggregator::DEFAULT_MAX_CONTEXT_SIZE
|
|
175
|
+
aggregation_max_context_size: StatsD::Instrument::Aggregator::DEFAULT_MAX_CONTEXT_SIZE,
|
|
176
|
+
aggregator: nil
|
|
161
177
|
)
|
|
162
178
|
@sink = sink
|
|
163
179
|
@datagram_builder_class = datagram_builder_class
|
|
@@ -167,10 +183,11 @@ module StatsD
|
|
|
167
183
|
@default_sample_rate = default_sample_rate
|
|
168
184
|
|
|
169
185
|
@datagram_builder = { false => nil, true => nil }
|
|
170
|
-
@
|
|
186
|
+
@injected_aggregator = aggregator
|
|
187
|
+
@enable_aggregation = enable_aggregation || !aggregator.nil?
|
|
171
188
|
@aggregation_flush_interval = aggregation_flush_interval
|
|
172
189
|
if @enable_aggregation
|
|
173
|
-
@aggregator =
|
|
190
|
+
@aggregator = aggregator ||
|
|
174
191
|
Aggregator.new(
|
|
175
192
|
@sink,
|
|
176
193
|
datagram_builder_class,
|
|
@@ -223,7 +240,7 @@ module StatsD
|
|
|
223
240
|
return StatsD::Instrument::VOID if sample_rate && !sample?(sample_rate)
|
|
224
241
|
|
|
225
242
|
if @enable_aggregation
|
|
226
|
-
@aggregator.increment(name, value, tags
|
|
243
|
+
@aggregator.increment(name, value, tags || EMPTY_TAGS, no_prefix, sample_rate)
|
|
227
244
|
else
|
|
228
245
|
emit(datagram_builder(no_prefix: no_prefix).c(name, value, sample_rate, tags))
|
|
229
246
|
end
|
|
@@ -357,7 +374,7 @@ module StatsD
|
|
|
357
374
|
end
|
|
358
375
|
|
|
359
376
|
if @enable_aggregation
|
|
360
|
-
@aggregator.aggregate_timing(name, value, tags
|
|
377
|
+
@aggregator.aggregate_timing(name, value, tags || EMPTY_TAGS, no_prefix, :ms, sample_rate)
|
|
361
378
|
return StatsD::Instrument::VOID
|
|
362
379
|
end
|
|
363
380
|
emit(datagram_builder(no_prefix: no_prefix).ms(name, value, sample_rate, tags))
|
|
@@ -379,7 +396,7 @@ module StatsD
|
|
|
379
396
|
# @return [void]
|
|
380
397
|
def gauge(name, value, sample_rate: nil, tags: nil, no_prefix: false)
|
|
381
398
|
if @enable_aggregation
|
|
382
|
-
@aggregator.gauge(name, value, tags
|
|
399
|
+
@aggregator.gauge(name, value, tags || EMPTY_TAGS, no_prefix)
|
|
383
400
|
return StatsD::Instrument::VOID
|
|
384
401
|
end
|
|
385
402
|
|
|
@@ -431,14 +448,7 @@ module StatsD
|
|
|
431
448
|
end
|
|
432
449
|
|
|
433
450
|
if @enable_aggregation
|
|
434
|
-
@aggregator.aggregate_timing(
|
|
435
|
-
name,
|
|
436
|
-
value,
|
|
437
|
-
tags: tags,
|
|
438
|
-
no_prefix: no_prefix,
|
|
439
|
-
type: :d,
|
|
440
|
-
sample_rate: sample_rate,
|
|
441
|
-
)
|
|
451
|
+
@aggregator.aggregate_timing(name, value, tags || EMPTY_TAGS, no_prefix, :d, sample_rate)
|
|
442
452
|
return StatsD::Instrument::VOID
|
|
443
453
|
end
|
|
444
454
|
|
|
@@ -467,7 +477,7 @@ module StatsD
|
|
|
467
477
|
end
|
|
468
478
|
|
|
469
479
|
if @enable_aggregation
|
|
470
|
-
@aggregator.aggregate_timing(name, value, tags
|
|
480
|
+
@aggregator.aggregate_timing(name, value, tags || EMPTY_TAGS, no_prefix, :h, sample_rate)
|
|
471
481
|
return StatsD::Instrument::VOID
|
|
472
482
|
end
|
|
473
483
|
|
|
@@ -508,10 +518,10 @@ module StatsD
|
|
|
508
518
|
@aggregator.aggregate_timing(
|
|
509
519
|
name,
|
|
510
520
|
latency_in_ms,
|
|
511
|
-
tags
|
|
512
|
-
no_prefix
|
|
513
|
-
|
|
514
|
-
sample_rate
|
|
521
|
+
tags || EMPTY_TAGS,
|
|
522
|
+
no_prefix,
|
|
523
|
+
metric_type,
|
|
524
|
+
sample_rate,
|
|
515
525
|
)
|
|
516
526
|
else
|
|
517
527
|
emit(datagram_builder(no_prefix: no_prefix).send(metric_type, name, latency_in_ms, sample_rate, tags))
|
|
@@ -598,7 +608,8 @@ module StatsD
|
|
|
598
608
|
prefix: NO_CHANGE,
|
|
599
609
|
default_sample_rate: NO_CHANGE,
|
|
600
610
|
default_tags: NO_CHANGE,
|
|
601
|
-
datagram_builder_class: NO_CHANGE
|
|
611
|
+
datagram_builder_class: NO_CHANGE,
|
|
612
|
+
aggregator: NO_CHANGE
|
|
602
613
|
)
|
|
603
614
|
client = clone_with_options(
|
|
604
615
|
sink: sink,
|
|
@@ -606,6 +617,7 @@ module StatsD
|
|
|
606
617
|
default_sample_rate: default_sample_rate,
|
|
607
618
|
default_tags: default_tags,
|
|
608
619
|
datagram_builder_class: datagram_builder_class,
|
|
620
|
+
aggregator: aggregator,
|
|
609
621
|
)
|
|
610
622
|
|
|
611
623
|
yield(client)
|
|
@@ -616,7 +628,8 @@ module StatsD
|
|
|
616
628
|
prefix: NO_CHANGE,
|
|
617
629
|
default_sample_rate: NO_CHANGE,
|
|
618
630
|
default_tags: NO_CHANGE,
|
|
619
|
-
datagram_builder_class: NO_CHANGE
|
|
631
|
+
datagram_builder_class: NO_CHANGE,
|
|
632
|
+
aggregator: NO_CHANGE
|
|
620
633
|
)
|
|
621
634
|
self.class.new(
|
|
622
635
|
sink: sink == NO_CHANGE ? @sink : sink,
|
|
@@ -627,6 +640,7 @@ module StatsD
|
|
|
627
640
|
datagram_builder_class == NO_CHANGE ? @datagram_builder_class : datagram_builder_class,
|
|
628
641
|
enable_aggregation: @enable_aggregation,
|
|
629
642
|
aggregation_flush_interval: @aggregation_flush_interval,
|
|
643
|
+
aggregator: aggregator == NO_CHANGE ? @injected_aggregator : aggregator,
|
|
630
644
|
)
|
|
631
645
|
end
|
|
632
646
|
|
data/test/aggregator_test.rb
CHANGED
|
@@ -36,8 +36,8 @@ class AggregatorTest < Minitest::Test
|
|
|
36
36
|
end
|
|
37
37
|
|
|
38
38
|
def test_increment_simple
|
|
39
|
-
@subject.increment("foo", 1,
|
|
40
|
-
@subject.increment("foo", 1,
|
|
39
|
+
@subject.increment("foo", 1, ["foo:bar"], false, 1.0)
|
|
40
|
+
@subject.increment("foo", 1, ["foo:bar"], false, 1.0)
|
|
41
41
|
@subject.flush
|
|
42
42
|
|
|
43
43
|
datagram = @sink.datagrams.first
|
|
@@ -48,10 +48,9 @@ class AggregatorTest < Minitest::Test
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def test_increment_with_sample_rate
|
|
51
|
-
|
|
52
|
-
@subject.increment("counter.sampled",
|
|
53
|
-
@subject.increment("counter.
|
|
54
|
-
@subject.increment("counter.unsampled", 1, tags: { foo: "bar" })
|
|
51
|
+
@subject.increment("counter.sampled", 1, ["foo:bar"], false, 0.5)
|
|
52
|
+
@subject.increment("counter.sampled", 2, ["foo:bar"], false, 0.5)
|
|
53
|
+
@subject.increment("counter.unsampled", 1, ["foo:bar"], false, 1.0)
|
|
55
54
|
@subject.flush
|
|
56
55
|
|
|
57
56
|
assert_equal(2, @sink.datagrams.size)
|
|
@@ -68,11 +67,10 @@ class AggregatorTest < Minitest::Test
|
|
|
68
67
|
end
|
|
69
68
|
|
|
70
69
|
def test_increment_different_sample_rates_create_different_aggregation_keys
|
|
71
|
-
|
|
72
|
-
@subject.increment("counter",
|
|
73
|
-
@subject.increment("counter",
|
|
74
|
-
@subject.increment("counter",
|
|
75
|
-
@subject.increment("counter", 20, sample_rate: 0.1)
|
|
70
|
+
@subject.increment("counter", 1, [], false, 0.5)
|
|
71
|
+
@subject.increment("counter", 2, [], false, 0.5)
|
|
72
|
+
@subject.increment("counter", 10, [], false, 0.1)
|
|
73
|
+
@subject.increment("counter", 20, [], false, 0.1)
|
|
76
74
|
@subject.flush
|
|
77
75
|
|
|
78
76
|
assert_equal(2, @sink.datagrams.size)
|
|
@@ -85,8 +83,8 @@ class AggregatorTest < Minitest::Test
|
|
|
85
83
|
end
|
|
86
84
|
|
|
87
85
|
def test_distribution_simple
|
|
88
|
-
@subject.aggregate_timing("foo", 1,
|
|
89
|
-
@subject.aggregate_timing("foo", 100,
|
|
86
|
+
@subject.aggregate_timing("foo", 1, ["foo:bar"], false, :d, 1.0)
|
|
87
|
+
@subject.aggregate_timing("foo", 100, ["foo:bar"], false, :d, 1.0)
|
|
90
88
|
@subject.flush
|
|
91
89
|
|
|
92
90
|
datagram = @sink.datagrams.first
|
|
@@ -96,9 +94,9 @@ class AggregatorTest < Minitest::Test
|
|
|
96
94
|
end
|
|
97
95
|
|
|
98
96
|
def test_timing_sampling_scaling
|
|
99
|
-
@subject.aggregate_timing("timing.sampled", 60.0,
|
|
100
|
-
@subject.aggregate_timing("timing.sampled", 80.0,
|
|
101
|
-
@subject.aggregate_timing("timing.unsampled", 60.0,
|
|
97
|
+
@subject.aggregate_timing("timing.sampled", 60.0, [], false, :d, 0.01)
|
|
98
|
+
@subject.aggregate_timing("timing.sampled", 80.0, [], false, :d, 0.01)
|
|
99
|
+
@subject.aggregate_timing("timing.unsampled", 60.0, [], false, :d, 1.0)
|
|
102
100
|
|
|
103
101
|
@subject.flush
|
|
104
102
|
|
|
@@ -114,11 +112,11 @@ class AggregatorTest < Minitest::Test
|
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
def test_mixed_type_timings
|
|
117
|
-
@subject.aggregate_timing("foo_ms", 1,
|
|
118
|
-
@subject.aggregate_timing("foo_ms", 100,
|
|
115
|
+
@subject.aggregate_timing("foo_ms", 1, ["foo:bar"], false, :ms, 1.0)
|
|
116
|
+
@subject.aggregate_timing("foo_ms", 100, ["foo:bar"], false, :ms, 1.0)
|
|
119
117
|
|
|
120
|
-
@subject.aggregate_timing("foo_d", 100,
|
|
121
|
-
@subject.aggregate_timing("foo_d", 120,
|
|
118
|
+
@subject.aggregate_timing("foo_d", 100, ["foo:bar"], false, :d, 1.0)
|
|
119
|
+
@subject.aggregate_timing("foo_d", 120, ["foo:bar"], false, :d, 1.0)
|
|
122
120
|
|
|
123
121
|
@subject.flush
|
|
124
122
|
|
|
@@ -130,8 +128,8 @@ class AggregatorTest < Minitest::Test
|
|
|
130
128
|
end
|
|
131
129
|
|
|
132
130
|
def test_gauge_simple
|
|
133
|
-
@subject.gauge("foo", 1,
|
|
134
|
-
@subject.gauge("foo", 100,
|
|
131
|
+
@subject.gauge("foo", 1, ["foo:bar"], false)
|
|
132
|
+
@subject.gauge("foo", 100, ["foo:bar"], false)
|
|
135
133
|
@subject.flush
|
|
136
134
|
|
|
137
135
|
datagram = @sink.datagrams.first
|
|
@@ -141,18 +139,18 @@ class AggregatorTest < Minitest::Test
|
|
|
141
139
|
end
|
|
142
140
|
|
|
143
141
|
def test_increment_with_tags_in_different_orders
|
|
144
|
-
@subject.increment("foo", 1,
|
|
145
|
-
@subject.increment("foo", 1,
|
|
142
|
+
@subject.increment("foo", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
143
|
+
@subject.increment("foo", 1, ["tag2:val2", "tag1:val1"], false, 1.0)
|
|
146
144
|
@subject.flush
|
|
147
145
|
|
|
148
146
|
assert_equal(2, @sink.datagrams.first.value)
|
|
149
147
|
end
|
|
150
148
|
|
|
151
149
|
def test_increment_with_different_tag_values
|
|
152
|
-
@subject.increment("foo", 1,
|
|
153
|
-
@subject.increment("foo", 1,
|
|
150
|
+
@subject.increment("foo", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
151
|
+
@subject.increment("foo", 1, { tag1: "val1", tag2: "val2" }, false, 1.0)
|
|
154
152
|
|
|
155
|
-
@subject.increment("bar")
|
|
153
|
+
@subject.increment("bar", 1, [], false, 1.0)
|
|
156
154
|
@subject.flush
|
|
157
155
|
|
|
158
156
|
assert_equal(2, @sink.datagrams.first.value)
|
|
@@ -161,8 +159,8 @@ class AggregatorTest < Minitest::Test
|
|
|
161
159
|
end
|
|
162
160
|
|
|
163
161
|
def test_increment_with_different_metric_names
|
|
164
|
-
@subject.increment("foo", 1,
|
|
165
|
-
@subject.increment("bar", 1,
|
|
162
|
+
@subject.increment("foo", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
163
|
+
@subject.increment("bar", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
166
164
|
@subject.flush
|
|
167
165
|
|
|
168
166
|
assert_equal(1, @sink.datagrams.find { |d| d.name == "foo" }.value)
|
|
@@ -170,22 +168,22 @@ class AggregatorTest < Minitest::Test
|
|
|
170
168
|
end
|
|
171
169
|
|
|
172
170
|
def test_increment_with_different_values
|
|
173
|
-
@subject.increment("foo", 1,
|
|
174
|
-
@subject.increment("foo", 2,
|
|
171
|
+
@subject.increment("foo", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
172
|
+
@subject.increment("foo", 2, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
175
173
|
@subject.flush
|
|
176
174
|
|
|
177
175
|
assert_equal(3, @sink.datagrams.first.value)
|
|
178
176
|
end
|
|
179
177
|
|
|
180
178
|
def test_send_mixed_types_will_pass_through
|
|
181
|
-
@subject.increment("test_counter", 1,
|
|
182
|
-
@subject.aggregate_timing("test_counter", 100,
|
|
179
|
+
@subject.increment("test_counter", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
180
|
+
@subject.aggregate_timing("test_counter", 100, ["tag1:val1", "tag2:val2"], false, :d, 1.0)
|
|
183
181
|
|
|
184
|
-
@subject.gauge("test_gauge", 100,
|
|
185
|
-
@subject.increment("test_gauge", 1,
|
|
182
|
+
@subject.gauge("test_gauge", 100, ["tag1:val1", "tag2:val2"], false)
|
|
183
|
+
@subject.increment("test_gauge", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
186
184
|
|
|
187
|
-
@subject.aggregate_timing("test_timing", 100,
|
|
188
|
-
@subject.gauge("test_timing", 100,
|
|
185
|
+
@subject.aggregate_timing("test_timing", 100, ["tag1:val1", "tag2:val2"], false, :d, 1.0)
|
|
186
|
+
@subject.gauge("test_timing", 100, ["tag1:val1", "tag2:val2"], false)
|
|
189
187
|
@subject.flush
|
|
190
188
|
|
|
191
189
|
assert_equal(6, @sink.datagrams.size)
|
|
@@ -202,10 +200,10 @@ class AggregatorTest < Minitest::Test
|
|
|
202
200
|
def test_with_prefix
|
|
203
201
|
aggregator = StatsD::Instrument::Aggregator.new(@sink, StatsD::Instrument::DatagramBuilder, "MyApp", [])
|
|
204
202
|
|
|
205
|
-
aggregator.increment("foo", 1,
|
|
206
|
-
aggregator.increment("foo", 1,
|
|
203
|
+
aggregator.increment("foo", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
204
|
+
aggregator.increment("foo", 1, ["tag1:val1", "tag2:val2"], false, 1.0)
|
|
207
205
|
|
|
208
|
-
aggregator.increment("foo", 1,
|
|
206
|
+
aggregator.increment("foo", 1, ["tag1:val1", "tag2:val2"], true, 1.0)
|
|
209
207
|
aggregator.flush
|
|
210
208
|
|
|
211
209
|
assert_equal(2, @sink.datagrams.size)
|
|
@@ -217,14 +215,11 @@ class AggregatorTest < Minitest::Test
|
|
|
217
215
|
end
|
|
218
216
|
|
|
219
217
|
def test_finalizer_with_prefix
|
|
220
|
-
# Test that the finalizer correctly uses the prefix when flushing metrics
|
|
221
|
-
# IMPORTANT: Use empty tags to ensure datagram_builder is not pre-created by tags_sorted
|
|
222
218
|
aggregator = StatsD::Instrument::Aggregator.new(@sink, StatsD::Instrument::DatagramBuilder, "MyApp", [])
|
|
223
219
|
|
|
224
|
-
aggregator.increment("foo", 1,
|
|
225
|
-
aggregator.increment("bar", 1,
|
|
220
|
+
aggregator.increment("foo", 1, [], false, 1.0)
|
|
221
|
+
aggregator.increment("bar", 1, [], true, 1.0)
|
|
226
222
|
|
|
227
|
-
# Manually trigger the finalizer (simulates GC cleanup)
|
|
228
223
|
finalizer = StatsD::Instrument::Aggregator.finalize(
|
|
229
224
|
aggregator.instance_variable_get(:@finalizer),
|
|
230
225
|
)
|
|
@@ -242,18 +237,15 @@ class AggregatorTest < Minitest::Test
|
|
|
242
237
|
end
|
|
243
238
|
|
|
244
239
|
def test_synchronous_operation_on_thread_failure
|
|
245
|
-
# Force thread_healthcheck to return false
|
|
246
240
|
@subject.stubs(:thread_healthcheck).returns(false)
|
|
247
241
|
|
|
248
|
-
# Stub methods on @aggregation_state to ensure they are not called
|
|
249
242
|
aggregation_state = @subject.instance_variable_get(:@aggregation_state)
|
|
250
243
|
aggregation_state.stubs(:[]=).never
|
|
251
244
|
|
|
252
|
-
@subject.increment("foo", 1,
|
|
253
|
-
@subject.aggregate_timing("bar", 100,
|
|
254
|
-
@subject.gauge("baz", 100,
|
|
245
|
+
@subject.increment("foo", 1, ["foo:bar"], false, 1.0)
|
|
246
|
+
@subject.aggregate_timing("bar", 100, ["foo:bar"], false, :d, 1.0)
|
|
247
|
+
@subject.gauge("baz", 100, ["foo:bar"], false)
|
|
255
248
|
|
|
256
|
-
# Verify metrics were sent immediately
|
|
257
249
|
assert_equal(3, @sink.datagrams.size)
|
|
258
250
|
|
|
259
251
|
counter_datagram = @sink.datagrams.find { |d| d.name == "foo" }
|
|
@@ -268,11 +260,9 @@ class AggregatorTest < Minitest::Test
|
|
|
268
260
|
assert_equal(100, gauge_datagram.value)
|
|
269
261
|
assert_equal(["foo:bar"], gauge_datagram.tags)
|
|
270
262
|
|
|
271
|
-
|
|
272
|
-
@subject.
|
|
273
|
-
@subject.aggregate_timing("bar", 200, tags: { foo: "bar" }, sample_rate: 0.5)
|
|
263
|
+
@subject.increment("foo", 1, ["foo:bar"], false, 1.0)
|
|
264
|
+
@subject.aggregate_timing("bar", 200, ["foo:bar"], false, :d, 0.5)
|
|
274
265
|
|
|
275
|
-
# Verify new metrics were also sent immediately
|
|
276
266
|
assert_equal(5, @sink.datagrams.size)
|
|
277
267
|
|
|
278
268
|
counter_datagram = @sink.datagrams.select { |d| d.name == "foo" }.last
|
|
@@ -287,65 +277,52 @@ class AggregatorTest < Minitest::Test
|
|
|
287
277
|
after_aggregation_state = @subject.instance_variable_get(:@aggregation_state)
|
|
288
278
|
assert_same(after_aggregation_state, aggregation_state)
|
|
289
279
|
|
|
290
|
-
# undo the stubbing
|
|
291
280
|
@subject.unstub(:thread_healthcheck)
|
|
292
281
|
end
|
|
293
282
|
|
|
294
283
|
def test_recreate_thread_after_fork
|
|
295
284
|
skip("#{RUBY_ENGINE} not supported for this test. Reason: fork()") if RUBY_ENGINE != "ruby"
|
|
296
|
-
|
|
297
|
-
@subject.
|
|
298
|
-
@subject.aggregate_timing("bar", 100, tags: { foo: "bar" })
|
|
285
|
+
@subject.increment("foo", 1, ["foo:bar"], false, 1.0)
|
|
286
|
+
@subject.aggregate_timing("bar", 100, ["foo:bar"], false, :d, 1.0)
|
|
299
287
|
|
|
300
|
-
# kill the flush thread
|
|
301
288
|
@subject.instance_variable_get(:@flush_thread).kill
|
|
302
289
|
|
|
303
|
-
# Fork the process
|
|
304
290
|
pid = Process.fork do
|
|
305
|
-
|
|
306
|
-
@subject.
|
|
307
|
-
@subject.aggregate_timing("bar", 200, tags: { foo: "bar" })
|
|
291
|
+
@subject.increment("foo", 2, ["foo:bar"], false, 1.0)
|
|
292
|
+
@subject.aggregate_timing("bar", 200, ["foo:bar"], false, :d, 1.0)
|
|
308
293
|
@subject.flush
|
|
309
294
|
|
|
310
295
|
assert_equal(2, @sink.datagrams.size)
|
|
311
296
|
exit!
|
|
312
297
|
end
|
|
313
298
|
|
|
314
|
-
# Wait for forked process to complete
|
|
315
299
|
Process.wait(pid)
|
|
316
300
|
|
|
317
|
-
|
|
318
|
-
@subject.
|
|
319
|
-
@subject.aggregate_timing("bar", 300, tags: { foo: "bar" })
|
|
301
|
+
@subject.increment("foo", 3, ["foo:bar"], false, 1.0)
|
|
302
|
+
@subject.aggregate_timing("bar", 300, ["foo:bar"], false, :d, 1.0)
|
|
320
303
|
@subject.flush
|
|
321
304
|
|
|
322
305
|
assert_equal(2, @sink.datagrams.size)
|
|
323
306
|
|
|
324
|
-
# Verify metrics were properly aggregated in parent process
|
|
325
307
|
counter_datagrams = @sink.datagrams.select { |d| d.name == "foo" }
|
|
326
308
|
timing_datagrams = @sink.datagrams.select { |d| d.name == "bar" }
|
|
327
309
|
|
|
328
310
|
assert_equal(1, counter_datagrams.size)
|
|
329
311
|
assert_equal(1, timing_datagrams.size)
|
|
330
312
|
|
|
331
|
-
# Aggregate despite fork
|
|
332
313
|
assert_equal(4, counter_datagrams.last.value)
|
|
333
314
|
assert_equal([100.0, 300.0], timing_datagrams.last.value)
|
|
334
315
|
end
|
|
335
316
|
|
|
336
317
|
def test_race_condition_during_forking
|
|
337
318
|
skip("#{RUBY_ENGINE} not supported for this test. Reason: fork()") if RUBY_ENGINE != "ruby"
|
|
338
|
-
|
|
339
|
-
@subject.
|
|
340
|
-
@subject.aggregate_timing("before_fork.timing", 100, tags: { foo: "bar" })
|
|
319
|
+
@subject.increment("before_fork.count", 1, ["foo:bar"], false, 1.0)
|
|
320
|
+
@subject.aggregate_timing("before_fork.timing", 100, ["foo:bar"], false, :d, 1.0)
|
|
341
321
|
|
|
342
|
-
# Fork the process
|
|
343
322
|
pid = Process.fork do
|
|
344
|
-
|
|
345
|
-
@subject.
|
|
346
|
-
@subject.aggregate_timing("in_child.timing", 200, tags: { foo: "bar" })
|
|
323
|
+
@subject.increment("in_child.count", 2, ["foo:bar"], false, 1.0)
|
|
324
|
+
@subject.aggregate_timing("in_child.timing", 200, ["foo:bar"], false, :d, 1.0)
|
|
347
325
|
|
|
348
|
-
# Simulate thread waiting for flush
|
|
349
326
|
sleep(0.1)
|
|
350
327
|
@subject.flush
|
|
351
328
|
|
|
@@ -353,20 +330,16 @@ class AggregatorTest < Minitest::Test
|
|
|
353
330
|
exit!
|
|
354
331
|
end
|
|
355
332
|
|
|
356
|
-
# Call flush concurrently in parent process
|
|
357
333
|
@subject.flush
|
|
358
334
|
|
|
359
|
-
# Wait for forked process to complete
|
|
360
335
|
Process.wait(pid)
|
|
361
336
|
|
|
362
|
-
|
|
363
|
-
@subject.
|
|
364
|
-
@subject.aggregate_timing("after_fork.timing", 300, tags: { foo: "bar" })
|
|
337
|
+
@subject.increment("after_fork.count", 3, ["foo:bar"], false, 1.0)
|
|
338
|
+
@subject.aggregate_timing("after_fork.timing", 300, ["foo:bar"], false, :d, 1.0)
|
|
365
339
|
@subject.flush
|
|
366
340
|
|
|
367
341
|
assert_equal(4, @sink.datagrams.size)
|
|
368
342
|
|
|
369
|
-
# Verify metrics were properly aggregated in parent process
|
|
370
343
|
counter_datagrams = @sink.datagrams.select { |d| d.name == "before_fork.count" }
|
|
371
344
|
timing_datagrams = @sink.datagrams.select { |d| d.name == "before_fork.count" }
|
|
372
345
|
assert_equal(
|
|
@@ -376,7 +349,6 @@ class AggregatorTest < Minitest::Test
|
|
|
376
349
|
)
|
|
377
350
|
assert_equal(1, timing_datagrams.size)
|
|
378
351
|
|
|
379
|
-
# After fork metrics
|
|
380
352
|
counter_datagrams = @sink.datagrams.select { |d| d.name == "after_fork.count" }
|
|
381
353
|
timing_datagrams = @sink.datagrams.select { |d| d.name == "after_fork.count" }
|
|
382
354
|
assert_equal(1, counter_datagrams.size)
|
|
@@ -384,18 +356,16 @@ class AggregatorTest < Minitest::Test
|
|
|
384
356
|
end
|
|
385
357
|
|
|
386
358
|
def test_finalizer_flushes_pending_metrics
|
|
387
|
-
@subject.increment("foo", 1,
|
|
388
|
-
@subject.aggregate_timing("bar", 100,
|
|
389
|
-
@subject.gauge("baz", 100,
|
|
390
|
-
@subject.aggregate_timing("sampled_timing", 100,
|
|
359
|
+
@subject.increment("foo", 1, ["foo:bar"], false, 1.0)
|
|
360
|
+
@subject.aggregate_timing("bar", 100, ["foo:bar"], false, :d, 1.0)
|
|
361
|
+
@subject.gauge("baz", 100, ["foo:bar"], false)
|
|
362
|
+
@subject.aggregate_timing("sampled_timing", 100, ["foo:bar"], false, :d, 0.01)
|
|
391
363
|
|
|
392
|
-
# Manually trigger the finalizer
|
|
393
364
|
finalizer = StatsD::Instrument::Aggregator.finalize(
|
|
394
365
|
@subject.instance_variable_get(:@finalizer),
|
|
395
366
|
)
|
|
396
367
|
finalizer.call
|
|
397
368
|
|
|
398
|
-
# Verify that all pending metrics are sent
|
|
399
369
|
assert_equal(4, @sink.datagrams.size)
|
|
400
370
|
|
|
401
371
|
counter_datagram = @sink.datagrams.find { |d| d.name == "foo" }
|
|
@@ -418,13 +388,11 @@ class AggregatorTest < Minitest::Test
|
|
|
418
388
|
def test_aggregator_finalizer_is_called_on_gc
|
|
419
389
|
skip_on_jruby("JRuby's GC does not guarantee finalizers are called promptly")
|
|
420
390
|
|
|
421
|
-
5.times { @subject.increment("foo", 1,
|
|
391
|
+
5.times { @subject.increment("foo", 1, ["foo:bar"], false, 1.0) }
|
|
422
392
|
|
|
423
393
|
@subject.instance_variable_get(:@flush_thread)&.kill
|
|
424
|
-
# Unbind the aggregator to allow GC to collect it.
|
|
425
394
|
@subject = nil
|
|
426
395
|
|
|
427
|
-
# We expect the finalizer to flush the aggregated metrics.
|
|
428
396
|
assert_empty(@sink.datagrams)
|
|
429
397
|
|
|
430
398
|
5.times do
|
|
@@ -441,10 +409,11 @@ class AggregatorTest < Minitest::Test
|
|
|
441
409
|
end
|
|
442
410
|
|
|
443
411
|
def test_aggregator_finalizer_is_called_on_gc_after_aggregation_state_move
|
|
444
|
-
(StatsD::Instrument::Aggregator::DEFAULT_MAX_CONTEXT_SIZE + 5).times
|
|
412
|
+
(StatsD::Instrument::Aggregator::DEFAULT_MAX_CONTEXT_SIZE + 5).times do
|
|
413
|
+
@subject.aggregate_timing("foo", 1, [], false, :d, 1.0)
|
|
414
|
+
end
|
|
445
415
|
assert_equal(1, @sink.datagrams.size)
|
|
446
416
|
|
|
447
|
-
# Unbind the aggregator to allow GC to collect it.
|
|
448
417
|
@subject = nil
|
|
449
418
|
5.times do
|
|
450
419
|
GC.start(full_mark: true, immediate_mark: true, immediate_sweep: true)
|
|
@@ -468,10 +437,9 @@ class AggregatorTest < Minitest::Test
|
|
|
468
437
|
|
|
469
438
|
old_trap = Signal.trap("USR1") do
|
|
470
439
|
signal_received = true
|
|
471
|
-
|
|
472
|
-
@subject.
|
|
473
|
-
@subject.
|
|
474
|
-
@subject.aggregate_timing("trap_timing", 100)
|
|
440
|
+
@subject.increment("trap_counter", 1, [], false, 1.0)
|
|
441
|
+
@subject.gauge("trap_gauge", 42, [], false)
|
|
442
|
+
@subject.aggregate_timing("trap_timing", 100, [], false, :d, 1.0)
|
|
475
443
|
|
|
476
444
|
metrics_sent_in_trap = @sink.datagrams.map(&:name)
|
|
477
445
|
end
|
data/test/client_test.rb
CHANGED
|
@@ -102,6 +102,70 @@ class ClientTest < Minitest::Test
|
|
|
102
102
|
assert_equal(true, !datagram.nil?)
|
|
103
103
|
end
|
|
104
104
|
|
|
105
|
+
def test_aggregation_preserves_block_timing_and_return_value
|
|
106
|
+
sink = StatsD::Instrument::CaptureSink.new(parent: StatsD::Instrument::NullSink.new)
|
|
107
|
+
client = StatsD::Instrument::Client.new(sink: sink, enable_aggregation: true)
|
|
108
|
+
Process.stubs(:clock_gettime).with(Process::CLOCK_MONOTONIC, :float_millisecond).returns(100.0, 125.0)
|
|
109
|
+
|
|
110
|
+
result = client.distribution("latency", tags: ["route:cart"]) { :result }
|
|
111
|
+
|
|
112
|
+
assert_equal(:result, result)
|
|
113
|
+
client.force_flush
|
|
114
|
+
datagram = sink.datagrams.find { |d| d.name == "latency" }
|
|
115
|
+
assert_equal(25.0, datagram.value)
|
|
116
|
+
ensure
|
|
117
|
+
client&.instance_variable_get(:@aggregator)&.instance_variable_get(:@flush_thread)&.kill
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def test_injected_aggregator_receives_positional_calls
|
|
121
|
+
aggregator = mock("aggregator")
|
|
122
|
+
tags = ["route:cart"]
|
|
123
|
+
aggregator.expects(:increment).with("requests", 2, tags, false, nil)
|
|
124
|
+
aggregator.expects(:flush)
|
|
125
|
+
|
|
126
|
+
client = StatsD::Instrument::Client.new(
|
|
127
|
+
aggregator: aggregator,
|
|
128
|
+
sink: StatsD::Instrument::NullSink.new,
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
client.increment("requests", 2, tags: tags)
|
|
132
|
+
client.force_flush
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def test_aggregation_supports_existing_compiled_metrics
|
|
136
|
+
sink = StatsD::Instrument::CaptureSink.new(parent: StatsD::Instrument::NullSink.new)
|
|
137
|
+
client = StatsD::Instrument::Client.new(sink: sink, enable_aggregation: true)
|
|
138
|
+
old_client = StatsD.singleton_client
|
|
139
|
+
StatsD.singleton_client = client
|
|
140
|
+
metric = Class.new(StatsD::Instrument::CompiledMetric::Counter) do
|
|
141
|
+
define(name: "compiled.requests")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
metric.increment(2)
|
|
145
|
+
client.force_flush
|
|
146
|
+
|
|
147
|
+
datagram = sink.datagrams.find { |d| d.name == "compiled.requests" }
|
|
148
|
+
assert_equal(2, datagram.value)
|
|
149
|
+
ensure
|
|
150
|
+
StatsD.singleton_client = old_client
|
|
151
|
+
client&.instance_variable_get(:@aggregator)&.instance_variable_get(:@flush_thread)&.kill
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def test_clone_with_options_preserves_aggregator
|
|
155
|
+
sink = StatsD::Instrument::CaptureSink.new(parent: StatsD::Instrument::NullSink.new)
|
|
156
|
+
client = StatsD::Instrument::Client.new(sink: sink, enable_aggregation: true)
|
|
157
|
+
clone = client.clone_with_options(prefix: "clone")
|
|
158
|
+
|
|
159
|
+
clone.increment("requests")
|
|
160
|
+
clone.force_flush
|
|
161
|
+
|
|
162
|
+
datagram = sink.datagrams.find { |d| d.name == "clone.requests" }
|
|
163
|
+
assert_equal(1, datagram.value)
|
|
164
|
+
ensure
|
|
165
|
+
client&.instance_variable_get(:@aggregator)&.instance_variable_get(:@flush_thread)&.kill
|
|
166
|
+
clone&.instance_variable_get(:@aggregator)&.instance_variable_get(:@flush_thread)&.kill
|
|
167
|
+
end
|
|
168
|
+
|
|
105
169
|
def test_capture
|
|
106
170
|
inner_datagrams = nil
|
|
107
171
|
|