#!/usr/bin/perl

# analyze-debug-alloc.pl
# generate allocation report by processing log files

# Note that this script is only useful when run against freeswitch log files
# produced when server is running with DEBUG_ALLOC and DEBUG_ALLOC2 set.
# It's purely for diagnosing memory leaks.

use strict;
use JSON;

my $debug = 0;

my @logs = sort glob("freeswitch.log.*");
push( @logs, "freeswitch.log" );

my %pools = ();

foreach my $file (@logs) {
    open( my $in, "<$file" );
    while ( defined( my $line = <$in> ) ) {
        if ( $line =~ /(0x[0-9A-Fa-f]+) DESTROY POOL$/o ) {
            my $paddr = $1;
            if ( !$pools{$paddr} ) {
                $debug && print "WARN: No ref to pool $paddr.\n";
            }
            else {
                foreach my $alloc ( @{ $pools{$paddr}->{allocs} } ) {

                    # debug, might not be needed
                }
                delete $pools{$paddr};
            }
        }
        elsif ( $line =~ /(0x[0-9A-Fa-f]+) Free Pool/o ) {
            my $paddr = $1;
            if ( !$pools{$paddr} ) {
                $debug && print "WARN: No ref to pool $paddr.\n";
            }
            else {
                foreach my $alloc ( @{ $pools{$paddr}->{allocs} } ) {

                    # debug, might not be needed
                }
                delete $pools{$paddr};
            }
        }
        elsif ( $line =~ /(0x[0-9A-Fa-f]+) New Pool (.*)$/o ) {
            my $paddr = $1;
            my $where = $2;
            if ( $pools{$paddr} ) {
                $debug && print "WARN: Duplicate pool $paddr at $where.\n";
            }
            $pools{$paddr}->{where} = $where;
            if ( !$pools{$paddr}->{allocs} ) {
                $pools{$paddr}->{allocs} = [];
            }
        }
        elsif ( $line =~ /CONSOLE\] \s*(.*?:\d+) (0x[0-9A-Fa-f]+) Core Allocate (.*:\d+)\s+(\d+)$/o ) {
            my $where  = $1;
            my $paddr  = $2;
            my $pwhere = $3;
            my $size   = $4;
            if ( !$pools{$paddr} ) {
                $debug && print "WARN: Missing pool ref for alloc of $size from $paddr at $where (pool $pwhere)\n";
            }
            $pools{$paddr}->{where} = $where;
            push( @{ $pools{$paddr}->{allocs} }, { size => $size, where => $where } );
        }
        elsif ( $line =~ /CONSOLE\] \s*(.*?:\d+) (0x[0-9A-Fa-f]+) Core Strdup Allocate (.*:\d+)\s+(\d+)$/o ) {
            my $where  = $1;
            my $paddr  = $2;
            my $pwhere = $3;
            my $size   = $4;
            if ( !$pools{$paddr} ) {
                $debug
                    && print "WARN: Missing pool ref for strdup alloc of $size from $paddr at $where (pool $pwhere)\n";
            }
            $pools{$paddr}->{where} = $where;
            push( @{ $pools{$paddr}->{allocs} }, { size => $size, where => $where } );
        }
    }
}

my $used                = 0;
my $pcount              = 0;
my $acount              = 0;
my %pool_cnt_by_where   = ();
my %alloc_size_by_where = ();
my %alloc_cnt_by_where  = ();
foreach my $pool ( keys %pools ) {
    my $where = $pools{$pool}->{where};
    $pcount++;
    $pool_cnt_by_where{$where}++;

    foreach my $alloc ( @{ $pools{$pool}->{allocs} } ) {
        my $sz    = $alloc->{size};
        my $where = $alloc->{where};

        $acount++;
        $alloc_size_by_where{$where} += $sz;
        $alloc_cnt_by_where{$where}++;

        $used += $sz;
    }
}

print "Used: $used\n";
print "Pool Count: $pcount\n";
print "Alloc Count: $acount\n";

my $json = new JSON;
$json->pretty(1);
$json->canonical(1);

print "Pool Count by Where:\n";
foreach my $pool ( sort { $pool_cnt_by_where{$a} <=> $pool_cnt_by_where{$b} || $a cmp $b } keys %pool_cnt_by_where ) {
    print $pool_cnt_by_where{$pool}, "\t", $pool, "\n";
}
print "\n";

print "Alloc Count by Where:\n";
foreach my $pool ( sort { $alloc_cnt_by_where{$a} <=> $alloc_cnt_by_where{$b} || $a cmp $b } keys %alloc_cnt_by_where )
{
    print $alloc_cnt_by_where{$pool}, "\t", $pool, "\n";
}
print "\n";

print "Alloc Size by Where:\n";
foreach
    my $pool ( sort { $alloc_size_by_where{$a} <=> $alloc_size_by_where{$b} || $a cmp $b } keys %alloc_size_by_where )
{
    print $alloc_size_by_where{$pool}, "\t", $pool, "\n";
}
print "\n";