logstash-filter-math 1.0 → 1.1.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.
@@ -0,0 +1,128 @@
1
+ # encoding: utf-8
2
+ require "logstash/util/loggable"
3
+
4
+ module LogStash module Filters
5
+ module MathCalculationElements
6
+ REGISTER_REFERENCE_RE = /^MEM\[(\d+)]$/
7
+
8
+ def self.build(reference, position, register)
9
+ case reference
10
+ when Numeric
11
+ if position == 3
12
+ # literal reference for result element
13
+ nil
14
+ else
15
+ LiteralElement.new(reference, position)
16
+ end
17
+ when String
18
+ match = REGISTER_REFERENCE_RE.match(reference)
19
+ if match
20
+ RegisterElement.new(reference, position, match[1].to_i, register)
21
+ else
22
+ FieldElement.new(reference, position)
23
+ end
24
+ else
25
+ nil
26
+ end
27
+ end
28
+
29
+ class RegisterElement
30
+ # supports `get` and `set`
31
+ def initialize(reference, position, index, register)
32
+ @reference = reference
33
+ @position = position
34
+ @index = index
35
+ @register = register
36
+ @description = (position == 3 ? "#{@index}" : "operand #{@position}").prepend("register ").concat(": '#{@reference}'")
37
+ end
38
+
39
+ def literal?
40
+ false
41
+ end
42
+
43
+ def set(value, event)
44
+ # raise usage error if called when position != 3 ??
45
+ @register[@index] = value
46
+ end
47
+
48
+ def get(event)
49
+ @register[@index] #log warning if nil
50
+ end
51
+
52
+ def inspect
53
+ "\"#{@description}\""
54
+ end
55
+
56
+ def to_s
57
+ @description
58
+ end
59
+ end
60
+
61
+ class FieldElement
62
+ include LogStash::Util::Loggable
63
+ # supports `get` and `set`
64
+ def initialize(field, position)
65
+ @field = field
66
+ @position = position
67
+ @description = (position == 3 ? "result" : "operand #{@position}").prepend("event ").concat(": '#{@field}'")
68
+ end
69
+
70
+ def literal?
71
+ false
72
+ end
73
+
74
+ def set(value, event)
75
+ event.set(@field, value)
76
+ end
77
+
78
+ def get(event)
79
+ value = event.get(@field)
80
+ if value.nil?
81
+ logger.warn("field not found", "field" => @field, "event" => event.to_hash)
82
+ return nil
83
+ end
84
+ case value
85
+ when Numeric
86
+ value
87
+ when LogStash::Timestamp, Time
88
+ value.to_f
89
+ else
90
+ logger.warn("field value is not numeric or time", "field" => @field, "value" => value, "event" => event.to_hash)
91
+ nil
92
+ end
93
+ end
94
+
95
+ def inspect
96
+ "\"#{@description}\""
97
+ end
98
+
99
+ def to_s
100
+ @description
101
+ end
102
+ end
103
+
104
+ class LiteralElement
105
+ # does not support `set`
106
+ def initialize(literal, position)
107
+ @literal = literal
108
+ @position = position
109
+ end
110
+
111
+ def literal?
112
+ true
113
+ end
114
+
115
+ def get(event = nil)
116
+ @literal
117
+ end
118
+
119
+ def inspect
120
+ "\"operand #{@position}: #{@literal.inspect}\""
121
+ end
122
+
123
+ def to_s
124
+ inspect
125
+ end
126
+ end
127
+ end
128
+ end end
@@ -0,0 +1,142 @@
1
+ # encoding: utf-8
2
+ require "logstash/util/loggable"
3
+
4
+ module LogStash module Filters
5
+ module MathFunctions
6
+ module DivByZeroValidityCheck
7
+ def invalid?(op1, op2, event = nil)
8
+ if op2.zero?
9
+ warning = "a divisor of zero is not permitted"
10
+ if event
11
+ # called from filter so log
12
+ logger.warn(warning, "operand 1" => op1, "operand 2" => op2, "event" => event.to_hash)
13
+ return true
14
+ else
15
+ # called from register don't log return warning for the error that is raised
16
+ return warning
17
+ end
18
+ end
19
+ nil
20
+ end
21
+ end
22
+
23
+ module NoValidityCheckNeeded
24
+ def invalid?(op1, op2, event = nil)
25
+ nil
26
+ end
27
+ end
28
+
29
+ class Add
30
+ include NoValidityCheckNeeded
31
+
32
+ def name
33
+ "add"
34
+ end
35
+
36
+ def call(op1, op2)
37
+ op1 + op2
38
+ end
39
+ end
40
+
41
+ class Subtract
42
+ include NoValidityCheckNeeded
43
+
44
+ def name
45
+ "subtract"
46
+ end
47
+ def call(op1, op2)
48
+ op1 - op2
49
+ end
50
+ end
51
+
52
+ class Multiply
53
+ include NoValidityCheckNeeded
54
+
55
+ def name
56
+ "multiply"
57
+ end
58
+
59
+ def call(op1, op2)
60
+ op1 * op2
61
+ end
62
+ end
63
+
64
+ class Round
65
+ include NoValidityCheckNeeded
66
+
67
+ def name
68
+ "round"
69
+ end
70
+
71
+ def call(op1, op2)
72
+ op1.round(op2)
73
+ end
74
+ end
75
+
76
+ class Power
77
+ include LogStash::Util::Loggable
78
+
79
+ def name
80
+ "power"
81
+ end
82
+
83
+ def call(op1, op2)
84
+ op1 ** op2
85
+ end
86
+
87
+ def invalid?(op1, op2, event = nil)
88
+ if op1.is_a?(Numeric) && op1 < 0 && !op2.integer?
89
+ warning = "raising a negative number to a fractional exponent results in a complex number that cannot be stored in an event"
90
+ if event
91
+ # called from filter so log
92
+ logger.warn(warning, "operand 1" => op1, "operand 2" => op2, "event" => event.to_hash)
93
+ return true
94
+ else
95
+ # called from register don't log return warning for the error that is raised
96
+ return warning
97
+ end
98
+ end
99
+ nil
100
+ end
101
+ end
102
+
103
+ class Divide
104
+ include LogStash::Util::Loggable
105
+ include DivByZeroValidityCheck
106
+
107
+ def name
108
+ "divide"
109
+ end
110
+
111
+ def call(op1, op2)
112
+ op1 / op2
113
+ end
114
+ end
115
+
116
+ class FloatDivide
117
+ include LogStash::Util::Loggable
118
+ include DivByZeroValidityCheck
119
+
120
+ def name
121
+ "float_divide"
122
+ end
123
+
124
+ def call(op1, op2)
125
+ op1.fdiv(op2)
126
+ end
127
+ end
128
+
129
+ class Modulo
130
+ include LogStash::Util::Loggable
131
+ include DivByZeroValidityCheck
132
+
133
+ def name
134
+ "modulo"
135
+ end
136
+
137
+ def call(op1, op2)
138
+ op1 % op2
139
+ end
140
+ end
141
+ end
142
+ end end
@@ -1,24 +1,24 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'logstash-filter-math'
3
- s.version = '1.0'
3
+ s.version = '1.1.0'
4
4
  s.licenses = ['Apache License (2.0)']
