fluent-plugin-datacalculator 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ # For TextMate, emacs, vim
6
+ *.tmproj
7
+ tmtags
8
+ *~
9
+ \#*
10
+ .\#*
11
+ *.swp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-datacounter.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012- TAGOMORI Satoshi
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.rdoc ADDED
@@ -0,0 +1,52 @@
1
+ = fluent-plugin-datacalculator
2
+
3
+ == Component
4
+
5
+ === DataCalculateOutput
6
+
7
+ Simple Calculate messages and summarize the calculated results.
8
+
9
+ - Summarize calculated results per min/hour/day
10
+ - Summarize calculated results per second (average every min/hour/day)
11
+ - Use finalizer of summarized results (e.g. average)
12
+
13
+ 'input_tag_remove_prefix' option available if you want to remove tag prefix from output field names.
14
+
15
+ == Configuration
16
+
17
+ === DataCalculateOutput
18
+
19
+ <match accesslog.**>
20
+ type datacalculate
21
+ unit minute
22
+ aggregate all
23
+ fomulas sum = amount * price, amounts = amount
24
+ </match>
25
+
26
+ If you use finalizer, like this
27
+
28
+ <match accesslog.**>
29
+ type datacalculate
30
+ unit minute
31
+ aggregate all
32
+ fomulas sum = amount * price, amounts = amount
33
+ finalizer average = amounts > 0 ? 1.0 * sum / amounts : 0
34
+ </match>
35
+
36
+ Finalizer uses the summarized output, so argv in finalizer must exist in left-hand side in fomulas.
37
+
38
+
39
+
40
+ == TODO
41
+
42
+ - multiple finalizer
43
+ - patches welcome!
44
+
45
+ == Thanks
46
+
47
+ tagomoris's fluent-plugin-datacounter is AWESOME plugin! That's nice!
48
+
49
+ == Copyright
50
+
51
+ Copyright:: Copyright (c) 2012- Muddy Dixon
52
+ License:: Apache License, Version 2.0
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new(:test) do |test|
5
+ test.libs << 'lib' << 'test'
6
+ test.pattern = 'test/**/test_*.rb'
7
+ test.verbose = true
8
+ end
9
+
10
+ task :default => :test
11
+
data/example.conf ADDED
@@ -0,0 +1,21 @@
1
+ <source>
2
+ type forward
3
+ </source>
4
+ <match payment.**>
5
+ type copy
6
+ <store>
7
+ type stdout
8
+ </store>
9
+ <store>
10
+ type datacalculate
11
+ tag result
12
+ count_interval 5s
13
+ aggregate all
14
+ formulas sum = amount * price, cnt = 1, total = amount
15
+ finalizer ave = cnt > 0 ? 1.00 * sum / cnt : 0
16
+ </store>
17
+ </match>
18
+
19
+ <match result>
20
+ type stdout
21
+ </match>
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = "fluent-plugin-datacalculator"
6
+ s.version = "0.0.0"
7
+ s.authors = ["Muddy Dixon"]
8
+ s.email = ["muddydixon@gmail.com"]
9
+ s.homepage = "https://github.com/muddydixon/fluent-plugin-datacalculator"
10
+ s.summary = %q{Output filter plugin to calculate messages that matches specified conditions}
11
+ s.description = %q{Output filter plugin to calculate messages that matches specified conditions}
12
+
13
+ s.rubyforge_project = "fluent-plugin-datacalculator"
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_development_dependency "rspec"
22
+ # s.add_runtime_dependency "rest-client"
23
+ s.add_development_dependency "fluentd"
24
+ s.add_runtime_dependency "fluentd"
25
+ end
@@ -0,0 +1,244 @@
1
+ class Fluent::DataCalculatorOutput < Fluent::Output
2
+ Fluent::Plugin.register_output('datacalculate', self)
3
+
4
+ config_param :count_interval, :time, :default => nil
5
+ config_param :unit, :string, :default => 'minute'
6
+ config_param :aggregate, :string, :default => 'tag'
7
+ config_param :tag, :string, :default => 'datacalculate'
8
+ config_param :input_tag_remove_prefix, :string, :default => nil
9
+ config_param :formulas, :string
10
+ config_param :finalizer, :string, :default => nil
11
+ config_param :outcast_unmatched, :bool, :default => false
12
+
13
+ attr_accessor :tick
14
+ attr_accessor :counts
15
+ attr_accessor :last_checked
16
+ attr_accessor :_formulas
17
+ attr_accessor :_finalizer
18
+
19
+ def configure(conf)
20
+ super
21
+
22
+ if @count_interval
23
+ @tick = @count_interval.to_i
24
+ else
25
+ @tick = case @unit
26
+ when 'minute' then 60
27
+ when 'hour' then 3600
28
+ when 'day' then 86400
29
+ else
30
+ raise RuntimeError, "@unit must be one of minute/hour/day"
31
+ end
32
+ end
33
+
34
+ @aggregate = case @aggregate
35
+ when 'tag' then :tag
36
+ when 'all' then :all
37
+ else
38
+ raise Fluent::ConfigError, "flowcounter aggregate allows tag/all"
39
+ end
40
+
41
+ def createFunc (cnt, str)
42
+ str.strip!
43
+ left, right = str.split(/\s*=\s*/, 2)
44
+ rights = right.scan(/[a-zA-Z][\w\d_\.\$]*/).uniq
45
+
46
+ begin
47
+ f = eval('lambda {|'+rights.join(',')+'| '+right + '}')
48
+ rescue SyntaxError
49
+ raise Fluent::ConfigError, "'" + str + "' is not valid"
50
+ end
51
+
52
+ [cnt, left, rights, f]
53
+ end
54
+
55
+ def execFunc (tag, obj, argv, formula)
56
+ if tag != nil
57
+ tag = stripped_tag (tag)
58
+ end
59
+ _argv = []
60
+
61
+ argv.each {|arg|
62
+ if tag != nil and tag != 'all'
63
+ arg = tag + '_' + arg
64
+ end
65
+ _argv.push obj[arg]
66
+ }
67
+ formula.call(*_argv)
68
+ end
69
+
70
+ @_formulas = [[0, 'unmatched', nil, nil]]
71
+ if conf.has_key?('formulas')
72
+ fs = conf['formulas'].split(/\s*,\s*/)
73
+ fs.each_with_index { |str,i |
74
+ @_formulas.push( createFunc(i + 1, str) )
75
+ }
76
+ end
77
+
78
+ if conf.has_key?('finalizer')
79
+ @_finalizer = createFunc(0, conf['finalizer'])
80
+
81
+ # check finalizer field
82
+ cnt = @_finalizer[2].length
83
+ @_finalizer[2].each { |key|
84
+ @_formulas.each { |formula|
85
+ next if formula[2] == nil
86
+ cnt -= 1 if formula[1] == key
87
+ }
88
+ }
89
+ if cnt != 0
90
+ raise Fluent::ConfigError, 'keys in finalizer is not satisfied'
91
+ end
92
+ end
93
+
94
+ if @input_tag_remove_prefix
95
+ @removed_prefix_string = @input_tag_remove_prefix + '.'
96
+ @removed_length = @removed_prefix_string.length
97
+ end
98
+
99
+ @counts = count_initialized
100
+ @mutex = Mutex.new
101
+ end
102
+
103
+ def start
104
+ super
105
+ start_watch
106
+ end
107
+
108
+ def shutdown
109
+ super
110
+ @watcher.terminate
111
+ @watcher.join
112
+ end
113
+
114
+ def count_initialized(keys=nil)
115
+ # counts['tag'][num] = count
116
+ if @aggregate == :all
117
+ {'all' => ([0] * @_formulas.length)}
118
+ elsif keys
119
+ values = Array.new(keys.length) {|i|
120
+ Array.new(@_formulas.length){|j| 0 }
121
+ }
122
+ Hash[[keys, values].transpose]
123
+ else
124
+ {}
125
+ end
126
+ end
127
+
128
+ def countups(tag, counts)
129
+ if @aggregate == :all
130
+ tag = 'all'
131
+ end
132
+
133
+ @mutex.synchronize {
134
+ @counts[tag] ||= [0] * @_formulas.length
135
+ counts.each_with_index do |count, i|
136
+ @counts[tag][i] += count
137
+ end
138
+ }
139
+ end
140
+
141
+ def stripped_tag(tag)
142
+ return tag unless @input_tag_remove_prefix
143
+ return tag[@removed_length..-1] if tag.start_with?(@removed_prefix_string) and tag.length > @removed_length
144
+ return tag[@removed_length..-1] if tag == @input_tag_remove_prefix
145
+ tag
146
+ end
147
+
148
+ def generate_output(counts, step)
149
+ output = {}
150
+ if @aggregate == :all
151
+ # index 0 is unmatched
152
+ sum = if @outcast_unmatched
153
+ counts['all'][1..-1].inject(:+)
154
+ else
155
+ counts['all'].inject(:+)
156
+ end
157
+ counts['all'].each_with_index do |count,i|
158
+ name = @_formulas[i][1]
159
+ output[name] = count
160
+ end
161
+
162
+ if @_finalizer
163
+ output[@_finalizer[1]] = execFunc('all', output, @_finalizer[2], @_finalizer[3])
164
+ end
165
+
166
+ return output
167
+ end
168
+
169
+ counts.keys.each do |tag|
170
+ t = stripped_tag(tag)
171
+ sum = if @outcast_unmatched
172
+ counts[tag][1..-1].inject(:+)
173
+ else
174
+ counts[tag].inject(:+)
175
+ end
176
+ counts[tag].each_with_index do |count,i|
177
+ name = @_formulas[i][1]
178
+ output[t + '_' + name] = count
179
+ end
180
+ if @_finalizer
181
+ output[t + '_' + @_finalizer[1]] = execFunc(tag, output, @_finalizer[2], @_finalizer[3])
182
+ end
183
+ end
184
+ output
185
+ end
186
+
187
+ def flush(step)
188
+ flushed,@counts = @counts,count_initialized(@counts.keys.dup)
189
+ generate_output(flushed, step)
190
+ end
191
+
192
+ def flush_emit(step)
193
+ Fluent::Engine.emit(@tag, Fluent::Engine.now, flush(step))
194
+ end
195
+
196
+ def start_watch
197
+ # for internal, or tests only
198
+ @watcher = Thread.new(&method(:watch))
199
+ end
200
+
201
+ def watch
202
+ # instance variable, and public accessable, for test
203
+ @last_checked = Fluent::Engine.now
204
+ while true
205
+ sleep 0.5
206
+ if Fluent::Engine.now - @last_checked >= @tick
207
+ now = Fluent::Engine.now
208
+ flush_emit(now - @last_checked)
209
+ @last_checked = now
210
+ end
211
+ end
212
+ end
213
+
214
+ def checkArgs (obj, inkeys)
215
+ inkeys.each{ |key|
216
+ if not obj.has_key?(key)
217
+ return false
218
+ end
219
+ }
220
+ return true
221
+ end
222
+
223
+ def emit(tag, es, chain)
224
+ c = [0] * @_formulas.length
225
+
226
+ es.each do |time,record|
227
+ matched = false
228
+ if @_formulas.length > 0
229
+ @_formulas.each do |index, outkey, inkeys, formula|
230
+ next unless formula and checkArgs(record, inkeys)
231
+
232
+ c[index] += execFunc(nil, record, inkeys, formula)
233
+ matched = true
234
+ end
235
+ else
236
+ $log.warn index
237
+ end
238
+ c[0] += 1 unless matched
239
+ end
240
+ countups(tag, c)
241
+
242
+ chain.next
243
+ end
244
+ end
data/test/helper.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'fluent/test'
15
+ unless ENV.has_key?('VERBOSE')
16
+ nulllogger = Object.new
17
+ nulllogger.instance_eval {|obj|
18
+ def method_missing(method, *args)
19
+ # pass
20
+ end
21
+ }
22
+ $log = nulllogger
23
+ end
24
+
25
+ require 'fluent/plugin/out_datacalculator'
26
+
27
+ class Test::Unit::TestCase
28
+ end
@@ -0,0 +1,172 @@
1
+ # -*- coding: utf-8 -*-
2
+ require 'helper'
3
+
4
+ class DataCalculatorOutputTest < Test::Unit::TestCase
5
+ def setup
6
+ Fluent::Test.setup
7
+ end
8
+
9
+ CONFIG = %[
10
+ unit minute
11
+ aggregate tag
12
+ input_tag_remove_prefix test
13
+ formulas sum = amount * price, amounts = amount, record = 1
14
+ finalizer ave = amounts > 0 ? 1.0 * sum / amounts : 0
15
+ ]
16
+
17
+ def create_driver(conf = CONFIG, tag='test.input')
18
+ Fluent::Test::OutputTestDriver.new(Fluent::DataCalculatorOutput, tag).configure(conf)
19
+ end
20
+
21
+ def test_configure
22
+ assert_raise(Fluent::ConfigError) {
23
+ d = create_driver('')
24
+ }
25
+ # 式がSyntax Error
26
+ assert_raise(Fluent::ConfigError) {
27
+ d = create_driver %[
28
+ formulas sum = 10 ab
29
+ ]
30
+ }
31
+ # finalizerに必要な要素がない
32
+ assert_raise(Fluent::ConfigError) {
33
+ d = create_driver %[
34
+ finalizer ave = cnt > 0 ? sum / cnt : 0
35
+ ]
36
+ }
37
+ # finalizerに必要な要素がない
38
+ assert_raise(Fluent::ConfigError) {
39
+ d = create_driver %[
40
+ formulas sum = 10
41
+ finalizer ave = cnt > 0 ? sum / cnt : 0
42
+ ]
43
+ }
44
+ d = create_driver %[
45
+ formulas sum = amount * price, cnt = amount
46
+ finalizer ave = cnt > 0 ? sum / cnt : 0
47
+ ]
48
+ assert_equal 60, d.instance.tick
49
+ assert_equal :tag, d.instance.aggregate
50
+ assert_equal 'datacalculate', d.instance.tag
51
+ assert_nil d.instance.input_tag_remove_prefix
52
+ assert_equal 'sum = amount * price, cnt = amount', d.instance.formulas
53
+ assert_equal 'ave = cnt > 0 ? sum / cnt : 0', d.instance.finalizer
54
+ assert_equal false, d.instance.outcast_unmatched
55
+
56
+ end
57
+
58
+ def test_count_initialized
59
+ d = create_driver %[
60
+ aggregate all
61
+ formulas sum = amount * price, cnt = amount
62
+ ]
63
+ assert_equal [0,0,0], d.instance.counts['all']
64
+ end
65
+
66
+ def test_create_formula
67
+ d = create_driver %[
68
+ aggregate all
69
+ formulas sum = amount * price, cnt = amount
70
+ ]
71
+ assert_equal 0, d.instance._formulas[0][0]
72
+ assert_equal 'unmatched', d.instance._formulas[0][1]
73
+ assert_equal nil, d.instance._formulas[0][2]
74
+ assert_equal 1, d.instance._formulas[1][0]
75
+ assert_equal 'sum', d.instance._formulas[1][1]
76
+ assert_equal ['amount', 'price'], d.instance._formulas[1][2]
77
+ assert_equal 2, d.instance._formulas[2][0]
78
+ assert_equal 'cnt', d.instance._formulas[2][1]
79
+ assert_equal ['amount'], d.instance._formulas[2][2]
80
+ end
81
+
82
+ def test_countups
83
+ d = create_driver
84
+ assert_nil d.instance.counts['test.input']
85
+ d.instance.countups('test.input', [0, 0, 0, 0])
86
+ assert_equal [0,0,0,0], d.instance.counts['test.input']
87
+ d.instance.countups('test.input', [1, 1, 1, 0])
88
+ assert_equal [1,1,1,0], d.instance.counts['test.input']
89
+ d.instance.countups('test.input', [0, 5, 1, 0])
90
+ assert_equal [1,6,2,0], d.instance.counts['test.input']
91
+ end
92
+
93
+ def test_stripped_tag
94
+ d = create_driver
95
+ assert_equal 'input', d.instance.stripped_tag('test.input')
96
+ assert_equal 'test.input', d.instance.stripped_tag('test.test.input')
97
+ assert_equal 'input', d.instance.stripped_tag('input')
98
+ end
99
+
100
+ def test_generate_output
101
+ d = create_driver
102
+ r1 = d.instance.generate_output({'test.input' => [60,240,120,180], 'test.input2' => [0,600,0,0]}, 60)
103
+
104
+ assert_equal 60, r1['input_unmatched']
105
+ assert_equal 240, r1['input_sum']
106
+ assert_equal 120, r1['input_amounts']
107
+ assert_equal 180, r1['input_record']
108
+ assert_equal 2, r1['input_ave']
109
+
110
+ assert_equal 0, r1['input2_unmatched']
111
+ assert_equal 600, r1['input2_sum']
112
+ assert_equal 0, r1['input2_amounts']
113
+ assert_equal 0, r1['input2_record']
114
+ assert_equal 0, r1['input2_ave']
115
+
116
+
117
+ d = create_driver %[
118
+ aggregate all
119
+ input_tag_remove_prefix test
120
+ formulas sum = amount * price, amounts = amount, record = 1
121
+ finalizer ave = amounts > 0 ? sum / amounts : 0
122
+ ]
123
+
124
+ r2 = d.instance.generate_output({'all' => [60,240,120,180]}, 60)
125
+ assert_equal 60, r2['unmatched']
126
+ assert_equal 240, r2['sum']
127
+ assert_equal 120, r2['amounts']
128
+ assert_equal 180, r2['record']
129
+ assert_equal 2, r2['ave']
130
+ end
131
+
132
+ def test_emit
133
+ d1 = create_driver(CONFIG, 'test.input')
134
+ d1.run do
135
+ 60.times do
136
+ d1.emit({'amount' => 3, 'price' => 100})
137
+ d1.emit({'amount' => 3, 'price' => 200})
138
+ d1.emit({'amount' => 6, 'price' => 50})
139
+ d1.emit({'amount' => 10, 'price' => 100})
140
+ end
141
+ end
142
+ r1 = d1.instance.flush(60)
143
+ assert_equal 0, r1['input_unmatched']
144
+ assert_equal 132000, r1['input_sum']
145
+ assert_equal 1320, r1['input_amounts']
146
+ assert_equal 240, r1['input_record']
147
+ assert_equal 100.0, r1['input_ave']
148
+
149
+ d2 = create_driver(%[
150
+ unit minute
151
+ aggregate all
152
+ input_tag_remove_prefix test
153
+ formulas sum = amount * price, amounts = amount, record = 1
154
+ finalizer ave = amounts > 0 ? 1.0 * sum / amounts : 0
155
+ ], 'test.input2')
156
+
157
+ d2.run do
158
+ 60.times do
159
+ d2.emit({'amount' => 3, 'price' => 100})
160
+ d2.emit({'amount' => 3, 'price' => 200})
161
+ d2.emit({'amount' => 6, 'price' => 50})
162
+ d2.emit({'amount' => 10, 'price' => 100})
163
+ end
164
+ end
165
+ r2 = d2.instance.flush(60)
166
+ assert_equal 0, r2['unmatched']
167
+ assert_equal 132000, r2['sum']
168
+ assert_equal 1320, r2['amounts']
169
+ assert_equal 240, r2['record']
170
+ assert_equal 100.0, r2['ave']
171
+ end
172
+ end
metadata ADDED
@@ -0,0 +1,89 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-datacalculator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Muddy Dixon
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-05-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: fluentd
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: fluentd
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Output filter plugin to calculate messages that matches specified conditions
47
+ email:
48
+ - muddydixon@gmail.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.rdoc
57
+ - Rakefile
58
+ - example.conf
59
+ - fluent-plugin-datacalculator.gemspec
60
+ - lib/fluent/plugin/out_datacalculator.rb
61
+ - test/helper.rb
62
+ - test/plugin/test_out_datacalculator.rb
63
+ homepage: https://github.com/muddydixon/fluent-plugin-datacalculator
64
+ licenses: []
65
+ post_install_message:
66
+ rdoc_options: []
67
+ require_paths:
68
+ - lib
69
+ required_ruby_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ required_rubygems_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project: fluent-plugin-datacalculator
83
+ rubygems_version: 1.8.21
84
+ signing_key:
85
+ specification_version: 3
86
+ summary: Output filter plugin to calculate messages that matches specified conditions
87
+ test_files:
88
+ - test/helper.rb
89
+ - test/plugin/test_out_datacalculator.rb