Skip to content

Commit

Permalink
NVMe Support in SMART Stats module (#895)
Browse files Browse the repository at this point in the history
* NVMe-Support

Includes migration, fixes to script logic, enables SMART on drives
before data is gathered, fixes blank keys not being nulled

* Fixed missing columns

* Updated listing

* Updated ReadMe

* Add failing badge to client tab
  • Loading branch information
tuxudo authored and bochoven committed Nov 22, 2017
1 parent 904e3ac commit ed8a721
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 40 deletions.
4 changes: 2 additions & 2 deletions app/modules/smart_stats/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ All other table columns correspond with SMART attributes.

## Dependency

smart_stats module required smartctl, a part of smartmontools: [https://www.smartmontools.org/](https://www.smartmontools.org/)
smart_stats module requires smartctl, a part of smartmontools: [https://www.smartmontools.org/](https://www.smartmontools.org/)

Download the latest version of smartmontools for macOS from the official repository here: [https://sourceforge.net/projects/smartmontools/files/latest/download?source=files](https://sourceforge.net/projects/smartmontools/files/latest/download?source=files)

Expand All @@ -37,4 +37,4 @@ The downloaded smartmontools package has a name that conflicts with Munki's vers

## Notes

Because smartmontools does not yet fully support NVMe drives on macOS, this module does not support NVMe drives.
Starting with smartmontools 6.6 (released October of 2017), the SMART Stats module supports NVMe drives. NVMe drives will not fully appear in the SMART Stats listing, but all available data is in the client tab.
27 changes: 26 additions & 1 deletion app/modules/smart_stats/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,15 @@
"lifetime_reads_gib": "Lifetime Reads GiB",
"uncorrectable_error_cnt": "Uncorrectable Error Count",
"ecc_error_rate": "ECC Error Rate",
"ecc_error_eate": "ECC Error Rate",
"crc_error_count": "CRC Error Count",
"supercap_status": "Supercap Status",
"exception_mode_status": "Exception Mode Status",
"por_recovery_count": "Power Recovery Count",
"total_reads_gib": "Total Reads GiB",
"total_writes_gib": "Total Writes GiB",
"thermal_throttle": "Thermal Throttle",
"perc_writeerase_Count": "Percent Write Erase Count",
"perc_writeerase_count": "Percent Write Erase Count",
"perc_avail_resrvd_space": "Percent Available Reserved Space",
"perc_writeerase_ct_bc": "Percent Write Erase Count Blocks",
"sata_phy_error": "SATA PHY Error",
Expand All @@ -119,5 +120,29 @@
"smart_support_is": "SMART Enabled",
"smart_is": "SMART Status",
"overall_health": "Overall Health",
"model_number": "Model Number",
"pci_vender_subsystem_id": "PCI Vendor/Subsystem ID",
"critical_warning": "Critical Warning",
"available_spare": "Available Spare",
"available_spare_threshold": "Available Spare Threshold",
"percentage_used": "Percentage Used",
"data_units_read": "Data Units Read",
"data_units_written": "Data Units Written",
"host_read_commands": "Host Read Commands",
"host_write_commands": "Host Write Commands",
"controller_busy_time": "Controller Busy Time",
"unsafe_shutdowns": "Unsafe Shutdowns",
"media_data_integrity_errors": "Media Data Integrity Errors",
"error_info_log_entries": "Error Information Log Entries",
"ieee_oui_id": "IEEE OUI ID",
"controller_id": "Controller ID",
"number_of_namespaces": "Number of Namespaces",
"firmware_updates": "Firmware Updates",
"optional_admin_commands": "Optional Admin Commands",
"optional_nvm_commands": "Options NVM Commands",
"max_data_transfer_size": "Maximum Data Transfer Size",
"temperature_nvme": "Temperature",
"power_on_hours_nvme": "Power on Hours",
"power_cycle_count_nvme": "Power Cycle Count",
"passed": "Passed"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<?php

class Migration_smart_stats_add_nvme_columns extends \Model
{
protected $new_columns = array(
'model_number' => 'VARCHAR(255)',
'pci_vender_subsystem_id' => 'VARCHAR(255)',
'temperature_nvme' => 'BIGINT',
'power_on_hours_nvme' => 'BIGINT',
'power_cycle_count_nvme' => 'BIGINT',
'critical_warning' => 'VARCHAR(255)',
'available_spare' => 'BIGINT',
'available_spare_threshold' => 'BIGINT',
'percentage_used' => 'BIGINT',
'data_units_read' => 'VARCHAR(255)',
'data_units_written' => 'VARCHAR(255)',
'host_read_commands' => 'BIGINT',
'host_write_commands' => 'BIGINT',
'controller_busy_time' => 'BIGINT',
'unsafe_shutdowns' => 'BIGINT',
'media_data_integrity_errors' => 'BIGINT',
'error_info_log_entries' => 'BIGINT',
'ieee_oui_id' => 'VARCHAR(255)',
'controller_id' => 'BIGINT',
'number_of_namespaces' => 'BIGINT',
'firmware_updates' => 'VARCHAR(255)',
'optional_admin_commands' => 'TEXT',
'optional_nvm_commands' => 'TEXT',
'max_data_transfer_size' => 'VARCHAR(255)',
);

public function __construct()
{
parent::__construct();
$this->tablename = 'smart_stats';
}

public function up()
{
// Get database handle
$dbh = $this->getdbh();

// Wrap in transaction
$dbh->beginTransaction();

// Adding a column is simple...
foreach ($this->new_columns as $column => $type) {
$sql = "ALTER TABLE smart_stats ADD COLUMN $column $type";
$this->exec($sql);
}

$dbh->commit();
}

public function down()
{
// Can't go back. Just like we can't go back to the 1964 New York World's Fair and ride the Greyhound people movers :/
}
}
2 changes: 1 addition & 1 deletion app/modules/smart_stats/provides.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

return array(
'client_tabs' => array(
'smart_stats-tab' => array('view' => 'smart_stats_tab', 'i18n' => 'smart_stats.clienttabtitle'),
'smart_stats-tab' => array('view' => 'smart_stats_tab', 'i18n' => 'smart_stats.clienttabtitle', 'badge' => 'smart_stats-cnt'),
),
'listings' => array(
'smart_stats' => array('view' => 'smart_stats_listing', 'i18n' => 'smart_stats.clienttabtitle'),
Expand Down
25 changes: 18 additions & 7 deletions app/modules/smart_stats/scripts/smart_stats.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,22 +39,33 @@ if [[ "$($SMARTCTL -i /dev/disk${DISK})" == *"Smartctl open device: /dev/disk"*
continue
fi

SMARTATTRIBUTES=$(${SMARTCTL} -A /dev/disk${DISK} | tail -n +8 | awk '{print $2, $10}' | cut -d '/' -f-1 | sed -e 's/#//' -e 's/\\/-/g' -e 's/\///g' -e 's/UDMA_CRC_Error_Count/UDMA_Error_Count/g' -e s'/(//g' -e 's/)//g')
SMARTINFO=$($SMARTCTL -i /dev/disk${DISK} | tail -n +5 | sed -e '1,/SMART support is: / s/SMART support is: /SMART is: /g')
SMARTERRORS=$($SMARTCTL -l error /dev/disk${DISK} | grep -e 'occurred at disk power-on lifetime' | awk {'print $1"PoH- "$8'} | head -n1)
SMARTERRORCOUNT=$($SMARTCTL -l error /dev/disk${DISK} | grep -e 'ATA Error Count: ' | awk {'print "error_count "$4'})
SMARTHEALTH=$($SMARTCTL -H /dev/disk${DISK} | awk -F: '/SMART overall-health self-assessment test result/ {$1=""; print "Overall_Health: " $0}')
if [[ "$($SMARTCTL -A /dev/disk${DISK})" == *"NVMe"* ]] ; then
# Run special attributes for NVMe drives
SMARTATTRIBUTES=$(${SMARTCTL} -A /dev/disk${DISK} | tail -n +6 | cut -d '/' -f-1 | sed -e 's/#//' -e 's/\\/-/g' -e 's/\///g' -e 's/UDMA_CRC_Error_Count/UDMA_Error_Count/g' -e s'/(//g' -e 's/)//g' -e 's/ Celsius//g')
# NVMe drives does yet support error logs, skip that check to save time
SMARTERRORCOUNT=""
SMARTERRORS=""
else
SMARTATTRIBUTES=$(${SMARTCTL} -s on -A /dev/disk${DISK} | tail -n +11 | awk '{print $2, $10}' | cut -d '/' -f-1 | sed -e 's/#//' -e 's/\\/-/g' -e 's/\///g' -e 's/UDMA_CRC_Error_Count/UDMA_Error_Count/g' -e s'/(//g' -e 's/)//g')
SMARTERRORCOUNT=$($SMARTCTL -s on -l error /dev/disk${DISK} | grep -e 'ATA Error Count: ' | awk {'print "error_count "$4'})
SMARTERRORS=$($SMARTCTL -s on -l error /dev/disk${DISK} | grep -e 'occurred at disk power-on lifetime' | awk {'print $1"PoH- "$8'} | head -n1)

fi

SMARTINFO=$($SMARTCTL -s on -i /dev/disk${DISK} | tail -n +5 | sed -e 's./.-.g' -e '1,/SMART support is: / s/SMART support is: /SMART is: /g')
SMARTHEALTH=$($SMARTCTL -s on -H /dev/disk${DISK} | awk -F: '/SMART overall-health self-assessment test result/ {$1=""; print "Overall_Health: " $0}')
DISKPROPER=$(echo $DISK | tr -cd '[[:alnum:]]')

SMARTSTATS="${nl}${SMARTATTRIBUTES}${nl}${SMARTINFO}${nl}${SMARTERRORS}${nl}${SMARTERRORCOUNT}${nl}${SMARTHEALTH}${nl}Disk Number: ${DISKPROPER}"

# Make a dict for the drive in the PLIST file
/usr/libexec/PlistBuddy -c "add :${DISKARRAY} dict" "${smart_stats_file}" 1>/dev/null

for TRANSLATE in 'Model Family: ' 'Device Model: ' 'Serial Number: ' 'LU WWN Device Id: ' 'Firmware Version: ' 'User Capacity: ' 'Sector Size: ' 'Rotation Rate: ' 'Form Factor: ' 'Device is: ' 'ATA Version is: ' 'SATA Version is: ' 'SMART is: ' 'SMART support is: ' 'ErrorPoH- ' 'error_count ' 'Disk Number: ' 'Raw_Read_Error_Rate ' 'Throughput_Performance ' 'Spin_Up_Time ' 'Start_Stop_Count ' 'Reallocated_Sector_Ct ' 'Read_Channel_Margin ' 'Seek_Error_Rate ' 'Seek_Time_Performance ' 'Power_On_Hours ' 'Spin_Retry_Count ' 'Calibration_Retry_Count ' 'Power_Cycle_Count ' 'Read_Soft_Error_Rate ' 'Program_Fail_Count_Chip ' 'Erase_Fail_Count_Chip ' 'Wear_Leveling_Count ' 'Used_Rsvd_Blk_Cnt_Chip ' 'Used_Rsvd_Blk_Cnt_Tot ' 'Unused_Rsvd_Blk_Cnt_Tot ' 'Program_Fail_Cnt_Total ' 'Erase_Fail_Count_Total ' 'Runtime_Bad_Block ' 'End-to-End_Error ' 'Reported_Uncorrect ' 'Command_Timeout ' 'High_Fly_Writes ' 'Airflow_Temperature_Cel ' 'G-Sense_Error_Rate ' 'Power-Off_Retract_Count ' 'Load_Cycle_Count ' 'Temperature_Celsius ' 'Hardware_ECC_Recovered ' 'Reallocated_Event_Count ' 'Current_Pending_Sector ' 'Offline_Uncorrectable ' 'UDMA_Error_Count ' 'Multi_Zone_Error_Rate ' 'Soft_Read_Error_Rate ' 'Data_Address_Mark_Errs ' 'Run_Out_Cancel ' 'Soft_ECC_Correction ' 'Thermal_Asperity_Rate ' 'Flying_Height ' 'Spin_High_Current ' 'Spin_Buzz ' 'Offline_Seek_Performnce ' 'Disk_Shift ' 'Loaded_Hours ' 'Power_On_Hours_and_Msec ' 'Load_Retry_Count ' 'Load_Friction ' 'Load-in_Time ' 'Torq-amp_Count ' 'Head_Amplitude ' 'Available_Reservd_Space ' 'Media_Wearout_Indicator ' 'Head_Flying_Hours ' 'Total_LBAs_Written ' 'Total_LBAs_Read ' 'Read_Error_Retry_Rate ' 'Host_Reads_MiB ' 'Host_Writes_MiB ' 'Grown_Failing_Block_Ct ' 'Unexpect_Power_Loss_Ct ' 'Non4k_Aligned_Access ' 'SATA_Iface_Downshift ' 'Factory_Bad_Block_Ct ' 'Percent_Lifetime_Used ' 'Write_Error_Rate ' 'Success_RAIN_Recov_Cnt ' 'Total_Host_Sector_Write ' 'Host_Program_Page_Count ' 'Bckgnd_Program_Page_Cnt ' 'Perc_Rated_Life_Used ' 'Reallocate_NAND_Blk_Cnt ' 'Ave_Block-Erase_Count ' 'Unused_Reserve_NAND_Blk ' 'SATA_Interfac_Downshift ' 'SSD_Life_Left ' 'Life_Curve_Status ' 'SuperCap_Health ' 'Lifetime_Writes_GiB ' 'Lifetime_Reads_GiB ' 'Uncorrectable_Error_Cnt ' 'ECC_Error_Rate ' 'CRC_Error_Count ' 'Supercap_Status ' 'Exception_Mode_Status ' 'POR_Recovery_Count ' 'Total_Reads_GiB ' 'Total_Writes_GiB ' 'Thermal_Throttle ' 'Perc_Write-Erase_Count ' 'Perc_Avail_Resrvd_Space ' 'Perc_Write-Erase_Ct_BC ' 'SATA_PHY_Error ' 'Avg_Write-Erase_Count ' 'Free_Fall_Sensor ' 'Overall_Health: '
for TRANSLATE in 'Model Family: ' 'Device Model: ' 'Serial Number: ' 'LU WWN Device Id: ' 'Firmware Version: ' 'User Capacity: ' 'Sector Size: ' 'Rotation Rate: ' 'Form Factor: ' 'Device is: ' 'ATA Version is: ' 'SATA Version is: ' 'SMART is: ' 'SMART support is: ' 'ErrorPoH- ' 'error_count ' 'Disk Number: ' 'Raw_Read_Error_Rate ' 'Throughput_Performance ' 'Spin_Up_Time ' 'Start_Stop_Count ' 'Reallocated_Sector_Ct ' 'Read_Channel_Margin ' 'Seek_Error_Rate ' 'Seek_Time_Performance ' 'Power_On_Hours ' 'Spin_Retry_Count ' 'Calibration_Retry_Count ' 'Power_Cycle_Count ' 'Read_Soft_Error_Rate ' 'Program_Fail_Count_Chip ' 'Erase_Fail_Count_Chip ' 'Wear_Leveling_Count ' 'Used_Rsvd_Blk_Cnt_Chip ' 'Used_Rsvd_Blk_Cnt_Tot ' 'Unused_Rsvd_Blk_Cnt_Tot ' 'Program_Fail_Cnt_Total ' 'Erase_Fail_Count_Total ' 'Runtime_Bad_Block ' 'End-to-End_Error ' 'Reported_Uncorrect ' 'Command_Timeout ' 'High_Fly_Writes ' 'Airflow_Temperature_Cel ' 'G-Sense_Error_Rate ' 'Power-Off_Retract_Count ' 'Load_Cycle_Count ' 'Temperature_Celsius ' 'Hardware_ECC_Recovered ' 'Reallocated_Event_Count ' 'Current_Pending_Sector ' 'Offline_Uncorrectable ' 'UDMA_Error_Count ' 'Multi_Zone_Error_Rate ' 'Soft_Read_Error_Rate ' 'Data_Address_Mark_Errs ' 'Run_Out_Cancel ' 'Soft_ECC_Correction ' 'Thermal_Asperity_Rate ' 'Flying_Height ' 'Spin_High_Current ' 'Spin_Buzz ' 'Offline_Seek_Performnce ' 'Disk_Shift ' 'Loaded_Hours ' 'Power_On_Hours_and_Msec ' 'Load_Retry_Count ' 'Load_Friction ' 'Load-in_Time ' 'Torq-amp_Count ' 'Head_Amplitude ' 'Available_Reservd_Space ' 'Media_Wearout_Indicator ' 'Head_Flying_Hours ' 'Total_LBAs_Written ' 'Total_LBAs_Read ' 'Read_Error_Retry_Rate ' 'Host_Reads_MiB ' 'Host_Writes_MiB ' 'Grown_Failing_Block_Ct ' 'Unexpect_Power_Loss_Ct ' 'Non4k_Aligned_Access ' 'SATA_Iface_Downshift ' 'Factory_Bad_Block_Ct ' 'Percent_Lifetime_Used ' 'Write_Error_Rate ' 'Success_RAIN_Recov_Cnt ' 'Total_Host_Sector_Write ' 'Host_Program_Page_Count ' 'Bckgnd_Program_Page_Cnt ' 'Perc_Rated_Life_Used ' 'Reallocate_NAND_Blk_Cnt ' 'Ave_Block-Erase_Count ' 'Unused_Reserve_NAND_Blk ' 'SATA_Interfac_Downshift ' 'SSD_Life_Left ' 'Life_Curve_Status ' 'SuperCap_Health ' 'Lifetime_Writes_GiB ' 'Lifetime_Reads_GiB ' 'Uncorrectable_Error_Cnt ' 'ECC_Error_Rate ' 'CRC_Error_Count ' 'Supercap_Status ' 'Exception_Mode_Status ' 'POR_Recovery_Count ' 'Total_Reads_GiB ' 'Total_Writes_GiB ' 'Thermal_Throttle ' 'Perc_Write-Erase_Count ' 'Perc_Avail_Resrvd_Space ' 'Perc_Write-Erase_Ct_BC ' 'SATA_PHY_Error ' 'Avg_Write-Erase_Count ' 'Free_Fall_Sensor ' 'Overall_Health: ' 'Critical Warning: ' 'Temperature: ' 'Available Spare: ' 'Available Spare Threshold: ' 'Percentage Used: ' 'Data Units Read: ' 'Data Units Written: ' 'Host Read Commands: ' 'Host Write Commands: ' 'Controller Busy Time: ' 'Power Cycles: ' 'Power On Hours: ' 'Unsafe Shutdowns: ' 'Media and Data Integrity Errors: ' 'Error Information Log Entries: ' 'Model Number: ' 'PCI Vendor-Subsystem ID: ' 'IEEE OUI Identifier: ' 'Controller ID: ' 'Number of Namespaces: ' 'Firmware Updates (0x06): ' 'Optional Admin Commands (0x0006): ' 'Optional NVM Commands (0x001f): ' 'Maximum Data Transfer Size: '
do

OUTKEY=$(awk -F' ' '{printf "%s%s\n", $1, $2}' <<< "${TRANSLATE}" | tr -cd '[[:alnum:]]._' )
#OUTKEY=$(awk -F' ' '{printf "%s%s\n", $1, $2}' <<< "${TRANSLATE}" | tr -cd '[[:alnum:]]._' )
OUTKEY=$(tr -cd '[[:alnum:]]._' <<< "${TRANSLATE}" )
OUTVALUE=$(grep -e "${TRANSLATE}" <<< "${SMARTSTATS}" | sed -e "s/${TRANSLATE}//g" | tr -d '\r' | tr -d '\n' )
/usr/libexec/PlistBuddy -c "add ${DISKARRAY}:${OUTKEY} string ${OUTVALUE}" "${smart_stats_file}" 1>/dev/null

Expand Down
Loading

0 comments on commit ed8a721

Please sign in to comment.