├── .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 [![Build Status](https://travis-ci.org/mackerelio/fluent-plugin-mackerel.png?branch=master)](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 | --------------------------------------------------------------------------------