diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-04 15:08:21 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-06-04 15:08:21 +0300 |
commit | 63546c0b11e768f1a82dee9507f27bd31a9fc460 (patch) | |
tree | 7284dadf385aa01a69c319dfb3566873e434df18 /lib/gitlab/instrumentation | |
parent | b3ce1ce45218454cc3f8b719d7748f8a467f36a3 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'lib/gitlab/instrumentation')
-rw-r--r-- | lib/gitlab/instrumentation/redis.rb | 92 | ||||
-rw-r--r-- | lib/gitlab/instrumentation/redis_base.rb | 101 | ||||
-rw-r--r-- | lib/gitlab/instrumentation/redis_interceptor.rb (renamed from lib/gitlab/instrumentation/redis_driver.rb) | 50 |
3 files changed, 155 insertions, 88 deletions
diff --git a/lib/gitlab/instrumentation/redis.rb b/lib/gitlab/instrumentation/redis.rb index 19d4d9d0f11..b3831c9b50a 100644 --- a/lib/gitlab/instrumentation/redis.rb +++ b/lib/gitlab/instrumentation/redis.rb @@ -1,90 +1,30 @@ # frozen_string_literal: true -require 'redis' - module Gitlab module Instrumentation - module RedisInterceptor - def call(*args, &block) - start = Time.now - super(*args, &block) - ensure - duration = (Time.now - start) - - if ::RequestStore.active? - ::Gitlab::Instrumentation::Redis.increment_request_count - ::Gitlab::Instrumentation::Redis.add_duration(duration) - ::Gitlab::Instrumentation::Redis.add_call_details(duration, args) - end - end - end - + # Aggregates Redis measurements from different request storage sources. class Redis - REDIS_REQUEST_COUNT = :redis_request_count - REDIS_CALL_DURATION = :redis_call_duration - REDIS_CALL_DETAILS = :redis_call_details - REDIS_READ_BYTES = :redis_read_bytes - REDIS_WRITE_BYTES = :redis_write_bytes + ActionCable = Class.new(RedisBase) + Cache = Class.new(RedisBase) + Queues = Class.new(RedisBase) + SharedState = Class.new(RedisBase) + + STORAGES = [ActionCable, Cache, Queues, SharedState].freeze # Milliseconds represented in seconds (from 1 to 500 milliseconds). QUERY_TIME_BUCKETS = [0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5].freeze - def self.get_request_count - ::RequestStore[REDIS_REQUEST_COUNT] || 0 - end - - def self.increment_request_count - ::RequestStore[REDIS_REQUEST_COUNT] ||= 0 - ::RequestStore[REDIS_REQUEST_COUNT] += 1 - end - - def self.increment_read_bytes(num_bytes) - ::RequestStore[REDIS_READ_BYTES] ||= 0 - ::RequestStore[REDIS_READ_BYTES] += num_bytes - end - - def self.increment_write_bytes(num_bytes) - ::RequestStore[REDIS_WRITE_BYTES] ||= 0 - ::RequestStore[REDIS_WRITE_BYTES] += num_bytes - end - - def self.read_bytes - ::RequestStore[REDIS_READ_BYTES] || 0 - end - - def self.write_bytes - ::RequestStore[REDIS_WRITE_BYTES] || 0 - end - - def self.detail_store - ::RequestStore[REDIS_CALL_DETAILS] ||= [] - end - - def self.query_time - query_time = ::RequestStore[REDIS_CALL_DURATION] || 0 - query_time.round(::Gitlab::InstrumentationHelper::DURATION_PRECISION) - end - - def self.add_duration(duration) - ::RequestStore[REDIS_CALL_DURATION] ||= 0 - ::RequestStore[REDIS_CALL_DURATION] += duration - end - - def self.add_call_details(duration, args) - return unless Gitlab::PerformanceBar.enabled_for_request? - # redis-rb passes an array (e.g. [:get, key]) - return unless args.length == 1 + class << self + def detail_store + STORAGES.flat_map(&:detail_store) + end - detail_store << { - cmd: args.first, - duration: duration, - backtrace: ::Gitlab::BacktraceCleaner.clean_backtrace(caller) - } + %i[get_request_count query_time read_bytes write_bytes].each do |method| + define_method method do + STORAGES.sum(&method) # rubocop:disable CodeReuse/ActiveRecord + end + end end end end end - -class ::Redis::Client - prepend ::Gitlab::Instrumentation::RedisInterceptor -end diff --git a/lib/gitlab/instrumentation/redis_base.rb b/lib/gitlab/instrumentation/redis_base.rb new file mode 100644 index 00000000000..a8fb8f5076b --- /dev/null +++ b/lib/gitlab/instrumentation/redis_base.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +require 'redis' + +module Gitlab + module Instrumentation + class RedisBase + class << self + include ::Gitlab::Utils::StrongMemoize + + # TODO: To be used by https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/395 + # as a 'label' alias. + def storage_key + self.name.underscore + end + + def add_duration(duration) + ::RequestStore[call_duration_key] ||= 0 + ::RequestStore[call_duration_key] += duration + end + + def add_call_details(duration, args) + return unless Gitlab::PerformanceBar.enabled_for_request? + # redis-rb passes an array (e.g. [[:get, key]]) + return unless args.length == 1 + + # TODO: Add information about current Redis client + # being instrumented. + # https://gitlab.com/gitlab-com/gl-infra/scalability/-/issues/316. + detail_store << { + cmd: args.first, + duration: duration, + backtrace: ::Gitlab::BacktraceCleaner.clean_backtrace(caller) + } + end + + def increment_request_count + ::RequestStore[request_count_key] ||= 0 + ::RequestStore[request_count_key] += 1 + end + + def increment_read_bytes(num_bytes) + ::RequestStore[read_bytes_key] ||= 0 + ::RequestStore[read_bytes_key] += num_bytes + end + + def increment_write_bytes(num_bytes) + ::RequestStore[write_bytes_key] ||= 0 + ::RequestStore[write_bytes_key] += num_bytes + end + + def get_request_count + ::RequestStore[request_count_key] || 0 + end + + def read_bytes + ::RequestStore[read_bytes_key] || 0 + end + + def write_bytes + ::RequestStore[write_bytes_key] || 0 + end + + def detail_store + ::RequestStore[call_details_key] ||= [] + end + + def query_time + query_time = ::RequestStore[call_duration_key] || 0 + query_time.round(::Gitlab::InstrumentationHelper::DURATION_PRECISION) + end + + private + + def request_count_key + strong_memoize(:request_count_key) { build_key(:redis_request_count) } + end + + def read_bytes_key + strong_memoize(:read_bytes_key) { build_key(:redis_read_bytes) } + end + + def write_bytes_key + strong_memoize(:write_bytes_key) { build_key(:redis_write_bytes) } + end + + def call_duration_key + strong_memoize(:call_duration_key) { build_key(:redis_call_duration) } + end + + def call_details_key + strong_memoize(:call_details_key) { build_key(:redis_call_details) } + end + + def build_key(namespace) + "#{storage_key}_#{namespace}" + end + end + end + end +end diff --git a/lib/gitlab/instrumentation/redis_driver.rb b/lib/gitlab/instrumentation/redis_interceptor.rb index 2d0bce07be5..a485fcaaea1 100644 --- a/lib/gitlab/instrumentation/redis_driver.rb +++ b/lib/gitlab/instrumentation/redis_interceptor.rb @@ -4,7 +4,20 @@ require 'redis' module Gitlab module Instrumentation - class RedisDriver < ::Redis::Connection::Ruby + module RedisInterceptor + def call(*args, &block) + start = Time.now + super(*args, &block) + ensure + duration = (Time.now - start) + + if ::RequestStore.active? + instrumentation_class.increment_request_count + instrumentation_class.add_duration(duration) + instrumentation_class.add_call_details(duration, args) + end + end + def write(command) measure_write_size(command) if ::RequestStore.active? super @@ -35,27 +48,40 @@ module Gitlab end end - ::Gitlab::Instrumentation::Redis.increment_write_bytes(size) + instrumentation_class.increment_write_bytes(size) end def measure_read_size(result) - # The superclass can return one of four types of results from read: + # The Connection::Ruby#read class can return one of four types of results from read: # https://github.com/redis/redis-rb/blob/f597f21a6b954b685cf939febbc638f6c803e3a7/lib/redis/connection/ruby.rb#L406 # # 1. Error (exception, will not reach this line) # 2. Status (string) # 3. Integer (will be converted to string by to_s.bytesize and thrown away) # 4. "Binary" string (i.e. may contain zero byte) - # 5. Array of binary string (recurses back into read) - - # Avoid double-counting array responses: the array elements themselves - # are retrieved with 'read'. - unless result.is_a? Array - # This count is an approximation that omits the Redis protocol overhead - # of type prefixes, length prefixes and line endings. - ::Gitlab::Instrumentation::Redis.increment_read_bytes(result.to_s.bytesize) - end + # 5. Array of binary string + + size = if result.is_a? Array + # This count is an approximation that omits the Redis protocol overhead + # of type prefixes, length prefixes and line endings. + result.inject(0) { |sum, y| sum + y.to_s.bytesize } + else + result.to_s.bytesize + end + + instrumentation_class.increment_read_bytes(size) + end + + # That's required so it knows which GitLab Redis instance + # it's interacting with in order to categorize accordingly. + # + def instrumentation_class + @options[:instrumentation_class] # rubocop:disable Gitlab/ModuleWithInstanceVariables end end end end + +class ::Redis::Client + prepend ::Gitlab::Instrumentation::RedisInterceptor +end |