app_profiler 0.1.3 → 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 +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.
|