5
5
  s.summary = "Do simple math functions on numeric fields."
6
6
  s.description = "This gem is a logstash plugin required to be installed on top of the Logstash core pipeline using $LS_HOME/bin/plugin install gemname. This gem is not a stand-alone program"
7
- s.authors = ["Robin Clarke"]
8
- s.email = 'robin@robinclarke.net'
9
- s.homepage = "http://www.elastics.co/guide/en/logstash/current/index.html"
7
+ s.authors = ["Elastic"]
8
+ s.email = 'info@elastic.co'
9
+ s.homepage = "http://www.elastic.co/guide/en/logstash/current/index.html"
10
10
  s.require_paths = ["lib"]
11
11
 
12
-
13
12
  # Files
14
- s.files = Dir['lib/**/*','spec/**/*','vendor/**/*','*.gemspec','*.md','CONTRIBUTORS','Gemfile','LICENSE','NOTICE.TXT']
15
- # Tests
13
+ s.files = Dir["lib/**/*","spec/**/*","*.gemspec","*.md","CONTRIBUTORS","Gemfile","LICENSE","NOTICE.TXT", "docs/**/*"]
14
+
15
+ # Tests
16
16
  s.test_files = s.files.grep(%r{^(test|spec|features)/})
17
17
 
18
18
  # Special flag to let us know this is actually a logstash plugin
