#!/usr/bin/perl # a simple script for measuring fragmentation in filesystems by displaying # the files with largest number of non-contiguous blocks # the basic functionality stolen from # http://lists.netisland.net/archives/phlpm/phlpm-2002/msg00354.html configure(); my @filelist; my %files; # get the list of files for (@ARGV) { push @filelist,recurse($_); } # analyze the files for (@filelist) { my @bm = blockmap($_); $files{$_}=analyze_noncont(@bm); } my $ncfiles=0; my $ncblocks=0; my $ncsize=0; my $cfiles=0; my $cblocks=0; my $csize=0; # print info of the files starting from the worst offenders for ( sort { $files{$b} <=> $files{$a} } keys %files) { if ($files{$_}>0) { printf("%6d %6.1fMB %8.2fkB %s\n", $files{$_}, (stat($_))[7]/(1024*1024), int((-s $_) / $BLOCK_SIZE + 1)/$files{$_} * ($BLOCK_SIZE / 1024), $_ ); $ncfiles++; $ncblocks+=$files{$_}; $ncsize+= (-s $_); } else { $cfiles++; $cblocks+=$files{$_}; $csize+= (-s $_); } } if ($ncfiles == 0) { printf("No non-contiguous files found!\n"); } else { printf("Non-contiguous:\n Files %d (%.1fMB, avg. %.2fkB per file), blocks %d, average block %.2fkB\n", $ncfiles, $ncsize/(1024*1024), $ncsize/$ncfiles/1024, $ncblocks, $ncsize/$ncblocks/1024 ); } if ($cfiles == 0) { printf("No contiguous files found!\n"); } else { printf("Contiguous:\n Files %d (%.1fMB, avg. %.2fkB per file)\n", $cfiles, $csize/(1024*1024), $csize/$cfiles/1024, ); } # END # get recursively contents of directories sub recurse($) { my($path) = @_; my @list; # append a trailing / if it's not there $path .= '/' if($path !~ /\/$/); # loop through the files contained in the directory for my $eachFile (glob($path.'*')) { # discard symlinks if (-l $eachFile) { next; } # recurse the directories and add normal files if( -d $eachFile) { push @list,recurse($eachFile); } elsif (-f $eachFile) { push @list,$eachFile; } } return @list; } # analyze how many non-contiguous blocks the file has. # number of non-contiguous blocks means that the drive will have to # seek that many times when reading the file sub analyze_noncont { my (@list)=@_; my $totalblocks=$#list; my $noncont=0; # return 0 if there is not at least 2 blocks in the file return 0 if $totalblocks<2; for (my $i=1;$i<=$#list;$i++) { $noncont++ unless $list[$i]-$list[$i-1] == 1; } # printf("%d %d\n",$totalblocks,$noncont); # return $noncont/$totalblocks; return $noncont; } # return block map for a file sub blockmap { my @res; my $f = shift; next unless (-f $f); unless (open F, "<", $f) { warn "Couldn't open file '$f': $!; skipping\n"; next; } my $extent = -s F; for (0 .. int(($extent-1)/$BLOCK_SIZE)) { my $absblock = block_no(\*F, $_); push @res, $absblock; } @res; } # return block number sub block_no { my ($fh, $ibn) = @_; my $arg = pack "I", $ibn; ioctl $fh, $FIBMAP, $arg or die "ioctl failed: $!"; unpack "I", $arg; } sub configure { unless (eval {require "fibmap.ph"}) { open C, "> fibmap.c" or die "Couldn't write configurator: $!; aborting"; print C while ; close C; system("cc -o fibmap fibmap.c") == 0 or die "Couldn't run C compiler.\n"; system("./fibmap > fibmap.ph") == 0 or die "Couldn't run configurator.\n"; unless (eval {require "fibmap.ph"}) { die "Couldn't load configurator: $@.\n"; } unlink "fibmap", "fibmap.c"; } ioctl(\*DATA, $FIGETBSZ, my $arg) or die "Couldn't get block size: $!"; $BLOCK_SIZE = unpack "I", $arg; print "Configurator ran OK; FIBMAP is $FIBMAP; BLOCK_SIZE is $BLOCK_SIZE\n"; } __DATA__ #include #include #include int main(void) { printf("$FIBMAP = %d;\n", FIBMAP); printf("$FIGETBSZ = %d;\n1;\n", FIGETBSZ); return 0; }