Module: Gitlab::Metrics::Instrumentation

Defined in:
lib/gitlab/metrics/instrumentation.rb

Overview

Module for instrumenting methods.

This module allows instrumenting of methods without having to actually alter the target code (e.g. by including modules).

Example usage:

Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login)

Constant Summary collapse

SERIES =
'method_calls'
PROXY_IVAR =
:@__gitlab_instrumentation_proxy

Class Method Summary collapse

Class Method Details

.configure {|_self| ... } ⇒ Object

Yields:

  • (_self)

Yield Parameters:


16
17
18
# File 'lib/gitlab/metrics/instrumentation.rb', line 16

def self.configure
  yield self
end

.instrument(type, mod, name) ⇒ Object

Instruments a method.

type - The type (:class or :instance) of method to instrument. mod - The module containing the method. name - The name of the method to instrument.


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/gitlab/metrics/instrumentation.rb', line 113

def self.instrument(type, mod, name)
  return unless Metrics.enabled?

  name   = name.to_sym
  target = type == :instance ? mod : mod.singleton_class

  if type == :instance
    target = mod
    label  = "#{mod.name}##{name}"
    method = mod.instance_method(name)
  else
    target = mod.singleton_class
    label  = "#{mod.name}.#{name}"
    method = mod.method(name)
  end

  unless instrumented?(target)
    target.instance_variable_set(PROXY_IVAR, Module.new)
  end

  proxy_module = self.proxy_module(target)

  # Some code out there (e.g. the "state_machine" Gem) checks the arity of
  # a method to make sure it only passes arguments when the method expects
  # any. If we were to always overwrite a method to take an `*args`
  # signature this would break things. As a result we'll make sure the
  # generated method _only_ accepts regular arguments if the underlying
  # method also accepts them.
  if method.arity == 0
    args_signature = '&block'
  else
    args_signature = '*args, &block'
  end

  proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1
    def #{name}(#{args_signature})
      trans = Gitlab::Metrics::Instrumentation.transaction

      if trans
        start    = Time.now
        retval   = super
        duration = (Time.now - start) * 1000.0

        if duration >= Gitlab::Metrics.method_call_threshold
          trans.increment(:method_duration, duration)

          trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
                           { duration: duration },
                           method: #{label.inspect})
        end

        retval
      else
        super
      end
    end
  EOF

  target.prepend(proxy_module)
end

.instrument_class_hierarchy(root, &block) ⇒ Object

Recursively instruments all subclasses of the given root module.

This can be used to for example instrument all ActiveRecord models (as these all inherit from ActiveRecord::Base).

This method can optionally take a block to pass to `instrument_methods` and `instrument_instance_methods`.

root - The root module for which to instrument subclasses. The root

module itself is not instrumented.

46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/gitlab/metrics/instrumentation.rb', line 46

def self.instrument_class_hierarchy(root, &block)
  visit = root.subclasses

  until visit.empty?
    klass = visit.pop

    instrument_methods(klass, &block)
    instrument_instance_methods(klass, &block)

    klass.subclasses.each { |c| visit << c }
  end
end

.instrument_instance_method(mod, name) ⇒ Object

Instruments an instance method.

mod - The module to instrument as a Module/Class. name - The name of the method to instrument.


32
33
34
# File 'lib/gitlab/metrics/instrumentation.rb', line 32

def self.instrument_instance_method(mod, name)
  instrument(:instance, mod, name)
end

.instrument_instance_methods(mod) ⇒ Object

Instruments all public instance methods of a module.

See `instrument_methods` for more information.

mod - The module to instrument.


84
85
86
87
88
89
90
91
92
93
94
# File 'lib/gitlab/metrics/instrumentation.rb', line 84

def self.instrument_instance_methods(mod)
  mod.public_instance_methods(false).each do |name|
    method = mod.instance_method(name)

    if method.owner == mod
      if !block_given? || block_given? && yield(mod, method)
        instrument_instance_method(mod, name)
      end
    end
  end
end

.instrument_method(mod, name) ⇒ Object

Instruments a class method.

mod - The module to instrument as a Module/Class. name - The name of the method to instrument.


24
25
26
# File 'lib/gitlab/metrics/instrumentation.rb', line 24

def self.instrument_method(mod, name)
  instrument(:class, mod, name)
end

.instrument_methods(mod) ⇒ Object

Instruments all public methods of a module.

This method optionally takes a block that can be used to determine if a method should be instrumented or not. The block is passed the receiving module and an UnboundMethod. If the block returns a non truthy value the method is not instrumented.

mod - The module to instrument.


67
68
69
70
71
72
73
74
75
76
77
# File 'lib/gitlab/metrics/instrumentation.rb', line 67

def self.instrument_methods(mod)
  mod.public_methods(false).each do |name|
    method = mod.method(name)

    if method.owner == mod.singleton_class
      if !block_given? || block_given? && yield(mod, method)
        instrument_method(mod, name)
      end
    end
  end
end

.instrumented?(mod) ⇒ Boolean

Returns true if a module is instrumented.

mod - The module to check

Returns:

  • (Boolean)

99
100
101
# File 'lib/gitlab/metrics/instrumentation.rb', line 99

def self.instrumented?(mod)
  mod.instance_variable_defined?(PROXY_IVAR)
end

.proxy_module(mod) ⇒ Object

Returns the proxy module (if any) of `mod`.


104
105
106
# File 'lib/gitlab/metrics/instrumentation.rb', line 104

def self.proxy_module(mod)
  mod.instance_variable_get(PROXY_IVAR)
end

.transactionObject

Small layer of indirection to make it easier to stub out the current transaction.


176
177
178
# File 'lib/gitlab/metrics/instrumentation.rb', line 176

def self.transaction
  Transaction.current
end