19
19
  s.metadata = { "logstash_plugin" => "true", "logstash_group" => "filter" }
20
20
 
21
21
  # Gem dependencies
22
- s.add_runtime_dependency "logstash-core-plugin-api", "~> 2.0"
22
+ s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
23
23
  s.add_development_dependency 'logstash-devutils'
24
24
  end
@@ -1,27 +1,26 @@
1
1
  # encoding: utf-8
2
- require "logstash/devutils/rspec/spec_helper"
2
+ require_relative "../spec_helper"
3
3
  require "logstash/filters/math"
4
4
 
5
5
  describe LogStash::Filters::Math do
6
-
7
6
  describe "Additions" do
8
7
  # The logstash config.
9
8
  config <<-CONFIG
10
9
  filter { math { calculate => [ [ "add", "var1", "var2", "result" ] ] } }
11
10
  CONFIG
12
-
11
+
13
12
  describe "should add two integers" do
14
13
  sample( "var1" => -2, "var2" => 7 ) do
15
14
  expect( subject.get("result") ).to eq( 5 )
16
15
  end
17
16
  end
18
-
17
+
19
18
  describe "should add two floats" do
20
19
  sample( "var1" => -2.4, "var2" => 7.8 ) do
21
20
  expect( subject.get("result") ).to eq( 5.4 )
22
21
  end
23
22
  end
24
-
23
+
25
24
  describe "two huge numbers should add to infinity" do
26
25
  sample( "var1" => 1.79769313486232e+308, "var2" => 1e+308 ) do
27
26
  expect( subject.get("result") ).to eq( Float::INFINITY )
@@ -39,7 +38,7 @@ describe LogStash::Filters::Math do
39
38
  expect( subject.get("result") ).to be_nil
40
39
  end
41
40
  end
42
-
41
+
43
42
  describe "Second value missing should result in nil" do
44
43
  sample( "var1" => 3 ) do
45
44
  expect( subject.get("result") ).to be_nil
@@ -50,27 +49,27 @@ describe LogStash::Filters::Math do
50
49
  describe "Subtractions" do
51
50
  # The logstash config.
52
51
  config <<-CONFIG
53
- filter { math { calculate => [ [ "sub", "var1", "var2", "result" ] ] } }
52
+ filter { math { calculate => [ [ "-", "var1", "var2", "result" ] ] } }
54
53
  CONFIG
55
-
54
+
56
55
  describe "should subtract two integers" do
57
56
  sample( "var1" => -2, "var2" => 7 ) do
58
57
  expect( subject.get("result") ).to eq( -9 )
59
58
  end
60
59
  end
61
-
60
+
62
61
  describe "should subtract two floats" do
63
62
  sample( "var1" => -2.4, "var2" => 7.8 ) do
64
63
  expect( subject.get("result") ).to eq( -10.2 )
65
64
  end
66
65
  end
67
-
66
+
68
67
  describe "two huge negative numbers should subtract to negative infinity" do
69
68
  sample( "var1" => -1.79769313486232e+308, "var2" => -1e+308 ) do
70
69
  expect( subject.get("result") ).to eq( -Float::INFINITY )
71
70
  end
72
71
  end
73
-
72
+
74
73
  describe "one value being 0 should work" do
75
74
  sample( "var1" => 0, "var2" => 7.8 ) do
76
75
  expect( subject.get("result") ).to eq( -7.8 )
@@ -82,7 +81,7 @@ describe LogStash::Filters::Math do
82
81
  expect( subject.get("result") ).to be_nil
83
82
  end
84
83
  end
85
-
84
+
86
85
  describe "Second value missing should result in nil" do
87
86
  sample( "var1" => 3 ) do
88
87
  expect( subject.get("result") ).to be_nil
@@ -90,30 +89,77 @@ describe LogStash::Filters::Math do
90
89
  end
91
90
  end
92
91
 
92
+ describe "Rounding" do
93
+ describe "when using a field as the right hand operand" do
94
+ config <<-CONFIG
95
+ filter { math { calculate => [ [ "round", "var1", "var2", "result" ] ] } }
96
+ CONFIG
97
+
98
+ describe "should round a float field" do
99
+ sample( "var1" => 0.42424242, "var2" => 2 ) do
100
+ expect( subject.get("result") ).to eq( 0.42 )
101
+ end
102
+ end
103
+
104
+ describe "should round an integer field" do
105
+ sample( "var1" => 42, "var2" => 2 ) do
106
+ expect( subject.get("result") ).to eq( 42.0 )
107
+ end
108
+ end
109
+ end
110
+
111
+ describe "when using a literal as the right hand operand" do
112
+ config <<-CONFIG
113
+ filter { math { calculate => [ [ "round", "var1", 3, "result" ] ] } }
114
+ CONFIG
115
+
116
+ describe "should round a float field" do
117
+ sample( "var1" => 0.42424242 ) do
118
+ expect( subject.get("result") ).to eq( 0.424 )
119
+ end
120
+ end
121
+
122
+ describe "should convert an integer to a float" do
123
+ sample( "var1" => 42 ) do
124
+ expect( subject.get("result") ).to eq( 42.0 )
125
+ end
126
+ end
127
+
128
+ describe "should convert a float to an integer" do
129
+ config <<-CONFIG
130
+ filter { math { calculate => [ [ "round", "var1", 0, "result" ] ] } }
131
+ CONFIG
132
+ sample( "var1" => 42.0 ) do
133
+ expect( subject.get("result") ).to eq( 42 )
134
+ end
135
+ end
136
+ end
137
+ end
138
+
93
139
  describe "Multiplication" do
94
140
  # The logstash config.
95
141
  config <<-CONFIG
96
- filter { math { calculate => [ [ "mpx", "var1", "var2", "result" ] ] } }
142
+ filter { math { calculate => [ [ "*", "var1", "var2", "result" ] ] } }
97
143
  CONFIG
98
-
144
+
99
145
  describe "should multiply two integers" do
100
146
  sample( "var1" => -2, "var2" => 7 ) do
101
147
  expect( subject.get("result") ).to eq( -14 )
102
148
  end
103
149
  end
104
-
150
+
105
151
  describe "should multiply two floats" do
106
152
  sample( "var1" => -2.4, "var2" => 7.8 ) do
107
153
  expect( subject.get("result") ).to eq( -18.72 )
108
154
  end
109
155
  end
110
-
156
+
111
157
  describe "two huge numbers should multiply to infinity" do
112
158
  sample( "var1" => 1.79769313486232e+300, "var2" => 1e+300 ) do
113
159
  expect( subject.get("result") ).to eq( Float::INFINITY )
114
160
  end
115
161
  end
116
-
162
+
117
163
  describe "one value being 0 should result in 0" do
118
164
  sample( "var1" => 0, "var2" => 7.8 ) do
119
165
  expect( subject.get("result") ).to eq( 0 )
@@ -125,7 +171,7 @@ describe LogStash::Filters::Math do
125
171
  expect( subject.get("result") ).to be_nil
126
172
  end
127
173
  end
128
-
174
+
129
175
  describe "Second value missing should result in nil" do
130
176
  sample( "var1" => 3 ) do
131
177
  expect( subject.get("result") ).to be_nil
@@ -133,36 +179,148 @@ describe LogStash::Filters::Math do
133
179
  end
134
180
  end
135
181
 
