Welcome to mirror list, hosted at ThFree Co, Russian Federation.

context.rb « transaction « database « gitlab « lib - gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 6defe9ae4432119804ff999ca36d02b826b73eaa (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
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
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
# frozen_string_literal: true

module Gitlab
  module Database
    module Transaction
      class Context
        attr_reader :context

        LOG_SAVEPOINTS_THRESHOLD = 1                # 1 `SAVEPOINT` created in a transaction
        LOG_DURATION_S_THRESHOLD = 120              # transaction that is running for 2 minutes or longer
        LOG_EXTERNAL_HTTP_COUNT_THRESHOLD = 50      # 50 external HTTP requests executed within transaction
        LOG_EXTERNAL_HTTP_DURATION_S_THRESHOLD = 1  # 1 second spent in HTTP requests in total within transaction
        LOG_THROTTLE_DURATION = 1

        def initialize
          @context = {}
        end

        def set_start_time
          @context[:start_time] = current_timestamp
        end

        def set_depth(depth)
          @context[:depth] = [@context[:depth].to_i, depth].max
        end

        def increment_savepoints
          @context[:savepoints] = @context[:savepoints].to_i + 1
        end

        def increment_rollbacks
          @context[:rollbacks] = @context[:rollbacks].to_i + 1
        end

        def increment_releases
          @context[:releases] = @context[:releases].to_i + 1
        end

        def track_sql(sql)
          (@context[:queries] ||= []).push(sql)
        end

        def track_backtrace(backtrace)
          cleaned_backtrace = Gitlab::BacktraceCleaner.clean_backtrace(backtrace)
          (@context[:backtraces] ||= []).push(cleaned_backtrace)
        end

        def initialize_external_http_tracking
          @context[:external_http_count_start] = external_http_requests_count_total
          @context[:external_http_duration_start] = external_http_requests_duration_total
        end

        def duration
          return unless @context[:start_time].present?

          current_timestamp - @context[:start_time]
        end

        def savepoints_threshold_exceeded?
          @context[:savepoints].to_i >= LOG_SAVEPOINTS_THRESHOLD
        end

        def duration_threshold_exceeded?
          duration.to_i >= LOG_DURATION_S_THRESHOLD
        end

        def external_http_requests_threshold_exceeded?
          external_http_requests_count >= LOG_EXTERNAL_HTTP_COUNT_THRESHOLD ||
            external_http_requests_duration >= LOG_EXTERNAL_HTTP_DURATION_S_THRESHOLD
        end

        def should_log?
          return false if logged_already?

          savepoints_threshold_exceeded? || duration_threshold_exceeded? ||
            external_http_requests_threshold_exceeded?
        end

        def commit
          log(:commit)
        end

        def rollback
          log(:rollback)
        end

        def backtraces
          @context[:backtraces].to_a
        end

        def external_http_requests_count
          @requests_count ||= external_http_requests_count_total - @context[:external_http_count_start].to_i
        end

        def external_http_requests_duration
          @requests_duration ||= external_http_requests_duration_total - @context[:external_http_duration_start].to_f
        end

        private

        def queries
          @context[:queries].to_a.join("\n")
        end

        def current_timestamp
          ::Gitlab::Metrics::System.monotonic_time
        end

        def logged_already?
          return false if @context[:last_log_timestamp].nil?

          (current_timestamp - @context[:last_log_timestamp].to_i) < LOG_THROTTLE_DURATION
        end

        def set_last_log_timestamp
          @context[:last_log_timestamp] = current_timestamp
        end

        def log(operation)
          return unless should_log?

          set_last_log_timestamp

          attributes = {
            class: self.class.name,
            result: operation,
            duration_s: duration,
            depth: @context[:depth].to_i,
            savepoints_count: @context[:savepoints].to_i,
            rollbacks_count: @context[:rollbacks].to_i,
            releases_count: @context[:releases].to_i,
            external_http_requests_count: external_http_requests_count,
            external_http_requests_duration: external_http_requests_duration,
            sql: queries,
            savepoint_backtraces: backtraces
          }

          application_info(attributes)
        end

        def application_info(attributes)
          Gitlab::AppJsonLogger.info(attributes)
        end

        def external_http_requests_count_total
          ::Gitlab::Metrics::Subscribers::ExternalHttp.request_count.to_i
        end

        def external_http_requests_duration_total
          ::Gitlab::Metrics::Subscribers::ExternalHttp.duration.to_f
        end
      end
    end
  end
end