Listing 1

	=1=	#!/usr/bin/perl
	=2=	use strict;
	=3=	use warnings;
	=4=	
	=5=	use constant ONEOVERLOG2 => 1/log(2);
	=6=	
	=7=	use File::Find;
	=8=	use Template;
	=9=	
	=10=	my %sums;
	=11=	## $sums{$uid}[$log_2_blocks]{blocks} += $blocks;
	=12=	## $sums{$uid}[$log_2_blocks]{count} ++;
	=13=	
	=14=	@ARGV = qw(.) unless @ARGV;
	=15=	
	=16=	my %seen;
	=17=	
	=18=	find sub {
	=19=	  return if -l;
	=20=	  return unless -f _ or -d _;
	=21=	  my @stat = stat(_) or return;
	=22=	  my ($dev, $ino, $uid, $blocks) = @stat[0, 1, 4, 12];
	=23=	  return if $seen{"$dev $ino"}++;
	=24=	
	=25=	  my $slot = $blocks ? log($blocks) * ONEOVERLOG2 : 0;
	=26=	  $sums{$uid}[$slot]{blocks} += $blocks;
	=27=	  $sums{$uid}[$slot]{count}++;
	=28=	}, @ARGV;
	=29=	
	=30=	my %usernames = map { $_ => scalar getpwuid $_ } keys %sums;
	=31=	my @labels;
	=32=	for (values %sums) {
	=33=	  while (@labels < @$_) {
	=34=	    push @labels, 2**@labels;
	=35=	  }
	=36=	}
	=37=	
	=38=	my %VARS = 
	=39=	  (sums => \%sums,
	=40=	   usernames => \%usernames,
	=41=	   labels => \@labels);
	=42=	
	=43=	Template->new({ POST_CHOMP => 1 })
	=44=	  ->process(\<<'END_OF_TEMPLATE', \%VARS) or die Template->error;
	=45=	[% BLOCKSIZE = 1024 %]
	=46=	<style type="text/css">
	=47=	th, td {
	=48=	  margin: 0; border: 1px dotted black; padding: 0.2em; text-align: center;
	=49=	}
	=50=	</style>
	=51=	[% MACRO nice(n) BLOCK;
	=52=	  IF n >= 1073741824 * 9.995;
	=53=	    n / 1073741824 | format('%dG');
	=54=	  ELSIF n >= 1073741824;
	=55=	    n / 1073741824 | format('%.2gG');
	=56=	  ELSIF n >= 1048576 * 9.995;
	=57=	    n / 1048576 | format('%dM');
	=58=	  ELSIF n >= 1048576;
	=59=	    n / 1048576 | format('%.2gM');
	=60=	  ELSIF n >= 1024 * 9.995;
	=61=	    n / 1024 | format('%dK');
	=62=	  ELSIF n >= 1024;
	=63=	    n / 1024 | format('%.2gK');
	=64=	  ELSE;
	=65=	    n;
	=66=	  END;
	=67=	END %]
	=68=	[% MACRO bc(b,c) BLOCK %]
	=69=	<td>[% bytes = b * BLOCKSIZE; nice(bytes) %]<br/>[% nice(c) %]</td>
	=70=	[% END %]
	=71=	[% FOR uid = sums.keys.nsort %]
	=72=	[%   IF loop.first %]
	=73=	<table>
	=74=	[%     FOR label = labels %]
	=75=	[%       IF loop.first %]
	=76=	<tr><th>User</th>
	=77=	[%       END %]
	=78=	<th>[% label_bytes = label * BLOCKSIZE; nice(label_bytes) %]</th>
	=79=	[%       IF loop.last %]
	=80=	<th>Total</th>
	=81=	</tr>
	=82=	[%       END %]
	=83=	[%     END %]
	=84=	[%   END %]
	=85=	[%   labels_max = labels.max %]
	=86=	[%   FOR column = [0..labels_max] %]
	=87=	[%     cell = sums.$uid.$column %]
	=88=	[%     IF loop.first %]
	=89=	[%       total_blocks = 0; total_count = 0 %]
	=90=	<tr><th>[% usernames.$uid || "?"; " ("; uid; ")" %]</th>
	=91=	[%     END %]
	=92=	[%     IF cell %]
	=93=	[%       total_blocks = total_blocks + cell.blocks %]
	=94=	[%       total_count = total_count + cell.count %]
	=95=	[%       bc(cell.blocks, cell.count) %]
	=96=	[%     ELSE %]
	=97=	<td>&nbsp;</td>
	=98=	[%     END %]
	=99=	[%     IF loop.last %]
	=100=	[%       bc(total_blocks, total_count) %]
	=101=	</tr>
	=102=	[%     END %]
	=103=	[%   END %]
	=104=	[%   IF loop.last %]
	=105=	</table>
	=106=	[%   END %]
	=107=	[% END %]
	=108=	END_OF_TEMPLATE