182
+ describe "Exponentiation" do
183
+ # The logstash config.
184
+ describe "verbose operation name" do
185
+ config <<-CONFIG
186
+ filter { math { calculate => [ [ "to the power of", "var1", "var2", "result" ] ] } }
187
+ CONFIG
188
+
189
+ describe "should exponentiate two integers" do
190
+ sample( "var1" => -2, "var2" => 5 ) do
191
+ expect( subject.get("result") ).to eq( -32 )
192
+ end
193
+ end
194
+
195
+ describe "should exponentiate two floats" do
196
+ sample( "var1" => 2.2, "var2" => 1.2 ) do
197
+ expect( subject.get("result") ).to eq( 2.5757708085227633 )
198
+ end
199
+ end
200
+
201
+ describe "two huge numbers should exponentiate to infinity" do
202
+ sample( "var1" => 1.79769313486232e+300, "var2" => 1e+300 ) do
203
+ expect( subject.get("result") ).to eq( Float::INFINITY )
204
+ end
205
+ end
206
+ end
207
+
208
+ describe "terse operation name" do
209
+ config <<-CONFIG
210
+ filter { math { calculate => [ [ "**", "var1", "var2", "result" ] ] } }
211
+ CONFIG
212
+
213
+ describe "base value being negative and the exponent being fractional should result in nil" do
214
+ sample( "var1" => -2.2, "var2" => 7.8 ) do
215
+ expect( subject.get("result") ).to eq( nil )
216
+ end
217
+ end
218
+
219
+ describe "base value being 0 should result in 0" do
220
+ sample( "var1" => 0, "var2" => 7.8 ) do
221
+ expect( subject.get("result") ).to eq( 0 )
222
+ end
223
+ end
224
+
225
+ describe "exponent value being 0 should result in 1" do
226
+ sample( "var1" => 7.8, "var2" => 0 ) do
227
+ expect( subject.get("result") ).to eq( 1 )
228
+ end
229
+ end
230
+
231
+ describe "first value missing should result in nil" do
232
+ sample( "var2" => 3 ) do
233
+ expect( subject.get("result") ).to be_nil
234
+ end
235
+ end
236
+
237
+ describe "Second value missing should result in nil" do
238
+ sample( "var1" => 3 ) do
239
+ expect( subject.get("result") ).to be_nil
240
+ end
241
+ end
242
+ end
243
+ end
244
+
136
245
  describe "Division" do
137
246
  # The logstash config.
138
247
  config <<-CONFIG
139
- filter { math { calculate => [ [ "div", "var1", "var2", "result" ] ] } }
248
+ filter { math { calculate => [ [ "/", "var1", "var2", "result" ] ] } }
140
249
  CONFIG
141
-
250
+
251
+ describe "should divide two integers" do
252
+ sample( "var1" => -2, "var2" => 7 ) do
253
+ expect( subject.get("result") ).to eq( -1 )
254
+ end
255
+ end
256
+
257
+ describe "should divide two floats" do
258
+ sample( "var1" => -2.4, "var2" => 7.8 ) do
259
+ expect( subject.get("result") ).to eq( -0.3076923076923077 )
260
+ end
261
+ end
262
+
263
+ describe "1 divided by a huge number should give zero" do
264
+ sample( "var1" => 1, "var2" => 1.79769313486232e+308 ) do
265
+ expect( subject.get("result") ).to eq( 0 )
266
+ end
267
+ end
268
+
269
+ describe "First variable being zero should result in 0" do
270
+ sample( "var1" => 0, "var2" => 7.8 ) do
271
+ expect( subject.get("result") ).to eq( 0 )
272
+ end
273
+ end
274
+
275
+ describe "Second variable being zero should result in nil (would be infinity, but this can't be represented in JSON)" do
276
+ sample( "var1" => 2, "var2" => 0 ) do
277
+ expect( subject.get("result") ).to be_nil
278
+ end
279
+ end
280
+
281
+ describe "first value missing should result in nil" do
282
+ sample( "var2" => 3 ) do
283
+ expect( subject.get("result") ).to be_nil
284
+ end
285
+ end
286
+
287
+ describe "Second value missing should result in nil" do
288
+ sample( "var1" => 3 ) do
289
+ expect( subject.get("result") ).to be_nil
290
+ end
291
+ end
292
+ end
293
+
294
+ describe "FloatDiv" do
295
+ # The logstash config.
296
+ config <<-CONFIG
297
+ filter { math { calculate => [ [ "fdiv", "var1", "var2", "result" ] ] } }
298
+ CONFIG
299
+
142
300
  describe "should divide two integers" do
143
301
  sample( "var1" => -2, "var2" => 7 ) do
144
302
  expect( subject.get("result") ).to eq( -0.2857142857142857 )
145
303
  end
146
304
  end
147
-
305
+
148
306
  describe "should divide two floats" do
149
307
  sample( "var1" => -2.4, "var2" => 7.8 ) do
150
308
  expect( subject.get("result") ).to eq( -0.3076923076923077 )
151
309
  end
152
310
  end
