Module: Gitlab::Profiler
- Defined in:
- lib/gitlab/profiler.rb
Constant Summary collapse
- FILTERED_STRING =
'[FILTERED]'
- IGNORE_BACKTRACES =
%w[ lib/gitlab/i18n.rb lib/gitlab/request_context.rb config/initializers lib/gitlab/database/load_balancing/ lib/gitlab/etag_caching/ lib/gitlab/metrics/ lib/gitlab/middleware/ ee/lib/gitlab/middleware/ lib/gitlab/performance_bar/ lib/gitlab/query_limiting/ lib/gitlab/tracing/ lib/gitlab/profiler.rb lib/gitlab/correlation_id.rb lib/gitlab/webpack/dev_server_middleware.rb lib/gitlab/sidekiq_status/ lib/gitlab/sidekiq_logging/ lib/gitlab/sidekiq_middleware/ ].freeze
Class Method Summary collapse
- .create_custom_logger(logger, private_token: nil) ⇒ Object
-
.log_load_times_by_model(logger) ⇒ Object
rubocop: disable CodeReuse/ActiveRecord.
-
.profile(url, logger: nil, post_data: nil, user: nil, private_token: nil, profiler_options: {}) ⇒ Object
Takes a URL to profile (can be a fully-qualified URL, or an absolute path) and returns the profiler result.
- .remove_method(klass, meth) ⇒ Object
- .with_custom_logger(logger) ⇒ Object
-
.with_profiler(profiler_options) ⇒ Object
rubocop: enable CodeReuse/ActiveRecord.
- .with_user(user) ⇒ Object
Class Method Details
.create_custom_logger(logger, private_token: nil) ⇒ Object
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/gitlab/profiler.rb', line 90 def self.create_custom_logger(logger, private_token: nil) return unless logger logger.dup.tap do |new_logger| new_logger.instance_variable_set(:@private_token, private_token) class << new_logger attr_reader :load_times_by_model, :private_token def debug(, *) = .gsub(private_token, FILTERED_STRING) if private_token _, type, time = *.match(/(\w+) Load \(([0-9.]+)ms\)/) if type && time @load_times_by_model ||= {} @load_times_by_model[type] ||= [] @load_times_by_model[type] << time.to_f end super Gitlab::BacktraceCleaner.clean_backtrace(caller).each do |caller_line| stripped_caller_line = caller_line.sub("#{Rails.root}/", '') super(" ↳ #{stripped_caller_line}") end end end end end |
.log_load_times_by_model(logger) ⇒ Object
rubocop: disable CodeReuse/ActiveRecord
159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/gitlab/profiler.rb', line 159 def self.log_load_times_by_model(logger) return unless logger.respond_to?(:load_times_by_model) summarised_load_times = logger.load_times_by_model.to_a.map do |(model, times)| [model, times.count, times.sum] end summarised_load_times.sort_by(&:last).reverse_each do |(model, query_count, time)| logger.info("#{model} total (#{query_count}): #{time.round(2)}ms") end end |
.profile(url, logger: nil, post_data: nil, user: nil, private_token: nil, profiler_options: {}) ⇒ Object
Takes a URL to profile (can be a fully-qualified URL, or an absolute path) and returns the profiler result. Formatting that result is the caller’s responsibility. Requests are GET requests unless post_data is passed.
Optional arguments:
-
logger: will be used for SQL logging, including a summary at the end of the log file of the total time spent per model class.
-
post_data: a string of raw POST data to use. Changes the HTTP verb to POST.
-
user: a user to authenticate as.
-
private_token: instead of providing a user instance, the token can be given as a string. Takes precedence over the user option.
-
profiler_options: A keyword Hash of arguments passed to the profiler. Defaults: { mode: :wall, out: <some temporary file>, interval: 1000, raw: true }
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
# File 'lib/gitlab/profiler.rb', line 46 def self.profile(url, logger: nil, post_data: nil, user: nil, private_token: nil, profiler_options: {}) app = ActionDispatch::Integration::Session.new(Rails.application) verb = :get headers = {} if post_data verb = :post headers['Content-Type'] = 'application/json' end if private_token headers['Private-Token'] = private_token user = nil # private_token overrides user end logger = create_custom_logger(logger, private_token: private_token) result = ::Gitlab::SafeRequestStore.ensure_request_store do # Make an initial call for an asset path in development mode to avoid # sprockets dominating the profiler output. ActionController::Base.helpers.asset_path('katex.css') if Rails.env.development? # Rails loads internationalization files lazily the first time a # translation is needed. Running this prevents this overhead from showing # up in profiles. ::I18n.t('.')[:test_string] # Remove API route mounting from the profile. app.get('/api/v4/users') with_custom_logger(logger) do with_user(user) do with_profiler() do app.public_send(verb, url, params: post_data, headers: headers) # rubocop:disable GitlabSecurity/PublicSend end end end end log_load_times_by_model(logger) result end |
.remove_method(klass, meth) ⇒ Object
154 155 156 |
# File 'lib/gitlab/profiler.rb', line 154 def self.remove_method(klass, meth) klass.send(:remove_method, meth) if klass.instance_methods(false).include?(meth) # rubocop:disable GitlabSecurity/PublicSend end |
.with_custom_logger(logger) ⇒ Object
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/gitlab/profiler.rb', line 122 def self.with_custom_logger(logger) original_colorize_logging = ActiveSupport::LogSubscriber.colorize_logging original_activerecord_logger = ActiveRecord::Base.logger original_actioncontroller_logger = ActionController::Base.logger if logger ActiveSupport::LogSubscriber.colorize_logging = false ActiveRecord::Base.logger = logger ActionController::Base.logger = logger end yield.tap do ActiveSupport::LogSubscriber.colorize_logging = original_colorize_logging ActiveRecord::Base.logger = original_activerecord_logger ActionController::Base.logger = original_actioncontroller_logger end end |
.with_profiler(profiler_options) ⇒ Object
rubocop: enable CodeReuse/ActiveRecord
172 173 174 175 176 177 |
# File 'lib/gitlab/profiler.rb', line 172 def self.with_profiler() require 'stackprof' args = { mode: :wall, interval: 1000, raw: true }.merge!() args[:out] ||= ::Tempfile.new(["profile-#{Time.now.to_i}-", ".dump"]).path ::StackProf.run(**args) { yield } end |
.with_user(user) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 152 |
# File 'lib/gitlab/profiler.rb', line 140 def self.with_user(user) if user API::Helpers::CommonHelpers.send(:define_method, :find_current_user!) { user } # rubocop:disable GitlabSecurity/PublicSend ApplicationController.send(:define_method, :current_user) { user } # rubocop:disable GitlabSecurity/PublicSend ApplicationController.send(:define_method, :authenticate_user!) {} # rubocop:disable GitlabSecurity/PublicSend end yield.tap do remove_method(API::Helpers::CommonHelpers, :find_current_user!) remove_method(ApplicationController, :current_user) remove_method(ApplicationController, :authenticate_user!) end end |