app_profiler 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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