@@ -8,11 +8,8 @@ class << self
88 attr_accessor :interval
99 LogStats . interval = 10
1010
11- attr_accessor :alarm_on_sentry
12- LogStats . alarm_on_sentry = true
13-
14- attr_accessor :alarm_notification_interval
15- LogStats . alarm_notification_interval = 60
11+ attr_accessor :notify_change_with
12+ LogStats . notify_change_with = :sentry
1613
1714 attr_accessor :warning_threshold
1815 LogStats . warning_threshold = 0.7
@@ -34,8 +31,10 @@ def start(launcher)
3431 @load_level = :normal
3532 while @running
3633 sleep LogStats . interval
34+ @previous_status_message = @status_message
3735 @stats = Puma . stats_hash
38- log ( status )
36+ @status_message = status_message
37+ log ( @status_message ) if @previous_status_message != @status_message
3938 check_alarms
4039 end
4140 end
@@ -50,27 +49,31 @@ def check_alarms
5049 def threshold_reached ( level , threshold )
5150 return false if threads_load < threshold
5251
53- notify_alarm ( "#{ level . to_s . upcase } : Puma threads load is more than #{ threshold * 100 } % (#{ max_threads - pool_capacity } /#{ max_threads } )" )
54- @load_level = level if @load_level != level
52+ change_level ( level , "Puma threads load is greater than #{ threshold * 100 } % (#{ max_threads - pool_capacity } /#{ max_threads } )" )
5553 true
5654 end
5755
5856 def normal_load
59- return if @load_level == :normal
57+ change_level ( :normal , "Puma threads load is back to normal values" )
58+ end
6059
61- log ( "INFO: Puma threads load is back to normal values" )
62- @load_level = :normal
60+ def change_level ( level , message )
61+ return if @load_level == level
62+
63+ log ( "#{ level . to_s . upcase } : #{ message } " )
64+ notify ( level , message )
65+ @load_level = level
6366 end
6467
65- def notify_alarm ( message )
66- if @notified_at . nil? || ( Time . now - @notified_at ) < LogStats . alarm_notification_interval
67- log ( message )
68- Sentry . capture_message ( message ) if LogStats . alarm_on_sentry && defined? ( Sentry )
69- @notified_at = Time . now
68+ def notify ( level , message )
69+ if LogStats . notify_change_with == :sentry
70+ Sentry . capture_message ( message , level : level == :critical ? :error : level ) if defined? ( Sentry ) && level != :normal
71+ elsif LogStats . notify_change_with . respond_to? ( :call )
72+ LogStats . notify_change_with . call ( level : level , message : message , threads_load : threads_load )
7073 end
7174 end
7275
73- def status
76+ def status_message
7477 if clustered?
7578 "cluster: #{ booted_workers } /#{ workers } workers: #{ running } /#{ max_threads } threads, #{ pool_capacity } available, #{ backlog } backlog"
7679 else
@@ -134,105 +137,3 @@ def max_threads
134137Puma ::Plugin . create do
135138 include LogStats
136139end
137-
138- # require "puma"
139- # require "puma/plugin"
140- # require "json"
141-
142- # # Puma plugin to log server stats whenever the number of
143- # # concurrent requests exceeds a configured threshold.
144- # module LogStats
145- # STAT_METHODS = %i[backlog running pool_capacity max_threads requests_count].freeze
146-
147- # class << self
148- # # Minimum concurrent requests per process that will trigger logging server
149- # # stats, or nil to disable logging.
150- # # Default is the max number of threads in the server's thread pool.
151- # # If this attribute is a Proc, it will be re-evaluated each interval.
152- # attr_accessor :threshold
153- # LogStats.threshold = :max
154-
155- # # Interval between logging attempts in seconds.
156- # attr_accessor :interval
157- # LogStats.interval = 1
158-
159- # # Proc to filter backtraces.
160- # attr_accessor :backtrace_filter
161- # LogStats.backtrace_filter = ->(bt) { bt }
162- # end
163-
164- # Puma::Plugin.create do
165- # attr_reader :launcher
166-
167- # def start(launcher)
168- # @launcher = launcher
169- # launcher.events.register(:state) do |state|
170- # @state = state
171- # stats_logger_thread if state == :running
172- # end
173-
174- # in_background { start }
175- # end
176-
177- # private
178-
179- # def stats_logger_thread
180- # Thread.new do
181- # if Thread.current.respond_to?(:name=)
182- # Thread.current.name = "puma stats logger"
183- # end
184- # start while @state == :running
185- # end
186- # end
187-
188- # def start
189- # sleep LogStats.interval
190- # return unless server
191-
192- # if should_log?
193- # stats = server_stats
194- # stats[:threads] = thread_backtraces
195- # stats[:gc] = GC.stat
196- # log stats.to_json
197- # end
198- # rescue => e
199- # log "LogStats failed: #{e}\n #{e.backtrace.join("\n ")}"
200- # end
201-
202- # def log(str)
203- # launcher.log_writer.log str
204- # end
205-
206- # # Save reference to Server object from the thread-local key.
207- # def server
208- # @server ||= Thread.list.map { |t| t[Puma::Server::ThreadLocalKey] }.compact.first
209- # end
210-
211- # def server_stats
212- # STAT_METHODS.select(&server.method(:respond_to?))
213- # .map { |name| [name, server.send(name) || 0] }.to_h
214- # end
215-
216- # # True if current server load meets configured threshold.
217- # def should_log?
218- # threshold = LogStats.threshold
219- # threshold = threshold.call if threshold.is_a?(Proc)
220- # threshold = server.max_threads if threshold == :max
221- # threshold && (server.max_threads - server.pool_capacity) >= threshold
222- # end
223-
224- # def thread_backtraces
225- # worker_threads.map do |t|
226- # name = t.respond_to?(:name) ? t.name : thread.object_id.to_s(36)
227- # [name, LogStats.backtrace_filter.call(t.backtrace)]
228- # end.sort.to_h
229- # end
230-
231- # # List all non-idle worker threads in the thread pool.
232- # def worker_threads
233- # server.instance_variable_get(:@thread_pool)
234- # .instance_variable_get(:@workers)
235- # .reject { |t| t.backtrace.first.match?(/thread_pool\.rb.*sleep/) }
236- # end
237- # end
238- # end
0 commit comments