Profile requests when a header is passed

This commit is contained in:
Ahmad Sherif 2016-07-15 17:46:39 +02:00
parent 0c799be6b6
commit 345cd22f21
14 changed files with 178 additions and 1 deletions

View file

@ -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

View file

@ -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'

View file

@ -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)

View 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

View file

@ -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

View 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

View file

@ -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

View file

@ -0,0 +1,9 @@
class RequestsProfilesWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform
Gitlab::RequestProfiler.remove_all_profiles
end
end

View file

@ -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

View file

@ -0,0 +1,3 @@
Rails.application.configure do |config|
config.middleware.use(Gitlab::RequestProfiler::Middleware)
end

View file

@ -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

View 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

View 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

View 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