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.
- 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
|