fluent-plugin-statsite 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +22 -0
- data/.gitmodules +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +13 -0
- data/README.md +66 -0
- data/Rakefile +25 -0
- data/example.conf +27 -0
- data/fluent-plugin-statsite.gemspec +25 -0
- data/lib/fluent/plugin/out_statsite.rb +156 -0
- data/lib/fluent/plugin/statsite/child_process.rb +101 -0
- data/lib/fluent/plugin/statsite/format.rb +47 -0
- data/lib/fluent/plugin/statsite/histogram.rb +65 -0
- data/lib/fluent/plugin/statsite/metric.rb +81 -0
- data/test/helper.rb +62 -0
- data/test/test_formatter.rb +25 -0
- data/test/test_histogram.rb +79 -0
- data/test/test_metric.rb +100 -0
- data/test/test_out_statsite.rb +65 -0
- data/test/test_parser.rb +87 -0
- metadata +111 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2e12dccb3b7db8188b8fc47b913d1ee260773d1e
|
4
|
+
data.tar.gz: f09fa661f3b430f1bcc2f1b9b03f3cc4876989ce
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0259eae4b813fa352773f84dbc465008463d4be9f9aa5e2c7f6dc4fa8c31a2e291fe2974b6a0de81cb856e5fbd0f6eae3601b3d0ba07cf7127457fbd42955219
|
7
|
+
data.tar.gz: 55b89401b72406cbcbfe3942b5ad5318e897a2e05c21c68d7836d9b7085ec22bc244c7fe6585fa3ee24c0a6e864ff141bd6409d87bf8d6a6047527a57b62c1de
|
data/.gitignore
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
*.bundle
|
19
|
+
*.so
|
20
|
+
*.o
|
21
|
+
*.a
|
22
|
+
mkmf.log
|
data/.gitmodules
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Copyright 2014 OKUNO Akihiro
|
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.md
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# Statsite Fluentd Plugin
|
2
|
+
|
3
|
+
This plugin calculates various useful metrics using [Statsite by armon](http://armon.github.io/statsite/).
|
4
|
+
|
5
|
+
[Statsite](http://armon.github.io/statsite/) is very cool software. Statsite works as daemon service, receiving events from tcp/udp, aggregating these events with specified methods, and sending the results via pluggable sinks. Statsite is written in C, cpu and memory efficient, and employ some approximate algorithms for unique sets and percentiles.
|
6
|
+
|
7
|
+
You may think this as standard output plugin which just sends events to a daemon process, such as [mongodb plugin](https://github.com/fluent/fluent-plugin-mongo). It is true that this plugin is registered as output plugin, but this works as the so-called **Filter Plugin**, which means that this plugin sends matched events to Statsite process, recieves results aggregated by the Statsite, then re-emitting these results as events.
|
8
|
+
|
9
|
+
Statsite process is launched as a child process from this plugin internally. All you have to do place statsite the binary under $PATH, or set the path of statsite binary as parameter. Neither config files or daemon process is not required. Besides, the communication between the plugin and the Statsite process takes place through STDIN/STDOUT, so no network port will be used.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
`$ fluent-gem install fluent-plugin-statsite`
|
14
|
+
|
15
|
+
### Statsite Installation
|
16
|
+
|
17
|
+
Statsite can work as sinble binary with few dependency. You probably could get it working just by downloading source files and executing make command.
|
18
|
+
|
19
|
+
Please refer to [Statsite official page](http://armon.github.io/statsite/).
|
20
|
+
|
21
|
+
## Configuration
|
22
|
+
|
23
|
+
It is strongly recommended to use '[V1 config format](http://docs.fluentd.org/articles/config-file#v1-format)' because this plugin requires to set deeply nested parameters.
|
24
|
+
|
25
|
+
### Example
|
26
|
+
|
27
|
+
```
|
28
|
+
<match **>
|
29
|
+
type statsite
|
30
|
+
tag statsite
|
31
|
+
metrics [
|
32
|
+
"${status}:1|c",
|
33
|
+
{"key": "request_time", "value_field": "request_time", "type": "ms"}
|
34
|
+
]
|
35
|
+
histograms [
|
36
|
+
{"prefix": "request_time" "min": 0, "max": 1, "width": 0.1}
|
37
|
+
]
|
38
|
+
statsite_path "statsite"
|
39
|
+
statsite_flush_interval 1s
|
40
|
+
timer_eps 0.01
|
41
|
+
set_eps 0.01
|
42
|
+
child_respawn 5
|
43
|
+
</match>
|
44
|
+
```
|
45
|
+
|
46
|
+
### Parameter
|
47
|
+
|
48
|
+
TODO
|
49
|
+
|
50
|
+
### Metrics Format
|
51
|
+
|
52
|
+
You can specify metrics in two format, string style, and hash style.
|
53
|
+
|
54
|
+
#### String style
|
55
|
+
|
56
|
+
TODO
|
57
|
+
|
58
|
+
#### Hash style
|
59
|
+
|
60
|
+
TODO
|
61
|
+
|
62
|
+
## Copyright
|
63
|
+
|
64
|
+
* Copyright (c) 2014- OKUNO Akihiro
|
65
|
+
* License
|
66
|
+
* Apache License, version 2.0
|
data/Rakefile
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
|
4
|
+
Rake::TestTask.new(:test) do |t|
|
5
|
+
t.libs << 'lib' << 'test'
|
6
|
+
t.test_files = FileList['test/test*.rb']
|
7
|
+
t.verbose = true
|
8
|
+
end
|
9
|
+
task :test => 'statsite:build'
|
10
|
+
|
11
|
+
namespace :statsite do
|
12
|
+
desc 'Build statsite binary'
|
13
|
+
task :'build' => 'vendor/statsite/statsite'
|
14
|
+
|
15
|
+
desc 'Clean statsite artifacts'
|
16
|
+
task :'clean' do
|
17
|
+
sh 'make -C vendor/statsite clean'
|
18
|
+
end
|
19
|
+
|
20
|
+
file 'vendor/statsite/statsite' do
|
21
|
+
sh 'make -C vendor/statsite'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
task :default => :test
|
data/example.conf
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
<source>
|
2
|
+
type forward
|
3
|
+
</source>
|
4
|
+
|
5
|
+
<match event.*>
|
6
|
+
type statsite
|
7
|
+
tag statsite
|
8
|
+
metrics [
|
9
|
+
"k1:1|kv",
|
10
|
+
"k2:1|g",
|
11
|
+
"k3:1|ms",
|
12
|
+
"k4:1|c",
|
13
|
+
"k5:1|s"
|
14
|
+
]
|
15
|
+
histograms [
|
16
|
+
{"prefix": "bar", "min": 0, "max": 10, "width": 1.0}
|
17
|
+
]
|
18
|
+
statsite_path "vendor/statsite/statsite"
|
19
|
+
statsite_flush_interval 1s
|
20
|
+
timer_eps 0.01
|
21
|
+
set_eps 0.02
|
22
|
+
child_respawn 5
|
23
|
+
</match>
|
24
|
+
|
25
|
+
<match *>
|
26
|
+
type stdout
|
27
|
+
</match>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "fluent-plugin-statsite"
|
7
|
+
spec.version = "0.0.2"
|
8
|
+
spec.authors = ["OKUNO Akihiro"]
|
9
|
+
spec.email = ["choplin.choplin@gmail.com"]
|
10
|
+
spec.summary = %q{Fluentd statsite plugin}
|
11
|
+
spec.description = %q{Fluentd plugin which caluculate statistics using statsite}
|
12
|
+
spec.homepage = ""
|
13
|
+
spec.homepage = "https://github.com/choplin/fluent-plugin-statsite"
|
14
|
+
spec.license = "Apache-2.0"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_runtime_dependency "fluentd"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.6"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require_relative 'statsite/child_process'
|
2
|
+
require_relative 'statsite/format'
|
3
|
+
require_relative 'statsite/metric'
|
4
|
+
require_relative 'statsite/histogram'
|
5
|
+
|
6
|
+
require 'tempfile'
|
7
|
+
require 'timeout'
|
8
|
+
|
9
|
+
module Fluent
|
10
|
+
class StatsiteOutput < Fluent::BufferedOutput
|
11
|
+
include Fluent::StatsitePlugin
|
12
|
+
|
13
|
+
Fluent::Plugin.register_output('statsite', self)
|
14
|
+
|
15
|
+
# TODO: should be configurable?
|
16
|
+
CONFIG_VALIDATION_WAIT = 0.1
|
17
|
+
|
18
|
+
config_param :tag, :string
|
19
|
+
config_param :metrics, :array
|
20
|
+
config_param :histograms, :array, :default => []
|
21
|
+
config_param :statsite_path, :string, :default => 'statsite'
|
22
|
+
config_param :statsite_flush_interval, :time, :default => 10
|
23
|
+
config_param :timer_eps, :float, :default => 0.01
|
24
|
+
config_param :set_eps, :float, :default => 0.02
|
25
|
+
config_param :child_respawn, :string, :default => nil
|
26
|
+
# TODO: should support input_counter?
|
27
|
+
|
28
|
+
def initialize
|
29
|
+
super
|
30
|
+
end
|
31
|
+
|
32
|
+
def configure(conf)
|
33
|
+
super
|
34
|
+
@metrics = validate_metrics
|
35
|
+
@metrics.each{|m| @log.info "out_statsite: #{m}"}
|
36
|
+
|
37
|
+
@histograms = validate_histograms
|
38
|
+
|
39
|
+
@respawns = configure_respawns
|
40
|
+
|
41
|
+
@parser = StatsiteParser.new(method(:on_message))
|
42
|
+
@formatter = StatsiteFormatter.new(@metrics)
|
43
|
+
|
44
|
+
$log.info "out_statsite: statsite config\n\n#{config}"
|
45
|
+
@conf = Tempfile.new('fluent-plugin-statsite-')
|
46
|
+
@conf.puts config
|
47
|
+
@conf.flush
|
48
|
+
|
49
|
+
@child = ChildProcess.new(@parser, @respawns, log)
|
50
|
+
|
51
|
+
@cmd = "#{@statsite_path} -f #{@conf.path}"
|
52
|
+
validate_statsite_confg
|
53
|
+
end
|
54
|
+
|
55
|
+
def start
|
56
|
+
super
|
57
|
+
|
58
|
+
begin
|
59
|
+
$log.info "out_statsite: launching statsite process", cmd: @cmd
|
60
|
+
@child.start(@cmd)
|
61
|
+
rescue
|
62
|
+
shutdown
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def before_shutdown
|
68
|
+
super
|
69
|
+
$log.debug "out_statsite#before_shutdown called"
|
70
|
+
@child.finished = true
|
71
|
+
sleep 0.5 # TODO wait time before killing child process
|
72
|
+
end
|
73
|
+
|
74
|
+
def shutdown
|
75
|
+
super
|
76
|
+
@conf.close
|
77
|
+
@child.shutdown
|
78
|
+
end
|
79
|
+
|
80
|
+
def format(tag, time, record)
|
81
|
+
@formatter.call(record)
|
82
|
+
end
|
83
|
+
|
84
|
+
def write(chunk)
|
85
|
+
@child.write chunk
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def run
|
91
|
+
@loop.run
|
92
|
+
rescue => e
|
93
|
+
log.error "out_statsite: unexpected error", :error => e, :error_class => e.class
|
94
|
+
log.error_backtrace
|
95
|
+
end
|
96
|
+
|
97
|
+
def configure_respawns
|
98
|
+
if @child_respawn.nil? or @child_respawn == 'none' or @child_respawn == '0'
|
99
|
+
0
|
100
|
+
elsif @child_respawn == 'inf' or @child_respawn == '-1'
|
101
|
+
-1
|
102
|
+
elsif @child_respawn =~ /^\d+$/
|
103
|
+
@child_respawn.to_i
|
104
|
+
else
|
105
|
+
raise ConfigError, "child_respawn option argument invalid: none(or 0), inf(or -1) or positive number"
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate_metrics
|
110
|
+
@metrics.map {|m| Metric.validate(m)}
|
111
|
+
end
|
112
|
+
|
113
|
+
def validate_histograms
|
114
|
+
@histograms.map {|h| Histogram.validate(h)}
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_statsite_confg
|
118
|
+
$log.debug "lanuch statsite process to validate statsite config"
|
119
|
+
pid = spawn(@cmd, out: '/dev/null')
|
120
|
+
if pid.nil?
|
121
|
+
raise ConfigError, 'failed to launch statsite process', cmd: @cmd
|
122
|
+
else
|
123
|
+
begin
|
124
|
+
Timeout::timeout(CONFIG_VALIDATION_WAIT) do
|
125
|
+
Process.waitpid(pid)
|
126
|
+
end
|
127
|
+
raise ConfigError, 'Statsite process cannot be launched correctly. A config is probably invalid.'
|
128
|
+
rescue Timeout::Error
|
129
|
+
# launched correctly
|
130
|
+
Process.kill(:KILL, pid)
|
131
|
+
Process.waitpid(pid)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def config
|
137
|
+
<<-CONFIG
|
138
|
+
[statsite]
|
139
|
+
port = 0
|
140
|
+
udp_port = 0
|
141
|
+
parse_stdin = 1
|
142
|
+
log_level = INFO
|
143
|
+
flush_interval = #{@statsite_flush_interval}
|
144
|
+
timer_eps = #{@timer_eps}
|
145
|
+
set_eps = #{@set_eps}
|
146
|
+
stream_cmd = cat
|
147
|
+
|
148
|
+
#{@histograms.map(&:to_ini).join("\n\n")}
|
149
|
+
CONFIG
|
150
|
+
end
|
151
|
+
|
152
|
+
def on_message(time, record)
|
153
|
+
Engine.emit(@tag, time, record)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
module Fluent
|
2
|
+
module StatsitePlugin
|
3
|
+
class ChildProcess
|
4
|
+
attr_accessor :finished
|
5
|
+
|
6
|
+
def initialize(parser, respawns=0, log = $log)
|
7
|
+
@pid = nil
|
8
|
+
@thread = nil
|
9
|
+
@parser = parser
|
10
|
+
@respawns = respawns
|
11
|
+
@mutex = Mutex.new
|
12
|
+
@finished = nil
|
13
|
+
@log = log
|
14
|
+
end
|
15
|
+
|
16
|
+
def start(command)
|
17
|
+
@command = command
|
18
|
+
@mutex.synchronize do
|
19
|
+
@io = IO.popen(command, "r+")
|
20
|
+
@pid = @io.pid
|
21
|
+
@io.sync = true
|
22
|
+
@thread = Thread.new(&method(:run))
|
23
|
+
end
|
24
|
+
@finished = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def kill_child(join_wait)
|
28
|
+
begin
|
29
|
+
Process.kill(:TERM, @pid)
|
30
|
+
rescue #Errno::ECHILD, Errno::ESRCH, Errno::EPERM
|
31
|
+
# Errno::ESRCH 'No such process', ignore
|
32
|
+
# child process killed by signal chained from fluentd process
|
33
|
+
end
|
34
|
+
if @thread.join(join_wait)
|
35
|
+
# @thread successfully shutdown
|
36
|
+
return
|
37
|
+
end
|
38
|
+
begin
|
39
|
+
Process.kill(:KILL, @pid)
|
40
|
+
rescue #Errno::ECHILD, Errno::ESRCH, Errno::EPERM
|
41
|
+
# ignore if successfully killed by :TERM
|
42
|
+
end
|
43
|
+
@thread.join
|
44
|
+
end
|
45
|
+
|
46
|
+
def shutdown
|
47
|
+
@finished = true
|
48
|
+
@mutex.synchronize do
|
49
|
+
kill_child(60) # TODO wait time
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def write(chunk)
|
54
|
+
begin
|
55
|
+
chunk.write_to(@io)
|
56
|
+
rescue Errno::EPIPE => e
|
57
|
+
# Broken pipe (child process unexpectedly exited)
|
58
|
+
@log.warn "statsite Broken pipe, child process maybe exited.", :command => @command
|
59
|
+
if try_respawn
|
60
|
+
retry # retry chunk#write_to with child respawned
|
61
|
+
else
|
62
|
+
raise e # to retry #write with other ChildProcess instance (when num_children > 1)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def try_respawn
|
68
|
+
return false if @respawns == 0
|
69
|
+
@mutex.synchronize do
|
70
|
+
return false if @respawns == 0
|
71
|
+
|
72
|
+
kill_child(5) # TODO wait time
|
73
|
+
|
74
|
+
@io = IO.popen(@command, "r+")
|
75
|
+
@pid = @io.pid
|
76
|
+
@io.sync = true
|
77
|
+
@thread = Thread.new(&method(:run))
|
78
|
+
|
79
|
+
@respawns -= 1 if @respawns > 0
|
80
|
+
end
|
81
|
+
@log.warn "statsite child process successfully respawned.", :command => @command, :respawns => @respawns
|
82
|
+
true
|
83
|
+
end
|
84
|
+
|
85
|
+
def run
|
86
|
+
@parser.call(@io)
|
87
|
+
rescue
|
88
|
+
@log.error "statsite thread unexpectedly failed with an error.", :command=>@command, :error=>$!.to_s
|
89
|
+
@log.warn_backtrace $!.backtrace
|
90
|
+
ensure
|
91
|
+
pid, stat = Process.waitpid2(@pid)
|
92
|
+
unless @finished
|
93
|
+
@log.error "statsite process unexpectedly exited.", :command=>@command, :ecode=>stat.to_i
|
94
|
+
unless @respawns == 0
|
95
|
+
@log.warn "statsite child process will respawn for next input data (respawns #{@respawns})."
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Fluent
|
2
|
+
module StatsitePlugin
|
3
|
+
class StatsiteParser
|
4
|
+
def initialize(on_message)
|
5
|
+
@on_message = on_message
|
6
|
+
end
|
7
|
+
|
8
|
+
def call(io)
|
9
|
+
io.each_line(&method(:each_line))
|
10
|
+
end
|
11
|
+
|
12
|
+
def each_line(line)
|
13
|
+
k,v,t = line.chomp.split('|')
|
14
|
+
type, key, statistic, range = k.split(".", 4)
|
15
|
+
|
16
|
+
record = case type
|
17
|
+
when 'timers' then 1
|
18
|
+
if statistic == 'histogram'
|
19
|
+
{type: type, key: key, value: v.to_i, statistic: statistic, range: range[4..-1]}
|
20
|
+
elsif statistic == 'count'
|
21
|
+
{type: type, key: key, value: v.to_i, statistic: statistic}
|
22
|
+
else
|
23
|
+
{type: type, key: key, value: v.to_f, statistic: statistic}
|
24
|
+
end
|
25
|
+
when 'kv', 'gauges', 'counts'
|
26
|
+
{type: type, key: key, value: v.to_f}
|
27
|
+
when 'sets'
|
28
|
+
{type: type, key: key, value: v.to_i}
|
29
|
+
end
|
30
|
+
|
31
|
+
raise "out_statsite: failed to parse a line. '#{line}'" if record.nil?
|
32
|
+
|
33
|
+
@on_message.call(t.to_i, record)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class StatsiteFormatter
|
38
|
+
def initialize(metrics)
|
39
|
+
@metrics = metrics
|
40
|
+
end
|
41
|
+
|
42
|
+
def call(record)
|
43
|
+
@metrics.map{|m| m.convert(record)}.select{|m| not m.nil?}.join('')
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Fluent
|
2
|
+
module StatsitePlugin
|
3
|
+
class Histogram
|
4
|
+
FIELD = %w(
|
5
|
+
min
|
6
|
+
max
|
7
|
+
width
|
8
|
+
)
|
9
|
+
|
10
|
+
OPTIONAL_FIELD = %w(prefix)
|
11
|
+
|
12
|
+
FLOATING_FIELD = %w(
|
13
|
+
min
|
14
|
+
max
|
15
|
+
width
|
16
|
+
)
|
17
|
+
|
18
|
+
def initialize(prefix, min, max, width)
|
19
|
+
@prefix = prefix
|
20
|
+
@min = min
|
21
|
+
@max = max
|
22
|
+
@width = width
|
23
|
+
|
24
|
+
@section = prefix.nil? ? "default" : prefix
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_ini
|
28
|
+
<<-INI
|
29
|
+
[histogram_#{@section}]
|
30
|
+
prefix=#{@prefix}
|
31
|
+
min=#{@min}
|
32
|
+
max=#{@max}
|
33
|
+
width=#{@width}
|
34
|
+
INI
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.validate(h)
|
38
|
+
if h.class != Hash
|
39
|
+
raise ConfigError, "a type of histogram element must be Hash, but specified as #{h.class}"
|
40
|
+
end
|
41
|
+
|
42
|
+
FIELD.each do |f|
|
43
|
+
if not h.has_key?(f)
|
44
|
+
raise ConfigError, "histogram element must contain '#{f}'"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
h.keys.each do |k|
|
49
|
+
if not FIELD.member?(k) and not OPTIONAL_FIELD.member?(k)
|
50
|
+
raise ConfigError, "invalid histogram hash key: #{k}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
FLOATING_FIELD.each do |f|
|
55
|
+
cls = h[f].class
|
56
|
+
if cls != Fixnum and cls != Float
|
57
|
+
raise ConfigError, "#{f} value of histogram must be Fixnum or Float"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
new(h['prefix'], h['min'], h['max'], h['width'])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Fluent
|
2
|
+
module StatsitePlugin
|
3
|
+
class Metric
|
4
|
+
TYPE = %w(kv g ms h c s)
|
5
|
+
|
6
|
+
HASH_FIELD = %w(
|
7
|
+
type
|
8
|
+
key
|
9
|
+
key_field
|
10
|
+
value
|
11
|
+
value_field
|
12
|
+
)
|
13
|
+
|
14
|
+
FIELD = '\w+|\$\{\w+\}'
|
15
|
+
|
16
|
+
STRING_PATTERN = /^(#{FIELD}):(#{FIELD})\|(#{TYPE.join('|')})$/
|
17
|
+
STRING_EXAMPLE = "key_field:value_field|type"
|
18
|
+
|
19
|
+
def initialize(key, key_field, value, value_field, type)
|
20
|
+
@key = key
|
21
|
+
@key_field = key_field
|
22
|
+
@value = value
|
23
|
+
@value_field = value_field
|
24
|
+
@type = type
|
25
|
+
end
|
26
|
+
|
27
|
+
def convert(record)
|
28
|
+
k = @key.nil? ? record[@key_field] : @key
|
29
|
+
v = @value.nil? ? record[@value_field] : @value
|
30
|
+
(k.nil? or v.nil?) ? nil : "#{k}:#{v}|#{@type}\n"
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
k = @key.nil? ? "key_field=#{@key_field}" :"key=#{@key}"
|
35
|
+
v = @value.nil? ? "value_field=#{@value_field}" :"value=#{@value}"
|
36
|
+
"Metric(#{k}, #{v}, type=#{@type})"
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.validate(m)
|
40
|
+
if not (m.class == Hash or m.class == String)
|
41
|
+
raise ConfigError, "a type of metrics element must be Hash or String, but specified as #{m.class}"
|
42
|
+
end
|
43
|
+
|
44
|
+
case m
|
45
|
+
when Hash
|
46
|
+
m.keys.each do |k|
|
47
|
+
if not HASH_FIELD.member?(k)
|
48
|
+
raise ConfigError, "invalid metrics element hash key: #{k}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
if not m.has_key?('key') ^ m.has_key?('key_field')
|
53
|
+
raise ConfigError, "metrics element must contain either one of 'key' or 'key_field'"
|
54
|
+
end
|
55
|
+
|
56
|
+
if not m.has_key?('value') ^ m.has_key?('value_field')
|
57
|
+
raise ConfigError, "metrics element must contain either one of 'value' or 'value_field'"
|
58
|
+
end
|
59
|
+
|
60
|
+
if not m.has_key?('type')
|
61
|
+
raise ConfigError, "metrics element must contain 'type'"
|
62
|
+
end
|
63
|
+
|
64
|
+
if not TYPE.member?(m['type'])
|
65
|
+
raise ConfigError, "metrics type must be one of the following: #{TYPE.join(' ')}, but specified as #{m['type']}"
|
66
|
+
end
|
67
|
+
|
68
|
+
new(m['key'], m['value_field'], m['value'], m['value_field'], m['type'])
|
69
|
+
when String
|
70
|
+
if (STRING_PATTERN =~ m).nil?
|
71
|
+
raise ConfigError, "metrics string must be #{STRING_PATTERN}, but specified as #{m}"
|
72
|
+
end
|
73
|
+
|
74
|
+
key, key_field = $1.start_with?('$') ? [nil, $1[2..-2]] : [$1, nil]
|
75
|
+
value, value_field = $2.start_with?('$') ? [nil, $2[2..-2]] : [$2, nil]
|
76
|
+
new(key, key_field, value, value_field, $3)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'fluent/log'
|
3
|
+
require 'fluent/test'
|
4
|
+
|
5
|
+
$log = Fluent::Log.new(Fluent::Test::DummyLogDevice.new, Fluent::Log::LEVEL_WARN)
|
6
|
+
|
7
|
+
def records
|
8
|
+
[
|
9
|
+
{
|
10
|
+
"remote_addr" => "114.170.6.118",
|
11
|
+
"remote_user" => "-",
|
12
|
+
"time_local" => "20/Jul/2014:18:25:50 +0000",
|
13
|
+
"request" => "GET /foo HTTP/1.1",
|
14
|
+
"status" => "200",
|
15
|
+
"body_bytes_sent" => "911",
|
16
|
+
"http_referer" => "-",
|
17
|
+
"http_user_agent" => "Mozilla/5.0 (Linux; U; Android 4.2.2; ja-jp; SO-04E Build/10.3.1.B.0.256) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
|
18
|
+
"request_time" => "0.058",
|
19
|
+
"upstream_addr" => "192.168.222.180:80",
|
20
|
+
"upstream_response_time" => "0.058"
|
21
|
+
},
|
22
|
+
{
|
23
|
+
"remote_addr" => "180.214.48.86",
|
24
|
+
"remote_user" => "-",
|
25
|
+
"time_local" => "20/Jul/2014:18:25:50 +0000",
|
26
|
+
"request" => "POST /bar HTTP/1.1",
|
27
|
+
"status" => "200",
|
28
|
+
"body_bytes_sent" => "57",
|
29
|
+
"http_referer" => "-",
|
30
|
+
"http_user_agent" => "Mozilla/5.0 (Linux; U; Android 4.2.2; ja-jp; SO-04E Build/10.3.1.B.0.256) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
|
31
|
+
"request_time" => "0.041",
|
32
|
+
"upstream_addr" => "10.0.0.143:80",
|
33
|
+
"upstream_response_time" => "0.041"
|
34
|
+
},
|
35
|
+
{
|
36
|
+
"remote_addr" => "153.160.159.80",
|
37
|
+
"remote_user" => "-",
|
38
|
+
"time_local" => "20/Jul/2014:18:25:50 +0000",
|
39
|
+
"request" => "GET /foo HTTP/1.1",
|
40
|
+
"status" => "200",
|
41
|
+
"body_bytes_sent" => "34139",
|
42
|
+
"http_referer" => "/bar",
|
43
|
+
"http_user_agent" => "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:30.0) Gecko/20100101 Firefox/30.0",
|
44
|
+
"request_time" => "0.000",
|
45
|
+
"upstream_addr" => "-",
|
46
|
+
"upstream_response_time" => "-"
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"remote_addr" => "172.56.33.226",
|
50
|
+
"remote_user" => "-",
|
51
|
+
"time_local" => "20/Jul/2014:18:25:50 +0000",
|
52
|
+
"request" => "GET /foo HTTP/1.1",
|
53
|
+
"status" => "200",
|
54
|
+
"body_bytes_sent" => "791",
|
55
|
+
"http_referer" => "-",
|
56
|
+
"http_user_agent" => "en;Mozilla/5.0 (Linux; U; Android 4.3; en-us; SGH-T999 Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
|
57
|
+
"request_time" => "0.073",
|
58
|
+
"upstream_addr" => "192.168.222.209:80",
|
59
|
+
"upstream_response_time" => "0.073"
|
60
|
+
}
|
61
|
+
]
|
62
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'fluent/plugin/statsite/format'
|
3
|
+
|
4
|
+
include Fluent::StatsitePlugin
|
5
|
+
|
6
|
+
class StatsiteFormatterTest < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
metrics = [
|
9
|
+
Metric.validate('${k}:${v}|g'),
|
10
|
+
Metric.validate('${k}:v|g')
|
11
|
+
]
|
12
|
+
@formatter = StatsiteFormatter.new(metrics)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_call
|
16
|
+
record = {'v' => 'value'}
|
17
|
+
assert_equal "", @formatter.call(record)
|
18
|
+
|
19
|
+
record = {'k' => 'key'}
|
20
|
+
assert_equal "key:v|g\n", @formatter.call(record)
|
21
|
+
|
22
|
+
record = {'k' => 'key', 'v' => 'value'}
|
23
|
+
assert_equal "key:value|g\nkey:v|g\n", @formatter.call(record)
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'fluent/plugin/statsite/histogram'
|
3
|
+
|
4
|
+
include Fluent::StatsitePlugin
|
5
|
+
|
6
|
+
class Histogram
|
7
|
+
attr_reader :section, :prefix, :min, :max, :width
|
8
|
+
end
|
9
|
+
|
10
|
+
class HistogramTest < Test::Unit::TestCase
|
11
|
+
def valid_config
|
12
|
+
{
|
13
|
+
'prefix' => 'pre',
|
14
|
+
'min' => 0.0,
|
15
|
+
'max' => 10.0,
|
16
|
+
'width' => 1.0,
|
17
|
+
}
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_validate_object_type
|
21
|
+
config = []
|
22
|
+
assert_raises(Fluent::ConfigError) { Histogram.validate(config) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def test_validate_mandatory_field
|
26
|
+
Histogram::FIELD.each do |f|
|
27
|
+
config = valid_config
|
28
|
+
config.delete(f)
|
29
|
+
assert_raises(Fluent::ConfigError) { Histogram.validate(config) }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_validate_extra_field
|
34
|
+
config = valid_config
|
35
|
+
config['foo'] = 'bar'
|
36
|
+
assert_raises(Fluent::ConfigError) { Histogram.validate(config) }
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_validate_floating_field
|
40
|
+
Histogram::FLOATING_FIELD.each do |f|
|
41
|
+
config = valid_config
|
42
|
+
config[f] = 'foo'
|
43
|
+
assert_raises(Fluent::ConfigError) { Histogram.validate(config) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def test_validate_result
|
48
|
+
c = valid_config
|
49
|
+
h = Histogram.validate(c)
|
50
|
+
assert_equal c['prefix'], h.section
|
51
|
+
assert_equal c['prefix'], h.prefix
|
52
|
+
assert_equal c['min'], h.min
|
53
|
+
assert_equal c['max'], h.max
|
54
|
+
assert_equal c['width'], h.width
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_to_init
|
58
|
+
c = valid_config
|
59
|
+
|
60
|
+
ini = Histogram.validate(c).to_ini
|
61
|
+
assert_equal ini, <<-INI
|
62
|
+
[histogram_#{c['prefix']}]
|
63
|
+
prefix=#{c['prefix']}
|
64
|
+
min=#{c['min']}
|
65
|
+
max=#{c['max']}
|
66
|
+
width=#{c['width']}
|
67
|
+
INI
|
68
|
+
|
69
|
+
c.delete('prefix')
|
70
|
+
ini = Histogram.validate(c).to_ini
|
71
|
+
assert_equal ini, <<-INI
|
72
|
+
[histogram_default]
|
73
|
+
prefix=
|
74
|
+
min=#{c['min']}
|
75
|
+
max=#{c['max']}
|
76
|
+
width=#{c['width']}
|
77
|
+
INI
|
78
|
+
end
|
79
|
+
end
|
data/test/test_metric.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'fluent/plugin/statsite/metric'
|
3
|
+
|
4
|
+
include Fluent::StatsitePlugin
|
5
|
+
|
6
|
+
class Metric
|
7
|
+
attr_reader :key, :key_field, :value, :value_field, :type
|
8
|
+
end
|
9
|
+
|
10
|
+
class MetricTest < Test::Unit::TestCase
|
11
|
+
|
12
|
+
def valid_config
|
13
|
+
{'key' => 'k', 'value' => 'v', 'type' => 'kv'}
|
14
|
+
end
|
15
|
+
|
16
|
+
def test_validate_object_type
|
17
|
+
config = []
|
18
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def test_validate_extra_field
|
22
|
+
config = valid_config
|
23
|
+
config['foo'] = 'bar'
|
24
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_validate_key
|
28
|
+
config = (valid_config)['key_field'] = 'k'
|
29
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
30
|
+
|
31
|
+
config = (valid_config)
|
32
|
+
config.delete('key')
|
33
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def test_validate_value
|
37
|
+
config = (valid_config)['value_field'] = 'k'
|
38
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
39
|
+
|
40
|
+
config = (valid_config)
|
41
|
+
config.delete('value')
|
42
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_validate_type
|
46
|
+
config = (valid_config)
|
47
|
+
config.delete('type')
|
48
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
49
|
+
|
50
|
+
config = (valid_config)
|
51
|
+
config['type'] = 'foo'
|
52
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_validate_string
|
56
|
+
config = "foo"
|
57
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
58
|
+
|
59
|
+
config = "k:v|foo"
|
60
|
+
assert_raises(Fluent::ConfigError) { Metric.validate(config) }
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_validate_result
|
64
|
+
m = Metric.validate(valid_config)
|
65
|
+
assert_equal 'k', m.key
|
66
|
+
assert_nil m.key_field
|
67
|
+
assert_equal 'v', m.value
|
68
|
+
assert_nil m.value_field
|
69
|
+
assert_equal 'kv', m.type
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_validate_result_string
|
73
|
+
m = Metric.validate('k:v|kv')
|
74
|
+
assert_equal 'k', m.key
|
75
|
+
assert_nil m.key_field
|
76
|
+
assert_equal 'v', m.value
|
77
|
+
assert_nil m.value_field
|
78
|
+
assert_equal 'kv', m.type
|
79
|
+
|
80
|
+
m = Metric.validate('${k}:${v}|kv')
|
81
|
+
assert_nil m.key
|
82
|
+
assert_equal 'k', m.key_field
|
83
|
+
assert_nil m.value
|
84
|
+
assert_equal 'v', m.value_field
|
85
|
+
assert_equal 'kv', m.type
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_convert
|
89
|
+
m = Metric.validate('${k}:${v}|kv')
|
90
|
+
|
91
|
+
record = {'k' => 'key'}
|
92
|
+
assert_nil m.convert(record)
|
93
|
+
|
94
|
+
record = {'v' => 'value'}
|
95
|
+
assert_nil m.convert(record)
|
96
|
+
|
97
|
+
record = {'k' => 'key', 'v' => 'value'}
|
98
|
+
assert_equal "key:value|kv\n", m.convert(record)
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'fluent/plugin/out_statsite'
|
3
|
+
|
4
|
+
class Fluent::StatsiteOutput
|
5
|
+
attr_reader :respawns
|
6
|
+
end
|
7
|
+
|
8
|
+
class StatsiteOutputTest < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
Fluent::Test.setup
|
11
|
+
end
|
12
|
+
|
13
|
+
ROOT = File.expand_path('..', File.dirname(__FILE__))
|
14
|
+
PATH = ROOT + '/vendor/statsite/statsite'
|
15
|
+
|
16
|
+
CONFIG = %[
|
17
|
+
type statsite
|
18
|
+
tag statsite
|
19
|
+
metrics [
|
20
|
+
"${status}:1|c"
|
21
|
+
]
|
22
|
+
histograms [
|
23
|
+
{"prefix": "k", "min": 0, "max": 10, "width": 1.0}
|
24
|
+
]
|
25
|
+
statsite_path "#{PATH}"
|
26
|
+
statsite_flush_interval 1s
|
27
|
+
timer_eps 0.01
|
28
|
+
set_eps 0.02
|
29
|
+
child_respawn 5
|
30
|
+
]
|
31
|
+
|
32
|
+
RECORDS = [
|
33
|
+
]
|
34
|
+
|
35
|
+
def create_driver(conf = CONFIG)
|
36
|
+
Fluent::Test::OutputTestDriver.new(Fluent::StatsiteOutput).configure(conf, true)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_configure
|
40
|
+
d = create_driver
|
41
|
+
|
42
|
+
assert_equal 'statsite', d.instance.tag
|
43
|
+
assert d.instance.metrics.all?{|m| m.class == Fluent::StatsitePlugin::Metric}
|
44
|
+
assert d.instance.histograms.all?{|m| m.class == Fluent::StatsitePlugin::Histogram}
|
45
|
+
assert_equal PATH, d.instance.statsite_path
|
46
|
+
assert_equal 1, d.instance.statsite_flush_interval
|
47
|
+
assert_equal 0.01, d.instance.timer_eps
|
48
|
+
assert_equal 0.02, d.instance.set_eps
|
49
|
+
assert_equal 5, d.instance.respawns
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_emit
|
53
|
+
d = create_driver
|
54
|
+
|
55
|
+
d.run do
|
56
|
+
records.each { |r| d.emit(r, Time.now) }
|
57
|
+
end
|
58
|
+
|
59
|
+
emits = d.emits
|
60
|
+
|
61
|
+
count_result = emits.pop
|
62
|
+
assert_equal 'statsite', count_result[0]
|
63
|
+
assert_equal({type: 'counts', key: '200', value: 4.0}, count_result[2])
|
64
|
+
end
|
65
|
+
end
|
data/test/test_parser.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'helper'
|
2
|
+
require 'fluent/plugin/statsite/format'
|
3
|
+
|
4
|
+
include Fluent::StatsitePlugin
|
5
|
+
|
6
|
+
class StatsiteParserTest < Test::Unit::TestCase
|
7
|
+
def setup
|
8
|
+
@res = []
|
9
|
+
proc = Proc.new {|time, record| @res << {time: time, record:record } }
|
10
|
+
@parser = StatsiteParser.new(proc)
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_eachline_kv
|
14
|
+
line = 'kv.k|1.000000|1405869579'
|
15
|
+
@parser.each_line(line)
|
16
|
+
expected = {
|
17
|
+
time: 1405869579,
|
18
|
+
record: { type: 'kv', key: 'k', value: 1.000000 }
|
19
|
+
}
|
20
|
+
assert_equal expected, @res.pop
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_eachline_gauge
|
24
|
+
line = 'gauges.k|1.000000|1405869579'
|
25
|
+
@parser.each_line(line)
|
26
|
+
expected = {
|
27
|
+
time: 1405869579,
|
28
|
+
record: { type: 'gauges', key: 'k', value: 1.000000 }
|
29
|
+
}
|
30
|
+
assert_equal expected, @res.pop
|
31
|
+
end
|
32
|
+
|
33
|
+
def test_eachline_counts
|
34
|
+
line = 'counts.k|1.000000|1405869579'
|
35
|
+
@parser.each_line(line)
|
36
|
+
expected = {
|
37
|
+
time: 1405869579,
|
38
|
+
record: { type: 'counts', key: 'k', value: 1.000000 }
|
39
|
+
}
|
40
|
+
assert_equal expected, @res.pop
|
41
|
+
end
|
42
|
+
|
43
|
+
def test_eachline_sets
|
44
|
+
line = 'sets.k|1|1405869579'
|
45
|
+
@parser.each_line(line)
|
46
|
+
expected = {
|
47
|
+
time: 1405869579,
|
48
|
+
record: { type: 'sets', key: 'k', value: 1 }
|
49
|
+
}
|
50
|
+
assert_equal expected, @res.pop
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_eachline_timers
|
54
|
+
line = 'timers.k.sum|1.000000|1405869579'
|
55
|
+
@parser.each_line(line)
|
56
|
+
expected = {
|
57
|
+
time: 1405869579,
|
58
|
+
record: { type: 'timers', key: 'k', value: 1.000000, statistic: 'sum' }
|
59
|
+
}
|
60
|
+
assert_equal expected, @res.pop
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_eachline_timers_count
|
64
|
+
line = 'timers.k.count|1|1405869579'
|
65
|
+
@parser.each_line(line)
|
66
|
+
expected = {
|
67
|
+
time: 1405869579,
|
68
|
+
record: { type: 'timers', key: 'k', value: 1, statistic: 'count' }
|
69
|
+
}
|
70
|
+
assert_equal expected, @res.pop
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_eachline_timers_histogram
|
74
|
+
line = 'timers.k.histogram.bin_<0.00|0|1405869579'
|
75
|
+
@parser.each_line(line)
|
76
|
+
expected = {
|
77
|
+
time: 1405869579,
|
78
|
+
record: { type: 'timers', key: 'k', value: 0, statistic: 'histogram', range: '<0.00' }
|
79
|
+
}
|
80
|
+
assert_equal expected, @res.pop
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_eachline_invalid_line
|
84
|
+
line = 'test'
|
85
|
+
assert_raise(RuntimeError) { @parser.each_line(line) }
|
86
|
+
end
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fluent-plugin-statsite
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- OKUNO Akihiro
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-07-20 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: fluentd
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.6'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.6'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Fluentd plugin which caluculate statistics using statsite
|
56
|
+
email:
|
57
|
+
- choplin.choplin@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".gitignore"
|
63
|
+
- ".gitmodules"
|
64
|
+
- Gemfile
|
65
|
+
- LICENSE.txt
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- example.conf
|
69
|
+
- fluent-plugin-statsite.gemspec
|
70
|
+
- lib/fluent/plugin/out_statsite.rb
|
71
|
+
- lib/fluent/plugin/statsite/child_process.rb
|
72
|
+
- lib/fluent/plugin/statsite/format.rb
|
73
|
+
- lib/fluent/plugin/statsite/histogram.rb
|
74
|
+
- lib/fluent/plugin/statsite/metric.rb
|
75
|
+
- test/helper.rb
|
76
|
+
- test/test_formatter.rb
|
77
|
+
- test/test_histogram.rb
|
78
|
+
- test/test_metric.rb
|
79
|
+
- test/test_out_statsite.rb
|
80
|
+
- test/test_parser.rb
|
81
|
+
homepage: https://github.com/choplin/fluent-plugin-statsite
|
82
|
+
licenses:
|
83
|
+
- Apache-2.0
|
84
|
+
metadata: {}
|
85
|
+
post_install_message:
|
86
|
+
rdoc_options: []
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
requirements:
|
96
|
+
- - ">="
|
97
|
+
- !ruby/object:Gem::Version
|
98
|
+
version: '0'
|
99
|
+
requirements: []
|
100
|
+
rubyforge_project:
|
101
|
+
rubygems_version: 2.2.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 4
|
104
|
+
summary: Fluentd statsite plugin
|
105
|
+
test_files:
|
106
|
+
- test/helper.rb
|
107
|
+
- test/test_formatter.rb
|
108
|
+
- test/test_histogram.rb
|
109
|
+
- test/test_metric.rb
|
110
|
+
- test/test_out_statsite.rb
|
111
|
+
- test/test_parser.rb
|