module OneApm module Collector class CollectorService module Helper def handle_serialization_error(method, e) OneApm::Manager.increment_metric("Supportability/serialization_failure") OneApm::Manager.increment_metric("Supportability/serialization_failure/#{method}") msg = "Failed to serialize #{method} data using #{@marshaller.class.to_s}: #{e.inspect}" error = SerializationError.new(msg) error.set_backtrace(e.backtrace) raise error end def record_timing_supportability_metrics(method, start_ts, serialize_finish_ts) serialize_time = serialize_finish_ts && (serialize_finish_ts - start_ts) duration = (Time.now - start_ts).to_f OneApm::Manager.record_metric("Supportability/invoke_remote", duration) OneApm::Manager.record_metric("Supportability/invoke_remote/#{method.to_s}", duration) if serialize_time OneApm::Manager.record_metric("Supportability/invoke_remote_serialize", serialize_time) OneApm::Manager.record_metric("Supportability/invoke_remote_serialize/#{method.to_s}", serialize_time) end end # For these metrics, we use the following fields: # call_count => number of times this remote method was invoked # total_call_time => total size in bytes of payloads across all invocations # total_exclusive_time => total size in items (e.g. unique metrics, traces, events, etc) across all invocations # # The last field doesn't make sense for all methods (e.g. get_agent_commands), # so we omit it for those methods that don't really take collections # of items as arguments. def record_size_supportability_metrics(method, size_bytes, item_count) metrics = [ "Supportability/invoke_remote_size", "Supportability/invoke_remote_size/#{method.to_s}" ] # we may not have an item count, in which case, just record 0 for the exclusive time item_count ||= 0 OneApm::Manager.agent.stats_engine.tl_record_unscoped_metrics(metrics, size_bytes, item_count) end def log_and_return_response(response) OneApm::Manager.logger.debug "Received response, status: #{response.code}, encoding: '#{response['content-encoding']}'" case response when Net::HTTPSuccess true # do nothing when Net::HTTPUnauthorized raise LicenseException, 'Invalid license key, please visit support.oneapm.com' when Net::HTTPServiceUnavailable raise ServerConnectionException, "Service unavailable (#{response.code}): #{response.message}" when Net::HTTPGatewayTimeOut raise ServerConnectionException, "Gateway timeout (#{response.code}): #{response.message}" when Net::HTTPRequestEntityTooLarge raise UnrecoverableServerException, '413 Request Entity Too Large' when Net::HTTPUnsupportedMediaType raise UnrecoverableServerException, '415 Unsupported Media Type' else raise ServerConnectionException, "Unexpected response from server (#{response.code}): #{response.message}" end response end def reset_metric_id_cache @metric_id_cache = {} end # ===================== Helpers ========================== # takes an array of arrays of spec and id, adds it into the # metric cache so we can save the collector some work by # sending integers instead of strings the next time around def fill_metric_id_cache(pairs_of_specs_and_ids) Array(pairs_of_specs_and_ids).each do |metric_spec_hash, metric_id| metric_spec = MetricSpec.new(metric_spec_hash['name'], metric_spec_hash['scope']) metric_id_cache[metric_spec] = metric_id end rescue => e # If we've gotten this far, we don't want this error to propagate and # make this post appear to have been non-successful, which would trigger # re-aggregation of the same metric data into the next post, so just log OneApm::Manager.logger.error("Failed to fill metric ID cache from response, error details follow ", e) end # The collector wants to recieve metric data in a format that's different # from how we store it internally, so this method handles the translation. # It also handles translating metric names to IDs using our metric ID cache. def build_metric_data_array(stats_hash) metric_data_array = [] stats_hash.each do |metric_spec, stats| # Omit empty stats as an optimization unless stats.is_reset? metric_id = metric_id_cache[metric_spec] metric_data = if metric_id OneApm::MetricData.new(nil, stats, metric_id) else OneApm::MetricData.new(metric_spec, stats, nil) end metric_data_array << metric_data end end metric_data_array end def valid_to_marshal?(data) @marshaller.dump(data) true rescue StandardError, SystemStackError => e OneApm::Manager.logger.warn("Unable to marshal environment report on connect.", e) false end # Sets the user agent for connections to the server, to # conform with the HTTP spec and allow for debugging. Includes # the ruby version and also zlib version if available since # that may cause corrupt compression if there is a problem. def user_agent ruby_description = '' # note the trailing space! ruby_description << "(ruby #{::RUBY_VERSION} #{::RUBY_PLATFORM}) " if defined?(::RUBY_VERSION) && defined?(::RUBY_PLATFORM) zlib_version = '' zlib_version << "zlib/#{Zlib.zlib_version}" if defined?(::Zlib) && Zlib.respond_to?(:zlib_version) "OneApm-RubyAgent/#{OneApm::VERSION::STRING} #{ruby_description}#{zlib_version}" end private # A shorthand for OneApm::Probe.instance def probe OneApm::Probe.instance end end end end end