From: Vsevolod Stakhov Date: Wed, 29 Oct 2025 11:19:25 +0000 (+0000) Subject: [Feature] Add ASAN leak analyzer script X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=6cd5df4c6116087e4713f6cff1b22fe656188b73;p=thirdparty%2Frspamd.git [Feature] Add ASAN leak analyzer script --- diff --git a/utils/asan_analyze.pl b/utils/asan_analyze.pl new file mode 100755 index 0000000000..5130d87bb8 --- /dev/null +++ b/utils/asan_analyze.pl @@ -0,0 +1,128 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use utf8; + +# Parse ASAN log and group leaks by stack trace +sub parse_asan_log { + my ($fh) = @_; + my %leaks; + + while (my $line = <$fh>) { + # Match leak header: "Indirect leak of 30 byte(s) in 2 object(s) allocated from:" + if ($line =~ /^(Indirect|Direct) leak of (\d+) byte\(s\) in (\d+) object\(s\) allocated from:/) { + my $leak_type = $1; + my $bytes_leaked = $2; + my $objects_count = $3; + + # Read stack trace + my @trace; + while (my $trace_line = <$fh>) { + chomp $trace_line; + $trace_line =~ s/^\s+|\s+$//g; + + # Stack trace lines start with #N + if ($trace_line =~ /^\s*#\d+/) { + push @trace, $trace_line; + } + else { + # End of trace + last; + } + } + + # Use trace as key for grouping + my $trace_key = join("\n", @trace); + + if (!exists $leaks{$trace_key}) { + $leaks{$trace_key} = { + bytes => 0, + objects => 0, + trace => \@trace, + type => $leak_type + }; + } + + $leaks{$trace_key}{bytes} += $bytes_leaked; + $leaks{$trace_key}{objects} += $objects_count; + } + } + + return \%leaks; +} + +# Print leaks sorted by bytes leaked +sub print_leaks_sorted { + my ($leaks) = @_; + + # Sort by bytes leaked (descending) + my @sorted_leaks = sort { $leaks->{$b}{bytes} <=> $leaks->{$a}{bytes} } keys %$leaks; + + my $total_bytes = 0; + my $total_objects = 0; + + print "=" x 80 . "\n"; + print "ASAN Leak Analysis - Sorted by bytes leaked\n"; + print "=" x 80 . "\n"; + print "\n"; + + my $idx = 1; + foreach my $trace_key (@sorted_leaks) { + my $leak = $leaks->{$trace_key}; + $total_bytes += $leak->{bytes}; + $total_objects += $leak->{objects}; + + print "Leak #$idx: $leak->{type} leak of $leak->{bytes} byte(s) in $leak->{objects} object(s) allocated from:\n"; + foreach my $trace_line (@{$leak->{trace}}) { + print " $trace_line\n"; + } + print "\n"; + $idx++; + } + + my $unique_traces = scalar @sorted_leaks; + print "=" x 80 . "\n"; + print "Total: $total_bytes bytes leaked in $total_objects objects across $unique_traces unique stack traces\n"; + print "=" x 80 . "\n"; +} + +# Main +sub main { + if (@ARGV < 1) { + print STDERR "Usage: $0 \n"; + print STDERR " $0 - (read from stdin)\n"; + print STDERR " command 2>&1 | $0 -\n"; + print STDERR "\n"; + print STDERR "Example:\n"; + print STDERR " $0 /tmp/asan.log\n"; + print STDERR " rspamadm configdump 2>&1 | $0 -\n"; + exit 1; + } + + my $log_file = $ARGV[0]; + my $fh; + + if ($log_file eq "-") { + # Read from stdin + $fh = \*STDIN; + binmode($fh, ":bytes"); + } + else { + # Read from file + if (!open($fh, "<:bytes", $log_file)) { + print STDERR "Error: Cannot open file '$log_file': $!\n"; + exit 1; + } + } + + my $leaks = parse_asan_log($fh); + + if ($log_file ne "-") { + close($fh); + } + + print_leaks_sorted($leaks); +} + +main();