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

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeSqlSampler

Returns a new instance of SqlSampler.



33
34
35
36
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 33

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

Instance Attribute Details

#sql_tracesObject (readonly)

Returns the value of attribute sql_traces.



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

def sql_traces
  @sql_traces
end

Class Method Details

.notice_sql(sql, metric_name, config, duration, state = nil, explainer = nil, binds = [], name = "SQL") ⇒ Object

duration=> sec



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 45

def self.notice_sql(sql, metric_name, config, duration, state=nil, explainer=nil, binds=[], name="SQL") #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
  threshold = duration*1000
  if threshold > TingYun::Agent.config[:'nbs.action_tracer.slow_sql_threshold'] && state.sql_recorded?
    backtrace = ''
    if threshold > TingYun::Agent.config[:'nbs.action_tracer.stack_trace_threshold']
      backtrace = caller.reject! { |t| t.include?('tingyun_rpm') }
      backtrace = backtrace.first(20).join("\n")
    end
    statement = TingYun::Agent::Database::Statement.new(sql, config, explainer, binds, name)
    data.sql_data << ::TingYun::Agent::Collector::SlowSql.new(statement, metric_name, duration, start_time, backtrace)
  end
end

.on_start_transaction(state, uri) ⇒ Object



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

def self.on_start_transaction(state, uri)
  return unless TingYun::Agent::Database.sql_sampler_enabled?

  state.init_sql_transaction(::TingYun::Agent::Collector::TransactionSqlData.new(uri))
end

Instance Method Details

#harvest!Object



112
113
114
115
116
117
118
119
120
121
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 112

def harvest!
  return [] unless TingYun::Agent::Database.sql_sampler_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)


107
108
109
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 107

def has_room?
  @sql_traces.size < MAX_SAMPLES
end

#merge!(sql_traces) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 129

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.slow_sql, trace.path, trace.url)
      else
        @sql_traces[trace.sql] = trace
      end
    end
  end
end

#on_finishing_transaction(state, name) ⇒ Object



62
63
64
65
66
67
68
69
70
71
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 62

def on_finishing_transaction(state, name)
  return unless TingYun::Agent::Database.sql_sampler_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

#reset!Object



123
124
125
126
127
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 123

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

#save(transaction_sql_data) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/ting_yun/agent/collector/sql_sampler.rb', line 83

def save (transaction_sql_data)
  action_metric_name = transaction_sql_data.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_traces[normalized_sql] = ::TingYun::Agent::Collector::SqlTrace.new(normalized_sql, sql_item, action_metric_name, uri)
      else
        min, max = @sql_traces.minmax_by { |(_, trace)| trace.max_call_time }
        if max.last.max_call_time < sql_item.duration
          @sql_traces.delete(min.first)
          @sql_traces[normalized_sql] = ::TingYun::Agent::Collector::SqlTrace.new(normalized_sql, sql_item, action_metric_name, uri)
        end
      end
    end
  end
end

#save_slow_sql(data) ⇒ Object



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

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