SMART.pm 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. package Disk::SMART;
  2. use warnings;
  3. use strict;
  4. use Carp;
  5. use Math::Round;
  6. {
  7. $Disk::SMART::VERSION = '0.05'
  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
  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.
  23. =cut
  24. sub new {
  25. my ( $class, @devices ) = @_;
  26. my $self = bless {}, $class;
  27. croak "Valid device identifier not supplied to constructor for $class.\n" if ( !@devices );
  28. croak "smartctl binary was not found on your system, are you running as root?\n" if !-f $smartctl;
  29. foreach my $device (@devices) {
  30. $self->update_data($device);
  31. }
  32. return $self;
  33. }
  34. =head1 USER METHODS
  35. =head2 B<get_disk_temp(DEVICE)>
  36. Returns an array with the temperature of the device in Celsius and Farenheit, or N/A.
  37. C<DEVICE> - Device identifier of SSD / Hard Drive
  38. my ($temp_c, $temp_f) = $smart->get_disk_temp('/dev/sda');
  39. =cut
  40. sub get_disk_temp {
  41. my ( $self, $device ) = @_;
  42. return $self->{'devices'}->{$device}->{'temp'};
  43. }
  44. =head2 B<get_disk_health(DEVICE)>
  45. Returns the health of the disk. Output is "PASSED", "FAILED", or "N/A".
  46. C<DEVICE> - Device identifier of SSD / Hard Drive
  47. my $disk_health = $smart->get_disk_health('/dev/sda');
  48. =cut
  49. sub get_disk_health {
  50. my ( $self, $device ) = @_;
  51. return $self->{'devices'}->{$device}->{'health'};
  52. }
  53. =head2 B<get_disk_model(DEVICE)>
  54. Returns the model of the device. eg. "ST3250410AS".
  55. C<DEVICE> - Device identifier of SSD / Hard Drive
  56. my $disk_model = $smart->get_disk_model('/dev/sda');
  57. =cut
  58. sub get_disk_model {
  59. my ( $self, $device ) = @_;
  60. return $self->{'devices'}->{$device}->{'model'};
  61. }
  62. =head2 B<get_disk_errors(DEVICE)>
  63. Returns scalar of any listed errors
  64. C<DEVICE> - Device identifier of SSD/ Hard Drive
  65. my $disk_errors = $smart->get_disk_errors('/dev/sda');
  66. =cut
  67. sub get_disk_errors {
  68. my ( $self, $device ) = @_;
  69. return $self->{'devices'}->{$device}->{'errors'};
  70. }
  71. =head2 B<get_disk_attributes(DEVICE)>
  72. Returns hash of the SMART disk attributes and values
  73. C<DEVICE> - Device identifier of SSD/ Hard Drive
  74. my %disk_attributes = $smart->get_disk_attributes('/dev/sda');
  75. =cut
  76. sub get_disk_attributes {
  77. my ( $self, $device ) = @_;
  78. return $self->{'devices'}->{$device}->{'attributes'};
  79. }
  80. =head2 B<update_data>
  81. Updates the SMART output and attributes of a device. Returns undef.
  82. C<DEVICE> - Device identifier of SSD/ Hard Drive
  83. $smart->update_data('/dev/sda');
  84. =cut
  85. sub update_data {
  86. my ( $self, $device ) = @_;
  87. chomp( my $out = qx($smartctl -a $device) );
  88. croak "Smartctl couldn't poll device $device\n" if $out =~ /No such device/;
  89. $self->{'devices'}->{$device}->{'SMART_OUTPUT'} = $out;
  90. # update_data() can be called at any time with a device name. Let's check
  91. # the device name given to make sure it matches what was given during
  92. # object construction.
  93. croak "$device not found in object, You probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
  94. $self->_process_disk_attributes($device);
  95. $self->_process_disk_errors($device);
  96. $self->_process_disk_health($device);
  97. $self->_process_disk_model($device);
  98. $self->_process_disk_temp($device);
  99. return 1;
  100. }
  101. sub _process_disk_attributes {
  102. my ( $self, $device ) = @_;
  103. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  104. my ($smart_attributes) = $smart_output =~ /(ID# ATTRIBUTE_NAME.*)\nSMART Error/s;
  105. my @attributes = split /\n/, $smart_attributes;
  106. shift @attributes;
  107. foreach my $attribute (@attributes) {
  108. my $name = substr $attribute, 4, +24;
  109. my $value = substr $attribute, 83, +50;
  110. $name =~ s/\s+$//g; # trim ending whitespace
  111. $value =~ s/^\s+//g; # trim beginning and ending whitepace
  112. $self->{'devices'}->{$device}->{'attributes'}->{$name} = $value;
  113. }
  114. return;
  115. }
  116. sub _process_disk_errors {
  117. my ( $self, $device ) = @_;
  118. $self->_validate_param($device);
  119. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  120. my ($errors) = $smart_output =~ /SMART Error Log Version: [1-9](.*)SMART Self-test log/s;
  121. if ( !defined $errors ) {
  122. $self->{'devices'}->{$device}->{'errors'} = 'N/A';
  123. return;
  124. }
  125. $errors =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  126. $self->{'devices'}->{$device}->{'errors'} = $errors;
  127. return;
  128. }
  129. sub _process_disk_health {
  130. my ( $self, $device ) = @_;
  131. $self->_validate_param($device);
  132. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  133. my ($health) = $smart_output =~ /(SMART overall-health self-assessment.*\n)/;
  134. if ( (!defined $health) or $health !~ /PASSED|FAILED/x ) {
  135. $self->{'devices'}->{$device}->{'health'} = 'N/A';
  136. return;
  137. }
  138. $health =~ s/.*: //;
  139. chomp $health;
  140. $self->{'devices'}->{$device}->{'health'} = $health;
  141. return;
  142. }
  143. sub _process_disk_model {
  144. my ( $self, $device ) = @_;
  145. $self->_validate_param($device);
  146. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  147. my ($model) = $smart_output =~ /(Cevice\ Model.*\n)/;
  148. if ( !defined $model ) {
  149. $self->{'devices'}->{$device}->{'model'} = 'N/A';
  150. return;
  151. }
  152. $model =~ s/.*:\ //;
  153. $model =~ s/^\s+|\s+$//g; #trim beginning and ending whitepace
  154. $self->{'devices'}->{$device}->{'model'} = $model;
  155. return;
  156. }
  157. sub _process_disk_temp {
  158. my ( $self, $device ) = @_;
  159. $self->_validate_param($device);
  160. my $smart_output = $self->{'devices'}->{$device}->{'SMART_OUTPUT'};
  161. my ($temp_c) = $smart_output =~ /(Temperature_Celsius.*\n)/;
  162. if ( !defined $temp_c || $smart_output =~ qr/S.M.A.R.T. not available/x ) {
  163. $self->{'devices'}->{$device}->{'temp'} = 'N/A';
  164. return;
  165. }
  166. chomp($temp_c);
  167. $temp_c = substr $temp_c, 83, +3;
  168. $temp_c =~ s/ //g;
  169. my $temp_f = round( ( $temp_c * 9 ) / 5 + 32 );
  170. $self->{'devices'}->{$device}->{'temp'} = [ $temp_c, $temp_f ];
  171. return;
  172. }
  173. sub _validate_param {
  174. my ( $self, $device ) = @_;
  175. croak "$device not found in object, You probably didn't enter it right" if ( !exists $self->{'devices'}->{$device} );
  176. return;
  177. }
  178. 1;
  179. __END__
  180. =head1 AUTHOR
  181. Paul Trost <ptrost@cpan.org>
  182. =head1 LICENSE AND COPYRIGHT
  183. Copyright 2014 by Paul Trost
  184. 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.
  185. <http://gnu.org/licenses/gpl.html>
  186. =cut