Class: ScoutApm::Store
- Inherits:
-
Object
- Object
- ScoutApm::Store
- Defined in:
- lib/scout_apm/store.rb
Overview
The store encapsolutes the logic that (1) saves instrumented data by Metric name to memory and (2) maintains a stack (just an Array) of instrumented methods that are being called. It’s accessed via ScoutApm::Agent.instance.store
.
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
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.
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/scout_apm/store.rb', line 120 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.
108 109 110 111 112 113 114 115 116 117 |
# File 'lib/scout_apm/store.rb', line 108 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
163 164 165 |
# File 'lib/scout_apm/store.rb', line 163 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
169 170 171 172 173 174 175 176 177 |
# File 'lib/scout_apm/store.rb', line 169 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
179 180 181 182 183 184 185 186 187 188 |
# File 'lib/scout_apm/store.rb', line 179 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
191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/scout_apm/store.rb', line 191 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 |
# 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.
103 104 105 |
# File 'lib/scout_apm/store.rb', line 103 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.
138 139 140 141 142 143 144 145 146 |
# File 'lib/scout_apm/store.rb', line 138 def store_slow(uri,transaction_hash,,parent_stat, = {}) @slow_transaction_lock.synchronize do # tree map of all slow transactions if parent_stat.total_call_time >= 2 @slow_transactions.push(ScoutApm::SlowTransaction.new(uri,.metric_name,parent_stat.total_call_time,transaction_hash.dup,ScoutApm::Context.current,Thread::current[:scout_apm_trace_time])) 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
.
154 155 156 157 158 159 160 |
# File 'lib/scout_apm/store.rb', line 154 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 |