SMART.pm 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. package Disk::SMART;
  2. use warnings;
  3. use strict;
  4. use Carp;
  5. use Math::Round;
  6. {
  7. $Disk::SMART::VERSION = '0.06'
  8. }
  9. our $smartctl = '/usr/sbin/smartctl';
  10. =head1 NAME
  11. Disk::SMART - Provides an interface to smartctl
  12. =head1 SYNOPSIS
  13. Disk::SMART is an object ooriented module that provides an interface to get SMART disk info from a device as well as initiate testing.
  14. use Disk::SMART;
  15. my $smart = Disk::SMART->new('/dev/sda');
  16. =cut
  17. =head1 CONSTRUCTOR
  18. =head2 B<new(DEVICE)>
  19. Instantiates the Disk::SMART object
  20. C<DEVICE> - Device identifier of SSD / Hard Drive. The constructor takes either a single device name, or an array of device names.
  21. my $smart = Disk::SMART->new( 'dev/sda', '/dev/sdb' );
  22. Returns C<Disk::SMART> object if smartctl is available and can poll the given device(s).
  23. =cut
  24. sub new {
  25. my ( $class, @devices ) = @_;
  26. my $self = bless {}, $class;
  27. my $test_data;
  28. croak "Valid device identifier not supplied to constructor for $class.\n"
  29. if !@devices && !defined $ENV{'TEST_MOCK_DATA'};
  30. croak "smartctl binary was not found on your system, are you running as root?\n"
  31. if !-f $smartctl && !defined $ENV{'TEST_MOCK_DATA'};
  32. $self->update_data($_) foreach @devices;
  33. return $self;
  34. }
  35. =head1 USER METHODS
  36. =head2 B<get_disk_attributes(DEVICE)>
  37. Returns hash of the SMART disk attributes and values
  38. C<DEVICE> - Device identifier of SSD/ Hard Drive
  39. my %disk_attributes = $smart->get_disk_attributes('/dev/sda');
  40. =cut
  41. sub get_disk_attributes {
  42. my ( $self, $device ) = @_;
  43. $self->_validate_param($device);
  44. return $self->{'devices'}->{$device}->{'attributes'};
  45. }
  46. =head2 B<get_disk_errors(DEVICE)>
  47. Returns scalar of any listed errors
  48. C<DEVICE> - Device identifier of SSD/ Hard Drive
  49. my $disk_errors = $smart->get_disk_errors('/dev/sda');
  50. =cut
  51. sub get_disk_errors {
  52. my ( $self, $device ) = @_;
  53. $self->_validate_param($device);
  54. return $self->{'devices'}->{$device}->{'errors'};
  55. }
  56. =head2 B<get_disk_health(DEVICE)>
  57. Returns the health of the disk. Output is "PASSED", "FAILED", or "N/A".
  58. C<DEVICE> - Device identifier of SSD / Hard Drive
  59. my $disk_health = $smart->get_disk_health('/dev/sda');
  60. =cut
  61. sub get_disk_health {
  62. my ( $self, $device ) = @_;
  63. $self->_validate_param($device);
  64. return $self->{'devices'}->{$device}->{'health'};
  65. }
  66. =head2 B<get_disk_model(DEVICE)>
  67. Returns the model of the device. eg. "ST3250410AS".
  68. C<DEVICE> - Device identifier of SSD / Hard Drive
  69. my $disk_model = $smart->get_disk_model('/dev/sda');
  70. =cut
  71. sub get_disk_model {
  72. my ( $self, $device ) = @_;
  73. $self->_validate_param($device);
  74. return $self->{'devices'}->{$device}->{'model'};
  75. }
  76. =head2 B<get_disk_temp(DEVICE)>
  77. Returns an array with the temperature of the device in Celsius and Farenheit, or N/A.
  78. C<DEVICE> - Device identifier of SSD / Hard Drive
  79. my ($temp_c, $temp_f) = $smart->get_disk_temp('/dev/sda');
  80. =cut
  81. sub get_disk_temp {
  82. my ( $self, $device ) = @_;
  83. $self->_validate_param($device);
  84. return @{ $self->{'devices'}->{$device}->{'temp'} };
  85. }
  86. =head2 B<update_data>
  87. Updates the SMART output and attributes of a device. Returns undef.
  88. C<DEVICE> - Device identifier of SSD/ Hard Drive
  89. $smart->update_data('/dev/sda');
  90. =cut
  91. sub update_data {
  92. my ( $self, $device ) = @_;
  93. my $out = ( defined $ENV{'MOCK_TEST_DATA'} ) ? $ENV{'MOCK_TEST_DATA'} : qx($smartctl -a $device);
  94. my $retval = $?;
  95. if ( !$ENV{'MOCK_TEST_DATA'} ) {
  96. croak "Smartctl couldn't poll device $device\n"
  97. if ( $out !~ /START OF INFORMATION SECTION/ );
  98. }
  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. sub _process_disk_attributes {
  109. my ( $self, $device ) = @_;
  110. $self->_validate_param($device);
  111. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  112. my ($smart_attributes) = $smart_output =~ /(ID# ATTRIBUTE_NAME.*)\nSMART Error/s;
  113. my @attributes = split /\n/, $smart_attributes;
  114. shift @attributes;
  115. foreach my $attribute (@attributes) {
  116. my $name = substr $attribute, 4, +24;
  117. my $value = substr $attribute, 83, +50;
  118. $name =~ s/\s+$//g; # trim ending whitespace
  119. $value =~ s/^\s+//g; # trim beginning and ending whitepace
  120. $self->{'devices'}->{$device}->{'attributes'}->{$name} = $value;
  121. }
  122. return;
  123. }
  124. sub _process_disk_errors {
  125. my ( $self, $device ) = @_;
  126. $self->_validate_param($device);
  127. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  128. my ($errors) = $smart_output =~ /SMART Error Log Version: [1-9](.*)SMART Self-test log/s;
  129. $errors =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  130. $errors = 'N/A' if !$errors;
  131. return $self->{'devices'}->{$device}->{'errors'} = $errors;
  132. }
  133. sub _process_disk_health {
  134. my ( $self, $device ) = @_;
  135. $self->_validate_param($device);
  136. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  137. my ($health) = $smart_output =~ /SMART overall-health self-assessment test result:(.*)\n/;
  138. $health =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  139. $health = 'N/A' if !$health or $health !~ /PASSED|FAILED/x;
  140. return $self->{'devices'}->{$device}->{'health'} = $health;
  141. }
  142. sub _process_disk_model {
  143. my ( $self, $device ) = @_;
  144. $self->_validate_param($device);
  145. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  146. my ($model) = $smart_output =~ /Device\ Model:(.*)\n/;
  147. $model =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  148. $model = 'N/A' if !$model;
  149. return $self->{'devices'}->{$device}->{'model'} = $model;
  150. }
  151. sub _process_disk_temp {
  152. my ( $self, $device ) = @_;
  153. $self->_validate_param($device);
  154. my ( $temp_c, $temp_f );
  155. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  156. ($temp_c) = $smart_output =~ /(Temperature_Celsius.*\n)/;
  157. if ($temp_c) {
  158. $temp_c = substr $temp_c, 83, +3;
  159. $temp_c =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  160. $temp_f = round( ( $temp_c * 9 ) / 5 + 32 );
  161. $temp_c = int $temp_c;
  162. $temp_f = int $temp_f;
  163. }
  164. else {
  165. $temp_c = 'N/A';
  166. $temp_f = 'N/A';
  167. }
  168. return $self->{'devices'}->{$device}->{'temp'} = [ ( $temp_c, $temp_f ) ];
  169. }
  170. sub _validate_param {
  171. my ( $self, $device ) = @_;
  172. croak "$device not found in object. Verify you specified the right device identifier.\n" if ( !exists $self->{'devices'}->{$device} );
  173. return;
  174. }
  175. 1;
  176. __END__
  177. =head1 AUTHOR
  178. Paul Trost <ptrost@cpan.org>
  179. =head1 LICENSE AND COPYRIGHT
  180. Copyright 2014 by Paul Trost
  181. 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.
  182. <http://gnu.org/licenses/gpl.html>
  183. =cut