fluent-plugin-growthforecast 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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