fluent-plugin-growthforecast 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,16 +1,4 @@
1
- source "http://rubygems.org"
2
- # Add dependencies required to use your gem here.
3
- # Example:
4
- # gem "activesupport", ">= 2.3.5"
1
+ source 'https://rubygems.org'
5
2
 
6
- # Add dependencies to develop your gem here.
7
- # Include everything needed to run rake, tests, features, etc.
8
- group :development do
9
- gem "shoulda", ">= 0"
10
- gem "bundler", "~> 1.0.0"
11
- gem "jeweler", "~> 1.6.4"
12
- gem "simplecov", ">= 0"
13
- end
14
-
15
- gem "fluentd"
16
- gem "rdoc"
3
+ # Specify your gem's dependencies in fluent-plugin-growthforecast.gemspec
4
+ gemspec
@@ -0,0 +1,82 @@
1
+ # fluent-plugin-growthforecast
2
+
3
+ ## GrowthForecastOutput
4
+
5
+ Plugin to output numbers(metrics) to 'GrowthForecast', metrics drawing tool over HTTP.
6
+
7
+ About GrowthForecast, see:
8
+ * Github: https://github.com/kazeburo/growthforecast
9
+ * Product site (japanese): http://kazeburo.github.com/GrowthForecast/
10
+ * Japanese blog post by @kazeburo: http://blog.nomadscafe.jp/2011/12/growthforecast.html
11
+
12
+ GrowthForecast is very simple and powerful tool to draw graphs what we want, with GrowthForecastOutput and Fluentd.
13
+
14
+ ### Configuration
15
+
16
+ For messages such as:
17
+ tag:metrics {"field1":300, "field2":20, "field3diff":-30}
18
+
19
+ Configuration example for graphs in growthforecast with POST api url 'http://growthforecast.local/api/service1/metrics1/metrics_FIELDNAME'.
20
+
21
+ <match metrics>
22
+ type growthforecast
23
+ gfapi_url http://growthforecast.local/api/
24
+ service service1
25
+ section metrics1
26
+ name_keys field1,field2,field3diff
27
+ </match>
28
+
29
+ With this configuration, out_growthforecast posts urls below.
30
+
31
+ http://growthforecast.local/api/service1/metrics1/metrics_field1
32
+ http://growthforecast.local/api/service1/metrics1/metrics_field2
33
+ http://growthforecast.local/api/service1/metrics1/metrics_field3diff
34
+
35
+ If you want to use tags for `section` or `service` in GrowthForecast, use `tag_for` options and `remove_prefix` (and not to set the `section` or `service` that the value of 'tag_for' used to.).
36
+
37
+ <match metrics.**>
38
+ type growthforecast
39
+ gfapi_url http://growthforecast.local/api/
40
+ service service1
41
+ name_keys field1,field2,field3diff
42
+ tag_for section # or 'name_prefix'(default) or 'ignore' or 'service'
43
+ remove_prefix metrics
44
+ </match>
45
+
46
+ `mode` option available with `gauge`(default), `count`, `modified`, just same as `mode` of GrowthForecast POST parameter.
47
+
48
+ `name_key_pattern REGEXP` available instead of `name_keys` like this:
49
+
50
+ <match metrics.**>
51
+ type growthforecast
52
+ gfapi_url http://growthforecast.local/api/
53
+ service service1
54
+ tag_for section # or 'name_prefix'(default) or 'ignore' or 'service'
55
+ remove_prefix metrics
56
+ name_key_pattern ^(field|key)\d+$
57
+ </match>
58
+
59
+ This configuration matches only with metrics.field1, metrics.key20, .... and doesn't match with metrics.field or metrics.foo.
60
+
61
+ If your GrowthForecast protected with basic authentication, specify `authentication` option:
62
+
63
+ <match metrics.**>
64
+ type growthforecast
65
+ gfapi_url http://growthforecast.protected.anywhere.example.com/api/
66
+ service yourservice
67
+ tag_for section
68
+ name_keys fieldname
69
+ authentication basic
70
+ username yourusername
71
+ password secret!
72
+ </match>
73
+
74
+ ## TODO
75
+
76
+ * patches welcome!
77
+
78
+ ## Copyright
79
+
80
+ * Copyright (c) 2012- TAGOMORI Satoshi (tagomoris)
81
+ * License
82
+ * Apache License, Version 2.0
data/Rakefile CHANGED
@@ -1,36 +1,5 @@
1
- # encoding: utf-8
2
-
3
- require 'rubygems'
4
- require 'bundler'
5
- begin
6
- Bundler.setup(:default, :development)
7
- rescue Bundler::BundlerError => e
8
- $stderr.puts e.message
9
- $stderr.puts "Run `bundle install` to install missing gems"
10
- exit e.status_code
11
- end
12
- require 'rake'
13
-
14
- require 'jeweler'
15
- Jeweler::Tasks.new do |gem|
16
- # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
- gem.name = "fluent-plugin-growthforecast"
18
- gem.description = "Plugin to post numbers to GrowthForecast (by kazeburo)"
19
- gem.homepage = "http://github.com/tagomoris/fluent-plugin-growthforecast"
20
- gem.summary = gem.description
21
- gem.email = "tagomoris@gmail.com"
22
- gem.authors = ["TAGOMORI Satoshi"]
23
- gem.has_rdoc = "false"
24
- # dependencies defined in Gemfile
25
- gem.files = `git ls-files`.split("\n")
26
- gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
27
- gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
28
- gem.require_paths = ['lib']
29
- gem.add_dependency "fluentd", "~> 0.10.8"
30
- gem.add_development_dependency "rake", ">= 0.9.2"
31
- gem.add_development_dependency "simplecov", ">= 0.5.4"
32
- end
33
- Jeweler::RubygemsDotOrgTasks.new
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
34
3
 
35
4
  require 'rake/testtask'
36
5
  Rake::TestTask.new(:test) do |test|
@@ -40,13 +9,3 @@ Rake::TestTask.new(:test) do |test|
40
9
  end
41
10
 
42
11
  task :default => :test
43
-
44
- require 'rdoc/task'
45
- Rake::RDocTask.new do |rdoc|
46
- version = File.exist?('VERSION') ? File.read('VERSION') : ""
47
-
48
- rdoc.rdoc_dir = 'rdoc'
49
- rdoc.title = "fluent-plugin-growthforecast #{version}"
50
- rdoc.rdoc_files.include('README*')
51
- rdoc.rdoc_files.include('lib/**/*.rb')
52
- end
@@ -1,75 +1,22 @@
1
- # Generated by jeweler
2
- # DO NOT EDIT THIS FILE DIRECTLY
3
- # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
1
  # -*- encoding: utf-8 -*-
5
2
 
6
- Gem::Specification.new do |s|
7
- s.name = "fluent-plugin-growthforecast"
8
- s.version = "0.1.4"
3
+ Gem::Specification.new do |gem|
4
+ gem.name = "fluent-plugin-growthforecast"
5
+ gem.version = "0.1.5"
6
+ gem.authors = ["TAGOMORI Satoshi"]
7
+ gem.email = ["tagomoris@gmail.com"]
8
+ gem.summary = %q{Fluentd output plugin to post numbers to GrowthForecast (by kazeburo)}
9
+ gem.description = %q{For GrowthForecast, see http://kazeburo.github.com/GrowthForecast/}
10
+ gem.homepage = "https://github.com/tagomoris/fluent-plugin-growthforecast"
9
11
 
10
- s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
- s.authors = ["TAGOMORI Satoshi"]
12
- s.date = "2012-12-05"
13
- s.description = "Plugin to post numbers to GrowthForecast (by kazeburo)"
14
- s.email = "tagomoris@gmail.com"
15
- s.extra_rdoc_files = [
16
- "LICENSE.txt",
17
- "README.rdoc"
18
- ]
19
- s.files = [
20
- ".document",
21
- ".gitignore",
22
- "AUTHORS",
23
- "Gemfile",
24
- "LICENSE.txt",
25
- "README.rdoc",
26
- "Rakefile",
27
- "VERSION",
28
- "fluent-plugin-growthforecast.gemspec",
29
- "lib/fluent/plugin/out_growthforecast.rb",
30
- "test/helper.rb",
31
- "test/plugin/test_out_growthforecast.rb"
32
- ]
33
- s.homepage = "http://github.com/tagomoris/fluent-plugin-growthforecast"
34
- s.require_paths = ["lib"]
35
- s.rubygems_version = "1.8.21"
36
- s.summary = "Plugin to post numbers to GrowthForecast (by kazeburo)"
37
- s.test_files = ["test/helper.rb", "test/plugin/test_out_growthforecast.rb"]
12
+ gem.files = `git ls-files`.split($\)
13
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
14
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
15
+ gem.require_paths = ["lib"]
38
16
 
39
- if s.respond_to? :specification_version then
40
- s.specification_version = 3
41
-
42
- if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
43
- s.add_runtime_dependency(%q<fluentd>, [">= 0"])
44
- s.add_runtime_dependency(%q<rdoc>, [">= 0"])
45
- s.add_development_dependency(%q<shoulda>, [">= 0"])
46
- s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
47
- s.add_development_dependency(%q<jeweler>, ["~> 1.6.4"])
48
- s.add_development_dependency(%q<simplecov>, [">= 0"])
49
- s.add_runtime_dependency(%q<fluentd>, ["~> 0.10.8"])
50
- s.add_development_dependency(%q<rake>, [">= 0.9.2"])
51
- s.add_development_dependency(%q<simplecov>, [">= 0.5.4"])
52
- else
53
- s.add_dependency(%q<fluentd>, [">= 0"])
54
- s.add_dependency(%q<rdoc>, [">= 0"])
55
- s.add_dependency(%q<shoulda>, [">= 0"])
56
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
57
- s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
58
- s.add_dependency(%q<simplecov>, [">= 0"])
59
- s.add_dependency(%q<fluentd>, ["~> 0.10.8"])
60
- s.add_dependency(%q<rake>, [">= 0.9.2"])
61
- s.add_dependency(%q<simplecov>, [">= 0.5.4"])
62
- end
63
- else
64
- s.add_dependency(%q<fluentd>, [">= 0"])
65
- s.add_dependency(%q<rdoc>, [">= 0"])
66
- s.add_dependency(%q<shoulda>, [">= 0"])
67
- s.add_dependency(%q<bundler>, ["~> 1.0.0"])
68
- s.add_dependency(%q<jeweler>, ["~> 1.6.4"])
69
- s.add_dependency(%q<simplecov>, [">= 0"])
70
- s.add_dependency(%q<fluentd>, ["~> 0.10.8"])
71
- s.add_dependency(%q<rake>, [">= 0.9.2"])
72
- s.add_dependency(%q<simplecov>, [">= 0.5.4"])
73
- end
17
+ gem.add_development_dependency "bundler"
18
+ gem.add_development_dependency "fluentd"
19
+ gem.add_development_dependency "fluent-mixin-config-placeholders"
20
+ gem.add_runtime_dependency "fluentd"
21
+ gem.add_runtime_dependency "fluent-mixin-config-placeholders"
74
22
  end
75
-
@@ -8,24 +8,30 @@ class Fluent::GrowthForecastOutput < Fluent::Output
8
8
  end
9
9
 
10
10
  config_param :gfapi_url, :string # growth.forecast.local/api/
11
- config_param :service, :string
11
+ config_param :service, :string, :default => nil
12
12
  config_param :section, :string, :default => nil
13
13
 
14
+ config_param :ssl, :bool, :default => false
15
+ config_param :verify_ssl, :bool, :default => false
16
+
14
17
  config_param :name_keys, :string, :default => nil
15
18
  config_param :name_key_pattern, :string, :default => nil
16
19
 
17
20
  config_param :mode, :string, :default => 'gauge' # or count/modified
18
21
 
19
22
  config_param :remove_prefix, :string, :default => nil
20
- config_param :tag_for, :string, :default => 'name_prefix' # or 'ignore' or 'section'
23
+ config_param :tag_for, :string, :default => 'name_prefix' # or 'ignore' or 'section' or 'service'
21
24
 
25
+ config_param :authentication, :string, :default => nil # nil or 'none' or 'basic'
26
+ config_param :username, :string, :default => ''
27
+ config_param :password, :string, :default => ''
28
+
22
29
  def configure(conf)
23
30
  super
24
31
 
25
32
  if @gfapi_url !~ /\/api\/\Z/
26
33
  raise Fluent::ConfigError, "gfapi_url must end with /api/"
27
34
  end
28
- @gfurl = @gfapi_url + @service + '/'
29
35
 
30
36
  if @name_keys.nil? and @name_key_pattern.nil?
31
37
  raise Fluent::ConfigError, "missing both of name_keys and name_key_pattern"
@@ -49,17 +55,27 @@ class Fluent::GrowthForecastOutput < Fluent::Output
49
55
  @tag_for = case @tag_for
50
56
  when 'ignore' then :ignore
51
57
  when 'section' then :section
58
+ when 'service' then :service
52
59
  else
53
60
  :name_prefix
54
61
  end
55
62
  if @tag_for != :section and @section.nil?
56
63
  raise Fluent::ConfigError, "section parameter is needed when tag_for is not 'section'"
57
64
  end
65
+ if @tag_for != :service and @service.nil?
66
+ raise Fluent::ConfigError, "service parameter is needed when tag_for is not 'service'"
67
+ end
58
68
 
59
69
  if @remove_prefix
60
70
  @removed_prefix_string = @remove_prefix + '.'
61
71
  @removed_length = @removed_prefix_string.length
62
72
  end
73
+
74
+ @auth = case @authentication
75
+ when 'basic' then :basic
76
+ else
77
+ :none
78
+ end
63
79
  end
64
80
 
65
81
  def start
@@ -71,25 +87,44 @@ class Fluent::GrowthForecastOutput < Fluent::Output
71
87
  end
72
88
 
73
89
  def format_url(tag, name)
74
- name_esc = URI.escape(name)
90
+ if @remove_prefix and
91
+ ( (tag.start_with?(@removed_prefix_string) and tag.length > @removed_length) or tag == @remove_prefix)
92
+ tag = tag[@removed_length..-1]
93
+ end
94
+
75
95
  case @tag_for
76
96
  when :ignore
77
- @gfurl + @section + '/' + name_esc
97
+ @gfapi_url + URI.escape(@service + '/' + @section + '/' + name)
78
98
  when :section
79
- @gfurl + tag + '/' + name_esc
99
+ @gfapi_url + URI.escape(@service + '/' + tag + '/' + name)
100
+ when :service
101
+ @gfapi_url + URI.escape(tag + '/' + @section + '/' + name)
80
102
  when :name_prefix
81
- @gfurl + @section + '/' + tag + '_' + name_esc
103
+ @gfapi_url + URI.escape(@service + '/' + @section + '/' + tag + '_' + name)
82
104
  end
83
105
  end
84
106
 
85
107
  def post(tag, name, value)
86
108
  url = format_url(tag,name)
109
+ res = nil
87
110
  begin
88
- res = Net::HTTP.post_form(URI.parse(url), {'number' => value.to_i, 'mode' => @mode.to_s})
111
+ url = URI.parse(url)
112
+ req = Net::HTTP::Post.new(url.path)
113
+ if @auth and @auth == :basic
114
+ req.basic_auth(@username, @password)
115
+ end
116
+ req.set_form_data({'number' => value.to_i, 'mode' => @mode.to_s})
117
+ http = Net::HTTP.new(url.host, url.port)
118
+ if @ssl
119
+ http.use_ssl = true
120
+ unless @verify_ssl
121
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
122
+ end
123
+ end
124
+ res = http.start {|http| http.request(req) }
89
125
  rescue IOError, EOFError, SystemCallError
90
126
  # server didn't respond
91
127
  $log.warn "Net::HTTP.post_form raises exception: #{$!.class}, '#{$!.message}'"
92
- res = nil
93
128
  end
94
129
  unless res and res.is_a?(Net::HTTPSuccess)
95
130
  $log.warn "failed to post to growthforecast: #{url}, number: #{value}, code: #{res && res.code}"
@@ -97,10 +132,6 @@ class Fluent::GrowthForecastOutput < Fluent::Output
97
132
  end
98
133
 
99
134
  def emit(tag, es, chain)
100
- if @remove_prefix and
101
- ( (tag.start_with?(@removed_prefix_string) and tag.length > @removed_length) or tag == @remove_prefix)
102
- tag = tag[@removed_length..-1]
103
- end
104
135
  if @name_keys
105
136
  es.each {|time,record|
106
137
  @name_keys.each {|name|
@@ -8,12 +8,45 @@ rescue Bundler::BundlerError => e
8
8
  exit e.status_code
9
9
  end
10
10
  require 'test/unit'
11
- require 'shoulda'
12
11
 
13
12
  $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14
13
  $LOAD_PATH.unshift(File.dirname(__FILE__))
15
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
+
16
25
  require 'fluent/plugin/out_growthforecast'
17
26
 
18
27
  class Test::Unit::TestCase
19
28
  end
29
+
30
+ require 'webrick'
31
+
32
+ # to handle POST/PUT/DELETE ...
33
+ module WEBrick::HTTPServlet
34
+ class ProcHandler < AbstractServlet
35
+ alias do_POST do_GET
36
+ alias do_PUT do_GET
37
+ alias do_DELETE do_GET
38
+ end
39
+ end
40
+
41
+ def get_code(server, port, path, headers={})
42
+ require 'net/http'
43
+ Net::HTTP.start(server, port){|http|
44
+ http.get(path, headers).code
45
+ }
46
+ end
47
+ def get_content(server, port, path, headers={})
48
+ require 'net/http'
49
+ Net::HTTP.start(server, port){|http|
50
+ http.get(path, headers).body
51
+ }
52
+ end
@@ -1,7 +1,421 @@
1
1
  require 'helper'
2
2
 
3
- class TestFluentPluginGrowthforecast < Test::Unit::TestCase
4
- should "probably rename this file and start testing for real" do
5
- flunk "hey buddy, you should probably rename this file and start testing for real"
3
+ class GrowthForecastOutputTest < Test::Unit::TestCase
4
+ # setup/teardown and tests of dummy growthforecast server defined at the end of this class...
5
+ GF_TEST_LISTEN_PORT = 5125
6
+
7
+ CONFIG1 = %[
8
+ gfapi_url http://127.0.0.1:5125/api/
9
+ service service
10
+ section metrics
11
+ name_keys field1,field2,otherfield
12
+ tag_for name_prefix
13
+ ]
14
+
15
+ CONFIG2 = %[
16
+ gfapi_url http://127.0.0.1:5125/api/
17
+ service service
18
+ section metrics
19
+ tag_for ignore
20
+ name_keys field1,field2,otherfield
21
+ mode count
22
+ ]
23
+
24
+ CONFIG3 = %[
25
+ gfapi_url http://127.0.0.1:5125/api/
26
+ service service
27
+ tag_for section
28
+ remove_prefix test
29
+ name_key_pattern ^(field|key)\\d+$
30
+ mode modified
31
+ ]
32
+
33
+ CONFIG4 = %[
34
+ gfapi_url http://127.0.0.1:5125/api/
35
+ section metrics
36
+ name_keys field1,field2,otherfield
37
+ tag_for service
38
+ remove_prefix test
39
+ ]
40
+
41
+ CONFIG_SPACE = %[
42
+ gfapi_url http://127.0.0.1:5125/api/
43
+ service service x
44
+ section metrics y
45
+ name_keys field z
46
+ tag_for ignore
47
+ ]
48
+
49
+ def create_driver(conf=CONFIG1, tag='test.metrics')
50
+ Fluent::Test::OutputTestDriver.new(Fluent::GrowthForecastOutput, tag).configure(conf)
51
+ end
52
+
53
+ def test_configure_and_format_url
54
+ d = create_driver
55
+ assert_equal 'http://127.0.0.1:5125/api/', d.instance.gfapi_url
56
+ assert_equal 'service', d.instance.service
57
+ assert_equal 'metrics', d.instance.section
58
+ assert_equal ['field1', 'field2', 'otherfield'], d.instance.name_keys
59
+ assert_nil d.instance.remove_prefix
60
+ assert_equal :name_prefix, d.instance.tag_for
61
+ assert_equal :gauge, d.instance.mode
62
+
63
+ assert_equal 'http://127.0.0.1:5125/api/service/metrics/test.data1_field1', d.instance.format_url('test.data1', 'field1')
64
+
65
+ d = create_driver(CONFIG2)
66
+ assert_equal 'http://127.0.0.1:5125/api/', d.instance.gfapi_url
67
+ assert_equal 'service', d.instance.service
68
+ assert_equal 'metrics', d.instance.section
69
+ assert_equal ['field1', 'field2', 'otherfield'], d.instance.name_keys
70
+ assert_nil d.instance.remove_prefix
71
+ assert_equal :ignore, d.instance.tag_for
72
+ assert_equal :count, d.instance.mode
73
+
74
+ assert_equal 'http://127.0.0.1:5125/api/service/metrics/field1', d.instance.format_url('test.data1', 'field1')
75
+
76
+ d = create_driver(CONFIG3)
77
+ assert_equal 'http://127.0.0.1:5125/api/', d.instance.gfapi_url
78
+ assert_equal 'service', d.instance.service
79
+ assert_nil d.instance.section
80
+ assert_equal Regexp.new('^(field|key)\d+$'), d.instance.name_key_pattern
81
+ assert_equal 'test', d.instance.remove_prefix
82
+ assert_equal :section, d.instance.tag_for
83
+ assert_equal 'test.', d.instance.instance_eval{ @removed_prefix_string }
84
+ assert_equal :modified, d.instance.mode
85
+
86
+ assert_equal 'http://127.0.0.1:5125/api/service/data1/field1', d.instance.format_url('test.data1', 'field1')
87
+ end
88
+
89
+ # CONFIG1 = %[
90
+ # gfapi_url http://127.0.0.1:5125/api/
91
+ # service service
92
+ # section metrics
93
+ # name_keys field1,field2,otherfield
94
+ # tag_for name_prefix
95
+ # ]
96
+ def test_emit_1
97
+ d = create_driver(CONFIG1, 'test.metrics')
98
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
99
+ d.run
100
+
101
+ assert_equal 3, @posted.size
102
+ v1st = @posted[0]
103
+ v2nd = @posted[1]
104
+ v3rd = @posted[2]
105
+
106
+ assert_equal 50, v1st[:data][:number]
107
+ assert_equal 'gauge', v1st[:data][:mode]
108
+ assert_nil v1st[:auth]
109
+ assert_equal 'service', v1st[:service]
110
+ assert_equal 'metrics', v1st[:section]
111
+ assert_equal 'test.metrics_field1', v1st[:name]
112
+
113
+ assert_equal 20, v2nd[:data][:number]
114
+ assert_equal 'test.metrics_field2', v2nd[:name]
115
+
116
+ assert_equal 1, v3rd[:data][:number]
117
+ assert_equal 'test.metrics_otherfield', v3rd[:name]
118
+ end
119
+
120
+ # CONFIG2 = %[
121
+ # gfapi_url http://127.0.0.1:5125/api/
122
+ # service service
123
+ # section metrics
124
+ # tag_for ignore
125
+ # name_keys field1,field2,otherfield
126
+ # mode count
127
+ # ]
128
+ def test_emit_2
129
+ d = create_driver(CONFIG2, 'test.metrics')
130
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
131
+ d.run
132
+
133
+ assert_equal 3, @posted.size
134
+ v1st = @posted[0]
135
+ v2nd = @posted[1]
136
+ v3rd = @posted[2]
137
+
138
+ assert_equal 50, v1st[:data][:number]
139
+ assert_equal 'count', v1st[:data][:mode]
140
+ assert_nil v1st[:auth]
141
+ assert_equal 'service', v1st[:service]
142
+ assert_equal 'metrics', v1st[:section]
143
+ assert_equal 'field1', v1st[:name]
144
+
145
+ assert_equal 20, v2nd[:data][:number]
146
+ assert_equal 'field2', v2nd[:name]
147
+
148
+ assert_equal 1, v3rd[:data][:number]
149
+ assert_equal 'otherfield', v3rd[:name]
150
+ end
151
+
152
+ # CONFIG3 = %[
153
+ # gfapi_url http://127.0.0.1:5125/api/
154
+ # service service
155
+ # tag_for section
156
+ # remove_prefix test
157
+ # name_key_pattern ^(field|key)\\d+$
158
+ # mode modified
159
+ # ]
160
+ def test_emit_3
161
+ d = create_driver(CONFIG3, 'test.metrics')
162
+ # recent ruby's Hash saves elements order....
163
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
164
+ d.run
165
+
166
+ assert_equal 3, @posted.size
167
+ v1st = @posted[0]
168
+ v2nd = @posted[1]
169
+ v3rd = @posted[2]
170
+
171
+ assert_equal 50, v1st[:data][:number]
172
+ assert_equal 'modified', v1st[:data][:mode]
173
+ assert_nil v1st[:auth]
174
+ assert_equal 'service', v1st[:service]
175
+ assert_equal 'metrics', v1st[:section]
176
+ assert_equal 'field1', v1st[:name]
177
+
178
+ assert_equal 20, v2nd[:data][:number]
179
+ assert_equal 'field2', v2nd[:name]
180
+
181
+ assert_equal 10, v3rd[:data][:number]
182
+ assert_equal 'field3', v3rd[:name]
183
+ end
184
+
185
+ # CONFIG1 = %[
186
+ # gfapi_url http://127.0.0.1:5125/api/
187
+ # service service
188
+ # section metrics
189
+ # name_keys field1,field2,otherfield
190
+ # tag_for name_prefix
191
+ # ]
192
+ def test_emit_4_auth
193
+ @auth = true # enable authentication of dummy server
194
+
195
+ d = create_driver(CONFIG1, 'test.metrics')
196
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
197
+ d.run # failed in background, and output warn log
198
+
199
+ assert_equal 0, @posted.size
200
+ assert_equal 3, @prohibited
201
+
202
+ d = create_driver(CONFIG1 + %[
203
+ authentication basic
204
+ username alice
205
+ password wrong_password
206
+ ], 'test.metrics')
207
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
208
+ d.run # failed in background, and output warn log
209
+
210
+ assert_equal 0, @posted.size
211
+ assert_equal 6, @prohibited
212
+
213
+ d = create_driver(CONFIG1 + %[
214
+ authentication basic
215
+ username alice
216
+ password secret!
217
+ ], 'test.metrics')
218
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
219
+ d.run # failed in background, and output warn log
220
+
221
+ assert_equal 6, @prohibited
222
+ assert_equal 3, @posted.size
6
223
  end
224
+
225
+ # CONFIG4 = %[
226
+ # gfapi_url http://127.0.0.1:5125/api/
227
+ # section metrics
228
+ # name_keys field1,field2,otherfield
229
+ # tag_for service
230
+ # remove_prefix test
231
+ # ]
232
+
233
+ def test_emit_5
234
+ d = create_driver(CONFIG4, 'test.service')
235
+ # recent ruby's Hash saves elements order....
236
+ d.emit({ 'field1' => 50, 'field2' => 20, 'field3' => 10, 'otherfield' => 1 })
237
+ d.run
238
+
239
+ assert_equal 3, @posted.size
240
+ v1st = @posted[0]
241
+ v2nd = @posted[1]
242
+ v3rd = @posted[2]
243
+
244
+ assert_equal 50, v1st[:data][:number]
245
+ assert_equal 'gauge', v1st[:data][:mode]
246
+ assert_nil v1st[:auth]
247
+ assert_equal 'service', v1st[:service]
248
+ assert_equal 'metrics', v1st[:section]
249
+ assert_equal 'field1', v1st[:name]
250
+
251
+ assert_equal 20, v2nd[:data][:number]
252
+ assert_equal 'field2', v2nd[:name]
253
+
254
+ assert_equal 1, v3rd[:data][:number]
255
+ assert_equal 'otherfield', v3rd[:name]
256
+ end
257
+
258
+ # CONFIG_SPACE = %[
259
+ # gfapi_url http://127.0.0.1:5125/api/
260
+ # service service x
261
+ # section metrics y
262
+ # name_keys field z
263
+ # tag_for ignore
264
+ # ]
265
+ def test_with_space_1
266
+ d = create_driver(CONFIG_SPACE, 'test.foo')
267
+ d.emit({ 'field z' => 3 })
268
+ d.run
269
+
270
+ assert_equal 1, @posted.size
271
+ v = @posted[0]
272
+
273
+ assert_equal 3, v[:data][:number]
274
+ assert_equal 'gauge', v[:data][:mode]
275
+ assert_nil v[:auth]
276
+ assert_equal 'service x', v[:service]
277
+ assert_equal 'metrics y', v[:section]
278
+ assert_equal 'field z', v[:name]
279
+ end
280
+
281
+ # setup / teardown for servers
282
+ def setup
283
+ Fluent::Test.setup
284
+ @posted = []
285
+ @prohibited = 0
286
+ @auth = false
287
+ @dummy_server_thread = Thread.new do
288
+ srv = if ENV['VERBOSE']
289
+ WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => GF_TEST_LISTEN_PORT})
290
+ else
291
+ logger = WEBrick::Log.new('/dev/null', WEBrick::BasicLog::DEBUG)
292
+ WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => GF_TEST_LISTEN_PORT, :Logger => logger, :AccessLog => []})
293
+ end
294
+ begin
295
+ srv.mount_proc('/api') { |req,res| # /api/:service/:section/:name
296
+ unless req.request_method == 'POST'
297
+ res.status = 405
298
+ res.body = 'request method mismatch'
299
+ next
300
+ end
301
+ if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
302
+ # ok, authorized
303
+ elsif @auth
304
+ res.status = 403
305
+ @prohibited += 1
306
+ next
307
+ else
308
+ # ok, authorization not required
309
+ end
310
+
311
+ req.path =~ /^\/api\/([^\/]*)\/([^\/]*)\/(.*)$/
312
+ service = $1
313
+ section = $2
314
+ graph_name = $3
315
+ post_param = Hash[*(req.body.split('&').map{|kv|kv.split('=')}.flatten)]
316
+
317
+ @posted.push({
318
+ :service => service,
319
+ :section => section,
320
+ :name => graph_name,
321
+ :auth => nil,
322
+ :data => { :number => post_param['number'].to_i, :mode => post_param['mode'] },
323
+ })
324
+
325
+ res.status = 200
326
+ }
327
+ srv.mount_proc('/') { |req,res|
328
+ res.status = 200
329
+ res.body = 'running'
330
+ }
331
+ srv.start
332
+ ensure
333
+ srv.shutdown
334
+ end
335
+ end
336
+
337
+ # to wait completion of dummy server.start()
338
+ require 'thread'
339
+ cv = ConditionVariable.new
340
+ watcher = Thread.new {
341
+ connected = false
342
+ while not connected
343
+ begin
344
+ get_content('localhost', GF_TEST_LISTEN_PORT, '/')
345
+ connected = true
346
+ rescue Errno::ECONNREFUSED
347
+ sleep 0.1
348
+ rescue StandardError => e
349
+ p e
350
+ sleep 0.1
351
+ end
352
+ end
353
+ cv.signal
354
+ }
355
+ mutex = Mutex.new
356
+ mutex.synchronize {
357
+ cv.wait(mutex)
358
+ }
359
+ end
360
+
361
+ def test_dummy_server
362
+ d = create_driver
363
+ d.instance.gfapi_url =~ /^http:\/\/([.:a-z0-9]+)\//
364
+ server = $1
365
+ host = server.split(':')[0]
366
+ port = server.split(':')[1].to_i
367
+ client = Net::HTTP.start(host, port)
368
+
369
+ assert_equal '200', client.request_get('/').code
370
+ assert_equal '200', client.request_post('/api/service/metrics/hoge', 'number=1&mode=gauge').code
371
+
372
+ assert_equal 1, @posted.size
373
+
374
+ assert_equal 1, @posted[0][:data][:number]
375
+ assert_equal 'gauge', @posted[0][:data][:mode]
376
+ assert_nil @posted[0][:auth]
377
+ assert_equal 'service', @posted[0][:service]
378
+ assert_equal 'metrics', @posted[0][:section]
379
+ assert_equal 'hoge', @posted[0][:name]
380
+
381
+ assert_equal '200', client.request_post(URI.escape('/api/service x/metrics/hoge'), 'number=1&mode=gauge').code
382
+
383
+ assert_equal 2, @posted.size
384
+
385
+ assert_equal 1, @posted[1][:data][:number]
386
+ assert_equal 'gauge', @posted[1][:data][:mode]
387
+ assert_nil @posted[1][:auth]
388
+ assert_equal 'service x', @posted[1][:service]
389
+ assert_equal 'metrics', @posted[1][:section]
390
+ assert_equal 'hoge', @posted[1][:name]
391
+
392
+ @auth = true
393
+
394
+ assert_equal '403', client.request_post('/api/service/metrics/pos', 'number=30&mode=gauge').code
395
+
396
+ req_with_auth = lambda do |number, mode, user, pass|
397
+ url = URI.parse("http://#{host}:#{port}/api/service/metrics/pos")
398
+ req = Net::HTTP::Post.new(url.path)
399
+ req.basic_auth user, pass
400
+ req.set_form_data({'number'=>number, 'mode'=>mode})
401
+ req
402
+ end
403
+
404
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
405
+
406
+ assert_equal '403', client.request(req_with_auth.call(500, 'count', 'alice', 'wrong password!')).code
407
+
408
+ assert_equal 2, @posted.size
409
+
410
+ assert_equal '200', client.request(req_with_auth.call(500, 'count', 'alice', 'secret!')).code
411
+
412
+ assert_equal 3, @posted.size
413
+
414
+ end
415
+
416
+ def teardown
417
+ @dummy_server_thread.kill
418
+ @dummy_server_thread.join
419
+ end
420
+
7
421
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-growthforecast
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,33 +9,17 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-05 00:00:00.000000000 Z
12
+ date: 2013-03-19 00:00:00.000000000 Z
13
13
  dependencies:
14
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: :runtime
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: rdoc
15
+ name: bundler
32
16
  requirement: !ruby/object:Gem::Requirement
33
17
  none: false
34
18
  requirements:
35
19
  - - ! '>='
36
20
  - !ruby/object:Gem::Version
37
21
  version: '0'
38
- type: :runtime
22
+ type: :development
39
23
  prerelease: false
40
24
  version_requirements: !ruby/object:Gem::Requirement
41
25
  none: false
@@ -44,7 +28,7 @@ dependencies:
44
28
  - !ruby/object:Gem::Version
45
29
  version: '0'
46
30
  - !ruby/object:Gem::Dependency
47
- name: shoulda
31
+ name: fluentd
48
32
  requirement: !ruby/object:Gem::Requirement
49
33
  none: false
50
34
  requirements:
@@ -60,39 +44,7 @@ dependencies:
60
44
  - !ruby/object:Gem::Version
61
45
  version: '0'
62
46
  - !ruby/object:Gem::Dependency
63
- name: bundler
64
- requirement: !ruby/object:Gem::Requirement
65
- none: false
66
- requirements:
67
- - - ~>
68
- - !ruby/object:Gem::Version
69
- version: 1.0.0
70
- type: :development
71
- prerelease: false
72
- version_requirements: !ruby/object:Gem::Requirement
73
- none: false
74
- requirements:
75
- - - ~>
76
- - !ruby/object:Gem::Version
77
- version: 1.0.0
78
- - !ruby/object:Gem::Dependency
79
- name: jeweler
80
- requirement: !ruby/object:Gem::Requirement
81
- none: false
82
- requirements:
83
- - - ~>
84
- - !ruby/object:Gem::Version
85
- version: 1.6.4
86
- type: :development
87
- prerelease: false
88
- version_requirements: !ruby/object:Gem::Requirement
89
- none: false
90
- requirements:
91
- - - ~>
92
- - !ruby/object:Gem::Version
93
- version: 1.6.4
94
- - !ruby/object:Gem::Dependency
95
- name: simplecov
47
+ name: fluent-mixin-config-placeholders
96
48
  requirement: !ruby/object:Gem::Requirement
97
49
  none: false
98
50
  requirements:
@@ -109,73 +61,56 @@ dependencies:
109
61
  version: '0'
110
62
  - !ruby/object:Gem::Dependency
111
63
  name: fluentd
112
- requirement: !ruby/object:Gem::Requirement
113
- none: false
114
- requirements:
115
- - - ~>
116
- - !ruby/object:Gem::Version
117
- version: 0.10.8
118
- type: :runtime
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- none: false
122
- requirements:
123
- - - ~>
124
- - !ruby/object:Gem::Version
125
- version: 0.10.8
126
- - !ruby/object:Gem::Dependency
127
- name: rake
128
64
  requirement: !ruby/object:Gem::Requirement
129
65
  none: false
130
66
  requirements:
131
67
  - - ! '>='
132
68
  - !ruby/object:Gem::Version
133
- version: 0.9.2
134
- type: :development
69
+ version: '0'
70
+ type: :runtime
135
71
  prerelease: false
136
72
  version_requirements: !ruby/object:Gem::Requirement
137
73
  none: false
138
74
  requirements:
139
75
  - - ! '>='
140
76
  - !ruby/object:Gem::Version
141
- version: 0.9.2
77
+ version: '0'
142
78
  - !ruby/object:Gem::Dependency
143
- name: simplecov
79
+ name: fluent-mixin-config-placeholders
144
80
  requirement: !ruby/object:Gem::Requirement
145
81
  none: false
146
82
  requirements:
147
83
  - - ! '>='
148
84
  - !ruby/object:Gem::Version
149
- version: 0.5.4
150
- type: :development
85
+ version: '0'
86
+ type: :runtime
151
87
  prerelease: false
152
88
  version_requirements: !ruby/object:Gem::Requirement
153
89
  none: false
154
90
  requirements:
155
91
  - - ! '>='
156
92
  - !ruby/object:Gem::Version
157
- version: 0.5.4
158
- description: Plugin to post numbers to GrowthForecast (by kazeburo)
159
- email: tagomoris@gmail.com
93
+ version: '0'
94
+ description: For GrowthForecast, see http://kazeburo.github.com/GrowthForecast/
95
+ email:
96
+ - tagomoris@gmail.com
160
97
  executables: []
161
98
  extensions: []
162
- extra_rdoc_files:
163
- - LICENSE.txt
164
- - README.rdoc
99
+ extra_rdoc_files: []
165
100
  files:
166
101
  - .document
167
102
  - .gitignore
168
103
  - AUTHORS
169
104
  - Gemfile
170
105
  - LICENSE.txt
171
- - README.rdoc
106
+ - README.md
172
107
  - Rakefile
173
108
  - VERSION
174
109
  - fluent-plugin-growthforecast.gemspec
175
110
  - lib/fluent/plugin/out_growthforecast.rb
176
111
  - test/helper.rb
177
112
  - test/plugin/test_out_growthforecast.rb
178
- homepage: http://github.com/tagomoris/fluent-plugin-growthforecast
113
+ homepage: https://github.com/tagomoris/fluent-plugin-growthforecast
179
114
  licenses: []
180
115
  post_install_message:
181
116
  rdoc_options: []
@@ -187,9 +122,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
187
122
  - - ! '>='
188
123
  - !ruby/object:Gem::Version
189
124
  version: '0'
190
- segments:
191
- - 0
192
- hash: -1202506327029138940
193
125
  required_rubygems_version: !ruby/object:Gem::Requirement
194
126
  none: false
195
127
  requirements:
@@ -201,7 +133,7 @@ rubyforge_project:
201
133
  rubygems_version: 1.8.21
202
134
  signing_key:
203
135
  specification_version: 3
204
- summary: Plugin to post numbers to GrowthForecast (by kazeburo)
136
+ summary: Fluentd output plugin to post numbers to GrowthForecast (by kazeburo)
205
137
  test_files:
206
138
  - test/helper.rb
207
139
  - test/plugin/test_out_growthforecast.rb
@@ -1,59 +0,0 @@
1
- = fluent-plugin-growthforecast
2
-
3
- == Component
4
-
5
- === GrowthForecastOutput
6
-
7
- Plugin to output numbers(metrics) to 'GrowthForecast', metrics drawing tool over HTTP.
8
-
9
- About GrowthForecast, see:
10
- - Github: https://github.com/kazeburo/growthforecast
11
- - Japanese blog post by @kazeburo: http://blog.nomadscafe.jp/2011/12/growthforecast.html
12
-
13
- GrowthForecast is very simple but useful tool to draw graphs what we want, with GrowthForecastOutput and Fluentd.
14
-
15
- == Configuration
16
-
17
- === GrowthForecastOutput
18
-
19
- For messages such as:
20
- tag:metrics {"field1":300, "field2":20, "field3diff":-30}
21
-
22
- Configuration example for graphs in growthforecast with POST api url 'http://growthforecast.local/api/service1/metrics1/metrics_FIELDNAME'.
23
-
24
- <match metrics>
25
- type growthforecast
26
- gfapi_url http://growthforecast.local/api/
27
- service service1
28
- section metrics1
29
- name_keys field1,field2,field3diff
30
- </match>
31
-
32
- With this configuration, out_growthforecast posts urls below.
33
- - http://growthforecast.local/api/service1/metrics1/metrics_field1
34
- - http://growthforecast.local/api/service1/metrics1/metrics_field2
35
- - http://growthforecast.local/api/service1/metrics1/metrics_field3diff
36
-
37
-
38
- If you want to use tags for 'section' in GrowthForecast, use 'tag_for' options and remove_prefix (and not to set 'section').
39
-
40
- <match metrics.**>
41
- type growthforecast
42
- gfapi_url http://growthforecast.local/api/
43
- service service1
44
- name_keys field1,field2,field3diff
45
- tag_for section # or 'name_prefix'(default) or 'ignore'
46
- remove_prefix metrics
47
- </match>
48
-
49
- 'mode' option available with 'gauge'(default), 'count', 'modified', just same as 'mode' of GrowthForecast POST parameter.
50
-
51
- == TODO
52
-
53
- - consider what to do next
54
- - patches welcome!
55
-
56
- == Copyright
57
-
58
- Copyright:: Copyright (c) 2012- TAGOMORI Satoshi (tagomoris)
59
- License:: Apache License, Version 2.0