# Copyright (c) 2016 SolarWinds, LLC. # All rights reserved. require 'json' module AppOpticsAPM module Inst ## # Moped # module Moped FLAVOR = :mongodb # Moped::Database DB_OPS = [:command, :drop].freeze # Moped::Indexes INDEX_OPS = [:create, :drop].freeze # Moped::Query QUERY_OPS = [:count, :sort, :limit, :distinct, :update, :update_all, :upsert, :explain, :modify, :remove, :remove_all].freeze # Moped::Collection COLLECTION_OPS = [:drop, :find, :indexes, :insert, :aggregate].freeze ## # remote_host # # This utility method converts the server into a host:port # pair for reporting # def remote_host(server) if ::Moped::VERSION < '2.0.0' server else "#{server.address.host}:#{server.address.port}" end end end ## # MopedDatabase # module MopedDatabase include AppOpticsAPM::Inst::Moped def self.included(klass) AppOpticsAPM::Inst::Moped::DB_OPS.each do |m| ::AppOpticsAPM::Util.method_alias(klass, m) end end def extract_trace_details(op) report_kvs = {} report_kvs[:Flavor] = AppOpticsAPM::Inst::Moped::FLAVOR # FIXME: We're only grabbing the first of potentially multiple servers here report_kvs[:RemoteHost] = remote_host(session.cluster.seeds.first) report_kvs[:Database] = name report_kvs[:QueryOp] = op.to_s report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:moped][:collect_backtraces] rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" ensure return report_kvs end def command_with_appoptics(command) if AppOpticsAPM.tracing? && !AppOpticsAPM.layer_op && command.key?(:mapreduce) begin report_kvs = extract_trace_details(:map_reduce) report_kvs[:Map_Function] = command[:map] report_kvs[:Reduce_Function] = command[:reduce] rescue => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do command_without_appoptics(command) end else command_without_appoptics(command) end end def drop_with_appoptics return drop_without_appoptics unless AppOpticsAPM.tracing? report_kvs = extract_trace_details(:drop_database) AppOpticsAPM::API.trace(:mongo, report_kvs) do drop_without_appoptics end end end ## # MopedIndexes # module MopedIndexes include AppOpticsAPM::Inst::Moped def self.included(klass) AppOpticsAPM::Inst::Moped::INDEX_OPS.each do |m| ::AppOpticsAPM::Util.method_alias(klass, m) end end def extract_trace_details(op) report_kvs = {} report_kvs[:Flavor] = AppOpticsAPM::Inst::Moped::FLAVOR # FIXME: We're only grabbing the first of potentially multiple servers here first = database.session.cluster.seeds.first if ::Moped::VERSION < '2.0.0' report_kvs[:RemoteHost] = first else report_kvs[:RemoteHost] = "#{first.address.host}:#{first.address.port}" end report_kvs[:Database] = database.name report_kvs[:QueryOp] = op.to_s report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:moped][:collect_backtraces] rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" ensure return report_kvs end def create_with_appoptics(key, options = {}) return create_without_appoptics(key, options) unless AppOpticsAPM.tracing? begin # We report :create_index here to be consistent # with other mongo implementations report_kvs = extract_trace_details(:create_index) report_kvs[:Key] = key.to_json report_kvs[:Options] = options.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs, :create_index) do create_without_appoptics(key, options = {}) end end def drop_with_appoptics(key = nil) return drop_without_appoptics(key) unless AppOpticsAPM.tracing? begin # We report :drop_indexes here to be consistent # with other mongo implementations report_kvs = extract_trace_details(:drop_indexes) report_kvs[:Key] = key.nil? ? :all : key.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do drop_without_appoptics(key = nil) end end end ## # MopedQuery # module MopedQuery include AppOpticsAPM::Inst::Moped def self.included(klass) AppOpticsAPM::Inst::Moped::QUERY_OPS.each do |m| ::AppOpticsAPM::Util.method_alias(klass, m) end end def extract_trace_details(op) report_kvs = {} report_kvs[:Flavor] = AppOpticsAPM::Inst::Moped::FLAVOR # FIXME: We're only grabbing the first of potentially multiple servers here first = collection.database.session.cluster.seeds.first report_kvs[:RemoteHost] = remote_host(first) report_kvs[:Database] = collection.database.name report_kvs[:Collection] = collection.name report_kvs[:QueryOp] = op.to_s report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:moped][:collect_backtraces] rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" ensure return report_kvs end def count_with_appoptics return count_without_appoptics unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:count) report_kvs[:Query] = selector.empty? ? :all : selector.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do count_without_appoptics end end def sort_with_appoptics(sort) return sort_without_appoptics(sort) unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:sort) report_kvs[:Query] = selector.empty? ? :all : selector.to_json report_kvs[:Order] = sort.to_s rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do sort_without_appoptics(sort) end end def limit_with_appoptics(limit) if AppOpticsAPM.tracing? && !AppOpticsAPM.tracing_layer_op?(:explain) begin report_kvs = extract_trace_details(:limit) report_kvs[:Query] = selector.empty? ? :all : selector.to_json report_kvs[:Limit] = limit.to_s rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do limit_without_appoptics(limit) end else limit_without_appoptics(limit) end end def distinct_with_appoptics(key) return distinct_without_appoptics(key) unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:distinct) report_kvs[:Query] = selector.empty? ? :all : selector.to_json report_kvs[:Key] = key.to_s rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do distinct_without_appoptics(key) end end def update_with_appoptics(change, flags = nil) if AppOpticsAPM.tracing? && !AppOpticsAPM.tracing_layer_op?(:update_all) && !AppOpticsAPM.tracing_layer_op?(:upsert) begin report_kvs = extract_trace_details(:update) report_kvs[:Flags] = flags.to_s if flags report_kvs[:Update_Document] = change.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do update_without_appoptics(change, flags) end else update_without_appoptics(change, flags) end end def update_all_with_appoptics(change) return update_all_without_appoptics(change) unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:update_all) report_kvs[:Update_Document] = change.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs, :update_all) do update_all_without_appoptics(change) end end def upsert_with_appoptics(change) return upsert_without_appoptics(change) unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:upsert) report_kvs[:Query] = selector.to_json report_kvs[:Update_Document] = change.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs, :upsert) do upsert_without_appoptics(change) end end def explain_with_appoptics return explain_without_appoptics unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:explain) report_kvs[:Query] = selector.empty? ? :all : selector.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs, :explain) do explain_without_appoptics end end def modify_with_appoptics(change, options = {}) return modify_without_appoptics(change, options) unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:modify) report_kvs[:Update_Document] = selector.empty? ? :all : selector.to_json report_kvs[:Change] = change.to_json report_kvs[:Options] = options.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do modify_without_appoptics(change, options) end end def remove_with_appoptics return remove_without_appoptics unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:remove) report_kvs[:Query] = selector.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do remove_without_appoptics end end def remove_all_with_appoptics return remove_all_without_appoptics unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:remove_all) report_kvs[:Query] = selector.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do remove_all_without_appoptics end end end ## # MopedCollection # module MopedCollection include AppOpticsAPM::Inst::Moped def self.included(klass) AppOpticsAPM::Inst::Moped::COLLECTION_OPS.each do |m| ::AppOpticsAPM::Util.method_alias(klass, m) end end def extract_trace_details(op) report_kvs = {} report_kvs[:Flavor] = AppOpticsAPM::Inst::Moped::FLAVOR # FIXME: We're only grabbing the first of potentially multiple servers here report_kvs[:RemoteHost] = remote_host(database.session.cluster.seeds.first) report_kvs[:Database] = database.name report_kvs[:Collection] = name report_kvs[:QueryOp] = op.to_s report_kvs[:Backtrace] = AppOpticsAPM::API.backtrace if AppOpticsAPM::Config[:moped][:collect_backtraces] rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" ensure return report_kvs end def drop_with_appoptics return drop_without_appoptics unless AppOpticsAPM.tracing? # We report :drop_collection here to be consistent # with other mongo implementations report_kvs = extract_trace_details(:drop_collection) AppOpticsAPM::API.trace(:mongo, report_kvs) do drop_without_appoptics end end def find_with_appoptics(selector = {}) return find_without_appoptics(selector) unless AppOpticsAPM.tracing? begin report_kvs = extract_trace_details(:find) report_kvs[:Query] = selector.empty? ? 'all' : selector.to_json rescue StandardError => e AppOpticsAPM.logger.debug "[appoptics_apm/debug] #{__method__}:#{File.basename(__FILE__)}:#{__LINE__}: #{e.message}" end AppOpticsAPM::API.trace(:mongo, report_kvs) do find_without_appoptics(selector) end end def indexes_with_appoptics return indexes_without_appoptics unless AppOpticsAPM.tracing? report_kvs = extract_trace_details(:indexes) AppOpticsAPM::API.trace(:mongo, report_kvs) do indexes_without_appoptics end end def insert_with_appoptics(documents, flags = nil) if AppOpticsAPM.tracing? && !AppOpticsAPM.tracing_layer_op?(:create_index) report_kvs = extract_trace_details(:insert) AppOpticsAPM::API.trace(:mongo, report_kvs) do insert_without_appoptics(documents, flags) end else insert_without_appoptics(documents, flags) end end def aggregate_with_appoptics(*pipeline) return aggregate_without_appoptics(*pipeline) unless AppOpticsAPM.tracing? report_kvs = extract_trace_details(:aggregate) report_kvs[:Query] = pipeline AppOpticsAPM::API.trace(:mongo, report_kvs) do aggregate_without_appoptics(pipeline) end end end end end if defined?(::Moped) && AppOpticsAPM::Config[:moped][:enabled] ::AppOpticsAPM.logger.info '[appoptics_apm/loading] Instrumenting moped' if AppOpticsAPM::Config[:verbose] ::AppOpticsAPM::Util.send_include(::Moped::Database, ::AppOpticsAPM::Inst::MopedDatabase) ::AppOpticsAPM::Util.send_include(::Moped::Collection, ::AppOpticsAPM::Inst::MopedCollection) ::AppOpticsAPM::Util.send_include(::Moped::Query, ::AppOpticsAPM::Inst::MopedQuery) ::AppOpticsAPM::Util.send_include(::Moped::Indexes, ::AppOpticsAPM::Inst::MopedIndexes) end