#!/usr/bin/perl
#
# $Id: reorg.pl,v 1.3 2005/05/11 05:04:30 mpeppler Exp $

use strict;
use Getopt::Long;
use DBI;
use DBAutil;


#

my $dbname;
my $server;
my $pwd;
my $user      = 'sybadmin';
my $allstats;
my $filename;
my $rebuild;

my $dpcr = $ENV{REORG_DPCR} || 0.95;
my $ipcr = $ENV{REORG_IPCR} || 0.95;
my $sput = $ENV{REORG_SPUT} || 0.75;
my $fwd  = $ENV{REORG_FWD}  || 0.001;

GetOptions('D=s' => \$dbname,   'S=s' => \$server,  'U=s' => \$user,
	   'A'   => \$allstats, 'B'   => \$rebuild, 'P=s' => \$pwd );

die "Usage: $0 -S server -D database [-A] [-B] [-P sa_pwd]"
unless $dbname && $server;

if(!$pwd) {
    $pwd = DBAutil::getPassword($server);
}

die "$0: Sybase user and/or password not set in environment"
    unless $pwd && $user;

my $dbh = DBI->connect("dbi:Sybase:server=$server;database=$dbname",
		       $user, $pwd, { RaiseError => 1 });

my $tabinfo = get_tabinfo($dbh);
run_reorg($dbh, $tabinfo);

exit(0);

sub get_tabinfo {
    my $dbh = shift;

    my $sql = q(
set arithabort off

select s.segment
     , "total_space" = convert(float, sum(u.size)) * @@maxpagesize/(1024.0 * 1024.0)
     , "free_space" = case when s.segment = 2
          then 
            convert(float, sum(lct_admin("logsegment_freepages", db_id()))) * @@maxpagesize / (1024.0 * 1024.0)
          else
            convert(float, sum(curunreservedpgs(u.dbid, u.lstart, u.unreservedpgs))) * @@maxpagesize / (1024.0 * 1024.0)
       end
     , s.name
  into #segments
  from master..sysusages u
     , syssegments s
 where u.dbid = db_id()
   and (power(2, s.segment) & u.segmap) = power(2, s.segment)
 group by s.segment, s.name

select i.id
     , "used" = convert(float, reserved_pgs(i.id, i.doampg)) * @@maxpagesize / (1024.0 * 1024.0)
     , i.segment
  into #used
  from sysobjects o
     , sysindexes i
 where o.id = i.id
   and i.indid < 2
   and o.type = 'U'
   and lockscheme(o.id, db_id()) != 'allpages'

select o.name
     , u.used
     , u.segment
     , 'segname' = s.name
     , s.free_space
--     , "pct" = s.free_space / u.used
     , 'indname' = i.name
     , i.indid
     , 'dpcr' = derived_stat(o.id, i.indid, 'dpcr')
     , 'ipcr' = derived_stat(o.id, i.indid, 'ipcr')
     , 'drcr' = derived_stat(o.id, i.indid, 'drcr')
     , 'lgio' = derived_stat(o.id, i.indid, 'lgio')
     , 'sput' = derived_stat(o.id, i.indid, 'sput') 
     , t.forwrowcnt
     , t.delrowcnt
     , t.rowcnt
  from sysobjects o
     , #used u
     , #segments s
     , sysindexes i
     , systabstats t
 where o.id = u.id
   and u.segment = s.segment
   and i.id = o.id
   and t.id = o.id
   and t.indid = i.indid
 order by u.used, i.indid
);
    my $sth = $dbh->prepare($sql);
    $sth->execute;

    my @tabinfo;
 REDO: {
	while(my $row = $sth->fetchrow_hashref) {
	    push (@tabinfo, {%$row});
	}
	redo REDO if $sth->{syb_more_results};
    }

    return \@tabinfo;
}

sub run_reorg {
    my $dbh     = shift;
    my $tabinfo = shift;

    my %done;

    print STDERR "-- Using the following limits:\n";
    print STDERR "-- Space Usage (sput):                $sput\n";
    print STDERR "-- Data Page Clustering Ratio (dpcr): $dpcr\n";
    print STDERR "-- Indx Page Clustering Ratio (ipcr): $ipcr\n";
    print STDERR "-- Forwarded Row Ratio (fwd)        : $fwd\n";

        foreach my $row (@$tabinfo){
	$row->{fwd} = $row->{forwrowcnt} / $row->{rowcnt} if $row->{rowcnt} > 0;
	$row->{del} = $row->{delrowcnt} / $row->{rowcnt} if $row->{rowcnt} > 0;
	printstat($row) if $allstats;
	
	next if $row->{dpcr} == 1.0 || $row->{ipcr} == 1.0;
	next if $row->{used} < 0.5;    # no point in reorg for very small
                                       # tables
	
	if($row->{indid} < 2) {        # it's the table
	    if(($row->{sput} < $sput && $row->{dpcr} < $dpcr) ||
	        $row->{fwd} > $fwd || $row->{del} > $fwd) {
		printstat($row);
		if(!$rebuild || $row->{pct} < 1.01) {
		    run_command($dbh, qq(print "reorg compact $row->{name}"\nreorg compact $row->{name}));
		} else{
		    run_command($dbh, qq(print "reorg rebuild $row->{name}"\nreorg rebuild $row->{name}));
		    $done{$row->{name}} = 1;
		}
	    }
	} else {                       # it's an index
	    if(!$done{$row->{name}}) {
		if($row->{sput} < $sput && $row->{ipcr} < $ipcr) {
		    printstat($row);
		    run_command($dbh, qq(print "reorg rebuild $row->{name} $row->{indname}"\nreorg rebuild $row->{name} $row->{indname}));
		}
	    }
	}
    }

}

sub printstat {
    my $row = shift;
    my @derived_cols = qw(dpcr ipcr drcr lgio sput fwd del used); 

    my $dfmt = " %-7s" x @derived_cols;
    printf STDERR "-- %-15s %5s$dfmt\n", 'name', 'indid', @derived_cols;
    $dfmt =~ s/-7s/7.5f/g;
    unshift(@derived_cols, 'name', 'indid');
    printf STDERR "-- %-15.15s %5d$dfmt\n", @{$row}{@derived_cols};
}

sub run_command {
    my $dbh = shift;
    my $sql = shift;

    my $sth = $dbh->prepare($sql);
    $sth->execute;
    REDO: {
	    while(my $row = $sth->fetch) {
		print "@$row\n";
	    }
	    redo REDO if $sth->{syb_more_results};
	}

    my $sec = 60;

    print STDERR "Sleeping for $sec seconds\n";
    sleep($sec);
}
