app_profiler 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 340bc83668cbe8abd27780ef23456a6d98f1f8c46059f2b447a25f1e6784483e
4
- data.tar.gz: 06fcbd4abe95e53f8e37595df7ca445bcd59f61cd9ba24af8c82578cdb6bb9c4
3
+ metadata.gz: 69d1e8d957672c105e954b64a547955e4e1c499d3d70b529e1de1b56b2fb7d51
4
+ data.tar.gz: d7f022da1ebf4ead06ebadae4ded97260276afb12cb885866d76f53e520171c8
5
5
  SHA512:
6
- metadata.gz: 04cf486ef95efd05af19fd241f05b45c0a942c1e701295181fdef898f59cb24ace85c86e047ea63359b5c5ddfb2f8a233afbc3958004a823e0ffcf0daf692830
7
- data.tar.gz: bab5ddd1f1dcfb4d06e23b575341a7d7a783e04191b62a74dd553cf78d5726187500931e34c49ebe5e019d60602ceec074d4071d024008c05c8bfe5e5ddb7d52
6
+ metadata.gz: 831da45ae0c02c629fc0e1788a6b1a220872b4c449291d72df9adbfdbfada6bf0ab815fe41e5034ad5376176b6235cfcbb83d00e4688a1ba4150faedb5ed5820
7
+ data.tar.gz: 0177d93977123800440539da265fac443023d8f654f74d64ccd7c4f9a74ed2750c83c2472a7e8285b065a79589fb6024280b4767ee6df148722d34339396cb72
@@ -27,8 +27,9 @@ module AppProfiler
27
27
  return unless autoredirect
28
28
 
29
29
  # Automatically redirect to profile if autoredirect is true.
30
- if response[0].to_i < 500
31
- response[1]["Location"] = AppProfiler.profile_url(upload)
30
+ location = AppProfiler.profile_url(upload)
31
+ if response[0].to_i < 500 && location
32
+ response[1]["Location"] = location
32
33
  response[0] = 303
33
34
  end
34
35
  end
@@ -5,6 +5,7 @@ require "rails"
5
5
  module AppProfiler
6
6
  class Railtie < Rails::Railtie
7
7
  config.app_profiler = ActiveSupport::OrderedOptions.new
8
+ config.app_profiler.profile_url_formatter = DefaultProfileFormatter
8
9
 
9
10
  initializer "app_profiler.configs" do |app|
10
11
  AppProfiler.logger = app.config.app_profiler.logger || Rails.logger
@@ -45,9 +46,9 @@ module AppProfiler
45
46
 
46
47
  initializer "app_profiler.enable_server" do
47
48
  if AppProfiler.server.enabled
48
- AppProfiler::Server.start
49
+ AppProfiler::Server.start(AppProfiler.logger)
49
50
  ActiveSupport::ForkTracker.after_fork do
50
- AppProfiler::Server.start
51
+ AppProfiler::Server.start(AppProfiler.logger)
51
52
  end
52
53
  end
53
54
  end
@@ -3,6 +3,9 @@
3
3
  require "socket"
4
4
  require "rack"
5
5
  require "tempfile"
6
+ require "json"
7
+ require "active_support/core_ext/hash"
8
+ require "active_support/core_ext/module"
6
9
 
7
10
  # This module provides a means to start a golang-inspired profile server
8
11
  # it is implemented using stdlib and Rack to avoid additional dependencies
@@ -49,80 +52,122 @@ module AppProfiler
49
52
  private
50
53
 
51
54
  def handle(request)
52
- response = Rack::Response.new
53
- if request.request_method != "GET"
54
- response.status = HTTP_NOT_ALLOWED
55
- response.write("Only GET requests are supported")
56
- return response
57
- end
55
+ return handle_not_allowed(request) if request.request_method != "GET"
56
+
58
57
  case request.path
59
58
  when "/profile"
60
- begin
61
- stackprof_args, duration = validate_profile_params(request.params)
62
- rescue InvalidProfileArgsError => e
63
- response.status = HTTP_BAD_REQUEST
64
- response.write("Invalid argument #{e.message}")
65
- return response
66
- end
59
+ handle_profile(request)
60
+ else
61
+ handle_not_found(request)
62
+ end
63
+ end
67
64
 
68
- if start_running
69
- start_time = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
70
- AppProfiler.start(**stackprof_args)
71
- sleep(duration)
72
- profile = AppProfiler.stop
73
- stop_running
74
- response.status = HTTP_OK
75
- response.set_header("Content-Type", "application/json")
76
- if AppProfiler::Server.cors
77
- response.set_header("Access-Control-Allow-Origin", AppProfiler::Server.cors_host)
78
- end
79
- profile_hash = profile.to_h
80
- profile_hash["start_time_nsecs"] = start_time # NOTE: this is not part of the stackprof profile spec
81
- response.write(JSON.dump(profile_hash))
82
- else
83
- response.status = HTTP_CONFLICT
84
- response.write("A profile is already running")
65
+ def handle_not_allowed(request)
66
+ response = Rack::Response.new
67
+
68
+ response.status = HTTP_NOT_ALLOWED
69
+ response.write("Only GET requests are supported")
70
+
71
+ response
72
+ end
73
+
74
+ def handle_profile(request)
75
+ begin
76
+ stackprof_args, duration = validate_profile_params(request.params)
77
+ rescue InvalidProfileArgsError => e
78
+ return handle_bad_request(request, e.message)
79
+ end
80
+
81
+ response = Rack::Response.new
82
+
83
+ if start_running(stackprof_args)
84
+ start_time = Process.clock_gettime(Process::CLOCK_REALTIME, :nanosecond)
85
+
86
+ sleep(duration)
87
+
88
+ profile = stop_running
89
+
90
+ response.status = HTTP_OK
91
+ response.set_header("Content-Type", "application/json")
92
+
93
+ profile_hash = profile.to_h
94
+ profile_hash["start_time_nsecs"] = start_time # NOTE: this is not part of the stackprof profile spec
95
+
96
+ response.write(JSON.dump(profile_hash))
97
+
98
+ if AppProfiler::Server.cors
99
+ response.set_header("Access-Control-Allow-Origin", AppProfiler::Server.cors_host)
85
100
  end
86
101
  else
87
- response.status = HTTP_NOT_FOUND
88
- response.write("Unsupported endpoint #{request.path}")
102
+ response.status = HTTP_CONFLICT
103
+ response.write("A profile is already running")
89
104
  end
105
+
106
+ response
107
+ end
108
+
109
+ def handle_not_found(request)
110
+ response = Rack::Response.new
111
+
112
+ response.status = HTTP_NOT_FOUND
113
+ response.write("Unsupported endpoint #{request.path}")
114
+
115
+ response
116
+ end
117
+
118
+ def handle_bad_request(request, message)
119
+ response = Rack::Response.new
120
+
121
+ response.status = HTTP_BAD_REQUEST
122
+ response.write("Invalid argument #{message}")
123
+
90
124
  response
91
125
  end
92
126
 
93
127
  def validate_profile_params(params)
94
128
  params = params.symbolize_keys
95
129
  stackprof_args = {}
130
+
96
131
  begin
97
132
  duration = Float(params.key?(:duration) ? params[:duration] : AppProfiler::Server.duration)
98
133
  rescue ArgumentError
99
- raise InvalidProfileArgsError, "invalid duration #{params[:duration]}"
134
+ raise InvalidProfileArgsError, "duration: #{params[:duration]}"
100
135
  end
136
+
101
137
  if params.key?(:mode)
102
138
  if ["cpu", "wall", "object"].include?(params[:mode])
103
139
  stackprof_args[:mode] = params[:mode].to_sym
104
140
  else
105
- raise InvalidProfileArgsError, "invalid mode #{params[:mode]}"
141
+ raise InvalidProfileArgsError, "mode: #{params[:mode]}"
106
142
  end
107
143
  end
144
+
108
145
  if params.key?(:interval)
109
146
  stackprof_args[:interval] = params[:interval].to_i
110
- raise InvalidProfileArgsError, "invalid interval #{params[:interval]}" if stackprof_args[:interval] <= 0
147
+
148
+ raise InvalidProfileArgsError, "interval: #{params[:interval]}" if stackprof_args[:interval] <= 0
111
149
  end
150
+
112
151
  [stackprof_args, duration]
113
152
  end
114
153
 
115
154
  # Prevent multiple concurrent profiles by synchronizing between threads
116
- def start_running
155
+ def start_running(stackprof_args)
117
156
  @semaphore.synchronize do
