Profile requests when a header is passed
This commit is contained in:
		
							parent
							
								
									0c799be6b6
								
							
						
					
					
						commit
						345cd22f21
					
				|  | @ -15,6 +15,7 @@ v 8.11.0 (unreleased) | ||||||
|   - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) |   - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) | ||||||
|   - Add the `sprockets-es6` gem |   - Add the `sprockets-es6` gem | ||||||
|   - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) |   - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) | ||||||
|  |   - Profile requests when a header is passed | ||||||
| 
 | 
 | ||||||
| v 8.10.2 (unreleased) | v 8.10.2 (unreleased) | ||||||
|   - User can now search branches by name. !5144 |   - 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 'email_reply_parser', '~> 0.5.8' | ||||||
| 
 | 
 | ||||||
|  | gem 'ruby-prof', '~> 0.15.9' | ||||||
|  | 
 | ||||||
| ## CI | ## CI | ||||||
| gem 'activerecord-session_store', '~> 1.0.0' | gem 'activerecord-session_store', '~> 1.0.0' | ||||||
| gem 'nested_form', '~> 0.3.2' | gem 'nested_form', '~> 0.3.2' | ||||||
|  |  | ||||||
|  | @ -620,6 +620,7 @@ GEM | ||||||
|       rubocop (>= 0.40.0) |       rubocop (>= 0.40.0) | ||||||
|     ruby-fogbugz (0.2.1) |     ruby-fogbugz (0.2.1) | ||||||
|       crack (~> 0.4) |       crack (~> 0.4) | ||||||
|  |     ruby-prof (0.15.9) | ||||||
|     ruby-progressbar (1.8.1) |     ruby-progressbar (1.8.1) | ||||||
|     ruby-saml (1.3.0) |     ruby-saml (1.3.0) | ||||||
|       nokogiri (>= 1.5.10) |       nokogiri (>= 1.5.10) | ||||||
|  | @ -948,6 +949,7 @@ DEPENDENCIES | ||||||
|   rubocop (~> 0.41.2) |   rubocop (~> 0.41.2) | ||||||
|   rubocop-rspec (~> 1.5.0) |   rubocop-rspec (~> 1.5.0) | ||||||
|   ruby-fogbugz (~> 0.2.1) |   ruby-fogbugz (~> 0.2.1) | ||||||
|  |   ruby-prof (~> 0.15.9) | ||||||
|   sanitize (~> 2.0) |   sanitize (~> 2.0) | ||||||
|   sass-rails (~> 5.0.0) |   sass-rails (~> 5.0.0) | ||||||
|   scss_lint (~> 0.47.0) |   scss_lint (~> 0.47.0) | ||||||
|  |  | ||||||
|  | @ -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 |       = link_to admin_health_check_path, title: 'Health Check' do | ||||||
|         %span |         %span | ||||||
|           Health Check |           Health Check | ||||||
|  |     = nav_link(controller: :requests_profiles) do | ||||||
|  |       = link_to admin_requests_profiles_path, title: 'Requests Profiles' do | ||||||
|  |         %span | ||||||
|  |           Requests Profiles | ||||||
|  |  | ||||||
|  | @ -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 |       = link_to admin_root_path, title: 'Overview', class: 'shortcuts-tree' do | ||||||
|         %span |         %span | ||||||
|           Overview |           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 |       = link_to admin_system_info_path, title: 'Monitoring' do | ||||||
|         %span |         %span | ||||||
|           Monitoring |           Monitoring | ||||||
|  |  | ||||||
|  | @ -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'] ||= Settingslogic.new({}) | ||||||
| Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *' | Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *' | ||||||
| Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker' | 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 | # GitLab Shell | ||||||
|  |  | ||||||
|  | @ -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 :health_check, controller: 'health_check', only: [:show] | ||||||
|     resource :background_jobs, controller: 'background_jobs', only: [:show] |     resource :background_jobs, controller: 'background_jobs', only: [:show] | ||||||
|     resource :system_info, controller: 'system_info', 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 |     resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do | ||||||
|       root to: 'projects#index', as: :projects |       root to: 'projects#index', as: :projects | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  | @ -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 | ||||||
|  | @ -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 New Issue