Class: Coverband::Collectors::QueryBurstTracker

Inherits:
AbstractTracker show all
Defined in:
lib/coverband/collectors/query_burst_tracker.rb

Overview

Lightweight SQL burst tracker for production runtime behavior. Aggregates per controller action / job class and persists compact stats.

Constant Summary collapse

REPORT_ROUTE =
"query_bursts_tracker"
TITLE =
"Query Bursts"
CONTEXT_STACK_KEY =
:coverband_query_burst_context_stack
IGNORED_SQL_NAMES =
%w[SCHEMA CACHE TRANSACTION].freeze

Instance Attribute Summary

Attributes inherited from AbstractTracker

#ignore_patterns, #logger, #store, #target

Instance Method Summary collapse

Methods inherited from AbstractTracker

#clear_key!, #keys_to_record, #logged_keys, #reset_recordings, #route, supported_version?, #title, #tracking_since

Constructor Details

#initialize(options = {}) ⇒ QueryBurstTracker

Returns a new instance of QueryBurstTracker.



17
18
19
20
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 17

def initialize(options = {})
  super
  @pending_stats = {}
end

Instance Method Details

#all_keysObject



79
80
81
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 79

def all_keys
  []
end

#as_jsonObject



83
84
85
86
87
88
89
90
91
92
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 83

def as_json
  {
    tracking_since: tracking_since,
    thresholds: {
      query_count: query_count_threshold,
      sql_time_ms: sql_time_threshold_ms
    },
    used_keys: used_key_stats
  }.to_json
end

#railtie!Object



22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 22

def railtie!
  ActiveSupport::Notifications.subscribe("start_processing.action_controller") do |_name, _start, _finish, _id, payload|
    start_context(:controller, controller_key(payload))
  end

  ActiveSupport::Notifications.subscribe("process_action.action_controller") do |_name, _start, _finish, _id, payload|
    finish_context(:controller, controller_key(payload))
  end

  ActiveSupport::Notifications.subscribe("perform_start.active_job") do |_name, _start, _finish, _id, payload|
    start_context(:job, job_key(payload))
  end

  ActiveSupport::Notifications.subscribe("perform.active_job") do |_name, _start, _finish, _id, payload|
    finish_context(:job, job_key(payload))
  end

  ActiveSupport::Notifications.subscribe("sql.active_record") do |_name, start, finish, _id, payload|
    next if ignore_sql_event?(payload)

    record_sql_event(duration_ms(start, finish))
  end
end

#save_reportObject



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 94

def save_report
  return unless redis_store
  return if @pending_stats.empty?

  redis_store.set(tracker_time_key, Time.now.to_i) unless @one_time_timestamp || tracker_time_key_exists?
  @one_time_timestamp = true

  existing_stats = redis_store.hgetall(tracker_key)
  merged = @pending_stats.each_with_object({}) do |(key, pending), h|
    existing = parse_stats(existing_stats[key])
    h[key] = merge_stats(existing, pending).to_json
  end

  redis_store.hset(tracker_key, merged) if merged.any?
  @pending_stats.clear
rescue => e
  logger&.error "Coverband: #{self.class.name} failed to store, error #{e.class.name} info #{e.message}"
end

#track_key(payload) ⇒ Object



46
47
48
49
50
51
52
53
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 46

def track_key(payload)
  key = payload[:key] || payload["key"]
  return unless key

  queries = (payload[:queries] || payload["queries"] || 0).to_i
  sql_time_ms = (payload[:sql_time_ms] || payload["sql_time_ms"] || 0.0).to_f
  record_observation(key, queries, sql_time_ms)
end

#unused_keys(_used_keys = nil) ⇒ Object



75
76
77
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 75

def unused_keys(_used_keys = nil)
  []
end

#used_key_statsObject



65
66
67
68
69
70
71
72
73
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 65

def used_key_stats
  return {} unless redis_store

  stats_hash = redis_store.hgetall(tracker_key)
  stats_hash
    .transform_values { |stats_json| parse_stats(stats_json) }
    .sort_by { |key, stats| [-stats["threshold_hits"].to_i, -stats["total_sql_time_ms"].to_f, key] }
    .to_h
end

#used_keysObject



55
56
57
58
59
60
61
62
63
# File 'lib/coverband/collectors/query_burst_tracker.rb', line 55

def used_keys
  return {} unless redis_store

  stats_hash = redis_store.hgetall(tracker_key)
  stats_hash.each_with_object({}) do |(key, stats_json), used|
    stats = parse_stats(stats_json)
    used[key] = stats["last_seen"].to_i
  end
end