108 lines
2.6 KiB
Ruby
108 lines
2.6 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
|
||
|
require 'ruby-prof'
|
||
|
require 'memory_profiler'
|
||
|
|
||
|
module Gitlab
|
||
|
module RequestProfiler
|
||
|
class Middleware
|
||
|
def initialize(app)
|
||
|
@app = app
|
||
|
end
|
||
|
|
||
|
def call(env)
|
||
|
if profile?(env)
|
||
|
call_with_profiling(env)
|
||
|
else
|
||
|
@app.call(env)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def profile?(env)
|
||
|
header_token = env['HTTP_X_PROFILE_TOKEN']
|
||
|
return unless header_token.present?
|
||
|
|
||
|
profile_token = Gitlab::RequestProfiler.profile_token
|
||
|
return unless profile_token.present?
|
||
|
|
||
|
header_token == profile_token
|
||
|
end
|
||
|
|
||
|
def call_with_profiling(env)
|
||
|
case env['HTTP_X_PROFILE_MODE']
|
||
|
when 'execution', nil
|
||
|
call_with_call_stack_profiling(env)
|
||
|
when 'memory'
|
||
|
call_with_memory_profiling(env)
|
||
|
else
|
||
|
raise ActionController::BadRequest, invalid_profile_mode(env)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def invalid_profile_mode(env)
|
||
|
<<~HEREDOC
|
||
|
Invalid X-Profile-Mode: #{env['HTTP_X_PROFILE_MODE']}.
|
||
|
Supported profile mode request header:
|
||
|
- X-Profile-Mode: execution
|
||
|
- X-Profile-Mode: memory
|
||
|
HEREDOC
|
||
|
end
|
||
|
|
||
|
def call_with_call_stack_profiling(env)
|
||
|
ret = nil
|
||
|
report = RubyProf::Profile.profile do
|
||
|
ret = catch(:warden) do # rubocop:disable Cop/BanCatchThrow
|
||
|
@app.call(env)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
generate_report(env, 'execution', 'html') do |file|
|
||
|
printer = RubyProf::CallStackPrinter.new(report)
|
||
|
printer.print(file)
|
||
|
end
|
||
|
|
||
|
handle_request_ret(ret)
|
||
|
end
|
||
|
|
||
|
def call_with_memory_profiling(env)
|
||
|
ret = nil
|
||
|
report = MemoryProfiler.report do
|
||
|
ret = catch(:warden) do # rubocop:disable Cop/BanCatchThrow
|
||
|
@app.call(env)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
generate_report(env, 'memory', 'txt') do |file|
|
||
|
report.pretty_print(to_file: file)
|
||
|
end
|
||
|
|
||
|
handle_request_ret(ret)
|
||
|
end
|
||
|
|
||
|
def generate_report(env, report_type, extension)
|
||
|
file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}"\
|
||
|
"_#{report_type}.#{extension}"
|
||
|
file_path = "#{PROFILES_DIR}/#{file_name}"
|
||
|
|
||
|
FileUtils.mkdir_p(PROFILES_DIR)
|
||
|
|
||
|
begin
|
||
|
File.open(file_path, 'wb') do |file|
|
||
|
yield(file)
|
||
|
end
|
||
|
rescue StandardError
|
||
|
FileUtils.rm(file_path)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def handle_request_ret(ret)
|
||
|
if ret.is_a?(Array)
|
||
|
ret
|
||
|
else
|
||
|
throw(:warden, ret) # rubocop:disable Cop/BanCatchThrow
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|