153
-
311
+
154
312
  describe "1 divided by a huge number should give zero" do
155
313
  sample( "var1" => 1, "var2" => 1.79769313486232e+308 ) do
156
314
  expect( subject.get("result") ).to eq( 0 )
157
315
  end
158
316
  end
159
-
317
+
160
318
  describe "First variable being zero should result in 0" do
161
319
  sample( "var1" => 0, "var2" => 7.8 ) do
162
320
  expect( subject.get("result") ).to eq( 0 )
163
321
  end
164
322
  end
165
-
323
+
166
324
  describe "Second variable being zero should result in nil (would be infinity, but this can't be represented in JSON)" do
167
325
  sample( "var1" => 2, "var2" => 0 ) do
168
326
  expect( subject.get("result") ).to be_nil
@@ -174,29 +332,155 @@ describe LogStash::Filters::Math do
174
332
  expect( subject.get("result") ).to be_nil
175
333
  end
176
334
  end
177
-
335
+
178
336
  describe "Second value missing should result in nil" do
179
337
  sample( "var1" => 3 ) do
180
338
  expect( subject.get("result") ).to be_nil
181
339
  end
182
340
  end
183
341
  end
184
-
342
+
343
+ describe "Modulo" do
344
+ # The logstash config.
345
+ config <<-CONFIG
346
+ filter { math { calculate => [ [ "mod", "var1", "var2", "result" ] ] } }
347
+ CONFIG
348
+
349
+ describe "should get modulo of two integers" do
350
+ sample( "var1" => 53, "var2" => 13 ) do
351
+ expect( subject.get("result") ).to eq( 1 )
352
+ end
353
+ end
354
+
355
+ describe "should get modulo of two floats" do
356
+ sample( "var1" => 53.4, "var2" => 13.1 ) do
357
+ expect( subject.get("result") ).to eq( 1.0 )
358
+ end
359
+ end
360
+
361
+ describe "1 modulo a huge number should give 1.0" do
362
+ sample( "var1" => 1, "var2" => 1.79769313486232e+308 ) do
363
+ expect( subject.get("result") ).to eq( 1.0 )
364
+ end
365
+ end
366
+
367
+ describe "First variable being zero should result in 0.0" do
368
+ sample( "var1" => 0, "var2" => 7.8 ) do
369
+ expect( subject.get("result") ).to eq( 0.0 )
370
+ end
371
+ end
372
+
373
+ describe "Second variable being zero should result in nil (would be infinity, but this can't be represented in JSON)" do
374
+ sample( "var1" => 2, "var2" => 0 ) do
375
+ expect( subject.get("result") ).to be_nil
376
+ end
377
+ end
378
+
379
+ describe "first value missing should result in nil" do
380
+ sample( "var2" => 3 ) do
381
+ expect( subject.get("result") ).to be_nil
382
+ end
383
+ end
384
+
385
+ describe "Second value missing should result in nil" do
386
+ sample( "var1" => 3 ) do
387
+ expect( subject.get("result") ).to be_nil
388
+ end
389
+ end
390
+ end
391
+
392
+ describe "Literals" do
393
+ context "how much smaller is one number than another in percent" do
394
+ config <<-CONFIG
395
+ filter { math { calculate => [ [ "float divide", "var1", "var2", "result" ],[ "times", "result", 100, "percent_difference" ] ] } }
396
+ CONFIG
397
+ sample( "var1" => 13, "var2" => 104 ) do
398
+ expect( subject.get("percent_difference") ).to eq(12.5)
399
+ end
400
+ end
401
+
402
+ context "what is the reciprocal of a value" do
403
+ config <<-CONFIG
404
+ filter { math { calculate => [ [ "float divide", 1, "var1", "result" ] ] } }
405
+ CONFIG
406
+ sample( "var1" => 8 ) do
407
+ expect( subject.get("result") ).to eq(0.125)
408
+ end
409
+ end
410
+
411
+ context "when specifying a zero literal, a divide by zero error is detected at plugin register" do
412
+ it "raises a validation error" do
413
+ pipeline = new_pipeline_from_string('filter { math { calculate => [ [ "divide", "var1", 0, "result" ] ] } }')
414
+ expect { pipeline.instance_eval{ @filters.each(&:register) } }.to raise_exception(LogStash::ConfigurationError, /Numeric literals are specified as in the calculation but the function invalidates with 'a divisor of zero is not permitted'/)
415
+ end
416
+ end
417
+
418
+ context "when specifying literals: -2 and 1.2 with the Power function, an error is detected at plugin register" do
419
+ it "raises a validation error" do
420
+ pipeline = new_pipeline_from_string('filter { math { calculate => [ [ "**", -2, 1.2, "result" ] ] } }')
421
+ expect { pipeline.instance_eval{ @filters.each(&:register) } }.to raise_exception(LogStash::ConfigurationError, /Numeric literals are specified as in the calculation but the function invalidates with 'raising a negative number to a fractional exponent results in a complex number that cannot be stored in an event'/)
422
+ end
423
+ end
424
+ end
425
+
426
+ describe "Timestamps" do
427
+ t1 = Time.new(2018, 06, 8, 11, 0, 0,"+00:00")
428
+ t2 = Time.new(2018, 06, 8, 11, 0, 30,"+00:00")
429
+ config <<-CONFIG
430
+ filter { math { calculate => [ [ "sub", "[var2]", "[var1]", "result" ] ] } }
431
+ CONFIG
432
+
433
+ context "subtracting 2 LogStash::Timestamps" do
434
+ sample( "var1" => LogStash::Timestamp.at(t1.to_f), "var2" => LogStash::Timestamp.at(t2.to_f)) do
435
+ expect( subject.get("result") ).to eq(30.0)
436
+ end
437
+ end
438
+
439
+ context "subtracting a Time from a Timestamp" do
440
+ sample( "var1" => t1, "var2" => LogStash::Timestamp.at(t2.to_f)) do
441
+ expect( subject.get("result") ).to eq(30.0)
442
+ end
443
+ end
444
+ end
445
+
185
446
  describe "Sequence" do
