fluentd 0.14.4-x64-mingw32
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of fluentd might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE.md +6 -0
- data/.gitignore +26 -0
- data/.travis.yml +45 -0
- data/AUTHORS +2 -0
- data/CONTRIBUTING.md +35 -0
- data/COPYING +14 -0
- data/ChangeLog +276 -0
- data/Gemfile +9 -0
- data/README.md +51 -0
- data/Rakefile +53 -0
- data/Vagrantfile +17 -0
- data/appveyor.yml +41 -0
- data/bin/fluent-debug +5 -0
- data/example/copy_roundrobin.conf +39 -0
- data/example/filter_stdout.conf +22 -0
- data/example/in_forward.conf +11 -0
- data/example/in_http.conf +14 -0
- data/example/in_out_forward.conf +17 -0
- data/example/in_syslog.conf +15 -0
- data/example/in_tail.conf +14 -0
- data/example/in_tcp.conf +13 -0
- data/example/in_udp.conf +13 -0
- data/example/multi_filters.conf +61 -0
- data/example/out_buffered_null.conf +32 -0
- data/example/out_copy.conf +20 -0
- data/example/out_file.conf +13 -0
- data/example/out_forward.conf +35 -0
- data/example/out_forward_buf_file.conf +23 -0
- data/example/v0_12_filter.conf +78 -0
- data/example/v1_literal_example.conf +36 -0
- data/fluent.conf +139 -0
- data/fluentd.gemspec +51 -0
- data/lib/fluent/agent.rb +194 -0
- data/lib/fluent/command/bundler_injection.rb +45 -0
- data/lib/fluent/command/cat.rb +319 -0
- data/lib/fluent/command/debug.rb +102 -0
- data/lib/fluent/command/fluentd.rb +273 -0
- data/lib/fluent/compat/call_super_mixin.rb +67 -0
- data/lib/fluent/compat/exec_util.rb +129 -0
- data/lib/fluent/compat/file_util.rb +54 -0
- data/lib/fluent/compat/filter.rb +68 -0
- data/lib/fluent/compat/formatter.rb +111 -0
- data/lib/fluent/compat/formatter_utils.rb +85 -0
- data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
- data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
- data/lib/fluent/compat/input.rb +49 -0
- data/lib/fluent/compat/output.rb +677 -0
- data/lib/fluent/compat/output_chain.rb +60 -0
- data/lib/fluent/compat/parser.rb +180 -0
- data/lib/fluent/compat/parser_utils.rb +40 -0
- data/lib/fluent/compat/propagate_default.rb +62 -0
- data/lib/fluent/compat/record_filter_mixin.rb +34 -0
- data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
- data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
- data/lib/fluent/compat/socket_util.rb +165 -0
- data/lib/fluent/compat/string_util.rb +34 -0
- data/lib/fluent/compat/structured_format_mixin.rb +26 -0
- data/lib/fluent/compat/type_converter.rb +90 -0
- data/lib/fluent/config.rb +56 -0
- data/lib/fluent/config/basic_parser.rb +123 -0
- data/lib/fluent/config/configure_proxy.rb +366 -0
- data/lib/fluent/config/dsl.rb +149 -0
- data/lib/fluent/config/element.rb +218 -0
- data/lib/fluent/config/error.rb +26 -0
- data/lib/fluent/config/literal_parser.rb +251 -0
- data/lib/fluent/config/parser.rb +107 -0
- data/lib/fluent/config/section.rb +212 -0
- data/lib/fluent/config/types.rb +136 -0
- data/lib/fluent/config/v1_parser.rb +190 -0
- data/lib/fluent/configurable.rb +176 -0
- data/lib/fluent/daemon.rb +15 -0
- data/lib/fluent/engine.rb +220 -0
- data/lib/fluent/env.rb +27 -0
- data/lib/fluent/event.rb +287 -0
- data/lib/fluent/event_router.rb +259 -0
- data/lib/fluent/filter.rb +21 -0
- data/lib/fluent/formatter.rb +23 -0
- data/lib/fluent/input.rb +21 -0
- data/lib/fluent/label.rb +38 -0
- data/lib/fluent/load.rb +36 -0
- data/lib/fluent/log.rb +445 -0
- data/lib/fluent/match.rb +141 -0
- data/lib/fluent/mixin.rb +31 -0
- data/lib/fluent/msgpack_factory.rb +62 -0
- data/lib/fluent/output.rb +26 -0
- data/lib/fluent/output_chain.rb +23 -0
- data/lib/fluent/parser.rb +23 -0
- data/lib/fluent/plugin.rb +161 -0
- data/lib/fluent/plugin/bare_output.rb +63 -0
- data/lib/fluent/plugin/base.rb +130 -0
- data/lib/fluent/plugin/buf_file.rb +154 -0
- data/lib/fluent/plugin/buf_memory.rb +34 -0
- data/lib/fluent/plugin/buffer.rb +603 -0
- data/lib/fluent/plugin/buffer/chunk.rb +160 -0
- data/lib/fluent/plugin/buffer/file_chunk.rb +323 -0
- data/lib/fluent/plugin/buffer/memory_chunk.rb +90 -0
- data/lib/fluent/plugin/exec_util.rb +22 -0
- data/lib/fluent/plugin/file_util.rb +22 -0
- data/lib/fluent/plugin/file_wrapper.rb +120 -0
- data/lib/fluent/plugin/filter.rb +93 -0
- data/lib/fluent/plugin/filter_grep.rb +75 -0
- data/lib/fluent/plugin/filter_record_transformer.rb +342 -0
- data/lib/fluent/plugin/filter_stdout.rb +53 -0
- data/lib/fluent/plugin/formatter.rb +45 -0
- data/lib/fluent/plugin/formatter_csv.rb +47 -0
- data/lib/fluent/plugin/formatter_hash.rb +29 -0
- data/lib/fluent/plugin/formatter_json.rb +44 -0
- data/lib/fluent/plugin/formatter_ltsv.rb +41 -0
- data/lib/fluent/plugin/formatter_msgpack.rb +29 -0
- data/lib/fluent/plugin/formatter_out_file.rb +78 -0
- data/lib/fluent/plugin/formatter_single_value.rb +34 -0
- data/lib/fluent/plugin/formatter_stdout.rb +74 -0
- data/lib/fluent/plugin/in_debug_agent.rb +64 -0
- data/lib/fluent/plugin/in_dummy.rb +135 -0
- data/lib/fluent/plugin/in_exec.rb +149 -0
- data/lib/fluent/plugin/in_forward.rb +366 -0
- data/lib/fluent/plugin/in_gc_stat.rb +52 -0
- data/lib/fluent/plugin/in_http.rb +422 -0
- data/lib/fluent/plugin/in_monitor_agent.rb +401 -0
- data/lib/fluent/plugin/in_object_space.rb +90 -0
- data/lib/fluent/plugin/in_syslog.rb +204 -0
- data/lib/fluent/plugin/in_tail.rb +838 -0
- data/lib/fluent/plugin/in_tcp.rb +41 -0
- data/lib/fluent/plugin/in_udp.rb +37 -0
- data/lib/fluent/plugin/in_unix.rb +201 -0
- data/lib/fluent/plugin/input.rb +33 -0
- data/lib/fluent/plugin/multi_output.rb +95 -0
- data/lib/fluent/plugin/out_buffered_null.rb +59 -0
- data/lib/fluent/plugin/out_buffered_stdout.rb +70 -0
- data/lib/fluent/plugin/out_copy.rb +42 -0
- data/lib/fluent/plugin/out_exec.rb +114 -0
- data/lib/fluent/plugin/out_exec_filter.rb +393 -0
- data/lib/fluent/plugin/out_file.rb +167 -0
- data/lib/fluent/plugin/out_forward.rb +646 -0
- data/lib/fluent/plugin/out_null.rb +27 -0
- data/lib/fluent/plugin/out_relabel.rb +28 -0
- data/lib/fluent/plugin/out_roundrobin.rb +80 -0
- data/lib/fluent/plugin/out_stdout.rb +48 -0
- data/lib/fluent/plugin/out_stream.rb +130 -0
- data/lib/fluent/plugin/output.rb +1020 -0
- data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
- data/lib/fluent/plugin/parser.rb +175 -0
- data/lib/fluent/plugin/parser_apache.rb +28 -0
- data/lib/fluent/plugin/parser_apache2.rb +84 -0
- data/lib/fluent/plugin/parser_apache_error.rb +26 -0
- data/lib/fluent/plugin/parser_csv.rb +33 -0
- data/lib/fluent/plugin/parser_json.rb +79 -0
- data/lib/fluent/plugin/parser_ltsv.rb +50 -0
- data/lib/fluent/plugin/parser_multiline.rb +104 -0
- data/lib/fluent/plugin/parser_nginx.rb +28 -0
- data/lib/fluent/plugin/parser_none.rb +36 -0
- data/lib/fluent/plugin/parser_regexp.rb +73 -0
- data/lib/fluent/plugin/parser_syslog.rb +82 -0
- data/lib/fluent/plugin/parser_tsv.rb +37 -0
- data/lib/fluent/plugin/socket_util.rb +22 -0
- data/lib/fluent/plugin/storage.rb +84 -0
- data/lib/fluent/plugin/storage_local.rb +132 -0
- data/lib/fluent/plugin/string_util.rb +22 -0
- data/lib/fluent/plugin_helper.rb +42 -0
- data/lib/fluent/plugin_helper/child_process.rb +298 -0
- data/lib/fluent/plugin_helper/compat_parameters.rb +224 -0
- data/lib/fluent/plugin_helper/event_emitter.rb +80 -0
- data/lib/fluent/plugin_helper/event_loop.rb +118 -0
- data/lib/fluent/plugin_helper/formatter.rb +149 -0
- data/lib/fluent/plugin_helper/inject.rb +125 -0
- data/lib/fluent/plugin_helper/parser.rb +147 -0
- data/lib/fluent/plugin_helper/retry_state.rb +177 -0
- data/lib/fluent/plugin_helper/storage.rb +331 -0
- data/lib/fluent/plugin_helper/thread.rb +147 -0
- data/lib/fluent/plugin_helper/timer.rb +90 -0
- data/lib/fluent/plugin_id.rb +63 -0
- data/lib/fluent/process.rb +504 -0
- data/lib/fluent/registry.rb +99 -0
- data/lib/fluent/root_agent.rb +314 -0
- data/lib/fluent/rpc.rb +94 -0
- data/lib/fluent/supervisor.rb +680 -0
- data/lib/fluent/system_config.rb +122 -0
- data/lib/fluent/test.rb +56 -0
- data/lib/fluent/test/base.rb +85 -0
- data/lib/fluent/test/driver/base.rb +179 -0
- data/lib/fluent/test/driver/base_owned.rb +70 -0
- data/lib/fluent/test/driver/base_owner.rb +125 -0
- data/lib/fluent/test/driver/event_feeder.rb +98 -0
- data/lib/fluent/test/driver/filter.rb +57 -0
- data/lib/fluent/test/driver/formatter.rb +30 -0
- data/lib/fluent/test/driver/input.rb +31 -0
- data/lib/fluent/test/driver/multi_output.rb +52 -0
- data/lib/fluent/test/driver/output.rb +76 -0
- data/lib/fluent/test/driver/parser.rb +30 -0
- data/lib/fluent/test/driver/test_event_router.rb +45 -0
- data/lib/fluent/test/filter_test.rb +77 -0
- data/lib/fluent/test/formatter_test.rb +65 -0
- data/lib/fluent/test/helpers.rb +79 -0
- data/lib/fluent/test/input_test.rb +172 -0
- data/lib/fluent/test/log.rb +73 -0
- data/lib/fluent/test/output_test.rb +156 -0
- data/lib/fluent/test/parser_test.rb +70 -0
- data/lib/fluent/time.rb +175 -0
- data/lib/fluent/timezone.rb +133 -0
- data/lib/fluent/unique_id.rb +39 -0
- data/lib/fluent/version.rb +21 -0
- data/lib/fluent/winsvc.rb +71 -0
- data/test/compat/test_calls_super.rb +166 -0
- data/test/compat/test_parser.rb +82 -0
- data/test/config/assertions.rb +42 -0
- data/test/config/test_config_parser.rb +507 -0
- data/test/config/test_configurable.rb +1194 -0
- data/test/config/test_configure_proxy.rb +386 -0
- data/test/config/test_dsl.rb +415 -0
- data/test/config/test_element.rb +403 -0
- data/test/config/test_literal_parser.rb +297 -0
- data/test/config/test_section.rb +184 -0
- data/test/config/test_system_config.rb +120 -0
- data/test/config/test_types.rb +171 -0
- data/test/helper.rb +119 -0
- data/test/plugin/data/2010/01/20100102-030405.log +0 -0
- data/test/plugin/data/2010/01/20100102-030406.log +0 -0
- data/test/plugin/data/2010/01/20100102.log +0 -0
- data/test/plugin/data/log/bar +0 -0
- data/test/plugin/data/log/foo/bar.log +0 -0
- data/test/plugin/data/log/test.log +0 -0
- data/test/plugin/test_bare_output.rb +118 -0
- data/test/plugin/test_base.rb +75 -0
- data/test/plugin/test_buf_file.rb +571 -0
- data/test/plugin/test_buf_memory.rb +42 -0
- data/test/plugin/test_buffer.rb +1200 -0
- data/test/plugin/test_buffer_chunk.rb +168 -0
- data/test/plugin/test_buffer_file_chunk.rb +771 -0
- data/test/plugin/test_buffer_memory_chunk.rb +265 -0
- data/test/plugin/test_file_util.rb +96 -0
- data/test/plugin/test_filter.rb +353 -0
- data/test/plugin/test_filter_grep.rb +119 -0
- data/test/plugin/test_filter_record_transformer.rb +600 -0
- data/test/plugin/test_filter_stdout.rb +211 -0
- data/test/plugin/test_formatter_csv.rb +94 -0
- data/test/plugin/test_formatter_json.rb +30 -0
- data/test/plugin/test_formatter_ltsv.rb +52 -0
- data/test/plugin/test_formatter_msgpack.rb +28 -0
- data/test/plugin/test_formatter_out_file.rb +95 -0
- data/test/plugin/test_formatter_single_value.rb +38 -0
- data/test/plugin/test_in_debug_agent.rb +28 -0
- data/test/plugin/test_in_dummy.rb +188 -0
- data/test/plugin/test_in_exec.rb +133 -0
- data/test/plugin/test_in_forward.rb +635 -0
- data/test/plugin/test_in_gc_stat.rb +39 -0
- data/test/plugin/test_in_http.rb +442 -0
- data/test/plugin/test_in_monitor_agent.rb +329 -0
- data/test/plugin/test_in_object_space.rb +64 -0
- data/test/plugin/test_in_syslog.rb +205 -0
- data/test/plugin/test_in_tail.rb +1001 -0
- data/test/plugin/test_in_tcp.rb +102 -0
- data/test/plugin/test_in_udp.rb +121 -0
- data/test/plugin/test_in_unix.rb +126 -0
- data/test/plugin/test_input.rb +122 -0
- data/test/plugin/test_multi_output.rb +180 -0
- data/test/plugin/test_out_buffered_null.rb +79 -0
- data/test/plugin/test_out_buffered_stdout.rb +122 -0
- data/test/plugin/test_out_copy.rb +160 -0
- data/test/plugin/test_out_exec.rb +155 -0
- data/test/plugin/test_out_exec_filter.rb +262 -0
- data/test/plugin/test_out_file.rb +383 -0
- data/test/plugin/test_out_forward.rb +590 -0
- data/test/plugin/test_out_null.rb +29 -0
- data/test/plugin/test_out_relabel.rb +28 -0
- data/test/plugin/test_out_roundrobin.rb +146 -0
- data/test/plugin/test_out_stdout.rb +92 -0
- data/test/plugin/test_out_stream.rb +93 -0
- data/test/plugin/test_output.rb +568 -0
- data/test/plugin/test_output_as_buffered.rb +1604 -0
- data/test/plugin/test_output_as_buffered_overflow.rb +250 -0
- data/test/plugin/test_output_as_buffered_retries.rb +839 -0
- data/test/plugin/test_output_as_buffered_secondary.rb +817 -0
- data/test/plugin/test_output_as_standard.rb +374 -0
- data/test/plugin/test_owned_by.rb +35 -0
- data/test/plugin/test_parser_apache.rb +42 -0
- data/test/plugin/test_parser_apache2.rb +38 -0
- data/test/plugin/test_parser_apache_error.rb +45 -0
- data/test/plugin/test_parser_base.rb +32 -0
- data/test/plugin/test_parser_csv.rb +104 -0
- data/test/plugin/test_parser_json.rb +107 -0
- data/test/plugin/test_parser_labeled_tsv.rb +129 -0
- data/test/plugin/test_parser_multiline.rb +100 -0
- data/test/plugin/test_parser_nginx.rb +48 -0
- data/test/plugin/test_parser_none.rb +53 -0
- data/test/plugin/test_parser_regexp.rb +277 -0
- data/test/plugin/test_parser_syslog.rb +66 -0
- data/test/plugin/test_parser_time.rb +46 -0
- data/test/plugin/test_parser_tsv.rb +121 -0
- data/test/plugin/test_storage.rb +167 -0
- data/test/plugin/test_storage_local.rb +8 -0
- data/test/plugin/test_string_util.rb +26 -0
- data/test/plugin_helper/test_child_process.rb +608 -0
- data/test/plugin_helper/test_compat_parameters.rb +242 -0
- data/test/plugin_helper/test_event_emitter.rb +51 -0
- data/test/plugin_helper/test_event_loop.rb +52 -0
- data/test/plugin_helper/test_formatter.rb +252 -0
- data/test/plugin_helper/test_inject.rb +487 -0
- data/test/plugin_helper/test_parser.rb +263 -0
- data/test/plugin_helper/test_retry_state.rb +399 -0
- data/test/plugin_helper/test_storage.rb +521 -0
- data/test/plugin_helper/test_thread.rb +164 -0
- data/test/plugin_helper/test_timer.rb +131 -0
- data/test/scripts/exec_script.rb +32 -0
- data/test/scripts/fluent/plugin/formatter_known.rb +8 -0
- data/test/scripts/fluent/plugin/out_test.rb +81 -0
- data/test/scripts/fluent/plugin/out_test2.rb +80 -0
- data/test/scripts/fluent/plugin/parser_known.rb +4 -0
- data/test/test_config.rb +179 -0
- data/test/test_configdsl.rb +148 -0
- data/test/test_event.rb +329 -0
- data/test/test_event_router.rb +331 -0
- data/test/test_event_time.rb +184 -0
- data/test/test_filter.rb +121 -0
- data/test/test_formatter.rb +319 -0
- data/test/test_input.rb +31 -0
- data/test/test_log.rb +572 -0
- data/test/test_match.rb +137 -0
- data/test/test_mixin.rb +351 -0
- data/test/test_output.rb +214 -0
- data/test/test_plugin_classes.rb +136 -0
- data/test/test_plugin_helper.rb +81 -0
- data/test/test_process.rb +48 -0
- data/test/test_root_agent.rb +278 -0
- data/test/test_supervisor.rb +339 -0
- data/test/test_time_formatter.rb +186 -0
- data/test/test_unique_id.rb +47 -0
- metadata +823 -0
@@ -0,0 +1,167 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'fileutils'
|
18
|
+
require 'zlib'
|
19
|
+
|
20
|
+
require 'fluent/output'
|
21
|
+
require 'fluent/config/error'
|
22
|
+
require 'fluent/system_config'
|
23
|
+
|
24
|
+
module Fluent
|
25
|
+
class FileOutput < TimeSlicedOutput
|
26
|
+
include SystemConfig::Mixin
|
27
|
+
|
28
|
+
Plugin.register_output('file', self)
|
29
|
+
|
30
|
+
SUPPORTED_COMPRESS = {
|
31
|
+
'gz' => :gz,
|
32
|
+
'gzip' => :gz,
|
33
|
+
}
|
34
|
+
|
35
|
+
FILE_PERMISSION = 0644
|
36
|
+
DIR_PERMISSION = 0755
|
37
|
+
|
38
|
+
desc "The Path of the file."
|
39
|
+
config_param :path, :string
|
40
|
+
desc "The format of the file content. The default is out_file."
|
41
|
+
config_param :format, :string, default: 'out_file', skip_accessor: true
|
42
|
+
desc "The flushed chunk is appended to existence file or not."
|
43
|
+
config_param :append, :bool, default: false
|
44
|
+
desc "Compress flushed file."
|
45
|
+
config_param :compress, default: nil do |val|
|
46
|
+
c = SUPPORTED_COMPRESS[val]
|
47
|
+
unless c
|
48
|
+
raise ConfigError, "Unsupported compression algorithm '#{val}'"
|
49
|
+
end
|
50
|
+
c
|
51
|
+
end
|
52
|
+
desc "Create symlink to temporary buffered file when buffer_type is file."
|
53
|
+
config_param :symlink_path, :string, default: nil
|
54
|
+
|
55
|
+
module SymlinkBufferMixin
|
56
|
+
def symlink_path=(path)
|
57
|
+
@_symlink_path = path
|
58
|
+
end
|
59
|
+
|
60
|
+
def generate_chunk(metadata)
|
61
|
+
chunk = super
|
62
|
+
latest_chunk = metadata_list.sort_by(&:timekey).last
|
63
|
+
if chunk.metadata == latest_chunk
|
64
|
+
FileUtils.ln_sf(chunk.path, @_symlink_path)
|
65
|
+
end
|
66
|
+
chunk
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def initialize
|
71
|
+
require 'zlib'
|
72
|
+
require 'time'
|
73
|
+
require 'fluent/plugin/file_util'
|
74
|
+
super
|
75
|
+
end
|
76
|
+
|
77
|
+
def configure(conf)
|
78
|
+
if path = conf['path']
|
79
|
+
@path = path
|
80
|
+
end
|
81
|
+
unless @path
|
82
|
+
raise ConfigError, "'path' parameter is required on file output"
|
83
|
+
end
|
84
|
+
|
85
|
+
if pos = @path.index('*')
|
86
|
+
@path_prefix = @path[0,pos]
|
87
|
+
@path_suffix = @path[pos+1..-1]
|
88
|
+
conf['buffer_path'] ||= "#{@path}"
|
89
|
+
else
|
90
|
+
@path_prefix = @path+"."
|
91
|
+
@path_suffix = ".log"
|
92
|
+
conf['buffer_path'] ||= "#{@path}.*"
|
93
|
+
end
|
94
|
+
|
95
|
+
test_path = generate_path(Time.now.strftime(@time_slice_format))
|
96
|
+
unless ::Fluent::FileUtil.writable_p?(test_path)
|
97
|
+
raise ConfigError, "out_file: `#{test_path}` is not writable"
|
98
|
+
end
|
99
|
+
|
100
|
+
super
|
101
|
+
|
102
|
+
@formatter = Plugin.new_formatter(@format)
|
103
|
+
@formatter.configure(conf)
|
104
|
+
|
105
|
+
if @symlink_path && @buffer.respond_to?(:path)
|
106
|
+
@buffer.extend SymlinkBufferMixin
|
107
|
+
@buffer.symlink_path = @symlink_path
|
108
|
+
end
|
109
|
+
|
110
|
+
@dir_perm = system_config.dir_permission || DIR_PERMISSION
|
111
|
+
@file_perm = system_config.file_permission || FILE_PERMISSION
|
112
|
+
end
|
113
|
+
|
114
|
+
def format(tag, time, record)
|
115
|
+
@formatter.format(tag, time, record)
|
116
|
+
end
|
117
|
+
|
118
|
+
def write(chunk)
|
119
|
+
path = generate_path(chunk.key)
|
120
|
+
FileUtils.mkdir_p File.dirname(path), mode: @dir_perm
|
121
|
+
|
122
|
+
case @compress
|
123
|
+
when nil
|
124
|
+
File.open(path, "ab", @file_perm) {|f|
|
125
|
+
chunk.write_to(f)
|
126
|
+
}
|
127
|
+
when :gz
|
128
|
+
File.open(path, "ab", @file_perm) {|f|
|
129
|
+
gz = Zlib::GzipWriter.new(f)
|
130
|
+
chunk.write_to(gz)
|
131
|
+
gz.close
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
return path # for test
|
136
|
+
end
|
137
|
+
|
138
|
+
def secondary_init(primary)
|
139
|
+
# don't warn even if primary.class is not FileOutput
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def suffix
|
145
|
+
case @compress
|
146
|
+
when nil
|
147
|
+
''
|
148
|
+
when :gz
|
149
|
+
".gz"
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def generate_path(time_string)
|
154
|
+
if @append
|
155
|
+
"#{@path_prefix}#{time_string}#{@path_suffix}#{suffix}"
|
156
|
+
else
|
157
|
+
path = nil
|
158
|
+
i = 0
|
159
|
+
begin
|
160
|
+
path = "#{@path_prefix}#{time_string}_#{i}#{@path_suffix}#{suffix}"
|
161
|
+
i += 1
|
162
|
+
end while File.exist?(path)
|
163
|
+
path
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,646 @@
|
|
1
|
+
#
|
2
|
+
# Fluentd
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
5
|
+
# you may not use this file except in compliance with the License.
|
6
|
+
# You may obtain a copy of the License at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
13
|
+
# See the License for the specific language governing permissions and
|
14
|
+
# limitations under the License.
|
15
|
+
#
|
16
|
+
|
17
|
+
require 'base64'
|
18
|
+
require 'socket'
|
19
|
+
require 'fileutils'
|
20
|
+
|
21
|
+
require 'cool.io'
|
22
|
+
|
23
|
+
require 'fluent/output'
|
24
|
+
require 'fluent/config/error'
|
25
|
+
|
26
|
+
module Fluent
|
27
|
+
class ForwardOutputError < StandardError
|
28
|
+
end
|
29
|
+
|
30
|
+
class ForwardOutputResponseError < ForwardOutputError
|
31
|
+
end
|
32
|
+
|
33
|
+
class ForwardOutputConnectionClosedError < ForwardOutputError
|
34
|
+
end
|
35
|
+
|
36
|
+
class ForwardOutputACKTimeoutError < ForwardOutputResponseError
|
37
|
+
end
|
38
|
+
|
39
|
+
class ForwardOutput < ObjectBufferedOutput
|
40
|
+
Plugin.register_output('forward', self)
|
41
|
+
|
42
|
+
LISTEN_PORT = 24224
|
43
|
+
|
44
|
+
def initialize
|
45
|
+
super
|
46
|
+
require 'fluent/plugin/socket_util'
|
47
|
+
@nodes = [] #=> [Node]
|
48
|
+
@loop = nil
|
49
|
+
@thread = nil
|
50
|
+
@finished = false
|
51
|
+
end
|
52
|
+
|
53
|
+
desc 'The timeout time when sending event logs.'
|
54
|
+
config_param :send_timeout, :time, default: 60
|
55
|
+
desc 'The transport protocol to use for heartbeats.(udp,tcp,none)'
|
56
|
+
config_param :heartbeat_type, default: :tcp do |val|
|
57
|
+
case val.downcase
|
58
|
+
when 'tcp'
|
59
|
+
:tcp
|
60
|
+
when 'udp'
|
61
|
+
:udp
|
62
|
+
when 'none'
|
63
|
+
:none
|
64
|
+
else
|
65
|
+
raise ConfigError, "forward output heartbeat type should be 'tcp', 'udp', or 'none'"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
desc 'The interval of the heartbeat packer.'
|
69
|
+
config_param :heartbeat_interval, :time, default: 1
|
70
|
+
desc 'The wait time before accepting a server fault recovery.'
|
71
|
+
config_param :recover_wait, :time, default: 10
|
72
|
+
desc 'The hard timeout used to detect server failure.'
|
73
|
+
config_param :hard_timeout, :time, default: 60
|
74
|
+
desc 'Set TTL to expire DNS cache in seconds.'
|
75
|
+
config_param :expire_dns_cache, :time, default: nil # 0 means disable cache
|
76
|
+
desc 'The threshold parameter used to detect server faults.'
|
77
|
+
config_param :phi_threshold, :integer, default: 16
|
78
|
+
desc 'Use the "Phi accrual failure detector" to detect server failure.'
|
79
|
+
config_param :phi_failure_detector, :bool, default: true
|
80
|
+
|
81
|
+
desc 'Change the protocol to at-least-once.'
|
82
|
+
config_param :require_ack_response, :bool, default: false # require in_forward to respond with ack
|
83
|
+
desc 'This option is used when require_ack_response is true.'
|
84
|
+
config_param :ack_response_timeout, :time, default: 190 # 0 means do not wait for ack responses
|
85
|
+
# Linux default tcp_syn_retries is 5 (in many environment)
|
86
|
+
# 3 + 6 + 12 + 24 + 48 + 96 -> 189 (sec)
|
87
|
+
desc 'Enable client-side DNS round robin.'
|
88
|
+
config_param :dns_round_robin, :bool, default: false # heartbeat_type 'udp' is not available for this
|
89
|
+
|
90
|
+
attr_reader :nodes
|
91
|
+
|
92
|
+
config_param :port, :integer, default: LISTEN_PORT, obsoleted: "User <server> section instead."
|
93
|
+
config_param :host, :string, default: nil, obsoleted: "Use <server> section instead."
|
94
|
+
|
95
|
+
def configure(conf)
|
96
|
+
super
|
97
|
+
|
98
|
+
recover_sample_size = @recover_wait / @heartbeat_interval
|
99
|
+
|
100
|
+
if @dns_round_robin
|
101
|
+
if @heartbeat_type == :udp
|
102
|
+
raise ConfigError, "forward output heartbeat type must be 'tcp' or 'none' to use dns_round_robin option"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
conf.elements.each {|e|
|
107
|
+
next if e.name != "server"
|
108
|
+
|
109
|
+
host = e['host']
|
110
|
+
port = e['port']
|
111
|
+
port = port ? port.to_i : LISTEN_PORT
|
112
|
+
|
113
|
+
weight = e['weight']
|
114
|
+
weight = weight ? weight.to_i : 60
|
115
|
+
|
116
|
+
standby = !!e['standby']
|
117
|
+
|
118
|
+
name = e['name']
|
119
|
+
unless name
|
120
|
+
name = "#{host}:#{port}"
|
121
|
+
end
|
122
|
+
|
123
|
+
failure = FailureDetector.new(@heartbeat_interval, @hard_timeout, Time.now.to_i.to_f)
|
124
|
+
|
125
|
+
node_conf = NodeConfig.new(name, host, port, weight, standby, failure,
|
126
|
+
@phi_threshold, recover_sample_size, @expire_dns_cache, @phi_failure_detector, @dns_round_robin)
|
127
|
+
|
128
|
+
if @heartbeat_type == :none
|
129
|
+
@nodes << NoneHeartbeatNode.new(log, node_conf)
|
130
|
+
else
|
131
|
+
@nodes << Node.new(log, node_conf)
|
132
|
+
end
|
133
|
+
log.info "adding forwarding server '#{name}'", host: host, port: port, weight: weight, plugin_id: plugin_id
|
134
|
+
}
|
135
|
+
|
136
|
+
if @nodes.empty?
|
137
|
+
raise ConfigError, "forward output plugin requires at least one <server> is required"
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def start
|
142
|
+
super
|
143
|
+
|
144
|
+
@rand_seed = Random.new.seed
|
145
|
+
rebuild_weight_array
|
146
|
+
@rr = 0
|
147
|
+
|
148
|
+
unless @heartbeat_type == :none
|
149
|
+
@loop = Coolio::Loop.new
|
150
|
+
|
151
|
+
if @heartbeat_type == :udp
|
152
|
+
# assuming all hosts use udp
|
153
|
+
@usock = SocketUtil.create_udp_socket(@nodes.first.host)
|
154
|
+
@usock.fcntl(Fcntl::F_SETFL, Fcntl::O_NONBLOCK)
|
155
|
+
@hb = HeartbeatHandler.new(@usock, method(:on_heartbeat))
|
156
|
+
@loop.attach(@hb)
|
157
|
+
end
|
158
|
+
|
159
|
+
@timer = HeartbeatRequestTimer.new(@heartbeat_interval, method(:on_timer))
|
160
|
+
@loop.attach(@timer)
|
161
|
+
|
162
|
+
@thread = Thread.new(&method(:run))
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def shutdown
|
167
|
+
@finished = true
|
168
|
+
if @loop
|
169
|
+
@loop.watchers.each {|w| w.detach }
|
170
|
+
@loop.stop
|
171
|
+
end
|
172
|
+
@thread.join if @thread
|
173
|
+
@usock.close if @usock
|
174
|
+
|
175
|
+
super
|
176
|
+
end
|
177
|
+
|
178
|
+
def run
|
179
|
+
@loop.run if @loop
|
180
|
+
rescue
|
181
|
+
log.error "unexpected error", error: $!.to_s
|
182
|
+
log.error_backtrace
|
183
|
+
end
|
184
|
+
|
185
|
+
def write_objects(tag, chunk)
|
186
|
+
return if chunk.empty?
|
187
|
+
|
188
|
+
error = nil
|
189
|
+
|
190
|
+
wlen = @weight_array.length
|
191
|
+
wlen.times do
|
192
|
+
@rr = (@rr + 1) % wlen
|
193
|
+
node = @weight_array[@rr]
|
194
|
+
|
195
|
+
if node.available?
|
196
|
+
begin
|
197
|
+
send_data(node, tag, chunk)
|
198
|
+
return
|
199
|
+
rescue
|
200
|
+
# for load balancing during detecting crashed servers
|
201
|
+
error = $! # use the latest error
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
if error
|
207
|
+
raise error
|
208
|
+
else
|
209
|
+
raise "no nodes are available" # TODO message
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
private
|
214
|
+
|
215
|
+
def rebuild_weight_array
|
216
|
+
standby_nodes, regular_nodes = @nodes.partition {|n|
|
217
|
+
n.standby?
|
218
|
+
}
|
219
|
+
|
220
|
+
lost_weight = 0
|
221
|
+
regular_nodes.each {|n|
|
222
|
+
unless n.available?
|
223
|
+
lost_weight += n.weight
|
224
|
+
end
|
225
|
+
}
|
226
|
+
log.debug "rebuilding weight array", lost_weight: lost_weight
|
227
|
+
|
228
|
+
if lost_weight > 0
|
229
|
+
standby_nodes.each {|n|
|
230
|
+
if n.available?
|
231
|
+
regular_nodes << n
|
232
|
+
log.warn "using standby node #{n.host}:#{n.port}", weight: n.weight
|
233
|
+
lost_weight -= n.weight
|
234
|
+
break if lost_weight <= 0
|
235
|
+
end
|
236
|
+
}
|
237
|
+
end
|
238
|
+
|
239
|
+
weight_array = []
|
240
|
+
gcd = regular_nodes.map {|n| n.weight }.inject(0) {|r,w| r.gcd(w) }
|
241
|
+
regular_nodes.each {|n|
|
242
|
+
(n.weight / gcd).times {
|
243
|
+
weight_array << n
|
244
|
+
}
|
245
|
+
}
|
246
|
+
|
247
|
+
# for load balancing during detecting crashed servers
|
248
|
+
coe = (regular_nodes.size * 6) / weight_array.size
|
249
|
+
weight_array *= coe if coe > 1
|
250
|
+
|
251
|
+
r = Random.new(@rand_seed)
|
252
|
+
weight_array.sort_by! { r.rand }
|
253
|
+
|
254
|
+
@weight_array = weight_array
|
255
|
+
end
|
256
|
+
|
257
|
+
# MessagePack FixArray length is 3
|
258
|
+
FORWARD_HEADER = [0x93].pack('C').freeze
|
259
|
+
def forward_header
|
260
|
+
FORWARD_HEADER
|
261
|
+
end
|
262
|
+
|
263
|
+
#FORWARD_TCP_HEARTBEAT_DATA = FORWARD_HEADER + ''.to_msgpack + [].to_msgpack
|
264
|
+
def send_heartbeat_tcp(node)
|
265
|
+
sock = connect(node)
|
266
|
+
begin
|
267
|
+
opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
268
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
269
|
+
opt = [@send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
270
|
+
# don't send any data to not cause a compatibility problem
|
271
|
+
#sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
272
|
+
#sock.write FORWARD_TCP_HEARTBEAT_DATA
|
273
|
+
node.heartbeat(true)
|
274
|
+
ensure
|
275
|
+
sock.close_write
|
276
|
+
sock.close
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
def send_data(node, tag, chunk)
|
281
|
+
sock = connect(node)
|
282
|
+
begin
|
283
|
+
opt = [1, @send_timeout.to_i].pack('I!I!') # { int l_onoff; int l_linger; }
|
284
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_LINGER, opt)
|
285
|
+
|
286
|
+
opt = [@send_timeout.to_i, 0].pack('L!L!') # struct timeval
|
287
|
+
sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDTIMEO, opt)
|
288
|
+
|
289
|
+
# beginArray(3)
|
290
|
+
sock.write forward_header
|
291
|
+
|
292
|
+
# writeRaw(tag)
|
293
|
+
sock.write tag.to_msgpack # tag
|
294
|
+
|
295
|
+
# beginRaw(size)
|
296
|
+
sz = chunk.size
|
297
|
+
#if sz < 32
|
298
|
+
# # FixRaw
|
299
|
+
# sock.write [0xa0 | sz].pack('C')
|
300
|
+
#elsif sz < 65536
|
301
|
+
# # raw 16
|
302
|
+
# sock.write [0xda, sz].pack('Cn')
|
303
|
+
#else
|
304
|
+
# raw 32
|
305
|
+
sock.write [0xdb, sz].pack('CN')
|
306
|
+
#end
|
307
|
+
|
308
|
+
# writeRawBody(packed_es)
|
309
|
+
chunk.write_to(sock)
|
310
|
+
|
311
|
+
option = { 'size' => chunk.size_of_events }
|
312
|
+
option['chunk'] = Base64.encode64(chunk.unique_id) if @require_ack_response
|
313
|
+
sock.write option.to_msgpack
|
314
|
+
|
315
|
+
if @require_ack_response && @ack_response_timeout > 0
|
316
|
+
# Waiting for a response here results in a decrease of throughput because a chunk queue is locked.
|
317
|
+
# To avoid a decrease of troughput, it is necessary to prepare a list of chunks that wait for responses
|
318
|
+
# and process them asynchronously.
|
319
|
+
if IO.select([sock], nil, nil, @ack_response_timeout)
|
320
|
+
raw_data = sock.recv(1024)
|
321
|
+
|
322
|
+
# When connection is closed by remote host, socket is ready to read and #recv returns an empty string that means EOF.
|
323
|
+
# If this happens we assume the data wasn't delivered and retry it.
|
324
|
+
if raw_data.empty?
|
325
|
+
@log.warn "node #{node.host}:#{node.port} closed the connection. regard it as unavailable."
|
326
|
+
node.disable!
|
327
|
+
raise ForwardOutputConnectionClosedError, "node #{node.host}:#{node.port} closed connection"
|
328
|
+
else
|
329
|
+
# Serialization type of the response is same as sent data.
|
330
|
+
res = MessagePack.unpack(raw_data)
|
331
|
+
|
332
|
+
if res['ack'] != option['chunk']
|
333
|
+
# Some errors may have occured when ack and chunk id is different, so send the chunk again.
|
334
|
+
raise ForwardOutputResponseError, "ack in response and chunk id in sent data are different"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
else
|
339
|
+
# IO.select returns nil on timeout.
|
340
|
+
# There are 2 types of cases when no response has been received:
|
341
|
+
# (1) the node does not support sending responses
|
342
|
+
# (2) the node does support sending response but responses have not arrived for some reasons.
|
343
|
+
@log.warn "no response from #{node.host}:#{node.port}. regard it as unavailable."
|
344
|
+
node.disable!
|
345
|
+
raise ForwardOutputACKTimeoutError, "node #{node.host}:#{node.port} does not return ACK"
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
node.heartbeat(false)
|
350
|
+
res # for test
|
351
|
+
ensure
|
352
|
+
sock.close_write
|
353
|
+
sock.close
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
def connect(node)
|
358
|
+
# TODO unix socket?
|
359
|
+
TCPSocket.new(node.resolved_host, node.port)
|
360
|
+
end
|
361
|
+
|
362
|
+
class HeartbeatRequestTimer < Coolio::TimerWatcher
|
363
|
+
def initialize(interval, callback)
|
364
|
+
super(interval, true)
|
365
|
+
@callback = callback
|
366
|
+
end
|
367
|
+
|
368
|
+
def on_timer
|
369
|
+
@callback.call
|
370
|
+
rescue
|
371
|
+
# TODO log?
|
372
|
+
end
|
373
|
+
end
|
374
|
+
|
375
|
+
def on_timer
|
376
|
+
return if @finished
|
377
|
+
@nodes.each {|n|
|
378
|
+
if n.tick
|
379
|
+
rebuild_weight_array
|
380
|
+
end
|
381
|
+
begin
|
382
|
+
#log.trace "sending heartbeat #{n.host}:#{n.port} on #{@heartbeat_type}"
|
383
|
+
if @heartbeat_type == :tcp
|
384
|
+
send_heartbeat_tcp(n)
|
385
|
+
else
|
386
|
+
@usock.send "\0", 0, Socket.pack_sockaddr_in(n.port, n.resolved_host)
|
387
|
+
end
|
388
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR, Errno::ECONNREFUSED
|
389
|
+
# TODO log
|
390
|
+
log.debug "failed to send heartbeat packet to #{n.host}:#{n.port}", error: $!.to_s
|
391
|
+
end
|
392
|
+
}
|
393
|
+
end
|
394
|
+
|
395
|
+
class HeartbeatHandler < Coolio::IO
|
396
|
+
def initialize(io, callback)
|
397
|
+
super(io)
|
398
|
+
@io = io
|
399
|
+
@callback = callback
|
400
|
+
end
|
401
|
+
|
402
|
+
def on_readable
|
403
|
+
begin
|
404
|
+
msg, addr = @io.recvfrom(1024)
|
405
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, Errno::EINTR
|
406
|
+
return
|
407
|
+
end
|
408
|
+
host = addr[3]
|
409
|
+
port = addr[1]
|
410
|
+
sockaddr = Socket.pack_sockaddr_in(port, host)
|
411
|
+
@callback.call(sockaddr, msg)
|
412
|
+
rescue
|
413
|
+
# TODO log?
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
def on_heartbeat(sockaddr, msg)
|
418
|
+
if node = @nodes.find {|n| n.sockaddr == sockaddr }
|
419
|
+
#log.trace "heartbeat from '#{node.name}'", :host=>node.host, :port=>node.port
|
420
|
+
if node.heartbeat
|
421
|
+
rebuild_weight_array
|
422
|
+
end
|
423
|
+
end
|
424
|
+
end
|
425
|
+
|
426
|
+
NodeConfig = Struct.new("NodeConfig", :name, :host, :port, :weight, :standby, :failure,
|
427
|
+
:phi_threshold, :recover_sample_size, :expire_dns_cache, :phi_failure_detector, :dns_round_robin)
|
428
|
+
|
429
|
+
class Node
|
430
|
+
def initialize(log, conf)
|
431
|
+
@log = log
|
432
|
+
@conf = conf
|
433
|
+
@name = @conf.name
|
434
|
+
@host = @conf.host
|
435
|
+
@port = @conf.port
|
436
|
+
@weight = @conf.weight
|
437
|
+
@failure = @conf.failure
|
438
|
+
@available = true
|
439
|
+
|
440
|
+
@resolved_host = nil
|
441
|
+
@resolved_time = 0
|
442
|
+
resolved_host # check dns
|
443
|
+
end
|
444
|
+
|
445
|
+
attr_reader :conf
|
446
|
+
attr_reader :name, :host, :port, :weight
|
447
|
+
attr_reader :sockaddr # used by on_heartbeat
|
448
|
+
attr_reader :failure, :available # for test
|
449
|
+
|
450
|
+
def available?
|
451
|
+
@available
|
452
|
+
end
|
453
|
+
|
454
|
+
def disable!
|
455
|
+
@available = false
|
456
|
+
end
|
457
|
+
|
458
|
+
def standby?
|
459
|
+
@conf.standby
|
460
|
+
end
|
461
|
+
|
462
|
+
def resolved_host
|
463
|
+
case @conf.expire_dns_cache
|
464
|
+
when 0
|
465
|
+
# cache is disabled
|
466
|
+
return resolve_dns!
|
467
|
+
|
468
|
+
when nil
|
469
|
+
# persistent cache
|
470
|
+
return @resolved_host ||= resolve_dns!
|
471
|
+
|
472
|
+
else
|
473
|
+
now = Engine.now
|
474
|
+
rh = @resolved_host
|
475
|
+
if !rh || now - @resolved_time >= @conf.expire_dns_cache
|
476
|
+
rh = @resolved_host = resolve_dns!
|
477
|
+
@resolved_time = now
|
478
|
+
end
|
479
|
+
return rh
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
def resolve_dns!
|
484
|
+
addrinfo_list = Socket.getaddrinfo(@host, @port, nil, Socket::SOCK_STREAM)
|
485
|
+
addrinfo = @conf.dns_round_robin ? addrinfo_list.sample : addrinfo_list.first
|
486
|
+
@sockaddr = Socket.pack_sockaddr_in(addrinfo[1], addrinfo[3]) # used by on_heartbeat
|
487
|
+
addrinfo[3]
|
488
|
+
end
|
489
|
+
private :resolve_dns!
|
490
|
+
|
491
|
+
def tick
|
492
|
+
now = Time.now.to_f
|
493
|
+
if !@available
|
494
|
+
if @failure.hard_timeout?(now)
|
495
|
+
@failure.clear
|
496
|
+
end
|
497
|
+
return nil
|
498
|
+
end
|
499
|
+
|
500
|
+
if @failure.hard_timeout?(now)
|
501
|
+
@log.warn "detached forwarding server '#{@name}'", host: @host, port: @port, hard_timeout: true
|
502
|
+
@available = false
|
503
|
+
@resolved_host = nil # expire cached host
|
504
|
+
@failure.clear
|
505
|
+
return true
|
506
|
+
end
|
507
|
+
|
508
|
+
if @conf.phi_failure_detector
|
509
|
+
phi = @failure.phi(now)
|
510
|
+
#$log.trace "phi '#{@name}'", :host=>@host, :port=>@port, :phi=>phi
|
511
|
+
if phi > @conf.phi_threshold
|
512
|
+
@log.warn "detached forwarding server '#{@name}'", host: @host, port: @port, phi: phi
|
513
|
+
@available = false
|
514
|
+
@resolved_host = nil # expire cached host
|
515
|
+
@failure.clear
|
516
|
+
return true
|
517
|
+
end
|
518
|
+
end
|
519
|
+
return false
|
520
|
+
end
|
521
|
+
|
522
|
+
def heartbeat(detect=true)
|
523
|
+
now = Time.now.to_f
|
524
|
+
@failure.add(now)
|
525
|
+
#@log.trace "heartbeat from '#{@name}'", :host=>@host, :port=>@port, :available=>@available, :sample_size=>@failure.sample_size
|
526
|
+
if detect && !@available && @failure.sample_size > @conf.recover_sample_size
|
527
|
+
@available = true
|
528
|
+
@log.warn "recovered forwarding server '#{@name}'", host: @host, port: @port
|
529
|
+
return true
|
530
|
+
else
|
531
|
+
return nil
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
def to_msgpack(out = '')
|
536
|
+
[@host, @port, @weight, @available].to_msgpack(out)
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
# Override Node to disable heartbeat
|
541
|
+
class NoneHeartbeatNode < Node
|
542
|
+
def available?
|
543
|
+
true
|
544
|
+
end
|
545
|
+
|
546
|
+
def tick
|
547
|
+
false
|
548
|
+
end
|
549
|
+
|
550
|
+
def heartbeat(detect=true)
|
551
|
+
true
|
552
|
+
end
|
553
|
+
end
|
554
|
+
|
555
|
+
class FailureDetector
|
556
|
+
PHI_FACTOR = 1.0 / Math.log(10.0)
|
557
|
+
SAMPLE_SIZE = 1000
|
558
|
+
|
559
|
+
def initialize(heartbeat_interval, hard_timeout, init_last)
|
560
|
+
@heartbeat_interval = heartbeat_interval
|
561
|
+
@last = init_last
|
562
|
+
@hard_timeout = hard_timeout
|
563
|
+
|
564
|
+
# microsec
|
565
|
+
@init_gap = (heartbeat_interval * 1e6).to_i
|
566
|
+
@window = [@init_gap]
|
567
|
+
end
|
568
|
+
|
569
|
+
def hard_timeout?(now)
|
570
|
+
now - @last > @hard_timeout
|
571
|
+
end
|
572
|
+
|
573
|
+
def add(now)
|
574
|
+
if @window.empty?
|
575
|
+
@window << @init_gap
|
576
|
+
@last = now
|
577
|
+
else
|
578
|
+
gap = now - @last
|
579
|
+
@window << (gap * 1e6).to_i
|
580
|
+
@window.shift if @window.length > SAMPLE_SIZE
|
581
|
+
@last = now
|
582
|
+
end
|
583
|
+
end
|
584
|
+
|
585
|
+
def phi(now)
|
586
|
+
size = @window.size
|
587
|
+
return 0.0 if size == 0
|
588
|
+
|
589
|
+
# Calculate weighted moving average
|
590
|
+
mean_usec = 0
|
591
|
+
fact = 0
|
592
|
+
@window.each_with_index {|gap,i|
|
593
|
+
mean_usec += gap * (1+i)
|
594
|
+
fact += (1+i)
|
595
|
+
}
|
596
|
+
mean_usec = mean_usec / fact
|
597
|
+
|
598
|
+
# Normalize arrive intervals into 1sec
|
599
|
+
mean = (mean_usec.to_f / 1e6) - @heartbeat_interval + 1
|
600
|
+
|
601
|
+
# Calculate phi of the phi accrual failure detector
|
602
|
+
t = now - @last - @heartbeat_interval + 1
|
603
|
+
phi = PHI_FACTOR * t / mean
|
604
|
+
|
605
|
+
return phi
|
606
|
+
end
|
607
|
+
|
608
|
+
def sample_size
|
609
|
+
@window.size
|
610
|
+
end
|
611
|
+
|
612
|
+
def clear
|
613
|
+
@window.clear
|
614
|
+
@last = 0
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
## TODO
|
619
|
+
#class RPC
|
620
|
+
# def initialize(this)
|
621
|
+
# @this = this
|
622
|
+
# end
|
623
|
+
#
|
624
|
+
# def list_nodes
|
625
|
+
# @this.nodes
|
626
|
+
# end
|
627
|
+
#
|
628
|
+
# def list_fault_nodes
|
629
|
+
# list_nodes.select {|n| !n.available? }
|
630
|
+
# end
|
631
|
+
#
|
632
|
+
# def list_available_nodes
|
633
|
+
# list_nodes.select {|n| n.available? }
|
634
|
+
# end
|
635
|
+
#
|
636
|
+
# def add_node(name, host, port, weight)
|
637
|
+
# end
|
638
|
+
#
|
639
|
+
# def recover_node(host, port)
|
640
|
+
# end
|
641
|
+
#
|
642
|
+
# def remove_node(host, port)
|
643
|
+
# end
|
644
|
+
#end
|
645
|
+
end
|
646
|
+
end
|