app_profiler 0.1.0 → 0.1.1

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: 8e7279a1707f2425058eb5db7cb7e4fca673f1a3634ff76542d2703ce5fdfefc
4
- data.tar.gz: 8002f14694a26a43c87725ac54c44161401dc116b35dd793dcdf9b3f8f2e85c7
3
+ metadata.gz: 340bc83668cbe8abd27780ef23456a6d98f1f8c46059f2b447a25f1e6784483e
4
+ data.tar.gz: 06fcbd4abe95e53f8e37595df7ca445bcd59f61cd9ba24af8c82578cdb6bb9c4
5
5
  SHA512:
6
- metadata.gz: ed3f2daae5d8a9729c2aee42179927e6f311fd0b1ea39f8934ad6811be19075debebb985660984e8b15ce93f4ab7c8812fde0e3fd63f9b972341147606a9fb23
7
- data.tar.gz: 336ac8b880934ef7078347ba1b6a1df046ee58147f91d387ef581b2071a3bcb2acbc50c8cd78e4c465585407bf74babb8b5147ce461936e71ca1993c6682d06b
6
+ metadata.gz: 04cf486ef95efd05af19fd241f05b45c0a942c1e701295181fdef898f59cb24ace85c86e047ea63359b5c5ddfb2f8a233afbc3958004a823e0ffcf0daf692830
7
+ data.tar.gz: bab5ddd1f1dcfb4d06e23b575341a7d7a783e04191b62a74dd553cf78d5726187500931e34c49ebe5e019d60602ceec074d4071d024008c05c8bfe5e5ddb7d52
@@ -21,26 +21,18 @@ module AppProfiler
21
21
  def append_headers(response, upload:, autoredirect:)
22
22
  return unless upload
23
23
 
24
- response[1][profile_header] = profile_url(upload)
24
+ response[1][profile_header] = AppProfiler.profile_url(upload)
25
25
  response[1][profile_data_header] = profile_data_url(upload)
26
26
 
27
27
  return unless autoredirect
28
28
 
29
29
  # Automatically redirect to profile if autoredirect is true.
30
30
  if response[0].to_i < 500
31
- response[1]["Location"] = profile_url(upload)
31
+ response[1]["Location"] = AppProfiler.profile_url(upload)
32
32
  response[0] = 303
33
33
  end
34
34
  end
35
35
 
36
- def profile_url(upload)
37
- if AppProfiler.profile_url_formatter.nil?
38
- "#{AppProfiler.speedscope_host}#profileURL=#{upload.url}"
39
- else
40
- AppProfiler.profile_url_formatter.call(upload)
41
- end
42
- end
43
-
44
36
  def profile_data_url(upload)
45
37
  upload.url.to_s
46
38
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module AppProfiler
4
4
  class Profile
5
- INTERNAL_METADATA_KEYS = %i(id context)
5
+ INTERNAL_METADATA_KEYS = [:id, :context]
6
6
  private_constant :INTERNAL_METADATA_KEYS
7
7
  class UnsafeFilename < StandardError; end
8
8
 
@@ -40,7 +40,13 @@ module AppProfiler
40
40
  def upload
41
41
  AppProfiler.storage.upload(self).tap do |upload|
42
42
  if upload && defined?(upload.url)
43
- AppProfiler.logger.info("[Profiler] uploaded profile profile_url=#{upload.url}")
43
+ AppProfiler.logger.info(
44
+ <<~INFO.squish
45
+ [Profiler] data uploaded:
46
+ profile_url=#{upload.url}
47
+ profile_viewer_url=#{AppProfiler.profile_url(upload)}
48
+ INFO
49
+ )
44
50
  end
45
51
  end
46
52
  rescue => error
@@ -71,7 +77,7 @@ module AppProfiler
71
77
  Socket.gethostname,
72
78
  ].compact.join("-") << ".json"
73
79
 
74
- raise UnsafeFilename if /[^0-9A-Za-z.\-\_]/.match(filename)
80
+ raise UnsafeFilename if /[^0-9A-Za-z.\-\_]/.match?(filename)
75
81
 
76
82
  AppProfiler.profile_root.join(filename)
77
83
  end
