Class: TingYun::Agent::Collector::SqlSampler

Inherits:
Object
  • Object
show all
Defined in:
lib/ting_yun/agent/collector/sql_sampler.rb

Overview

This class contains the logic of recording slow SQL traces, which may represent multiple aggregated SQL queries.

A slow SQL trace consists of a collection of SQL instrumented SQL queries that all normalize to the same text. For example, the following two queries would be aggregated together into a single slow SQL trace:

SELECT * FROM table WHERE id=42
SELECT * FROM table WHERE id=1234

Each slow SQL trace keeps track of the number of times the same normalized query was seen, the min, max, and total time spent executing those queries, and an example backtrace from one of the aggregated queries.

Constant Summary collapse

MAX_SAMPLES =
10

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSqlSampler

Returns a new instance of SqlSampler.



31
32
33
34
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 31

def initialize
  @sql_traces = {}
  @samples_lock = Mutex.new
end

Instance Attribute Details

#sql_tracesObject (readonly)

Returns the value of attribute sql_traces.



29
30
31
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 29

def sql_traces
  @sql_traces
end

Instance Method Details

#enabled?Boolean

Returns:

  • (Boolean)


36
37
38
39
40
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 36

def enabled?
  Agent.config[:'nbs.action_tracer.enabled'] &&
      Agent.config[:'nbs.action_tracer.slow_sql'] &&
          TingYun::Agent::Database.should_record_sql?('nbs.action_tracer.record_sql')
end

#harvest!Object



139
140
141
142
143
144
145
146
147
148
149
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 139

def harvest!
  return [] unless enabled?

  slowest = []
  @samples_lock.synchronize do
    slowest = @sql_traces.values
    @sql_traces = {}
  end
  slowest.each {|trace| trace.prepare_to_send }
  slowest
end

#has_room?Boolean

this should always be called under the @samples_lock

Returns:

  • (Boolean)


121
122
123
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 121

def has_room?
  @sql_traces.size < MAX_SAMPLES
end

#merge!(sql_traces) ⇒ Object



157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 157

def merge!(sql_traces)
  @samples_lock.synchronize do
    sql_traces.each do |trace|
      existing_trace = @sql_traces[trace.sql]
      if existing_trace
        existing_trace.aggregate_trace(trace)
      else
        @sql_traces[trace.sql] = trace
      end
    end
  end
end

#notice_sql(sql, metric_name, config, duration, state = nil, explainer = nil) ⇒ Object

duration=> sec



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 53

def notice_sql(sql, metric_name, config, duration, state=nil, explainer=nil) #THREAD_LOCAL_ACCESS sometimes
  start_time = Time.now.to_f
  state ||= TingYun::Agent::TransactionState.tl_get
  data = state.sql_sampler_transaction_data
  return unless data

  if state.sql_recorded? && !metric_name.nil?
    if duration*1000 > TingYun::Agent.config[:'nbs.action_tracer.slow_sql_threshold']
      if duration*1000 > TingYun::Agent.config[:'nbs.action_tracer.stack_trace_threshold']
        backtrace = (caller.reject! { |t| t.include?('tingyun_rpm') })
        backtrace = backtrace.first(40) if backtrace.length > 40
        backtrace = backtrace.join("\n")
      else
        backtrace = ''
      end
      statement = TingYun::Agent::Database::Statement.new(sql, config, explainer)
      data.sql_data << SlowSql.new(statement, metric_name, duration, start_time, backtrace)
    end
  end
end

#on_finishing_transaction(state, name) ⇒ Object



75
76
77
78
79
80
81
82
83
84
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 75

def on_finishing_transaction(state, name)
  return unless enabled?

  transaction_sql_data = state.sql_sampler_transaction_data
  return unless transaction_sql_data

  transaction_sql_data.set_transaction_name(name)

  save_slow_sql(transaction_sql_data)
end

#on_start_transaction(state, uri = nil) ⇒ Object



42
43
44
45
46
47
48
49
50
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 42

def on_start_transaction(state, uri=nil)
  return unless enabled?

  state.sql_sampler_transaction_data = TransactionSqlData.new

  if Agent.config[:'nbs.action_tracer.slow_sql'] && state.sql_sampler_transaction_data
    state.sql_sampler_transaction_data.set_transaction_info(uri)
  end
end

#remove_shortest_traceObject

this should always be called under the @samples_lock



133
134
135
136
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 133

def remove_shortest_trace
  shortest_key, _ = @sql_traces.min_by { |(_, trace)| trace.max_call_time }
  @sql_traces.delete(shortest_key)
end

#reset!Object



151
152
153
154
155
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 151

def reset!
  @samples_lock.synchronize do
    @sql_traces = {}
  end
end

#save(transaction_sql_data) ⇒ Object



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 95

def save (transaction_sql_data)
  action_metric_name = transaction_sql_data.action_metric_name
  uri                = transaction_sql_data.uri

  transaction_sql_data.sql_data.each do |sql_item|
    normalized_sql = sql_item.normalize
    sql_trace = @sql_traces[normalized_sql]
    if sql_trace
      sql_trace.aggregate(sql_item, action_metric_name, uri)
    else
      if has_room?
        sql_trace = SqlTrace.new(normalized_sql, sql_item, action_metric_name, uri)
      elsif should_add_trace?(sql_item)
        remove_shortest_trace
        sql_trace = SqlTrace.new(normalized_sql, sql_item, action_metric_name, uri)
      end

      if sql_trace
        @sql_traces[normalized_sql] = sql_trace
      end

    end
  end
end

#save_slow_sql(data) ⇒ Object



86
87
88
89
90
91
92
93
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 86

def save_slow_sql(data)
  if data.sql_data.size > 0
    @samples_lock.synchronize do
      ::TingYun::Agent.logger.debug "Examining #{data.sql_data.size} slow transaction sql statement(s)"
      save data
    end
  end
end

#should_add_trace?(sql_item) ⇒ Boolean

this should always be called under the @samples_lock

Returns:

  • (Boolean)


126
127
128
129
130
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 126

def should_add_trace?(sql_item)
  @sql_traces.any? do |(_, existing_trace)|
    existing_trace.max_call_time < sql_item.duration
  end
end