logstash-filter-math 1.0 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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