logstash-filter-throttle 3.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 49222db6e126acebec2ed52567967ab4ae8a6d43
4
- data.tar.gz: e28c425f6b7be002bb1f1441fba0745e7ad7bfb8
3
+ metadata.gz: 29516e66e87030588ab5f56191b0ac346efbaaa7
4
+ data.tar.gz: 74a3f0a23b80d19244eca960b00bd5f80898b253
5
5
  SHA512:
6
- metadata.gz: f4604cb0222bced5174872b4a6b7c908b70a972b77d84128151722f4230f28dd78ddbeb444a8250e67393d72a7f173db00bfa83aa2182a117ea617c35d4a5478
7
- data.tar.gz: d9c69b7b908c8fdfb5e4a2c9a2291eab3333c8cc3850af0d8d2125357e6e6e686f4a57ebb1840b6d1c7e106dc981247faf63d73b833fa5905032a7601a436e33
6
+ metadata.gz: a992934db1063a273159ff44ab7f00bba78f51a8c144b508c5d84c287ca10f53c1a4fb77143dffee7c018737bea7e2865bb414f050ab64ee879cf1032434c218
7
+ data.tar.gz: 035919b7fd35b9274c1e099946f4f36299a671d5cbc76559ec5efebd5aa4e225fbc6c0079c61b9dae855ecb3ed3c58d2775a84f2a05cce059b2b6d8832b78921
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 4.0.0
2
+ - Full reimplementation of the plugin. The plugin is now thread-safe and properly tracks past events.
3
+ - Updated tests and added runtime dependencies
4
+
1
5
  ## 3.0.2
2
6
  - Relax constraint on logstash-core-plugin-api to >= 1.60 <= 2.99
3
7
 
data/CONTRIBUTORS CHANGED
@@ -6,6 +6,7 @@ Contributors:
6
6
  * Pier-Hugues Pellerin (ph)
7
7
  * Richard Pijnenburg (electrical)
8
8
  * Suyog Rao (suyograo)
9
+ * Frank de Jong (frapex)
9
10
 
10
11
  Note: If you've sent us patches, bug reports, or otherwise contributed to
11
12
  Logstash, and you aren't on the list above and want to be, please let us know
@@ -1,18 +1,22 @@
1
1
  require "logstash/filters/base"
2
2
  require "logstash/namespace"
3
+ require "thread_safe"
4
+ require "atomic"
3
5
 
4
- # The throttle filter is for throttling the number of events received. The filter
5
- # is configured with a lower bound, the `before_count`, and upper bound, the `after_count`,
6
- # and a period of time. All events passing through the filter will be counted based on
7
- # a key. As long as the count is less than the `before_count` or greater than the
8
- # `after_count`, the event will be "throttled" which means the filter will be considered
9
- # successful and any tags or fields will be added.
6
+ # The throttle filter is for throttling the number of events. The filter is
7
+ # configured with a lower bound, the "before_count", and upper bound, the "after_count",
8
+ # and a period of time. All events passing through the filter will be counted based on
9
+ # their key and the event timestamp. As long as the count is less than the "before_count"
10
+ # or greater than the "after_count", the event will be "throttled" which means the filter
11
+ # will be considered successful and any tags or fields will be added (or removed).
10
12
  #
11
- # For example, if you wanted to throttle events so you only receive an event after 2
12
- # occurrences and you get no more than 3 in 10 minutes, you would use the
13
- # configuration:
13
+ # The plugin is thread-safe and properly tracks past events.
14
+ #
15
+ # For example, if you wanted to throttle events so you only receive an event after 2
16
+ # occurrences and you get no more than 3 in 10 minutes, you would use the configuration:
14
17
  # [source,ruby]
15
18
  # period => 600
19
+ # max_age => 1200
16
20
  # before_count => 3
17
21
  # after_count => 5
18
22
  #
@@ -35,10 +39,11 @@ require "logstash/namespace"
35
39
  # event 6 - throttled (successful filter)
36
40
  # ...
37
41
  # ==========================
38
- # Another example is if you wanted to throttle events so you only receive 1 event per
39
- # hour, you would use the configuration:
42
+ # Another example is if you wanted to throttle events so you only
43
+ # receive 1 event per hour, you would use the configuration:
40
44
  # [source,ruby]
41
45
  # period => 3600
46
+ # max_age => 7200
42
47
  # before_count => -1
43
48
  # after_count => 1
44
49
  #
@@ -56,8 +61,8 @@ require "logstash/namespace"
56
61
  # event 4 - throttled (successful filter)
57
62
  # ...
58
63
  # ==========================
59
- # A common use case would be to use the throttle filter to throttle events before 3 and
60
- # after 5 while using multiple fields for the key and then use the drop filter to remove
64
+ # A common use case would be to use the throttle filter to throttle events before 3 and
65
+ # after 5 while using multiple fields for the key and then use the drop filter to remove
61
66
  # throttled events. This configuration might appear as:
62
67
  # [source,ruby]
63
68
  # filter {
@@ -65,6 +70,7 @@ require "logstash/namespace"
65
70
  # before_count => 3
66
71
  # after_count => 5
67
72
  # period => 3600
73
+ # max_age => 7200
68
74
  # key => "%{host}%{message}"
69
75
  # add_tag => "throttled"
70
76
  # }
@@ -73,8 +79,8 @@ require "logstash/namespace"
73
79
  # }
74
80
  # }
75
81
  #
76
- # Another case would be to store all events, but only email non-throttled
77
- # events so the op's inbox isn't flooded with emails in the event of a system error.
82
+ # Another case would be to store all events, but only email non-throttled events
83
+ # so the op's inbox isn't flooded with emails in the event of a system error.
78
84
  # This configuration might appear as:
79
85
  # [source,ruby]
80
86
  # filter {
@@ -82,6 +88,7 @@ require "logstash/namespace"
82
88
  # before_count => 3
83
89
  # after_count => 5
84
90
  # period => 3600
91
+ # max_age => 7200
85
92
  # key => "%{message}"
86
93
  # add_tag => "throttled"
87
94
  # }
@@ -89,12 +96,12 @@ require "logstash/namespace"
89
96
  # output {
90
97
  # if "throttled" not in [tags] {
91
98
  # email {
92
- # from => "logstash@mycompany.com"
93
- # subject => "Production System Alert"
94
- # to => "ops@mycompany.com"
95
- # via => "sendmail"
96
- # body => "Alert on %{host} from path %{path}:\n\n%{message}"
97
- # options => { "location" => "/usr/sbin/sendmail" }
99
+ # from => "logstash@mycompany.com"
100
+ # subject => "Production System Alert"
101
+ # to => "ops@mycompany.com"
102
+ # via => "sendmail"
103
+ # body => "Alert on %{host} from path %{path}:\n\n%{message}"
104
+ # options => { "location" => "/usr/sbin/sendmail" }
98
105
  # }
99
106
  # }
100
107
  # elasticsearch_http {
@@ -103,161 +110,200 @@ require "logstash/namespace"
103
110
  # }
104
111
  # }
105
112
  #
106
- # The event counts are cleared after the configured period elapses since the
107
- # first instance of the event. That is, all the counts don't reset at the same
108
- # time but rather the throttle period is per unique key value.
113
+ # When an event is received, the event key is stored in a key_cache. The key references
114
+ # a timeslot_cache. The event is allocated to a timeslot (created dynamically) based on
115
+ # the timestamp of the event. The timeslot counter is incremented. When the next event is
116
+ # received (same key), within the same "period", it is allocated to the same timeslot.
117
+ # The timeslot counter is incremented once again.
118
+ #
119
+ # The timeslot expires if the maximum age has been exceeded. The age is calculated
120
+ # based on the latest event timestamp and the max_age configuration option.
121
+ #
122
+ # ---[::.. DESIGN ..::]---
109
123
  #
124
+ # +- [key_cache] -+ +-- [timeslot_cache] --+
125
+ # | | | @created: 1439839636 |
126
+ # | @latest: 1439839836 |
127
+ # [a.b.c] => +----------------------+
128
+ # | [1439839636] => 1 |
129
+ # | [1439839736] => 3 |
130
+ # | [1439839836] => 2 |
131
+ # +----------------------+
132
+ #
133
+ # +-- [timeslot_cache] --+
134
+ # | @created: eeeeeeeeee |
135
+ # | @latest: llllllllll |
136
+ # [x.y.z] => +----------------------+
137
+ # | [0000000060] => x |
138
+ # | [0000000120] => y |
139
+ # | | | [..........] => N |
140
+ # +---------------+ +----------------------+
141
+ #
142
+ # Frank de Jong (@frapex)
110
143
  # Mike Pilone (@mikepilone)
111
- #
112
- class LogStash::Filters::Throttle < LogStash::Filters::Base
144
+ #
145
+
146
+ class ThreadSafe::TimeslotCache < ThreadSafe::Cache
147
+ attr_reader :created
148
+
149
+ def initialize(epoch, options = nil, &block)
150
+ @created = epoch
151
+ @latest = Atomic.new(epoch)
152
+
153
+ super(options, &block)
154
+ end
113
155
 
156
+ def latest
157
+ @latest.value
158
+ end
159
+
160
+ def latest=(val)
161
+ # only update if greater than current
162
+ @latest.update { |v| v = (val > v) ? val : v }
163
+ end
164
+ end
165
+
166
+ class LogStash::Filters::Throttle < LogStash::Filters::Base
114
167
  # The name to use in configuration files.
115
168
  config_name "throttle"
116
169
 
170
+ # The memory control mechanism automatically ajusts the maximum age
171
+ # of a timeslot based on the maximum number of counters.
172
+ MC_MIN_PCT = 5 # Lower bound percentage.
173
+ MC_MAX_PCT = 100 # Upper bound percentage.
174
+ MC_INCR_PCT = 80 # Increase if total below percentage.
175
+ MC_STEP_PCT = 5 # Increase/decrease by this percentage at a time.
176
+
177
+ # Call the filter flush method at regular interval. It is used by the memory
178
+ # control mechanism. Set to false if you like your VM to go (B)OOM.
179
+ config :periodic_flush, :validate => :boolean, :default => true
117
180
 
118
- # The key used to identify events. Events with the same key will be throttled
119
- # as a group. Field substitutions are allowed, so you can combine multiple
120
- # fields.
181
+ # The key used to identify events. Events with the same key are grouped together.
182
+ # Field substitutions are allowed, so you can combine multiple fields.
121
183
  config :key, :validate => :string, :required => true
122
-
123
- # Events less than this count will be throttled. Setting this value to -1, the
124
- # default, will cause no messages to be throttled based on the lower bound.
184
+
185
+ # Events less than this count will be throttled. Setting this value to -1, the
186
+ # default, will cause no events to be throttled based on the lower bound.
125
187
  config :before_count, :validate => :number, :default => -1, :required => false
126
-
127
- # Events greater than this count will be throttled. Setting this value to -1, the
128
- # default, will cause no messages to be throttled based on the upper bound.
188
+
189
+ # Events greater than this count will be throttled. Setting this value to -1, the
190
+ # default, will cause no events to be throttled based on the upper bound.
129
191
  config :after_count, :validate => :number, :default => -1, :required => false
130
-
131
- # The period in seconds after the first occurrence of an event until the count is
132
- # reset for the event. This period is tracked per unique key value. Field
133
- # substitutions are allowed in this value. They will be evaluated when the _first_
134
- # event for a given key is seen. This allows you to specify that certain kinds
135
- # of events throttle for a specific period.
136
- config :period, :validate => :string, :default => "3600", :required => false
137
-
138
- # The maximum number of counters to store before the oldest counter is purged. Setting
139
- # this value to -1 will prevent an upper bound no constraint on the number of counters
140
- # and they will only be purged after expiration. This configuration value should only
141
- # be used as a memory control mechanism and can cause early counter expiration if the
142
- # value is reached. It is recommended to leave the default value and ensure that your
143
- # key is selected such that it limits the number of counters required (i.e. don't
144
- # use UUID as the key!)
192
+
193
+ # The period in seconds after the first occurrence of an event until a new timeslot
194
+ # is created. This period is tracked per unique key and per timeslot.
195
+ # Field substitutions are allowed in this value. This allows you to specify that
196
+ # certain kinds of events throttle for a specific period of time.
197
+ config :period, :validate => :string, :default => "60", :required => false
198
+
199
+ # The maximum age of a timeslot. Higher values allow better tracking of an asynchronous
200
+ # flow of events, but require more memory. As a rule of thumb you should set this value
201
+ # to at least twice the period. Or set this value to period + maximum time offset
202
+ # between unordered events with the same key. Values below the specified period give
203
+ # unexpected results if unordered events are processed simultaneously.
204
+ config :max_age, :validate => :number, :default => 3600, :required => false
205
+
206
+ # The maximum number of counters to store before decreasing the maximum age of a timeslot.
207
+ # Setting this value to -1 will prevent an upper bound with no constraint on the
208
+ # number of counters. This configuration value should only be used as a memory
209
+ # control mechanism and can cause early counter expiration if the value is reached.
210
+ # It is recommended to leave the default value and ensure that your key is selected
211
+ # such that it limits the number of counters required (i.e. don't use UUID as the key).
145
212
  config :max_counters, :validate => :number, :default => 100000, :required => false
146
213
 
147
- # Performs initialization of the filter.
214
+ # performs initialization of the filter
148
215
  public
149
216
  def register
150
- @threadsafe = false
151
-
152
- @event_counters = Hash.new
153
- @next_expiration = nil
217
+ @key_cache = ThreadSafe::Cache.new
218
+ @max_age_orig = @max_age
154
219
  end # def register
155
220
 
156
- # Filters the event. The filter is successful if the event should be throttled.
221
+ # filters the event
157
222
  public
158
223
  def filter(event)
159
-
160
- # Return nothing unless there's an actual filter event
161
-
162
-
163
- now = Time.now
164
- key = event.sprintf(@key)
165
-
166
- # Purge counters if too large to prevent OOM.
167
- if @max_counters != -1 && @event_counters.size > @max_counters then
168
- purgeOldestEventCounter()
169
- end
170
-
171
- # Expire existing counter if needed
172
- if @next_expiration.nil? || now >= @next_expiration then
173
- expireEventCounters(now)
224
+ key = event.sprintf(@key) # substitute field
225
+ period = event.sprintf(@period).to_i # substitute period
226
+ period = 60 if period == 0 # fallback if unparsable
227
+ epoch = event.timestamp.to_i # event epoch time
228
+
229
+ @key_cache.compute_if_absent(key) do # initialise timeslot cache
230
+ ThreadSafe::TimeslotCache.new(epoch) # and add to key cache
174
231
  end
175
-
232
+
233
+ timeslot_cache = @key_cache[key] # get timeslot cache
234
+ timeslot_cache.latest = epoch # update to latest epoch
235
+
236
+ # find target timeslot
237
+ timeslot_key = epoch - (epoch - timeslot_cache.created) % period
238
+
239
+ # initialise timeslot and counter (if required)
240
+ timeslot_cache.compute_if_absent(timeslot_key) { Atomic.new(0) }
241
+
242
+ timeslot = timeslot_cache[timeslot_key] # get timeslot
243
+ timeslot.update { |v| v + 1 } # increment counter
244
+ count = timeslot.value # get latest counter value
245
+
176
246
  @logger.debug? and @logger.debug(
177
- "filters/#{self.class.name}: next expiration",
178
- { "next_expiration" => @next_expiration })
179
-
180
- # Create new counter for this event if this is the first occurrence
181
- counter = nil
182
- if !@event_counters.include?(key) then
183
- period = event.sprintf(@period).to_i
184
- period = 3600 if period == 0
185
- expiration = now + period
186
- @event_counters[key] = { :count => 0, :expiration => expiration }
187
-
188
- @logger.debug? and @logger.debug("filters/#{self.class.name}: new event",
189
- { :key => key, :expiration => expiration })
190
- end
191
-
192
- # Fetch the counter
193
- counter = @event_counters[key]
194
-
195
- # Count this event
196
- counter[:count] = counter[:count] + 1;
197
-
198
- @logger.debug? and @logger.debug("filters/#{self.class.name}: current count",
199
- { :key => key, :count => counter[:count] })
200
-
201
- # Throttle if count is < before count or > after count
202
- if ((@before_count != -1 && counter[:count] < @before_count) ||
203
- (@after_count != -1 && counter[:count] > @after_count)) then
247
+ "filters/#{self.class.name}: counter incremented",
248
+ { key: key, epoch: epoch, timeslot: timeslot_key, count: count }
249
+ )
250
+
251
+ # throttle event if counter value not in range
252
+ if ((@before_count != -1 && count < @before_count) ||
253
+ (@after_count != -1 && count > @after_count))
204
254
  @logger.debug? and @logger.debug(
205
- "filters/#{self.class.name}: throttling event", { :key => key })
206
-
255
+ "filters/#{self.class.name}: throttling event",
256
+ { key: key, epoch: epoch }
257
+ )
258
+
207
259
  filter_matched(event)
208
260
  end
209
-
261
+
262
+ # Delete expired timeslots older than the latest. Do not use variable
263
+ # timeslot_cache.latest for this. If used, it might delete the latest timeslot.
264
+ latest_timeslot = timeslot_cache.keys.max || 0
265
+ timeslot_cache.each_key { |key| timeslot_cache.delete(key) if key < (latest_timeslot - @max_age) }
210
266
  end # def filter
