# frozen_string_literal: true require "ffi" module Appsignal class Extension # JRuby extension wrapper # # Only loaded if the system is detected as JRuby. # # @api private module Jruby # rubocop:disable Metrics/ModuleLength extend FFI::Library # JRuby extension String helpers. # # Based on the make_appsignal_string and make_ruby_string helpers from the # AppSignal C-extension in `ext/appsignal_extension.c`. module StringHelpers class AppsignalString < FFI::Struct layout :len, :size_t, :buf, :pointer end def make_appsignal_string(ruby_string) unless ruby_string.is_a?(String) raise ArgumentError, "argument is not a string" end AppsignalString.new.tap do |appsignal_string| appsignal_string[:len] = ruby_string.bytesize appsignal_string[:buf] = FFI::MemoryPointer.from_string(ruby_string) end end def make_ruby_string(appsignal_string) appsignal_string[:buf].read_string(appsignal_string[:len]).tap do |ruby_string| ruby_string.force_encoding(Encoding::UTF_8) end end end include StringHelpers def self.lib_extension if Appsignal::System.agent_platform.include?("darwin") "dylib" else "so" end end begin ffi_lib File.join(File.dirname(__FILE__), "../../../ext/libappsignal.#{lib_extension}") typedef AppsignalString.by_value, :appsignal_string attach_function :appsignal_start, [], :void attach_function :appsignal_stop, [], :void attach_function :appsignal_diagnose, [], :appsignal_string attach_function :appsignal_get_server_state, [:appsignal_string], :appsignal_string attach_function :appsignal_running_in_container, [], :bool # Metrics methods attach_function :appsignal_set_gauge, [:appsignal_string, :double, :pointer], :void attach_function :appsignal_set_host_gauge, [:appsignal_string, :double], :void attach_function :appsignal_set_process_gauge, [:appsignal_string, :double], :void attach_function :appsignal_increment_counter, [:appsignal_string, :double, :pointer], :void attach_function :appsignal_add_distribution_value, [:appsignal_string, :double, :pointer], :void # Transaction methods attach_function :appsignal_free_transaction, [], :void attach_function :appsignal_start_transaction, [:appsignal_string, :appsignal_string, :long], :pointer attach_function :appsignal_start_event, [:pointer, :long], :void attach_function :appsignal_finish_event, [:pointer, :appsignal_string, :appsignal_string, :appsignal_string, :int64, :long], :void attach_function :appsignal_finish_event_data, [:pointer, :appsignal_string, :appsignal_string, :pointer, :int64, :long], :void attach_function :appsignal_record_event, [:pointer, :appsignal_string, :appsignal_string, :appsignal_string, :int64, :long, :long], :void attach_function :appsignal_record_event_data, [:pointer, :appsignal_string, :appsignal_string, :pointer, :int64, :long, :long], :void attach_function :appsignal_set_transaction_error, [:pointer, :appsignal_string, :appsignal_string, :pointer], :void attach_function :appsignal_set_transaction_action, [:pointer, :appsignal_string], :void attach_function :appsignal_set_transaction_namespace, [:pointer, :appsignal_string], :void attach_function :appsignal_set_transaction_sample_data, [:pointer, :appsignal_string, :pointer], :void attach_function :appsignal_set_transaction_queue_start, [:pointer, :long], :void attach_function :appsignal_set_transaction_metadata, [:pointer, :appsignal_string, :appsignal_string], :void attach_function :appsignal_finish_transaction, [:pointer, :long], :void attach_function :appsignal_complete_transaction, [:pointer], :void attach_function :appsignal_transaction_to_json, [:pointer], :appsignal_string # Data struct methods attach_function :appsignal_free_data, [], :void attach_function :appsignal_data_map_new, [], :pointer attach_function :appsignal_data_array_new, [], :pointer attach_function :appsignal_data_map_set_string, [:pointer, :appsignal_string, :appsignal_string], :void attach_function :appsignal_data_map_set_integer, [:pointer, :appsignal_string, :int64], :void attach_function :appsignal_data_map_set_float, [:pointer, :appsignal_string, :double], :void attach_function :appsignal_data_map_set_boolean, [:pointer, :appsignal_string, :bool], :void attach_function :appsignal_data_map_set_null, [:pointer, :appsignal_string], :void attach_function :appsignal_data_map_set_data, [:pointer, :appsignal_string, :pointer], :void attach_function :appsignal_data_array_append_string, [:pointer, :appsignal_string], :void attach_function :appsignal_data_array_append_integer, [:pointer, :int64], :void attach_function :appsignal_data_array_append_float, [:pointer, :double], :void attach_function :appsignal_data_array_append_boolean, [:pointer, :bool], :void attach_function :appsignal_data_array_append_null, [:pointer], :void attach_function :appsignal_data_array_append_data, [:pointer, :pointer], :void attach_function :appsignal_data_equal, [:pointer, :pointer], :bool attach_function :appsignal_data_to_json, [:pointer], :appsignal_string Appsignal.extension_loaded = true rescue LoadError => err Appsignal.logger.error( "Failed to load extension (#{err}), please email us at " \ "support@appsignal.com" ) Appsignal.extension_loaded = false end def start appsignal_start end def stop appsignal_stop end def diagnose make_ruby_string(appsignal_diagnose) end def get_server_state(key) state = appsignal_get_server_state(make_appsignal_string(key)) make_ruby_string state if state[:len] > 0 end def start_transaction(transaction_id, namespace, gc_duration_ms) transaction = appsignal_start_transaction( make_appsignal_string(transaction_id), make_appsignal_string(namespace), gc_duration_ms ) return if !transaction || transaction.null? Transaction.new(transaction) end def data_map_new Data.new(appsignal_data_map_new) end def data_array_new Data.new(appsignal_data_array_new) end def running_in_container? appsignal_running_in_container end def set_gauge(key, value, tags) appsignal_set_gauge(make_appsignal_string(key), value, tags.pointer) end def set_host_gauge(key, value) appsignal_set_host_gauge(make_appsignal_string(key), value) end def set_process_gauge(key, value) appsignal_set_process_gauge(make_appsignal_string(key), value) end def increment_counter(key, value, tags) appsignal_increment_counter(make_appsignal_string(key), value, tags.pointer) end def add_distribution_value(key, value, tags) appsignal_add_distribution_value(make_appsignal_string(key), value, tags.pointer) end class Transaction # rubocop:disable Metrics/ClassLength include StringHelpers attr_reader :pointer def initialize(pointer) @pointer = FFI::AutoPointer.new( pointer, Extension.method(:appsignal_free_transaction) ) end def start_event(gc_duration_ms) Extension.appsignal_start_event(pointer, gc_duration_ms) end def finish_event(name, title, body, body_format, gc_duration_ms) case body when String method = :appsignal_finish_event body_arg = make_appsignal_string(body) when Data method = :appsignal_finish_event_data body_arg = body.pointer else raise ArgumentError, "body argument should be a String or Appsignal::Extension::Data" end Extension.public_send( method, pointer, make_appsignal_string(name), make_appsignal_string(title), body_arg, body_format, gc_duration_ms ) end def record_event(name, title, body, body_format, duration, gc_duration_ms) # rubocop:disable Metrics/ParameterLists case body when String method = :appsignal_record_event body_arg = make_appsignal_string(body) when Data method = :appsignal_record_event_data body_arg = body.pointer else raise ArgumentError, "body argument should be a String or Appsignal::Extension::Data" end Extension.public_send( method, pointer, make_appsignal_string(name), make_appsignal_string(title), body_arg, body_format, duration, gc_duration_ms ) end def set_error(name, message, backtrace) Extension.appsignal_set_transaction_error( pointer, make_appsignal_string(name), make_appsignal_string(message), backtrace.pointer ) end def set_action(action_name) # rubocop:disable Naming/AccessorMethodName Extension.appsignal_set_transaction_action( pointer, make_appsignal_string(action_name) ) end def set_namespace(namespace) # rubocop:disable Naming/AccessorMethodName Extension.appsignal_set_transaction_namespace( pointer, make_appsignal_string(namespace) ) end def set_sample_data(key, payload) Extension.appsignal_set_transaction_sample_data( pointer, make_appsignal_string(key), payload.pointer ) end def set_queue_start(time) # rubocop:disable Naming/AccessorMethodName Extension.appsignal_set_transaction_queue_start(pointer, time) end def set_metadata(key, value) Extension.appsignal_set_transaction_metadata( pointer, make_appsignal_string(key), make_appsignal_string(value) ) end def finish(gc_duration_ms) Extension.appsignal_finish_transaction(pointer, gc_duration_ms) end def complete Extension.appsignal_complete_transaction(pointer) end def to_json json = Extension.appsignal_transaction_to_json(pointer) make_ruby_string(json) if json[:len] > 0 end end class Data include StringHelpers attr_reader :pointer def initialize(pointer) @pointer = FFI::AutoPointer.new( pointer, Extension.method(:appsignal_free_data) ) end def set_string(key, value) Extension.appsignal_data_map_set_string( pointer, make_appsignal_string(key), make_appsignal_string(value) ) end def set_integer(key, value) Extension.appsignal_data_map_set_integer( pointer, make_appsignal_string(key), value ) end def set_float(key, value) Extension.appsignal_data_map_set_float( pointer, make_appsignal_string(key), value ) end def set_boolean(key, value) Extension.appsignal_data_map_set_boolean( pointer, make_appsignal_string(key), value ) end def set_nil(key) # rubocop:disable Naming/AccessorMethodName Extension.appsignal_data_map_set_null( pointer, make_appsignal_string(key) ) end def set_data(key, value) Extension.appsignal_data_map_set_data( pointer, make_appsignal_string(key), value.pointer ) end def append_string(value) Extension.appsignal_data_array_append_string( pointer, make_appsignal_string(value) ) end def append_integer(value) Extension.appsignal_data_array_append_integer(pointer, value) end def append_float(value) Extension.appsignal_data_array_append_float(pointer, value) end def append_boolean(value) Extension.appsignal_data_array_append_boolean(pointer, value) end def append_nil Extension.appsignal_data_array_append_null(pointer) end def append_data(value) Extension.appsignal_data_array_append_data(pointer, value.pointer) end def ==(other) Extension.appsignal_data_equal(pointer, other.pointer) end def to_s make_ruby_string Extension.appsignal_data_to_json(pointer) end end end end end