app_profiler 0.1.2 → 0.1.4
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/lib/app_profiler/middleware/upload_action.rb +13 -10
- data/lib/app_profiler/middleware.rb +4 -4
- data/lib/app_profiler/parameters.rb +35 -0
- data/lib/app_profiler/profile.rb +4 -0
- data/lib/app_profiler/railtie.rb +5 -2
- data/lib/app_profiler/request_parameters.rb +7 -7
- data/lib/app_profiler/server.rb +42 -18
- data/lib/app_profiler/storage/base_storage.rb +4 -0
- data/lib/app_profiler/storage/file_storage.rb +4 -0
- data/lib/app_profiler/storage/google_cloud_storage.rb +51 -0
- data/lib/app_profiler/version.rb +1 -1
- data/lib/app_profiler.rb +4 -0
- metadata +4 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: eaffd76839111b3280bce8241889441b85b4346121e1478cc60839f791916445
|
4
|
+
data.tar.gz: f9aab66b3b52953756610b9acb8a314178ce535075d8702d4ced0624e4880409
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c299676be9b01c62ab73885f1e6b8c6b787721acbe94266b71fec79d91e623bd599aee7b6607eb17dda0f0187a1024c43227c9bae256b660cd8155f41a9dacf4
|
7
|
+
data.tar.gz: f1fd90215f29e46175ca3cd3f7bbaeed4448a40acf07e25aa1a1f7c59d8b10cc66854e0c6453cdc4367a469b03e16c8b930da6d53034e0e1d92e1b9dd4b5b5d4
|
@@ -4,16 +4,19 @@ module AppProfiler
|
|
4
4
|
class Middleware
|
5
5
|
class UploadAction < BaseAction
|
6
6
|
class << self
|
7
|
-
def call(profile, response: nil, autoredirect: nil)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
7
|
+
def call(profile, response: nil, autoredirect: nil, async: false)
|
8
|
+
if async
|
9
|
+
profile.enqueue_upload
|
10
|
+
response[1][AppProfiler.profile_async_header] = true
|
11
|
+
else
|
12
|
+
profile_upload = profile.upload
|
13
|
+
|
14
|
+
append_headers(
|
15
|
+
response,
|
16
|
+
upload: profile_upload,
|
17
|
+
autoredirect: autoredirect.nil? ? AppProfiler.autoredirect : autoredirect
|
18
|
+
) if response
|
19
|
+
end
|
17
20
|
end
|
18
21
|
|
19
22
|
private
|
@@ -14,16 +14,15 @@ module AppProfiler
|
|
14
14
|
@app = app
|
15
15
|
end
|
16
16
|
|
17
|
-
def call(env)
|
18
|
-
profile(env) do
|
17
|
+
def call(env, params = AppProfiler::RequestParameters.new(Rack::Request.new(env)))
|
18
|
+
profile(env, params) do
|
19
19
|
@app.call(env)
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
23
|
private
|
24
24
|
|
25
|
-
def profile(env)
|
26
|
-
params = AppProfiler::RequestParameters.new(Rack::Request.new(env))
|
25
|
+
def profile(env, params)
|
27
26
|
response = nil
|
28
27
|
|
29
28
|
return yield unless params.valid?
|
@@ -42,6 +41,7 @@ module AppProfiler
|
|
42
41
|
profile,
|
43
42
|
response: response,
|
44
43
|
autoredirect: params.autoredirect,
|
44
|
+
async: params.async
|
45
45
|
)
|
46
46
|
|
47
47
|
response
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rack"
|
4
|
+
|
5
|
+
module AppProfiler
|
6
|
+
class Parameters
|
7
|
+
DEFAULT_INTERVALS = { "cpu" => 1000, "wall" => 1000, "object" => 2000 }.freeze
|
8
|
+
MIN_INTERVALS = { "cpu" => 200, "wall" => 200, "object" => 400 }.freeze
|
9
|
+
MODES = DEFAULT_INTERVALS.keys.freeze
|
10
|
+
|
11
|
+
attr_reader :autoredirect, :async
|
12
|
+
|
13
|
+
def initialize(mode: :wall, interval: nil, ignore_gc: false, autoredirect: false, async: false, metadata: {})
|
14
|
+
@mode = mode.to_sym
|
15
|
+
@interval = [interval&.to_i || DEFAULT_INTERVALS.fetch(@mode.to_s), MIN_INTERVALS.fetch(@mode.to_s)].max
|
16
|
+
@ignore_gc = !!ignore_gc
|
17
|
+
@autoredirect = autoredirect
|
18
|
+
@metadata = { context: AppProfiler.context }.merge(metadata)
|
19
|
+
@async = async
|
20
|
+
end
|
21
|
+
|
22
|
+
def valid?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_h
|
27
|
+
{
|
28
|
+
mode: @mode,
|
29
|
+
interval: @interval,
|
30
|
+
ignore_gc: @ignore_gc,
|
31
|
+
metadata: @metadata,
|
32
|
+
}
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/app_profiler/profile.rb
CHANGED
data/lib/app_profiler/railtie.rb
CHANGED
@@ -28,11 +28,14 @@ module AppProfiler
|
|
28
28
|
"APP_PROFILER_SPEEDSCOPE_URL", "https://speedscope.app"
|
29
29
|
)
|
30
30
|
AppProfiler.profile_header = app.config.app_profiler.profile_header || "X-Profile"
|
31
|
+
AppProfiler.profile_async_header = app.config.app_profiler.profile_async_header || "X-Profile-Async"
|
31
32
|
AppProfiler.profile_root = app.config.app_profiler.profile_root || Rails.root.join(
|
32
33
|
"tmp", "app_profiler"
|
33
34
|
)
|
34
35
|
AppProfiler.context = app.config.app_profiler.context || Rails.env
|
35
36
|
AppProfiler.profile_url_formatter = app.config.app_profiler.profile_url_formatter
|
37
|
+
AppProfiler.upload_queue_max_length = app.config.app_profiler.upload_queue_max_length || 10
|
38
|
+
AppProfiler.upload_queue_interval_secs = app.config.app_profiler.upload_queue_interval_secs || 5
|
36
39
|
end
|
37
40
|
|
38
41
|
initializer "app_profiler.add_middleware" do |app|
|
@@ -46,9 +49,9 @@ module AppProfiler
|
|
46
49
|
|
47
50
|
initializer "app_profiler.enable_server" do
|
48
51
|
if AppProfiler.server.enabled
|
49
|
-
AppProfiler::Server.start
|
52
|
+
AppProfiler::Server.start(AppProfiler.logger)
|
50
53
|
ActiveSupport::ForkTracker.after_fork do
|
51
|
-
AppProfiler::Server.start
|
54
|
+
AppProfiler::Server.start(AppProfiler.logger)
|
52
55
|
end
|
53
56
|
end
|
54
57
|
end
|
@@ -4,10 +4,6 @@ require "rack"
|
|
4
4
|
|
5
5
|
module AppProfiler
|
6
6
|
class RequestParameters
|
7
|
-
DEFAULT_INTERVALS = { "cpu" => 1000, "wall" => 1000, "object" => 2000 }.freeze
|
8
|
-
MIN_INTERVALS = { "cpu" => 200, "wall" => 200, "object" => 400 }.freeze
|
9
|
-
MODES = DEFAULT_INTERVALS.keys.freeze
|
10
|
-
|
11
7
|
def initialize(request)
|
12
8
|
@request = request
|
13
9
|
end
|
@@ -16,17 +12,21 @@ module AppProfiler
|
|
16
12
|
query_param("autoredirect") || profile_header_param("autoredirect")
|
17
13
|
end
|
18
14
|
|
15
|
+
def async
|
16
|
+
query_param("async")
|
17
|
+
end
|
18
|
+
|
19
19
|
def valid?
|
20
20
|
if mode.blank?
|
21
21
|
return false
|
22
22
|
end
|
23
23
|
|
24
|
-
unless MODES.include?(mode)
|
24
|
+
unless Parameters::MODES.include?(mode)
|
25
25
|
AppProfiler.logger.info("[Profiler] unsupported profiling mode=#{mode}")
|
26
26
|
return false
|
27
27
|
end
|
28
28
|
|
29
|
-
if interval.to_i < MIN_INTERVALS[mode]
|
29
|
+
if interval.to_i < Parameters::MIN_INTERVALS[mode]
|
30
30
|
return false
|
31
31
|
end
|
32
32
|
|
@@ -56,7 +56,7 @@ module AppProfiler
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def interval
|
59
|
-
query_param("interval") || profile_header_param("interval") || DEFAULT_INTERVALS[mode]
|
59
|
+
query_param("interval") || profile_header_param("interval") || Parameters::DEFAULT_INTERVALS[mode]
|
60
60
|
end
|
61
61
|
|
62
62
|
def request_id
|
data/lib/app_profiler/server.rb
CHANGED
@@ -180,6 +180,14 @@ module AppProfiler
|
|
180
180
|
class Transport
|
181
181
|
attr_reader :socket
|
182
182
|
|
183
|
+
def initialize
|
184
|
+
start
|
185
|
+
end
|
186
|
+
|
187
|
+
def start
|
188
|
+
raise(NotImplementedError)
|
189
|
+
end
|
190
|
+
|
183
191
|
def client
|
184
192
|
raise(NotImplementedError)
|
185
193
|
end
|
@@ -190,9 +198,7 @@ module AppProfiler
|
|
190
198
|
end
|
191
199
|
|
192
200
|
class UNIX < Transport
|
193
|
-
def
|
194
|
-
super
|
195
|
-
|
201
|
+
def start
|
196
202
|
FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
|
197
203
|
@socket_file = File.join(PROFILER_TEMPFILE_PATH, "app-profiler-#{Process.pid}.sock")
|
198
204
|
File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
|
@@ -213,9 +219,13 @@ module AppProfiler
|
|
213
219
|
SERVER_ADDRESS = "127.0.0.1" # it is ONLY safe to run this bound to localhost
|
214
220
|
|
215
221
|
def initialize(port = 0)
|
222
|
+
@port_argument = port
|
216
223
|
super()
|
224
|
+
end
|
225
|
+
|
226
|
+
def start
|
217
227
|
FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
|
218
|
-
@socket = TCPServer.new(SERVER_ADDRESS,
|
228
|
+
@socket = TCPServer.new(SERVER_ADDRESS, @port_argument)
|
219
229
|
@port = @socket.addr[1]
|
220
230
|
@port_file = Tempfile.new("profileserver-#{Process.pid}-port-#{@port}-", PROFILER_TEMPFILE_PATH)
|
221
231
|
end
|
@@ -230,7 +240,8 @@ module AppProfiler
|
|
230
240
|
end
|
231
241
|
end
|
232
242
|
|
233
|
-
def initialize(transport)
|
243
|
+
def initialize(transport, logger)
|
244
|
+
@logger = logger
|
234
245
|
case transport
|
235
246
|
when TRANSPORT_UNIX
|
236
247
|
@transport = ProfileServer::UNIX.new
|
@@ -242,7 +253,7 @@ module AppProfiler
|
|
242
253
|
|
243
254
|
@listen_thread = nil
|
244
255
|
|
245
|
-
|
256
|
+
@logger.info(
|
246
257
|
"[AppProfiler::Server] listening on addr=#{@transport.socket.addr}"
|
247
258
|
)
|
248
259
|
@pid = Process.pid
|
@@ -260,7 +271,15 @@ module AppProfiler
|
|
260
271
|
|
261
272
|
@listen_thread = Thread.new do
|
262
273
|
loop do
|
263
|
-
|
274
|
+
session = begin
|
275
|
+
@transport.socket.accept
|
276
|
+
rescue
|
277
|
+
@transport.close
|
278
|
+
@transport.start
|
279
|
+
next
|
280
|
+
end
|
281
|
+
|
282
|
+
Thread.new(session) do |session|
|
264
283
|
request = session.gets
|
265
284
|
|
266
285
|
if request.nil?
|
@@ -293,7 +312,7 @@ module AppProfiler
|
|
293
312
|
session.print(part)
|
294
313
|
end
|
295
314
|
rescue => e
|
296
|
-
|
315
|
+
@logger.error(
|
297
316
|
"[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}"
|
298
317
|
)
|
299
318
|
ensure
|
@@ -314,19 +333,22 @@ module AppProfiler
|
|
314
333
|
|
315
334
|
private_constant :ProfileApplication, :ProfileServer
|
316
335
|
|
336
|
+
@pid = Process.pid
|
337
|
+
@profile_server = nil
|
338
|
+
|
317
339
|
class << self
|
318
340
|
def reset
|
319
|
-
|
341
|
+
self.profile_server = nil
|
320
342
|
|
321
343
|
DEFAULTS.each do |config, value|
|
322
344
|
class_variable_set(:"@@#{config}", value) # rubocop:disable Style/ClassVars
|
323
345
|
end
|
324
346
|
end
|
325
347
|
|
326
|
-
def start
|
348
|
+
def start(logger = Logger.new(IO::NULL))
|
327
349
|
return if profile_server
|
328
350
|
|
329
|
-
|
351
|
+
self.profile_server = ProfileServer.new(AppProfiler::Server.transport, logger)
|
330
352
|
profile_server.serve
|
331
353
|
profile_server
|
332
354
|
end
|
@@ -340,20 +362,22 @@ module AppProfiler
|
|
340
362
|
def stop
|
341
363
|
return unless profile_server
|
342
364
|
|
343
|
-
profile_server
|
344
|
-
|
365
|
+
server = profile_server
|
366
|
+
server.stop
|
367
|
+
self.profile_server = nil
|
368
|
+
server
|
345
369
|
end
|
346
370
|
|
347
371
|
private
|
348
372
|
|
349
373
|
def profile_server
|
350
|
-
|
374
|
+
@profile_server = nil if @pid != Process.pid
|
375
|
+
@profile_server
|
351
376
|
end
|
352
377
|
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
@profile_servers ||= {}
|
378
|
+
def profile_server=(server)
|
379
|
+
@pid = Process.pid
|
380
|
+
@profile_server = server
|
357
381
|
end
|
358
382
|
end
|
359
383
|
end
|
@@ -28,8 +28,59 @@ module AppProfiler
|
|
28
28
|
end
|
29
29
|
end
|
30
30
|
|
31
|
+
def enqueue_upload(profile)
|
32
|
+
mutex.synchronize do
|
33
|
+
process_queue_thread unless @process_queue_thread&.alive?
|
34
|
+
|
35
|
+
@queue ||= init_queue
|
36
|
+
begin
|
37
|
+
@queue.push(profile, true) # non-blocking push, raises ThreadError if queue is full
|
38
|
+
rescue ThreadError
|
39
|
+
AppProfiler.logger.info("[AppProfiler] upload queue is full, profile discarded")
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def reset_queue # for testing
|
45
|
+
init_queue
|
46
|
+
@process_queue_thread&.kill
|
47
|
+
@process_queue_thread = nil
|
48
|
+
end
|
49
|
+
|
31
50
|
private
|
32
51
|
|
52
|
+
def mutex
|
53
|
+
@mutex ||= Mutex.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def init_queue
|
57
|
+
@queue = SizedQueue.new(AppProfiler.upload_queue_max_length)
|
58
|
+
end
|
59
|
+
|
60
|
+
def process_queue_thread
|
61
|
+
@process_queue_thread ||= Thread.new do
|
62
|
+
loop do
|
63
|
+
process_queue
|
64
|
+
sleep(AppProfiler.upload_queue_interval_secs)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
@process_queue_thread.priority = -1 # low priority
|
68
|
+
end
|
69
|
+
|
70
|
+
def process_queue
|
71
|
+
queue = nil
|
72
|
+
mutex.synchronize do
|
73
|
+
break if @queue.nil? || @queue.empty?
|
74
|
+
|
75
|
+
queue = @queue
|
76
|
+
init_queue
|
77
|
+
end
|
78
|
+
|
79
|
+
return unless queue
|
80
|
+
|
81
|
+
queue.size.times { queue.pop(false).upload }
|
82
|
+
end
|
83
|
+
|
33
84
|
def gcs_filename(profile)
|
34
85
|
File.join(profile.context.to_s, profile.file.basename)
|
35
86
|
end
|
data/lib/app_profiler/version.rb
CHANGED
data/lib/app_profiler.rb
CHANGED
@@ -26,6 +26,7 @@ module AppProfiler
|
|
26
26
|
end
|
27
27
|
|
28
28
|
require "app_profiler/middleware"
|
29
|
+
require "app_profiler/parameters"
|
29
30
|
require "app_profiler/request_parameters"
|
30
31
|
require "app_profiler/profiler"
|
31
32
|
require "app_profiler/profile"
|
@@ -38,6 +39,7 @@ module AppProfiler
|
|
38
39
|
mattr_accessor :speedscope_host, default: "https://speedscope.app"
|
39
40
|
mattr_accessor :autoredirect, default: false
|
40
41
|
mattr_reader :profile_header, default: "X-Profile"
|
42
|
+
mattr_accessor :profile_async_header, default: "X-Profile-Async"
|
41
43
|
mattr_accessor :context, default: nil
|
42
44
|
mattr_reader :profile_url_formatter,
|
43
45
|
default: DefaultProfileFormatter
|
@@ -46,6 +48,8 @@ module AppProfiler
|
|
46
48
|
mattr_accessor :viewer, default: Viewer::SpeedscopeViewer
|
47
49
|
mattr_accessor :middleware, default: Middleware
|
48
50
|
mattr_accessor :server, default: Server
|
51
|
+
mattr_accessor :upload_queue_max_length, default: 10
|
52
|
+
mattr_accessor :upload_queue_interval_secs, default: 5
|
49
53
|
|
50
54
|
class << self
|
51
55
|
def run(*args, &block)
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: app_profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gannon McGibbon
|
@@ -13,7 +13,7 @@ authors:
|
|
13
13
|
autorequire:
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
|
-
date:
|
16
|
+
date: 2023-09-20 00:00:00.000000000 Z
|
17
17
|
dependencies:
|
18
18
|
- !ruby/object:Gem::Dependency
|
19
19
|
name: activesupport
|
@@ -43,20 +43,6 @@ dependencies:
|
|
43
43
|
- - ">="
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
|
-
- !ruby/object:Gem::Dependency
|
47
|
-
name: railties
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
49
|
-
requirements:
|
50
|
-
- - ">="
|
51
|
-
- !ruby/object:Gem::Version
|
52
|
-
version: '5.2'
|
53
|
-
type: :runtime
|
54
|
-
prerelease: false
|
55
|
-
version_requirements: !ruby/object:Gem::Requirement
|
56
|
-
requirements:
|
57
|
-
- - ">="
|
58
|
-
- !ruby/object:Gem::Version
|
59
|
-
version: '5.2'
|
60
46
|
- !ruby/object:Gem::Dependency
|
61
47
|
name: stackprof
|
62
48
|
requirement: !ruby/object:Gem::Requirement
|
@@ -153,6 +139,7 @@ files:
|
|
153
139
|
- lib/app_profiler/middleware/base_action.rb
|
154
140
|
- lib/app_profiler/middleware/upload_action.rb
|
155
141
|
- lib/app_profiler/middleware/view_action.rb
|
142
|
+
- lib/app_profiler/parameters.rb
|
156
143
|
- lib/app_profiler/profile.rb
|
157
144
|
- lib/app_profiler/profiler.rb
|
158
145
|
- lib/app_profiler/railtie.rb
|
@@ -188,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
175
|
- !ruby/object:Gem::Version
|
189
176
|
version: '0'
|
190
177
|
requirements: []
|
191
|
-
rubygems_version: 3.
|
178
|
+
rubygems_version: 3.4.19
|
192
179
|
signing_key:
|
193
180
|
specification_version: 4
|
194
181
|
summary: Collect performance profiles for your Rails application.
|