Commit 7e467d1c authored by Sean McGivern's avatar Sean McGivern

Add metric initializer spec

An empty file in one of the instrumented directories will cause the app
to fail to start when metrics are enabled. Metrics aren't enabled by
default in development or test.

We could handle the empty file case explicitly, but a file could still
not define the constant it is expected to, so instead run the
initializer manually in a spec and check that it succeeds.
parent 1473908e
if Gitlab::Metrics.enabled? # Autoload all classes that we want to instrument, and instrument the methods we
require 'pathname' # need. This takes the Gitlab::Metrics::Instrumentation module as an argument so
require 'influxdb' # that we can stub it for testing, as it is only called when metrics are
require 'connection_pool' # enabled.
require 'method_source' #
# rubocop:disable Metrics/AbcSize
# These are manually require'd so the classes are registered properly with def instrument_classes(instrumentation)
# ActiveSupport. instrumentation.instrument_instance_methods(Gitlab::Shell)
require 'gitlab/metrics/subscribers/action_view'
require 'gitlab/metrics/subscribers/active_record'
require 'gitlab/metrics/subscribers/rails_cache'
Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::Metrics::RackMiddleware)
config.middleware.use(Gitlab::Middleware::RailsQueueDuration)
end
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Gitlab::Metrics::SidekiqMiddleware
end
end
# This instruments all methods residing in app/models that (appear to) use any
# of the ActiveRecord methods. This has to take place _after_ initializing as
# for some unknown reason calling eager_load! earlier breaks Devise.
Gitlab::Application.config.after_initialize do
Rails.application.eager_load!
models = Rails.root.join('app', 'models').to_s
regex = Regexp.union(
ActiveRecord::Querying.public_instance_methods(false).map(&:to_s)
)
Gitlab::Metrics::Instrumentation.
instrument_class_hierarchy(ActiveRecord::Base) do |klass, method|
# Instrumenting the ApplicationSetting class can lead to an infinite
# loop. Since the data is cached any way we don't really need to
# instrument it.
if klass == ApplicationSetting
false
else
loc = method.source_location
loc && loc[0].start_with?(models) && method.source =~ regex instrumentation.instrument_methods(Gitlab::Git)
end
end
end
Gitlab::Metrics::Instrumentation.configure do |config|
config.instrument_instance_methods(Gitlab::Shell)
config.instrument_methods(Gitlab::Git)
Gitlab::Git.constants.each do |name| Gitlab::Git.constants.each do |name|
const = Gitlab::Git.const_get(name) const = Gitlab::Git.const_get(name)
next unless const.is_a?(Module) next unless const.is_a?(Module)
config.instrument_methods(const) instrumentation.instrument_methods(const)
config.instrument_instance_methods(const) instrumentation.instrument_instance_methods(const)
end end
# Path to search => prefix to strip from constant # Path to search => prefix to strip from constant
...@@ -80,13 +36,13 @@ if Gitlab::Metrics.enabled? ...@@ -80,13 +36,13 @@ if Gitlab::Metrics.enabled?
path = Pathname.new(file_path).relative_path_from(prefix) path = Pathname.new(file_path).relative_path_from(prefix)
const = path.to_s.sub('.rb', '').camelize.constantize const = path.to_s.sub('.rb', '').camelize.constantize
config.instrument_methods(const) instrumentation.instrument_methods(const)
config.instrument_instance_methods(const) instrumentation.instrument_instance_methods(const)
end end
end end
config.instrument_methods(Premailer::Adapter::Nokogiri) instrumentation.instrument_methods(Premailer::Adapter::Nokogiri)
config.instrument_instance_methods(Premailer::Adapter::Nokogiri) instrumentation.instrument_instance_methods(Premailer::Adapter::Nokogiri)
[ [
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository, :Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
...@@ -94,8 +50,8 @@ if Gitlab::Metrics.enabled? ...@@ -94,8 +50,8 @@ if Gitlab::Metrics.enabled?
].each do |name| ].each do |name|
const = Rugged.const_get(name) const = Rugged.const_get(name)
config.instrument_methods(const) instrumentation.instrument_methods(const)
config.instrument_instance_methods(const) instrumentation.instrument_instance_methods(const)
end end
# Instruments all Banzai filters and reference parsers # Instruments all Banzai filters and reference parsers
...@@ -107,74 +63,129 @@ if Gitlab::Metrics.enabled? ...@@ -107,74 +63,129 @@ if Gitlab::Metrics.enabled?
klass = File.basename(file, File.extname(file)).camelize klass = File.basename(file, File.extname(file)).camelize
const = Banzai.const_get(const_name).const_get(klass) const = Banzai.const_get(const_name).const_get(klass)
config.instrument_methods(const) instrumentation.instrument_methods(const)
config.instrument_instance_methods(const) instrumentation.instrument_instance_methods(const)
end end
end end
config.instrument_methods(Banzai::Renderer) instrumentation.instrument_methods(Banzai::Renderer)
config.instrument_methods(Banzai::Querying) instrumentation.instrument_methods(Banzai::Querying)
config.instrument_instance_methods(Banzai::ObjectRenderer) instrumentation.instrument_instance_methods(Banzai::ObjectRenderer)
config.instrument_instance_methods(Banzai::Redactor) instrumentation.instrument_instance_methods(Banzai::Redactor)
config.instrument_methods(Banzai::NoteRenderer) instrumentation.instrument_methods(Banzai::NoteRenderer)
[Issuable, Mentionable, Participable].each do |klass| [Issuable, Mentionable, Participable].each do |klass|
config.instrument_instance_methods(klass) instrumentation.instrument_instance_methods(klass)
config.instrument_instance_methods(klass::ClassMethods) instrumentation.instrument_instance_methods(klass::ClassMethods)
end end
config.instrument_methods(Gitlab::ReferenceExtractor) instrumentation.instrument_methods(Gitlab::ReferenceExtractor)
config.instrument_instance_methods(Gitlab::ReferenceExtractor) instrumentation.instrument_instance_methods(Gitlab::ReferenceExtractor)
# Instrument the classes used for checking if somebody has push access. # Instrument the classes used for checking if somebody has push access.
config.instrument_instance_methods(Gitlab::GitAccess) instrumentation.instrument_instance_methods(Gitlab::GitAccess)
config.instrument_instance_methods(Gitlab::GitAccessWiki) instrumentation.instrument_instance_methods(Gitlab::GitAccessWiki)
config.instrument_instance_methods(API::Helpers) instrumentation.instrument_instance_methods(API::Helpers)
config.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker) instrumentation.instrument_instance_methods(RepositoryCheck::SingleRepositoryWorker)
config.instrument_instance_methods(Rouge::Plugins::Redcarpet) instrumentation.instrument_instance_methods(Rouge::Plugins::Redcarpet)
config.instrument_instance_methods(Rouge::Formatters::HTMLGitlab) instrumentation.instrument_instance_methods(Rouge::Formatters::HTMLGitlab)
[:XML, :HTML].each do |namespace| [:XML, :HTML].each do |namespace|
namespace_mod = Nokogiri.const_get(namespace) namespace_mod = Nokogiri.const_get(namespace)
config.instrument_methods(namespace_mod) instrumentation.instrument_methods(namespace_mod)
config.instrument_methods(namespace_mod::Document) instrumentation.instrument_methods(namespace_mod::Document)
end end
config.instrument_methods(Rinku) instrumentation.instrument_methods(Rinku)
config.instrument_instance_methods(Repository) instrumentation.instrument_instance_methods(Repository)
config.instrument_methods(Gitlab::Highlight) instrumentation.instrument_methods(Gitlab::Highlight)
config.instrument_instance_methods(Gitlab::Highlight) instrumentation.instrument_instance_methods(Gitlab::Highlight)
config.instrument_methods(Elasticsearch::Git::Repository) instrumentation.instrument_methods(Elasticsearch::Git::Repository)
config.instrument_instance_methods(Elasticsearch::Git::Repository) instrumentation.instrument_instance_methods(Elasticsearch::Git::Repository)
config.instrument_instance_methods(Search::GlobalService) instrumentation.instrument_instance_methods(Search::GlobalService)
config.instrument_instance_methods(Search::ProjectService) instrumentation.instrument_instance_methods(Search::ProjectService)
config.instrument_instance_methods(Gitlab::Elastic::SearchResults) instrumentation.instrument_instance_methods(Gitlab::Elastic::SearchResults)
config.instrument_instance_methods(Gitlab::Elastic::ProjectSearchResults) instrumentation.instrument_instance_methods(Gitlab::Elastic::ProjectSearchResults)
config.instrument_instance_methods(Gitlab::Elastic::Indexer) instrumentation.instrument_instance_methods(Gitlab::Elastic::Indexer)
config.instrument_instance_methods(Gitlab::Elastic::SnippetSearchResults) instrumentation.instrument_instance_methods(Gitlab::Elastic::SnippetSearchResults)
config.instrument_methods(Gitlab::Elastic::Helper) instrumentation.instrument_methods(Gitlab::Elastic::Helper)
config.instrument_instance_methods(Elastic::ApplicationSearch) instrumentation.instrument_instance_methods(Elastic::ApplicationSearch)
config.instrument_instance_methods(Elastic::IssuesSearch) instrumentation.instrument_instance_methods(Elastic::IssuesSearch)
config.instrument_instance_methods(Elastic::MergeRequestsSearch) instrumentation.instrument_instance_methods(Elastic::MergeRequestsSearch)
config.instrument_instance_methods(Elastic::MilestonesSearch) instrumentation.instrument_instance_methods(Elastic::MilestonesSearch)
config.instrument_instance_methods(Elastic::NotesSearch) instrumentation.instrument_instance_methods(Elastic::NotesSearch)
config.instrument_instance_methods(Elastic::ProjectsSearch) instrumentation.instrument_instance_methods(Elastic::ProjectsSearch)
config.instrument_instance_methods(Elastic::RepositoriesSearch) instrumentation.instrument_instance_methods(Elastic::RepositoriesSearch)
config.instrument_instance_methods(Elastic::SnippetsSearch) instrumentation.instrument_instance_methods(Elastic::SnippetsSearch)
config.instrument_instance_methods(Elastic::WikiRepositoriesSearch) instrumentation.instrument_instance_methods(Elastic::WikiRepositoriesSearch)
# This is a Rails scope so we have to instrument it manually. # This is a Rails scope so we have to instrument it manually.
config.instrument_method(Project, :visible_to_user) instrumentation.instrument_method(Project, :visible_to_user)
end
# rubocop:enable Metrics/AbcSize
if Gitlab::Metrics.enabled?
require 'pathname'
require 'influxdb'
require 'connection_pool'
require 'method_source'
# These are manually require'd so the classes are registered properly with
# ActiveSupport.
require 'gitlab/metrics/subscribers/action_view'
require 'gitlab/metrics/subscribers/active_record'
require 'gitlab/metrics/subscribers/rails_cache'
Gitlab::Application.configure do |config|
config.middleware.use(Gitlab::Metrics::RackMiddleware)
config.middleware.use(Gitlab::Middleware::RailsQueueDuration)
end
Sidekiq.configure_server do |config|
config.server_middleware do |chain|
chain.add Gitlab::Metrics::SidekiqMiddleware
end
end
# This instruments all methods residing in app/models that (appear to) use any
# of the ActiveRecord methods. This has to take place _after_ initializing as
# for some unknown reason calling eager_load! earlier breaks Devise.
Gitlab::Application.config.after_initialize do
Rails.application.eager_load!
models = Rails.root.join('app', 'models').to_s
regex = Regexp.union(
ActiveRecord::Querying.public_instance_methods(false).map(&:to_s)
)
Gitlab::Metrics::Instrumentation.
instrument_class_hierarchy(ActiveRecord::Base) do |klass, method|
# Instrumenting the ApplicationSetting class can lead to an infinite
# loop. Since the data is cached any way we don't really need to
# instrument it.
if klass == ApplicationSetting
false
else
loc = method.source_location
loc && loc[0].start_with?(models) && method.source =~ regex
end
end
end
Gitlab::Metrics::Instrumentation.configure do |config|
instrument_classes(config)
end end
GC::Profiler.enable GC::Profiler.enable
......
require 'spec_helper'
require_relative '../../config/initializers/metrics'
describe 'instrument_classes', lib: true do
let(:config) { double(:config) }
before do
allow(config).to receive(:instrument_method)
allow(config).to receive(:instrument_methods)
allow(config).to receive(:instrument_instance_methods)
end
it 'can autoload and instrument all files' do
expect { instrument_classes(config) }.not_to raise_error
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment