fluent-plugin-watch-objectspace 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 50ef80ccfe4236df352654ccbf415698e15bcf98927fc6bebba8ddbffd3d76bb
4
- data.tar.gz: 25efcc62552470384eebf776edcd6dc89e45d7550319dd2bd141b4f4608e6d7c
3
+ metadata.gz: f79b912a0f6dc31b0b0a11ebec4780ecdb56abd848842b1250440afe15995ab1
4
+ data.tar.gz: 343fceb8323cc98295d3c0fe1b52a5b0d8eff59f061bbd8f8191f876af2af3d0
5
5
  SHA512:
6
- metadata.gz: '0935e7413eefa8bc4302b0d151c403fa24131960f3aa9dddc87c2c815dab3321328b007a51d5b829a9019c3b5e2450d4fcb48d8aff23f5cbc29e226bc783a8bc'
7
- data.tar.gz: 73f7efa2832cce3b36f024b9c1feb4ac729a20aa63426b583b5012ff784391af7150c26a732471ff14aee03342d01647deb7101b35c95a06f5f016dc37c2001d
6
+ metadata.gz: 4d6b10a8abcbbe443c81f1f59bcc44610d62d451dedfeaeab0c458d76aee9459854c1197e6ad0f3a5b30fbc34d357b71815e40251189dba6921cc562dcdd397b
7
+ data.tar.gz: 606d74ca77ac3aa93ce6113d0c318e7777caed306feda3c80eba93ccce68dce0ef3b262672698783adef649c0a33cf7607b8e4891bf6348ada076fdb4670d3dd
data/README.md CHANGED
@@ -58,10 +58,10 @@ If memory usage is over 1.3 times, it raise an exception.
58
58
 
59
59
  ## FAQ
60
60
 
61
- # What is the different between fluent-plugin-watch-objectspace and fluent-plugin-watch-process
61
+ ### What is the difference between fluent-plugin-watch-objectspace and fluent-plugin-watch-process?
62
62
 
63
63
  fluent-plugin-watch-process is useful cron/batch process monitoring, In contrast to it, fluent-plugin-watch-objectspace is
64
- focused on used plugin's resource (memory) usage especially object and memory.
64
+ focused on used plugin's resource usage especially object and memory.
65
65
 
66
66
  ## Copyright
67
67
 
@@ -3,7 +3,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
3
 
4
4
  Gem::Specification.new do |spec|
5
5
  spec.name = "fluent-plugin-watch-objectspace"
6
- spec.version = "0.1.0"
6
+ spec.version = "0.2.0"
7
7
  spec.authors = ["Kentaro Hayashi"]
8
8
  spec.email = ["kenhys@gmail.com"]
9
9
 
@@ -14,6 +14,8 @@
14
14
  # limitations under the License.
15
15
 
16
16
  require "fluent/plugin/input"
17
+ require "fluent/config/error"
18
+ require "objspace"
17
19
 
18
20
  module Fluent
19
21
  module Plugin
@@ -34,14 +36,25 @@ module Fluent
34
36
  config_param :watch_delay, :time, default: 60
35
37
  desc "Collect GC::Profiler.raw_data"
36
38
  config_param :gc_raw_data, :bool, default: false
37
- desc "Threshold rate which regards increased RES as memory leaks"
38
- config_param :res_incremental_threshold_rate, :float, default: 1.3
39
-
39
+ desc "Specify included fields of top command"
40
+ config_param :top_fields, :array, default: ["VIRT", "RES", "SHR", "%CPU", "%MEM", "TIME+"]
41
+
42
+ config_section :threshold, required: false, multi: false do
43
+ desc "Threshold rate which regards increased memsize as memory leaks"
44
+ config_param :memsize_of_all, :float, default: 1.3
45
+ desc "Threshold rate which regards increased RES as memory leaks"
46
+ config_param :res_of_top, :float, default: nil
47
+ end
48
+
40
49
  def configure(conf)
