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.
- checksums.yaml +5 -5
- data/CHANGELOG.md +15 -0
- data/CONTRIBUTORS +11 -0
- data/Gemfile +8 -0
- data/README.md +17 -5
- data/docs/index.asciidoc +207 -0
- data/lib/logstash/filters/math.rb +104 -41
- data/lib/logstash/filters/math_calculation_elements.rb +128 -0
- data/lib/logstash/filters/math_functions.rb +142 -0
- data/logstash-filter-math.gemspec +8 -8
- data/spec/filters/math_spec.rb +316 -32
- data/spec/spec_helper.rb +4 -0
- metadata +28 -15
@@ -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 = ["
|
8
|
-
s.email = '
|
9
|
-
s.homepage = "http://www.
|
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[
|
15
|
-
|
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", "
|
22
|
+
s.add_runtime_dependency "logstash-core-plugin-api", ">= 1.60", "<= 2.99"
|
23
23
|
s.add_development_dependency 'logstash-devutils'
|
24
24
|
end
|
data/spec/filters/math_spec.rb
CHANGED
@@ -1,27 +1,26 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
-
|
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 => [ [ "
|
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 => [ [ "
|
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 => [ [ "
|
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
|
-
|
190
|
-
|
191
|
-
|
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
|