@@ -16,6 +16,12 @@ module AppProfiler
16
16
  AppProfiler.middleware = app.config.app_profiler.middleware || Middleware
17
17
  AppProfiler.middleware.action = app.config.app_profiler.middleware_action || default_middleware_action
18
18
  AppProfiler.middleware.disabled = app.config.app_profiler.middleware_disabled || false
19
+ AppProfiler.server.enabled = app.config.app_profiler.server_enabled || false
20
+ AppProfiler.server.transport = app.config.app_profiler.server_transport || default_appprofiler_transport
21
+ AppProfiler.server.port = app.config.app_profiler.server_port || 0
22
+ AppProfiler.server.duration = app.config.app_profiler.server_duration || 30
23
+ AppProfiler.server.cors = app.config.app_profiler.server_cors || true
24
+ AppProfiler.server.cors_host = app.config.app_profiler.server_cors_host || "*"
19
25
  AppProfiler.autoredirect = app.config.app_profiler.autoredirect || false
20
26
  AppProfiler.speedscope_host = app.config.app_profiler.speedscope_host || ENV.fetch(
21
27
  "APP_PROFILER_SPEEDSCOPE_URL", "https://speedscope.app"
@@ -37,6 +43,15 @@ module AppProfiler
37
43
  end
38
44
  end
39
45
 
46
+ initializer "app_profiler.enable_server" do
47
+ if AppProfiler.server.enabled
48
+ AppProfiler::Server.start
49
+ ActiveSupport::ForkTracker.after_fork do
50
+ AppProfiler::Server.start
51
+ end
52
+ end
53
+ end
54
+
40
55
  private
41
56
 
42
57
  def default_middleware_action
@@ -46,5 +61,15 @@ module AppProfiler
46
61
  Middleware::UploadAction
47
62
  end
48
63
  end
64
+
65
+ def default_appprofiler_transport
66
+ if Rails.env.development?
67
+ # default to TCP server in development so that if wanted users are able to target
68
+ # the server with speedscope
69
+ AppProfiler::Server::TRANSPORT_TCP
70
+ else
71
+ AppProfiler::Server::TRANSPORT_UNIX
72
+ end
73
+ end
49
74
  end
50
75
  end
@@ -81,6 +81,7 @@ module AppProfiler
81
81
 
82
82
  def header(name)
83
83
  return unless @request.has_header?(name)
84
+
84
85
  @request.get_header(name)
85
86
  end
86
87
 
@@ -0,0 +1,307 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "socket"
4
+ require "rack"
5
+ require "tempfile"
6
+
7
+ # This module provides a means to start a golang-inspired profile server
8
+ # it is implemented using stdlib and Rack to avoid additional dependencies
9
+
10
+ module AppProfiler
11
+ module Server
12
+ HTTP_OK = 200
13
+ HTTP_BAD_REQUEST = 400
14
+ HTTP_NOT_FOUND = 404
15
+ HTTP_NOT_ALLOWED = 405
16
+ HTTP_CONFLICT = 409
17
+
18
+ TRANSPORT_UNIX = "unix"
19
+ TRANSPORT_TCP = "tcp"
20
+
21
+ DEFAULTS = {
22
+ enabled: false,
23
+ transport: TRANSPORT_UNIX,
24
+ cors: true,
25
+ cors_host: "*",
26
+ port: 0,
27
+ duration: 30,
28
+ }
29
+
30
+ mattr_accessor :enabled, default: DEFAULTS[:enabled]
31
+ mattr_accessor :transport, default: DEFAULTS[:transport]
32
+ mattr_accessor :cors, default: DEFAULTS[:cors]
33
+ mattr_accessor :cors_host, default: DEFAULTS[:cors_host]
34
+ mattr_accessor :port, default: DEFAULTS[:port]
35
+ mattr_accessor :duration, default: DEFAULTS[:duration]
36
+
37
+ class ProfileApplication
38
+ class InvalidProfileArgsError < StandardError; end
39
+
40
+ def initialize
41
+ @semaphore = Thread::Mutex.new
42
+ @profile_running = false
43
+ end
44
+
45
+ def call(env)
46
+ handle(Rack::Request.new(env)).finish
47
+ end
48
+
49
+ private
50
+
51
+ 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
58
+ case request.path
59
+ 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
67
+
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")
85
+ end
86
+ else
87
+ response.status = HTTP_NOT_FOUND
88
+ response.write("Unsupported endpoint #{request.path}")
89
+ end
90
+ response
91
+ end
92
+
93
+ def validate_profile_params(params)
94
+ params = params.symbolize_keys
95
+ stackprof_args = {}
96
+ begin
97
+ duration = Float(params.key?(:duration) ? params[:duration] : AppProfiler::Server.duration)
98
+ rescue ArgumentError
99
+ raise InvalidProfileArgsError, "invalid duration #{params[:duration]}"
100
+ end
101
+ if params.key?(:mode)
102
+ if ["cpu", "wall", "object"].include?(params[:mode])
103
+ stackprof_args[:mode] = params[:mode].to_sym
104
+ else
105
+ raise InvalidProfileArgsError, "invalid mode #{params[:mode]}"
106
+ end
107
+ end
108
+ if params.key?(:interval)
109
+ stackprof_args[:interval] = params[:interval].to_i
110
+ raise InvalidProfileArgsError, "invalid interval #{params[:interval]}" if stackprof_args[:interval] <= 0
111
+ end
112
+ [stackprof_args, duration]
113
+ end
114
+
115
+ # Prevent multiple concurrent profiles by synchronizing between threads
116
+ def start_running
117
+ @semaphore.synchronize do
118
+ return false if @profile_running
119
+
120
+ @profile_running = true
121
+ end
122
+ end
123
+
124
+ def stop_running
125
+ @semaphore.synchronize { @profile_running = false }
126
+ end
127
+ end
128
+
129
+ # This is a minimal, non-compliant "HTTP" server.
130
+ # It will create an extremely minimal rack environment hash and hand it off
131
+ # to our application to process
132
+ class ProfileServer
133
+ PROFILER_TEMPFILE_PATH = "/tmp/app_profiler" # for tempfile that indicates port in filename or unix sockets
134
+
135
+ class Transport
136
+ attr_reader :socket
137
+
138
+ def client
139
+ raise(NotImplementedError)
140
+ end
141
+
142
+ def stop
143
+ raise(NotImplementedError)
144
+ end
145
+ end
146
+
147
+ class UNIX < Transport
148
+ def initialize
149
+ super
150
+ FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
151
+ @socket_file = File.join(PROFILER_TEMPFILE_PATH, "app-profiler-#{Process.pid}.sock")
152
+ File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
153
+ @socket = UNIXServer.new(@socket_file)
154
+ end
155
+
156
+ def client
157
+ UNIXSocket.new(@socket_file)
158
+ end
159
+
160
+ def stop
161
+ File.unlink(@socket_file) if File.exist?(@socket_file) && File.socket?(@socket_file)
162
+ @socket.close
163
+ end
164
+ end
165
+
166
+ class TCP < Transport
167
+ SERVER_ADDRESS = "127.0.0.1" # it is ONLY safe to run this bound to localhost
168
+
169
+ def initialize(port = 0)
170
+ super()
171
+ FileUtils.mkdir_p(PROFILER_TEMPFILE_PATH)
172
+ @socket = TCPServer.new(SERVER_ADDRESS, port)
173
+ @port = @socket.addr[1]
174
+ @port_file = Tempfile.new("profileserver-#{Process.pid}-port-#{@port}-", PROFILER_TEMPFILE_PATH)
175
+ end
176
+
177
+ def client
178
+ TCPSocket.new(SERVER_ADDRESS, @port)
179
+ end
180
+
181
+ def stop
182
+ @port_file.unlink
183
+ @socket.close
184
+ end
185
+ end
186
+
187
+ def initialize(transport)
188
+ case transport
189
+ when TRANSPORT_UNIX
190
+ @transport = ProfileServer::UNIX.new
191
+ when TRANSPORT_TCP
192
+ @transport = ProfileServer::TCP.new(AppProfiler::Server.port)
193
+ else
194
+ raise "invalid transport #{transport}"
195
+ end
196
+
197
+ @listen_thread = nil
198
+
199
+ AppProfiler.logger.info(
200
+ "[AppProfiler::Server] listening on addr=#{@transport.socket.addr}"
201
+ )
202
+ @pid = Process.pid
203
+ at_exit { stop }
204
+ end
205
+
206
+ def client
207
+ @transport.client
208
+ end
209
+
210
+ def serve
211
+ return unless @listen_thread.nil?
212
+
213
+ app = ProfileApplication.new
214
+
215
+ @listen_thread = Thread.new do
216
+ loop do
217
+ Thread.new(@transport.socket.accept) do |session|
218
+ request = session.gets
219
+ if request.nil?
220
+ session.close
221
+ next
222
+ end
223
+ method, path, http_version = request.split(" ")
224
+ path_info, query_string = path.split("?")
225
+ env = { # an extremely minimal rack env hash, just enough to get the job done
226
+ "HTTP_VERSION" => http_version,
227
+ "REQUEST_METHOD" => method,
228
+ "PATH_INFO" => path_info,
229
+ "QUERY_STRING" => query_string,
230
+ "rack.input" => "",
231
+ }
232
+ status, headers, body = app.call(env)
233
+
234
+ begin
235
+ session.print("#{http_version} #{status}\r\n")
236
+ headers.each do |header, value|
237
+ session.print("#{header}: #{value}\r\n")
238
+ end
239
+ session.print("\r\n")
240
+ body.each do |part|
241
+ session.print(part)
242
+ end
243
+ rescue => e
244
+ AppProfiler.logger.error(
245
+ "[AppProfiler::Server] exception #{e} responding to request #{request}: #{e.message}"
246
+ )
247
+ ensure
248
+ session.close
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
254
+
255
+ def stop
256
+ return unless @pid == Process.pid
257
+
258
+ @listen_thread.kill
259
+ @transport.stop
260
+ end
261
+ end
262
+
263
+ private_constant :ProfileApplication, :ProfileServer
264
+
265
+ class << self
266
+ def reset
267
+ profile_servers.clear
268
+ DEFAULTS.each do |config, value|
269
+ class_variable_set(:"@@#{config}", value) # rubocop:disable Style/ClassVars
270
+ end
271
+ end
272
+
273
+ def start
274
+ return if profile_server
275
+
276
+ profile_servers[Process.pid] = ProfileServer.new(AppProfiler::Server.transport)
277
+ profile_server.serve
278
+ profile_server
279
+ end
280
+
281
+ def client
282
+ return unless profile_server
283
+
284
+ profile_server.client
285
+ end
286
+
287
+ def stop
288
+ return unless profile_server
289
+
290
+ profile_server.stop
291
+ profile_servers.delete(Process.pid)
292
+ end
293
+
294
+ private
295
+
296
+ def profile_server
297
+ profile_servers[Process.pid]
298
+ end
299
+
300
+ # It is possible there will be a server pre-fork, this is to distinguish
301
+ # the child server from its parent via PID
302
+ def profile_servers
303
+ @profile_servers ||= {}
304
+ end
305
+ end
306
+ end
307
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module AppProfiler
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -7,10 +7,12 @@ module AppProfiler
7
7
  class SpeedscopeRemoteViewer < BaseViewer
8
8
  class BaseMiddleware
9
9
  class Sanitizer < Rails::Html::SafeListSanitizer
10
- self.allowed_tags = Set.new(%w(strong em b i p code pre tt samp kbd var sub
11
- sup dfn cite big small address hr br div span
12
- h1 h2 h3 h4 h5 h6 ul ol li dl dt dd abbr
13
- acronym a img blockquote del ins script))
10
+ self.allowed_tags = Set.new([
11
+ "strong", "em", "b", "i", "p", "code", "pre", "tt", "samp", "kbd", "var", "sub",
12
+ "sup", "dfn", "cite", "big", "small", "address", "hr", "br", "div", "span", "h1",
13
+ "h2", "h3", "h4", "h5", "h6", "ul", "ol", "li", "dl", "dt", "dd", "abbr", "acronym",
14
+ "a", "img", "blockquote", "del", "ins", "script",
15
+ ])
14
16
  end
15
17
 
16
18
  private_constant(:Sanitizer)
@@ -73,7 +75,7 @@ module AppProfiler
73
75
 
74
76
  def index(_env)
75
77
  render(
76
- String.new.tap do |content|
78
+ (+"").tap do |content|
77
79
  content << "<h1>Profiles</h1>"
78
80
  profile_files.each do |file|
79
81
  content << <<~HTML
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module AppProfiler
3
4
  module Yarn
4
5
  module Command
@@ -32,7 +33,7 @@ module AppProfiler
32
33
 
33
34
  def ensure_command_valid(command)
34
35
  unless valid_command?(command)
35
- raise YarnError, "Illegal command: #{command.join(' ')}."
36
+ raise YarnError, "Illegal command: #{command.join(" ")}."
36
37
  end
37
38
  end
38
39
 
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module AppProfiler
3
4
  module Yarn
4
5
  module WithSpeedscope
data/lib/app_profiler.rb CHANGED
@@ -10,6 +10,10 @@ module AppProfiler
10
10
  class ConfigurationError < StandardError
11
11
  end
12
12
 
13
+ DefaultProfileFormatter = proc do |upload|
14
+ "#{AppProfiler.speedscope_host}#profileURL=#{upload.url}"
15
+ end
16
+
13
17
  module Storage
14
18
  autoload :BaseStorage, "app_profiler/storage/base_storage"
15
19
  autoload :FileStorage, "app_profiler/storage/file_storage"
@@ -26,6 +30,7 @@ module AppProfiler
26
30
  require "app_profiler/request_parameters"
27
31
  require "app_profiler/profiler"
28
32
  require "app_profiler/profile"
33
+ require "app_profiler/server"
29
34
 
30
35
  mattr_accessor :logger, default: Logger.new($stdout)
31
36
  mattr_accessor :root
@@ -35,11 +40,13 @@ module AppProfiler
35
40
  mattr_accessor :autoredirect, default: false
36
41
  mattr_reader :profile_header, default: "X-Profile"
37
42
  mattr_accessor :context, default: nil
38
- mattr_reader :profile_url_formatter, default: nil
43
+ mattr_reader :profile_url_formatter,
44
+ default: DefaultProfileFormatter
39
45
 
40
46
  mattr_accessor :storage, default: Storage::FileStorage
41
47
  mattr_accessor :viewer, default: Viewer::SpeedscopeViewer
42
48
  mattr_accessor :middleware, default: Middleware
49
+ mattr_accessor :server, default: Server
43
50
 
44
51
  class << self
45
52
  def run(*args, &block)
@@ -62,9 +69,7 @@ module AppProfiler
62
69
  end
63
70
 
64
71
  def request_profile_header
65
- @@request_profile_header ||= begin # rubocop:disable Style/ClassVars
66
- profile_header.upcase.gsub("-", "_").prepend("HTTP_")
67
- end
72
+ @@request_profile_header ||= profile_header.upcase.tr("-", "_").prepend("HTTP_") # rubocop:disable Style/ClassVars
68
73
  end
69
74
 
70
75
  def profile_data_header
@@ -74,5 +79,9 @@ module AppProfiler
74
79
  def profile_url_formatter=(block)
75
80
  @@profile_url_formatter = block # rubocop:disable Style/ClassVars
76
81
  end
82
+
83
+ def profile_url(upload)
84
+ AppProfiler.profile_url_formatter.call(upload)
85
+ end
77
86
  end
78
87
  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.0
4
+ version: 0.1.1
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-04-26 00:00:00.000000000 Z
16
+ date: 2022-06-16 00:00:00.000000000 Z
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
19
19
  name: activesupport
@@ -30,33 +30,33 @@ dependencies:
30
30
  - !ruby/object:Gem::Version
31
31
  version: '5.2'
32
32
  - !ruby/object:Gem::Dependency
33
- name: railties
33
+ name: rack
34
34
  requirement: !ruby/object:Gem::Requirement
35
35
  requirements:
36
36
  - - ">="
37
37
  - !ruby/object:Gem::Version
38
- version: '5.2'
38
+ version: '0'
39
39
  type: :runtime
40
40
  prerelease: false
41
41
  version_requirements: !ruby/object:Gem::Requirement
42
42
  requirements:
43
43
  - - ">="
44
44
  - !ruby/object:Gem::Version
45
- version: '5.2'
45
+ version: '0'
46
46
  - !ruby/object:Gem::Dependency
47
- name: rack
47
+ name: railties
48
48
  requirement: !ruby/object:Gem::Requirement
49
49
  requirements:
50
50
  - - ">="
51
51
  - !ruby/object:Gem::Version
52
- version: '0'
52
+ version: '5.2'
53
53
  type: :runtime
54
54
  prerelease: false
55
55
  version_requirements: !ruby/object:Gem::Requirement
56
56
  requirements:
57
57
  - - ">="
58
58
  - !ruby/object:Gem::Version
59
- version: '0'
59
+ version: '5.2'
60
60
  - !ruby/object:Gem::Dependency
61
61
  name: stackprof
62
62
  requirement: !ruby/object:Gem::Requirement
@@ -86,7 +86,7 @@ dependencies:
86
86
  - !ruby/object:Gem::Version
87
87
  version: '0'
88
88
  - !ruby/object:Gem::Dependency
89
- name: rake
89
+ name: minitest
90
90
  requirement: !ruby/object:Gem::Requirement
91
91
  requirements:
92
92
  - - ">="
@@ -100,19 +100,19 @@ dependencies:
100
100
  - !ruby/object:Gem::Version
101
101
  version: '0'
102
102
  - !ruby/object:Gem::Dependency
103
- name: minitest
103
+ name: minitest-stub-const
104
104
  requirement: !ruby/object:Gem::Requirement
105
105
  requirements:
106
- - - ">="
106
+ - - '='
107
107
  - !ruby/object:Gem::Version
108
- version: '0'
108
+ version: '0.6'
109
109
  type: :development
110
110
  prerelease: false
111
111
  version_requirements: !ruby/object:Gem::Requirement
112
112
  requirements:
113
- - - ">="
113
+ - - '='
114
114
  - !ruby/object:Gem::Version
115
- version: '0'
115
+ version: '0.6'
116
116
  - !ruby/object:Gem::Dependency
117
117
  name: mocha
118
118
  requirement: !ruby/object:Gem::Requirement
@@ -128,19 +128,19 @@ dependencies:
128
128
  - !ruby/object:Gem::Version
129
129
  version: '0'
130
130
  - !ruby/object:Gem::Dependency
131
- name: minitest-stub-const
131
+ name: rake
132
132
  requirement: !ruby/object:Gem::Requirement
133
133
  requirements:
134
- - - '='
134
+ - - ">="
135
135
  - !ruby/object:Gem::Version
136
- version: '0.6'
136
+ version: '0'
137
137
  type: :development
138
138
  prerelease: false
139
139
  version_requirements: !ruby/object:Gem::Requirement
140
140
  requirements:
141
- - - '='
141
+ - - ">="
142
142
  - !ruby/object:Gem::Version
143
- version: '0.6'
143
+ version: '0'
144
144
  description:
145
145
  email:
146
146
  - gems@shopify.com
@@ -157,6 +157,7 @@ files:
157
157
  - lib/app_profiler/profiler.rb
158
158
  - lib/app_profiler/railtie.rb
159
159
  - lib/app_profiler/request_parameters.rb
160
+ - lib/app_profiler/server.rb
160
161
  - lib/app_profiler/storage/base_storage.rb
161
162
  - lib/app_profiler/storage/file_storage.rb
162
163
  - lib/app_profiler/storage/google_cloud_storage.rb
@@ -180,14 +181,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
180
181
  requirements:
181
182
  - - ">="
182
183
  - !ruby/object:Gem::Version
183
- version: '0'
184
+ version: '2.7'
184
185
  required_rubygems_version: !ruby/object:Gem::Requirement
185
186
  requirements:
186
187
  - - ">="
187
188
  - !ruby/object:Gem::Version
188
189
  version: '0'
189
190
  requirements: []
190
- rubygems_version: 3.2.20
191
+ rubygems_version: 3.3.3
191
192
  signing_key:
192
193
  specification_version: 4
193
194
  summary: Collect performance profiles for your Rails application.