41
50
  super(conf)
42
51
  if @modules
43
52
  @modules.each do |mod|
44
- require mod
53
+ begin
54
+ require mod
55
+ rescue LoadError
56
+ raise Fluent::ConfigError.new("BUG: module <#{mod}> can't be loaded")
57
+ end
45
58
  end
46
59
  end
47
60
  @warmup_time = Time.now + @watch_delay
@@ -59,6 +72,7 @@ module Fluent
59
72
  fields = content.split("\n")[-2].split
60
73
  values = content.split("\n").last.split
61
74
  fields.each_with_index do |field, index|
75
+ next unless @top_fields.include?(field)
62
76
  case field
63
77
  when "USER", "S", "TIME+", "COMMAND"
64
78
  record[field.downcase] = values[index]
@@ -78,7 +92,8 @@ module Fluent
78
92
  record = {
79
93
  "pid" => pid,
80
94
  "count" => {},
81
- "memory_leaks" => false
95
+ "memory_leaks" => false,
96
+ "memsize_of_all" => ObjectSpace.memsize_of_all
82
97
  }
83
98
 
84
99
  begin
@@ -88,23 +103,20 @@ module Fluent
88
103
  record.merge!(parse_top_result(content))
89
104
 
90
105
  if @gc_raw_data
91
- record["raw_data"] = GC::Profiler.raw_data
106
+ record["gc_raw_data"] = GC::Profiler.raw_data
92
107
  end
93
- @watch_class.each do |klass|
94
- record["count"]["#{klass.downcase}"] = ObjectSpace.each_object(Object.const_get(klass)) { |x| x }
108
+ if @watch_class
109
+ @watch_class.each do |klass|
110
+ record["count"]["#{klass.downcase}"] = ObjectSpace.each_object(Object.const_get(klass)) { |x| x }
111
+ end
95
112
  end
96
113
 
97
114
  if @source.empty?
115
+ record["memsize_of_all"] = ObjectSpace.memsize_of_all
98
116
  @source = record
99
117
  end
100
118
 
101
- if @source["res"] * @res_incremental_threshold_rate < record["res"]
102
- record["memory_leaks"] = true
103
- message = sprintf("Memory leak is detected, threshold rate <%f>: %f > %f * %f",
104
- @res_incremental_threshold_rate, record["res"],
105
- @source["res"], @res_incremental_threshold_rate)
106
- raise message
107
- end
119
+ check_threshold(record)
108
120
  es = OneEventStream.new(Fluent::EventTime.now, record)
109
121
  router.emit_stream(@tag, es)
110
122
  rescue => e
@@ -112,6 +124,29 @@ module Fluent
112
124
  end
113
125
  end
114
126
 
127
+ def check_threshold(record)
128
+ return unless @threshold
129
+
130
+ if @threshold.res_of_top
131
+ if @source["res"] * @threshold.res_of_top < record["res"]
132
+ record["memory_leaks"] = true
133
+ message = sprintf("Memory leak is detected, threshold res_of_top rate <%f>: %f > %f * %f",
134
+ @threshold.res_of_top, record["res"],
135
+ @source["res"], @threshold.res_of_top)
136
+ raise message
137
+ end
138
+ end
139
+ if @threshold.memsize_of_all
140
+ if @source["memsize_of_all"] * @threshold.memsize_of_all < record["memsize_of_all"]
141
+ record["memory_leaks"] = true
142
+ message = sprintf("Memory leak is detected, threshold of memsize_of_all rate <%f>: %f > %f * %f",
143
+ @threshold.memsize_of_all, record["memsize_of_all"],
144
+ @source["memsize_of_all"], @threshold.memsize_of_all)
145
+ raise message
146
+ end
147
+ end
148
+ end
149
+
115
150
  def shutdown
116
151
  end
117
152
  end
@@ -14,28 +14,197 @@ class WatchObjectspaceInputTest < Test::Unit::TestCase
14
14
  Fluent::Test::Driver::Input.new(Fluent::Plugin::WatchObjectspaceInput).configure(conf)
