Commit 905f8d76 authored by Yorick Peterse's avatar Yorick Peterse

Reduce instrumentation overhead

This reduces the overhead of the method instrumentation code primarily
by reducing the number of method calls. There are also some other small
optimisations such as not casting timing values to Floats (there's no
particular need for this), using Symbols for method call metric names,
and reducing the number of Hash lookups for instrumented methods.

The exact impact depends on the code being executed. For example, for a
method that's only called once the difference won't be very noticeable.
However, for methods that are called many times the difference can be
more significant.

For example, the loading time of a large commit
(nrclark/dummy_project@81ebdea5df2fb42e59257cb3eaad671a5c53ca36)
was reduced from around 19 seconds to around 15 seconds using these
changes.
parent 17be364d
...@@ -15,6 +15,7 @@ v 8.11.0 (unreleased) ...@@ -15,6 +15,7 @@ v 8.11.0 (unreleased)
- Add build event color in HipChat messages (David Eisner) - Add build event color in HipChat messages (David Eisner)
- Make fork counter always clickable. !5463 (winniehell) - Make fork counter always clickable. !5463 (winniehell)
- All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333
- The overhead of instrumented method calls has been reduced
- Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le)
- Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Load project invited groups and members eagerly in `ProjectTeam#fetch_members`
- Make branches sortable without push permission !5462 (winniehell) - Make branches sortable without push permission !5462 (winniehell)
......
...@@ -124,6 +124,11 @@ module Gitlab ...@@ -124,6 +124,11 @@ module Gitlab
trans.action = action if trans trans.action = action if trans
end end
# Returns the prefix to use for the name of a series.
def self.series_prefix
@series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_'
end
# When enabled this should be set before being used as the usual pattern # When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe. # "@foo ||= bar" is _not_ thread-safe.
if enabled? if enabled?
......
...@@ -9,14 +9,17 @@ module Gitlab ...@@ -9,14 +9,17 @@ module Gitlab
# #
# Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login) # Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)
module Instrumentation module Instrumentation
SERIES = 'method_calls'
PROXY_IVAR = :@__gitlab_instrumentation_proxy PROXY_IVAR = :@__gitlab_instrumentation_proxy
def self.configure def self.configure
yield self yield self
end end
# Returns the name of the series to use for storing method calls.
def self.series
@series ||= "#{Metrics.series_prefix}method_calls"
end
# Instruments a class method. # Instruments a class method.
# #
# mod - The module to instrument as a Module/Class. # mod - The module to instrument as a Module/Class.
...@@ -141,15 +144,15 @@ module Gitlab ...@@ -141,15 +144,15 @@ module Gitlab
# generated method _only_ accepts regular arguments if the underlying # generated method _only_ accepts regular arguments if the underlying
# method also accepts them. # method also accepts them.
if method.arity == 0 if method.arity == 0
args_signature = '&block' args_signature = ''
else else
args_signature = '*args, &block' args_signature = '*args'
end end
proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1 proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
def #{name}(#{args_signature}) def #{name}(#{args_signature})
if trans = Gitlab::Metrics::Instrumentation.transaction if trans = Gitlab::Metrics::Instrumentation.transaction
trans.measure_method(#{label.inspect}) { super } trans.method_call_for(#{label.to_sym.inspect}).measure { super }
else else
super super
end end
......
...@@ -11,8 +11,8 @@ module Gitlab ...@@ -11,8 +11,8 @@ module Gitlab
def initialize(name, series) def initialize(name, series)
@name = name @name = name
@series = series @series = series
@real_time = 0.0 @real_time = 0
@cpu_time = 0.0 @cpu_time = 0
@call_count = 0 @call_count = 0
end end
......
...@@ -35,12 +35,12 @@ module Gitlab ...@@ -35,12 +35,12 @@ module Gitlab
if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID) if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID)
def self.cpu_time def self.cpu_time
Process. Process.
clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond)
end end
else else
def self.cpu_time def self.cpu_time
Process. Process.
clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond)
end end
end end
...@@ -48,14 +48,14 @@ module Gitlab ...@@ -48,14 +48,14 @@ module Gitlab
# #
# Returns the time as a Float. # Returns the time as a Float.
def self.real_time(precision = :millisecond) def self.real_time(precision = :millisecond)
Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f Process.clock_gettime(Process::CLOCK_REALTIME, precision)
end end
# Returns the current monotonic clock time in a given precision. # Returns the current monotonic clock time in a given precision.
# #
# Returns the time as a Float. # Returns the time as a Float.
def self.monotonic_time(precision = :millisecond) def self.monotonic_time(precision = :millisecond)
Process.clock_gettime(Process::CLOCK_MONOTONIC, precision).to_f Process.clock_gettime(Process::CLOCK_MONOTONIC, precision)
end end
end end
end end
......
...@@ -52,23 +52,16 @@ module Gitlab ...@@ -52,23 +52,16 @@ module Gitlab
end end
def add_metric(series, values, tags = {}) def add_metric(series, values, tags = {})
@metrics << Metric.new("#{series_prefix}#{series}", values, tags) @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags)
end end
# Measures the time it takes to execute a method. # Returns a MethodCall object for the given name.
# def method_call_for(name)
# Multiple calls to the same method add up to the total runtime of the unless method = @methods[name]
# method. @methods[name] = method = MethodCall.new(name, Instrumentation.series)
#
# name - The full name of the method to measure (e.g. `User#sign_in`).
def measure_method(name, &block)
unless @methods[name]
series = "#{series_prefix}#{Instrumentation::SERIES}"
@methods[name] = MethodCall.new(name, series)
end end
@methods[name].measure(&block) method
end end
def increment(name, value) def increment(name, value)
...@@ -115,14 +108,6 @@ module Gitlab ...@@ -115,14 +108,6 @@ module Gitlab
Metrics.submit_metrics(submit_hashes) Metrics.submit_metrics(submit_hashes)
end end
def sidekiq?
Sidekiq.server?
end
def series_prefix
sidekiq? ? 'sidekiq_' : 'rails_'
end
end end
end end
end end
...@@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do
allow(@dummy).to receive(:name).and_return('Dummy') allow(@dummy).to receive(:name).and_return('Dummy')
end end
describe '.series' do
it 'returns a String' do
expect(described_class.series).to be_an_instance_of(String)
end
end
describe '.configure' do describe '.configure' do
it 'yields self' do it 'yields self' do
described_class.configure do |c| described_class.configure do |c|
...@@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:measure_method). expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
with('Dummy.foo')
@dummy.foo @dummy.foo
end end
...@@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do ...@@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction). allow(described_class).to receive(:transaction).
and_return(transaction) and_return(transaction)
expect(transaction).to receive(:measure_method). expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure)
with('Dummy#bar')
@dummy.new.bar @dummy.new.bar
end end
......
...@@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do ...@@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do
end end
describe '.cpu_time' do describe '.cpu_time' do
it 'returns a Float' do it 'returns a Fixnum' do
expect(described_class.cpu_time).to be_an_instance_of(Float) expect(described_class.cpu_time).to be_an_instance_of(Fixnum)
end end
end end
describe '.real_time' do describe '.real_time' do
it 'returns a Float' do it 'returns a Fixnum' do
expect(described_class.real_time).to be_an_instance_of(Float) expect(described_class.real_time).to be_an_instance_of(Fixnum)
end end
end end
describe '.monotonic_time' do describe '.monotonic_time' do
it 'returns a Float' do it 'returns a Fixnum' do
expect(described_class.monotonic_time).to be_an_instance_of(Float) expect(described_class.monotonic_time).to be_an_instance_of(Fixnum)
end end
end end
end end
...@@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do ...@@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do
end end
end end
describe '#measure_method' do describe '#method_call_for' do
it 'adds a new method if it does not exist already' do it 'returns a MethodCall' do
transaction.measure_method('Foo#bar') { 'foo' } method = transaction.method_call_for('Foo#bar')
expect(transaction.methods['Foo#bar']). expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall)
to be_an_instance_of(Gitlab::Metrics::MethodCall)
end
it 'adds timings to an existing method call' do
transaction.measure_method('Foo#bar') { 'foo' }
transaction.measure_method('Foo#bar') { 'foo' }
expect(transaction.methods['Foo#bar'].call_count).to eq(2)
end end
end end
......
...@@ -147,4 +147,10 @@ describe Gitlab::Metrics do ...@@ -147,4 +147,10 @@ describe Gitlab::Metrics do
end end
end end
end end
describe '#series_prefix' do
it 'returns a String' do
expect(described_class.series_prefix).to be_an_instance_of(String)
end
end
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