lib/ting_yun/agent/transaction.rb in tingyun_rpm-1.1.4.2 vs lib/ting_yun/agent/transaction.rb in tingyun_rpm-1.2.0

- old
+ new

@@ -4,202 +4,112 @@ require 'ting_yun/support/helper' require 'ting_yun/agent/method_tracer_helpers' require 'ting_yun/agent/transaction/transaction_metrics' require 'ting_yun/agent/transaction/request_attributes' require 'ting_yun/agent/transaction/attributes' +require 'ting_yun/agent/transaction/exceptions' +require 'ting_yun/agent/transaction/apdex' +require 'ting_yun/agent/transaction/class_method' +require 'ting_yun/agent/transaction/instance_method' module TingYun module Agent + # web transaction class Transaction + include TingYun::Agent::Transaction::InstanceMethod - APDEX_TXN_METRIC_PREFIX = 'Apdex/'.freeze + extend TingYun::Agent::Transaction::ClassMethod + + + + SUBTRANSACTION_PREFIX = 'Nested/'.freeze CONTROLLER_PREFIX = 'WebAction/'.freeze RAKE_TRANSACTION_PREFIX = 'BackgroundAction/Rake'.freeze TASK_PREFIX = 'OtherTransaction/Background/'.freeze RACK_PREFIX = 'Rack/'.freeze SINATRA_PREFIX = 'Sinatra/'.freeze MIDDLEWARE_PREFIX = 'Middleware/Rack/'.freeze GRAPE_PREFIX = 'Grape/'.freeze RAKE_PREFIX = 'Rake'.freeze - WEB_TRANSACTION_CATEGORIES = [:controller, :uri, :rack, :sinatra, :grape, :middleware, :thrift].freeze + EMPTY_SUMMARY_METRICS = [].freeze MIDDLEWARE_SUMMARY_METRICS = ['Middleware/all'.freeze].freeze TRACE_OPTIONS_SCOPED = {:metric => true, :scoped_metric => true}.freeze TRACE_OPTIONS_UNSCOPED = {:metric => true, :scoped_metric => false}.freeze NESTED_TRACE_STOP_OPTIONS = {:metric => true}.freeze - # A Time instance for the start time, never nil - attr_accessor :start_time - # A Time instance used for calculating the apdex score, which # might end up being @start, or it might be further upstream if # we can find a request header for the queue entry time - attr_accessor :apdex_start, - :category, - :frame_stack, - :exceptions, - :default_name, - :metrics, - :http_response_code, - :response_content_type, - :error_recorded, - :guid, - :attributes, - :request_attributes - def initialize(category, options) - @guid = options[:client_transaction_id] || generate_guid + attr_reader :apdex, + :exceptions, + :metrics, + :attributes, + :request_attributes, + :frame_stack, + :guid, + :category, + :default_name, + :start_time + + + + def initialize(category, client_transaction_id, options) + @start_time = Time.now + + @exceptions = TingYun::Agent::Transaction::Exceptions.new + @metrics = TingYun::Agent::TransactionMetrics.new + @attributes = TingYun::Agent::Transaction::Attributes.new + @apdex = TingYun::Agent::Transaction::Apdex.new(options[:apdex_start_time], @start_time) + @has_children = false @category = category - @exceptions = {} - @start_time = Time.now - @apdex_start = options[:apdex_start_time] || @start_time + + @guid = client_transaction_id || generate_guid @frame_stack = [] @frozen_name = nil @default_name = TingYun::Helper.correctly_encoded(options[:transaction_name]) - @metrics = TingYun::Agent::TransactionMetrics.new - @error_recorded = false - - @attributes = TingYun::Agent::Transaction::Attributes.new - - if request = options[:request] @request_attributes = TingYun::Agent::Transaction::RequestAttributes.new request else @request_attributes = nil end end - def request_path @request_attributes && @request_attributes.request_path end def request_port @request_attributes && @request_attributes.port end - def self.wrap(state, name, category, options = {}) - Transaction.start(state, category, options.merge(:transaction_name => name)) - begin - # We shouldn't raise from Transaction.start, but only wrap the yield - # to be absolutely sure we don't report agent problems as app errors - yield - rescue => e - Transaction.notice_error(e) - raise e - ensure - Transaction.stop(state) - end - end - - - def self.start(state, category, options) - category ||= :controller - txn = state.current_transaction - options[:client_transaction_id] = state.client_transaction_id - if txn - txn.create_nested_frame(state, category, options) - else - txn = start_new_transaction(state, category, options) - end - - # merge params every step into here - txn.attributes.merge_request_parameters(options[:filtered_params]) - - txn - rescue => e - TingYun::Agent.logger.error("Exception during Transaction.start", e) - end - - def self.start_new_transaction(state, category, options) - txn = Transaction.new(category, options) - state.reset(txn) - txn.start(state) - txn - end - def start(state) return if !state.execution_traced? + ::TingYun::Agent.instance.events.notify(:start_transaction) # Dispatcher调用 - transaction_sampler.on_start_transaction(state, start_time) - sql_sampler.on_start_transaction(state, request_path) - TingYun::Agent.instance.events.notify(:start_transaction) + ::TingYun::Agent::Collector::TransactionSampler.on_start_transaction(state, start_time) + ::TingYun::Agent::Collector::SqlSampler.on_start_transaction(state, request_path) + frame_stack.push TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_header(state, Time.now.to_f) name_last_frame @default_name freeze_name_and_execute if @default_name.start_with?(RAKE_TRANSACTION_PREFIX) end - def create_nested_frame(state, category, options) - @has_children = true - frame_stack.push TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_header(state, Time.now.to_f) - name_last_frame(options[:transaction_name]) - set_default_transaction_name(options[:transaction_name], category) - end - def set_default_transaction_name(name, category) - return log_frozen_name(name) if name_frozen? - if influences_transaction_name?(category) - self.default_name = name - @category = category if category - end - end - - - def self.stop(state, end_time = Time.now) - - txn = state.current_transaction - - if txn.nil? - TingYun::Agent.logger.error("Failed during Transaction.stop because there is no current transaction") - return - end - - nested_frame = txn.frame_stack.pop - - if txn.frame_stack.empty? - txn.stop(state, end_time, nested_frame) - state.reset - else - nested_name = nested_transaction_name(nested_frame.name) - - if nested_name.start_with?(MIDDLEWARE_PREFIX) - summary_metrics = MIDDLEWARE_SUMMARY_METRICS - else - summary_metrics = EMPTY_SUMMARY_METRICS - end - - TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_footer( - state, - nested_frame.start_time.to_f, - nested_name, - summary_metrics, - nested_frame, - NESTED_TRACE_STOP_OPTIONS, - end_time.to_f) - - end - - :transaction_stopped - rescue => e - state.reset - TingYun::Agent.logger.error("Exception during Transaction.stop", e) - nil - end - - def stop(state, end_time, outermost_frame) freeze_name_and_execute if @has_children @@ -208,11 +118,11 @@ else name = @frozen_name trace_options = TRACE_OPTIONS_UNSCOPED end - if needs_middleware_summary_metrics?(name) + if name.start_with?(MIDDLEWARE_PREFIX) summary_metrics_with_exclusive_time = MIDDLEWARE_SUMMARY_METRICS else summary_metrics_with_exclusive_time = EMPTY_SUMMARY_METRICS end @@ -224,281 +134,28 @@ summary_metrics_with_exclusive_time, outermost_frame, trace_options, end_time.to_f) - commit!(state, end_time, name) unless ignore(best_name) + commit(state, end_time, name) unless ignore(best_name) end - def self.nested_transaction_name(name) - if name.start_with?(CONTROLLER_PREFIX) || name.start_with?(RAKE_TRANSACTION_PREFIX) - "#{SUBTRANSACTION_PREFIX}#{name}" - else - name - end - end - def commit!(state, end_time, outermost_node_name) + def commit(state, end_time, outermost_node_name) assign_agent_attributes - transaction_sampler.on_finishing_transaction(state, self, end_time) + TingYun::Agent.instance.transaction_sampler.on_finishing_transaction(state, self, end_time) - sql_sampler.on_finishing_transaction(state, @frozen_name) + TingYun::Agent.instance.sql_sampler.on_finishing_transaction(state, @frozen_name) record_summary_metrics(outermost_node_name, end_time) - record_apdex(state, end_time) - record_exceptions - merge_metrics - end + @apdex.record_apdex(@frozen_name, end_time, @exceptions.had_error?) + @exceptions.record_exceptions(@attributes) - def record_summary_metrics(outermost_node_name,end_time) - unless @frozen_name == outermost_node_name - @metrics.record_unscoped(@frozen_name, TingYun::Helper.time_to_millis(end_time.to_f - start_time.to_f)) - end - end - - def assign_agent_attributes - - add_agent_attribute(:threadName, "pid-#{$$}"); - - if http_response_code - add_agent_attribute(:httpStatus, http_response_code.to_s) - end - - if response_content_type - add_agent_attribute(:contentType, response_content_type) - end - - - if @request_attributes - @request_attributes.assign_agent_attributes self - end - - end - - def add_agent_attribute(key, value) - @attributes.add_agent_attribute(key, value) - end - - #collector error - def had_error? - if @exceptions.empty? - return false - else - return true - end - end - - def record_exceptions - unless @exceptions.empty? - @exceptions.each do |exception, options| - - options[:uri] ||= request_path if request_path - options[:port] = request_port if request_port - options[:metric_name] = best_name - options[:attributes] = @attributes - - @error_recorded = !!::TingYun::Agent.instance.error_collector.notice_error(exception, options) || @error_recorded - end - end - end - - def record_apdex(state, end_time=Time.now) - total_duration = (end_time - apdex_start)*1000 - if recording_web_transaction? - record_apdex_metrics(APDEX_TXN_METRIC_PREFIX, total_duration, apdex_t) - end - end - - - def record_apdex_metrics(transaction_prefix, total_duration, current_apdex_t) - return unless current_apdex_t - return unless @frozen_name.start_with?(CONTROLLER_PREFIX) - - apdex_bucket_global = apdex_bucket(total_duration, current_apdex_t) - txn_apdex_metric = @frozen_name.sub(/^[^\/]+\//, transaction_prefix) - @metrics.record_unscoped(txn_apdex_metric, apdex_bucket_global, current_apdex_t) - end - - - def apdex_bucket(duration, current_apdex_t) - self.class.apdex_bucket(duration, had_error?, current_apdex_t) - end - - def self.apdex_bucket(duration, failed, apdex_t) - case - when failed - :apdex_f - when duration <= apdex_t - :apdex_s - when duration <= 4 * apdex_t - :apdex_t - else - :apdex_f - end - end - - def apdex_t - TingYun::Agent.config[:apdex_t] - end - - - # See TingYun::Agent.notice_error for options and commentary - def self.notice_error(e, options={}) - state = TingYun::Agent::TransactionState.tl_get - txn = state.current_transaction - if txn - txn.notice_error(e, options) - elsif TingYun::Agent.instance - TingYun::Agent.instance.error_collector.notice_error(e, options) - end - end - - # Do not call this. Invoke the class method instead. - def notice_error(error, options={}) # :nodoc: - if @exceptions[error] - @exceptions[error].merge! options - else - @exceptions[error] = options - end - end - - - # This transaction-local hash may be used as temprory storage by - # instrumentation that needs to pass data from one instrumentation point - # to another. - # - # For example, if both A and B are instrumented, and A calls B - # but some piece of state needed by the instrumentation at B is only - # available at A, the instrumentation at A may write into the hash, call - # through, and then remove the key afterwards, allowing the - # instrumentation at B to read the value in between. - # - # Keys should be symbols, and care should be taken to not generate key - # names dynamically, and to ensure that keys are removed upon return from - # the method that creates them. - # - def instrumentation_state - @instrumentation_state ||= {} - end - - def with_database_metric_name(model, method, product=nil) - previous = self.instrumentation_state[:datastore_override] - model_name = case model - when Class - model.name - when String - model - else - model.to_s - end - self.instrumentation_state[:datastore_override] = [method, model_name, product] - yield - ensure - self.instrumentation_state[:datastore_override] = previous - end - - def freeze_name_and_execute - if !name_frozen? - @name_frozen = true - @frozen_name = best_name - end - - yield if block_given? - end - - def promoted_transaction_name(name) - if name.start_with?(MIDDLEWARE_PREFIX) - "#{CONTROLLER_PREFIX}#{name}" - else - name - end - end - - def merge_metrics TingYun::Agent.instance.stats_engine.merge_transaction_metrics!(@metrics, best_name) - end - - def name_last_frame(name) - frame_stack.last.name = name - end - - def name_frozen? - @frozen_name ? true : false - end - - def log_frozen_name(name) - TingYun::Agent.logger.warn("Attempted to rename transaction to '#{name}' after transaction name was already frozen as '#{@frozen_name}'.") - nil - end - - def influences_transaction_name?(category) - !category || frame_stack.size == 1 || similar_category?(category) - end - - def web_category?(category) - WEB_TRANSACTION_CATEGORIES.include?(category) - end - - def similar_category?(category) - web_category?(@category) == web_category?(category) - end - - def recording_web_transaction? - web_category?(@category) - end - - def self.recording_web_transaction? #THREAD_LOCAL_ACCESS - txn = tl_current - txn && txn.recording_web_transaction? - end - - def self.tl_current - TingYun::Agent::TransactionState.tl_get.current_transaction - end - - def needs_middleware_summary_metrics?(name) - name.start_with?(MIDDLEWARE_PREFIX) - end - - alias_method :ignore, :needs_middleware_summary_metrics? - - def best_name - @frozen_name || @default_name || ::TingYun::Agent::UNKNOWN_METRIC - end - - def queue_time - @apdex_start ? @start_time - @apdex_start : 0 - end - - - def agent - TingYun::Agent.instance - end - - def sql_sampler - agent.sql_sampler - end - - - def transaction_sampler - TingYun::Agent.instance.transaction_sampler - end - - HEX_DIGITS = (0..15).map{|i| i.to_s(16)} - GUID_LENGTH = 16 - - # generate a random 64 bit uuid - private - def generate_guid - guid = '' - GUID_LENGTH.times do - guid << HEX_DIGITS[rand(16)] - end - guid end end end end