211
-
212
- # Expires any counts where the period has elapsed. Sets the next expiration time
213
- # for when this method should be called again.
214
- private
215
- def expireEventCounters(now)
216
-
217
- @next_expiration = nil
218
-
219
- @event_counters.delete_if do |key, counter|
220
- expiration = counter[:expiration]
221
- expired = expiration <= now
222
-
223
- if expired then
224
- @logger.debug? and @logger.debug(
225
- "filters/#{self.class.name}: deleting expired counter",
226
- { :key => key })
227
-
228
- elsif @next_expiration.nil? || (expiration < @next_expiration)
229
- @next_expiration = expiration
267
+
268
+ public
269
+ def flush(options = {})
270
+ max_latest = 0 # get maximum epoch
271
+ @key_cache.each_value { |tc| max_latest = tc.latest if tc.latest > max_latest }
272
+
273
+ total_counters = 0
274
+ @key_cache.each_pair do |key,timeslot_cache|
275
+ if timeslot_cache.latest < max_latest - @max_age
276
+ @key_cache.delete(key) # delete expired timeslot cache
277
+ else
278
+ total_counters += timeslot_cache.size # get total number of counters
230
279
  end
231
-
232
- expired
233
280
  end
234
-
235
- end # def expireEventCounters
236
-
237
- # Purges the oldest event counter. This operation is for memory control only
238
- # and can cause early period expiration and thrashing if invoked.
239
- private
240
- def purgeOldestEventCounter()
241
-
242
- # Return unless we have something to purge
243
- return unless @event_counters.size > 0
244
-
245
- oldestCounter = nil
246
- oldestKey = nil
247
-
248
- @event_counters.each do |key, counter|
249
- if oldestCounter.nil? || counter[:expiration] < oldestCounter[:expiration] then
250
- oldestKey = key;
251
- oldestCounter = counter;
281
+
282
+ @logger.debug? and @logger.debug(
283
+ "filters/#{self.class.name}: statistics",
284
+ { total_counters: total_counters, max_age: @max_age }
285
+ )
286
+
287
+ # memory control mechanism
288
+ if @max_counters != -1
289
+ over_limit = total_counters - @max_counters
290
+
291
+ # decrease max age of timeslot cache by x percent
292
+ if (over_limit > 0) && (@max_age > @max_age_orig * MC_MIN_PCT / 100)
293
+ @max_age -= @max_age_orig * MC_STEP_PCT / 100
294
+ @logger.warn? and @logger.warn(
295
+ "filters/#{self.class.name}: Decreased timeslot max_age to #{@max_age} because " +
296
+ "max_counters exceeded by #{over_limit}. Use a better key to prevent too many unique event counters.")
297
+
298
+ # increase max age of timeslot cache by x percent
299
+ elsif (@max_age < @max_age_orig * MC_MAX_PCT / 100) && (total_counters < (@max_counters * MC_INCR_PCT / 100))
300
+ @max_age += @max_age_orig * MC_STEP_PCT / 100
301
+ @logger.warn? and @logger.warn(
302
+ "filters/#{self.class.name}: Increased timeslot max_age to #{@max_age} because max_counters no longer exceeded.")
252
303
  end
253
304
  end
254
-
255
- @logger.warn? and @logger.warn(
256
- "filters/#{self.class.name}: Purging oldest counter because max_counters " +
257
- "exceeded. Use a better key to prevent too many unique event counters.",
258
- { :key => oldestKey, :expiration => oldestCounter[:expiration] })
259
-
260
- @event_counters.delete(oldestKey)
261
-
262
- end
305
+
306
+ return
307
+ end # def flush
308
+
263
309
  end # class LogStash::Filters::Throttle
@@ -1,7 +1,7 @@
1
1
  Gem::Specification.new do |s|
2
2
 
3
3
  s.name = 'logstash-filter-throttle'
4
- s.version = '3.0.2'
4
+ s.version = '4.0.0'
5
5
  s.licenses = ['Apache License (2.0)']
6
6
  s.summary = "The throttle filter is for throttling the number of events received."
7
7
  s.description = "This gem is a Logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/logstash-plugin install gemname. This gem is not a stand-alone program"
@@ -21,7 +21,8 @@ Gem::Specification.new do |s|
21
21
 
22
22
  # Gem dependencies
23
23
  s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
