logstash-core 5.0.0.alpha5.snapshot1-java → 5.0.0.alpha6.snapshot1-java
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of logstash-core might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/lib/logstash-core/version.rb +1 -1
- data/lib/logstash/agent.rb +1 -1
- data/lib/logstash/api/commands/default_metadata.rb +1 -1
- data/lib/logstash/api/commands/hot_threads_reporter.rb +4 -7
- data/lib/logstash/api/commands/node.rb +5 -4
- data/lib/logstash/api/commands/stats.rb +8 -3
- data/lib/logstash/api/modules/base.rb +5 -0
- data/lib/logstash/api/modules/node.rb +1 -2
- data/lib/logstash/api/modules/node_stats.rb +1 -2
- data/lib/logstash/codecs/base.rb +29 -1
- data/lib/logstash/config/mixin.rb +1 -1
- data/lib/logstash/environment.rb +5 -5
- data/lib/logstash/filter_delegator.rb +4 -5
- data/lib/logstash/instrument/periodic_poller/jvm.rb +43 -10
- data/lib/logstash/output_delegator.rb +33 -168
- data/lib/logstash/output_delegator_strategies/legacy.rb +29 -0
- data/lib/logstash/output_delegator_strategies/shared.rb +20 -0
- data/lib/logstash/output_delegator_strategies/single.rb +23 -0
- data/lib/logstash/output_delegator_strategy_registry.rb +36 -0
- data/lib/logstash/outputs/base.rb +39 -26
- data/lib/logstash/patches/clamp.rb +6 -0
- data/lib/logstash/pipeline.rb +42 -14
- data/lib/logstash/pipeline_reporter.rb +2 -8
- data/lib/logstash/plugin.rb +6 -10
- data/lib/logstash/runner.rb +12 -9
- data/lib/logstash/settings.rb +124 -21
- data/lib/logstash/util/wrapped_synchronous_queue.rb +17 -1
- data/lib/logstash/version.rb +1 -1
- data/lib/logstash/webserver.rb +44 -33
- data/locales/en.yml +5 -1
- data/logstash-core.gemspec +2 -2
- data/spec/api/lib/api/node_spec.rb +62 -10
- data/spec/api/lib/api/node_stats_spec.rb +16 -3
- data/spec/api/lib/api/support/resource_dsl_methods.rb +11 -1
- data/spec/api/spec_helper.rb +1 -1
- data/spec/conditionals_spec.rb +12 -1
- data/spec/logstash/agent_spec.rb +3 -0
- data/spec/logstash/codecs/base_spec.rb +74 -0
- data/spec/logstash/instrument/periodic_poller/jvm_spec.rb +37 -10
- data/spec/logstash/output_delegator_spec.rb +64 -89
- data/spec/logstash/outputs/base_spec.rb +91 -15
- data/spec/logstash/pipeline_reporter_spec.rb +1 -6
- data/spec/logstash/pipeline_spec.rb +20 -22
- data/spec/logstash/plugin_spec.rb +3 -3
- data/spec/logstash/runner_spec.rb +86 -3
- data/spec/logstash/settings/integer_spec.rb +20 -0
- data/spec/logstash/settings/numeric_spec.rb +28 -0
- data/spec/logstash/settings/port_range_spec.rb +93 -0
- data/spec/logstash/util/wrapped_synchronous_queue_spec.rb +6 -0
- data/spec/logstash/webserver_spec.rb +95 -0
- metadata +20 -6
data/lib/logstash/settings.rb
CHANGED
@@ -176,18 +176,43 @@ module LogStash
|
|
176
176
|
end
|
177
177
|
end
|
178
178
|
|
179
|
-
|
180
|
-
|
181
|
-
class Boolean < Setting
|
182
|
-
def initialize(name, default, strict=true, &validator_proc)
|
179
|
+
class Coercible < Setting
|
180
|
+
def initialize(name, klass, default=nil, strict=true, &validator_proc)
|
183
181
|
@name = name
|
184
|
-
|
182
|
+
unless klass.is_a?(Class)
|
183
|
+
raise ArgumentError.new("Setting \"#{@name}\" must be initialized with a class (received #{klass})")
|
184
|
+
end
|
185
|
+
@klass = klass
|
186
|
+
@validator_proc = validator_proc
|
185
187
|
@value = nil
|
186
188
|
@value_is_set = false
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
189
|
+
|
190
|
+
if strict
|
191
|
+
coerced_default = coerce(default)
|
192
|
+
validate(coerced_default)
|
193
|
+
@default = coerced_default
|
194
|
+
else
|
195
|
+
@default = default
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def set(value)
|
200
|
+
coerced_value = coerce(value)
|
201
|
+
validate(coerced_value)
|
202
|
+
@value = coerce(coerced_value)
|
203
|
+
@value_is_set = true
|
204
|
+
@value
|
205
|
+
end
|
206
|
+
|
207
|
+
def coerce(value)
|
208
|
+
raise NotImplementedError.new("Please implement #coerce for #{self.class}")
|
209
|
+
end
|
210
|
+
end
|
211
|
+
### Specific settings #####
|
212
|
+
|
213
|
+
class Boolean < Coercible
|
214
|
+
def initialize(name, default, strict=true, &validator_proc)
|
215
|
+
super(name, Object, default, strict, &validator_proc)
|
191
216
|
end
|
192
217
|
|
193
218
|
def coerce(value)
|
@@ -200,25 +225,104 @@ module LogStash
|
|
200
225
|
raise ArgumentError.new("could not coerce #{value} into a boolean")
|
201
226
|
end
|
202
227
|
end
|
228
|
+
end
|
203
229
|
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
230
|
+
class Numeric < Coercible
|
231
|
+
def initialize(name, default=nil, strict=true)
|
232
|
+
super(name, ::Numeric, default, strict)
|
233
|
+
end
|
234
|
+
|
235
|
+
def coerce(v)
|
236
|
+
return v if v.is_a?(::Numeric)
|
237
|
+
|
238
|
+
# I hate these "exceptions as control flow" idioms
|
239
|
+
# but Ruby's `"a".to_i => 0` makes it hard to do anything else.
|
240
|
+
coerced_value = (Integer(v) rescue nil) || (Float(v) rescue nil)
|
241
|
+
|
242
|
+
if coerced_value.nil?
|
243
|
+
raise ArgumentError.new("Failed to coerce value to Numeric. Received #{v} (#{v.class})")
|
244
|
+
else
|
245
|
+
coerced_value
|
246
|
+
end
|
210
247
|
end
|
211
248
|
end
|
212
249
|
|
213
|
-
class
|
250
|
+
class Integer < Coercible
|
214
251
|
def initialize(name, default=nil, strict=true)
|
215
|
-
super(name, ::
|
252
|
+
super(name, ::Integer, default, strict)
|
253
|
+
end
|
254
|
+
|
255
|
+
def coerce(value)
|
256
|
+
return value unless value.is_a?(::String)
|
257
|
+
|
258
|
+
coerced_value = Integer(value) rescue nil
|
259
|
+
|
260
|
+
if coerced_value.nil?
|
261
|
+
raise ArgumentError.new("Failed to coerce value to Integer. Received #{value} (#{value.class})")
|
262
|
+
else
|
263
|
+
coerced_value
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
|
268
|
+
class PositiveInteger < Integer
|
269
|
+
def initialize(name, default=nil, strict=true)
|
270
|
+
super(name, default, strict) do |v|
|
271
|
+
if v > 0
|
272
|
+
true
|
273
|
+
else
|
274
|
+
raise ArgumentError.new("Number must be bigger than 0. Received: #{v}")
|
275
|
+
end
|
276
|
+
end
|
216
277
|
end
|
217
278
|
end
|
218
279
|
|
219
|
-
class Port <
|
280
|
+
class Port < Integer
|
281
|
+
VALID_PORT_RANGE = 1..65535
|
282
|
+
|
220
283
|
def initialize(name, default=nil, strict=true)
|
221
|
-
super(name,
|
284
|
+
super(name, default, strict) { |value| valid?(value) }
|
285
|
+
end
|
286
|
+
|
287
|
+
def valid?(port)
|
288
|
+
VALID_PORT_RANGE.cover?(port)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
class PortRange < Coercible
|
293
|
+
PORT_SEPARATOR = "-"
|
294
|
+
|
295
|
+
def initialize(name, default=nil, strict=true)
|
296
|
+
super(name, ::Range, default, strict=true) { |value| valid?(value) }
|
297
|
+
end
|
298
|
+
|
299
|
+
def valid?(range)
|
300
|
+
Port::VALID_PORT_RANGE.first <= range.first && Port::VALID_PORT_RANGE.last >= range.last
|
301
|
+
end
|
302
|
+
|
303
|
+
def coerce(value)
|
304
|
+
case value
|
305
|
+
when ::Range
|
306
|
+
value
|
307
|
+
when ::Fixnum
|
308
|
+
value..value
|
309
|
+
when ::String
|
310
|
+
first, last = value.split(PORT_SEPARATOR)
|
311
|
+
last = first if last.nil?
|
312
|
+
begin
|
313
|
+
(Integer(first))..(Integer(last))
|
314
|
+
rescue ArgumentError # Trap and reraise a more human error
|
315
|
+
raise ArgumentError.new("Could not coerce #{value} into a port range")
|
316
|
+
end
|
317
|
+
else
|
318
|
+
raise ArgumentError.new("Could not coerce #{value} into a port range")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
|
322
|
+
def validate(value)
|
323
|
+
unless valid?(value)
|
324
|
+
raise ArgumentError.new("Invalid value \"#{value}, valid options are within the range of #{Port::VALID_PORT_RANGE.first}-#{Port::VALID_PORT_RANGE.last}")
|
325
|
+
end
|
222
326
|
end
|
223
327
|
end
|
224
328
|
|
@@ -242,7 +346,7 @@ module LogStash
|
|
242
346
|
def validate(value)
|
243
347
|
super(value)
|
244
348
|
unless @possible_strings.empty? || @possible_strings.include?(value)
|
245
|
-
raise ArgumentError.new("
|
349
|
+
raise ArgumentError.new("Invalid value \"#{value}\". Options are: #{@possible_strings.inspect}")
|
246
350
|
end
|
247
351
|
end
|
248
352
|
end
|
@@ -270,7 +374,6 @@ module LogStash
|
|
270
374
|
end
|
271
375
|
end
|
272
376
|
end
|
273
|
-
|
274
377
|
end
|
275
378
|
|
276
379
|
SETTINGS = Settings.new
|
@@ -57,6 +57,9 @@ module LogStash; module Util
|
|
57
57
|
# Note that @infilght_batches as a central mechanism for tracking inflight
|
58
58
|
# batches will fail if we have multiple read clients in the pipeline.
|
59
59
|
@inflight_batches = {}
|
60
|
+
|
61
|
+
# allow the worker thread to report the execution time of the filter + output
|
62
|
+
@inflight_clocks = {}
|
60
63
|
@batch_size = batch_size
|
61
64
|
@wait_for = wait_for
|
62
65
|
end
|
@@ -89,6 +92,7 @@ module LogStash; module Util
|
|
89
92
|
batch = ReadBatch.new(@queue, @batch_size, @wait_for)
|
90
93
|
add_starting_metrics(batch)
|
91
94
|
set_current_thread_inflight_batch(batch)
|
95
|
+
start_clock
|
92
96
|
batch
|
93
97
|
end
|
94
98
|
end
|
@@ -100,11 +104,23 @@ module LogStash; module Util
|
|
100
104
|
def close_batch(batch)
|
101
105
|
@mutex.synchronize do
|
102
106
|
@inflight_batches.delete(Thread.current)
|
107
|
+
stop_clock
|
103
108
|
end
|
104
109
|
end
|
105
110
|
|
111
|
+
def start_clock
|
112
|
+
@inflight_clocks[Thread.current] = [
|
113
|
+
@event_metric.time(:duration_in_millis),
|
114
|
+
@pipeline_metric.time(:duration_in_millis)
|
115
|
+
]
|
116
|
+
end
|
117
|
+
|
118
|
+
def stop_clock
|
119
|
+
@inflight_clocks[Thread.current].each(&:stop)
|
120
|
+
@inflight_clocks.delete(Thread.current)
|
121
|
+
end
|
122
|
+
|
106
123
|
def add_starting_metrics(batch)
|
107
|
-
return if @event_metric.nil? || @pipeline_metric.nil?
|
108
124
|
@event_metric.increment(:in, batch.starting_size)
|
109
125
|
@pipeline_metric.increment(:in, batch.starting_size)
|
110
126
|
end
|
data/lib/logstash/version.rb
CHANGED
data/lib/logstash/webserver.rb
CHANGED
@@ -1,73 +1,84 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
require "logstash/api/rack_app"
|
2
3
|
require "puma"
|
3
4
|
require "puma/server"
|
4
|
-
require "
|
5
|
+
require "concurrent"
|
5
6
|
|
6
|
-
module LogStash
|
7
|
+
module LogStash
|
7
8
|
class WebServer
|
8
9
|
extend Forwardable
|
9
10
|
|
10
|
-
attr_reader :logger, :status, :config, :options, :
|
11
|
+
attr_reader :logger, :status, :config, :options, :runner, :binder, :events, :http_host, :http_ports, :http_environment, :agent
|
11
12
|
|
12
13
|
def_delegator :@runner, :stats
|
13
14
|
|
14
15
|
DEFAULT_HOST = "127.0.0.1".freeze
|
15
|
-
|
16
|
+
DEFAULT_PORTS = (9600..9700).freeze
|
16
17
|
DEFAULT_ENVIRONMENT = 'production'.freeze
|
17
18
|
|
18
19
|
def initialize(logger, agent, options={})
|
19
20
|
@logger = logger
|
20
21
|
@agent = agent
|
21
22
|
@http_host = options[:http_host] || DEFAULT_HOST
|
22
|
-
@
|
23
|
+
@http_ports = options[:http_ports] || DEFAULT_PORTS
|
23
24
|
@http_environment = options[:http_environment] || DEFAULT_ENVIRONMENT
|
24
25
|
@options = {}
|
25
|
-
@
|
26
|
-
|
27
|
-
:debug => logger.debug?,
|
28
|
-
# Prevent puma from queueing request when not able to properly handling them,
|
29
|
-
# fixed https://github.com/elastic/logstash/issues/4674. See
|
30
|
-
# https://github.com/puma/puma/pull/640 for mode internal details in PUMA.
|
31
|
-
:queue_requests => false
|
32
|
-
})
|
33
|
-
@status = nil
|
26
|
+
@status = nil
|
27
|
+
@running = Concurrent::AtomicBoolean.new(false)
|
34
28
|
end
|
35
29
|
|
36
30
|
def run
|
37
|
-
|
31
|
+
logger.debug("Starting puma")
|
38
32
|
|
39
33
|
stop # Just in case
|
40
34
|
|
41
|
-
|
42
|
-
@server = ::Puma::Server.new(app)
|
43
|
-
@server.add_tcp_listener(http_host, http_port)
|
35
|
+
running!
|
44
36
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
37
|
+
http_ports.each_with_index do |port, idx|
|
38
|
+
begin
|
39
|
+
if running?
|
40
|
+
@port = port
|
41
|
+
logger.debug("Trying to start WebServer", :port => @port)
|
42
|
+
start_webserver(@port)
|
43
|
+
else
|
44
|
+
break # we are closing down the server so just get out of the loop
|
45
|
+
end
|
46
|
+
rescue Errno::EADDRINUSE
|
47
|
+
if http_ports.count == 1
|
48
|
+
raise Errno::EADDRINUSE.new(I18n.t("logstash.web_api.cant_bind_to_port", :port => http_ports.first))
|
49
|
+
elsif idx == http_ports.count-1
|
50
|
+
raise Errno::EADDRINUSE.new(I18n.t("logstash.web_api.cant_bind_to_port_in_range", :http_ports => http_ports))
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
49
54
|
end
|
50
55
|
|
51
|
-
def
|
52
|
-
|
56
|
+
def running!
|
57
|
+
@running.make_true
|
53
58
|
end
|
54
59
|
|
55
|
-
def
|
56
|
-
|
60
|
+
def running?
|
61
|
+
@running.value
|
57
62
|
end
|
58
63
|
|
59
64
|
def address
|
60
|
-
"#{http_host}:#{
|
65
|
+
"#{http_host}:#{@port}"
|
61
66
|
end
|
62
|
-
|
63
|
-
# Empty method, this method is required because of the puma usage we make through
|
64
|
-
# the Single interface, https://github.com/puma/puma/blob/master/lib/puma/single.rb#L82
|
65
|
-
# for more details. This can always be implemented when we want to keep track of this
|
66
|
-
# bit of data.
|
67
|
-
def write_state; end
|
68
67
|
|
69
68
|
def stop(options={})
|
69
|
+
@running.make_false
|
70
70
|
@server.stop(true) if @server
|
71
71
|
end
|
72
|
+
|
73
|
+
def start_webserver(port)
|
74
|
+
app = LogStash::Api::RackApp.app(logger, agent, http_environment)
|
75
|
+
|
76
|
+
@server = ::Puma::Server.new(app)
|
77
|
+
@server.add_tcp_listener(http_host, port)
|
78
|
+
|
79
|
+
logger.info("Succesfully started Logstash API", :port => @port)
|
80
|
+
|
81
|
+
@server.run.join
|
82
|
+
end
|
72
83
|
end
|
73
84
|
end
|
data/locales/en.yml
CHANGED
@@ -73,12 +73,16 @@ en:
|
|
73
73
|
non_reloadable_config_register: |-
|
74
74
|
Logstash is not able to start since configuration auto reloading was enabled but the configuration contains plugins that don't support it. Quitting...
|
75
75
|
web_api:
|
76
|
+
cant_bind_to_port: |-
|
77
|
+
Logstash tried to bind to port %{port}, but the port is already in use. You can specify a new port by launching logtash with the --http-port option."
|
78
|
+
cant_bind_to_port_in_range: |-
|
79
|
+
Logstash tried to bind to port range %{http_ports}, but all the ports are already in use. You can specify a new port by launching logtash with the --http-port option."
|
76
80
|
hot_threads:
|
77
81
|
title: |-
|
78
82
|
::: {%{hostname}}
|
79
83
|
Hot threads at %{time}, busiestThreads=%{top_count}:
|
80
84
|
thread_title: |-
|
81
|
-
|
85
|
+
%{percent_of_cpu_time} % of cpu usage, state: %{thread_state}, thread name: '%{thread_name}'
|
82
86
|
runner:
|
83
87
|
short-help: |-
|
84
88
|
usage:
|
data/logstash-core.gemspec
CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
|
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
gem.version = LOGSTASH_CORE_VERSION
|
19
19
|
|
20
|
-
gem.add_runtime_dependency "logstash-core-event-java", "5.0.0.
|
20
|
+
gem.add_runtime_dependency "logstash-core-event-java", "5.0.0.alpha6.snapshot1"
|
21
21
|
|
22
22
|
gem.add_runtime_dependency "cabin", "~> 0.8.0" #(Apache 2.0 license)
|
23
23
|
gem.add_runtime_dependency "pry", "~> 0.10.1" #(Ruby license)
|
@@ -46,7 +46,7 @@ Gem::Specification.new do |gem|
|
|
46
46
|
|
47
47
|
if RUBY_PLATFORM == 'java'
|
48
48
|
gem.platform = RUBY_PLATFORM
|
49
|
-
gem.add_runtime_dependency "jrjackson", "~> 0.
|
49
|
+
gem.add_runtime_dependency "jrjackson", "~> 0.4.0" #(Apache 2.0 license)
|
50
50
|
else
|
51
51
|
gem.add_runtime_dependency "oj" #(MIT-style license)
|
52
52
|
end
|
@@ -39,30 +39,81 @@ describe LogStash::Api::Modules::Node do
|
|
39
39
|
end
|
40
40
|
|
41
41
|
context "when asking for human output" do
|
42
|
+
[
|
43
|
+
"/hot_threads?human",
|
44
|
+
"/hot_threads?human=true",
|
45
|
+
"/hot_threads?human=1",
|
46
|
+
"/hot_threads?human=t",
|
47
|
+
].each do |path|
|
48
|
+
|
49
|
+
before(:all) do
|
50
|
+
do_request { get path }
|
51
|
+
end
|
52
|
+
|
53
|
+
let(:payload) { last_response.body }
|
54
|
+
|
55
|
+
it "should return a text/plain content type" do
|
56
|
+
expect(last_response.content_type).to eq("text/plain;charset=utf-8")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should return a plain text payload" do
|
60
|
+
expect{ JSON.parse(payload) }.to raise_error
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
42
64
|
|
65
|
+
context "When asking for human output and threads count" do
|
43
66
|
before(:all) do
|
44
|
-
|
67
|
+
# Make sure we have enough threads for this to work.
|
68
|
+
@threads = []
|
69
|
+
5.times { @threads << Thread.new { loop {} } }
|
70
|
+
|
71
|
+
do_request { get "/hot_threads?human=t&threads=2"}
|
72
|
+
end
|
73
|
+
|
74
|
+
after(:all) do
|
75
|
+
@threads.each { |t| t.kill } rescue nil
|
45
76
|
end
|
46
77
|
|
47
78
|
let(:payload) { last_response.body }
|
48
79
|
|
49
|
-
it "should return
|
50
|
-
expect(
|
80
|
+
it "should return information for <= # requested threads" do
|
81
|
+
expect(payload.scan(/thread name/).size).to eq(2)
|
51
82
|
end
|
83
|
+
end
|
52
84
|
|
53
|
-
|
54
|
-
|
85
|
+
context "when not asking for human output" do
|
86
|
+
[
|
87
|
+
"/hot_threads?human=false",
|
88
|
+
"/hot_threads?human=0",
|
89
|
+
"/hot_threads?human=f",
|
90
|
+
].each do |path|
|
91
|
+
before(:all) do
|
92
|
+
do_request { get path }
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should return a json payload content type" do
|
96
|
+
expect(last_response.content_type).to eq("application/json")
|
97
|
+
end
|
98
|
+
|
99
|
+
let(:payload) { last_response.body }
|
100
|
+
|
101
|
+
it "should return a json payload" do
|
102
|
+
expect{ JSON.parse(payload) }.not_to raise_error
|
103
|
+
end
|
55
104
|
end
|
56
105
|
end
|
57
106
|
|
58
107
|
describe "Generic JSON testing" do
|
59
108
|
extend ResourceDSLMethods
|
60
|
-
|
109
|
+
|
61
110
|
root_structure = {
|
62
111
|
"pipeline" => {
|
63
112
|
"workers" => Numeric,
|
64
113
|
"batch_size" => Numeric,
|
65
|
-
"batch_delay" => Numeric
|
114
|
+
"batch_delay" => Numeric,
|
115
|
+
"config_reload_automatic" => Boolean,
|
116
|
+
"config_reload_interval" => Numeric
|
66
117
|
},
|
67
118
|
"os" => {
|
68
119
|
"name" => String,
|
@@ -82,7 +133,8 @@ describe LogStash::Api::Modules::Node do
|
|
82
133
|
"heap_max_in_bytes" => Numeric,
|
83
134
|
"non_heap_init_in_bytes" => Numeric,
|
84
135
|
"non_heap_max_in_bytes" => Numeric
|
85
|
-
|
136
|
+
},
|
137
|
+
"gc_collectors" => Array
|
86
138
|
},
|
87
139
|
"hot_threads"=> {
|
88
140
|
"time" => String,
|
@@ -90,8 +142,8 @@ describe LogStash::Api::Modules::Node do
|
|
90
142
|
"threads" => Array
|
91
143
|
}
|
92
144
|
}
|
93
|
-
|
145
|
+
|
94
146
|
test_api_and_resources(root_structure, :exclude_from_root => ["hot_threads"])
|
95
|
-
end
|
147
|
+
end
|
96
148
|
end
|
97
149
|
end
|