mirror of https://github.com/rspamd/rspamd.git
Rapid spam filtering system
https://rspamd.com/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
128 lines
3.3 KiB
128 lines
3.3 KiB
#!/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 <asan_log_file>\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();
|