app_profiler 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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 +3 -0
- data/lib/app_profiler/request_parameters.rb +7 -7
- data/lib/app_profiler/server.rb +37 -14
- 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 -3
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|
|
@@ -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
|
@@ -261,7 +271,15 @@ module AppProfiler
|
|
261
271
|
|
262
272
|
@listen_thread = Thread.new do
|
263
273
|
loop do
|
264
|
-
|
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|
|
265
283
|
request = session.gets
|
266
284
|
|
267
285
|
if request.nil?
|
@@ -315,9 +333,12 @@ module AppProfiler
|
|
315
333
|
|
316
334
|
private_constant :ProfileApplication, :ProfileServer
|
317
335
|
|
336
|
+
@pid = Process.pid
|
337
|
+
@profile_server = nil
|
338
|
+
|
318
339
|
class << self
|
319
340
|
def reset
|
320
|
-
|
341
|
+
self.profile_server = nil
|
321
342
|
|
322
343
|
DEFAULTS.each do |config, value|
|
323
344
|
class_variable_set(:"@@#{config}", value) # rubocop:disable Style/ClassVars
|
@@ -327,7 +348,7 @@ module AppProfiler
|
|
327
348
|
def start(logger = Logger.new(IO::NULL))
|
328
349
|
return if profile_server
|
329
350
|
|
330
|
-
|
351
|
+
self.profile_server = ProfileServer.new(AppProfiler::Server.transport, logger)
|
331
352
|
profile_server.serve
|
332
353
|
profile_server
|
333
354
|
end
|
@@ -341,20 +362,22 @@ module AppProfiler
|
|
341
362
|
def stop
|
342
363
|
return unless profile_server
|
343
364
|
|
344
|
-
profile_server
|
345
|
-
|
365
|
+
server = profile_server
|
366
|
+
server.stop
|
367
|
+
self.profile_server = nil
|
368
|
+
server
|
346
369
|
end
|
347
370
|
|
348
371
|
private
|
349
372
|
|
350
373
|
def profile_server
|
351
|
-
|
374
|
+
@profile_server = nil if @pid != Process.pid
|
375
|
+
@profile_server
|
352
376
|
end
|
353
377
|
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
@profile_servers ||= {}
|
378
|
+
def profile_server=(server)
|
379
|
+
@pid = Process.pid
|
380
|
+
@profile_server = server
|
358
381
|
end
|
359
382
|
end
|
360
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
|
@@ -139,6 +139,7 @@ files:
|
|
139
139
|
- lib/app_profiler/middleware/base_action.rb
|
140
140
|
- lib/app_profiler/middleware/upload_action.rb
|
141
141
|
- lib/app_profiler/middleware/view_action.rb
|
142
|
+
- lib/app_profiler/parameters.rb
|
142
143
|
- lib/app_profiler/profile.rb
|
143
144
|
- lib/app_profiler/profiler.rb
|
144
145
|
- lib/app_profiler/railtie.rb
|
@@ -174,7 +175,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
175
|
- !ruby/object:Gem::Version
|
175
176
|
version: '0'
|
176
177
|
requirements: []
|
177
|
-
rubygems_version: 3.
|
178
|
+
rubygems_version: 3.4.19
|
178
179
|
signing_key:
|
179
180
|
specification_version: 4
|
180
181
|
summary: Collect performance profiles for your Rails application.
|