24
+ s.add_runtime_dependency "thread_safe"
25
+ s.add_runtime_dependency "atomic"
24
26
 
25
27
  s.add_development_dependency 'logstash-devutils'
26
28
  end
27
-
@@ -163,14 +163,13 @@ describe LogStash::Filters::Throttle do
163
163
  }
164
164
  end
165
165
  end
166
-
167
- describe "max_counter exceeded" do
166
+
167
+ describe "correct timeslot assigned/calculated, after_count exceeded" do
168
168
  config <<-CONFIG
169
169
  filter {
170
170
  throttle {
171
171
  period => 60
172
172
  after_count => 1
173
- max_counters => 2
174
173
  key => "%{message}"
175
174
  add_tag => [ "throttled" ]
176
175
  }
@@ -178,19 +177,67 @@ describe LogStash::Filters::Throttle do
178
177
  CONFIG
179
178
 
180
179
  events = [{
181
- "message" => "foo"
180
+ "@timestamp" => "2016-07-09T00:05:00.000Z",
181
+ "message" => "server1"
182
182
  }, {
183
- "message" => "bar"
183
+ "@timestamp" => "2016-07-09T00:05:59.000Z",
184
+ "message" => "server1"
184
185
  }, {
185
- "message" => "poo"
186
+ "@timestamp" => "2016-07-09T00:10:33.000Z",
187
+ "message" => "server1"
186
188
  }, {
187
- "message" => "foo"
189
+ "@timestamp" => "2016-07-09T00:10:34.000Z",
190
+ "message" => "server1"
191
+ }, {
192
+ "@timestamp" => "2016-07-09T00:00:00.000Z",
193
+ "message" => "server1"
194
+ }, {
195
+ "@timestamp" => "2016-07-09T00:00:45.000Z",
196
+ "message" => "server1"
188
197
  }]
189
198
 
190
199
  sample events do
191
- insist { subject[3].get("tags") } == nil
200
+ insist { subject[0].get("tags") } == nil
201
+ insist { subject[1].get("tags") } == [ "throttled" ]
202
+ insist { subject[2].get("tags") } == nil
203
+ insist { subject[3].get("tags") } == [ "throttled" ]
204
+ insist { subject[4].get("tags") } == nil
205
+ insist { subject[5].get("tags") } == [ "throttled" ]
192
206
  end
193
207
  end
194
208
 
195
- end # LogStash::Filters::Throttle
209
+ describe "asynchronous input, after_count exceeded" do
210
+ config <<-CONFIG
211
+ filter {
212
+ throttle {
213
+ period => 60
214
+ after_count => 1
215
+ key => "%{message}"
216
+ add_tag => [ "throttled" ]
217
+ }
218
+ }
219
+ CONFIG
220
+
221
+ events = [{
222
+ "@timestamp" => "2016-07-09T00:01:00.000Z",
223
+ "message" => "server1"
224
+ }, {
225
+ "@timestamp" => "2016-07-09T00:00:30.000Z",
226
+ "message" => "server1"
227
+ }, {
228
+ "@timestamp" => "2016-07-09T00:01:59.000Z",
229
+ "message" => "server1"
230
+ }, {
231
+ "@timestamp" => "2016-07-09T00:00:59.000Z",
232
+ "message" => "server1"
233
+ }]
234
+
235
+ sample events do
236
+ insist { subject[0].get("tags") } == nil
237
+ insist { subject[1].get("tags") } == nil
238
+ insist { subject[2].get("tags") } == [ "throttled" ]
239
+ insist { subject[3].get("tags") } == [ "throttled" ]
240
+ end
241
+ end
196
242
 
243
+ end # LogStash::Filters::Throttle
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-throttle
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.2
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-07-14 00:00:00.000000000 Z
11
+ date: 2016-07-31 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +30,34 @@ dependencies:
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - ">="
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ name: thread_safe
40
+ prerelease: false
41
+ type: :runtime
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ name: atomic
54
+ prerelease: false
55
+ type: :runtime
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
33
61
  - !ruby/object:Gem::Dependency
34
62
  requirement: !ruby/object:Gem::Requirement
35
63
  requirements:
@@ -81,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
109
  version: '0'
82
110
  requirements: []
83
111
  rubyforge_project:
84
- rubygems_version: 2.6.3
112
+ rubygems_version: 2.4.8
85
113
  signing_key:
86
114
  specification_version: 4
87
115
  summary: The throttle filter is for throttling the number of events received.