summaryrefslogtreecommitdiff
path: root/lib/gitlab/sherlock/transaction.rb
blob: 5cb3e86aa4eb47a9a9e89ca58d7ca293b0d73e04 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
module Gitlab
  module Sherlock
    class Transaction
      attr_reader :id, :type, :path, :queries, :file_samples, :started_at,
        :finished_at

      def initialize(type, path)
        @id = SecureRandom.uuid
        @type = type
        @path = path
        @duration = 0
        @queries = []
        @file_samples = []
        @started_at = nil
        @finished_at = nil
        @thread = Thread.current
      end

      def run
        @started_at = Time.now

        subscriber = subscribe_to_active_record

        retval = profile_lines { yield }

        @finished_at = Time.now

        ActiveSupport::Notifications.unsubscribe(subscriber)

        retval
      end

      def duration
        @started_at && @finished_at ? @finished_at - @started_at : 0
      end

      def to_param
        @id
      end

      def sorted_queries
        @queries.sort { |a, b| b.duration <=> a.duration }
      end

      def sorted_file_samples
        @file_samples.sort { |a, b| b.duration <=> a.duration }
      end

      def find_query(id)
        @queries.find { |query| query.id == id }
      end

      def find_file_sample(id)
        @file_samples.find { |sample| sample.id == id }
      end

      def track_query(query, bindings, start, finish)
        @queries << Query.new_with_bindings(query, bindings, start, finish)
      end

      def profile_lines
        retval = nil

        if Sherlock.enable_line_profiler?
          retval, @file_samples = LineProfiler.new.profile { yield }
        else
          retval = yield
        end

        retval
      end

      def subscribe_to_active_record
        ActiveSupport::Notifications.subscribe('sql.active_record') do |_, start, finish, _, data|
          # In case somebody uses a multi-threaded server locally (e.g. Puma) we
          # _only_ want to track queries that originate from the transaction
          # thread.
          next unless Thread.current == @thread

          track_query(data[:sql].strip, data[:binds], start, finish)
        end
      end
    end
  end
end