@@ 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
@@ 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