fluentd 0.12.40 → 0.14.0
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 +4 -4
- data/.github/ISSUE_TEMPLATE.md +6 -0
- data/.gitignore +2 -0
- data/.travis.yml +33 -21
- data/CONTRIBUTING.md +1 -0
- data/ChangeLog +810 -237
- data/README.md +0 -25
- data/Rakefile +2 -1
- data/Vagrantfile +17 -0
- data/appveyor.yml +35 -0
- data/example/filter_stdout.conf +5 -5
- data/example/in_forward.conf +2 -2
- data/example/in_http.conf +2 -2
- data/example/in_out_forward.conf +17 -0
- data/example/in_syslog.conf +2 -2
- data/example/in_tail.conf +2 -2
- data/example/in_tcp.conf +2 -2
- data/example/in_udp.conf +2 -2
- data/example/out_copy.conf +4 -4
- data/example/out_file.conf +2 -2
- data/example/out_forward.conf +2 -2
- data/example/out_forward_buf_file.conf +23 -0
- data/example/v0_12_filter.conf +8 -8
- data/fluent.conf +29 -0
- data/fluentd.gemspec +18 -11
- data/lib/fluent/agent.rb +60 -58
- data/lib/fluent/command/cat.rb +1 -1
- data/lib/fluent/command/debug.rb +7 -5
- data/lib/fluent/command/fluentd.rb +97 -2
- data/lib/fluent/compat/call_super_mixin.rb +67 -0
- data/lib/fluent/compat/filter.rb +50 -0
- data/lib/fluent/compat/formatter.rb +109 -0
- data/lib/fluent/compat/input.rb +50 -0
- data/lib/fluent/compat/output.rb +617 -0
- data/lib/fluent/compat/output_chain.rb +60 -0
- data/lib/fluent/compat/parser.rb +163 -0
- data/lib/fluent/compat/propagate_default.rb +62 -0
- data/lib/fluent/config.rb +23 -20
- data/lib/fluent/config/configure_proxy.rb +119 -70
- data/lib/fluent/config/dsl.rb +5 -18
- data/lib/fluent/config/element.rb +72 -8
- data/lib/fluent/config/error.rb +0 -3
- data/lib/fluent/config/literal_parser.rb +0 -2
- data/lib/fluent/config/parser.rb +4 -4
- data/lib/fluent/config/section.rb +39 -28
- data/lib/fluent/config/types.rb +2 -13
- data/lib/fluent/config/v1_parser.rb +1 -3
- data/lib/fluent/configurable.rb +48 -16
- data/lib/fluent/daemon.rb +15 -0
- data/lib/fluent/engine.rb +26 -52
- data/lib/fluent/env.rb +6 -4
- data/lib/fluent/event.rb +58 -11
- data/lib/fluent/event_router.rb +5 -5
- data/lib/fluent/filter.rb +2 -50
- data/lib/fluent/formatter.rb +4 -293
- data/lib/fluent/input.rb +2 -32
- data/lib/fluent/label.rb +2 -2
- data/lib/fluent/load.rb +3 -2
- data/lib/fluent/log.rb +107 -38
- data/lib/fluent/match.rb +0 -36
- data/lib/fluent/mixin.rb +117 -7
- data/lib/fluent/msgpack_factory.rb +62 -0
- data/lib/fluent/output.rb +7 -612
- data/lib/fluent/output_chain.rb +23 -0
- data/lib/fluent/parser.rb +4 -800
- data/lib/fluent/plugin.rb +100 -121
- data/lib/fluent/plugin/bare_output.rb +63 -0
- data/lib/fluent/plugin/base.rb +121 -0
- data/lib/fluent/plugin/buf_file.rb +101 -182
- data/lib/fluent/plugin/buf_memory.rb +9 -92
- data/lib/fluent/plugin/buffer.rb +473 -0
- data/lib/fluent/plugin/buffer/chunk.rb +135 -0
- data/lib/fluent/plugin/buffer/file_chunk.rb +339 -0
- data/lib/fluent/plugin/buffer/memory_chunk.rb +100 -0
- data/lib/fluent/plugin/exec_util.rb +80 -75
- data/lib/fluent/plugin/file_util.rb +33 -28
- data/lib/fluent/plugin/file_wrapper.rb +120 -0
- data/lib/fluent/plugin/filter.rb +51 -0
- data/lib/fluent/plugin/filter_grep.rb +13 -40
- data/lib/fluent/plugin/filter_record_transformer.rb +22 -18
- data/lib/fluent/plugin/formatter.rb +93 -0
- data/lib/fluent/plugin/formatter_csv.rb +48 -0
- data/lib/fluent/plugin/formatter_hash.rb +32 -0
- data/lib/fluent/plugin/formatter_json.rb +47 -0
- data/lib/fluent/plugin/formatter_ltsv.rb +42 -0
- data/lib/fluent/plugin/formatter_msgpack.rb +32 -0
- data/lib/fluent/plugin/formatter_out_file.rb +45 -0
- data/lib/fluent/plugin/formatter_single_value.rb +34 -0
- data/lib/fluent/plugin/formatter_stdout.rb +39 -0
- data/lib/fluent/plugin/in_debug_agent.rb +4 -0
- data/lib/fluent/plugin/in_dummy.rb +22 -18
- data/lib/fluent/plugin/in_exec.rb +18 -8
- data/lib/fluent/plugin/in_forward.rb +36 -79
- data/lib/fluent/plugin/in_gc_stat.rb +4 -0
- data/lib/fluent/plugin/in_http.rb +21 -18
- data/lib/fluent/plugin/in_monitor_agent.rb +15 -48
- data/lib/fluent/plugin/in_object_space.rb +6 -1
- data/lib/fluent/plugin/in_stream.rb +7 -3
- data/lib/fluent/plugin/in_syslog.rb +46 -95
- data/lib/fluent/plugin/in_tail.rb +51 -595
- data/lib/fluent/plugin/in_tcp.rb +8 -1
- data/lib/fluent/plugin/in_udp.rb +8 -14
- 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_copy.rb +11 -7
- data/lib/fluent/plugin/out_exec.rb +15 -11
- data/lib/fluent/plugin/out_exec_filter.rb +18 -10
- data/lib/fluent/plugin/out_file.rb +34 -5
- data/lib/fluent/plugin/out_forward.rb +19 -9
- data/lib/fluent/plugin/out_null.rb +0 -14
- data/lib/fluent/plugin/out_roundrobin.rb +11 -7
- data/lib/fluent/plugin/out_stdout.rb +5 -7
- data/lib/fluent/plugin/out_stream.rb +3 -1
- data/lib/fluent/plugin/output.rb +979 -0
- data/lib/fluent/plugin/owned_by_mixin.rb +42 -0
- data/lib/fluent/plugin/parser.rb +244 -0
- data/lib/fluent/plugin/parser_apache.rb +24 -0
- data/lib/fluent/plugin/parser_apache2.rb +84 -0
- data/lib/fluent/plugin/parser_apache_error.rb +21 -0
- data/lib/fluent/plugin/parser_csv.rb +31 -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 +102 -0
- data/lib/fluent/plugin/parser_nginx.rb +24 -0
- data/lib/fluent/plugin/parser_none.rb +36 -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 +120 -114
- data/lib/fluent/plugin/storage.rb +84 -0
- data/lib/fluent/plugin/storage_local.rb +116 -0
- data/lib/fluent/plugin/string_util.rb +16 -13
- data/lib/fluent/plugin_helper.rb +39 -0
- data/lib/fluent/plugin_helper/child_process.rb +298 -0
- data/lib/fluent/plugin_helper/compat_parameters.rb +99 -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/retry_state.rb +177 -0
- data/lib/fluent/plugin_helper/storage.rb +308 -0
- data/lib/fluent/plugin_helper/thread.rb +147 -0
- data/lib/fluent/plugin_helper/timer.rb +85 -0
- data/lib/fluent/plugin_id.rb +63 -0
- data/lib/fluent/process.rb +21 -30
- data/lib/fluent/registry.rb +21 -9
- data/lib/fluent/root_agent.rb +115 -40
- data/lib/fluent/supervisor.rb +330 -320
- data/lib/fluent/system_config.rb +42 -18
- data/lib/fluent/test.rb +6 -1
- data/lib/fluent/test/base.rb +23 -3
- data/lib/fluent/test/driver/base.rb +247 -0
- data/lib/fluent/test/driver/event_feeder.rb +98 -0
- data/lib/fluent/test/driver/filter.rb +35 -0
- data/lib/fluent/test/driver/input.rb +31 -0
- data/lib/fluent/test/driver/output.rb +78 -0
- data/lib/fluent/test/driver/test_event_router.rb +45 -0
- data/lib/fluent/test/filter_test.rb +0 -1
- data/lib/fluent/test/formatter_test.rb +2 -1
- data/lib/fluent/test/input_test.rb +23 -17
- data/lib/fluent/test/output_test.rb +28 -39
- data/lib/fluent/test/parser_test.rb +1 -1
- data/lib/fluent/time.rb +104 -1
- data/lib/fluent/{status.rb → unique_id.rb} +15 -24
- data/lib/fluent/version.rb +1 -1
- data/lib/fluent/winsvc.rb +72 -0
- data/test/compat/test_calls_super.rb +164 -0
- data/test/config/test_config_parser.rb +83 -0
- data/test/config/test_configurable.rb +547 -274
- data/test/config/test_configure_proxy.rb +146 -29
- data/test/config/test_dsl.rb +3 -181
- data/test/config/test_element.rb +274 -0
- data/test/config/test_literal_parser.rb +1 -1
- data/test/config/test_section.rb +79 -7
- data/test/config/test_system_config.rb +21 -0
- data/test/config/test_types.rb +3 -26
- data/test/helper.rb +78 -8
- data/test/plugin/test_bare_output.rb +118 -0
- data/test/plugin/test_base.rb +75 -0
- data/test/plugin/test_buf_file.rb +420 -521
- data/test/plugin/test_buf_memory.rb +32 -194
- data/test/plugin/test_buffer.rb +981 -0
- data/test/plugin/test_buffer_chunk.rb +110 -0
- data/test/plugin/test_buffer_file_chunk.rb +770 -0
- data/test/plugin/test_buffer_memory_chunk.rb +265 -0
- data/test/plugin/test_filter.rb +255 -0
- data/test/plugin/test_filter_grep.rb +2 -73
- data/test/plugin/test_filter_record_transformer.rb +24 -68
- data/test/plugin/test_filter_stdout.rb +6 -6
- data/test/plugin/test_in_debug_agent.rb +2 -0
- data/test/plugin/test_in_dummy.rb +11 -17
- data/test/plugin/test_in_exec.rb +6 -25
- data/test/plugin/test_in_forward.rb +112 -151
- data/test/plugin/test_in_gc_stat.rb +2 -0
- data/test/plugin/test_in_http.rb +106 -157
- data/test/plugin/test_in_object_space.rb +21 -5
- data/test/plugin/test_in_stream.rb +14 -13
- data/test/plugin/test_in_syslog.rb +30 -275
- data/test/plugin/test_in_tail.rb +95 -234
- data/test/plugin/test_in_tcp.rb +14 -0
- data/test/plugin/test_in_udp.rb +21 -13
- 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_copy.rb +15 -2
- data/test/plugin/test_out_exec.rb +75 -25
- data/test/plugin/test_out_exec_filter.rb +74 -8
- data/test/plugin/test_out_file.rb +61 -7
- data/test/plugin/test_out_forward.rb +92 -15
- data/test/plugin/test_out_roundrobin.rb +1 -0
- data/test/plugin/test_out_stdout.rb +22 -13
- data/test/plugin/test_out_stream.rb +18 -0
- data/test/plugin/test_output.rb +515 -0
- data/test/plugin/test_output_as_buffered.rb +1540 -0
- data/test/plugin/test_output_as_buffered_overflow.rb +247 -0
- data/test/plugin/test_output_as_buffered_retries.rb +808 -0
- data/test/plugin/test_output_as_buffered_secondary.rb +776 -0
- data/test/plugin/test_output_as_standard.rb +362 -0
- data/test/plugin/test_owned_by.rb +35 -0
- data/test/plugin/test_storage.rb +167 -0
- data/test/plugin/test_storage_local.rb +8 -0
- data/test/plugin_helper/test_child_process.rb +599 -0
- data/test/plugin_helper/test_compat_parameters.rb +175 -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_retry_state.rb +399 -0
- data/test/plugin_helper/test_storage.rb +411 -0
- data/test/plugin_helper/test_thread.rb +164 -0
- data/test/plugin_helper/test_timer.rb +100 -0
- data/test/scripts/exec_script.rb +0 -6
- data/test/scripts/fluent/plugin/out_test.rb +3 -0
- data/test/test_config.rb +13 -4
- data/test/test_event.rb +24 -13
- data/test/test_event_router.rb +8 -7
- data/test/test_event_time.rb +187 -0
- data/test/test_formatter.rb +13 -51
- data/test/test_input.rb +1 -1
- data/test/test_log.rb +239 -16
- data/test/test_mixin.rb +1 -1
- data/test/test_output.rb +53 -66
- data/test/test_parser.rb +105 -323
- data/test/test_plugin_helper.rb +81 -0
- data/test/test_root_agent.rb +4 -52
- data/test/test_supervisor.rb +272 -0
- data/test/test_unique_id.rb +47 -0
- metadata +180 -54
- data/lib/fluent/buffer.rb +0 -365
- data/lib/fluent/plugin/filter_parser.rb +0 -107
- data/lib/fluent/plugin/in_status.rb +0 -76
- data/lib/fluent/test/helpers.rb +0 -86
- data/test/plugin/data/log/foo/bar2 +0 -0
- data/test/plugin/test_filter_parser.rb +0 -744
- data/test/plugin/test_in_status.rb +0 -38
- data/test/test_buffer.rb +0 -624
@@ -15,218 +15,137 @@
|
|
15
15
|
#
|
16
16
|
|
17
17
|
require 'fileutils'
|
18
|
-
require 'uri'
|
19
18
|
|
20
|
-
require 'fluent/
|
21
|
-
require 'fluent/plugin'
|
22
|
-
require 'fluent/
|
19
|
+
require 'fluent/plugin/buffer'
|
20
|
+
require 'fluent/plugin/buffer/file_chunk'
|
21
|
+
require 'fluent/system_config'
|
23
22
|
|
24
23
|
module Fluent
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
@path = path
|
29
|
-
@unique_id = unique_id
|
30
|
-
@file = File.open(@path, mode, DEFAULT_FILE_PERMISSION)
|
31
|
-
@file.sync = true
|
32
|
-
@size = @file.stat.size
|
33
|
-
FileUtils.ln_sf(@path, symlink_path) if symlink_path
|
34
|
-
end
|
35
|
-
|
36
|
-
attr_reader :unique_id, :path
|
37
|
-
|
38
|
-
def <<(data)
|
39
|
-
@file.write(data)
|
40
|
-
@size += data.bytesize
|
41
|
-
end
|
42
|
-
|
43
|
-
def size
|
44
|
-
@size
|
45
|
-
end
|
24
|
+
module Plugin
|
25
|
+
class FileBuffer < Fluent::Plugin::Buffer
|
26
|
+
Plugin.register_buffer('file', self)
|
46
27
|
|
47
|
-
|
48
|
-
@size == 0
|
49
|
-
end
|
50
|
-
|
51
|
-
def close
|
52
|
-
stat = @file.stat
|
53
|
-
@file.close
|
54
|
-
if stat.size == 0
|
55
|
-
File.unlink(@path)
|
56
|
-
end
|
57
|
-
end
|
28
|
+
include SystemConfig::Mixin
|
58
29
|
|
59
|
-
|
60
|
-
|
61
|
-
File.unlink(@path) rescue nil # TODO rescue?
|
62
|
-
end
|
30
|
+
DEFAULT_CHUNK_LIMIT_SIZE = 256 * 1024 * 1024 # 256MB
|
31
|
+
DEFAULT_TOTAL_LIMIT_SIZE = 64 * 1024 * 1024 * 1024 # 64GB, same with v0.12 (TimeSlicedOutput + buf_file)
|
63
32
|
|
64
|
-
|
65
|
-
@file.pos = 0
|
66
|
-
@file.read
|
67
|
-
end
|
33
|
+
DIR_PERMISSION = 0755
|
68
34
|
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
end
|
35
|
+
# TODO: buffer_path based on system config
|
36
|
+
desc 'The path where buffer chunks are stored.'
|
37
|
+
config_param :path, :string
|
73
38
|
|
74
|
-
|
75
|
-
|
76
|
-
@path = path
|
77
|
-
end
|
78
|
-
end
|
39
|
+
config_set_default :chunk_limit_size, DEFAULT_CHUNK_LIMIT_SIZE
|
40
|
+
config_set_default :total_limit_size, DEFAULT_TOTAL_LIMIT_SIZE
|
79
41
|
|
80
|
-
|
81
|
-
|
42
|
+
config_param :file_permission, :string, default: nil # '0644'
|
43
|
+
config_param :dir_permission, :string, default: nil # '0755'
|
82
44
|
|
83
|
-
|
45
|
+
##TODO: Buffer plugin cannot handle symlinks because new API @stage has many writing buffer chunks
|
46
|
+
## re-implement this feature on out_file, w/ enqueue_chunk(or generate_chunk) hook + chunk.path
|
47
|
+
# attr_accessor :symlink_path
|
84
48
|
|
85
|
-
|
86
|
-
require 'uri'
|
87
|
-
super
|
49
|
+
@@buffer_paths = {}
|
88
50
|
|
89
|
-
|
90
|
-
|
51
|
+
def initialize
|
52
|
+
super
|
53
|
+
@symlink_path = nil
|
54
|
+
end
|
91
55
|
|
92
|
-
|
93
|
-
|
94
|
-
desc 'If true, queued chunks are flushed at shutdown process.'
|
95
|
-
config_param :flush_at_shutdown, :bool, default: false
|
56
|
+
def configure(conf)
|
57
|
+
super
|
96
58
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
59
|
+
type_of_owner = Plugin.lookup_type_from_class(@_owner.class)
|
60
|
+
if @@buffer_paths.has_key?(@path) && !buffer_path_for_test?
|
61
|
+
type_using_this_path = @@buffer_paths[@path]
|
62
|
+
raise ConfigError, "Other '#{type_using_this_path}' plugin already use same buffer path: type = #{type_of_owner}, buffer path = #{@path}"
|
63
|
+
end
|
101
64
|
|
102
|
-
|
103
|
-
|
65
|
+
@@buffer_paths[@path] = type_of_owner
|
66
|
+
|
67
|
+
# TODO: create buffer path with plugin_id, under directory specified by system config
|
68
|
+
if File.exist?(@path)
|
69
|
+
if File.directory?(@path)
|
70
|
+
@path = File.join(@path, 'buffer.*.log')
|
71
|
+
elsif File.basename(@path).include?('.*.')
|
72
|
+
# valid path (buffer.*.log will be ignored)
|
73
|
+
elsif File.basename(@path).end_with?('.*')
|
74
|
+
@path = @path + '.log'
|
75
|
+
else
|
76
|
+
# existing file will be ignored
|
77
|
+
@path = @path + '.*.log'
|
78
|
+
end
|
79
|
+
else # path doesn't exist
|
80
|
+
if File.basename(@path).include?('.*.')
|
81
|
+
# valid path
|
82
|
+
elsif File.basename(@path).end_with?('.*')
|
83
|
+
@path = @path + '.log'
|
84
|
+
else
|
85
|
+
# path is handled as directory, and it will be created at #start
|
86
|
+
@path = File.join(@path, 'buffer.*.log')
|
87
|
+
end
|
88
|
+
end
|
104
89
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
@@buffer_paths[@buffer_path] = conf['@type'] || conf['type']
|
90
|
+
unless @dir_permission
|
91
|
+
@dir_permission = system_config.dir_permission || DIR_PERMISSION
|
92
|
+
end
|
109
93
|
end
|
110
94
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
95
|
+
def buffer_path_for_test?
|
96
|
+
caller_locations.each do |location|
|
97
|
+
# Thread::Backtrace::Location#path returns base filename or absolute path.
|
98
|
+
# #absolute_path returns absolute_path always.
|
99
|
+
# https://bugs.ruby-lang.org/issues/12159
|
100
|
+
if location.absolute_path =~ /\/test_[^\/]+\.rb$/ # location.path =~ /test_.+\.rb$/
|
101
|
+
return true
|
102
|
+
end
|
103
|
+
end
|
104
|
+
false
|
117
105
|
end
|
118
106
|
|
119
|
-
|
120
|
-
|
121
|
-
def start
|
122
|
-
FileUtils.mkdir_p File.dirname(@buffer_path_prefix + "path"), mode: DEFAULT_DIR_PERMISSION
|
123
|
-
super
|
124
|
-
end
|
107
|
+
def start
|
108
|
+
FileUtils.mkdir_p File.dirname(@path), mode: @dir_permission
|
125
109
|
|
126
|
-
|
127
|
-
|
128
|
-
PATH_MATCH = /^([-_.%0-9a-zA-Z]*)\.(b|q)([0-9a-fA-F]{1,32})$/
|
110
|
+
super
|
111
|
+
end
|
129
112
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
unique_id = tsuffix_to_unique_id(tsuffix)
|
134
|
-
FileBufferChunk.new(key, path, unique_id, "a+", @symlink_path)
|
135
|
-
end
|
113
|
+
def persistent?
|
114
|
+
true
|
115
|
+
end
|
136
116
|
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
elsif bq == 'q'
|
154
|
-
chunk = FileBufferChunk.new(key, path, unique_id, "r")
|
155
|
-
queues << [timestamp, chunk]
|
117
|
+
def resume
|
118
|
+
stage = {}
|
119
|
+
queue = []
|
120
|
+
|
121
|
+
Dir.glob(@path) do |path|
|
122
|
+
m = new_metadata() # this metadata will be overwritten by resuming .meta file content
|
123
|
+
# so it should not added into @metadata_list for now
|
124
|
+
mode = Fluent::Plugin::Buffer::FileChunk.assume_chunk_state(path)
|
125
|
+
chunk = Fluent::Plugin::Buffer::FileChunk.new(m, path, mode) # file chunk resumes contents of metadata
|
126
|
+
case chunk.state
|
127
|
+
when :staged
|
128
|
+
stage[chunk.metadata] = chunk
|
129
|
+
when :queued
|
130
|
+
queue << chunk
|
131
|
+
else
|
132
|
+
raise "BUG: unexpected chunk state '#{chunk.state}' for path '#{path}'"
|
156
133
|
end
|
157
134
|
end
|
158
|
-
}
|
159
|
-
|
160
|
-
map = {}
|
161
|
-
maps.sort_by {|(timestamp,chunk)|
|
162
|
-
timestamp
|
163
|
-
}.each {|(timestamp,chunk)|
|
164
|
-
map[chunk.key] = chunk
|
165
|
-
}
|
166
|
-
|
167
|
-
queue = queues.sort_by {|(timestamp,chunk)|
|
168
|
-
timestamp
|
169
|
-
}.map {|(timestamp,chunk)|
|
170
|
-
chunk
|
171
|
-
}
|
172
|
-
|
173
|
-
return queue, map
|
174
|
-
end
|
175
|
-
|
176
|
-
def chunk_identifier_in_path(path)
|
177
|
-
pos_after_prefix = @buffer_path_prefix.length
|
178
|
-
pos_before_suffix = @buffer_path_suffix.length + 1 # from tail of path
|
179
|
-
|
180
|
-
path.slice(pos_after_prefix..-pos_before_suffix)
|
181
|
-
end
|
182
|
-
|
183
|
-
def enqueue(chunk)
|
184
|
-
path = chunk.path
|
185
|
-
identifier_part = chunk_identifier_in_path(path)
|
186
135
|
|
187
|
-
|
188
|
-
encoded_key = m ? m[1] : ""
|
189
|
-
tsuffix = m[3]
|
190
|
-
npath = "#{@buffer_path_prefix}#{encoded_key}.q#{tsuffix}#{@buffer_path_suffix}"
|
136
|
+
queue.sort_by!{ |chunk| chunk.modified_at }
|
191
137
|
|
192
|
-
|
193
|
-
|
138
|
+
return stage, queue
|
139
|
+
end
|
194
140
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
while pop(out)
|
202
|
-
end
|
141
|
+
def generate_chunk(metadata)
|
142
|
+
# FileChunk generates real path with unique_id
|
143
|
+
if @file_permission
|
144
|
+
Fluent::Plugin::Buffer::FileChunk.new(metadata, @path, :create, perm: @file_permission)
|
145
|
+
else
|
146
|
+
Fluent::Plugin::Buffer::FileChunk.new(metadata, @path, :create)
|
203
147
|
end
|
204
148
|
end
|
205
149
|
end
|
206
|
-
|
207
|
-
private
|
208
|
-
|
209
|
-
# Dots are separator for many cases:
|
210
|
-
# we should have to escape dots in keys...
|
211
|
-
def encode_key(key)
|
212
|
-
@uri_parser.escape(key, /[^-_.a-zA-Z0-9]/n) # //n switch means explicit 'ASCII-8BIT' pattern
|
213
|
-
end
|
214
|
-
|
215
|
-
def decode_key(encoded_key)
|
216
|
-
@uri_parser.unescape(encoded_key)
|
217
|
-
end
|
218
|
-
|
219
|
-
def make_path(encoded_key, bq)
|
220
|
-
now = Time.now.utc
|
221
|
-
timestamp = ((now.to_i * 1000 * 1000 + now.usec) << 12 | rand(0xfff))
|
222
|
-
tsuffix = timestamp.to_s(16)
|
223
|
-
path = "#{@buffer_path_prefix}#{encoded_key}.#{bq}#{tsuffix}#{@buffer_path_suffix}"
|
224
|
-
return path, tsuffix
|
225
|
-
end
|
226
|
-
|
227
|
-
def tsuffix_to_unique_id(tsuffix)
|
228
|
-
# why *2 ? frsyuki said that I forgot why completely.
|
229
|
-
tsuffix.scan(/../).map {|x| x.to_i(16) }.pack('C*') * 2
|
230
|
-
end
|
231
150
|
end
|
232
151
|
end
|
@@ -14,104 +14,21 @@
|
|
14
14
|
# limitations under the License.
|
15
15
|
#
|
16
16
|
|
17
|
-
require '
|
18
|
-
|
19
|
-
require 'fluent/engine'
|
20
|
-
require 'fluent/plugin'
|
21
|
-
require 'fluent/buffer'
|
17
|
+
require 'fluent/plugin/buffer'
|
18
|
+
require 'fluent/plugin/buffer/memory_chunk'
|
22
19
|
|
23
20
|
module Fluent
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
@data.force_encoding('ASCII-8BIT')
|
28
|
-
now = Time.now.utc
|
29
|
-
u1 = ((now.to_i*1000*1000+now.usec) << 12 | rand(0xfff))
|
30
|
-
@unique_id = [u1 >> 32, u1 & 0xffffffff, rand(0xffffffff), rand(0xffffffff)].pack('NNNN')
|
31
|
-
super(key)
|
32
|
-
end
|
33
|
-
|
34
|
-
attr_reader :unique_id
|
35
|
-
|
36
|
-
def <<(data)
|
37
|
-
data.force_encoding('ASCII-8BIT')
|
38
|
-
@data << data
|
39
|
-
end
|
40
|
-
|
41
|
-
def size
|
42
|
-
@data.bytesize
|
43
|
-
end
|
44
|
-
|
45
|
-
def close
|
46
|
-
end
|
47
|
-
|
48
|
-
def purge
|
49
|
-
end
|
50
|
-
|
51
|
-
def read
|
52
|
-
@data
|
53
|
-
end
|
54
|
-
|
55
|
-
def open(&block)
|
56
|
-
StringIO.open(@data, &block)
|
57
|
-
end
|
58
|
-
|
59
|
-
# optimize
|
60
|
-
def write_to(io)
|
61
|
-
io.write @data
|
62
|
-
end
|
21
|
+
module Plugin
|
22
|
+
class MemoryBuffer < Fluent::Plugin::Buffer
|
23
|
+
Plugin.register_buffer('memory', self)
|
63
24
|
|
64
|
-
|
65
|
-
|
66
|
-
u = Fluent::Engine.msgpack_factory.unpacker
|
67
|
-
u.feed_each(@data, &block)
|
68
|
-
end
|
69
|
-
end
|
70
|
-
|
71
|
-
|
72
|
-
class MemoryBuffer < BasicBuffer
|
73
|
-
Plugin.register_buffer('memory', self)
|
74
|
-
|
75
|
-
def initialize
|
76
|
-
super
|
77
|
-
end
|
78
|
-
|
79
|
-
desc 'If true, queued chunks are flushed at shutdown process. Otherwise queued chunks are discarded'
|
80
|
-
config_param :flush_at_shutdown, :bool, default: true
|
81
|
-
# Overwrite default BasicBuffer#buffer_queue_limit
|
82
|
-
# to limit total memory usage upto 512MB.
|
83
|
-
config_set_default :buffer_queue_limit, 64
|
84
|
-
|
85
|
-
def configure(conf)
|
86
|
-
super
|
87
|
-
|
88
|
-
unless @flush_at_shutdown
|
89
|
-
$log.warn "When flush_at_shutdown is false, buf_memory discards buffered chunks at shutdown."
|
90
|
-
$log.warn "Please confirm 'flush_at_shutdown false' configuration is correct or not."
|
25
|
+
def resume
|
26
|
+
return {}, []
|
91
27
|
end
|
92
|
-
end
|
93
28
|
|
94
|
-
|
95
|
-
|
96
|
-
synchronize do
|
97
|
-
@map.each_key {|key|
|
98
|
-
push(key)
|
99
|
-
}
|
100
|
-
while pop(out)
|
101
|
-
end
|
102
|
-
end
|
29
|
+
def generate_chunk(metadata)
|
30
|
+
Fluent::Plugin::Buffer::MemoryChunk.new(metadata)
|
103
31
|
end
|
104
32
|
end
|
105
|
-
|
106
|
-
def new_chunk(key)
|
107
|
-
MemoryBufferChunk.new(key)
|
108
|
-
end
|
109
|
-
|
110
|
-
def resume
|
111
|
-
return [], {}
|
112
|
-
end
|
113
|
-
|
114
|
-
def enqueue(chunk)
|
115
|
-
end
|
116
33
|
end
|
117
34
|
end
|
@@ -0,0 +1,473 @@
|
|
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 'fluent/plugin/base'
|
18
|
+
require 'fluent/plugin/owned_by_mixin'
|
19
|
+
require 'fluent/unique_id'
|
20
|
+
|
21
|
+
require 'monitor'
|
22
|
+
|
23
|
+
module Fluent
|
24
|
+
module Plugin
|
25
|
+
class Buffer < Base
|
26
|
+
include OwnedByMixin
|
27
|
+
include UniqueId::Mixin
|
28
|
+
include MonitorMixin
|
29
|
+
|
30
|
+
class BufferError < StandardError; end
|
31
|
+
class BufferOverflowError < BufferError; end
|
32
|
+
class BufferChunkOverflowError < BufferError; end # A record size is larger than chunk size limit
|
33
|
+
|
34
|
+
MINIMUM_APPEND_ATTEMPT_RECORDS = 10
|
35
|
+
|
36
|
+
DEFAULT_CHUNK_LIMIT_SIZE = 8 * 1024 * 1024 # 8MB
|
37
|
+
DEFAULT_TOTAL_LIMIT_SIZE = 512 * 1024 * 1024 # 512MB, same with v0.12 (BufferedOutput + buf_memory: 64 x 8MB)
|
38
|
+
|
39
|
+
DEFAULT_CHUNK_FULL_THRESHOLD = 0.95
|
40
|
+
|
41
|
+
configured_in :buffer
|
42
|
+
|
43
|
+
# TODO: system total buffer limit size in bytes by SystemConfig
|
44
|
+
|
45
|
+
config_param :chunk_limit_size, :size, default: DEFAULT_CHUNK_LIMIT_SIZE
|
46
|
+
config_param :total_limit_size, :size, default: DEFAULT_TOTAL_LIMIT_SIZE
|
47
|
+
|
48
|
+
# If user specify this value and (chunk_size * queue_length) is smaller than total_size,
|
49
|
+
# then total_size is automatically configured to that value
|
50
|
+
config_param :queue_length_limit, :integer, default: nil
|
51
|
+
|
52
|
+
# optional new limitations
|
53
|
+
config_param :chunk_records_limit, :integer, default: nil
|
54
|
+
|
55
|
+
# if chunk size (or records) is 95% or more after #write, then that chunk will be enqueued
|
56
|
+
config_param :chunk_full_threshold, :float, default: DEFAULT_CHUNK_FULL_THRESHOLD
|
57
|
+
|
58
|
+
Metadata = Struct.new(:timekey, :tag, :variables)
|
59
|
+
|
60
|
+
# for tests
|
61
|
+
attr_accessor :stage_size, :queue_size
|
62
|
+
attr_reader :stage, :queue, :dequeued, :queued_num
|
63
|
+
|
64
|
+
def initialize
|
65
|
+
super
|
66
|
+
|
67
|
+
@chunk_limit_size = nil
|
68
|
+
@total_limit_size = nil
|
69
|
+
@queue_length_limit = nil
|
70
|
+
@chunk_records_limit = nil
|
71
|
+
|
72
|
+
@stage = {} #=> Hash (metadata -> chunk) : not flushed yet
|
73
|
+
@queue = [] #=> Array (chunks) : already flushed (not written)
|
74
|
+
@dequeued = {} #=> Hash (unique_id -> chunk): already written (not purged)
|
75
|
+
@queued_num = {} # metadata => int (number of queued chunks)
|
76
|
+
|
77
|
+
@stage_size = @queue_size = 0
|
78
|
+
@metadata_list = [] # keys of @stage
|
79
|
+
end
|
80
|
+
|
81
|
+
def persistent?
|
82
|
+
false
|
83
|
+
end
|
84
|
+
|
85
|
+
def configure(conf)
|
86
|
+
super
|
87
|
+
|
88
|
+
unless @queue_length_limit.nil?
|
89
|
+
@total_limit_size = @chunk_limit_size * @queue_length_limit
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def start
|
94
|
+
super
|
95
|
+
|
96
|
+
@stage, @queue = resume
|
97
|
+
@stage.each_pair do |metadata, chunk|
|
98
|
+
@metadata_list << metadata unless @metadata_list.include?(metadata)
|
99
|
+
@stage_size += chunk.bytesize
|
100
|
+
end
|
101
|
+
@queue.each do |chunk|
|
102
|
+
@metadata_list << chunk.metadata unless @metadata_list.include?(chunk.metadata)
|
103
|
+
@queued_num[chunk.metadata] ||= 0
|
104
|
+
@queued_num[chunk.metadata] += 1
|
105
|
+
@queue_size += chunk.bytesize
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def close
|
110
|
+
super
|
111
|
+
synchronize do
|
112
|
+
@dequeued.each_pair do |chunk_id, chunk|
|
113
|
+
chunk.close
|
114
|
+
end
|
115
|
+
until @queue.empty?
|
116
|
+
@queue.shift.close
|
117
|
+
end
|
118
|
+
@stage.each_pair do |metadata, chunk|
|
119
|
+
chunk.close
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def terminate
|
125
|
+
super
|
126
|
+
@dequeued = @stage = @queue = @queued_num = @metadata_list = nil
|
127
|
+
@stage_size = @queue_size = 0
|
128
|
+
end
|
129
|
+
|
130
|
+
def storable?
|
131
|
+
@total_limit_size > @stage_size + @queue_size
|
132
|
+
end
|
133
|
+
|
134
|
+
## TODO: for back pressure feature
|
135
|
+
# def used?(ratio)
|
136
|
+
# @total_size_limit * ratio > @stage_size + @queue_size
|
137
|
+
# end
|
138
|
+
|
139
|
+
def resume
|
140
|
+
# return {}, []
|
141
|
+
raise NotImplementedError, "Implement this method in child class"
|
142
|
+
end
|
143
|
+
|
144
|
+
def generate_chunk(metadata)
|
145
|
+
raise NotImplementedError, "Implement this method in child class"
|
146
|
+
end
|
147
|
+
|
148
|
+
def metadata_list
|
149
|
+
synchronize do
|
150
|
+
@metadata_list.dup
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def new_metadata(timekey: nil, tag: nil, variables: nil)
|
155
|
+
Metadata.new(timekey, tag, variables)
|
156
|
+
end
|
157
|
+
|
158
|
+
def add_metadata(metadata)
|
159
|
+
synchronize do
|
160
|
+
if i = @metadata_list.index(metadata)
|
161
|
+
@metadata_list[i]
|
162
|
+
else
|
163
|
+
@metadata_list << metadata
|
164
|
+
metadata
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def metadata(timekey: nil, tag: nil, variables: nil)
|
170
|
+
meta = new_metadata(timekey: timekey, tag: tag, variables: variables)
|
171
|
+
add_metadata(meta)
|
172
|
+
end
|
173
|
+
|
174
|
+
# metadata MUST have consistent object_id for each variation
|
175
|
+
# data MUST be Array of serialized events
|
176
|
+
# metadata_and_data MUST be a hash of { metadata => data }
|
177
|
+
def write(metadata_and_data, bulk: false, enqueue: false)
|
178
|
+
return if metadata_and_data.size < 1
|
179
|
+
raise BufferOverflowError, "buffer space has too many data" unless storable?
|
180
|
+
|
181
|
+
staged_bytesize = 0
|
182
|
+
operated_chunks = []
|
183
|
+
|
184
|
+
begin
|
185
|
+
metadata_and_data.each do |metadata, data|
|
186
|
+
write_once(metadata, data, bulk: bulk) do |chunk, adding_bytesize|
|
187
|
+
chunk.mon_enter # add lock to prevent to be committed/rollbacked from other threads
|
188
|
+
operated_chunks << chunk
|
189
|
+
staged_bytesize += adding_bytesize
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
return if operated_chunks.empty?
|
194
|
+
|
195
|
+
first_chunk = operated_chunks.shift
|
196
|
+
# Following commits for other chunks also can finish successfully if the first commit operation
|
197
|
+
# finishes without any exceptions.
|
198
|
+
# In most cases, #commit just requires very small disk spaces, so major failure reason are
|
199
|
+
# permission errors, disk failures and other permanent(fatal) errors.
|
200
|
+
begin
|
201
|
+
first_chunk.commit
|
202
|
+
enqueue_chunk(first_chunk.metadata) if enqueue || chunk_size_full?(first_chunk)
|
203
|
+
first_chunk.mon_exit
|
204
|
+
rescue
|
205
|
+
operated_chunks.unshift(first_chunk)
|
206
|
+
raise
|
207
|
+
end
|
208
|
+
|
209
|
+
errors = []
|
210
|
+
# Buffer plugin estimates there's no serious error cause: will commit for all chunks eigher way
|
211
|
+
operated_chunks.each do |chunk|
|
212
|
+
begin
|
213
|
+
chunk.commit
|
214
|
+
enqueue_chunk(chunk.metadata) if enqueue || chunk_size_full?(chunk)
|
215
|
+
chunk.mon_exit
|
216
|
+
rescue => e
|
217
|
+
chunk.rollback
|
218
|
+
chunk.mon_exit
|
219
|
+
errors << e
|
220
|
+
end
|
221
|
+
end
|
222
|
+
operated_chunks.clear if errors.empty?
|
223
|
+
|
224
|
+
@stage_size += staged_bytesize
|
225
|
+
|
226
|
+
if errors.size > 0
|
227
|
+
log.warn "error occurs in committing chunks: only first one raised", errors: errors.map(&:class)
|
228
|
+
raise errors.first
|
229
|
+
end
|
230
|
+
ensure
|
231
|
+
operated_chunks.each do |chunk|
|
232
|
+
chunk.rollback rescue nil # nothing possible to do for #rollback failure
|
233
|
+
chunk.mon_exit rescue nil # this may raise ThreadError for chunks already committed
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def queued_records
|
239
|
+
synchronize { @queue.reduce(0){|r, chunk| r + chunk.size } }
|
240
|
+
end
|
241
|
+
|
242
|
+
def queued?(metadata=nil)
|
243
|
+
synchronize do
|
244
|
+
if metadata
|
245
|
+
n = @queued_num[metadata]
|
246
|
+
n && n.nonzero?
|
247
|
+
else
|
248
|
+
!@queue.empty?
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def enqueue_chunk(metadata)
|
254
|
+
synchronize do
|
255
|
+
chunk = @stage.delete(metadata)
|
256
|
+
return nil unless chunk
|
257
|
+
|
258
|
+
chunk.synchronize do
|
259
|
+
if chunk.empty?
|
260
|
+
chunk.close
|
261
|
+
else
|
262
|
+
@queue << chunk
|
263
|
+
@queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
|
264
|
+
chunk.enqueued! if chunk.respond_to?(:enqueued!)
|
265
|
+
end
|
266
|
+
end
|
267
|
+
bytesize = chunk.bytesize
|
268
|
+
@stage_size -= bytesize
|
269
|
+
@queue_size += bytesize
|
270
|
+
end
|
271
|
+
nil
|
272
|
+
end
|
273
|
+
|
274
|
+
def enqueue_all
|
275
|
+
synchronize do
|
276
|
+
if block_given?
|
277
|
+
@stage.keys.each do |metadata|
|
278
|
+
chunk = @stage[metadata]
|
279
|
+
v = yield metadata, chunk
|
280
|
+
enqueue_chunk(metadata) if v
|
281
|
+
end
|
282
|
+
else
|
283
|
+
@stage.keys.each do |metadata|
|
284
|
+
enqueue_chunk(metadata)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
def dequeue_chunk
|
291
|
+
return nil if @queue.empty?
|
292
|
+
synchronize do
|
293
|
+
chunk = @queue.shift
|
294
|
+
|
295
|
+
# this buffer is dequeued by other thread just before "synchronize" in this thread
|
296
|
+
return nil unless chunk
|
297
|
+
|
298
|
+
@dequeued[chunk.unique_id] = chunk
|
299
|
+
@queued_num[chunk.metadata] -= 1 # BUG if nil, 0 or subzero
|
300
|
+
chunk
|
301
|
+
end
|
302
|
+
end
|
303
|
+
|
304
|
+
def takeback_chunk(chunk_id)
|
305
|
+
synchronize do
|
306
|
+
chunk = @dequeued.delete(chunk_id)
|
307
|
+
return false unless chunk # already purged by other thread
|
308
|
+
@queue.unshift(chunk)
|
309
|
+
@queued_num[chunk.metadata] += 1 # BUG if nil
|
310
|
+
end
|
311
|
+
true
|
312
|
+
end
|
313
|
+
|
314
|
+
def purge_chunk(chunk_id)
|
315
|
+
synchronize do
|
316
|
+
chunk = @dequeued.delete(chunk_id)
|
317
|
+
return nil unless chunk # purged by other threads
|
318
|
+
|
319
|
+
metadata = chunk.metadata
|
320
|
+
begin
|
321
|
+
bytesize = chunk.bytesize
|
322
|
+
chunk.purge
|
323
|
+
@queue_size -= bytesize
|
324
|
+
rescue => e
|
325
|
+
log.error "failed to purge buffer chunk", chunk_id: dump_unique_id_hex(chunk_id), error_class: e.class, error: e
|
326
|
+
end
|
327
|
+
|
328
|
+
if metadata && !@stage[metadata] && (!@queued_num[metadata] || @queued_num[metadata] < 1)
|
329
|
+
@metadata_list.delete(metadata)
|
330
|
+
end
|
331
|
+
end
|
332
|
+
nil
|
333
|
+
end
|
334
|
+
|
335
|
+
def clear_queue!
|
336
|
+
synchronize do
|
337
|
+
until @queue.empty?
|
338
|
+
begin
|
339
|
+
q = @queue.shift
|
340
|
+
log.debug("purging a chunk in queue"){ {id: dump_unique_id_hex(chunk.unique_id), bytesize: chunk.bytesize, size: chunk.size} }
|
341
|
+
q.purge
|
342
|
+
rescue => e
|
343
|
+
log.error "unexpected error while clearing buffer queue", error_class: e.class, error: e
|
344
|
+
end
|
345
|
+
end
|
346
|
+
@queue_size = 0
|
347
|
+
end
|
348
|
+
end
|
349
|
+
|
350
|
+
def chunk_size_over?(chunk)
|
351
|
+
chunk.bytesize > @chunk_limit_size || (@chunk_records_limit && chunk.size > @chunk_records_limit)
|
352
|
+
end
|
353
|
+
|
354
|
+
def chunk_size_full?(chunk)
|
355
|
+
chunk.bytesize >= @chunk_limit_size * @chunk_full_threshold || (@chunk_records_limit && chunk.size >= @chunk_records_limit * @chunk_full_threshold)
|
356
|
+
end
|
357
|
+
|
358
|
+
class ShouldRetry < StandardError; end
|
359
|
+
|
360
|
+
def write_once(metadata, data, bulk: false, &block)
|
361
|
+
return if !bulk && (data.nil? || data.empty?)
|
362
|
+
return if bulk && (data.empty? || data.first.nil? || data.first.empty?)
|
363
|
+
|
364
|
+
stored = false
|
365
|
+
adding_bytesize = nil
|
366
|
+
|
367
|
+
chunk = synchronize { @stage[metadata] ||= generate_chunk(metadata) }
|
368
|
+
enqueue_list = []
|
369
|
+
|
370
|
+
chunk.synchronize do
|
371
|
+
# retry this method if chunk is already queued (between getting chunk and entering critical section)
|
372
|
+
raise ShouldRetry unless chunk.staged?
|
373
|
+
|
374
|
+
empty_chunk = chunk.empty?
|
375
|
+
|
376
|
+
original_bytesize = chunk.bytesize
|
377
|
+
begin
|
378
|
+
if bulk
|
379
|
+
content, size = data
|
380
|
+
chunk.concat(content, size)
|
381
|
+
else
|
382
|
+
chunk.append(data)
|
383
|
+
end
|
384
|
+
adding_bytesize = chunk.bytesize - original_bytesize
|
385
|
+
|
386
|
+
if chunk_size_over?(chunk)
|
387
|
+
if empty_chunk && bulk
|
388
|
+
log.warn "chunk bytes limit exceeds for a bulk event stream: #{bulk.bytesize}bytes"
|
389
|
+
stored = true
|
390
|
+
else
|
391
|
+
chunk.rollback
|
392
|
+
end
|
393
|
+
else
|
394
|
+
stored = true
|
395
|
+
end
|
396
|
+
rescue
|
397
|
+
chunk.rollback
|
398
|
+
raise
|
399
|
+
end
|
400
|
+
|
401
|
+
if stored
|
402
|
+
block.call(chunk, adding_bytesize)
|
403
|
+
elsif bulk
|
404
|
+
# this metadata might be enqueued already by other threads
|
405
|
+
# but #enqueue_chunk does nothing in such case
|
406
|
+
enqueue_list << metadata
|
407
|
+
raise ShouldRetry
|
408
|
+
end
|
409
|
+
end
|
410
|
+
|
411
|
+
unless stored
|
412
|
+
# try step-by-step appending if data can't be stored into existing a chunk in non-bulk mode
|
413
|
+
write_step_by_step(metadata, data, data.size / 3, &block)
|
414
|
+
end
|
415
|
+
rescue ShouldRetry
|
416
|
+
enqueue_list.each do |metadata|
|
417
|
+
enqueue_chunk(metadata)
|
418
|
+
end
|
419
|
+
retry
|
420
|
+
end
|
421
|
+
|
422
|
+
def write_step_by_step(metadata, data, attempt_records, &block)
|
423
|
+
while data.size > 0
|
424
|
+
if attempt_records < MINIMUM_APPEND_ATTEMPT_RECORDS
|
425
|
+
attempt_records = MINIMUM_APPEND_ATTEMPT_RECORDS
|
426
|
+
end
|
427
|
+
|
428
|
+
chunk = synchronize{ @stage[metadata] ||= generate_chunk(metadata) }
|
429
|
+
chunk.synchronize do # critical section for chunk (chunk append/commit/rollback)
|
430
|
+
raise ShouldRetry unless chunk.staged?
|
431
|
+
begin
|
432
|
+
empty_chunk = chunk.empty?
|
433
|
+
original_bytesize = chunk.bytesize
|
434
|
+
|
435
|
+
attempt = data.slice(0, attempt_records)
|
436
|
+
chunk.append(attempt)
|
437
|
+
adding_bytesize = (chunk.bytesize - original_bytesize)
|
438
|
+
|
439
|
+
if chunk_size_over?(chunk)
|
440
|
+
chunk.rollback
|
441
|
+
|
442
|
+
if attempt_records <= MINIMUM_APPEND_ATTEMPT_RECORDS
|
443
|
+
if empty_chunk # record is too large even for empty chunk
|
444
|
+
raise BufferChunkOverflowError, "minimum append butch exceeds chunk bytes limit"
|
445
|
+
end
|
446
|
+
# no more records for this chunk -> enqueue -> to be flushed
|
447
|
+
enqueue_chunk(metadata) # `chunk` will be removed from stage
|
448
|
+
attempt_records = data.size # fresh chunk may have enough space
|
449
|
+
else
|
450
|
+
# whole data can be processed by twice operation
|
451
|
+
# ( by using apttempt /= 2, 3 operations required for odd numbers of data)
|
452
|
+
attempt_records = (attempt_records / 2) + 1
|
453
|
+
end
|
454
|
+
|
455
|
+
next
|
456
|
+
end
|
457
|
+
|
458
|
+
block.call(chunk, adding_bytesize)
|
459
|
+
data.slice!(0, attempt_records)
|
460
|
+
# same attempt size
|
461
|
+
nil # discard return value of data.slice!() immediately
|
462
|
+
rescue
|
463
|
+
chunk.rollback
|
464
|
+
raise
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
rescue ShouldRetry
|
469
|
+
retry
|
470
|
+
end # write_step_by_step
|
471
|
+
end
|
472
|
+
end
|
473
|
+
end
|