Profile requests when a header is passed
This commit is contained in:
parent
0c799be6b6
commit
345cd22f21
14 changed files with 178 additions and 1 deletions
|
@ -15,6 +15,7 @@ v 8.11.0 (unreleased)
|
|||
- Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Add the `sprockets-es6` gem
|
||||
- Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska)
|
||||
- Profile requests when a header is passed
|
||||
|
||||
v 8.10.2 (unreleased)
|
||||
- User can now search branches by name. !5144
|
||||
|
|
2
Gemfile
2
Gemfile
|
@ -334,6 +334,8 @@ gem 'mail_room', '~> 0.8'
|
|||
|
||||
gem 'email_reply_parser', '~> 0.5.8'
|
||||
|
||||
gem 'ruby-prof', '~> 0.15.9'
|
||||
|
||||
## CI
|
||||
gem 'activerecord-session_store', '~> 1.0.0'
|
||||
gem 'nested_form', '~> 0.3.2'
|
||||
|
|
|
@ -620,6 +620,7 @@ GEM
|
|||
rubocop (>= 0.40.0)
|
||||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-prof (0.15.9)
|
||||
ruby-progressbar (1.8.1)
|
||||
ruby-saml (1.3.0)
|
||||
nokogiri (>= 1.5.10)
|
||||
|
@ -948,6 +949,7 @@ DEPENDENCIES
|
|||
rubocop (~> 0.41.2)
|
||||
rubocop-rspec (~> 1.5.0)
|
||||
ruby-fogbugz (~> 0.2.1)
|
||||
ruby-prof (~> 0.15.9)
|
||||
sanitize (~> 2.0)
|
||||
sass-rails (~> 5.0.0)
|
||||
scss_lint (~> 0.47.0)
|
||||
|
|
17
app/controllers/admin/requests_profiles_controller.rb
Normal file
17
app/controllers/admin/requests_profiles_controller.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
class Admin::RequestsProfilesController < Admin::ApplicationController
|
||||
def index
|
||||
@profile_token = Gitlab::RequestProfiler.profile_token
|
||||
@profiles = Gitlab::RequestProfiler::Profile.all.group_by(&:request_path)
|
||||
end
|
||||
|
||||
def show
|
||||
clean_name = Rack::Utils.clean_path_info(params[:name])
|
||||
profile = Gitlab::RequestProfiler::Profile.find(clean_name)
|
||||
|
||||
if profile
|
||||
render text: profile.content
|
||||
else
|
||||
redirect_to admin_requests_profiles_path, alert: 'Profile not found'
|
||||
end
|
||||
end
|
||||
end
|
|
@ -16,3 +16,7 @@
|
|||
= link_to admin_health_check_path, title: 'Health Check' do
|
||||
%span
|
||||
Health Check
|
||||
= nav_link(controller: :requests_profiles) do
|
||||
= link_to admin_requests_profiles_path, title: 'Requests Profiles' do
|
||||
%span
|
||||
Requests Profiles
|
||||
|
|
26
app/views/admin/requests_profiles/index.html.haml
Normal file
26
app/views/admin/requests_profiles/index.html.haml
Normal file
|
@ -0,0 +1,26 @@
|
|||
- @no_container = true
|
||||
- page_title 'Requests Profiles'
|
||||
= render 'admin/background_jobs/head'
|
||||
|
||||
%div{ class: container_class }
|
||||
%h3.page-title
|
||||
= page_title
|
||||
|
||||
.bs-callout.clearfix
|
||||
Pass the header
|
||||
%code X-Profile-Token: #{@profile_token}
|
||||
to profile the request
|
||||
|
||||
- if @profiles.present?
|
||||
.prepend-top-default
|
||||
- @profiles.each do |path, profiles|
|
||||
.panel.panel-default.panel-small
|
||||
.panel-heading
|
||||
%code= path
|
||||
%ul.content-list
|
||||
- profiles.each do |profile|
|
||||
%li
|
||||
= link_to profile.time.to_s(:long), admin_requests_profile_path(profile), data: {no_turbolink: true}
|
||||
- else
|
||||
%p
|
||||
No profiles found
|
|
@ -9,7 +9,7 @@
|
|||
= link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do
|
||||
%span
|
||||
Overview
|
||||
= nav_link(controller: %w(system_info background_jobs logs health_check)) do
|
||||
= nav_link(controller: %w(system_info background_jobs logs health_check requests_profiles)) do
|
||||
= link_to admin_system_info_path, title: 'Monitoring' do
|
||||
%span
|
||||
Monitoring
|
||||
|
|
9
app/workers/requests_profiles_worker.rb
Normal file
9
app/workers/requests_profiles_worker.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class RequestsProfilesWorker
|
||||
include Sidekiq::Worker
|
||||
|
||||
sidekiq_options queue: :default
|
||||
|
||||
def perform
|
||||
Gitlab::RequestProfiler.remove_all_profiles
|
||||
end
|
||||
end
|
|
@ -290,6 +290,9 @@ Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'Repository
|
|||
Settings.cron_jobs['gitlab_remove_project_export_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *'
|
||||
Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker'
|
||||
Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({})
|
||||
Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *'
|
||||
Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker'
|
||||
|
||||
#
|
||||
# GitLab Shell
|
||||
|
|
3
config/initializers/request_profiler.rb
Normal file
3
config/initializers/request_profiler.rb
Normal file
|
@ -0,0 +1,3 @@
|
|||
Rails.application.configure do |config|
|
||||
config.middleware.use(Gitlab::RequestProfiler::Middleware)
|
||||
end
|
|
@ -281,6 +281,7 @@ Rails.application.routes.draw do
|
|||
resource :health_check, controller: 'health_check', only: [:show]
|
||||
resource :background_jobs, controller: 'background_jobs', only: [:show]
|
||||
resource :system_info, controller: 'system_info', only: [:show]
|
||||
resources :requests_profiles, only: [:index, :show], param: :name
|
||||
|
||||
resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do
|
||||
root to: 'projects#index', as: :projects
|
||||
|
|
19
lib/gitlab/request_profiler.rb
Normal file
19
lib/gitlab/request_profiler.rb
Normal file
|
@ -0,0 +1,19 @@
|
|||
require 'fileutils'
|
||||
|
||||
module Gitlab
|
||||
module RequestProfiler
|
||||
PROFILES_DIR = "#{Gitlab.config.shared.path}/tmp/requests_profiles"
|
||||
|
||||
def profile_token
|
||||
Rails.cache.fetch('profile-token') do
|
||||
Devise.friendly_token
|
||||
end
|
||||
end
|
||||
module_function :profile_token
|
||||
|
||||
def remove_all_profiles
|
||||
FileUtils.rm_rf(PROFILES_DIR)
|
||||
end
|
||||
module_function :remove_all_profiles
|
||||
end
|
||||
end
|
47
lib/gitlab/request_profiler/middleware.rb
Normal file
47
lib/gitlab/request_profiler/middleware.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
require 'ruby-prof'
|
||||
|
||||
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 = RequestProfiler.profile_token
|
||||
return unless profile_token.present?
|
||||
|
||||
header_token == profile_token
|
||||
end
|
||||
|
||||
def call_with_profiling(env)
|
||||
ret = nil
|
||||
result = RubyProf::Profile.profile do
|
||||
ret = @app.call(env)
|
||||
end
|
||||
|
||||
printer = RubyProf::CallStackPrinter.new(result)
|
||||
file_name = "#{env['PATH_INFO'].tr('/', '|')}_#{Time.current.to_i}.html"
|
||||
file_path = "#{PROFILES_DIR}/#{file_name}"
|
||||
|
||||
FileUtils.mkdir_p(PROFILES_DIR)
|
||||
File.open(file_path, 'wb') do |file|
|
||||
printer.print(file)
|
||||
end
|
||||
|
||||
ret
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
43
lib/gitlab/request_profiler/profile.rb
Normal file
43
lib/gitlab/request_profiler/profile.rb
Normal file
|
@ -0,0 +1,43 @@
|
|||
module Gitlab
|
||||
module RequestProfiler
|
||||
class Profile
|
||||
attr_reader :name, :time, :request_path
|
||||
|
||||
alias_method :to_param, :name
|
||||
|
||||
def self.all
|
||||
Dir["#{PROFILES_DIR}/*.html"].map do |path|
|
||||
new(File.basename(path))
|
||||
end
|
||||
end
|
||||
|
||||
def self.find(name)
|
||||
name_dup = name.dup
|
||||
name_dup << '.html' unless name.end_with?('.html')
|
||||
|
||||
file_path = "#{PROFILES_DIR}/#{name_dup}"
|
||||
return unless File.exist?(file_path)
|
||||
|
||||
new(name_dup)
|
||||
end
|
||||
|
||||
def initialize(name)
|
||||
@name = name
|
||||
|
||||
set_attributes
|
||||
end
|
||||
|
||||
def content
|
||||
File.read("#{PROFILES_DIR}/#{name}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_attributes
|
||||
_, path, timestamp = name.split(/(.*)_(\d+)\.html$/)
|
||||
@request_path = path.tr('|', '/')
|
||||
@time = Time.at(timestamp.to_i).utc
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue