Created
July 22, 2018 01:41
-
-
Save JohnRobson/64d4bed5b0729ff5f6b223f3c94ad01e to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# defragfs.pl -- Measurement and Report and Defrag fs/file fragmentation | |
# CanHao Xu <[email protected]>, 2007 | |
# John Robson <[email protected]>, 2011 | |
# Using: $ s_defrag / -f | |
# Using: $ sudo /home/john/Documents/Linux/Scripts/defragfs /srv -af | |
# Help: $ ./defragfs / -h | |
# Also check: $ sudo e4defrag -c /tmp/ # $ sudo e4defrag -v /tmp/ | |
print ("defragfs 1.4.0, Released under GPLv3 (GNU General Public License, Version 3 or any later version) by John Robson <john.robson\@usp.br>, Apr 2016 (help: \$ defragfs / -h)\n\n"); | |
($DIR, $ARG) = @ARGV; | |
if($ARG =~ m/h/) { die("GNU/Linux file systems rarely fragmented files. The file system always allocates more space to write a file, but sometimes the file size grows so that space becomes insufficient and the file is fragmented, but even so the file system fragments the file efficiently. | |
Using defragfs: | |
\$ sudo ./defragfs <partition or directory>\te.g. \$ sudo ./defragfs /usr/ | |
(or copy to /usr/bin/ and use in anywhere) \t\$ sudo cp -af defragfs /usr/bin/\te.g. \$ sudo defragfs /home/ | |
(perhaps you may need to give execute permissions: \$ sudo chmod +x /usr/bin/defragfs) | |
Options: | |
-a\tAutomatically defrag (configure: \$max_filefragrate and \$max_filefragmtsrate according to your preference). | |
Use this in your crontab.\te.g. sudo crontab -e\t(and add this line)\t0 0 1 */2 * defragfs / -a | |
-f\tForce defrag if there is at least one fragmented file. | |
-h\tDisplay this Help. | |
Examples of using: | |
\$ sudo defragfs /\t\tAnalyzes 'root', displays statistics and whether or not you need to defragment. | |
\$ sudo defragfs /home/ -a\tAnalyzes 'home', displays statistics and whether or not you need to defragment, if necessary, automatically defragments and exits. | |
\$ sudo defragfs /usr/ -f\tAnalyzes 'usr', displays statistics and waiting for your permission to defragment. | |
\$ sudo defragfs /home/user/.VirtualBox/HardDisks/ -af\tAnalyzes 'this directory', displays statistics, automatically defragments and exits. | |
Note 1: This program was tested on several different machines and also encrypted HOME partitions; worked without any problem. | |
Note 2: This program simply copies a fragmented file from an active or mounted file system, keeping all its attributes unchanged (cp -a file) and verifying that the file has not changed during the process. The *file system performs the defragmentation process*. Not all files can be defragmented. | |
Note 3: If the directory you specified contains too much files (e.g. tens of thousands), it could take you several minutes on analysis, you may disturb it by CTRL+C at anytime. And BTW: the program is CURRENTLY not accurate on Reiser4 due to its default tailing policy (you may see a high fragment rate before and even after) | |
Note 4: If you want to see how many fragments the file contains: \$ sudo filefrag -sv file | |
If the file is large and has more than 10 fragments, calculate the loss of performance: | |
\t1) \$ sudo cp -af file file.bak\t# copy the file, the copy usually has no fragments or very little | |
\t2) \$ sudo filefrag -sv file.bak\t# check if the copy is less fragmented | |
\t3) calculate the time to read the copy and the original: | |
\t\ttime cat file.bak > /dev/null | |
\t\ttime cat file > /dev/null | |
* Generally a very fragmented file takes much longer to read and less fragmented file is much faster. If the file is small (less than 100 mb) you may not notice the loss of performance because of *disk caching*, the ideal is to test large files over 300 mb or 1 gb. | |
I hope you enjoy this program. | |
Thanks, John. | |
"); } | |
if (!(-e $DIR) || !(-d $DIR)) { die "You must specify a correct directory name!"; } | |
my $AUTO = 0; | |
my $FORCE = 0; | |
if ($ARG =~ m/a/) { $AUTO = 1; } | |
if ($ARG =~ m/f/) { $FORCE = 1; } | |
if ($DIR eq "") { die "Usage: defragfs.pl DIRECTORY [-af], -af means force automatic defragmentation"; } # || ($DIR =~ m/-/) | |
start: | |
print ("Analysis in progress...\n\n"); | |
my $tmp_file = $DIR; | |
$tmp_file =~ s/[\$#@~!&*()\[\];.,:?^ `\\\/]+//g; # remove specific characters from $DIR string | |
my $TMP_filefrag_1 = "/tmp/fragsfs_$tmp_file-result-tmp"; | |
my $TMP_filefrag_2 = "/tmp/fragsfs_$tmp_file-result"; | |
my $TMP_defrag_filelist_1 = "/tmp/fragsfs_$tmp_file-filelist-tmp"; | |
my $TMP_defrag_filelist_2 = "/tmp/fragsfs_$tmp_file-filelist"; | |
my $files = 0; # number of files | |
my $fragmentsB = 0; # number of fragment before defrag | |
my $fragfilesB = 0; # number of fragmented files before defrag | |
my $fragmentsA = 0; # number of fragment after defrag | |
my $fragfilesA = 0; # number of fragmented files after defrag | |
my $max_filefragrate = 2.5; # max "File Fragmentation Rate" used to determine whether worth defrag. | |
my $max_filefragmtsrate = 10; # max "File Fragments Rate" used to determine whether worth defrag. | |
my $default_defrag_ratio; # default defragmentation ratio in percentage | |
my $max_display_num = 10; # display how much files in report | |
my $total_defrag_files = 0; # which files to be defrag, determined after user input the ratio | |
my $max_tries = 1; # max "Max Tries" used to determine max attempts to defrag a file after first attempt. | |
my $files_defragmented = 0; # number of files defragmented | |
my $files_fragments = 0; # number of fragments defragmented | |
system("rm -f $TMP_filefrag_1"); | |
system("rm -f $TMP_filefrag_2"); | |
system("rm -f $TMP_defrag_filelist_1"); | |
system("rm -f $TMP_defrag_filelist_2"); | |
my $progress = 0; | |
open (FILES, "find \"" . $DIR . "\" -xdev -type f |"); # will "find" all files under the folder specified in the arguments | |
while (defined (my $file = <FILES>)) { | |
$file =~ s/ /\\ /g; | |
$file =~ s/!/\\!/g; | |
$file =~ s/"/\\"/g; | |
$file =~ s/#/\\#/g; | |
$file =~ s/&/\\&/g; | |
$file =~ s/'/\\'/g; | |
$file =~ s/:/\\:/g; | |
$file =~ s/;/\\;/g; | |
$file =~ s/</\\</g; | |
$file =~ s/>/\\>/g; | |
$file =~ s/\$/\\\$/g; | |
$file =~ s/\(/\\\(/g; | |
$file =~ s/\)/\\\)/g; | |
$file =~ s/\|/\\\|/g; | |
$file =~ s/`/\\`/g; | |
open (FRAG, "filefrag $file |"); # will check the fragments of each file (command: filefrag) | |
my $res = <FRAG>; | |
if ($res =~ m/.*:\s+(\d+) extents? found/) { # if find fragment | |
my $fragment = $1; | |
if ($fragment eq 0) { $fragment = 1; } | |
$fragmentsB += $fragment; | |
if ($fragment > 1) { # if more than 1 fragment, add to list of files to defrag | |
system("echo -n \"$res\" >> $TMP_filefrag_1"); | |
$fragfilesB++; | |
} | |
$files++; | |
} | |
close (FRAG); | |
if (($progress++ % 1000) eq 0) { print "."; } | |
} | |
close (FILES); | |
if ($files eq 0) { | |
print ("The selected directory contains no file!\n"); | |
exit; | |
} | |
if ($fragfilesB eq 0) { | |
print ("The selected directory contains no framented file!\n"); | |
exit; | |
} | |
system("sort $TMP_filefrag_1 -g -t : -k 2 -r | sed \"/^\$/d\" > $TMP_filefrag_2"); # sort by most fragmented files | |
print ("\n\nStatistics for $DIR\n\n"); | |
print ("Total Files:\t\t\t" . $files . "\t\t(1 fragmented file for each " . 1 / ($fragfilesB / $files) . " files)\n"); | |
print ("Total Fragmented Files:\t\t" . $fragfilesB . "\t\t"); | |
if ($files > 0) { print ("File Fragmentation Rate:\t" . ($fragfilesB / $files) * 100 . " %\n"); } | |
$fragmentsB = ($fragmentsB - $files); | |
print ("Total Fragments:\t\t" . $fragmentsB . "\t\t"); | |
if ($files > 0) { print ("Avg Fragments Per Frag File:\t" . ($fragmentsB / $fragfilesB) . "\n"); } | |
#if ($fragfilesB > 0) { print ("Fragments per Fragmented File:\t" . $fragmentsB / $fragfilesB . "\n\n"); } | |
if ($fragfilesB > 0) { | |
if ($max_display_num > $fragfilesB) { $max_display_num = $fragfilesB; } | |
print ("10 Most Fragmented Files (for details see: $TMP_filefrag_2):\n\n"); | |
system("head $TMP_filefrag_2 -n $max_display_num"); | |
} else { | |
print ("\nYou do not need a defragmentation!\n"); | |
exit; | |
} | |
if (((($fragfilesB / $files) * 100) > $max_filefragrate) || ((($fragmentsB / $files) * 100) > $max_filefragmtsrate) || ($FORCE)) { | |
print ("\nYou need a defragmentation or you are using -f parameter!\n"); | |
} else { | |
print ("\nYou do not need a defragmentation!\n"); | |
exit; | |
} | |
defrag: | |
$default_defrag_ratio = ($files eq 1) ? 100 : ($fragfilesB / $files) * 100; | |
print ("\nPlease specify the percentage of files should be defrag (1-100) [$default_defrag_ratio] or hit Enter."); | |
if ($AUTO) { $defrag_ratio = "" } | |
else { $defrag_ratio = <STDIN>; } | |
chop($defrag_ratio); | |
if (!($defrag_ratio eq "") && (($defrag_ratio < 0) || ($defrag_ratio > 100))) { | |
print ("Error percentage numbers, please re-enter!\n"); | |
goto defrag; | |
} else { | |
$total_defrag_files = ($defrag_ratio eq "") ? $fragfilesB : int($defrag_ratio * $files / 100); | |
} | |
print ("\nPreparing defragmentation, please wait...\n"); | |
print ("\nFiles to be defragmented: " . $total_defrag_files . "\n\n"); | |
if ($total_defrag_files eq 0) { exit; } | |
system("head $TMP_filefrag_2 -n $total_defrag_files > $TMP_defrag_filelist_1"); | |
open (TMPFRAGLIST, "$TMP_defrag_filelist_1"); | |
while (<TMPFRAGLIST>) { | |
m/(.*):\s+(\d+) extents? found/; | |
my $filename = $1; | |
system("echo \"$1\" >> $TMP_defrag_filelist_2"); | |
} | |
open (GETSIZE, "$TMP_defrag_filelist_2"); | |
my $max = 0; | |
while (<GETSIZE>) { | |
s/(.*)\n/$1/; | |
$size = -s "$_"; | |
if ($size > $max) { $max = $size; } | |
} | |
print ("You need AT LEAST " . sprintf("%.3f", $max / 1048576) . " Megabytes temporarily used for defragmentation (at the directory where you specified), continue (Y/N)? [Y] "); | |
if ($AUTO) { $confirm = "" } | |
else { $confirm = <STDIN>; } | |
chop($confirm); | |
if (($confirm eq "y") || ($confirm eq "Y") || ($confirm eq "")) { | |
print ("\nOK, please drink a cup of tea and wait...\n"); | |
print ("\nFile Number - File Name (Size Mb) [actual extents] - extents after defrag attempt\n"); | |
my $actual_file = $total_defrag_files; | |
open (DEFRAG, "$TMP_defrag_filelist_2"); | |
while (<DEFRAG>) { | |
s/(.*)\n/$1/; | |
$from = $_; | |
s/(.*)/$1.ft/; # tmp file extension: .ft / if break, execute: $ sudo find . -name "*.ft" | |
$to = $_; | |
my $i_file = $actual_file; | |
my $i_tries = 0; | |
while ($i_tries++ < $max_tries) { | |
my $fragment_from = 0; | |
my $fragment_to = 0; | |
open (FRAG, "-|", "filefrag", $from); | |
if (<FRAG> =~ m/.*:\s+(\d+) extents? found/) { $fragment_from = $1; } | |
if ($i_file eq $actual_file) { | |
my $size = sprintf("%.3f", (stat($from))[7] / 1048576); | |
print "\n$actual_file - $from ($size) [$fragment_from] - "; | |
$actual_file--; | |
} | |
my $mtime = (stat($from))[9]; | |
system("sync"); | |
system("cp -af \"$from\" \"$to\" 2>/dev/null"); # create a file copy, hope that the system create a defragmented copy | |
#system("rsync -aEHAX --devices --specials \"$from\" \"$to\" 2>/dev/null"); | |
system("sync"); | |
if ($mtime != (stat($from))[9]) { # check the file hasn't been altered since we copied it | |
system("rm -f \"$to\""); | |
print "! "; | |
} else { | |
system("sync"); | |
open (FRAG, "-|", "filefrag", $to); | |
if (<FRAG> =~ m/.*:\s+(\d+) extents? found/) { $fragment_to = $1; } | |
if ($fragment_to <= $fragment_from) { # <= not just < | |
system("mv -f \"$to\" \"$from\" 2>/dev/null"); # if this copy has less fragments will replace the previous one | |
print "$fragment_to "; | |
if ($fragment_to eq $fragment_from) { | |
$i_tries++; | |
next; | |
} | |
if ($fragment_to < $fragment_from) { | |
$i_tries--; | |
$files_fragments += ($fragment_from - $fragment_to); | |
} | |
if ($fragment_to eq 1) { | |
$files_defragmented++; | |
last; | |
} | |
} else { | |
system("rm -f \"$to\""); | |
print "$fragment_from "; | |
} | |
} | |
system("sync"); | |
} | |
} | |
system("sync"); | |
system("rm -f $TMP_filefrag_1"); | |
system("rm -f $TMP_filefrag_2"); | |
system("rm -f $TMP_defrag_filelist_1"); | |
system("rm -f $TMP_defrag_filelist_2"); | |
print ("\n\nDefragmented Files: $files_defragmented / Fragments: $files_fragments\n"); | |
print ("\nDone!\n\n"); | |
if ($AUTO) { exit; } | |
else { goto start; } | |
} else { | |
exit; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment