fluentd 1.20.0-aarch64-mingw-ucrt

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +116 -0
  3. data/ADOPTERS.md +5 -0
  4. data/AGENTS.md +46 -0
  5. data/AUTHORS +2 -0
  6. data/CHANGELOG.md +3324 -0
  7. data/CONTRIBUTING.md +45 -0
  8. data/GOVERNANCE.md +55 -0
  9. data/GithubWorkflow.md +78 -0
  10. data/LICENSE +202 -0
  11. data/MAINTAINERS.md +19 -0
  12. data/README.md +73 -0
  13. data/Rakefile +89 -0
  14. data/SECURITY.md +47 -0
  15. data/bin/fluent-binlog-reader +7 -0
  16. data/bin/fluent-ca-generate +6 -0
  17. data/bin/fluent-cap-ctl +7 -0
  18. data/bin/fluent-cat +5 -0
  19. data/bin/fluent-ctl +7 -0
  20. data/bin/fluent-debug +5 -0
  21. data/bin/fluent-gem +9 -0
  22. data/bin/fluent-plugin-config-format +5 -0
  23. data/bin/fluent-plugin-generate +5 -0
  24. data/bin/fluentd +15 -0
  25. data/code-of-conduct.md +3 -0
  26. data/docs/SECURITY_AUDIT.pdf +0 -0
  27. data/example/copy_roundrobin.conf +39 -0
  28. data/example/counter.conf +18 -0
  29. data/example/filter_stdout.conf +22 -0
  30. data/example/in_forward.conf +14 -0
  31. data/example/in_forward_client.conf +37 -0
  32. data/example/in_forward_shared_key.conf +15 -0
  33. data/example/in_forward_tls.conf +14 -0
  34. data/example/in_forward_users.conf +24 -0
  35. data/example/in_forward_workers.conf +21 -0
  36. data/example/in_http.conf +16 -0
  37. data/example/in_out_forward.conf +17 -0
  38. data/example/in_sample_blocks.conf +17 -0
  39. data/example/in_sample_with_compression.conf +23 -0
  40. data/example/in_syslog.conf +15 -0
  41. data/example/in_tail.conf +14 -0
  42. data/example/in_tcp.conf +13 -0
  43. data/example/in_udp.conf +13 -0
  44. data/example/logevents.conf +25 -0
  45. data/example/multi_filters.conf +61 -0
  46. data/example/out_copy.conf +20 -0
  47. data/example/out_exec_filter.conf +42 -0
  48. data/example/out_file.conf +13 -0
  49. data/example/out_forward.conf +35 -0
  50. data/example/out_forward_buf_file.conf +23 -0
  51. data/example/out_forward_client.conf +109 -0
  52. data/example/out_forward_heartbeat_none.conf +16 -0
  53. data/example/out_forward_sd.conf +17 -0
  54. data/example/out_forward_shared_key.conf +36 -0
  55. data/example/out_forward_tls.conf +18 -0
  56. data/example/out_forward_users.conf +65 -0
  57. data/example/out_null.conf +36 -0
  58. data/example/sd.yaml +8 -0
  59. data/example/secondary_file.conf +42 -0
  60. data/example/suppress_config_dump.conf +7 -0
  61. data/example/v0_12_filter.conf +78 -0
  62. data/example/v1_literal_example.conf +36 -0
  63. data/example/worker_section.conf +36 -0
  64. data/fluent.conf +139 -0
  65. data/lib/fluent/agent.rb +168 -0
  66. data/lib/fluent/capability.rb +87 -0
  67. data/lib/fluent/clock.rb +66 -0
  68. data/lib/fluent/command/binlog_reader.rb +244 -0
  69. data/lib/fluent/command/bundler_injection.rb +45 -0
  70. data/lib/fluent/command/ca_generate.rb +184 -0
  71. data/lib/fluent/command/cap_ctl.rb +174 -0
  72. data/lib/fluent/command/cat.rb +365 -0
  73. data/lib/fluent/command/ctl.rb +180 -0
  74. data/lib/fluent/command/debug.rb +103 -0
  75. data/lib/fluent/command/fluentd.rb +385 -0
  76. data/lib/fluent/command/plugin_config_formatter.rb +308 -0
  77. data/lib/fluent/command/plugin_generator.rb +365 -0
  78. data/lib/fluent/compat/call_super_mixin.rb +76 -0
  79. data/lib/fluent/compat/detach_process_mixin.rb +33 -0
  80. data/lib/fluent/compat/exec_util.rb +144 -0
  81. data/lib/fluent/compat/file_util.rb +54 -0
  82. data/lib/fluent/compat/filter.rb +68 -0
  83. data/lib/fluent/compat/formatter.rb +117 -0
  84. data/lib/fluent/compat/formatter_utils.rb +85 -0
  85. data/lib/fluent/compat/handle_tag_and_time_mixin.rb +62 -0
  86. data/lib/fluent/compat/handle_tag_name_mixin.rb +53 -0
  87. data/lib/fluent/compat/input.rb +49 -0
  88. data/lib/fluent/compat/output.rb +721 -0
  89. data/lib/fluent/compat/output_chain.rb +60 -0
  90. data/lib/fluent/compat/parser.rb +310 -0
  91. data/lib/fluent/compat/parser_utils.rb +40 -0
  92. data/lib/fluent/compat/propagate_default.rb +62 -0
  93. data/lib/fluent/compat/record_filter_mixin.rb +34 -0
  94. data/lib/fluent/compat/set_tag_key_mixin.rb +50 -0
  95. data/lib/fluent/compat/set_time_key_mixin.rb +69 -0
  96. data/lib/fluent/compat/socket_util.rb +165 -0
  97. data/lib/fluent/compat/string_util.rb +34 -0
  98. data/lib/fluent/compat/structured_format_mixin.rb +26 -0
  99. data/lib/fluent/compat/type_converter.rb +90 -0
  100. data/lib/fluent/config/basic_parser.rb +122 -0
  101. data/lib/fluent/config/configure_proxy.rb +424 -0
  102. data/lib/fluent/config/dsl.rb +152 -0
  103. data/lib/fluent/config/element.rb +265 -0
  104. data/lib/fluent/config/error.rb +44 -0
  105. data/lib/fluent/config/literal_parser.rb +305 -0
  106. data/lib/fluent/config/parser.rb +119 -0
  107. data/lib/fluent/config/section.rb +272 -0
  108. data/lib/fluent/config/types.rb +257 -0
  109. data/lib/fluent/config/v1_parser.rb +200 -0
  110. data/lib/fluent/config/yaml_parser/fluent_value.rb +47 -0
  111. data/lib/fluent/config/yaml_parser/loader.rb +124 -0
  112. data/lib/fluent/config/yaml_parser/parser.rb +179 -0
  113. data/lib/fluent/config/yaml_parser/section_builder.rb +107 -0
  114. data/lib/fluent/config/yaml_parser.rb +56 -0
  115. data/lib/fluent/config.rb +89 -0
  116. data/lib/fluent/configurable.rb +202 -0
  117. data/lib/fluent/counter/base_socket.rb +44 -0
  118. data/lib/fluent/counter/client.rb +297 -0
  119. data/lib/fluent/counter/error.rb +86 -0
  120. data/lib/fluent/counter/mutex_hash.rb +163 -0
  121. data/lib/fluent/counter/server.rb +273 -0
  122. data/lib/fluent/counter/store.rb +205 -0
  123. data/lib/fluent/counter/validator.rb +145 -0
  124. data/lib/fluent/counter.rb +23 -0
  125. data/lib/fluent/daemon.rb +14 -0
  126. data/lib/fluent/daemonizer.rb +88 -0
  127. data/lib/fluent/engine.rb +269 -0
  128. data/lib/fluent/env.rb +45 -0
  129. data/lib/fluent/error.rb +37 -0
  130. data/lib/fluent/event.rb +369 -0
  131. data/lib/fluent/event_router.rb +315 -0
  132. data/lib/fluent/ext_monitor_require.rb +28 -0
  133. data/lib/fluent/file_wrapper.rb +137 -0
  134. data/lib/fluent/filter.rb +21 -0
  135. data/lib/fluent/fluent_log_event_router.rb +139 -0
  136. data/lib/fluent/formatter.rb +23 -0
  137. data/lib/fluent/input.rb +21 -0
  138. data/lib/fluent/label.rb +46 -0
  139. data/lib/fluent/load.rb +33 -0
  140. data/lib/fluent/log/console_adapter.rb +66 -0
  141. data/lib/fluent/log.rb +775 -0
  142. data/lib/fluent/match.rb +187 -0
  143. data/lib/fluent/mixin.rb +31 -0
  144. data/lib/fluent/msgpack_factory.rb +111 -0
  145. data/lib/fluent/oj_options.rb +61 -0
  146. data/lib/fluent/output.rb +29 -0
  147. data/lib/fluent/output_chain.rb +23 -0
  148. data/lib/fluent/parser.rb +23 -0
  149. data/lib/fluent/plugin/bare_output.rb +88 -0
  150. data/lib/fluent/plugin/base.rb +219 -0
  151. data/lib/fluent/plugin/buf_file.rb +256 -0
  152. data/lib/fluent/plugin/buf_file_single.rb +272 -0
  153. data/lib/fluent/plugin/buf_memory.rb +34 -0
  154. data/lib/fluent/plugin/buffer/chunk.rb +313 -0
  155. data/lib/fluent/plugin/buffer/file_chunk.rb +432 -0
  156. data/lib/fluent/plugin/buffer/file_single_chunk.rb +325 -0
  157. data/lib/fluent/plugin/buffer/memory_chunk.rb +91 -0
  158. data/lib/fluent/plugin/buffer.rb +1063 -0
  159. data/lib/fluent/plugin/compressable.rb +87 -0
  160. data/lib/fluent/plugin/exec_util.rb +22 -0
  161. data/lib/fluent/plugin/extractor.rb +132 -0
  162. data/lib/fluent/plugin/file_util.rb +22 -0
  163. data/lib/fluent/plugin/filter.rb +119 -0
  164. data/lib/fluent/plugin/filter_grep.rb +189 -0
  165. data/lib/fluent/plugin/filter_parser.rb +124 -0
  166. data/lib/fluent/plugin/filter_record_transformer.rb +325 -0
  167. data/lib/fluent/plugin/filter_stdout.rb +53 -0
  168. data/lib/fluent/plugin/formatter.rb +75 -0
  169. data/lib/fluent/plugin/formatter_csv.rb +92 -0
  170. data/lib/fluent/plugin/formatter_hash.rb +35 -0
  171. data/lib/fluent/plugin/formatter_json.rb +62 -0
  172. data/lib/fluent/plugin/formatter_ltsv.rb +44 -0
  173. data/lib/fluent/plugin/formatter_msgpack.rb +33 -0
  174. data/lib/fluent/plugin/formatter_out_file.rb +55 -0
  175. data/lib/fluent/plugin/formatter_single_value.rb +36 -0
  176. data/lib/fluent/plugin/formatter_stdout.rb +76 -0
  177. data/lib/fluent/plugin/formatter_tsv.rb +40 -0
  178. data/lib/fluent/plugin/in_debug_agent.rb +71 -0
  179. data/lib/fluent/plugin/in_dummy.rb +18 -0
  180. data/lib/fluent/plugin/in_exec.rb +124 -0
  181. data/lib/fluent/plugin/in_forward.rb +489 -0
  182. data/lib/fluent/plugin/in_gc_stat.rb +72 -0
  183. data/lib/fluent/plugin/in_http.rb +737 -0
  184. data/lib/fluent/plugin/in_monitor_agent.rb +406 -0
  185. data/lib/fluent/plugin/in_object_space.rb +93 -0
  186. data/lib/fluent/plugin/in_sample.rb +148 -0
  187. data/lib/fluent/plugin/in_syslog.rb +346 -0
  188. data/lib/fluent/plugin/in_tail/compatibility.rb +111 -0
  189. data/lib/fluent/plugin/in_tail/dir_watcher.rb +713 -0
  190. data/lib/fluent/plugin/in_tail/group_watch.rb +204 -0
  191. data/lib/fluent/plugin/in_tail/io_handler.rb +391 -0
  192. data/lib/fluent/plugin/in_tail/line_feeder.rb +230 -0
  193. data/lib/fluent/plugin/in_tail/position_file.rb +269 -0
  194. data/lib/fluent/plugin/in_tail/stat_watcher.rb +52 -0
  195. data/lib/fluent/plugin/in_tail/tail_watcher.rb +276 -0
  196. data/lib/fluent/plugin/in_tail/worker_pool.rb +298 -0
  197. data/lib/fluent/plugin/in_tail.rb +917 -0
  198. data/lib/fluent/plugin/in_tcp.rb +230 -0
  199. data/lib/fluent/plugin/in_udp.rb +101 -0
  200. data/lib/fluent/plugin/in_unix.rb +197 -0
  201. data/lib/fluent/plugin/input.rb +71 -0
  202. data/lib/fluent/plugin/metrics.rb +119 -0
  203. data/lib/fluent/plugin/metrics_local.rb +96 -0
  204. data/lib/fluent/plugin/multi_output.rb +179 -0
  205. data/lib/fluent/plugin/out_buffer.rb +40 -0
  206. data/lib/fluent/plugin/out_copy.rb +120 -0
  207. data/lib/fluent/plugin/out_exec.rb +105 -0
  208. data/lib/fluent/plugin/out_exec_filter.rb +319 -0
  209. data/lib/fluent/plugin/out_file.rb +371 -0
  210. data/lib/fluent/plugin/out_forward/ack_handler.rb +176 -0
  211. data/lib/fluent/plugin/out_forward/connection_manager.rb +131 -0
  212. data/lib/fluent/plugin/out_forward/error.rb +28 -0
  213. data/lib/fluent/plugin/out_forward/failure_detector.rb +84 -0
  214. data/lib/fluent/plugin/out_forward/handshake_protocol.rb +125 -0
  215. data/lib/fluent/plugin/out_forward/load_balancer.rb +114 -0
  216. data/lib/fluent/plugin/out_forward/socket_cache.rb +177 -0
  217. data/lib/fluent/plugin/out_forward.rb +865 -0
  218. data/lib/fluent/plugin/out_http.rb +419 -0
  219. data/lib/fluent/plugin/out_null.rb +74 -0
  220. data/lib/fluent/plugin/out_relabel.rb +32 -0
  221. data/lib/fluent/plugin/out_roundrobin.rb +84 -0
  222. data/lib/fluent/plugin/out_secondary_file.rb +148 -0
  223. data/lib/fluent/plugin/out_stdout.rb +81 -0
  224. data/lib/fluent/plugin/out_stream.rb +130 -0
  225. data/lib/fluent/plugin/output.rb +1688 -0
  226. data/lib/fluent/plugin/owned_by_mixin.rb +41 -0
  227. data/lib/fluent/plugin/parser.rb +274 -0
  228. data/lib/fluent/plugin/parser_apache.rb +28 -0
  229. data/lib/fluent/plugin/parser_apache2.rb +88 -0
  230. data/lib/fluent/plugin/parser_apache_error.rb +26 -0
  231. data/lib/fluent/plugin/parser_csv.rb +124 -0
  232. data/lib/fluent/plugin/parser_json.rb +118 -0
  233. data/lib/fluent/plugin/parser_ltsv.rb +49 -0
  234. data/lib/fluent/plugin/parser_msgpack.rb +72 -0
  235. data/lib/fluent/plugin/parser_multiline.rb +152 -0
  236. data/lib/fluent/plugin/parser_nginx.rb +28 -0
  237. data/lib/fluent/plugin/parser_none.rb +36 -0
  238. data/lib/fluent/plugin/parser_regexp.rb +68 -0
  239. data/lib/fluent/plugin/parser_syslog.rb +543 -0
  240. data/lib/fluent/plugin/parser_tsv.rb +47 -0
  241. data/lib/fluent/plugin/sd_file.rb +157 -0
  242. data/lib/fluent/plugin/sd_srv.rb +135 -0
  243. data/lib/fluent/plugin/sd_static.rb +58 -0
  244. data/lib/fluent/plugin/service_discovery.rb +65 -0
  245. data/lib/fluent/plugin/socket_util.rb +22 -0
  246. data/lib/fluent/plugin/storage.rb +84 -0
  247. data/lib/fluent/plugin/storage_local.rb +166 -0
  248. data/lib/fluent/plugin/string_util.rb +22 -0
  249. data/lib/fluent/plugin.rb +206 -0
  250. data/lib/fluent/plugin_helper/cert_option.rb +199 -0
  251. data/lib/fluent/plugin_helper/child_process.rb +369 -0
  252. data/lib/fluent/plugin_helper/compat_parameters.rb +343 -0
  253. data/lib/fluent/plugin_helper/counter.rb +51 -0
  254. data/lib/fluent/plugin_helper/event_emitter.rb +112 -0
  255. data/lib/fluent/plugin_helper/event_loop.rb +170 -0
  256. data/lib/fluent/plugin_helper/extract.rb +104 -0
  257. data/lib/fluent/plugin_helper/formatter.rb +147 -0
  258. data/lib/fluent/plugin_helper/http_server/app.rb +81 -0
  259. data/lib/fluent/plugin_helper/http_server/methods.rb +35 -0
  260. data/lib/fluent/plugin_helper/http_server/request.rb +53 -0
  261. data/lib/fluent/plugin_helper/http_server/router.rb +54 -0
  262. data/lib/fluent/plugin_helper/http_server/server.rb +116 -0
  263. data/lib/fluent/plugin_helper/http_server/ssl_context_builder.rb +41 -0
  264. data/lib/fluent/plugin_helper/http_server.rb +129 -0
  265. data/lib/fluent/plugin_helper/inject.rb +154 -0
  266. data/lib/fluent/plugin_helper/metrics.rb +136 -0
  267. data/lib/fluent/plugin_helper/parser.rb +147 -0
  268. data/lib/fluent/plugin_helper/record_accessor.rb +207 -0
  269. data/lib/fluent/plugin_helper/retry_state.rb +219 -0
  270. data/lib/fluent/plugin_helper/server.rb +840 -0
  271. data/lib/fluent/plugin_helper/service_discovery/manager.rb +146 -0
  272. data/lib/fluent/plugin_helper/service_discovery/round_robin_balancer.rb +43 -0
  273. data/lib/fluent/plugin_helper/service_discovery.rb +125 -0
  274. data/lib/fluent/plugin_helper/socket.rb +288 -0
  275. data/lib/fluent/plugin_helper/socket_option.rb +98 -0
  276. data/lib/fluent/plugin_helper/storage.rb +349 -0
  277. data/lib/fluent/plugin_helper/thread.rb +180 -0
  278. data/lib/fluent/plugin_helper/timer.rb +92 -0
  279. data/lib/fluent/plugin_helper.rb +75 -0
  280. data/lib/fluent/plugin_id.rb +93 -0
  281. data/lib/fluent/process.rb +22 -0
  282. data/lib/fluent/registry.rb +117 -0
  283. data/lib/fluent/root_agent.rb +468 -0
  284. data/lib/fluent/rpc.rb +95 -0
  285. data/lib/fluent/source_only_buffer_agent.rb +102 -0
  286. data/lib/fluent/static_config_analysis.rb +195 -0
  287. data/lib/fluent/supervisor.rb +1347 -0
  288. data/lib/fluent/system_config.rb +213 -0
  289. data/lib/fluent/test/base.rb +83 -0
  290. data/lib/fluent/test/driver/base.rb +231 -0
  291. data/lib/fluent/test/driver/base_owned.rb +83 -0
  292. data/lib/fluent/test/driver/base_owner.rb +135 -0
  293. data/lib/fluent/test/driver/event_feeder.rb +98 -0
  294. data/lib/fluent/test/driver/filter.rb +61 -0
  295. data/lib/fluent/test/driver/formatter.rb +30 -0
  296. data/lib/fluent/test/driver/input.rb +31 -0
  297. data/lib/fluent/test/driver/multi_output.rb +53 -0
  298. data/lib/fluent/test/driver/output.rb +102 -0
  299. data/lib/fluent/test/driver/parser.rb +30 -0
  300. data/lib/fluent/test/driver/storage.rb +30 -0
  301. data/lib/fluent/test/driver/test_event_router.rb +45 -0
  302. data/lib/fluent/test/filter_test.rb +77 -0
  303. data/lib/fluent/test/formatter_test.rb +65 -0
  304. data/lib/fluent/test/helpers.rb +138 -0
  305. data/lib/fluent/test/input_test.rb +174 -0
  306. data/lib/fluent/test/log.rb +79 -0
  307. data/lib/fluent/test/output_test.rb +156 -0
  308. data/lib/fluent/test/parser_test.rb +70 -0
  309. data/lib/fluent/test/startup_shutdown.rb +44 -0
  310. data/lib/fluent/test.rb +61 -0
  311. data/lib/fluent/time.rb +513 -0
  312. data/lib/fluent/timezone.rb +171 -0
  313. data/lib/fluent/tls.rb +105 -0
  314. data/lib/fluent/unique_id.rb +39 -0
  315. data/lib/fluent/variable_store.rb +40 -0
  316. data/lib/fluent/version.rb +21 -0
  317. data/lib/fluent/win32api.rb +38 -0
  318. data/lib/fluent/winsvc.rb +130 -0
  319. data/templates/new_gem/.gitignore +8 -0
  320. data/templates/new_gem/Gemfile +3 -0
  321. data/templates/new_gem/README.md.erb +43 -0
  322. data/templates/new_gem/Rakefile +13 -0
  323. data/templates/new_gem/fluent-plugin.gemspec.erb +28 -0
  324. data/templates/new_gem/lib/fluent/plugin/filter.rb.erb +14 -0
  325. data/templates/new_gem/lib/fluent/plugin/formatter.rb.erb +14 -0
  326. data/templates/new_gem/lib/fluent/plugin/input.rb.erb +11 -0
  327. data/templates/new_gem/lib/fluent/plugin/output.rb.erb +11 -0
  328. data/templates/new_gem/lib/fluent/plugin/parser.rb.erb +15 -0
  329. data/templates/new_gem/lib/fluent/plugin/storage.rb.erb +40 -0
  330. data/templates/new_gem/test/helper.rb.erb +7 -0
  331. data/templates/new_gem/test/plugin/test_filter.rb.erb +18 -0
  332. data/templates/new_gem/test/plugin/test_formatter.rb.erb +18 -0
  333. data/templates/new_gem/test/plugin/test_input.rb.erb +18 -0
  334. data/templates/new_gem/test/plugin/test_output.rb.erb +18 -0
  335. data/templates/new_gem/test/plugin/test_parser.rb.erb +18 -0
  336. data/templates/new_gem/test/plugin/test_storage.rb.erb +18 -0
  337. data/templates/plugin_config_formatter/param.md-compact.erb +25 -0
  338. data/templates/plugin_config_formatter/param.md-table.erb +10 -0
  339. data/templates/plugin_config_formatter/param.md.erb +34 -0
  340. data/templates/plugin_config_formatter/section.md.erb +12 -0
  341. metadata +996 -0