15
15
  end
16
16
 
17
+ def create_config(params={})
18
+ config_element("ROOT", "", params)
19
+ end
20
+
21
+ def default_params(data={})
22
+ {
23
+ "watch_delay" => 0,
24
+ "watch_interval" => 1
25
+ }.merge(data)
26
+ end
27
+
17
28
  sub_test_case "configure" do
18
29
  def test_default_configuration
19
30
  d = create_driver
20
31
  assert_equal([
32
+ nil,
33
+ 60,
34
+ 60,
35
+ "watch_objectspace",
36
+ nil,
37
+ false
38
+ ],
39
+ [
40
+ d.instance.watch_class,
41
+ d.instance.watch_interval,
42
+ d.instance.watch_delay,
43
+ d.instance.tag,
44
+ d.instance.modules,
45
+ d.instance.gc_raw_data
46
+ ])
47
+ end
48
+
49
+ def test_customize
50
+ config = config_element("ROOT", "", {
51
+ "watch_delay" => 1,
52
+ "watch_interval" => 2,
53
+ "watch_class" => ["String"],
54
+ "tag" => "customized",
55
+ "modules" => ["objspace"],
56
+ "gc_raw_data" => true,
57
+ "res_incremental_threshold_rate" => 1.1,
58
+ "memsize_of_all_incremental_threshold_rate" => 1.5
59
+ })
60
+ d = create_driver(config)
61
+ assert_equal([
62
+ ["String"],
63
+ 2.0,
64
+ 1.0,
65
+ "customized",
66
+ ["objspace"],
67
+ true
68
+ ],
69
+ [
21
70
  d.instance.watch_class,
22
71
  d.instance.watch_interval,
23
72
  d.instance.watch_delay,
24
73
  d.instance.tag,
25
74
  d.instance.modules,
26
75
  d.instance.gc_raw_data,
27
- d.instance.res_incremental_threshold_rate
76
+ ])
77
+ end
78
+
79
+ def test_watch_class
80
+ config = create_config(default_params({"watch_class" => ["String"]}))
81
+ d = create_driver(config)
82
+ d.run(expect_records: 1, timeout: 1)
83
+ assert_equal([
84
+ 1,
85
+ ["string"],
86
+ true
28
87
  ],
29
88
  [
30
- nil,
31
- 60,
32
- 60,
33
- "watch_objectspace",
34
- nil,
35
- false,
36
- 1.3
89
+ d.events.size,
90
+ d.events.first.last["count"].keys,
91
+ d.events.first.last["count"]["string"] > 0
37
92
  ])
38
93
  end
