|
|
@@ -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__
|