@@ -0,0 +1,1063 @@
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/plugin_id'
20
+ require 'fluent/plugin_helper'
21
+ require 'fluent/unique_id'
22
+ require 'fluent/ext_monitor_require'
23
+
24
+ module Fluent
25
+ module Plugin
26
+ class Buffer < Base
27
+ include OwnedByMixin
28
+ include UniqueId::Mixin
29
+ include PluginId
30
+ include MonitorMixin
31
+ include PluginHelper::Mixin # for metrics
32
+
33
+ class BufferError < StandardError; end
34
+ class BufferOverflowError < BufferError; end
35
+ class BufferChunkOverflowError < BufferError; end # A record size is larger than chunk size limit
36
+
37
+ MINIMUM_APPEND_ATTEMPT_RECORDS = 10
38
+
39
+ DEFAULT_CHUNK_LIMIT_SIZE = 8 * 1024 * 1024 # 8MB
40
+ DEFAULT_TOTAL_LIMIT_SIZE = 512 * 1024 * 1024 # 512MB, same with v0.12 (BufferedOutput + buf_memory: 64 x 8MB)
41
+
42
+ DEFAULT_CHUNK_FULL_THRESHOLD = 0.95
43
+
44
+ configured_in :buffer
45
+
46
+ helpers_internal :metrics
47
+
48
+ # TODO: system total buffer limit size in bytes by SystemConfig
49
+
50
+ config_param :chunk_limit_size, :size, default: DEFAULT_CHUNK_LIMIT_SIZE
51
+ config_param :total_limit_size, :size, default: DEFAULT_TOTAL_LIMIT_SIZE
52
+
53
+ # If user specify this value and (chunk_size * queue_length) is smaller than total_size,
54
+ # then total_size is automatically configured to that value
55
+ config_param :queue_limit_length, :integer, default: nil
56
+
57
+ # optional new limitations
58
+ config_param :chunk_limit_records, :integer, default: nil
59
+
60
+ # if chunk size (or records) is 95% or more after #write, then that chunk will be enqueued
61
+ config_param :chunk_full_threshold, :float, default: DEFAULT_CHUNK_FULL_THRESHOLD
62
+
63
+ desc 'The max number of queued chunks.'
64
+ config_param :queued_chunks_limit_size, :integer, default: nil
65
+
66
+ desc 'Compress buffered data.'
67
+ config_param :compress, :enum, list: [:text, :gzip, :zstd], default: :text
68
+
69
+ desc 'The size limit of the decompressed element.'
70
+ config_param :decompression_size_limit, :size, default: Compressable::DEFAULT_DECOMPRESSION_SIZE_LIMIT
71
+
72
+ desc 'If true, chunks are thrown away when unrecoverable error happens'
73
+ config_param :disable_chunk_backup, :bool, default: false
74
+
75
+ desc 'The approximate size limit of the evacuated chunk files in the evacuation directory. When the retry limit is reached, queued chunks that fit within the remaining capacity are evacuated, and the others are purged without being evacuated. The total size may exceed this limit due to chunk metadata or concurrent evacuation by multiple workers. Set to 0 to disable evacuation. nil (the default) means no limit.'
76
+ config_param :evacuate_limit_size, :size, default: nil
77
+
78
+ Metadata = Struct.new(:timekey, :tag, :variables, :seq) do
79
+ def initialize(timekey, tag, variables)
80
+ super(timekey, tag, variables, 0)
81
+ end
82
+
83
+ def dup_next
84
+ m = dup
85
+ m.seq = seq + 1
86
+ m
87
+ end
88
+
89
+ def empty?
90
+ timekey.nil? && tag.nil? && variables.nil?
91
+ end
92
+
93
+ def cmp_variables(v1, v2)
94
+ if v1.nil? && v2.nil?
95
+ return 0
96
+ elsif v1.nil? # v2 is non-nil
97
+ return -1
98
+ elsif v2.nil? # v1 is non-nil
99
+ return 1
100
+ end
101
+ # both of v1 and v2 are non-nil
102
+ v1_sorted_keys = v1.keys.sort
103
+ v2_sorted_keys = v2.keys.sort
104
+ if v1_sorted_keys != v2_sorted_keys
105
+ if v1_sorted_keys.size == v2_sorted_keys.size
106
+ v1_sorted_keys <=> v2_sorted_keys
107
+ else
108
+ v1_sorted_keys.size <=> v2_sorted_keys.size
109
+ end
110
+ else
111
+ v1_sorted_keys.each do |k|
112
+ a = v1[k]
113
+ b = v2[k]
114
+ if a && b && a != b
115
+ return a <=> b
116
+ elsif a && b || (!a && !b) # same value (including both are nil)
117
+ next
118
+ elsif a # b is nil
119
+ return 1
120
+ else # a is nil (but b is non-nil)
121
+ return -1
122
+ end
123
+ end
124
+
125
+ 0
126
+ end
127
+ end
128
+
129
+ def <=>(o)
130
+ timekey2 = o.timekey
131
+ tag2 = o.tag
132
+ variables2 = o.variables
133
+ if (!!timekey ^ !!timekey2) || (!!tag ^ !!tag2) || (!!variables ^ !!variables2)
134
+ # One has value in a field, but another doesn't have value in same field
135
+ # This case occurs very rarely
136
+ if timekey == timekey2 # including the case of nil == nil
137
+ if tag == tag2
138
+ cmp_variables(variables, variables2)
139
+ elsif tag.nil?
140
+ -1
141
+ elsif tag2.nil?
142
+ 1
143
+ else
144
+ tag <=> tag2
145
+ end
146
+ elsif timekey.nil?
147
+ -1
148
+ elsif timekey2.nil?
149
+ 1
150
+ else
151
+ timekey <=> timekey2
152
+ end
153
+ else
154
+ # objects have values in same field pairs (comparison with non-nil and nil doesn't occur here)
155
+ (timekey <=> timekey2 || 0).nonzero? || # if `a <=> b` is nil, then both are nil
156
+ (tag <=> tag2 || 0).nonzero? ||
157
+ cmp_variables(variables, variables2)
158
+ end
159
+ end
160
+
161
+ # This is an optimization code. Current Struct's implementation is comparing all data.
162
+ # https://github.com/ruby/ruby/blob/0623e2b7cc621b1733a760b72af246b06c30cf96/struct.c#L1200-L1203
163
+ # Actually this overhead is very small but this class is generated *per chunk* (and used in hash object).
164
+ # This means that this class is one of the most called object in Fluentd.
165
+ # See https://github.com/fluent/fluentd/pull/2560
166
+ def hash
167
+ timekey.hash
168
+ end
169
+ end
170
+
171
+ # for metrics
172
+ attr_reader :stage_size_metrics, :stage_length_metrics, :queue_size_metrics, :queue_length_metrics
173
+ attr_reader :available_buffer_space_ratios_metrics, :total_queued_size_metrics
174
+ attr_reader :newest_timekey_metrics, :oldest_timekey_metrics
175
+ # for tests
176
+ attr_reader :stage, :queue, :dequeued, :queued_num
177
+
178
+ def initialize
179
+ super
180
+
181
+ @chunk_limit_size = nil
182
+ @total_limit_size = nil
183
+ @queue_limit_length = nil
184
+ @chunk_limit_records = nil
185
+
186
+ @stage = {} #=> Hash (metadata -> chunk) : not flushed yet
187
+ @queue = [] #=> Array (chunks) : already flushed (not written)
188
+ @dequeued = {} #=> Hash (unique_id -> chunk): already written (not purged)
189
+ @queued_num = {} # metadata => int (number of queued chunks)
190
+ @dequeued_num = {} # metadata => int (number of dequeued chunks)
191
+
192
+ @stage_length_metrics = nil
193
+ @stage_size_metrics = nil
194
+ @queue_length_metrics = nil
195
+ @queue_size_metrics = nil
196
+ @available_buffer_space_ratios_metrics = nil
197
+ @total_queued_size_metrics = nil
198
+ @newest_timekey_metrics = nil
199
+ @oldest_timekey_metrics = nil
200
+ @timekeys = Hash.new(0)
201
+ @enable_update_timekeys = false
202
+ @mutex = Mutex.new
203
+ end
204
+
205
+ # The metrics_create method defines getter methods named stage_byte_size and queue_byte_size.
206
+ # For compatibility, stage_size, stage_size=, queue_size, and queue_size= are still available.
207
+ def stage_size
208
+ @stage_size_metrics.get
209
+ end
210
+
211
+ def stage_size=(value)
212
+ @stage_size_metrics.set(value)
213
+ end
214
+
215
+ def queue_size
216
+ @queue_size_metrics.get
217
+ end
218
+
219
+ def queue_size=(value)
220
+ @queue_size_metrics.set(value)
221
+ end
222
+
223
+ def persistent?
224
+ false
225
+ end
226
+
227
+ def configure(conf)
228
+ super
229
+
230
+ unless @queue_limit_length.nil?
231
+ @total_limit_size = @chunk_limit_size * @queue_limit_length
232
+ end
233
+ if @evacuate_limit_size
234
+ raise Fluent::ConfigError, "evacuate_limit_size must be 0 or larger" if @evacuate_limit_size < 0
235
+ if !persistent?
236
+ log.warn "evacuate_limit_size is ignored because this buffer does not evacuate chunks"
237
+ @evacuate_limit_size = nil
238
+ elsif @evacuate_limit_size > 0
239
+ unless owner.plugin_id_configured?
240
+ log.warn "evacuate_limit_size should be used with @id, because the evacuation directory without @id can change when the configuration is changed or reloaded"
241
+ end
242
+ if @evacuate_limit_size < @chunk_limit_size
243
+ log.warn "evacuate_limit_size is smaller than chunk_limit_size, so full chunks may not be evacuated", evacuate_limit_size: @evacuate_limit_size, chunk_limit_size: @chunk_limit_size
244
+ end
245
+ end
246
+ end
247
+ @stage_length_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "stage_length",
248
+ help_text: 'Length of stage buffers', prefer_gauge: true)
249
+ @stage_length_metrics.set(0)
250
+ @stage_size_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "stage_byte_size",
251
+ help_text: 'Total size of stage buffers', prefer_gauge: true)
252
+ @stage_size_metrics.set(0) # Ensure zero.
253
+ @queue_length_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "queue_length",
254
+ help_text: 'Length of queue buffers', prefer_gauge: true)
255
+ @queue_length_metrics.set(0)
256
+ @queue_size_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "queue_byte_size",
257
+ help_text: 'Total size of queue buffers', prefer_gauge: true)
258
+ @queue_size_metrics.set(0) # Ensure zero.
259
+ @available_buffer_space_ratios_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "available_buffer_space_ratios",
260
+ help_text: 'Ratio of available space in buffer', prefer_gauge: true)
261
+ @available_buffer_space_ratios_metrics.set(100) # Default is 100%.
262
+ @total_queued_size_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "total_queued_size",
263
+ help_text: 'Total size of stage and queue buffers', prefer_gauge: true)
264
+ @total_queued_size_metrics.set(0)
265
+ @newest_timekey_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "newest_timekey",
266
+ help_text: 'Newest timekey in buffer', prefer_gauge: true)
267
+ @oldest_timekey_metrics = metrics_create(namespace: "fluentd", subsystem: "buffer", name: "oldest_timekey",
268
+ help_text: 'Oldest timekey in buffer', prefer_gauge: true)
269
+ end
270
+
271
+ def enable_update_timekeys
272
+ @enable_update_timekeys = true
273
+ end
274
+
275
+ def start
276
+ super
277
+
278
+ @stage, @queue = resume
279
+ @stage.each_pair do |metadata, chunk|
280
+ @stage_size_metrics.add(chunk.bytesize)
281
+ end
282
+ @queue.each do |chunk|
283
+ @queued_num[chunk.metadata] ||= 0
284
+ @queued_num[chunk.metadata] += 1
285
+ @queue_size_metrics.add(chunk.bytesize)
286
+ end
287
+ update_timekeys
288
+ log.debug "buffer started", instance: self.object_id, stage_size: @stage_size_metrics.get, queue_size: @queue_size_metrics.get
289
+ end
290
+
291
+ def close
292
+ super
293
+ synchronize do
294
+ log.debug "closing buffer", instance: self.object_id
295
+ @dequeued.each_pair do |chunk_id, chunk|
296
+ chunk.close
297
+ end
298
+ until @queue.empty?
299
+ @queue.shift.close
300
+ end
301
+ @stage.each_pair do |metadata, chunk|
302
+ chunk.close
303
+ end
304
+ end
305
+ end
306
+
307
+ def terminate
308
+ super
309
+ @dequeued = @stage = @queue = @queued_num = nil
310
+ @stage_length_metrics = @stage_size_metrics = @queue_length_metrics = @queue_size_metrics = nil
311
+ @available_buffer_space_ratios_metrics = @total_queued_size_metrics = nil
312
+ @newest_timekey_metrics = @oldest_timekey_metrics = nil
313
+ @timekeys.clear
314
+ end
315
+
316
+ def storable?
317
+ @total_limit_size > @stage_size_metrics.get + @queue_size_metrics.get
318
+ end
319
+
320
+ ## TODO: for back pressure feature
321
+ # def used?(ratio)
322
+ # @total_limit_size * ratio > @stage_size_metrics.get + @queue_size_metrics.get
323
+ # end
324
+
325
+ def resume
326
+ # return {}, []
327
+ raise NotImplementedError, "Implement this method in child class"
328
+ end
329
+
330
+ def generate_chunk(metadata)
331
+ raise NotImplementedError, "Implement this method in child class"
332
+ end
333
+
334
+ def new_metadata(timekey: nil, tag: nil, variables: nil)
335
+ Metadata.new(timekey, tag, variables)
336
+ end
337
+
338
+ # Keep this method for existing code
339
+ def metadata(timekey: nil, tag: nil, variables: nil)
340
+ Metadata.new(timekey, tag, variables)
341
+ end
342
+
343
+ def timekeys
344
+ @timekeys.keys
345
+ end
346
+
347
+ # metadata MUST have consistent object_id for each variation
348
+ # data MUST be Array of serialized events, or EventStream
349
+ # metadata_and_data MUST be a hash of { metadata => data }
350
+ def write(metadata_and_data, format: nil, size: nil, enqueue: false)
351
+ return if metadata_and_data.size < 1
352
+ raise BufferOverflowError, "buffer space has too many data" unless storable?
353
+
354
+ log.on_trace { log.trace "writing events into buffer", instance: self.object_id, metadata_size: metadata_and_data.size }
355
+
356
+ operated_chunks = []
357
+ unstaged_chunks = {} # metadata => [chunk, chunk, ...]
358
+ chunks_to_enqueue = []
359
+ staged_bytesizes_by_chunk = {}
360
+ # track internal BufferChunkOverflowError in write_step_by_step
361
+ buffer_chunk_overflow_errors = []
362
+
363
+ begin
364
+ # sort metadata to get lock of chunks in same order with other threads
365
+ metadata_and_data.keys.sort.each do |metadata|
366
+ data = metadata_and_data[metadata]
367
+ write_once(metadata, data, format: format, size: size) do |chunk, adding_bytesize, error|
368
+ chunk.mon_enter # add lock to prevent to be committed/rollbacked from other threads
369
+ operated_chunks << chunk
370
+ if chunk.staged?
371
+ #
372
+ # https://github.com/fluent/fluentd/issues/2712
373
+ # write_once is supposed to write to a chunk only once
374
+ # but this block **may** run multiple times from write_step_by_step and previous write may be rollbacked
375
+ # So we should be counting the stage_size only for the last successful write
376
+ #
377
+ staged_bytesizes_by_chunk[chunk] = adding_bytesize
378
+ elsif chunk.unstaged?
379
+ unstaged_chunks[metadata] ||= []
380
+ unstaged_chunks[metadata] << chunk
381
+ end
382
+ if error && !error.empty?
383
+ buffer_chunk_overflow_errors << error
384
+ end
385
+ end
386
+ end
387
+
388
+ return if operated_chunks.empty?
389
+
390
+ # Now, this thread acquires many locks of chunks... getting buffer-global lock causes dead lock.
391
+ # Any operations needs buffer-global lock (including enqueueing) should be done after releasing locks.
392
+
393
+ first_chunk = operated_chunks.shift
394
+ # Following commits for other chunks also can finish successfully if the first commit operation
395
+ # finishes without any exceptions.
396
+ # In most cases, #commit just requires very small disk spaces, so major failure reason are
397
+ # permission errors, disk failures and other permanent(fatal) errors.
398
+ begin
399
+ first_chunk.commit
400
+ if enqueue || first_chunk.unstaged? || chunk_size_full?(first_chunk)
401
+ chunks_to_enqueue << first_chunk
402
+ end
403
+ if (bytesize = staged_bytesizes_by_chunk[first_chunk])
404
+ # Account while the chunk lock is still held so enqueue_chunk
405
+ # cannot remove the chunk and subtract its bytes first.
406
+ @stage_size_metrics.add(bytesize)
407
+ log.on_trace { log.trace { "chunk #{first_chunk.path} size_added: #{bytesize} new_size: #{first_chunk.bytesize}" } }
408
+ end
409
+ first_chunk.mon_exit
410
+ rescue
411
+ operated_chunks.unshift(first_chunk)
412
+ raise
413
+ end
414
+
415
+ errors = []
416
+ # Buffer plugin estimates there's no serious error cause: will commit for all chunks either way
417
+ operated_chunks.each do |chunk|
418
+ begin
419
+ chunk.commit
420
+ if enqueue || chunk.unstaged? || chunk_size_full?(chunk)
421
+ chunks_to_enqueue << chunk
422
+ end
423
+ if (bytesize = staged_bytesizes_by_chunk[chunk])
424
+ @stage_size_metrics.add(bytesize)
425
+ log.on_trace { log.trace { "chunk #{chunk.path} size_added: #{bytesize} new_size: #{chunk.bytesize}" } }
426
+ end
427
+ chunk.mon_exit
428
+ rescue => e
429
+ chunk.rollback
430
+ chunk.mon_exit
431
+ errors << e
432
+ end
433
+ end
434
+
435
+ # All locks about chunks are released. Stage-size accounting for the
436
+ # staged chunks happened inside each chunk monitor above, so a
437
+ # concurrent enqueue_chunk cannot observe a transiently negative
438
+ # stage_size (issue #2712 race).
439
+
440
+ enqueue_errors = []
441
+ chunks_to_enqueue.each do |c|
442
+ begin
443
+ if c.staged? && (enqueue || chunk_size_full?(c))
444
+ m = c.metadata
445
+ enqueue_chunk(m)
446
+ if unstaged_chunks[m] && !unstaged_chunks[m].empty?
447
+ u = unstaged_chunks[m].pop
448
+ u.synchronize do
449
+ if u.unstaged? && !chunk_size_full?(u)
450
+ # `u.metadata.seq` and `m.seq` can be different but Buffer#enqueue_chunk expect them to be the same value
451
+ u.metadata.seq = 0
452
+ synchronize {
453
+ @stage[m] = u.staged!
454
+ @stage_size_metrics.add(u.bytesize)
455
+ }
456
+ end
457
+ end
458
+ end
459
+ elsif c.unstaged?
460
+ enqueue_unstaged_chunk(c)
461
+ else
462
+ # previously staged chunk is already enqueued, closed or purged.
463
+ # no problem.
464
+ end
465
+ rescue => e
466
+ enqueue_errors << e
467
+ end
468
+ end
469
+
470
+ enqueue_errors.each do |e|
471
+ log.warn "error occurs in enqueueing a chunk", error: e
472
+ log.warn_backtrace e.backtrace
473
+ end
474
+
475
+ operated_chunks.clear if errors.empty?
476
+
477
+ if errors.size > 0
478
+ log.warn "error occurs in committing chunks: only first one raised", errors: errors.map(&:class)
479
+ raise errors.first
480
+ end
481
+ ensure
482
+ operated_chunks.each do |chunk|
483
+ chunk.rollback rescue nil # nothing possible to do for #rollback failure
484
+ if chunk.unstaged?
485
+ chunk.purge rescue nil # to prevent leakage of unstaged chunks
486
+ end
487
+ chunk.mon_exit rescue nil # this may raise ThreadError for chunks already committed
488
+ end
489
+ unless buffer_chunk_overflow_errors.empty?
490
+ # Notify delayed BufferChunkOverflowError here
491
+ raise BufferChunkOverflowError, buffer_chunk_overflow_errors.join(", ")
492
+ end
493
+ end
494
+ end
495
+
496
+ def queue_full?
497
+ synchronize { @queue.size } >= @queued_chunks_limit_size
498
+ end
499
+
500
+ def queued_records
501
+ synchronize { @queue.reduce(0){|r, chunk| r + chunk.size } }
502
+ end
503
+
504
+ def queued?(metadata = nil, optimistic: false)
505
+ if optimistic
506
+ optimistic_queued?(metadata)
507
+ else
508
+ synchronize do
509
+ optimistic_queued?(metadata)
510
+ end
511
+ end
512
+ end
513
+
514
+ def enqueue_chunk(metadata)
515
+ log.on_trace { log.trace "enqueueing chunk", instance: self.object_id, metadata: metadata }
516
+
517
+ chunk = synchronize do
518
+ @stage.delete(metadata)
519
+ end
520
+ return nil unless chunk
521
+
522
+ chunk.synchronize do
523
+ synchronize do
524
+ bytesize = chunk.bytesize
525
+ @stage_size_metrics.sub(bytesize)
526
+ @queue_size_metrics.add(bytesize)
527
+ if chunk.empty?
528
+ chunk.close
529
+ else
530
+ chunk.metadata.seq = 0 # metadata.seq should be 0 for counting @queued_num
531
+ @queue << chunk
532
+ @queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
533
+ chunk.enqueued!
534
+ end
535
+ end
536
+ end
537
+ nil
538
+ end
539
+
540
+ def enqueue_unstaged_chunk(chunk)
541
+ log.on_trace { log.trace "enqueueing unstaged chunk", instance: self.object_id, metadata: chunk.metadata }
542
+
543
+ synchronize do
544
+ chunk.synchronize do
545
+ metadata = chunk.metadata
546
+ metadata.seq = 0 # metadata.seq should be 0 for counting @queued_num
547
+ @queue << chunk
548
+ @queued_num[metadata] = @queued_num.fetch(metadata, 0) + 1
549
+ @queue_size_metrics.add(chunk.bytesize)
550
+ chunk.enqueued!
551
+ end
552
+ end
553
+ end
554
+
555
+ def update_timekeys
556
+ synchronize do
557
+ chunks = @stage.values
558
+ chunks.concat(@queue)
559
+ @timekeys = chunks.each_with_object({}) do |chunk, keys|
560
+ if chunk.metadata&.timekey
561
+ t = chunk.metadata.timekey
562
+ keys[t] = keys.fetch(t, 0) + 1
563
+ end
564
+ end
565
+ end
566
+ end
567
+
568
+ # At flush_at_shutdown, all staged chunks should be enqueued for buffer flush. Set true to force_enqueue for it.
569
+ def enqueue_all(force_enqueue = false)
570
+ log.on_trace { log.trace "enqueueing all chunks in buffer", instance: self.object_id }
571
+ update_timekeys if @enable_update_timekeys
572
+
573
+ if block_given?
574
+ synchronize{ @stage.keys }.each do |metadata|
575
+ return if !force_enqueue && queue_full?
576
+ # NOTE: The following line might cause data race depending on Ruby implementations except CRuby
577
+ # cf. https://github.com/fluent/fluentd/pull/1721#discussion_r146170251
578
+ chunk = @stage[metadata]
579
+ next unless chunk
580
+ v = yield metadata, chunk
581
+ enqueue_chunk(metadata) if v
582
+ end
583
+ else
584
+ synchronize{ @stage.keys }.each do |metadata|
585
+ return if !force_enqueue && queue_full?
586
+ enqueue_chunk(metadata)
587
+ end
588
+ end
589
+ end
590
+
591
+ def dequeue_chunk
592
+ return nil if @queue.empty?
593
+ log.on_trace { log.trace "dequeueing a chunk", instance: self.object_id }
594
+
595
+ synchronize do
596
+ chunk = @queue.shift
597
+
598
+ # this buffer is dequeued by other thread just before "synchronize" in this thread
599
+ return nil unless chunk
600
+
601
+ @dequeued[chunk.unique_id] = chunk
602
+ @queued_num[chunk.metadata] -= 1 # BUG if nil, 0 or subzero
603
+ @dequeued_num[chunk.metadata] ||= 0
604
+ @dequeued_num[chunk.metadata] += 1
605
+ log.trace "chunk dequeued", instance: self.object_id, metadata: chunk.metadata
606
+ chunk
607
+ end
608
+ end
609
+
610
+ def takeback_chunk(chunk_id)
611
+ log.on_trace { log.trace "taking back a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id) }
612
+
613
+ synchronize do
614
+ chunk = @dequeued.delete(chunk_id)
615
+ return false unless chunk # already purged by other thread
616
+ @queue.unshift(chunk)
617
+ log.on_trace { log.trace "chunk taken back", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: chunk.metadata }
618
+ @queued_num[chunk.metadata] += 1 # BUG if nil
619
+ @dequeued_num[chunk.metadata] -= 1
620
+ end
621
+ true
622
+ end
623
+
624
+ def purge_chunk(chunk_id)
625
+ metadata = nil
626
+ synchronize do
627
+ chunk = @dequeued.delete(chunk_id)
628
+ return nil unless chunk # purged by other threads
629
+
630
+ metadata = chunk.metadata
631
+ log.on_trace { log.trace "purging a chunk", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata }
632
+
633
+ bytesize = chunk.bytesize
634
+ begin
635
+ chunk.purge
636
+ rescue => e
637
+ log.error "failed to purge buffer chunk", chunk_id: dump_unique_id_hex(chunk_id), error_class: e.class, error: e
638
+ log.error_backtrace
639
+ ensure
640
+ # Always release the queued byte counter, even when purge raises
641
+ # (e.g. unlink/close failure on the buffer path). Otherwise
642
+ # @queue_size is never decremented for a dequeued chunk that is
643
+ # gone from both @queue and @dequeued, so the leak ratchets toward
644
+ # total_limit_size and storable? becomes permanently false (#5468).
645
+ @queue_size_metrics.sub(bytesize)
646
+ end
647
+
648
+ @dequeued_num[chunk.metadata] -= 1
649
+ if metadata && !@stage[metadata] && (!@queued_num[metadata] || @queued_num[metadata] < 1) && @dequeued_num[metadata].zero?
650
+ @queued_num.delete(metadata)
651
+ @dequeued_num.delete(metadata)
652
+ end
653
+ log.on_trace { log.trace "chunk purged", instance: self.object_id, chunk_id: dump_unique_id_hex(chunk_id), metadata: metadata }
654
+ end
655
+
656
+ nil
657
+ end
658
+
659
+ def clear_queue!
660
+ log.on_trace { log.trace "clearing queue", instance: self.object_id }
661
+
662
+ evacuate_budget = nil
663
+ skipped_num = 0
664
+ skipped_bytesize = 0
665
+
666
+ synchronize do
667
+ until @queue.empty?
668
+ begin
669
+ q = @queue.shift
670
+ if @evacuate_limit_size.nil?
671
+ evacuate_chunk(q)
672
+ elsif @evacuate_limit_size > 0
673
+ evacuate_budget ||= remaining_evacuate_size
674
+ if q.bytesize <= evacuate_budget
675
+ evacuate_chunk(q)
676
+ evacuate_budget -= q.bytesize
677
+ else
678
+ skipped_num += 1
679
+ skipped_bytesize += q.bytesize
680
+ end
681
+ end
682
+ log.trace("purging a chunk in queue"){ {id: dump_unique_id_hex(q.unique_id), bytesize: q.bytesize, size: q.size} }
683
+ q.purge
684
+ rescue => e
685
+ log.error "unexpected error while clearing buffer queue", error_class: e.class, error: e
686
+ log.error_backtrace
687
+ end
688
+ end
689
+ @queue_size_metrics.set(0)
690
+ end
691
+
692
+ if skipped_num > 0
693
+ log.warn "some chunks are purged without being evacuated to stay within evacuate_limit_size.", evacuate_limit_size: @evacuate_limit_size, chunks: skipped_num, bytesize: skipped_bytesize
694
+ end
695
+ end
696
+
697
+ def evacuate_chunk(chunk)
698
+ # Overwrite this on demand.
699
+ #
700
+ # Note: Difference from the `backup` feature.
701
+ # The `backup` feature is for unrecoverable errors, mainly for bad chunks.
702
+ # On the other hand, this feature is for normal chunks.
703
+ # The main motivation for this feature is to enable recovery by evacuating buffer files
704
+ # when the retry limit is reached due to external factors such as network issues.
705
+ #
706
+ # Note: Difference from the `secondary` feature.
707
+ # The `secondary` feature is not suitable for recovery.
708
+ # It can be difficult to recover files made by `out_secondary_file` because the metadata
709
+ # is lost.
710
+ # For file buffers, the easiest way for recovery is to evacuate the chunk files as is.
711
+ # Once the issue is recovered, we can put back the chunk files, and restart Fluentd to
712
+ # load them.
713
+ # This feature enables it.
714
+ end
715
+
716
+ def chunk_size_over?(chunk)
717
+ chunk.bytesize > @chunk_limit_size || (@chunk_limit_records && chunk.size > @chunk_limit_records)
718
+ end
719
+
720
+ def chunk_size_full?(chunk)
721
+ chunk.bytesize >= @chunk_limit_size * @chunk_full_threshold || (@chunk_limit_records && chunk.size >= @chunk_limit_records * @chunk_full_threshold)
722
+ end
723
+
724
+ class ShouldRetry < StandardError; end
725
+
726
+ # write once into a chunk
727
+ # 1. append whole data into existing chunk
728
+ # 2. commit it & return unless chunk_size_over?
729
+ # 3. enqueue existing chunk & retry whole method if chunk was not empty
730
+ # 4. go to step_by_step writing
731
+
732
+ def write_once(metadata, data, format: nil, size: nil, &block)
733
+ return if data.empty?
734
+
735
+ stored = false
736
+ adding_bytesize = nil
737
+
738
+ chunk = synchronize { @stage[metadata] ||= generate_chunk(metadata).staged! }
739
+ enqueue_chunk_before_retry = false
740
+ chunk.synchronize do
741
+ # retry this method if chunk is already queued (between getting chunk and entering critical section)
742
+ raise ShouldRetry unless chunk.staged?
743
+
744
+ empty_chunk = chunk.empty?
745
+
746
+ original_bytesize = chunk.bytesize
747
+ begin
748
+ if format
749
+ serialized = format.call(data)
750
+ chunk.concat(serialized, size ? size.call : data.size)
751
+ else
752
+ chunk.append(data, compress: @compress)
753
+ end
754
+ adding_bytesize = chunk.bytesize - original_bytesize
755
+
756
+ if chunk_size_over?(chunk)
757
+ if format && empty_chunk
758
+ if chunk.bytesize > @chunk_limit_size
759
+ log.warn "chunk bytes limit exceeds for an emitted event stream: #{adding_bytesize}bytes"
760
+ else
761
+ log.warn "chunk size limit exceeds for an emitted event stream: #{chunk.size}records"
762
+ end
763
+ end
764
+ chunk.rollback
765
+
766
+ if format && !empty_chunk
767
+ # Event streams should be appended into a chunk at once
768
+ # as far as possible, to improve performance of formatting.
769
+ # Event stream may be a MessagePackEventStream. We don't want to split it into
770
+ # 2 or more chunks (except for a case that the event stream is larger than chunk limit).
771
+ enqueue_chunk_before_retry = true
772
+ raise ShouldRetry
773
+ end
774
+ else
775
+ stored = true
776
+ end
777
+ rescue
778
+ chunk.rollback
779
+ raise
780
+ end
781
+
782
+ if stored
783
+ block.call(chunk, adding_bytesize)
784
+ end
785
+ end
786
+
787
+ unless stored
788
+ # try step-by-step appending if data can't be stored into existing a chunk in non-bulk mode
789
+ #
790
+ # 1/10 size of original event stream (splits_count == 10) seems enough small
791
+ # to try emitting events into existing chunk.
792
+ # it does not matter to split event stream into very small splits, because chunks have less
793
+ # overhead to write data many times (even about file buffer chunks).
794
+ write_step_by_step(metadata, data, format, 10, &block)
795
+ end
796
+ rescue ShouldRetry
797
+ enqueue_chunk(metadata) if enqueue_chunk_before_retry
798
+ retry
799
+ end
800
+
801
+ # EventStream can be split into many streams
802
+ # because (es1 + es2).to_msgpack_stream == es1.to_msgpack_stream + es2.to_msgpack_stream
803
+
804
+ # 1. split event streams into many (10 -> 100 -> 1000 -> ...) chunks
805
+ # 2. append splits into the staged chunks as much as possible
806
+ # 3. create unstaged chunk and append rest splits -> repeat it for all splits
807
+
808
+ def write_step_by_step(metadata, data, format, splits_count, &block)
809
+ splits = []
810
+ if splits_count > data.size
811
+ splits_count = data.size
812
+ end
813
+ slice_size = if data.size % splits_count == 0
814
+ data.size / splits_count
815
+ else
816
+ data.size / (splits_count - 1)
817
+ end
818
+ slice_origin = 0
819
+ while slice_origin < data.size
820
+ splits << data.slice(slice_origin, slice_size)
821
+ slice_origin += slice_size
822
+ end
823
+
824
+ # This method will append events into the staged chunk at first.
825
+ # Then, will generate chunks not staged (not queued) to append rest data.
826
+ staged_chunk_used = false
827
+ modified_chunks = []
828
+ modified_metadata = metadata
829
+ get_next_chunk = ->(){
830
+ if staged_chunk_used
831
+ # Staging new chunk here is bad idea:
832
+ # Recovering whole state including newly staged chunks is much harder than current implementation.
833
+ modified_metadata = modified_metadata.dup_next
834
+ generate_chunk(modified_metadata)
835
+ else
836
+ synchronize { @stage[modified_metadata] ||= generate_chunk(modified_metadata).staged! }
837
+ end
838
+ }
839
+
840
+ writing_splits_index = 0
841
+ enqueue_chunk_before_retry = false
842
+
843
+ while writing_splits_index < splits.size
844
+ chunk = get_next_chunk.call
845
+ errors = []
846
+ # The chunk must be locked until being passed to &block.
847
+ chunk.mon_enter
848
+ modified_chunks << {chunk: chunk, adding_bytesize: 0, errors: errors}
849
+
850
+ raise ShouldRetry unless chunk.writable?
851
+ staged_chunk_used = true if chunk.staged?
852
+
853
+ original_bytesize = committed_bytesize = chunk.bytesize
854
+ begin
855
+ while writing_splits_index < splits.size
856
+ split = splits[writing_splits_index]
857
+ formatted_split = format ? format.call(split) : nil
858
+
859
+ if split.size == 1 # Check BufferChunkOverflowError
860
+ determined_bytesize = nil
861
+ if @compress != :text
862
+ determined_bytesize = nil
863
+ elsif formatted_split
864
+ determined_bytesize = formatted_split.bytesize
865
+ elsif split.first.respond_to?(:bytesize)
866
+ determined_bytesize = split.first.bytesize
867
+ end
868
+
869
+ if determined_bytesize && determined_bytesize > @chunk_limit_size
870
+ # It is a obvious case that BufferChunkOverflowError should be raised here.
871
+ # But if it raises here, already processed 'split' or
872
+ # the proceeding 'split' will be lost completely.
873
+ # So it is a last resort to delay raising such a exception
874
+ errors << "a #{determined_bytesize} bytes record (nth: #{writing_splits_index}) is larger than buffer chunk limit size (#{@chunk_limit_size})"
875
+ writing_splits_index += 1
876
+ next
877
+ end
878
+
879
+ if determined_bytesize.nil? || chunk.bytesize + determined_bytesize > @chunk_limit_size
880
+ # The split will (might) cause size over so keep already processed
881
+ # 'split' content here (allow performance regression a bit).
882
+ chunk.commit
883
+ committed_bytesize = chunk.bytesize
884
+ end
885
+ end
886
+
887
+ if format
888
+ chunk.concat(formatted_split, split.size)
889
+ else
890
+ chunk.append(split, compress: @compress)
891
+ end
892
+ adding_bytes = chunk.bytesize - committed_bytesize
893
+
894
+ if chunk_size_over?(chunk) # split size is larger than difference between size_full? and size_over?
895
+ chunk.rollback
896
+ committed_bytesize = chunk.bytesize
897
+
898
+ if split.size == 1 # Check BufferChunkOverflowError again
899
+ if adding_bytes > @chunk_limit_size
900
+ errors << "concatenated/appended a #{adding_bytes} bytes record (nth: #{writing_splits_index}) is larger than buffer chunk limit size (#{@chunk_limit_size})"
901
+ writing_splits_index += 1
902
+ next
903
+ else
904
+ # As already processed content is kept after rollback, then unstaged chunk should be queued.
905
+ # After that, re-process current split again.
906
+ # New chunk should be allocated, to do it, modify @stage and so on.
907
+ synchronize do
908
+ @stage.delete(modified_metadata)
909
+ # Subtract this chunk's already-counted staged bytes here;
910
+ # otherwise @stage_size leaks upward over the buffer's lifetime.
911
+ @stage_size_metrics.sub(original_bytesize) if chunk.staged?
912
+ end
913
+ staged_chunk_used = false
914
+ chunk.unstaged!
915
+ break
916
+ end
917
+ end
918
+
919
+ if chunk_size_full?(chunk) || split.size == 1
920
+ enqueue_chunk_before_retry = true
921
+ else
922
+ splits_count *= 10
923
+ end
924
+
925
+ raise ShouldRetry
926
+ end
927
+
928
+ writing_splits_index += 1
929
+
930
+ if chunk_size_full?(chunk)
931
+ break
932
+ end
933
+ end
934
+ rescue
935
+ chunk.purge if chunk.unstaged? # unstaged chunk will leak unless purge it
936
+ raise
937
+ end
938
+
939
+ modified_chunks.last[:adding_bytesize] = chunk.bytesize - original_bytesize
940
+ end
941
+ modified_chunks.each do |data|
942
+ block.call(data[:chunk], data[:adding_bytesize], data[:errors])
943
+ end
944
+ rescue ShouldRetry
945
+ modified_chunks.each do |data|
946
+ chunk = data[:chunk]
947
+ chunk.rollback rescue nil
948
+ if chunk.unstaged?
949
+ chunk.purge rescue nil
950
+ end
951
+ chunk.mon_exit rescue nil
952
+ end
953
+ enqueue_chunk(metadata) if enqueue_chunk_before_retry
954
+ retry
955
+ ensure
956
+ modified_chunks.each do |data|
957
+ chunk = data[:chunk]
958
+ chunk.mon_exit
959
+ end
960
+ end
961
+
962
+ STATS_KEYS = [
963
+ 'stage_length',
964
+ 'stage_byte_size',
965
+ 'queue_length',
966
+ 'queue_byte_size',
967
+ 'available_buffer_space_ratios',
968
+ 'total_queued_size',
969
+ 'oldest_timekey',
970
+ 'newest_timekey'
971
+ ]
972
+
973
+ def statistics
974
+ # Export-only clamp: internal gauges may go transiently negative during
975
+ # the deferred stage_size add vs enqueue_chunk sub race (#5303, #2712).
976
+ # Clamping the gauge store itself would turn that into a permanent
977
+ # over-count and break Buffer#storable? -- keep raw gauge semantics.
978
+ stage_size = [@stage_size_metrics.get, 0].max
979
+ queue_size = [@queue_size_metrics.get, 0].max
980
+ denom = @total_limit_size.to_f
981
+ # denom > 0 already excludes 0/0 NaN; stage/queue are floored above.
982
+ buffer_space = denom > 0.0 ? (1.0 - (stage_size + queue_size).to_f / denom).clamp(0.0, 1.0) : 0.0
983
+ @stage_length_metrics.set(@stage.size)
984
+ @queue_length_metrics.set(@queue.size)
985
+ @available_buffer_space_ratios_metrics.set(buffer_space * 100)
986
+ @total_queued_size_metrics.set(stage_size + queue_size)
987
+ stats = {
988
+ 'stage_length' => @stage_length_metrics.get,
989
+ 'stage_byte_size' => stage_size,
990
+ 'queue_length' => @queue_length_metrics.get,
991
+ 'queue_byte_size' => queue_size,
992
+ 'available_buffer_space_ratios' => @available_buffer_space_ratios_metrics.get.round(1),
993
+ 'total_queued_size' => @total_queued_size_metrics.get,
994
+ }
995
+
996
+ tkeys = timekeys
997
+ if (m = tkeys.min)
998
+ @oldest_timekey_metrics.set(m)
999
+ stats['oldest_timekey'] = @oldest_timekey_metrics.get
1000
+ end
1001
+ if (m = tkeys.max)
1002
+ @newest_timekey_metrics.set(m)
1003
+ stats['newest_timekey'] = @newest_timekey_metrics.get
1004
+ end
1005
+
1006
+ { 'buffer' => stats }
1007
+ end
1008
+
1009
+ def backup(chunk_unique_id)
1010
+ unique_id = dump_unique_id_hex(chunk_unique_id)
1011
+
1012
+ if @disable_chunk_backup
1013
+ log.warn "disable_chunk_backup is true. #{unique_id} chunk is not backed up."
1014
+ return
1015
+ end
1016
+
1017
+ backup_file = File.join(backup_base_dir, 'backup', "worker#{fluentd_worker_id}", safe_owner_id, "#{unique_id}.log")
1018
+ backup_dir = File.dirname(backup_file)
1019
+
1020
+ log.warn "bad chunk is moved to #{backup_file}"
1021
+ FileUtils.mkdir_p(backup_dir, mode: system_config.dir_permission || Fluent::DEFAULT_DIR_PERMISSION) unless Dir.exist?(backup_dir)
1022
+ File.open(backup_file, 'ab', system_config.file_permission || Fluent::DEFAULT_FILE_PERMISSION) { |f| yield f }
1023
+ end
1024
+
1025
+ private
1026
+
1027
+ def optimistic_queued?(metadata = nil)
1028
+ if metadata
1029
+ n = @queued_num[metadata]
1030
+ n&.nonzero?
1031
+ else
1032
+ !@queue.empty?
1033
+ end
1034
+ end
1035
+
1036
+ def safe_owner_id
1037
+ owner.plugin_id.gsub(/[ "\/\\:;|*<>?]/, '_')
1038
+ end
1039
+
1040
+ def backup_base_dir
1041
+ system_config.root_dir || DEFAULT_BACKUP_DIR
1042
+ end
1043
+
1044
+ def evacuate_dir
1045
+ File.join(backup_base_dir, 'buffer', safe_owner_id)
1046
+ end
1047
+
1048
+ def evacuated_bytesize
1049
+ dir = evacuate_dir
1050
+ Dir.children(dir, encoding: dir.encoding).sum { |name| File.size?(File.join(dir, name)).to_i }
1051
+ rescue Errno::ENOENT
1052
+ 0
1053
+ end
1054
+
1055
+ def remaining_evacuate_size
1056
+ @evacuate_limit_size - evacuated_bytesize
1057
+ rescue => e
1058
+ log.warn "failed to get the size of the evacuated chunk files. queued chunks are purged without being evacuated.", error: e
1059
+ 0
1060
+ end
1061
+ end
1062
+ end
1063
+ end