fluent-plugin-prometheus 2.2.1 → 2.3.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.
@@ -50,6 +50,94 @@ describe Fluent::Plugin::Prometheus::ExpandBuilder::PlaceholderExpander do
50
50
  expander.expand('${hostname}')
51
51
  end
52
52
 
53
+ # tag_parts, tag_prefix and tag_suffix must be expanded with the tag,
54
+ # not with "forged"
55
+ context 'with a value named after a tag placeholder' do
56
+ let(:forged_placeholder) do
57
+ {
58
+ 'tag' => '1.2.3',
59
+ 'tag_parts' => %w[forged forged forged],
60
+ 'tag_prefix' => %w[forged forged forged],
61
+ 'tag_suffix' => %w[forged forged forged],
62
+ }
63
+ end
64
+
65
+ it 'expands the placeholders with the tag' do
66
+ expander = builder.build(forged_placeholder)
67
+
68
+ expect(expander.expand('${tag_parts[0]}.${tag_parts[1]}.${tag_parts[2]}')).to eq('1.2.3')
69
+ expect(expander.expand('${tag_parts[-3]}.${tag_parts[-2]}.${tag_parts[-1]}')).to eq('1.2.3')
70
+ expect(expander.expand('${tag_prefix[0]},${tag_prefix[1]},${tag_prefix[2]}')).to eq('1,1.2,1.2.3')
71
+ expect(expander.expand('${tag_suffix[0]},${tag_suffix[1]},${tag_suffix[2]}')).to eq('3,2.3,1.2.3')
72
+ end
73
+
74
+ it 'does not expand an index which the tag does not have' do
75
+ # tag_prefix and tag_suffix have no negative index, so they are kept
76
+ # as they are. This checks that the record does not fill them.
77
+ expander = builder.build(forged_placeholder)
78
+
79
+ expect(expander.expand('${tag_prefix[-1]}')).to eq('${tag_prefix[-1]}')
80
+ expect(expander.expand('${tag_suffix[-1]}')).to eq('${tag_suffix[-1]}')
81
+
82
+ # the tag has 3 parts, so the 4th value of the record is out of it.
83
+ longer = forged_placeholder.merge('tag_parts' => %w[forged forged forged forged])
84
+ expander = builder.build(longer)
85
+
86
+ expect(expander.expand('${tag_parts[3]}')).to eq('${tag_parts[3]}')
87
+ expect(expander.expand('${tag_parts[-4]}')).to eq('${tag_parts[-4]}')
88
+ end
89
+ end
90
+
91
+ # a record may also have a key named "tag_parts[0]", which makes the
92
+ # same placeholder as the tag
93
+ context 'with a value whose key is a tag placeholder' do
94
+ it 'expands the placeholders with the tag' do
95
+ spelled = {
96
+ 'tag' => '1.2.3',
97
+ 'tag_parts[0]' => 'forged',
98
+ 'tag_prefix[0]' => 'forged',
99
+ 'tag_suffix[0]' => 'forged',
100
+ }
101
+ expander = builder.build(spelled)
102
+
103
+ expect(expander.expand('${tag_parts[0]}')).to eq('1')
104
+ expect(expander.expand('${tag_prefix[0]}')).to eq('1')
105
+ expect(expander.expand('${tag_suffix[0]}')).to eq('3')
106
+ end
107
+
108
+ it 'does not expand an index which the tag does not have' do
109
+ spelled = {
110
+ 'tag' => '1',
111
+ 'tag_parts[1]' => 'forged',
112
+ 'tag_parts[-2]' => 'forged',
113
+ 'tag_prefix[-1]' => 'forged',
114
+ 'tag_suffix[-1]' => 'forged',
115
+ }
116
+ expander = builder.build(spelled)
117
+
118
+ expect(expander.expand('${tag_parts[1]}')).to eq('${tag_parts[1]}')
119
+ expect(expander.expand('${tag_parts[-2]}')).to eq('${tag_parts[-2]}')
120
+ expect(expander.expand('${tag_prefix[-1]}')).to eq('${tag_prefix[-1]}')
121
+ expect(expander.expand('${tag_suffix[-1]}')).to eq('${tag_suffix[-1]}')
122
+ end
123
+
124
+ it 'keeps the tag itself' do
125
+ spelled = {
126
+ 'tag' => '1.2.3',
127
+ 'tag_parts' => 'forged',
128
+ 'tag_prefix' => 'forged',
129
+ 'tag_suffix' => 'forged',
130
+ }
131
+ expander = builder.build(spelled)
132
+
133
+ expect(expander.expand('${tag}')).to eq('1.2.3')
134
+ # these are not built from the tag, so they are kept as they are
135
+ expect(expander.expand('${tag_parts}')).to eq('${tag_parts}')
136
+ expect(expander.expand('${tag_prefix}')).to eq('${tag_prefix}')
137
+ expect(expander.expand('${tag_suffix}')).to eq('${tag_suffix}')
138
+ end
139
+ end
140
+
53
141
  context 'when not found placeholder' do
54
142
  it 'prints wanring log and as it is' do
55
143
  expect(log).to receive(:warn).with('unknown placeholder `${tag_prefix[100]}` found').once
@@ -87,6 +175,14 @@ describe Fluent::Plugin::Prometheus::ExpandBuilder::PlaceholderExpander do
87
175
  expect(expander.expand('${tag_suffix[0]}.${tag_suffix[1]}.${tag_suffix[2]}', dynamic_placeholders: dynamic_placeholder)).to eq('3.2.3.1.2.3')
88
176
  end
89
177
 
178
+ it 'expands the placeholders with the dynamic tag' do
179
+ forged = static_placeholder.merge('tag_parts' => %w[forged forged forged])
180
+ expander = builder.build(forged)
181
+
182
+ expect(expander.expand('${tag_parts[0]}.${tag_parts[1]}.${tag_parts[2]}',
183
+ dynamic_placeholders: dynamic_placeholder)).to eq('1.2.3')
184
+ end
185
+
90
186
  it 'does not create expander twice if given the same placeholder' do
91
187
  builder # cached before mock
92
188
 
@@ -0,0 +1,469 @@
1
+ require 'spec_helper'
2
+
3
+ # The limit is exercised through the plugins as well, by the 'limits label
4
+ # expansion' shared examples. These examples stay at the Metric level, where a
5
+ # slot can be observed while an instrumentation is still running.
6
+ describe Fluent::Plugin::Prometheus::Metric do
7
+ let(:registry) { ::Prometheus::Client::Registry.new }
8
+ let(:max_series_per_metric) { 1 }
9
+ let(:element) do
10
+ Fluent::Config::Element.new(
11
+ 'metric', '',
12
+ {
13
+ 'name' => 'limited',
14
+ 'type' => 'counter',
15
+ 'desc' => 'Something foo.',
16
+ 'key' => 'foo',
17
+ 'max_series_per_metric' => max_series_per_metric.to_s,
18
+ },
19
+ [Fluent::Config::Element.new('labels', '', {'path' => '$.path'}, [])]
20
+ )
21
+ end
22
+ # the label is a RecordAccessor, so no placeholder is expanded here
23
+ let(:expander) { double('expander') }
24
+ let(:metric) { Fluent::Plugin::Prometheus::Counter.new(element, registry, {}, {}) }
25
+ # the client metric is registered by the Metric, so it has to be built before
26
+ # the registry is asked for it
27
+ let(:client_counter) do
28
+ metric
29
+ registry.get(:limited)
30
+ end
31
+
32
+ def instrument(path, value = 1)
33
+ metric.instrument({'foo' => value, 'path' => path}, expander)
34
+ end
35
+
36
+ def build_metrics(*elements)
37
+ Fluent::Plugin::Prometheus.parse_metrics_elements(
38
+ Fluent::Config::Element.new('ROOT', '', {}, elements), registry, {}, {}
39
+ )
40
+ end
41
+
42
+ # A <metric> section named 'shared', so that two of them instrument the same
43
+ # client metric.
44
+ def counter_element(limit, initlabels = nil)
45
+ attributes = {
46
+ 'name' => 'shared',
47
+ 'type' => 'counter',
48
+ 'desc' => 'Something foo.',
49
+ 'key' => 'foo',
50
+ 'max_series_per_metric' => limit.to_s,
51
+ }
52
+ attributes['initialized'] = 'true' if initlabels
53
+
54
+ Fluent::Config::Element.new(
55
+ 'metric', '', attributes,
56
+ [Fluent::Config::Element.new('labels', '', {'path' => '$.path'}, [])] +
57
+ Array(initlabels).map { |path| Fluent::Config::Element.new('initlabels', '', {'path' => path}, []) }
58
+ )
59
+ end
60
+
61
+ describe 'max_series_per_metric' do
62
+ it 'refuses a new label set once the limit is reached' do
63
+ instrument('/a')
64
+
65
+ expect { instrument('/b') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
66
+ expect(client_counter.values.keys).to eq([{path: '/a'}])
67
+ end
68
+
69
+ it 'gives the slot back when the instrumentation failed' do
70
+ # a negative value makes Counter#increment raise, after the label set has
71
+ # been reserved
72
+ expect { instrument('/a', -1) }.to raise_error(ArgumentError)
73
+
74
+ expect { instrument('/b') }.not_to raise_error
75
+ expect(client_counter.values.keys).to eq([{path: '/b'}])
76
+ end
77
+
78
+ it 'does not take a slot for a value which is not a number' do
79
+ # such a value is refused before the label set is reserved, so the metric
80
+ # is left as if the record had never arrived
81
+ expect { instrument('/a', 'not a number') }.to raise_error(ArgumentError)
82
+
83
+ expect { instrument('/b') }.not_to raise_error
84
+ expect(client_counter.values.keys).to eq([{path: '/b'}])
85
+ end
86
+
87
+ it 'takes the slot before instrumenting, so that concurrent calls cannot both pass' do
88
+ # the slot has to be taken under the same lock as the check: taking it
89
+ # after the client call would let both label sets through and expand the
90
+ # metric beyond max_series_per_metric
91
+ instrumenting = Queue.new
92
+ resume = Queue.new
93
+ allow(client_counter).to receive(:increment).and_wrap_original do |original, *args, **kwargs|
94
+ instrumenting << true
95
+ resume.pop
96
+ original.call(*args, **kwargs)
97
+ end
98
+
99
+ first = Thread.new { instrument('/a') }
100
+ instrumenting.pop # '/a' is inside the client call and holds the only slot
101
+
102
+ expect { instrument('/b') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
103
+
104
+ resume << true
105
+ first.join
106
+
107
+ expect(client_counter.values.keys).to eq([{path: '/a'}])
108
+ end
109
+
110
+ # Two records which expand to the very same label set may be instrumented
111
+ # at the same time: only one of them reserves the slot, the other one joins
112
+ # that reservation. Giving the slot back on failure must then not drop a
113
+ # label set the client already holds, otherwise a new one would take its
114
+ # place and the metric would grow past max_series_per_metric.
115
+ context 'when concurrent instrumentations share a label set' do
116
+ # stalls the very first client call, so that a second instrumentation can
117
+ # be run while the first one is still in flight
118
+ def stall_first_instrumentation(entered, resume)
119
+ stalled = false
120
+ allow(client_counter).to receive(:increment).and_wrap_original do |original, *args, **kwargs|
121
+ unless stalled
122
+ stalled = true
123
+ entered << true
124
+ resume.pop
125
+ end
126
+ original.call(*args, **kwargs)
127
+ end
128
+ end
129
+
130
+ it 'keeps the slot when the call which reserved it fails after another one succeeded' do
131
+ entered = Queue.new
132
+ resume = Queue.new
133
+ stall_first_instrumentation(entered, resume)
134
+
135
+ # a negative value is the one which fails inside the client call: a
136
+ # value which is not a number never gets there, so it could not be
137
+ # stalled
138
+ failing = Thread.new do
139
+ expect { instrument('/a', -1) }.to raise_error(ArgumentError)
140
+ end
141
+ entered.pop # {path: '/a'} is reserved by the record which is about to fail
142
+
143
+ # joins that reservation and does give the label set to the client
144
+ instrument('/a')
145
+
146
+ resume << true
147
+ failing.join
148
+
149
+ # the client holds {path: '/a'}, so its slot must stay taken
150
+ expect { instrument('/b') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
151
+ expect(client_counter.values.keys).to eq([{path: '/a'}])
152
+ end
153
+
154
+ it 'keeps the slot when a call which joined a reservation fails' do
155
+ entered = Queue.new
156
+ resume = Queue.new
157
+ stall_first_instrumentation(entered, resume)
158
+
159
+ pending_call = Thread.new { instrument('/a') }
160
+ entered.pop # {path: '/a'} is reserved and being instrumented
161
+
162
+ # joins that reservation and fails in the client, without owning the slot
163
+ expect { instrument('/a', -1) }.to raise_error(ArgumentError)
164
+
165
+ resume << true
166
+ pending_call.join
167
+
168
+ expect { instrument('/b') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
169
+ expect(client_counter.values.keys).to eq([{path: '/a'}])
170
+ end
171
+
172
+ it 'takes the slot back when a joined call succeeds after the reservation was released' do
173
+ failing_entered = Queue.new
174
+ failing_resume = Queue.new
175
+ succeeding_entered = Queue.new
176
+ succeeding_resume = Queue.new
177
+ allow(client_counter).to receive(:increment).and_wrap_original do |original, *args, **kwargs|
178
+ case kwargs[:by]
179
+ when -1
180
+ failing_entered << true
181
+ failing_resume.pop
182
+ when 2
183
+ succeeding_entered << true
184
+ succeeding_resume.pop
185
+ end
186
+ original.call(*args, **kwargs)
187
+ end
188
+
189
+ failing = Thread.new do
190
+ expect { instrument('/a', -1) }.to raise_error(ArgumentError)
191
+ end
192
+ failing_entered.pop # {path: '/a'} is reserved
193
+
194
+ succeeding = Thread.new { instrument('/a', 2) }
195
+ succeeding_entered.pop # joined the reservation, the client has nothing yet
196
+
197
+ failing_resume << true
198
+ failing.join # the reservation is given back here
199
+
200
+ succeeding_resume << true
201
+ succeeding.join # from now on the client holds {path: '/a'}
202
+
203
+ expect { instrument('/b') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
204
+ expect(client_counter.values.keys).to eq([{path: '/a'}])
205
+ end
206
+ end
207
+ end
208
+
209
+ # Summary#observe increments the count and the sum one after the other, so a
210
+ # value which is not a number leaves the count incremented and raises on the
211
+ # sum. Giving the slot back would not take that half instrumented label set
212
+ # away from the client, so the value is refused before it reaches either.
213
+ describe 'a summary of a value which is not a number' do
214
+ let(:element) do
215
+ Fluent::Config::Element.new(
216
+ 'metric', '',
217
+ {
218
+ 'name' => 'limited',
219
+ 'type' => 'summary',
220
+ 'desc' => 'Something foo.',
221
+ 'key' => 'foo',
222
+ 'max_series_per_metric' => max_series_per_metric.to_s,
223
+ },
224
+ [Fluent::Config::Element.new('labels', '', {'path' => '$.path'}, [])]
225
+ )
226
+ end
227
+ let(:metric) { Fluent::Plugin::Prometheus::Summary.new(element, registry, {}, {}) }
228
+ let(:client_summary) do
229
+ metric
230
+ registry.get(:limited)
231
+ end
232
+
233
+ it 'leaves the client holding nothing' do
234
+ expect { instrument('/a', 'not a number') }.to raise_error(ArgumentError)
235
+
236
+ expect(client_summary.values).to be_empty
237
+ end
238
+
239
+ it 'does not take a slot' do
240
+ expect { instrument('/a', 'not a number') }.to raise_error(ArgumentError)
241
+
242
+ expect { instrument('/b', 2) }.not_to raise_error
243
+ expect(client_summary.values.keys).to eq([{path: '/b'}])
244
+ end
245
+ end
246
+
247
+ describe 'a metric name shared by two <metric> sections' do
248
+ # both sections instrument the same client metric, so counting per section
249
+ # would let it hold max_series_per_metric label sets twice over
250
+ let(:max_series_per_metric) { 2 }
251
+ let(:other_metric) { Fluent::Plugin::Prometheus::Counter.new(element, registry, {}, {}) }
252
+
253
+ def instrument_other(path, value = 1)
254
+ other_metric.instrument({'foo' => value, 'path' => path}, expander)
255
+ end
256
+
257
+ it 'wraps one and the same client metric' do
258
+ expect(other_metric.instance_variable_get(:@counter))
259
+ .to equal(metric.instance_variable_get(:@counter))
260
+ end
261
+
262
+ it 'counts the label sets of both sections against one limit' do
263
+ instrument('/a')
264
+ instrument_other('/b')
265
+
266
+ # the metric is full, whichever section the next record goes through
267
+ expect { instrument_other('/c') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
268
+ expect { instrument('/d') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
269
+ expect(client_counter.values.keys).to contain_exactly({path: '/a'}, {path: '/b'})
270
+ end
271
+
272
+ it 'lets both sections instrument a label set the metric already holds' do
273
+ instrument('/a')
274
+
275
+ expect { instrument_other('/a', 2) }.not_to raise_error
276
+ expect(client_counter.values[{path: '/a'}]).to eq(3)
277
+ end
278
+ end
279
+
280
+ describe 'initialized true with <initlabels>' do
281
+ let(:initlabels) { ['/a', '/b', '/c'] }
282
+ let(:element) do
283
+ Fluent::Config::Element.new(
284
+ 'metric', '',
285
+ {
286
+ 'name' => 'limited',
287
+ 'type' => 'counter',
288
+ 'desc' => 'Something foo.',
289
+ 'key' => 'foo',
290
+ 'initialized' => 'true',
291
+ 'max_series_per_metric' => max_series_per_metric.to_s,
292
+ },
293
+ [Fluent::Config::Element.new('labels', '', {'path' => '$.path'}, [])] +
294
+ initlabels.map { |path| Fluent::Config::Element.new('initlabels', '', {'path' => path}, []) }
295
+ )
296
+ end
297
+
298
+ context 'with a limit below the number of <initlabels> label sets' do
299
+ # 3 label sets exist at startup, so a limit of 1 would drop every record
300
+ let(:max_series_per_metric) { 1 }
301
+
302
+ it 'stops at startup instead of dropping every record' do
303
+ expect { build_metrics(element) }
304
+ .to raise_error(Fluent::ConfigError,
305
+ /already holds 3 label sets from <initlabels>.*max_series_per_metric is 1/)
306
+ end
307
+ end
308
+
309
+ context 'with a limit equal to the number of <initlabels> label sets' do
310
+ # every label set is known in advance, so the limit is reached but no
311
+ # record is dropped
312
+ let(:max_series_per_metric) { 3 }
313
+
314
+ it 'accepts the config' do
315
+ expect { build_metrics(element) }.not_to raise_error
316
+ end
317
+
318
+ it 'still counts a record on an <initlabels> label set' do
319
+ instrument('/a')
320
+
321
+ expect(client_counter.values[{path: '/a'}]).to eq(1)
322
+ end
323
+
324
+ it 'refuses a label set which is not in <initlabels>' do
325
+ expect { instrument('/d') }.to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
326
+ end
327
+ end
328
+
329
+ context 'with two <initlabels> holding the same values' do
330
+ # both make the same label set, so they take one slot
331
+ let(:initlabels) { ['/a', '/a'] }
332
+ let(:max_series_per_metric) { 1 }
333
+
334
+ it 'counts the label sets and not the <initlabels> blocks' do
335
+ expect { build_metrics(element) }.not_to raise_error
336
+ end
337
+ end
338
+
339
+ context 'without a limit' do
340
+ let(:max_series_per_metric) { 0 }
341
+
342
+ it 'accepts any number of <initlabels> label sets' do
343
+ expect { build_metrics(element) }.not_to raise_error
344
+ expect { instrument('/d') }.not_to raise_error
345
+ end
346
+ end
347
+ end
348
+
349
+ describe '<initlabels> shared by <metric> sections with the same name' do
350
+ # Every section with this name instruments the same client metric. The
351
+ # label sets from <initlabels> count in every section, even in one that
352
+ # declares none. A section with no limit still adds its own to the count.
353
+ let(:five_initlabels) { ['/a', '/b', '/c', '/d', '/e'] }
354
+
355
+ it 'refuses a section that has no <initlabels>' do
356
+ expect { build_metrics(counter_element(10, five_initlabels), counter_element(3)) }
357
+ .to raise_error(Fluent::ConfigError,
358
+ /already holds 5 label sets from <initlabels>.*max_series_per_metric is 3/)
359
+ end
360
+
361
+ it 'counts the <initlabels> of a section that has no limit' do
362
+ expect { build_metrics(counter_element(0, five_initlabels), counter_element(3, ['/z'])) }
363
+ .to raise_error(Fluent::ConfigError,
364
+ /already holds 6 label sets from <initlabels>.*max_series_per_metric is 3/)
365
+ end
366
+
367
+ it 'does not depend on the order of the sections' do
368
+ expect { build_metrics(counter_element(3), counter_element(10, five_initlabels)) }
369
+ .to raise_error(Fluent::ConfigError, /max_series_per_metric is 3/)
370
+ end
371
+
372
+ it 'accepts a config when every limit fits the shared label sets' do
373
+ expect { build_metrics(counter_element(5, five_initlabels), counter_element(10)) }
374
+ .not_to raise_error
375
+ end
376
+
377
+ it 'counts them against the limit at runtime as well' do
378
+ # the section with no limit puts them on the client, so the section with
379
+ # a limit has to see them: none of its six slots is left
380
+ _unlimited, limited = build_metrics(counter_element(0, five_initlabels),
381
+ counter_element(6, ['/z']))
382
+
383
+ expect { limited.instrument({'foo' => 1, 'path' => '/new'}, expander) }
384
+ .to raise_error(Fluent::Plugin::Prometheus::LabelSetLimitError)
385
+ end
386
+ end
387
+
388
+ describe 'reading the configuration again' do
389
+ # A reload builds the <metric> sections again against the registry the
390
+ # process already has, so the client metric keeps its label sets. Reading
391
+ # the configuration twice here does the same.
392
+ def instrument_through(metric, path)
393
+ metric.instrument({'foo' => 1, 'path' => path}, expander)
394
+ end
395
+
396
+ it 'does not count the label sets that records brought' do
397
+ # the section with the wider limit fills five slots of the set both
398
+ # sections share, which leaves none for the limit of three
399
+ wide, _narrow = build_metrics(counter_element(10), counter_element(3))
400
+ ['/a', '/b', '/c', '/d', '/e'].each { |path| instrument_through(wide, path) }
401
+
402
+ expect { build_metrics(counter_element(10), counter_element(3)) }.not_to raise_error
403
+ end
404
+
405
+ it 'accepts the same <initlabels> again' do
406
+ metric, = build_metrics(counter_element(3, ['/a', '/b', '/c']))
407
+ instrument_through(metric, '/a')
408
+
409
+ expect { build_metrics(counter_element(3, ['/a', '/b', '/c'])) }.not_to raise_error
410
+ end
411
+
412
+ it 'keeps counting an <initlabels> label set a record came on' do
413
+ # the client still holds the three label sets, so a limit of 2 does not
414
+ # fit them even though a record made one of them look like its own
415
+ metric, = build_metrics(counter_element(3, ['/a', '/b', '/c']))
416
+ instrument_through(metric, '/a')
417
+
418
+ expect { build_metrics(counter_element(2, ['/a', '/b', '/c'])) }
419
+ .to raise_error(Fluent::ConfigError,
420
+ /already holds 3 label sets from <initlabels>.*max_series_per_metric is 2/)
421
+ end
422
+ end
423
+
424
+ describe '<metric> overriding the plugin limit' do
425
+ # the plugin is configured with 100, which <metric> has to win over
426
+ let(:metric) do
427
+ Fluent::Plugin::Prometheus::Counter.new(element, registry, {}, {max_series_per_metric: 100})
428
+ end
429
+
430
+ it 'narrows down the limit given to the plugin' do
431
+ expect(metric.max_series_per_metric).to eq(1)
432
+ end
433
+
434
+ context 'with a limit above the one given to the plugin' do
435
+ let(:max_series_per_metric) { 1000 }
436
+
437
+ it 'widens the limit given to the plugin' do
438
+ expect(metric.max_series_per_metric).to eq(1000)
439
+ end
440
+ end
441
+
442
+ context 'with 0' do
443
+ let(:max_series_per_metric) { 0 }
444
+
445
+ it 'lifts the limit given to the plugin' do
446
+ expect(metric.max_series_per_metric).to eq(0)
447
+ end
448
+ end
449
+
450
+ context 'without a limit in <metric>' do
451
+ let(:element) do
452
+ Fluent::Config::Element.new(
453
+ 'metric', '',
454
+ {
455
+ 'name' => 'limited',
456
+ 'type' => 'counter',
457
+ 'desc' => 'Something foo.',
458
+ 'key' => 'foo',
459
+ },
460
+ [Fluent::Config::Element.new('labels', '', {'path' => '$.path'}, [])]
461
+ )
462
+ end
463
+
464
+ it 'falls back to the limit given to the plugin' do
465
+ expect(metric.max_series_per_metric).to eq(100)
466
+ end
467
+ end
468
+ end
469
+ end