puma-log_stats-codeur 0.1.1 → 0.1.3
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.
- checksums.yaml +4 -4
- data/README.md +5 -2
- data/lib/puma/log_stats/version.rb +1 -1
- data/lib/puma/log_stats.rb +0 -2
- data/lib/puma/plugin/log_stats.rb +101 -198
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dccacaaaed068382710999238bf1563335653ba81ac0afe70373eb53fc6c4ec
|
4
|
+
data.tar.gz: 3183d3640deb3b060873061e10087c06e429aa0be8d60ba0b8924adf3c379890
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 060ed7df03b49ccfb212d4d18f117bff2f4fc054fc6dcece408d11efbd8bbacf4f958c9e7e46251fb3da1703318afb60df7bad77723038677febf1197cc5d7bf
|
7
|
+
data.tar.gz: fbf036c802861ff041d4a3c777af0767b6c8e0d73cebb489c2049dea6183036f16bd6964cd8b8c54312a244502454271ead82259e2cb4abcee93f82811919a6c
|
data/README.md
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# PumaLogStats
|
2
2
|
|
3
|
-
Puma plugin to log server stats
|
3
|
+
Puma plugin to log server stats to puma.log. It logs changes only and can raise Sentry issues when a threshold is reached.
|
4
4
|
|
5
5
|
## Installation
|
6
6
|
|
@@ -26,7 +26,10 @@ This plugin is loaded using Puma's plugin API. To enable, add a `plugin :log_sta
|
|
26
26
|
# config/puma.rb
|
27
27
|
|
28
28
|
plugin :log_stats
|
29
|
-
LogStats.
|
29
|
+
# LogStats.interval = 10
|
30
|
+
# LogStats.notify_change_with = :sentry # can be a Proc
|
31
|
+
# LogStats.warning_threshold = 0.7
|
32
|
+
# LogStats.critical_threshold = 0.85
|
30
33
|
```
|
31
34
|
|
32
35
|
## Development
|
data/lib/puma/log_stats.rb
CHANGED
@@ -2,237 +2,140 @@
|
|
2
2
|
|
3
3
|
require "puma/plugin"
|
4
4
|
|
5
|
-
module
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
5
|
+
module Puma
|
6
|
+
module LogStats
|
7
|
+
class << self
|
8
|
+
# Interval between logging attempts in seconds.
|
9
|
+
attr_accessor :interval
|
10
|
+
LogStats.interval = 10
|
10
11
|
|
11
|
-
|
12
|
-
|
12
|
+
attr_accessor :notify_change_with
|
13
|
+
LogStats.notify_change_with = :sentry
|
13
14
|
|
14
|
-
|
15
|
-
|
15
|
+
attr_accessor :warning_threshold
|
16
|
+
LogStats.warning_threshold = 0.7
|
16
17
|
|
17
|
-
|
18
|
-
|
18
|
+
attr_accessor :critical_threshold
|
19
|
+
LogStats.critical_threshold = 0.85
|
20
|
+
end
|
19
21
|
|
20
|
-
|
21
|
-
|
22
|
-
|
22
|
+
def start(launcher)
|
23
|
+
@launcher = launcher
|
24
|
+
launcher.events.register(:state) do |state|
|
25
|
+
if %i[halt restart stop].include?(state)
|
26
|
+
@running = false
|
27
|
+
end
|
28
|
+
end
|
23
29
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
30
|
+
in_background do
|
31
|
+
@running = true
|
32
|
+
@load_level = :normal
|
33
|
+
while @running
|
34
|
+
sleep LogStats.interval
|
35
|
+
@previous_status_message = @status_message
|
36
|
+
@stats = Puma.stats_hash
|
37
|
+
@status_message = status_message
|
38
|
+
log(@status_message) if @previous_status_message != @status_message
|
39
|
+
check_alarms
|
40
|
+
end
|
29
41
|
end
|
30
42
|
end
|
31
43
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
sleep LogStats.interval
|
37
|
-
@stats = Puma.stats_hash
|
38
|
-
log(status)
|
39
|
-
check_alarms
|
40
|
-
end
|
44
|
+
def check_alarms
|
45
|
+
threshold_reached(:critical, LogStats.critical_threshold) ||
|
46
|
+
threshold_reached(:warning, LogStats.warning_threshold) ||
|
47
|
+
normal_load
|
41
48
|
end
|
42
|
-
end
|
43
49
|
|
44
|
-
|
45
|
-
|
46
|
-
threshold_reached(:warning, LogStats.warning_threshold) ||
|
47
|
-
normal_load
|
48
|
-
end
|
50
|
+
def threshold_reached(level, threshold)
|
51
|
+
return false if threads_load < threshold
|
49
52
|
|
50
|
-
|
51
|
-
|
53
|
+
change_level(level, "Puma threads load is greater than #{threshold * 100}% (#{max_threads - pool_capacity}/#{max_threads})")
|
54
|
+
true
|
55
|
+
end
|
52
56
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
end
|
57
|
+
def normal_load
|
58
|
+
change_level(:normal, "Puma threads load is back to normal values")
|
59
|
+
end
|
57
60
|
|
58
|
-
|
59
|
-
|
61
|
+
def change_level(level, message)
|
62
|
+
return if @load_level == level
|
60
63
|
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
+
log("#{level.to_s.upcase}: #{message}")
|
65
|
+
notify(level, message)
|
66
|
+
@load_level = level
|
67
|
+
end
|
64
68
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
69
|
+
def notify(level, message)
|
70
|
+
if LogStats.notify_change_with == :sentry
|
71
|
+
Sentry.capture_message(message, level: (level == :critical) ? :error : level) if defined?(Sentry) && level != :normal
|
72
|
+
elsif LogStats.notify_change_with.respond_to?(:call)
|
73
|
+
LogStats.notify_change_with.call(level: level, message: message, threads_load: threads_load)
|
74
|
+
end
|
70
75
|
end
|
71
|
-
end
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
77
|
+
def status_message
|
78
|
+
if clustered?
|
79
|
+
"cluster: #{booted_workers}/#{workers} workers: #{running}/#{max_threads} threads, #{pool_capacity} available, #{backlog} backlog"
|
80
|
+
else
|
81
|
+
"single: #{running}/#{max_threads} threads, #{pool_capacity} available, #{backlog} backlog"
|
82
|
+
end
|
78
83
|
end
|
79
|
-
end
|
80
84
|
|
81
|
-
|
82
|
-
|
83
|
-
|
85
|
+
def log(str)
|
86
|
+
@launcher.log_writer.log("[#{Time.now}][puma #{Puma::Const::VERSION}] #{str}")
|
87
|
+
end
|
84
88
|
|
85
|
-
|
86
|
-
|
87
|
-
|
89
|
+
def threads_load
|
90
|
+
1.0 - pool_capacity.to_f / max_threads.to_f
|
91
|
+
end
|
88
92
|
|
89
|
-
|
90
|
-
|
91
|
-
|
93
|
+
def clustered?
|
94
|
+
@stats.key?(:workers)
|
95
|
+
end
|
92
96
|
|
93
|
-
|
94
|
-
|
95
|
-
|
97
|
+
def workers
|
98
|
+
@stats.fetch(:workers, 1)
|
99
|
+
end
|
96
100
|
|
97
|
-
|
98
|
-
|
99
|
-
|
101
|
+
def booted_workers
|
102
|
+
@stats.fetch(:booted_workers, 1)
|
103
|
+
end
|
100
104
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
105
|
+
def running
|
106
|
+
if clustered?
|
107
|
+
@stats[:worker_status].sum { |s| s[:last_status].fetch(:running, 0) }
|
108
|
+
else
|
109
|
+
@stats.fetch(:running, 0)
|
110
|
+
end
|
106
111
|
end
|
107
|
-
end
|
108
112
|
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
113
|
+
def backlog
|
114
|
+
if clustered?
|
115
|
+
@stats[:worker_status].sum { |s| s[:last_status].fetch(:backlog, 0) }
|
116
|
+
else
|
117
|
+
@stats.fetch(:backlog, 0)
|
118
|
+
end
|
114
119
|
end
|
115
|
-
end
|
116
120
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
121
|
+
def pool_capacity
|
122
|
+
if clustered?
|
123
|
+
@stats[:worker_status].sum { |s| s[:last_status].fetch(:pool_capacity, 0) }
|
124
|
+
else
|
125
|
+
@stats.fetch(:pool_capacity, 0)
|
126
|
+
end
|
122
127
|
end
|
123
|
-
end
|
124
128
|
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
129
|
+
def max_threads
|
130
|
+
if clustered?
|
131
|
+
@stats[:worker_status].sum { |s| s[:last_status].fetch(:max_threads, 0) }
|
132
|
+
else
|
133
|
+
@stats.fetch(:max_threads, 0)
|
134
|
+
end
|
130
135
|
end
|
131
136
|
end
|
132
137
|
end
|
133
138
|
|
134
139
|
Puma::Plugin.create do
|
135
|
-
include LogStats
|
140
|
+
include Puma::LogStats
|
136
141
|
end
|
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
|