├── .github
└── workflows
│ └── ci.yml
├── .gitignore
├── Gemfile
├── LICENSE.txt
├── README.md
├── Rakefile
├── fluent-plugin-mackerel.gemspec
├── lib
└── fluent
│ └── plugin
│ ├── out_mackerel.rb
│ └── out_mackerel_hostid_tag.rb
└── test
├── helper.rb
└── plugin
├── hostid
├── test_out_mackerel.rb
└── test_out_mackerel_hostid_tag.rb
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | push:
4 | pull_request:
5 | jobs:
6 | test:
7 | strategy:
8 | fail-fast: false
9 | matrix:
10 | os: [ubuntu-latest]
11 | ruby: ['2.7', '3.0', '3.1']
12 | runs-on: ${{ matrix.os }}
13 | steps:
14 | - uses: ruby/setup-ruby@v1
15 | with:
16 | ruby-version: ${{ matrix.ruby }}
17 | bundler-cache: true
18 | - uses: actions/checkout@v3
19 | - run: gem install bundler:1.17.3
20 | - run: bundle _1.17.3_ install
21 | - run: VERBOSE=1 bundle exec rake test
22 |
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
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 | .idea
19 | vendor/bundle
20 | test_out_mackerel_send.rb
21 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | # Specify your gem's dependencies in fluent-plugin-mackerel.gemspec
4 | gemspec
5 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2014- SOMEDA Takashi
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # fluent-plugin-mackerel [](https://travis-ci.org/mackerelio/fluent-plugin-mackerel)
2 |
3 | ## Overview
4 |
5 | This is the plugin for sending metrics to [mackerel.io](http://mackerel.io/) using [Fluentd](http://fluentd.org).
6 |
7 | This plugin includes two components, MackerelOutput and MackerelHostidTagOutput. The former is used to send metrics to Mackerel and the latter is used to append the Mackerel hostid for tagging or recording.
8 |
9 | ## Installation
10 |
11 | Install with either the gem or fluent-gem command as shown:
12 |
13 | ```
14 | # for fluentd
15 | $ gem install fluent-plugin-mackerel
16 |
17 | # for td-agent
18 | $ sudo /usr/lib64/fluent/ruby/bin/fluent-gem install fluent-plugin-mackerel
19 | ```
20 |
21 | ## Configuration
22 |
23 | ### MackerelOutput
24 |
25 | This plugin uses mackerel.io's [APIv0](https://mackerel.io/api-docs/).
26 | ```
27 |
28 | @type mackerel
29 | api_key 123456
30 | hostid xyz
31 | metrics_name http_status.${out_key}
32 | use_zero_for_empty
33 | out_keys 2xx_count,3xx_count,4xx_count,5xx_count
34 |
35 | ```
36 |
37 | Metric data that has been sent will look like this:
38 | ```
39 | {
40 | "hostId": "xyz",
41 | "name": "custom.http_status.2xx_count",
42 | "time": 1399997498,
43 | "value": 100.0
44 | }
45 | ```
46 | As shown above, `${out_key}` will be replaced with out_key like "2xx_count" when sending metrics.
47 |
48 | In the case an outkey does not have any value, the value will be set to `0` with `use_zero_for_empty`, the default of which is true.
49 | For example, if you have set `out_keys 2xx_count,3xx_count,4xx_count,5xx_count`, but only get `2xx_count`, `3xx_count` and `4xx_count`, then `5xx_count` will be set to `0` with `use_zero_for_empty`.
50 |
51 | `out_key_pattern` can be used instead of `out_keys`. Input records whose keys match the pattern set to `out_key_pattern` will be sent. Either `out_keys` or `out_key_pattern` is required.
52 |
53 | ```
54 |
55 | @type mackerel
56 | api_key 123456
57 | service yourservice
58 | metrics_name http_status.${out_key}
59 | out_key_pattern [2-5]xx_count
60 | ```
61 |
62 | `${[n]}` can be used as a tag for `metrics_name` where `n` represents any decimal number including negative values.
63 |
64 | ```
65 |
66 | @type mackerel
67 | api_key 123456
68 | hostid xyz
69 | metrics_name ${[1]}.${out_key}
70 | out_keys 2xx_count,3xx_count,4xx_count,5xx_count
71 |
72 | ```
73 |
74 | This indicates the value of the `n` th index of the array. By splitting the tag with a `.` (dot) the following output can be got when the tag is `mackerel.http_status`.
75 | ```
76 | {
77 | "hostId": "xyz",
78 | "name": "custom.http_status.2xx_count",
79 | "time": 1399997498,
80 | "value": 100.0
81 | }
82 | ```
83 | "custom" will be automatically appended to the `name` attribute before sending metrics to mackerel.
84 |
85 | You can also send [service metrics](http://help.mackerel.io/entry/spec/api/v0#service-metric-value-post) as shown.
86 | ```
87 |
88 | @type mackerel
89 | api_key 123456
90 | service yourservice
91 | metrics_name http_status.${out_key}
92 | out_keys 2xx_count,3xx_count,4xx_count,5xx_count
93 |
94 | ```
95 |
96 | When sending service metrics, the prefix "custom" can be removed with `remove_prefix` as follows.
97 | This option is not availabe when sending host metrics.
98 |
99 | ```
100 |
101 | @type mackerel
102 | api_key 123456
103 | service yourservice
104 | remove_prefix
105 | metrics_name http_status.${out_key}
106 | out_keys 2xx_count,3xx_count,4xx_count,5xx_count
107 |
108 | ```
109 |
110 | `flush_interval` may not be set to less than 60 seconds so as not to send API requests more than once per minute.
111 |
112 | This plugin overwrites the default values of `buffer_queue_limit` and `buffer_chunk_limit` as shown.
113 |
114 | * buffer_queue_limit to 4096
115 | * buffer_chunk_limit to 100K
116 |
117 | Unless there is some particular reason to change these values, we suggest leaving them as is.
118 |
119 | From version 0.0.4 and on, metrics_prefix has been removed and metrics_name should be used instead.
120 |
121 | ### MackerelHostidTagOutput
122 |
123 | Let's say you want to add the hostid to the record with a specific key name...
124 | ```
125 |
126 | @type mackerel_hostid_tag
127 | add_to record
128 | key_name mackerel_hostid
129 |
130 | ```
131 | As shown above, the key_name field is required. For example if the host_id is "xyz" and input is `["test", 1407650400, {"val1"=>1, "val2"=>2, "val3"=>3, "val4"=>4}]`, you'll get `["test", 1407650400, {"val1"=>1, "val2"=>2, "val3"=>3, "val4"=>4, "mackerel_hostid"=>"xyz"}]`
132 |
133 | To append the hostid to the tag, you can simply configure "add_to" as "tag" like this.
134 | ```
135 |
136 | @type mackerel_hostid_tag
137 | add_to tag
138 |
139 | ```
140 | If the input is `["test", 1407650400, {"val1"=>1, "val2"=>2, "val3"=>3, "val4"=>4}]`, then the output will be `["test.xyz", 1407650400, {"val1"=>1, "val2"=>2, "val3"=>3, "val4"=>4}]`
141 |
142 | Both `add_prefix` and `remove_prefix` options are availale to control rebuilding tags.
143 |
144 | ## TODO
145 |
146 | Pull requests are very welcome!!
147 |
148 | ## For developers
149 |
150 | You'll need to run the command below when starting development.
151 | ```
152 | $ bundle install --path vendor/bundle
153 | ```
154 |
155 | To run tests...
156 | ```
157 | $ VERBOSE=1 bundle exec rake test
158 | ```
159 |
160 | If you want to run a certain file, run rake like this
161 | ```
162 | $ VERBOSE=1 bundle exec rake test TEST=test/plugin/test_out_mackerel.rb
163 | ```
164 |
165 | Additionally, you can run a specific method like this.
166 | ```
167 | $ VERBOSE=1 bundle exec rake test TEST=test/plugin/test_out_mackerel.rb TESTOPTS="--name=test_configure"
168 | ```
169 |
170 | When releasing, call rake release as shown.
171 | ```
172 | $ bundle exec rake release
173 | ```
174 |
175 | For debugging purposes, you can change the Mackerel endpoint with an `origin` parameter like this.
176 | ```
177 |
178 | @type mackerel
179 | api_key 123456
180 | hostid xyz
181 | metrics_name http_status.${out_key}
182 | out_keys 2xx_count,3xx_count,4xx_count,5xx_count
183 | origin https://example.com
184 |
185 | ```
186 |
187 | ## References
188 |
189 | * [Posting Service Metrics with fluentd](http://help.mackerel.io/entry/advanced/fluentd)
190 | * [How to use fluent-plugin-mackerel (Japanese)](http://qiita.com/tksmd/items/1212331a5a18afe520df)
191 |
192 | ## Authors
193 |
194 | - Takashi Someda ([@tksmd](http://twitter.com/tksmd/))
195 | - Mackerel Development Team
196 |
197 | ## Copyright
198 |
199 | * Copyright (c) 2014- Hatena Co., Ltd. and Authors
200 | * Apache License, Version 2.0
201 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require "bundler/gem_tasks"
2 | require "rake/testtask"
3 |
4 | Rake::TestTask.new(:test) do |test|
5 | test.libs << 'lib' << 'test'
6 | test.pattern = 'test/**/test_*.rb'
7 | test.verbose = true
8 | end
9 |
10 | task :default => :test
11 |
--------------------------------------------------------------------------------
/fluent-plugin-mackerel.gemspec:
--------------------------------------------------------------------------------
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-mackerel"
7 | spec.version = "1.1.0"
8 | spec.authors = ["tksmd","hatz48","stanaka","Songmu"]
9 | spec.email = ["developers@mackerel.io"]
10 | spec.description = %q{fluent plugin to send metrics to mackerel.io}
11 | spec.summary = spec.description
12 | spec.homepage = "https://github.com/mackerelio/fluent-plugin-mackerel"
13 | spec.license = "Apache-2.0"
14 |
15 | spec.files = `git ls-files`.split($/)
16 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18 | spec.require_paths = ["lib"]
19 |
20 | spec.add_dependency "mackerel-client", ">= 0.3.0"
21 |
22 | spec.add_development_dependency "bundler", "~> 1.3"
23 | spec.add_development_dependency "rake"
24 | spec.add_development_dependency "test-unit"
25 | spec.add_development_dependency "test-unit-rr"
26 | spec.add_runtime_dependency "fluentd", [">= 0.14.8", "< 2"]
27 |
28 | spec.required_ruby_version = '>= 1.9.3'
29 | end
30 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/out_mackerel.rb:
--------------------------------------------------------------------------------
1 | require 'mackerel/client'
2 |
3 | module Fluent::Plugin
4 | class MackerelOutput < Output
5 | Fluent::Plugin.register_output('mackerel', self)
6 |
7 | helpers :compat_parameters
8 |
9 | DEFAULT_FLUSH_INTERVAL = 60
10 |
11 | config_param :api_key, :string, :secret => true
12 | config_param :hostid, :string, :default => nil
13 | config_param :hostid_path, :string, :default => nil
14 | config_param :service, :string, :default => nil
15 | config_param :remove_prefix, :bool, :default => false
16 | config_param :metrics_name, :string, :default => nil
17 | config_param :out_keys, :string, :default => nil
18 | config_param :out_key_pattern, :string, :default => nil
19 | config_param :origin, :string, :default => nil
20 | config_param :use_zero_for_empty, :bool, :default => true
21 |
22 | MAX_BUFFER_CHUNK_LIMIT = 100 * 1024
23 | config_set_default :buffer_chunk_limit, MAX_BUFFER_CHUNK_LIMIT
24 | config_set_default :buffer_queue_limit, 4096
25 | config_section :buffer do
26 | config_set_default :@type, 'memory'
27 | config_set_default :flush_interval, DEFAULT_FLUSH_INTERVAL
28 | end
29 |
30 | attr_reader :mackerel
31 |
32 | # Define `log` method for v0.10.42 or earlier
33 | unless method_defined?(:log)
34 | define_method("log") { $log }
35 | end
36 |
37 | def initialize
38 | super
39 | end
40 |
41 | def configure(conf)
42 | compat_parameters_convert(conf, :buffer)
43 | super
44 |
45 | @mackerel = Mackerel::Client.new(:mackerel_api_key => conf['api_key'], :mackerel_origin => conf['origin'])
46 |
47 | if @out_keys
48 | @out_keys = @out_keys.split(',')
49 | end
50 | if @out_key_pattern
51 | @out_key_pattern = Regexp.new(@out_key_pattern)
52 | end
53 | if @out_keys.nil? and @out_key_pattern.nil?
54 | raise Fluent::ConfigError, "Either 'out_keys' or 'out_key_pattern' must be specifed."
55 | end
56 |
57 | if @buffer_config.flush_interval and @buffer_config.flush_interval < 60
58 | raise Fluent::ConfigError, "flush_interval less than 60s is not allowed."
59 | end
60 |
61 | unless @hostid_path.nil?
62 | @hostid = File.open(@hostid_path).read
63 | end
64 |
65 | if @hostid.nil? and @service.nil?
66 | raise Fluent::ConfigError, "Either 'hostid' or 'hostid_path' or 'service' must be specifed."
67 | end
68 |
69 | if @hostid and @service
70 | raise Fluent::ConfigError, "Niether 'hostid' and 'service' cannot be specifed."
71 | end
72 |
73 | if @remove_prefix and @service.nil?
74 | raise Fluent::ConfigError, "'remove_prefix' must be used with 'service'."
75 | end
76 |
77 | unless @hostid.nil?
78 | if matched = @hostid.match(/^\${tag_parts\[(\d+)\]}$/)
79 | hostid_idx = matched[1].to_i
80 | @hostid_processor = Proc.new{ |args| args[:tokens][hostid_idx] }
81 | else
82 | @hostid_processor = Proc.new{ @hostid }
83 | end
84 | end
85 |
86 | if @metrics_name
87 | @name_processor = @metrics_name.split('.').map{ |token|
88 | Proc.new{ |args|
89 | token.gsub(/\${(out_key|\[(-?\d+)\])}/) {
90 | if $1 == 'out_key'
91 | args[:out_key]
92 | else
93 | args[:tokens][$1[1..-1].to_i]
94 | end
95 | }
96 | }
97 | }
98 | else
99 | @name_processor = nil
100 | end
101 | end
102 |
103 | def start
104 | super
105 | end
106 |
107 | def shutdown
108 | super
109 | end
110 |
111 | def formatted_to_msgpack_binary
112 | true
113 | end
114 |
115 | def format(tag, time, record)
116 | [tag, time, record].to_msgpack
117 | end
118 |
119 | def generate_metric(key, tokens, time, value)
120 | name = @name_processor.nil? ? key :
121 | @name_processor.map{ |p| p.call(:out_key => key, :tokens => tokens) }.join('.')
122 |
123 | metric = {
124 | 'value' => value,
125 | 'time' => time,
126 | 'name' => @remove_prefix ? name : "%s.%s" % ['custom', name]
127 | }
128 | metric['hostId'] = @hostid_processor.call(:tokens => tokens) if @hostid
129 | return metric
130 | end
131 |
132 | def write(chunk)
133 | metrics = []
134 | processed = {}
135 | tags = {}
136 | time_latest = 0
137 | chunk.msgpack_each do |(tag,time,record)|
138 | tags[tag] = true
139 | tokens = tag.split('.')
140 |
141 | if @out_keys
142 | out_keys = @out_keys.select{|key| record.has_key?(key)}
143 | else # @out_key_pattern
144 | out_keys = record.keys.select{|key| @out_key_pattern.match(key)}
145 | end
146 |
147 | out_keys.map do |key|
148 | metrics << generate_metric(key, tokens, time, record[key].to_f)
149 | time_latest = time if time_latest == 0 || time_latest < time
150 | processed[tag + "." + key] = true
151 | end
152 | end
153 |
154 | if @out_keys && @use_zero_for_empty
155 | tags.each_key do |tag|
156 | tokens = tag.split('.')
157 | @out_keys.each do |key|
158 | unless processed[tag + "." + key]
159 | metrics << generate_metric(key, tokens, time_latest, 0.0)
160 | end
161 | end
162 | end
163 | end
164 |
165 | send_metrics(metrics) unless metrics.empty?
166 | metrics.clear
167 | end
168 |
169 | def send_metrics(metrics)
170 | log.debug("out_mackerel: #{metrics}")
171 | begin
172 | if @hostid
173 | @mackerel.post_metrics(metrics)
174 | else
175 | @mackerel.post_service_metrics(@service, metrics)
176 | end
177 | rescue => e
178 | log.error("out_mackerel:", :error_class => e.class, :error => e.message)
179 | raise e
180 | end
181 | end
182 |
183 | end
184 |
185 | end
186 |
--------------------------------------------------------------------------------
/lib/fluent/plugin/out_mackerel_hostid_tag.rb:
--------------------------------------------------------------------------------
1 | module Fluent
2 |
3 | class MackerelHostidTagOutput < Fluent::Output
4 | Fluent::Plugin.register_output('mackerel_hostid_tag', self)
5 |
6 | config_param :hostid_path, :string, :default => '/var/lib/mackerel-agent/id'
7 | config_param :add_to, :string
8 | config_param :key_name, :default => nil
9 | config_param :add_prefix, :string, :default => nil
10 | config_param :remove_prefix, :string, :default => nil
11 |
12 | # Define `log` method for v0.10.42 or earlier
13 | unless method_defined?(:log)
14 | define_method("log") { $log }
15 | end
16 |
17 | # Define `router` method to support v0.10.57 or earlier
18 | unless method_defined?(:router)
19 | define_method("router") { Fluent::Engine }
20 | end
21 |
22 | def initialize
23 | super
24 | end
25 |
26 | def configure(conf)
27 | super
28 | @hostid = File.open(@hostid_path).read
29 | if @add_to == 'record' and @key_name.nil?
30 | raise Fluent::ConfigError, "'key_name' must be specified"
31 | end
32 | if @remove_prefix
33 | @removed_prefix_string = @remove_prefix + '.'
34 | @removed_length = @removed_prefix_string.length
35 | end
36 | @added_prefix_string = @add_prefix.nil? ? nil : @add_prefix + '.'
37 | end
38 |
39 | def emit(tag, es, chain)
40 | if @remove_prefix and
41 | ( (tag.start_with?(@removed_prefix_string) and tag.length > @removed_length) or tag == @remove_prefix)
42 | tag = tag[@removed_length..-1]
43 | end
44 | if tag.length > 0
45 | tag = @added_prefix_string + tag if @added_prefix_string
46 | else
47 | tag = @add_prefix
48 | end
49 | if @add_to == 'tag'
50 | tag = [tag, @hostid].join('.')
51 | end
52 |
53 | es.each do |time, record|
54 | if @add_to == 'record'
55 | record[@key_name] = @hostid
56 | end
57 | router.emit(tag, time, record)
58 | end
59 |
60 | chain.next
61 | end
62 |
63 | end
64 |
65 | end
66 |
--------------------------------------------------------------------------------
/test/helper.rb:
--------------------------------------------------------------------------------
1 | require 'rubygems'
2 | require 'bundler'
3 | begin
4 | Bundler.setup(:default, :development)
5 | rescue Bundler::BundlerError => e
6 | $stderr.puts e.message
7 | $stderr.puts "Run `bundle install` to install missing gems"
8 | exit e.status_code
9 | end
10 | require 'test/unit'
11 | require 'test/unit/rr'
12 |
13 | $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
14 | $LOAD_PATH.unshift(File.dirname(__FILE__))
15 | require 'fluent/test'
16 | unless ENV.has_key?('VERBOSE')
17 | nulllogger = Object.new
18 | nulllogger.instance_eval {|obj|
19 | def method_missing(method, *args)
20 | # pass
21 | end
22 | }
23 | $log = nulllogger
24 | end
25 |
26 | require 'test/unit/rr'
27 | require 'fluent/test/driver/output'
28 | require 'fluent/plugin/out_mackerel'
29 | require 'fluent/plugin/out_mackerel_hostid_tag'
30 |
31 | class Test::Unit::TestCase
32 | end
33 |
--------------------------------------------------------------------------------
/test/plugin/hostid:
--------------------------------------------------------------------------------
1 | xyz
--------------------------------------------------------------------------------
/test/plugin/test_out_mackerel.rb:
--------------------------------------------------------------------------------
1 | require 'helper'
2 |
3 | class MackerelOutputTest < Test::Unit::TestCase
4 |
5 | def setup
6 | Fluent::Test.setup
7 | end
8 |
9 | CONFIG = %[
10 | type mackerel
11 | api_key 123456
12 | hostid xyz
13 | metrics_name service.${out_key}
14 | out_keys val1,val2,val3
15 | ]
16 |
17 | CONFIG_NOHOST = %[
18 | type mackerel
19 | api_key 123456
20 | metrics_name service.${out_key}
21 | out_keys val1,val2,val3
22 | ]
23 |
24 | CONFIG_BLANK_METRICS = %[
25 | type mackerel
26 | api_key 123456
27 | metrics_prefix
28 | out_keys val1,val2,val3
29 | ]
30 |
31 | CONFIG_SMALL_FLUSH_INTERVAL = %[
32 | type mackerel
33 | api_key 123456
34 | hostid xyz
35 | metrics_name service.${out_key}
36 | out_keys val1,val2,val3
37 | flush_interval 1s
38 | ]
39 |
40 | CONFIG_ORIGIN = %[
41 | type mackerel
42 | api_key 123456
43 | hostid xyz
44 | metrics_name service.${out_key}
45 | out_keys val1,val2,val3
46 | origin example.domain
47 | ]
48 |
49 | CONFIG_NO_OUT_KEYS = %[
50 | type mackerel
51 | api_key 123456
52 | hostid xyz
53 | metrics_name service.${out_key}
54 | ]
55 |
56 | CONFIG_OUT_KEY_PATTERN = %[
57 | type mackerel
58 | api_key 123456
59 | hostid xyz
60 | metrics_name service.${out_key}
61 | out_key_pattern ^val[0-9]$
62 | ]
63 |
64 | CONFIG_FOR_ISSUE_4 = %[
65 | type mackerel
66 | api_key 123456
67 | hostid xyz
68 | metrics_name a-${[1]}-b.${out_key}
69 | out_keys val1,val2
70 | ]
71 |
72 | CONFIG_BUFFER_LIMIT_DEFAULT = %[
73 | type mackerel
74 | service xyz
75 | api_key 123456
76 | out_keys val1,val2,val3
77 | ]
78 |
79 | CONFIG_BUFFER_LIMIT_IGNORE = %[
80 | type mackerel
81 | service xyz
82 | api_key 123456
83 | out_keys val1,val2,val3
84 | buffer_chunk_limit 1k
85 | ]
86 |
87 | CONFIG_SERVICE = %[
88 | type mackerel
89 | api_key 123456
90 | service xyz
91 | out_keys val1,val2
92 | ]
93 |
94 | CONFIG_SERVICE_REMOVE_PREFIX = %[
95 | type mackerel
96 | api_key 123456
97 | service xyz
98 | remove_prefix
99 | out_keys val1,val2
100 | ]
101 |
102 | CONFIG_INVALID_REMOVE_PREFIX = %[
103 | type mackerel
104 | api_key 123456
105 | remove_prefix
106 | out_keys val1,val2,val3
107 | ]
108 |
109 | CONFIG_SERVICE_USE_ZERO = %[
110 | type mackerel
111 | api_key 123456
112 | service xyz
113 | use_zero_for_empty
114 | out_keys val1,val2,val3
115 | ]
116 |
117 | def create_driver(conf = CONFIG)
118 | Fluent::Test::Driver::Output.new(Fluent::Plugin::MackerelOutput).configure(conf)
119 | end
120 |
121 | def test_configure
122 |
123 | assert_raise(Fluent::ConfigError) {
124 | create_driver('')
125 | }
126 |
127 | assert_raise(Fluent::ConfigError) {
128 | create_driver(CONFIG_NOHOST)
129 | }
130 |
131 | assert_raise(Fluent::ConfigError) {
132 | create_driver(CONFIG_BLANK_METRICS)
133 | }
134 |
135 | assert_raise(Fluent::ConfigError) {
136 | create_driver(CONFIG_NO_OUT_KEYS)
137 | }
138 |
139 | assert_raise(Fluent::ConfigError) {
140 | create_driver(CONFIG_INVALID_REMOVE_PREFIX)
141 | }
142 |
143 | assert_raise(Fluent::ConfigError) {
144 | create_driver(CONFIG_SMALL_FLUSH_INTERVAL)
145 | }
146 |
147 | d = create_driver(CONFIG_ORIGIN)
148 | assert_equal d.instance.instance_variable_get(:@origin), 'example.domain'
149 |
150 | d = create_driver()
151 | assert_equal d.instance.instance_variable_get(:@api_key), '123456'
152 | assert_equal d.instance.instance_variable_get(:@hostid), 'xyz'
153 | assert_equal d.instance.instance_variable_get(:@metrics_name), 'service.${out_key}'
154 | assert_equal d.instance.instance_variable_get(:@out_keys), ['val1','val2','val3']
155 | buffer = d.instance.instance_variable_get(:@buffer_config)
156 | assert_equal buffer.flush_interval, 60
157 |
158 | d = create_driver(CONFIG_OUT_KEY_PATTERN)
159 | assert_match d.instance.instance_variable_get(:@out_key_pattern), "val1"
160 | assert_no_match d.instance.instance_variable_get(:@out_key_pattern), "foo"
161 |
162 | d = create_driver(CONFIG_BUFFER_LIMIT_DEFAULT)
163 | assert_equal d.instance.instance_variable_get(:@buffer_chunk_limit), Fluent::Plugin::MackerelOutput::MAX_BUFFER_CHUNK_LIMIT
164 | assert_equal d.instance.instance_variable_get(:@buffer_queue_limit), 4096
165 |
166 | d = create_driver(CONFIG_BUFFER_LIMIT_IGNORE)
167 | assert_equal d.instance.instance_variable_get(:@buffer_chunk_limit), Fluent::Plugin::MackerelOutput::MAX_BUFFER_CHUNK_LIMIT
168 |
169 | end
170 |
171 | def test_write
172 | d = create_driver()
173 | mock(d.instance.mackerel).post_metrics([
174 | {"hostId"=>"xyz", "value"=>1.0, "time"=>1399997498, "name"=>"custom.service.val1"},
175 | {"hostId"=>"xyz", "value"=>2.0, "time"=>1399997498, "name"=>"custom.service.val2"},
176 | {"hostId"=>"xyz", "value"=>3.0, "time"=>1399997498, "name"=>"custom.service.val3"},
177 | {"hostId"=>"xyz", "value"=>5.0, "time"=>1399997498, "name"=>"custom.service.val1"},
178 | {"hostId"=>"xyz", "value"=>6.0, "time"=>1399997498, "name"=>"custom.service.val2"},
179 | {"hostId"=>"xyz", "value"=>7.0, "time"=>1399997498, "name"=>"custom.service.val3"},
180 | {"hostId"=>"xyz", "value"=>9.0, "time"=>1399997498, "name"=>"custom.service.val1"},
181 | {"hostId"=>"xyz", "value"=>10.0, "time"=>1399997498, "name"=>"custom.service.val2"},
182 | ])
183 |
184 | ENV["TZ"]="Asia/Tokyo"
185 | t = Time.strptime('2014-05-14 01:11:38', '%Y-%m-%d %T').to_i
186 | d.run(default_tag: 'test') do
187 | d.feed(t, {'val1' => 1, 'val2' => 2, 'val3' => 3, 'val4' => 4})
188 | d.feed(t, {'val1' => 5, 'val2' => 6, 'val3' => 7, 'val4' => 8})
189 | d.feed(t, {'val1' => 9, 'val2' => 10})
190 | end
191 | end
192 |
193 | def test_write_pattern
194 | d = create_driver(CONFIG_OUT_KEY_PATTERN)
195 | mock(d.instance.mackerel).post_metrics([
196 | {"hostId"=>"xyz", "value"=>1.0, "time"=>1399997498, "name"=>"custom.service.val1"},
197 | {"hostId"=>"xyz", "value"=>2.0, "time"=>1399997498, "name"=>"custom.service.val2"},
198 | ])
199 |
200 | ENV["TZ"]="Asia/Tokyo"
201 | t = Time.strptime('2014-05-14 01:11:38', '%Y-%m-%d %T').to_i
202 | d.run(default_tag: 'test') do
203 | d.feed(t, {'val1' => 1, 'val2' => 2, 'foo' => 3})
204 | end
205 | end
206 |
207 | def test_write_issue4
208 | d = create_driver(CONFIG_FOR_ISSUE_4)
209 | mock(d.instance.mackerel).post_metrics([
210 | {"hostId"=>"xyz", "value"=>1.0, "time"=>1399997498, "name"=>"custom.a-status-b.val1"},
211 | {"hostId"=>"xyz", "value"=>2.0, "time"=>1399997498, "name"=>"custom.a-status-b.val2"},
212 | ])
213 |
214 | ENV["TZ"]="Asia/Tokyo"
215 | t = Time.strptime('2014-05-14 01:11:38', '%Y-%m-%d %T').to_i
216 | d.run(default_tag: 'test.status') do
217 | d.feed(t, {'val1' => 1, 'val2' => 2})
218 | end
219 | end
220 |
221 | def test_service
222 | d = create_driver(CONFIG_SERVICE)
223 | mock(d.instance.mackerel).post_service_metrics('xyz', [
224 | {"value"=>1.0, "time"=>1399997498, "name"=>"custom.val1"},
225 | {"value"=>2.0, "time"=>1399997498, "name"=>"custom.val2"},
226 | ])
227 |
228 | ENV["TZ"]="Asia/Tokyo"
229 | t = Time.strptime('2014-05-14 01:11:38', '%Y-%m-%d %T').to_i
230 | d.run(default_tag: 'test') do
231 | d.feed(t, {'val1' => 1, 'val2' => 2, 'foo' => 3})
232 | end
233 | end
234 |
235 | def test_service_remove_prefix
236 | d = create_driver(CONFIG_SERVICE_REMOVE_PREFIX)
237 | mock(d.instance.mackerel).post_service_metrics('xyz', [
238 | {"value"=>1.0, "time"=>1399997498, "name"=>"val1"},
239 | {"value"=>2.0, "time"=>1399997498, "name"=>"val2"},
240 | ])
241 |
242 | ENV["TZ"]="Asia/Tokyo"
243 | t = Time.strptime('2014-05-14 01:11:38', '%Y-%m-%d %T').to_i
244 | d.run(default_tag: 'test') do
245 | d.feed(t, {'val1' => 1, 'val2' => 2, 'foo' => 3})
246 | end
247 | end
248 |
249 | def test_service_use_zero
250 | d = create_driver(CONFIG_SERVICE_USE_ZERO)
251 | mock(d.instance.mackerel).post_service_metrics('xyz', [
252 | {"value"=>1.0, "time"=>1399997498, "name"=>"custom.val1"},
253 | {"value"=>2.0, "time"=>1399997498, "name"=>"custom.val2"},
254 | {"value"=>0.0, "time"=>1399997498, "name"=>"custom.val3"},
255 | ])
256 |
257 | ENV["TZ"]="Asia/Tokyo"
258 | t = Time.strptime('2014-05-14 01:11:38', '%Y-%m-%d %T').to_i
259 | d.run(default_tag: 'test') do
260 | d.feed(t, {'val1' => 1, 'val2' => 2, 'foo' => 3})
261 | end
262 | end
263 |
264 | def test_name_processor
265 | [
266 | {metrics_name: "${out_key}", expected: "val1"},
267 | {metrics_name: "name.${out_key}", expected: "name.val1"},
268 | {metrics_name: "a-${out_key}", expected: "a-val1"},
269 | {metrics_name: "${out_key}-b", expected: "val1-b"},
270 | {metrics_name: "a-${out_key}-b", expected: "a-val1-b"},
271 | {metrics_name: "name.a-${out_key}-b", expected: "name.a-val1-b"},
272 | {metrics_name: "${out_key}-a-${out_key}", expected: "val1-a-val1"},
273 | {metrics_name: "${out_keyx}", expected: "${out_keyx}"},
274 | {metrics_name: "${[1]}", expected: "status"},
275 | {metrics_name: "${[-1]}", expected: "status"},
276 | {metrics_name: "name.${[1]}", expected: "name.status"},
277 | {metrics_name: "a-${[1]}", expected: "a-status"},
278 | {metrics_name: "${[1]}-b", expected: "status-b"},
279 | {metrics_name: "a-${[1]}-b", expected: "a-status-b"},
280 | {metrics_name: "${[0]}.${[1]}", expected: "test.status"},
281 | {metrics_name: "${[0]}-${[1]}", expected: "test-status"},
282 | {metrics_name: "${[0]}-${[1]}-${out_key}", expected: "test-status-val1"},
283 | {metrics_name: "${[2]}", expected: ""},
284 | ].map { |obj|
285 | test_config = %[
286 | type mackerel
287 | api_key 123456
288 | hostid xyz
289 | metrics_name #{obj[:metrics_name]}
290 | out_keys val1,val2
291 | ]
292 | d = create_driver(test_config)
293 | name_processor = d.instance.instance_variable_get(:@name_processor)
294 | actual = name_processor.map{ |p| p.call(:out_key => 'val1', :tokens => ['test', 'status']) }.join('.')
295 | assert_equal obj[:expected], actual
296 | }
297 | end
298 |
299 |
300 | end
301 |
--------------------------------------------------------------------------------
/test/plugin/test_out_mackerel_hostid_tag.rb:
--------------------------------------------------------------------------------
1 | # coding: utf-8
2 |
3 | require 'helper'
4 |
5 | class MackerelHostidTagOutputTest < Test::Unit::TestCase
6 |
7 | def setup
8 | Fluent::Test.setup
9 | end
10 |
11 | HOSTID_PATH = File.dirname(__FILE__) + "/hostid"
12 |
13 | CONFIG = %[
14 | type mackerel_hostid_tag
15 | hostid_path #{HOSTID_PATH}
16 | add_to tag
17 | ]
18 |
19 | CONFIG_RECORD = %[
20 | type mackerel_hostid_tag
21 | hostid_path #{HOSTID_PATH}
22 | add_to record
23 | key_name mackerel_hostid
24 | ]
25 |
26 | CONFIG_RECORD_NO_KEY_NAME = %[
27 | type mackerel_hostid_tag
28 | hostid_path #{HOSTID_PATH}
29 | add_to record
30 | ]
31 |
32 | CONFIG_TAG_REMOVE = %[
33 | type mackerel_hostid_tag
34 | hostid_path #{HOSTID_PATH}
35 | add_to record
36 | key_name mackerel_hostid
37 | remove_prefix test
38 | ]
39 |
40 | CONFIG_TAG_ADD = %[
41 | type mackerel_hostid_tag
42 | hostid_path #{HOSTID_PATH}
43 | add_to record
44 | key_name mackerel_hostid
45 | add_prefix mackerel
46 | ]
47 |
48 | CONFIG_TAG_BOTH = %[
49 | type mackerel_hostid_tag
50 | hostid_path #{HOSTID_PATH}
51 | add_to record
52 | key_name mackerel_hostid
53 | remove_prefix test
54 | add_prefix mackerel
55 | ]
56 |
57 | CONFIG_TAG_BOTH_TAG = %[
58 | type mackerel_hostid_tag
59 | hostid_path #{HOSTID_PATH}
60 | add_to tag
61 | remove_prefix test
62 | add_prefix mackerel
63 | ]
64 |
65 | def create_driver(conf = CONFIG, tag='test')
66 | Fluent::Test::OutputTestDriver.new(Fluent::MackerelHostidTagOutput, tag).configure(conf)
67 | end
68 |
69 | def test_configure
70 |
71 | assert_raise(Fluent::ConfigError) {
72 | create_driver('')
73 | }
74 |
75 | assert_raise(Fluent::ConfigError) {
76 | create_driver(CONFIG_RECORD_NO_KEY_NAME)
77 | }
78 |
79 | d = create_driver()
80 | assert_equal d.instance.instance_variable_get(:@hostid), 'xyz'
81 | assert_equal d.instance.instance_variable_get(:@add_to), 'tag'
82 |
83 | d = create_driver(CONFIG_RECORD)
84 | assert_equal d.instance.instance_variable_get(:@hostid), 'xyz'
85 | assert_equal d.instance.instance_variable_get(:@add_to), 'record'
86 | assert_equal d.instance.instance_variable_get(:@key_name), 'mackerel_hostid'
87 |
88 | end
89 |
90 | def test_write_tag
91 |
92 | d = create_driver()
93 |
94 | ENV["TZ"]="Asia/Tokyo"
95 | t = Time.strptime('2014-08-10 15:00:00', '%Y-%m-%d %T')
96 | d.run do
97 | d.emit({'val1' => 1, 'val2' => 2, 'val3' => 3, 'val4' => 4}, t)
98 | d.emit({'val1' => 5, 'val2' => 6, 'val3' => 7, 'val4' => 8}, t)
99 | end
100 |
101 | emits = d.emits
102 | assert_equal 2, emits.length
103 | assert_equal 'test.xyz', emits[0][0] # tag
104 | assert_equal 1407650400, emits[0][1].to_i # time
105 | assert_equal 4, emits[0][2].length # record
106 | assert_equal 1, emits[0][2]["val1"] # record
107 | assert_equal 2, emits[0][2]["val2"] # record
108 | assert_equal 3, emits[0][2]["val3"] # record
109 | assert_equal 4, emits[0][2]["val4"] # record
110 |
111 | assert_equal 'test.xyz', emits[1][0] # tag
112 | assert_equal 1407650400, emits[1][1].to_i # time
113 | assert_equal 4, emits[1][2].length # record
114 | assert_equal 5, emits[1][2]["val1"] # record
115 | assert_equal 6, emits[1][2]["val2"] # record
116 | assert_equal 7, emits[1][2]["val3"] # record
117 | assert_equal 8, emits[1][2]["val4"] # record
118 | end
119 |
120 | def test_write_record
121 |
122 | d = create_driver(CONFIG_RECORD)
123 |
124 | ENV["TZ"]="Asia/Tokyo"
125 | t = Time.strptime('2014-08-10 15:00:00', '%Y-%m-%d %T')
126 | d.run do
127 | d.emit({'val1' => 1, 'val2' => 2, 'val3' => 3, 'val4' => 4}, t)
128 | d.emit({'val1' => 5, 'val2' => 6, 'val3' => 7, 'val4' => 8}, t)
129 | end
130 |
131 | emits = d.emits
132 | assert_equal 2, emits.length
133 | assert_equal 'test', emits[0][0] # tag
134 | assert_equal 1407650400, emits[0][1].to_i # time
135 | assert_equal 5, emits[0][2].length # record length
136 | assert_equal 1, emits[0][2]["val1"] # record
137 | assert_equal 2, emits[0][2]["val2"] # record
138 | assert_equal 3, emits[0][2]["val3"] # record
139 | assert_equal 4, emits[0][2]["val4"] # record
140 | assert_equal "xyz", emits[0][2]["mackerel_hostid"] # record
141 |
142 | assert_equal 'test', emits[1][0] # tag
143 | assert_equal 1407650400, emits[1][1].to_i # time
144 | assert_equal 5, emits[1][2].length # record length
145 | assert_equal 5, emits[1][2]["val1"] # record
146 | assert_equal 6, emits[1][2]["val2"] # record
147 | assert_equal 7, emits[1][2]["val3"] # record
148 | assert_equal 8, emits[1][2]["val4"] # record
149 | assert_equal "xyz", emits[1][2]["mackerel_hostid"] # record
150 | end
151 |
152 | def test_write_record_remove_prefix
153 |
154 | d = create_driver(CONFIG_TAG_REMOVE, 'test.service')
155 |
156 | ENV["TZ"]="Asia/Tokyo"
157 | t = Time.strptime('2014-08-10 15:00:00', '%Y-%m-%d %T')
158 | d.run do
159 | d.emit({'val1' => 1, 'val2' => 2, 'val3' => 3, 'val4' => 4}, t)
160 | end
161 |
162 | emits = d.emits
163 | assert_equal 1, emits.length
164 | assert_equal 'service', emits[0][0] # tag
165 | assert_equal 1407650400, emits[0][1].to_i # time
166 | assert_equal 5, emits[0][2].length # record length
167 | assert_equal 1, emits[0][2]["val1"] # record
168 | assert_equal 2, emits[0][2]["val2"] # record
169 | assert_equal 3, emits[0][2]["val3"] # record
170 | assert_equal 4, emits[0][2]["val4"] # record
171 | assert_equal "xyz", emits[0][2]["mackerel_hostid"] # record
172 |
173 | end
174 |
175 | def test_write_record_add_prefix
176 |
177 | d = create_driver(CONFIG_TAG_ADD)
178 |
179 | ENV["TZ"]="Asia/Tokyo"
180 | t = Time.strptime('2014-08-10 15:00:00', '%Y-%m-%d %T')
181 | d.run do
182 | d.emit({'val1' => 1, 'val2' => 2, 'val3' => 3, 'val4' => 4}, t)
183 | end
184 |
185 | emits = d.emits
186 | assert_equal 1, emits.length
187 | assert_equal 'mackerel.test', emits[0][0] # tag
188 | assert_equal 1407650400, emits[0][1].to_i # time
189 | assert_equal 5, emits[0][2].length # record length
190 | assert_equal 1, emits[0][2]["val1"] # record
191 | assert_equal 2, emits[0][2]["val2"] # record
192 | assert_equal 3, emits[0][2]["val3"] # record
193 | assert_equal 4, emits[0][2]["val4"] # record
194 | assert_equal "xyz", emits[0][2]["mackerel_hostid"] # record
195 |
196 | end
197 |
198 | def test_write_record_remove_and_add_prefix
199 |
200 | d = create_driver(CONFIG_TAG_BOTH, 'test.service')
201 |
202 | ENV["TZ"]="Asia/Tokyo"
203 | t = Time.strptime('2014-08-10 15:00:00', '%Y-%m-%d %T')
204 | d.run do
205 | d.emit({'val1' => 1, 'val2' => 2, 'val3' => 3, 'val4' => 4}, t)
206 | end
207 |
208 | emits = d.emits
209 | assert_equal 1, emits.length
210 | assert_equal 'mackerel.service', emits[0][0] # tag
211 | assert_equal 1407650400, emits[0][1].to_i # time
212 | assert_equal 5, emits[0][2].length # record length
213 | assert_equal 1, emits[0][2]["val1"] # record
214 | assert_equal 2, emits[0][2]["val2"] # record
215 | assert_equal 3, emits[0][2]["val3"] # record
216 | assert_equal 4, emits[0][2]["val4"] # record
217 | assert_equal "xyz", emits[0][2]["mackerel_hostid"] # record
218 |
219 | end
220 |
221 | def test_write_tag_remove_and_add_prefix
222 |
223 | d = create_driver(CONFIG_TAG_BOTH_TAG, 'test.service')
224 |
225 | ENV["TZ"]="Asia/Tokyo"
226 | t = Time.strptime('2014-08-10 15:00:00', '%Y-%m-%d %T')
227 | d.run do
228 | d.emit({'val1' => 1, 'val2' => 2, 'val3' => 3, 'val4' => 4}, t)
229 | end
230 |
231 | emits = d.emits
232 | assert_equal 1, emits.length
233 | assert_equal 'mackerel.service.xyz', emits[0][0] # tag
234 | assert_equal 1407650400, emits[0][1].to_i # time
235 | assert_equal 4, emits[0][2].length # record length
236 | assert_equal 1, emits[0][2]["val1"] # record
237 | assert_equal 2, emits[0][2]["val2"] # record
238 | assert_equal 3, emits[0][2]["val3"] # record
239 | assert_equal 4, emits[0][2]["val4"] # record
240 |
241 | end
242 |
243 | end
244 |
--------------------------------------------------------------------------------