# frozen_string_literal: true module Gitlab module PerformanceBar # This class fetches Peek stats stored in redis and logs them in a # structured log (so these can be then analyzed in Kibana) class Stats def initialize(redis) @redis = redis end def process(id) data = request(id) return unless data log_sql_queries(id, data) rescue => err logger.error(message: "failed to process request id #{id}: #{err.message}") end private def request(id) # Peek gem stores request data under peek:requests:request_id key json_data = @redis.get("peek:requests:#{id}") Gitlab::Json.parse(json_data) end def log_sql_queries(id, data) queries_by_location(data).each do |location, queries| next unless location duration = queries.sum { |query| query['duration'].to_f } log_info = { method_path: "#{location[:filename]}:#{location[:method]}", filename: location[:filename], type: :sql, request_id: id, count: queries.count, duration_ms: duration } logger.info(log_info) end end def queries_by_location(data) return [] unless queries = data.dig('data', 'active-record', 'details') queries.group_by do |query| parse_backtrace(query['backtrace']) end end def parse_backtrace(backtrace) return unless match = /(?.*):(?\d+):in `(?.*)'/.match(backtrace.first) { filename: match[:filename], # filenum may change quite frequently with every change in the file, # because the intention is to aggregate these queries, we group # them rather by method name which should not change so frequently # filenum: match[:filenum].to_i, method: match[:method] } end def logger @logger ||= Gitlab::PerformanceBar::Logger.build end end end end