Class: ScoutApm::Store
- Inherits:
-
Object
- Object
- ScoutApm::Store
- Defined in:
- lib/scout_apm/store.rb
Constant Summary collapse
- MAX_SIZE =
Limits the size of the metric hash to prevent a metric explosion.
1000
- MAX_SLOW_TRANSACTIONS_TO_STORE_METRICS =
Limit the number of slow transactions that we store metrics with to prevent writing too much data to the layaway file if there are are many processes and many slow slow_transactions.
10
- SLOW_TRANSACTION_THRESHOLD =
2
Instance Attribute Summary collapse
-
#metric_hash ⇒ Object
Returns the value of attribute metric_hash.
-
#slow_transaction_lock ⇒ Object
readonly
Returns the value of attribute slow_transaction_lock.
-
#slow_transactions ⇒ Object
array of slow transaction slow_transactions.
-
#stack ⇒ Object
Returns the value of attribute stack.
-
#transaction_hash ⇒ Object
Returns the value of attribute transaction_hash.
Instance Method Summary collapse
-
#aggregate_calls(metrics, parent_meta) ⇒ Object
Takes a metric_hash of calls and generates aggregates for ActiveRecord and View calls.
-
#categories(metrics) ⇒ Object
Returns the top-level category names used in the
metrics
hash. - #ignore_transaction! ⇒ Object
-
#initialize ⇒ Store
constructor
A new instance of Store.
-
#merge_data(old_data) ⇒ Object
Combines old and current data.
-
#merge_data_and_clear(old_data) ⇒ Object
Merges old and current data, clears the current in-memory metric hash, and returns the merged data.
- #merge_metrics(old_metrics) ⇒ Object
-
#merge_slow_transactions(old_slow_transactions) ⇒ Object
Merges slow_transactions together, removing transaction sample metrics from slow_transactions if the > MAX_SLOW_TRANSACTIONS_TO_STORE_METRICS.
-
#record(metric_name) ⇒ Object
Called at the start of Tracer#instrument: (1) Either finds an existing MetricStats object in the metric_hash or initialize a new one.
-
#reset_transaction! ⇒ Object
Called when the last stack item completes for the current transaction to clear for the next run.
-
#stop_recording(sanity_check_item, options = {}) ⇒ Object
Options: * :scope - If specified, sets the sub-scope for the metric.
-
#store_metric?(stack_empty) ⇒ Boolean
TODO - Move more logic to SlowTransaction.
-
#store_slow(uri, transaction_hash, parent_meta, parent_stat, options = {}) ⇒ Object
Stores slow transactions.
-
#track!(metric_name, call_time, options = {}) ⇒ Object
Finds or creates the metric w/the given name in the metric_hash, and updates the time.
Constructor Details
#initialize ⇒ Store
Returns a new instance of Store.
19 20 21 22 23 24 25 26 27 28 |
# File 'lib/scout_apm/store.rb', line 19 def initialize @metric_hash = Hash.new # Stores aggregate metrics for the current transaction. When the transaction is finished, metrics # are merged with the +metric_hash+. @transaction_hash = Hash.new @stack = Array.new # ensure background thread doesn't manipulate transaction sample while the store is. @slow_transaction_lock = Mutex.new @slow_transactions = Array.new end |
Instance Attribute Details
#metric_hash ⇒ Object
Returns the value of attribute metric_hash.
13 14 15 |
# File 'lib/scout_apm/store.rb', line 13 def metric_hash @metric_hash end |
#slow_transaction_lock ⇒ Object (readonly)
Returns the value of attribute slow_transaction_lock.
17 18 19 |
# File 'lib/scout_apm/store.rb', line 17 def slow_transaction_lock @slow_transaction_lock end |
#slow_transactions ⇒ Object
array of slow transaction slow_transactions
16 17 18 |
# File 'lib/scout_apm/store.rb', line 16 def slow_transactions @slow_transactions end |
#stack ⇒ Object
Returns the value of attribute stack.
15 16 17 |
# File 'lib/scout_apm/store.rb', line 15 def stack @stack end |
#transaction_hash ⇒ Object
Returns the value of attribute transaction_hash.
14 15 16 |
# File 'lib/scout_apm/store.rb', line 14 def transaction_hash @transaction_hash end |
Instance Method Details
#aggregate_calls(metrics, parent_meta) ⇒ Object
Takes a metric_hash of calls and generates aggregates for ActiveRecord and View calls.
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 |
# File 'lib/scout_apm/store.rb', line 125 def aggregate_calls(metrics,) categories = categories(metrics) aggregates = {} categories.each do |cat| =ScoutApm::MetricMeta.new("#{cat}/all") .scope = .metric_name agg_stats = ScoutApm::MetricStats.new metrics.each do |,stats| if .metric_name =~ /\A#{cat}\// agg_stats.combine!(stats) end end # metrics.each aggregates[] = agg_stats unless agg_stats.call_count.zero? end # categories.each aggregates end |
#categories(metrics) ⇒ Object
Returns the top-level category names used in the metrics
hash.
113 114 115 116 117 118 119 120 121 122 |
# File 'lib/scout_apm/store.rb', line 113 def categories(metrics) cats = Set.new metrics.keys.each do || next if .scope.nil? # ignore controller if match=.metric_name.match(/\A([\w]+)\//) cats << match[1] end end # metrics.each cats end |
#ignore_transaction! ⇒ Object
39 40 41 |
# File 'lib/scout_apm/store.rb', line 39 def ignore_transaction! Thread::current[:scout_apm_ignore_transaction] = true end |
#merge_data(old_data) ⇒ Object
Combines old and current data
176 177 178 179 180 181 |
# File 'lib/scout_apm/store.rb', line 176 def merge_data(old_data) { :metrics => merge_metrics(old_data[:metrics]), :slow_transactions => merge_slow_transactions(old_data[:slow_transactions]) } end |
#merge_data_and_clear(old_data) ⇒ Object
Merges old and current data, clears the current in-memory metric hash, and returns the merged data
185 186 187 188 189 190 191 192 193 |
# File 'lib/scout_apm/store.rb', line 185 def merge_data_and_clear(old_data) merged = merge_data(old_data) self.metric_hash = {} # TODO - is this lock needed? @slow_transaction_lock.synchronize do self.slow_transactions = [] end merged end |
#merge_metrics(old_metrics) ⇒ Object
195 196 197 198 199 200 201 202 203 204 |
# File 'lib/scout_apm/store.rb', line 195 def merge_metrics(old_metrics) old_metrics.each do |,old_stats| if stats = metric_hash[] metric_hash[] = stats.combine!(old_stats) elsif metric_hash.size < MAX_SIZE metric_hash[] = old_stats end end metric_hash end |
#merge_slow_transactions(old_slow_transactions) ⇒ Object
Merges slow_transactions together, removing transaction sample metrics from slow_transactions if the > MAX_SLOW_TRANSACTIONS_TO_STORE_METRICS
207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/scout_apm/store.rb', line 207 def merge_slow_transactions(old_slow_transactions) # need transaction lock here? self.slow_transactions += old_slow_transactions if trim_slow_transactions = self.slow_transactions[MAX_SLOW_TRANSACTIONS_TO_STORE_METRICS..-1] ScoutApm::Agent.instance.logger.debug "Trimming metrics from #{trim_slow_transactions.size} slow_transactions." i = MAX_SLOW_TRANSACTIONS_TO_STORE_METRICS trim_slow_transactions.each do |sample| self.slow_transactions[i] = sample.clear_metrics! end end self.slow_transactions end |
#record(metric_name) ⇒ Object
Called at the start of Tracer#instrument: (1) Either finds an existing MetricStats object in the metric_hash or initialize a new one. An existing MetricStats object is present if this metric_name
has already been instrumented. (2) Adds a StackItem to the stack. This StackItem is returned and later used to validate the item popped off the stack when an instrumented code block completes.
48 49 50 51 52 |
# File 'lib/scout_apm/store.rb', line 48 def record(metric_name) item = ScoutApm::StackItem.new(metric_name) stack << item item end |
#reset_transaction! ⇒ Object
Called when the last stack item completes for the current transaction to clear for the next run.
32 33 34 35 36 37 |
# File 'lib/scout_apm/store.rb', line 32 def reset_transaction! Thread::current[:scout_apm_ignore_transaction] = nil Thread::current[:scout_apm_scope_name] = nil @transaction_hash = Hash.new @stack = Array.new end |
#stop_recording(sanity_check_item, options = {}) ⇒ Object
Options:
-
:scope - If specified, sets the sub-scope for the metric. We allow additional scope level. This is used
-
uri - the request uri
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 |
# File 'lib/scout_apm/store.rb', line 57 def stop_recording(sanity_check_item, ={}) item = stack.pop stack_empty = stack.empty? # if ignoring the transaction, the item is popped but nothing happens. if Thread::current[:scout_apm_ignore_transaction] return end # unbalanced stack check - unreproducable cases have seen this occur. when it does, sets a Thread variable # so we ignore further recordings. +Store#reset_transaction!+ resets this. if item != sanity_check_item ScoutApm::Agent.instance.logger.warn "Scope [#{Thread::current[:scout_apm_scope_name]}] Popped off stack: #{item.inspect} Expected: #{sanity_check_item.inspect}. Aborting." ignore_transaction! return end duration = Time.now - item.start_time if last = stack.last last.children_time += duration end = ScoutApm::MetricMeta.new(item.metric_name, :desc => [:desc]) .scope = nil if stack_empty # add backtrace for slow calls ... how is exclusive time handled? if duration > ScoutApm::SlowTransaction::BACKTRACE_THRESHOLD and !stack_empty .extra = {:backtrace => ScoutApm::SlowTransaction.backtrace_parser(caller)} end stat = transaction_hash[] || ScoutApm::MetricStats.new(!stack_empty) stat.update!(duration,duration-item.children_time) transaction_hash[] = stat if store_metric?(stack_empty) # Uses controllers as the entry point for a transaction. Otherwise, stats are ignored. if stack_empty and .metric_name.match(/\AController\//) aggs = aggregate_calls(transaction_hash.dup,) store_slow([:uri], transaction_hash.dup.merge(aggs), , stat) # deep duplicate duplicate = aggs.dup duplicate.each_pair do |k,v| duplicate[k.dup] = v.dup end merge_metrics(duplicate.merge({.dup => stat.dup})) # aggregrates + controller end end |
#store_metric?(stack_empty) ⇒ Boolean
TODO - Move more logic to SlowTransaction
Limits the size of the transaction hash to prevent a large transactions. The final item on the stack is allowed to be stored regardless of hash size to wrapup the transaction sample w/the parent metric.
108 109 110 |
# File 'lib/scout_apm/store.rb', line 108 def store_metric?(stack_empty) transaction_hash.size < ScoutApm::SlowTransaction::MAX_SIZE or stack_empty end |
#store_slow(uri, transaction_hash, parent_meta, parent_stat, options = {}) ⇒ Object
Stores slow transactions. This will be sent to the server.
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/scout_apm/store.rb', line 145 def store_slow(uri, transaction_hash, , parent_stat, = {}) @slow_transaction_lock.synchronize do if parent_stat.total_call_time >= SLOW_TRANSACTION_THRESHOLD slow_transaction = ScoutApm::SlowTransaction.new(uri, .metric_name, parent_stat.total_call_time, transaction_hash.dup, ScoutApm::Context.current, Thread::current[:scout_apm_trace_time], Thread::current[:scout_apm_prof]) @slow_transactions.push(slow_transaction) ScoutApm::Agent.instance.logger.debug "Slow transaction sample added. [URI: #{uri}] [Context: #{ScoutApm::Context.current.to_hash}] Array Size: #{@slow_transactions.size}" end end end |
#track!(metric_name, call_time, options = {}) ⇒ Object
Finds or creates the metric w/the given name in the metric_hash, and updates the time. Primarily used to record sampled metrics. For instrumented methods, #record and #stop_recording are used.
Options: :scope => If provided, overrides the default scope. :exclusive_time => Sets the exclusive time for the method. If not provided, uses call_time
.
167 168 169 170 171 172 173 |
# File 'lib/scout_apm/store.rb', line 167 def track!(metric_name, call_time, = {}) = ScoutApm::MetricMeta.new(metric_name) .scope = [:scope] if .has_key?(:scope) stat = metric_hash[] || ScoutApm::MetricStats.new stat.update!(call_time,[:exclusive_time] || call_time) metric_hash[] = stat end |