94
+
95
+ def test_watch_interval
96
+ config = create_config(default_params({"watch_interval" => 5}))
97
+ d = create_driver(config)
98
+ d.run(expect_records: 1, timeout: 5)
99
+ assert_equal(1, d.events.size)
100
+ end
101
+
102
+ sub_test_case "watch_delay" do
103
+ data(
104
+ before_interval: [1, 0, 5],
105
+ after_interval: [0, 10, 5]
106
+ )
107
+ test "watch delay" do |(count, delay, interval)|
108
+ config = create_config(default_params({"watch_delay" => delay, "watch_interval" => interval}))
109
+ d = create_driver(config)
110
+ d.run(expect_records: 1, timeout: interval)
111
+ assert_equal(count, d.events.size)
112
+ end
113
+ end
114
+
115
+ sub_test_case "tag" do
116
+ data(
117
+ with_tag: ["changed", "changed"],
118
+ default: ["watch_objectspace", nil]
119
+ )
120
+ test "tag" do |(tag, specified)|
121
+ config = if specified
122
+ create_config(default_params({"tag" => specified}))
123
+ else
124
+ create_config(default_params)
125
+ end
126
+ d = create_driver(config)
127
+ d.run(expect_records: 1, timeout: 5)
128
+ assert_equal([tag], d.events.collect { |event| event.first })
129
+ end
130
+ end
131
+
132
+ sub_test_case "modules" do
133
+ def test_invalid_module
134
+ config = create_config(default_params({"modules" => "404",
135
+ "watch_class" => "404"}))
136
+ assert_raise do
137
+ create_driver(config)
138
+ end
139
+ end
140
+
141
+ def test_valid_module
142
+ config = create_config(default_params({"modules" => "fluent/plugin/in_watch_objectspace",
143
+ "watch_class" => "Fluent::Plugin::WatchObjectspaceInput"}))
144
+ d = create_driver(config)
145
+ d.run(expect_records: 1, timeout: 1)
146
+ assert_equal([
147
+ ["fluent::plugin::watchobjectspaceinput"],
148
+ true
149
+ ],
150
+ [
151
+ d.events.collect { |event| event.last["count"].keys }.flatten,
152
+ d.events.all? { |event| event.last["count"]["fluent::plugin::watchobjectspaceinput"] > 0 }
153
+ ])
154
+ end
155
+ end
156
+
157
+ def gc_raw_data_keys(data)
158
+ if data.empty?
159
+ data
160
+ else
161
+ data.collect do |raw_data|
162
+ raw_data.keys
163
+ end
164
+ end
165
+ end
166
+
167
+ sub_test_case "gc_raw_data" do
168
+ data(
169
+ without_gc: [false, [[]]],
170
+ with_gc: [true, [[%i(GC_FLAGS GC_TIME GC_INVOKE_TIME HEAP_USE_SIZE HEAP_TOTAL_SIZE HEAP_TOTAL_OBJECTS GC_IS_MARKED)]]]
171
+ )
172
+ test "gc" do |(gc, keys)|
173
+ config = create_config(default_params({"gc_raw_data" => "true"}))
174
+ d = create_driver(config)
175
+ GC::Profiler.clear
176
+ GC.start if gc
177
+ d.run(expect_records: 1, timeout: 1)
178
+ assert_equal([
179
+ 1,
180
+ keys
181
+ ],
182
+ [
183
+ d.events.size,
184
+ d.events.collect { |event| gc_raw_data_keys(event.last["gc_raw_data"])},
185
+ ])
186
+ end
187
+ end
188
+
189
+ sub_test_case "threshold" do
190
+ def test_memsize_of_all
191
+ config = config_element("ROOT", "", {
192
+ "watch_delay" => 0,
193
+ "watch_interval" => 2,
194
+ }, [config_element("threshold", "", {})])
195
+ d = create_driver(config)
196
+ assert_equal(1.3, d.instance.threshold.memsize_of_all)
197
+ end
198
+
199
+ def test_res_of_top
200
+ config = config_element("ROOT", "", {
201
+ "watch_delay" => 0,
202
+ "watch_interval" => 2,
203
+ }, [config_element("threshold", "", {"res_of_top" => 1.5})])
204
+ d = create_driver(config)
205
+ assert_equal(1.5, d.instance.threshold.res_of_top)
206
+ end
207
+ end
39
208
  end
40
209
 
41
210
  sub_test_case "parser" do
@@ -50,7 +219,7 @@ class WatchObjectspaceInputTest < Test::Unit::TestCase
50
219
  assert_equal([1,
51
220
  "watch_objectspace",
52
221
  Fluent::EventTime,
53
- ["pid", "count", "memory_leaks", "user", "pr", "ni", "virt", "res", "shr", "s", "%cpu", "%mem", "time+", "command"]
222
+ ["pid", "count", "memory_leaks", "memsize_of_all", "virt", "res", "shr", "%cpu", "%mem", "time+"]
54
223
  ],
55
224
  [d.events.size,
56
225
  event[0],
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-watch-objectspace
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kentaro Hayashi
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-09-27 00:00:00.000000000 Z
11
+ date: 2021-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler