Browse Source

Separated out data processing from the getter methods. Getter methods now only return hash data.
Added methods and unit tests for update_data() and get_disk_attributes(). update_data() can be called at any point in a calling script to update the drive data. This makes it where Disk::SMART can be used in a script called by cron, or a continuously running daemon.
Updated MANIFEST to include LICENSE file
Changes to satisfy perl critic
Simplified get_disk_temp with substr to read the disk temp

Paul Trost 11 years ago
parent
commit
6aee7e1dfb
4 changed files with 179 additions and 66 deletions
  1. 8 1
      Changes
  2. 3 0
      MANIFEST
  3. 156 63
      lib/Disk/SMART.pm
  4. 12 2
      t/02-get_functions.t

+ 8 - 1
Changes

@@ -1,6 +1,13 @@
 Revision history for Disk-SMART
 
-0.03.1  2014-10-06
+0.05    2014-10-08
+        Separated out data processing from the getter methods. Getter methods now only return hash data.
+        Added methods and unit tests for update_data() and get_disk_attributes(). update_data() can be called at any point in a calling script to update the drive data. This makes it where Disk::SMART can be used in a script called by cron, or a continuously running daemon.
+        Updated MANIFEST to include LICENSE file
+        Changes to satisfy perl critic
+        Simplified get_disk_temp with substr to read the disk temp
+
+0.04    2014-10-06
         Updated Makefile.PL to better interact with Meta::CPAN
         Updated pod for clarity
 

+ 3 - 0
MANIFEST

@@ -3,6 +3,9 @@ lib/Disk/SMART.pm
 Makefile.PL
 MANIFEST			This list of files
 INSTALL
+README
+README.md
+LICENSE
 t/00-load.t
 t/01-instantiation.t
 t/02-get_functions.t

+ 156 - 63
lib/Disk/SMART.pm

@@ -1,8 +1,16 @@
 package Disk::SMART;
+
+use warnings;
+use strict;
+use Carp;
+use Math::Round;
+
 {
-    $Disk::SMART::VERSION = '0.03.1'
+    $Disk::SMART::VERSION = '0.05'
 }
 
+our $smartctl = '/usr/sbin/smartctl';
+
 =head1 NAME
 
 Disk::SMART - Provides an interface to smartctl
@@ -15,18 +23,12 @@ Disk::SMART is an object ooriented module that provides an interface to get SMAR
 
     my $smart = Disk::SMART->new('/dev/sda');
 
-
 =cut
 
-use warnings;
-use strict;
-use Carp;
-use Math::Round;
-
 
 =head1 CONSTRUCTOR
 
-=head2 B<new (DEVICE)>
+=head2 B<new(DEVICE)>
 
 Instantiates the Disk::SMART object
 
@@ -40,26 +42,22 @@ Returns C<Disk::SMART> object if smartctl is available and can poll the given de
 
 sub new {
     my ( $class, @devices ) = @_;
-    my $smartctl = '/usr/sbin/smartctl';
     my $self = bless {}, $class;
 
     croak "Valid device identifier not supplied to constructor for $class.\n" if ( !@devices );
     croak "smartctl binary was not found on your system, are you running as root?\n" if !-f $smartctl;
 
     foreach my $device (@devices) {
-        my $out = qx($smartctl -a $device);
-        if ( $out =~ /No such device/i ) {
-            croak "Smartctl couldn't poll device $device\n";
-        }
-        $self->{'devices'}->{$device}->{'SMART_OUTPUT'} = $out;
+        $self->update_data($device);
     }
-
     return $self;
 }
 
+
 =head1 USER METHODS
 
-=head2 B<get_disk_temp (DEVICE)>
+
+=head2 B<get_disk_temp(DEVICE)>
 
 Returns an array with the temperature of the device in Celsius and Farenheit, or N/A.
 
@@ -71,25 +69,11 @@ C<DEVICE> - Device identifier of SSD / Hard Drive
 
 sub get_disk_temp {
     my ( $self, $device ) = @_;
-    $self->_validate_param($device);
-    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
-    my ($temp_c) = $smart_output =~ /(Temperature_Celsius.*\n)/;
-
-    if ( !defined $temp_c || $smart_output =~ qr/S.M.A.R.T. not available/x ) {
-        return 'N/A';
-    }
-
-    chomp $temp_c;
-    $temp_c =~ s/ //g;
-    $temp_c =~ s/.*-//;
-    $temp_c =~ s/\(.*\)//;
-
-    my $temp_f = round( ( $temp_c * 9 ) / 5 + 32 );
-    return ( $temp_c, $temp_f );
-   
+    return $self->{'devices'}->{$device}->{'temp'};
 }
 
-=head2 B<get_disk_health (DEVICE)>
+
+=head2 B<get_disk_health(DEVICE)>
 
 Returns the health of the disk. Output is "PASSED", "FAILED", or "N/A".
 
@@ -101,20 +85,11 @@ C<DEVICE> - Device identifier of SSD / Hard Drive
 
 sub get_disk_health {
     my ( $self, $device ) = @_;
-    $self->_validate_param($device);
-    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
-    my ($health) = $smart_output =~ /(SMART overall-health self-assessment.*\n)/;
-
-    if ( (!defined $health) or $health !~ /PASSED|FAILED/x ) {
-        return 'N/A';
-    }
-
-    $health =~ s/.*: //;
-    chomp $health;
-    return $health;
+    return $self->{'devices'}->{$device}->{'health'};
 }
 
-=head2 B<get_disk_model (DEVICE)>
+
+=head2 B<get_disk_model(DEVICE)>
 
 Returns the model of the device. eg. "ST3250410AS".
 
@@ -126,30 +101,92 @@ C<DEVICE> - Device identifier of SSD / Hard Drive
 
 sub get_disk_model {
     my ( $self, $device ) = @_;
-    $self->_validate_param($device);
-    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
-    my ($model) = $smart_output =~ /(Device\ Model.*\n)/;
-
-    if ( !defined $model ) {
-        return 'N/A';
-    }
-    
-    $model =~ s/.*:\ //;
-    $model =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
-    return $model;
+    return $self->{'devices'}->{$device}->{'model'};
 }
 
-=head2 B<get_disk_errors (DEVICE)>
 
-Returns any listed errors
+=head2 B<get_disk_errors(DEVICE)>
 
