From 535846e55a1aaa6c19121db9ed7adeb2c2faa3ad Mon Sep 17 00:00:00 2001 From: Ryan Gonzalez Date: Sat, 19 Aug 2023 19:42:36 -0500 Subject: [PATCH] Add support for writing events at sample intervals --- README.md | 7 ++++++ src/alys.cr | 63 ++++++++++++++++++++++++++++++++++------------------- 2 files changed, 47 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index f2823c5..d172f88 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/alys.cr b/src/alys.cr index 9a4df6d..6a17d76 100644 --- a/src/alys.cr +++ b/src/alys.cr @@ -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 -- 2.45.2