~refi64/alys

535846e55a1aaa6c19121db9ed7adeb2c2faa3ad — Ryan Gonzalez 1 year, 2 months ago 3b70e0b
Add support for writing events at sample intervals
2 files changed, 47 insertions(+), 23 deletions(-)

M README.md
M src/alys.cr
M README.md => README.md +7 -0
@@ 166,6 166,13 @@ TODO: add support for & document flipping Alys on / off at runtime

## Performance Notes

### Sampling intervals

Instead of writing an event for every trace, you can have Alys write an event
for every N bytes allocated by setting `ALYS_SAMPLE_INTERVAL=bytes:N`. This
should significantly increase the performance of allocations at the cost of
some granularity.

### Release Builds

**Gathering backtraces on release builds is significantly slower than debug

M src/alys.cr => src/alys.cr +40 -23
@@ 38,36 38,38 @@ module Alys
    Full
  end

  record BacktraceSettings, type_ : BacktraceType, limit : UInt32? = nil do
  record Settings, backtrace_type : BacktraceType,
    backtrace_limit : UInt32? = nil,
    sample_interval : UInt64 = 0 do
    def self.default
      self.name
    end

    def self.none
      BacktraceSettings.new BacktraceType::None
      Settings.new BacktraceType::None
    end

    def self.addr(limit = nil)
      BacktraceSettings.new BacktraceType::Addr, limit
    def self.addr(backtrace_limit = nil)
      Settings.new BacktraceType::Addr, backtrace_limit
    end

    def self.name(limit = nil)
      BacktraceSettings.new BacktraceType::Name, limit
    def self.name(backtrace_limit = nil)
      Settings.new BacktraceType::Name, backtrace_limit
    end

    def self.full(limit = nil)
      BacktraceSettings.new BacktraceType::Full, limit
    def self.full(backtrace_limit = nil)
      Settings.new BacktraceType::Full, backtrace_limit
    end
  end

  @@backtrace : BacktraceSettings = BacktraceSettings.default
  @@settings : Settings = Settings.default

  def self.backtrace_settings
    @@backtrace
  def self.settings
    @@settings
  end

  def self.backtrace_settings=(value : BacktraceSettings)
    @@backtrace = value
  def self.settings=(value : Settings)
    @@settings = value
  end

  def self.enabled?


@@ 84,13 86,21 @@ module Alys
    if backtrace_type_s = ENV["ALYS_BACKTRACE_TYPE"]?
      backtrace_type = BacktraceType.parse? backtrace_type_s
      raise "Invalid value for ALYS_BACKTRACE_TYPE: #{backtrace_type_s}" if !backtrace_type
      Alys.backtrace_settings = Alys.backtrace_settings.copy_with type_: backtrace_type
      Alys.settings = Alys.settings.copy_with backtrace_type: backtrace_type
    end

    if backtrace_limit_s = ENV["ALYS_BACKTRACE_LIMIT"]?
      backtrace_limit = backtrace_limit_s.to_u32?
      raise "Invalid value for ALYS_BACKTRACE_LIMIT: #{backtrace_limit_s}" if !backtrace_limit
      Alys.backtrace_settings = Alys.backtrace_settings.copy_with limit: backtrace_limit
      Alys.settings = Alys.settings.copy_with backtrace_limit: backtrace_limit
    end

    if sample_interval_s = ENV["ALYS_SAMPLE_INTERVAL"]?
      if sample_interval = sample_interval_s.lchop?("bytes:").try(&.to_u64?)
        Alys.settings = Alys.settings.copy_with sample_interval: sample_interval
      else
        raise "Invalid value for ALYS_SAMPLE_INTERVAL: #{sample_interval_s}"
      end
    end

    target = ENV["ALYS_TRACING"]? || return


@@ 270,6 280,7 @@ module Alys::Internal
  end

  @@in_write = false
  @@bytes_since_sample = 0u64

  private def self.in_write
    if @@in_write


@@ 297,7 308,7 @@ module Alys::Internal
  end

  private record BacktraceHandlerData,
    settings : BacktraceSettings,
    settings : Settings,
    packer : MsgPacker,
    count : UInt32 = 0



@@ 306,7 317,13 @@ module Alys::Internal
                        prev_addr : Void* = Pointer(Void).null)
    return unless packer = @@packer

    backtrace_settings = Alys.backtrace_settings
    settings = Alys.settings

    if settings.sample_interval != 0
      @@bytes_since_sample += size
      return unless @@bytes_since_sample > settings.sample_interval
      @@bytes_since_sample %= settings.sample_interval
    end

    if !prev_addr
      LibGC.register_finalizer_no_order mem, ->(obj, data) {


@@ 330,13 347,13 @@ module Alys::Internal
        extra.to_msgpack packer
      end

      if backtrace_settings.type_ != BacktraceType::None
        backtrace_data = BacktraceHandlerData.new backtrace_settings, packer
      if settings.backtrace_type != BacktraceType::None
        backtrace_data = BacktraceHandlerData.new settings, packer

        LibUnwind.backtrace ->(ctx, data) {
          bt_data = data.as Pointer(BacktraceHandlerData)
          our_packer = bt_data.value.packer
          settings = bt_data.value.settings
          our_settings = bt_data.value.settings

          ip = LibUnwind.get_ip(ctx).as UInt64



@@ 346,7 363,7 @@ module Alys::Internal
          file = name = nil
          line = 0

          if settings.type_ == BacktraceType::Name || settings.type_ == BacktraceType::Full
          if our_settings.backtrace_type == BacktraceType::Name || our_settings.backtrace_type == BacktraceType::Full
            name = Exception::CallStack.alys_decode_function_name ip

            if !name && (frame_info = Exception::CallStack.alys_decode_frame Pointer(Void).new(ip))


@@ 354,7 371,7 @@ module Alys::Internal
            end
          end

          if settings.type_ == BacktraceType::Full
          if our_settings.backtrace_type == BacktraceType::Full
            file, line, _ = Exception::CallStack.alys_decode_line_number ip
          end



@@ 364,7 381,7 @@ module Alys::Internal
            name: name || Bytes.empty
          frame.to_msgpack our_packer

          if limit = bt_data.value.settings.limit
          if limit = our_settings.backtrace_limit
            return LibUnwind::ReasonCode::END_OF_STACK if bt_data.value.count >= limit
            bt_data.value = bt_data.value.copy_with count: bt_data.value.count + 1
          end