-C<DEVICE> - DEvice identifier of SSD/ Hard Drive
+Returns scalar of any listed errors
+
+C<DEVICE> - Device identifier of SSD/ Hard Drive
 
     my $disk_errors = $smart->get_disk_errors('/dev/sda');
 
 =cut
 
 sub get_disk_errors {
+    my ( $self, $device ) = @_;
+    return $self->{'devices'}->{$device}->{'errors'};
+}
+
+
+=head2 B<get_disk_attributes(DEVICE)>
+
+Returns hash of the SMART disk attributes and values
+
+C<DEVICE> - Device identifier of SSD/ Hard Drive
+
+    my %disk_attributes = $smart->get_disk_attributes('/dev/sda');
+
+=cut
+
+sub get_disk_attributes {
+    my ( $self, $device ) = @_;
+    return $self->{'devices'}->{$device}->{'attributes'};
+}
+
+
+=head2 B<update_data>
+
+Updates the SMART output and attributes of a device. Returns undef.
+
+C<DEVICE> - Device identifier of SSD/ Hard Drive
+
+    $smart->update_data('/dev/sda');
+
+=cut
+
+sub update_data {
+    my ( $self, $device ) = @_;
+
+    chomp( my $out = qx($smartctl -a $device) );
+    croak "Smartctl couldn't poll device $device\n" if $out =~ /No such device/;
+    $self->{'devices'}->{$device}->{'SMART_OUTPUT'} = $out;
+    
+    # update_data() can be called at any time with a device name. Let's check
+    # the device name given to make sure it matches what was given during
+    # object construction.
+    croak "$device not found in object, You probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
+
+    $self->_process_disk_attributes($device);
+    $self->_process_disk_errors($device);
+    $self->_process_disk_health($device);
+    $self->_process_disk_model($device);
+    $self->_process_disk_temp($device);
+
+    return 1;
+}
+
+sub _process_disk_attributes {
+    my ( $self, $device ) = @_;
+
+    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
+    my ($smart_attributes) = $smart_output =~ /(ID# ATTRIBUTE_NAME.*)\nSMART Error/s;
+    my @attributes = split /\n/, $smart_attributes;
+    shift @attributes;
+
+    foreach my $attribute (@attributes) {
+        my $name  = substr $attribute, 4,  +24;
+        my $value = substr $attribute, 83, +50;
+        $name  =~ s/\s+$//g;    # trim ending whitespace
+        $value =~ s/^\s+//g;    # trim beginning and ending whitepace
+        $self->{'devices'}->{$device}->{'attributes'}->{$name} = $value;
+    }
+    return;
+}
+
+sub _process_disk_errors {
     my ( $self, $device ) = @_;
     $self->_validate_param($device);
 
@@ -157,19 +194,75 @@ sub get_disk_errors {
     my ($errors) = $smart_output =~ /SMART Error Log Version: [1-9](.*)SMART Self-test log/s;
 
     if ( !defined $errors ) {
-        return 'N/A';
+        $self->{'devices'}->{$device}->{'errors'} = 'N/A';
+        return;
     }
 
     $errors =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
-    return $errors;
+    $self->{'devices'}->{$device}->{'errors'} = $errors;
+    return;
 }
 
-sub _validate_param {
+sub _process_disk_health {
     my ( $self, $device ) = @_;
-    croak "$device not found in object, You probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
+    $self->_validate_param($device);
+    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
+    my ($health) = $smart_output =~ /(SMART overall-health self-assessment.*\n)/;
+
+    if ( (!defined $health) or $health !~ /PASSED|FAILED/x ) {
+        $self->{'devices'}->{$device}->{'health'} = 'N/A';
+        return;
+    }
+
+    $health =~ s/.*: //;
+    chomp $health;
+    $self->{'devices'}->{$device}->{'health'} = $health;
+    return;
 }
 
+sub _process_disk_model {
+    my ( $self, $device ) = @_;
+    $self->_validate_param($device);
+    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
+    my ($model) = $smart_output =~ /(Cevice\ Model.*\n)/;
+
+    if ( !defined $model ) {
+        $self->{'devices'}->{$device}->{'model'} = 'N/A';
+        return;
+    }
 
+    $model =~ s/.*:\ //;
+    $model =~ s/^\s+|\s+$//g;    #trim beginning and ending whitepace
+    $self->{'devices'}->{$device}->{'model'} = $model;
+    return;
+}
+
+sub _process_disk_temp {
+    my ( $self, $device ) = @_;
+    $self->_validate_param($device);
+    my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
+    my ($temp_c) = $smart_output =~ /(Temperature_Celsius.*\n)/;
+
+    if ( !defined $temp_c || $smart_output =~ qr/S.M.A.R.T. not available/x ) {
+        $self->{'devices'}->{$device}->{'temp'} = 'N/A';
+        return;
+    }
+
+    chomp($temp_c);
+    $temp_c = substr $temp_c, 83, +3;
+    $temp_c =~ s/ //g;
+
+    my $temp_f = round( ( $temp_c * 9 ) / 5 + 32 );
+    $self->{'devices'}->{$device}->{'temp'} = [ $temp_c, $temp_f ];
+    return;
+}
+
+sub _validate_param {
+    my ( $self, $device ) = @_;
+    croak "$device not found in object, You probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
+    return;
+}
+    
 1;
 
 __END__

+ 12 - 2
t/02-get_functions.t

@@ -1,6 +1,6 @@
 use warnings;
 use strict;
-use Test::More 'tests' => 8;
+use Test::More 'tests' => 12;
 use Disk::SMART;
 
 my $disk  = '/dev/sda';
@@ -8,9 +8,19 @@ my $smart = Disk::SMART->new($disk);
 
 ok( $smart->get_disk_temp($disk), 'get_disk_temp() returns drive temperature or N/A' );
 is( eval{$smart->get_disk_temp('/dev/sdz')}, undef, 'get_disk_temp() returns failure when passed invalid device' );
+
 like( $smart->get_disk_health($disk), qr/PASSED|FAILED|N\/A/, 'get_disk_health() returns health status or N/A' );
 is( eval{$smart->get_disk_health('/dev/sdz')}, undef, 'get_disk_health() returns failure when passed invalid device' );
-like( $smart->get_disk_model($disk),  qr/\w+/,    'get_disk_model() returns disk model or N/A' );
+
+cmp_ok( length $smart->get_disk_model($disk), '>=', 3, 'get_disk_model() returns disk model or N/A' );
 is( eval{$smart->get_disk_errors('/dev/sdz')}, undef, 'get_disk_model() returns failure when passed invalid device' );
+
 like( $smart->get_disk_errors($disk), qr/\w+/, 'get_disk_errors() returns proper string' );
 is( eval{$smart->get_disk_errors('/dev/sdz')}, undef, 'get_disk_errors() returns failure when passed invalid device' );
+
+cmp_ok( scalar( keys $smart->get_disk_attributes($disk) ), '>', 1, 'get_disk_attributes() returns hash of device attributes' );
+is( eval{$smart->get_disk_attributes('/dev/sdz')}, undef, 'get_disk_attributes() returns failure when passed invalid device' );
+
+is( $smart->update_data($disk), 1, 'update_data() updated object with current device data' );
+is( eval{$smart->update_data('/dev/sdz')}, undef, 'update_data() returns falure when passed invalid device' );
+