118
157
  return false if @profile_running
119
158
 
120
159
  @profile_running = true
160
+
161
+ AppProfiler.start(**stackprof_args)
121
162
  end
122
163
  end
123
164
 
124
165
  def stop_running
125
- @semaphore.synchronize { @profile_running = false }
166
+ @semaphore.synchronize do
167
+ AppProfiler.stop.tap do
168
+ @profile_running = false
169
+ end
170
+ end
126
171
  end
127
172
  end
128
173
 
@@ -147,6 +192,7 @@ module AppProfiler
147
192
  class UNIX < Transport
148
193
  def initialize
149
194
  super
195
+
150
196
  FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
151
197
  @socket_file = File.join(PROFILER_TEMPFILE_PATH, "app-profiler-#{Process.pid}.sock")
152
198
  File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
@@ -184,7 +230,8 @@ module AppProfiler
184
230
  end
185
231
  end
186
232
 
187
- def initialize(transport)
233
+ def initialize(transport, logger)
234
+ @logger = logger
188
235
  case transport
189
236
  when TRANSPORT_UNIX
190
237
  @transport = ProfileServer::UNIX.new
@@ -196,7 +243,7 @@ module AppProfiler
196
243
 
197
244
  @listen_thread = nil
198
245
 
199
- AppProfiler.logger.info(
246
+ @logger.info(
200
247
  "[AppProfiler::Server] listening on addr=#{@transport.socket.addr}"
201
248
  )
202
249
  @pid = Process.pid
@@ -216,10 +263,13 @@ module AppProfiler
216
263
  loop do
217
264
  Thread.new(@transport.socket.accept) do |session|
218
265
  request = session.gets
266
+
219
267
  if request.nil?
220
268
  session.close
269
+
221
270
  next
222
271
  end
272
+
223
273
  method, path, http_version = request.split(" ")
224
274
  path_info, query_string = path.split("?")
225
275
  env = { # an extremely minimal rack env hash, just enough to get the job done
@@ -233,15 +283,18 @@ module AppProfiler
233
283
 
234
284
  begin
235
285
  session.print("#{http_version} #{status}\r\n")
286
+
236
287
  headers.each do |header, value|
237
288
  session.print("#{header}: #{value}\r\n")
238
289
  end
290
+
239
291
  session.print("\r\n")
292
+
240
293
  body.each do |part|
241
294
  session.print(part)
242
295
  end
243
296
  rescue => e
244
- AppProfiler.logger.error(
297
+ @logger.error(
245
298
  "[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}"
246
299
  )
247
300
  ensure
@@ -265,15 +318,16 @@ module AppProfiler
265
318
  class << self
266
319
  def reset
267
320
  profile_servers.clear
321
+
268
322
  DEFAULTS.each do |config, value|
269
323
  class_variable_set(:"@@#{config}", value) # rubocop:disable Style/ClassVars
270
324
  end
271
325
  end
272
326
 
273
- def start
327
+ def start(logger = Logger.new(IO::NULL))
274
328
  return if profile_server
275
329
 
276
- profile_servers[Process.pid] = ProfileServer.new(AppProfiler::Server.transport)
330
+ profile_servers[Process.pid] = ProfileServer.new(AppProfiler::Server.transport, logger)
277
331
  profile_server.serve
278
332
  profile_server
279
333
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppProfiler
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.3"
5
5
  end
data/lib/app_profiler.rb CHANGED
@@ -4,7 +4,6 @@ require "active_support/core_ext/class"
4
4
  require "active_support/core_ext/module"
5
5
  require "logger"
6
6
  require "app_profiler/version"
7
- require "app_profiler/railtie" if defined?(Rails::Railtie)
8
7
 
9
8
  module AppProfiler
10
9
  class ConfigurationError < StandardError
@@ -81,7 +80,11 @@ module AppProfiler
81
80
  end
82
81
 
83
82
  def profile_url(upload)
83
+ return unless AppProfiler.profile_url_formatter
84
+
84
85
  AppProfiler.profile_url_formatter.call(upload)
85
86
  end
86
87
  end
88
+
89
+ require "app_profiler/railtie" if defined?(Rails::Railtie)
87
90
  end
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.1
4
+ version: 0.1.3
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: 2022-06-16 00:00:00.000000000 Z
16
+ date: 2022-09-23 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