#!/usr/bin/perl -w # pgindexsize # Report on PostgreSQL index sizes # by Jon Jensen # 2003-03-07 use strict; use DBI; use Getopt::Long; #use Data::Dumper; #$Data::Dumper::Terse = 1; select STDERR; $| = 1; select STDOUT; $| = 1; my %opt = qw( report 1 ); GetOptions(\%opt, qw( dsn=s dbname|database|db=s user|username=s both=s password|pass=s pgdata=s rebuild report! quiet )); my $starttime = time; my $base = $opt{pgdata} || $ENV{PGDATA} || "/var/lib/pgsql/data"; $base =~ s:/+$::; $base .= "/base" unless $base =~ m:/base$:; $opt{dbname} = $opt{user} = $opt{both} if $opt{both}; if ($opt{dsn}) { $opt{dbname} = $1 if ! $opt{dbname} and $opt{dsn} =~ /\bdbname\s*=\s*(\S+)/i; } else { die "Please specify DSN or database name.\n" unless $opt{dbname}; $opt{dsn} = "dbi:Pg:dbname=$opt{dbname}"; }; my $sizelen = 12; unless ($opt{quiet}) { print STDERR "Connecting to $opt{dsn}"; print STDERR " as user $opt{user}" if $opt{user}; print STDERR "\n"; } my $dbh = DBI->connect($opt{dsn}, $opt{user}, $opt{password}, { AutoCommit => 1, RaiseError => 1 }); my $sth; $sth = $dbh->prepare("SELECT datid FROM pg_stat_database WHERE datname = ?"); $sth->execute($opt{dbname}); my ($dboid) = ($sth->fetchrow_array); $sth->finish; die "Error getting OID of database '$opt{dbname}'\n" unless $dboid; my $datadir = "$base/$dboid"; my $sizes1 = getoidsizes($datadir); die "No OID files found in $datadir!\n" unless %$sizes1; $sth = $dbh->prepare(<<'EOQ'); SELECT i.oid, c.relname AS tablename, i.relname AS indexname, pg_get_indexdef(x.indexrelid) AS indexdef FROM pg_index x, pg_class c, pg_class i WHERE c.relkind = 'r'::"char" AND i.relkind = 'i'::"char" AND c.oid = x.indrelid AND i.oid = x.indexrelid AND c.relname NOT LIKE 'pg_%'::"text" EOQ $sth->execute; my $result1 = $sth->fetchall_hashref('indexname'); die "No results found for indexes query!\n" unless %$result1; my $namelen = 0; for (keys %$result1) { my $len = length($result1->{$_}{indexname}); $namelen = $len if $len > $namelen; } #print Dumper($result1, $sizes1); unless ($opt{rebuild}) { exit unless $opt{report}; my $headerformat = "%-${namelen}s %${sizelen}s\n"; my $rowformat = "%-${namelen}s %${sizelen}lu\n"; my $separator = "-" x ($namelen + $sizelen + 1) . "\n"; printf $headerformat, "Index Name", "Bytes"; print $separator; my $totalsize = 0; for (sort keys %$result1) { my $size = $sizes1->{$result1->{$_}{oid}}; $totalsize += $size; printf $rowformat, $_, $size; } print $separator; printf $rowformat, "(total)", $totalsize; exit; } for (sort keys %$result1) { my $sql = $result1->{$_}{indexdef}; die "Index definition missing for $_!\n" unless $sql =~ /^CREATE(?:\s+UNIQUE)?\s+INDEX\s+$_\s+/; print STDERR "Rebuilding $_\n" unless $opt{quiet}; $dbh->begin_work; $dbh->do("DROP INDEX $_"); $dbh->do($sql); $dbh->commit; } exit unless $opt{report}; sleep 1; $sth->execute; my $result2 = $sth->fetchall_hashref('indexname'); my $sizes2 = getoidsizes($datadir); my $headerformat = "%-${namelen}s" . (" %${sizelen}s" x 3) . " %4s\n"; my $rowformat = "%-${namelen}s" . (" %${sizelen}lu" x 2) . " %${sizelen}li %3li%%\n"; my $separator = "-" x ($namelen + ($sizelen + 1) * 3 + 5) . "\n"; printf $headerformat, "Index Name", "Before", "After", "Bytes Less", "Pct."; print $separator; my $beforetotal = my $aftertotal = 0; for (sort keys %$result1) { my $before = $sizes1->{$result1->{$_}{oid}}; $beforetotal += $before; my $after = $sizes2->{$result2->{$_}{oid}}; $aftertotal += $after; printf $rowformat, $_, $before, $after, $before - $after, $after / $before * 100; } print $separator; printf $rowformat, "(total)", $beforetotal, $aftertotal, $beforetotal - $aftertotal, $aftertotal / $beforetotal * 100; printf STDERR "Elapsed time: %u seconds\n", time - $starttime unless $opt{quiet}; END { $dbh and $dbh->disconnect; } sub getoidsizes { my ($datadir) = @_; opendir DIR, $datadir or die "Can't opendir $datadir: $!\n"; my %sizes = map { $_ => -s "$datadir/$_" } grep ! /\D/, readdir(DIR); closedir DIR; return \%sizes; }