186
447
  # The logstash config.
187
448
  config <<-CONFIG
188
- filter { math { calculate => [
189
- [ "add", "var1", "var2", "r1" ],
190
- [ "sub", "var3", "var4", "r2" ],
191
- [ "mpx", "r1", "r2", "result" ]
449
+ filter { math { calculate => [
450
+ [ "add", "var1", "var2", "r1" ],
451
+ [ "sub", "var3", "var4", "r2" ],
452
+ [ "mpx", "r1", "r2", "result" ]
192
453
  ] } }
193
454
  CONFIG
194
-
195
- describe "results of one calculation can be used in the next calculation"
455
+
456
+ describe "results of one calculation can be used in the next calculation" do
457
+ sample( "var1" => 3.4, "var2" => 6.6, "var3" => 4.4, "var4" => 2.4 ) do
458
+ # I would really expect 20.0 here... what kind of floating point error is this?!
459
+ expect( subject.get("result") ).to eq( 20.000000000000004 )
460
+ end
461
+ end
462
+ end
463
+
464
+ describe "Sequence with registers" do
465
+ describe "results of one calculation can be used in the next calculation" do
466
+ config <<-CONFIG
467
+ filter { math { calculate => [
468
+ [ "+", "[var1]", "[var2]", "MEM[0]" ],
469
+ [ "-", "[var3]", "[var4]", "MEM[1]" ],
470
+ [ "*", "MEM[0]", "MEM[1]", "[result]" ]
471
+ ] } }
472
+ CONFIG
196
473
  sample( "var1" => 3.4, "var2" => 6.6, "var3" => 4.4, "var4" => 2.4 ) do
197
474
  # I would really expect 20.0 here... what kind of floating point error is this?!
198
475
  expect( subject.get("result") ).to eq( 20.000000000000004 )
199
476
  end
200
477
  end
201
478
 
479
+ describe "when a register is used as the very last target, a configuration error is raised" do
480
+ it "raises a validation error" do
481
+ pipeline = new_pipeline_from_string('filter { math { calculate => [ [ "**", "[var1]", "[var2]", "MEM[0]" ] ] } }')
482
+ expect { pipeline.instance_eval{ @filters.each(&:register) } }.to raise_exception(LogStash::ConfigurationError, /The final target is a Register, the overall calculation result will not be set in the event/)
483
+ end
484
+ end
485
+ end
202
486
  end