SMART.pm 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. package Disk::SMART;
  2. use warnings;
  3. use strict;
  4. use Carp;
  5. use Math::Round;
  6. {
  7. $Disk::SMART::VERSION = '0.10'
  8. }
  9. our $smartctl = qx(which smartctl);
  10. chomp($smartctl);
  11. =head1 NAME
  12. Disk::SMART - Provides an interface to smartctl to return disk stats and to run tests.
  13. =head1 SYNOPSIS
  14. Disk::SMART is an object oriented module that provides an interface to get SMART disk info from a device as well as initiate testing.
  15. use Disk::SMART;
  16. my $smart = Disk::SMART->new('/dev/sda', '/dev/sdb');
  17. my $disk_health = $smart->get_disk_health('/dev/sda');
  18. =cut
  19. =head1 CONSTRUCTOR
  20. =head2 B<new(DEVICE)>
  21. Instantiates the Disk::SMART object
  22. C<DEVICE> - Device identifier of SSD / Hard Drive. The constructor takes either a single device name, or an array of device names.
  23. my $smart = Disk::SMART->new( 'dev/sda', '/dev/sdb' );
  24. Returns C<Disk::SMART> object if smartctl is available and can poll the given device(s).
  25. =cut
  26. sub new {
  27. my ( $class, @devices ) = @_;
  28. my $self = bless {}, $class;
  29. croak "Valid device identifier not supplied to constructor for $class.\n"
  30. if !@devices && !defined $ENV{'MOCK_TEST_DATA'};
  31. croak "smartctl binary was not found on your system, are you running as root?\n"
  32. if !-f $smartctl && !defined $ENV{'MOCK_TEST_DATA'};
  33. $self->update_data($_) foreach @devices;
  34. return $self;
  35. }
  36. =head1 USER METHODS
  37. =head2 B<get_disk_attributes(DEVICE)>
  38. Returns hash of the SMART disk attributes and values
  39. C<DEVICE> - Device identifier of SSD/ Hard Drive
  40. my %disk_attributes = $smart->get_disk_attributes('/dev/sda');
  41. =cut
  42. sub get_disk_attributes {
  43. my ( $self, $device ) = @_;
  44. $self->_validate_param($device);
  45. return %{ $self->{'devices'}->{$device}->{'attributes'} };
  46. }
  47. =head2 B<get_disk_errors(DEVICE)>
  48. Returns scalar of any listed errors
  49. C<DEVICE> - Device identifier of SSD/ Hard Drive
  50. my $disk_errors = $smart->get_disk_errors('/dev/sda');
  51. =cut
  52. sub get_disk_errors {
  53. my ( $self, $device ) = @_;
  54. $self->_validate_param($device);
  55. return $self->{'devices'}->{$device}->{'errors'};
  56. }
  57. =head2 B<get_disk_health(DEVICE)>
  58. Returns the health of the disk. Output is "PASSED", "FAILED", or "N/A".
  59. C<DEVICE> - Device identifier of SSD / Hard Drive
  60. my $disk_health = $smart->get_disk_health('/dev/sda');
  61. =cut
  62. sub get_disk_health {
  63. my ( $self, $device ) = @_;
  64. $self->_validate_param($device);
  65. return $self->{'devices'}->{$device}->{'health'};
  66. }
  67. =head2 B<get_disk_model(DEVICE)>
  68. Returns the model of the device. eg. "ST3250410AS".
  69. C<DEVICE> - Device identifier of SSD / Hard Drive
  70. my $disk_model = $smart->get_disk_model('/dev/sda');
  71. =cut
  72. sub get_disk_model {
  73. my ( $self, $device ) = @_;
  74. $self->_validate_param($device);
  75. return $self->{'devices'}->{$device}->{'model'};
  76. }
  77. =head2 B<get_disk_temp(DEVICE)>
  78. Returns an array with the temperature of the device in Celsius and Farenheit, or N/A.
  79. C<DEVICE> - Device identifier of SSD / Hard Drive
  80. my ($temp_c, $temp_f) = $smart->get_disk_temp('/dev/sda');
  81. =cut
  82. sub get_disk_temp {
  83. my ( $self, $device ) = @_;
  84. $self->_validate_param($device);
  85. return @{ $self->{'devices'}->{$device}->{'temp'} };
  86. }
  87. =head2 B<update_data(DEVICE)>
  88. Updates the SMART output and attributes of a device. Returns undef.
  89. C<DEVICE> - Device identifier of SSD/ Hard Drive
  90. $smart->update_data('/dev/sda');
  91. =cut
  92. sub update_data {
  93. my ( $self, $device ) = @_;
  94. my $out;
  95. $out = $ENV{'MOCK_TEST_DATA'} if defined $ENV{'MOCK_TEST_DATA'};
  96. $out = qx($smartctl -a $device) if ( !defined $ENV{'MOCK_TEST_DATA'} && -f $smartctl );
  97. croak "Smartctl couldn't poll device $device\n"
  98. if ( !$out || $out !~ /START OF INFORMATION SECTION/ );
  99. chomp($out);
  100. $self->{'devices'}->{$device}->{'SMART_OUTPUT'} = $out;
  101. $self->_process_disk_attributes($device);
  102. $self->_process_disk_errors($device);
  103. $self->_process_disk_health($device);
  104. $self->_process_disk_model($device);
  105. $self->_process_disk_temp($device);
  106. return 1;
  107. }
  108. =head2 B<run_short_test(DEVICE)>
  109. Runs the SMART short self test and returns the result.
  110. C<DEVICE> - Device identifier of SSD/ Hard Drive
  111. $smart->run_short_test('/dev/sda');
  112. =cut
  113. sub run_short_test {
  114. my ( $self, $device ) = @_;
  115. $self->_validate_param($device);
  116. if ( !defined $ENV{'MOCK_TEST_DATA'} ) {
  117. my $out = qx( $smartctl -t short $device );
  118. my ($short_test_time) = $out =~ /Please wait (.*) minutes/s;
  119. sleep( $short_test_time * 60 );
  120. }
  121. my $smart_output = ( defined $ENV{'MOCK_TEST_DATA'} ) ? $ENV{'MOCK_TEST_DATA'} : qx($smartctl -a $device);
  122. ($smart_output) = $smart_output =~ /(SMART Self-test log.*)\nSMART Selective self-test/s;
  123. my @device_tests = split /\n/, $smart_output;
  124. my $short_test_number = $device_tests[2];
  125. my $short_test_status = substr $short_test_number, 25, +30;
  126. $short_test_status =~ s/\s+$//g; #trim beginning and ending whitepace
  127. return $short_test_status;
  128. }
  129. ##INTERNAL FUNCTIONS BELOW. THESE SHOULD NEVER BE CALLED BY A SCRIPT##
  130. sub _process_disk_attributes {
  131. my ( $self, $device ) = @_;
  132. $self->_validate_param($device);
  133. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  134. my ($smart_attributes) = $smart_output =~ /(ID# ATTRIBUTE_NAME.*)\nSMART Error/s;
  135. my @attributes = split /\n/, $smart_attributes;
  136. shift @attributes;
  137. foreach my $attribute (@attributes) {
  138. my $name = substr $attribute, 4, +24;
  139. my $value = substr $attribute, 83, +50;
  140. $name =~ s/\s+$//g; # trim ending whitespace
  141. $value =~ s/^\s+//g; # trim beginning and ending whitepace
  142. $self->{'devices'}->{$device}->{'attributes'}->{$name} = $value;
  143. }
  144. return;
  145. }
  146. sub _process_disk_errors {
  147. my ( $self, $device ) = @_;
  148. $self->_validate_param($device);
  149. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  150. my ($errors) = $smart_output =~ /SMART Error Log Version: [1-9](.*)SMART Self-test log/s;
  151. $errors =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  152. $errors = 'N/A' if !$errors;
  153. return $self->{'devices'}->{$device}->{'errors'} = $errors;
  154. }
  155. sub _process_disk_health {
  156. my ( $self, $device ) = @_;
  157. $self->_validate_param($device);
  158. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  159. my ($health) = $smart_output =~ /SMART overall-health self-assessment test result:(.*)\n/;
  160. $health =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  161. $health = 'N/A' if !$health || $health !~ /PASSED|FAILED/x;
  162. return $self->{'devices'}->{$device}->{'health'} = $health;
  163. }
  164. sub _process_disk_model {
  165. my ( $self, $device ) = @_;
  166. $self->_validate_param($device);
  167. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  168. my ($model) = $smart_output =~ /Device\ Model:(.*)\n/;
  169. $model =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  170. $model = 'N/A' if !$model;
  171. return $self->{'devices'}->{$device}->{'model'} = $model;
  172. }
  173. sub _process_disk_temp {
  174. my ( $self, $device ) = @_;
  175. $self->_validate_param($device);
  176. my ( $temp_c, $temp_f );
  177. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  178. ($temp_c) = $smart_output =~ /(Temperature_Celsius.*\n|Airflow_Temperature_Cel.*\n)/;
  179. if ($temp_c) {
  180. $temp_c = substr $temp_c, 83, +3;
  181. $temp_c =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  182. $temp_f = round( ( $temp_c * 9 ) / 5 + 32 );
  183. $temp_c = int $temp_c;
  184. $temp_f = int $temp_f;
  185. }
  186. else {
  187. $temp_c = 'N/A';
  188. $temp_f = 'N/A';
  189. }
  190. return $self->{'devices'}->{$device}->{'temp'} = [ ( $temp_c, $temp_f ) ];
  191. }
  192. sub _validate_param {
  193. my ( $self, $device ) = @_;
  194. croak "$device not found in object. Verify you specified the right device identifier.\n"
  195. if ( !exists $self->{'devices'}->{$device} );
  196. return;
  197. }
  198. 1;
  199. __END__
  200. =head1 COMPATIBILITY
  201. This module should run on any UNIX like OS with Perl 5.10+ and the smartctl progam installed from the smartmontools package.
  202. =head1 AUTHOR
  203. Paul Trost <ptrost@cpan.org>
  204. =head1 LICENSE AND COPYRIGHT
  205. Copyright 2014 by Paul Trost
  206. This script is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License v2, or at your option any later version.
  207. <http://gnu.org/licenses/gpl.html>
  208. =cut