public inbox for util-linux@vger.kernel.org
 help / color / mirror / Atom feed
* Automatic SSD trim script
@ 2013-12-05 19:20 Sten Heinze
  2013-12-06 10:49 ` Karel Zak
  0 siblings, 1 reply; 11+ messages in thread
From: Sten Heinze @ 2013-12-05 19:20 UTC (permalink / raw)
  To: util-linux

[-- Attachment #1: Type: text/plain, Size: 356 bytes --]

Having experienced the drop in speed when using a SSD with online discard, I wrote a small perl script to run fstrim using batched discard on partitions located on a SSD through cron. Do you think util-linux would be a good place for such a script, given that it is a helper to fstrim? What would be the best way to include one in util-linux?

Thanks,
Sten

[-- Attachment #2: autotrim.pl --]
[-- Type: text/plain, Size: 4151 bytes --]

#!/usr/bin/perl

# This script tries to detect all mounted file systems, which of them are on SSD drives, and run fstrim on them.
# For details see http://wiki.ubuntuusers.de/SSD/TRIM or https://wiki.debian.org/SSDOptimization
# Use cron to run regularly, logger to redirect the output to syslog (essentially the output of fstrim).
# Written by Sten Heinze, 2013.

use strict;
use warnings;
use Cwd qw(realpath);

my $hdparm = '/sbin/hdparm';
my $grep = '/bin/grep';
my $block_dev_path = '/sys/block/';
my $rotational_file = '/queue/rotational';
my $mount = '/bin/mount';
my $discard_option = 'discard';
my $fstrim = '/sbin/fstrim -v'; # verbose needed?

# remove leading and trailing whitespace
sub trim {
  my $str = shift;
  $str =~ s/^\s+|\s+$//g;
  return $str;
}

# read file content
sub read_file {
  my $filename = shift;
  open( my $file_handle, "<", $filename ) || die "$filename: $!";
  my $content = trim( join( '', <$file_handle> ) );
  close( $file_handle );
  return $content;
}

# print and execute the given command
sub e {
  my $cmd = shift;
  print( "$cmd\n" );
  system( "$cmd" );
}

# list all mounted drives; blkid doesn't provide mount points; fstab does ans is another possible source.
# maybe only include fixed/internal drives? /sys/block/sdX/removable doesn't help for deciding if a dev is fixed.
sub get_devs_from_mount {
  my %devs = (); # empty hash

  my $output = `$mount`; 
  my @lines = split( '\n', $output); # split the output into lines
  foreach my $line ( @lines ) {
    if( $line =~ m$(\S+) on (/\S*) type \S+ (.*)$) { # eg. /dev/sda8 on /home type ext4 (rw,relatime,data=ordered)
      my $path = $1;
      my $mount_point = $2;
      my $mount_options = $3;
      next if( index( $mount_options, $discard_option ) != -1 ); # not -1 means found discard, i.e. skip this line
      next if( ! -e $path ); # skip lines that are virtual fs
      $path = realpath( $path ); # get absolute path for those mount points that are uuid symlinks
      $devs{ $path } = $mount_point; # add to hash
    }
  }
  return %devs;
}

# check if device is ssd using hdparm
sub is_ssd_hdparm {
  my $dev = shift;
  $dev = substr( $dev, 5, 3 ); # short to 3 chars: /dev/xxxN to xxx

  return 0 if( ! -X $hdparm || ! -X $grep ); # return no ssd if no hdparm or no grep command available

  `$hdparm -I /dev/$dev 2>&1 | $grep 'TRIM supported' 2>/dev/null`; # perl calls bash, use bash redirect
  if( $? == -1 ) {
    #print "failed to execute: $!\n";
  }
  elsif( $? & 127 ) {
    #printf "child died with signal %d, %s coredump\n", ($? & 127),  ($? & 128) ? 'with' : 'without';
  }
  else {
    my $exit_code = $? >> 8;
    #printf "child exited with value %d\n", $exit_code;
    return 1 if( $exit_code == 0 ); # only if grep found something, TRIM is supported
  }
  return 0;
}

# check if device is ssd using /sys/block/sdX/queue/rotational: 0=SSD, 1=likely HDD, but could be USB memory etc.
# if any error occurs, assume dev is not rotational and return 0.
sub is_ssd_sysfs {
  my $dev = shift;
  $dev = substr( $dev, 5, 3 ); # short to 3 chars: /dev/xxxN to xxx
  my $dev_substr = substr( $dev, 0, 2 );

  return 0 if( $dev_substr ne "sd" ); # if device name is not sdX, assume it is not a ssd
  return 0 if( ! -R $block_dev_path.$dev.$rotational_file ); # return false if file is not readable
  return 0 if( read_file( $block_dev_path.$dev.$rotational_file ) eq "1" ); # if rotational, it's likely not a SSD
  return 1;
}

# check if $block_dev_dir/dev/queue/rotational is 0 -> not reliable, use only as fallback
sub filter_ssd {
  my %devs = @_;
  while( my ( $dev, $mount_point ) = each( %devs ) ) { # loop over hash and remove all non-ssd devs
    delete $devs{ $dev } if( ! is_ssd_hdparm( $dev ) && ! is_ssd_sysfs( $dev ) ); # remove dev if no ssd
  }
  return %devs;
}

# trim the mount points
sub trim_mount_points {
  my %devs = @_;
  while( my ( $dev, $mount_point ) = each( %devs ) ) { # loop over hash and call trim on mount points
    e( "$fstrim $mount_point" );
    #return; # for testing: use only the first
  }
}

# main
my %devs = get_devs_from_mount();
%devs = filter_ssd( %devs );
trim_mount_points( %devs );

^ permalink raw reply	[flat|nested] 11+ messages in thread

end of thread, other threads:[~2014-01-13 12:12 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2013-12-05 19:20 Automatic SSD trim script Sten Heinze
2013-12-06 10:49 ` Karel Zak
2013-12-09 13:43   ` Lukáš Czerner
2013-12-10 15:44     ` Karel Zak
2013-12-11  2:07       ` Sten Heinze
2013-12-11 10:54       ` Pádraig Brady
2013-12-11 11:36         ` Karel Zak
2013-12-11 16:03         ` Karel Zak
2013-12-17 19:46       ` Marcos Mello
2013-12-17 19:58         ` Marcos Mello
2014-01-13 12:12         ` Karel Zak

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox