From ed8a7215672fe19c9c713414d1018ded501afc56 Mon Sep 17 00:00:00 2001 From: tuxudo Date: Wed, 22 Nov 2017 03:44:08 -0500 Subject: [PATCH] NVMe Support in SMART Stats module (#895) * 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 --- app/modules/smart_stats/README.md | 4 +- app/modules/smart_stats/locales/en.json | 27 +++++- .../004_smart_stats_add_nvme_columns.php | 59 +++++++++++++ app/modules/smart_stats/provides.php | 2 +- .../smart_stats/scripts/smart_stats.sh | 25 ++++-- app/modules/smart_stats/smart_stats_model.php | 87 +++++++++++++++---- .../smart_stats/views/smart_stats_listing.php | 14 +-- .../smart_stats/views/smart_stats_tab.php | 17 +++- 8 files changed, 195 insertions(+), 40 deletions(-) create mode 100644 app/modules/smart_stats/migrations/004_smart_stats_add_nvme_columns.php diff --git a/app/modules/smart_stats/README.md b/app/modules/smart_stats/README.md index eed4ac21e..1dae1ba5a 100755 --- a/app/modules/smart_stats/README.md +++ b/app/modules/smart_stats/README.md @@ -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) @@ -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. diff --git a/app/modules/smart_stats/locales/en.json b/app/modules/smart_stats/locales/en.json index 7098153e8..50e5da0ed 100644 --- a/app/modules/smart_stats/locales/en.json +++ b/app/modules/smart_stats/locales/en.json @@ -89,6 +89,7 @@ "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", @@ -96,7 +97,7 @@ "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", @@ -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" } diff --git a/app/modules/smart_stats/migrations/004_smart_stats_add_nvme_columns.php b/app/modules/smart_stats/migrations/004_smart_stats_add_nvme_columns.php new file mode 100644 index 000000000..4c159e3a8 --- /dev/null +++ b/app/modules/smart_stats/migrations/004_smart_stats_add_nvme_columns.php @@ -0,0 +1,59 @@ + '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 :/ + } +} diff --git a/app/modules/smart_stats/provides.php b/app/modules/smart_stats/provides.php index 480107049..c447e5b5d 100644 --- a/app/modules/smart_stats/provides.php +++ b/app/modules/smart_stats/provides.php @@ -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'), diff --git a/app/modules/smart_stats/scripts/smart_stats.sh b/app/modules/smart_stats/scripts/smart_stats.sh index 12a61b980..01dfa310d 100755 --- a/app/modules/smart_stats/scripts/smart_stats.sh +++ b/app/modules/smart_stats/scripts/smart_stats.sh @@ -39,11 +39,21 @@ 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}" @@ -51,10 +61,11 @@ SMARTSTATS="${nl}${SMARTATTRIBUTES}${nl}${SMARTINFO}${nl}${SMARTERRORS}${nl}${SM # 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 diff --git a/app/modules/smart_stats/smart_stats_model.php b/app/modules/smart_stats/smart_stats_model.php index c9759361c..99dbfaf59 100644 --- a/app/modules/smart_stats/smart_stats_model.php +++ b/app/modules/smart_stats/smart_stats_model.php @@ -6,7 +6,7 @@ class Smart_stats_model extends \Model { function __construct($serial='') { - parent::__construct('id', 'smart_stats'); //primary key, tablename + parent::__construct('id', 'smart_stats'); //primary key, tablename $this->rs['id'] = 0; $this->rs['serial_number'] = $serial; $this->rs['disk_number'] = 0; @@ -102,12 +102,12 @@ function __construct($serial='') $this->rs['host_program_page_count'] = 0; $this->rt['host_program_page_count'] = 'BIGINT'; $this->rs['bckgnd_program_page_cnt'] = 0; $this->rt['bckgnd_program_page_cnt'] = 'BIGINT'; $this->rs['perc_rated_life_used'] = 0; $this->rt['perc_rated_life_used'] = 'BIGINT'; - $this->rs['reallocate_nand_blk_cnt'] = 0; $this->rt['reallocate_nand_blk_cnt'] = 'BIGINT'; - $this->rs['ave_blockerase_count'] = 0; $this->rt['ave_blockerase_count'] = 'BIGINT'; - $this->rs['Unused_Reserve_NAND_Blk'] = 0; $this->rt['Unused_Reserve_NAND_Blk'] = 'BIGINT'; - $this->rs['sata_interfac_downshift'] = 0; $this->rt['sata_interfac_downshift'] = 'BIGINT'; + $this->rs['reallocate_nand_blk_cnt'] = 0; $this->rt['reallocate_nand_blk_cnt'] = 'BIGINT'; + $this->rs['ave_blockerase_count'] = 0; $this->rt['ave_blockerase_count'] = 'BIGINT'; + $this->rs['Unused_Reserve_NAND_Blk'] = 0; $this->rt['Unused_Reserve_NAND_Blk'] = 'BIGINT'; + $this->rs['sata_interfac_downshift'] = 0; $this->rt['sata_interfac_downshift'] = 'BIGINT'; $this->rs['ssd_life_left'] = 0; $this->rt['ssd_life_left'] = 'BIGINT'; - $this->rs['life_curve_status'] = 0; $this->rt['life_curve_status'] = 'BIGINT'; + $this->rs['life_curve_status'] = 0; $this->rt['life_curve_status'] = 'BIGINT'; $this->rs['supercap_health'] = 0; $this->rt['supercap_health'] = 'BIGINT'; $this->rs['lifetime_writes_gib'] = 0; $this->rt['lifetime_writes_gib'] = 'BIGINT'; $this->rs['lifetime_reads_gib'] = 0; $this->rt['lifetime_reads_gib'] = 'BIGINT'; @@ -126,9 +126,33 @@ function __construct($serial='') $this->rs['avg_writeerase_count'] = 0; $this->rt['avg_writeerase_count'] = 'BIGINT'; $this->rs['sata_phy_error'] = 0; $this->rt['sata_phy_error'] = 'BIGINT'; $this->rs['overall_health'] = ''; + $this->rs['pci_vender_subsystem_id'] = ''; // Start of NVMe columns + $this->rs['model_number'] = ''; + $this->rs['temperature_nvme'] = 0; + $this->rs['power_on_hours_nvme'] = 0; + $this->rs['power_cycle_count_nvme'] = 0; + $this->rs['critical_warning'] = ''; + $this->rs['available_spare'] = 0; $this->rt['available_spare'] = 'BIGINT'; + $this->rs['available_spare_threshold'] = 0; $this->rt['available_spare_threshold'] = 'BIGINT'; + $this->rs['percentage_used'] = 0; $this->rt['percentage_used'] = 'BIGINT'; + $this->rs['data_units_read'] = ''; + $this->rs['data_units_written'] = ''; + $this->rs['host_read_commands'] = 0; $this->rt['host_read_commands'] = 'BIGINT'; + $this->rs['host_write_commands'] = 0; $this->rt['host_write_commands'] = 'BIGINT'; + $this->rs['controller_busy_time'] = 0; $this->rt['controller_busy_time'] = 'BIGINT'; + $this->rs['unsafe_shutdowns'] = 0; $this->rt['unsafe_shutdowns'] = 'BIGINT'; + $this->rs['media_data_integrity_errors'] = 0; $this->rt['media_data_integrity_errors'] = 'BIGINT'; + $this->rs['error_info_log_entries'] = 0; $this->rt['error_info_log_entries'] = 'BIGINT'; + $this->rs['ieee_oui_id'] = ''; + $this->rs['controller_id'] = 0; $this->rt['controller_id'] = 'BIGINT'; + $this->rs['number_of_namespaces'] = 0; $this->rt['number_of_namespaces'] = 'BIGINT'; + $this->rs['firmware_updates'] = ''; + $this->rs['optional_admin_commands'] = ''; $this->rt['optional_admin_commands'] = 'TEXT'; + $this->rs['optional_nvm_commands'] = ''; $this->rt['optional_nvm_commands'] = 'TEXT'; + $this->rs['max_data_transfer_size'] = ''; // End of new NVMe columns // Schema version, increment when creating a db migration - $this->schema_version = 3; + $this->schema_version = 4; // Indexes to optimize queries $this->idx[] = array('serial_number'); @@ -276,7 +300,7 @@ function process($data) 'Lifetime_Writes_GiB' => 'lifetime_writes_gib', 'Lifetime_Reads_GiB' => 'lifetime_reads_gib', 'Uncorrectable_Error_Cnt' => 'uncorrectable_error_cnt', - 'ECC_Error_Rate' => 'ecc_error_rate', + 'ECC_Error_Rate' => 'ecc_error_eate', 'CRC_Error_Count' => 'crc_error_count', 'Supercap_Status' => 'supercap_status', 'Exception_Mode_Status' => 'exception_mode_status', @@ -284,7 +308,7 @@ function process($data) 'Total_Reads_GiB' => 'total_reads_gib', 'Total_Writes_GiB' => 'total_writes_gib', 'Thermal_Throttle' => 'thermal_throttle', - 'Perc_WriteErase_Count' => 'perc_writeerase_Count', + 'Perc_WriteErase_Count' => 'perc_writeerase_count', 'Perc_Avail_Resrvd_Space' => 'perc_avail_resrvd_space', 'Perc_WriteErase_Ct_BC' => 'perc_writeerase_ct_bc', 'SATA_PHY_Error' => 'sata_phy_error', @@ -293,20 +317,44 @@ function process($data) 'ModelFamily' => 'model_family', 'DeviceModel' => 'device_model', 'SerialNumber' => 'serial_number_hdd', - 'LUWWN' => 'lu_wwn_device_id', + 'LUWWNDeviceID' => 'lu_wwn_device_id', 'FirmwareVersion' => 'firmware_version', 'UserCapacity' => 'user_capacity', 'SectorSize' => 'sector_size', 'RotationRate' => 'rotation_rate', 'Deviceis' => 'device_is', - 'ATAVersion' => 'ata_version_is', - 'SATAVersion' => 'sata_version_is', + 'ATAVersionis' => 'ata_version_is', + 'SATAVersionis' => 'sata_version_is', 'FormFactor' => 'form_factor', 'ErrorPoH' => 'error_poh', 'error_count' => 'error_count', - 'SMARTsupport' => 'smart_support_is', + 'SMARTsupportis' => 'smart_support_is', 'DiskNumber' => 'disk_number', 'SMARTis' => 'smart_is', + 'CriticalWarning' => 'critical_warning', // Start of NVMe translations + 'AvailableSpare' => 'available_spare', + 'AvailableSpareThreshold' => 'available_spare_threshold', + 'PercentageUsed' => 'percentage_used', + 'DataUnitsRead' => 'data_units_read', + 'DataUnitsWritten' => 'data_units_written', + 'HostReadCommands' => 'host_read_commands', + 'HostWriteCommands' => 'host_write_commands', + 'ControllerBusyTime' => 'controller_busy_time', + 'UnsafeShutdowns' => 'unsafe_shutdowns', + 'MediaandDataIntegrityErrors' => 'media_data_integrity_errors', + 'ErrorInformationLogEntries' => 'error_info_log_entries', + 'PCIVendorSubsystemID' => 'pci_vender_subsystem_id', + 'IEEEOUIIdentifier' => 'ieee_oui_id', + 'ControllerID' => 'controller_id', + 'NumberofNamespaces' => 'number_of_namespaces', + 'FirmwareUpdates0x06' => 'firmware_updates', + 'OptionalAdminCommands0x0006' => 'optional_admin_commands', + 'OptionalNVMCommands0x001f' => 'optional_nvm_commands', + 'ModelNumber' => 'model_number', + 'Temperature' => 'temperature_nvme', + 'PowerCycles' => 'power_cycle_count_nvme', + 'PowerOnHours' => 'power_on_hours_nvme', + 'MaximumDataTransferSize' => 'max_data_transfer_size', // End of NVMe translations 'Overall_Health' => 'overall_health'); // Delete previous entries @@ -318,7 +366,7 @@ function process($data) $plist = $parser->toArray(); // Array of string for nulling with "" - $strings = array('model_family','device_model','serial_number_hdd','lu_wwn_device_id','firmware_version','user_capacity','sector_size','rotation_rate','device_is','ata_version_is','sata_version_is','form_factor','smart_support_is','smart_is','serial_number','power_on_hours_and_msec','airflow_temperature_cel','temperature_celsius','overall_health'); + $strings = array('model_family','device_model','serial_number_hdd','lu_wwn_device_id','firmware_version','user_capacity','sector_size','rotation_rate','device_is','ata_version_is','sata_version_is','form_factor','smart_support_is','smart_is','serial_number','power_on_hours_and_msec','airflow_temperature_cel','temperature_celsius','overall_health','critical_warning', 'data_units_read', 'data_units_written', 'pci_vender_subsystem_id', 'ieee_oui_id', 'firmware_updates', 'optional_admin_commands', 'optional_nvm_commands', 'max_data_transfer_size'); // Get index ID $disk_id = (count($plist) -1 ); @@ -328,12 +376,15 @@ function process($data) // Traverse the xml with translations foreach ($translate as $search => $field) { - // If key is empty + // If key is empty if ( ! isset($plist[$disk_id][$search])) { - if ( ! in_array($field, $strings)) { $this->$field = null; - } - } else { // Key is set + // If key has blank value, null it in the db + } else if ( $plist[$disk_id][$search] == "") { + $this->$field = null; + + // Key is set + } else { if ($search == "Head_Flying_Hours" && stripos($plist[$disk_id][$search], 'h') !== false) { $headhours = explode("h+",$plist[$disk_id][$search]); diff --git a/app/modules/smart_stats/views/smart_stats_listing.php b/app/modules/smart_stats/views/smart_stats_listing.php index acd143793..68b3814c8 100644 --- a/app/modules/smart_stats/views/smart_stats_listing.php +++ b/app/modules/smart_stats/views/smart_stats_listing.php @@ -43,7 +43,7 @@ - + @@ -132,7 +132,7 @@ // Format SMART Error Count var status=$('td:eq(4)', nRow).html(); - if (status != ""){ + if (status != "" && status != 0){ $('td:eq(4)', nRow).addClass('danger').html(status) } else { $('td:eq(4)', nRow).html("") @@ -140,19 +140,19 @@ // Format SMART Power On Hours Error Count var status=$('td:eq(5)', nRow).html(); - if (status != ""){ - $('td:eq(5)', nRow).addClass('danger').html(status) + if (status != "" && status != 0){ + $('td:eq(5)', nRow).addClass('danger').html(''+status+'') } else { $('td:eq(5)', nRow).html("") } // Format timestamp - var timestamp = (($('td:eq(15)', nRow).html()) * 1000); - $('td:eq(15)', nRow).html(moment(timestamp).format("YYYY-MM-DD H:mm:ss")) + var timestamp = (($('td:eq(15)', nRow).html()) * 1000); + $('td:eq(15)', nRow).html(moment(timestamp).format("YYYY-MM-DD H:mm:ss")) var status=$('td:eq(16)', nRow).html(); if (status != ""){ - $('td:eq(16)', nRow).html(status) + $('td:eq(16)', nRow).html(''+status+'') } else { $('td:eq(16)', nRow).html("") } diff --git a/app/modules/smart_stats/views/smart_stats_tab.php b/app/modules/smart_stats/views/smart_stats_tab.php index 1b317cc3c..dc32edefa 100644 --- a/app/modules/smart_stats/views/smart_stats_tab.php +++ b/app/modules/smart_stats/views/smart_stats_tab.php @@ -8,6 +8,9 @@ $.getJSON(appUrl + '/module/smart_stats/get_client_tab_data/' + serialNumber, function(data){ var skipThese = ['id','serial_number','disk_number','temperature_unit']; $.each(data, function(i,d){ + + // Set the tab badge to blank + $('#smart_stats-cnt').html(""); // Generate rows from data var rows = '' @@ -16,7 +19,7 @@ // Skip skipThese if(skipThese.indexOf(prop) == -1){ if (d[prop] == null){ - // Do nothing for the fake nulls to blank them + // Do nothing for the nulls to blank them } else if (d[prop] == "" && d[prop] != "0"){ // Do nothing for the nulls to blank them } else if (d[prop] == "Enabled"){ // Localize enabled @@ -27,14 +30,18 @@ rows = rows + ''+i18n.t('smart_stats.'+prop)+''+i18n.t('yes')+''; } else if (d[prop] == "Not in smartctl database [for details use: -P showall]"){ // Localize if drive is not in database rows = rows + ''+i18n.t('smart_stats.'+prop)+''+i18n.t('no')+''; - } else if (prop == "error_poh" || prop == "error_count"){ // Formate SMART Errors + } else if (prop == "error_poh" && d[prop] != 0){ // Format SMART Error Power on Hours + rows = rows + ''+i18n.t('smart_stats.'+prop)+''+d[prop]+''; + } else if (prop == "power_on_hours" || prop == "power_on_hours_nvme"){ // Format Power on Hours + rows = rows + ''+i18n.t('smart_stats.'+prop)+''+d[prop]+''; + } else if (prop == "error_count" && d[prop] != 0){ // Format SMART Error count rows = rows + ''+i18n.t('smart_stats.'+prop)+''+d[prop]+''; - } else if (prop == "total_lbas_written" || prop == "total_lbas_read"){ // Formate LBAs Read/Written + } else if (prop == "total_lbas_written" || prop == "total_lbas_read"){ // Format LBAs Read/Written rows = rows + ''+i18n.t('smart_stats.'+prop)+''+d[prop]+''; } else if (prop == "timestamp"){ // Format timestamp var timestamp = (d[prop] * 1000) rows = rows + ''+i18n.t('smart_stats.'+prop)+''+moment(+timestamp).format("YYYY-MM-DD H:mm:ss")+''; - } else if (prop == "airflow_temperature_cel" || prop == "temperature_celsius"){ // Formate temperatures + } else if (prop == "airflow_temperature_cel" || prop == "temperature_celsius" || prop == "temperature_nvme"){ // Format temperatures temperature_f = parseFloat(((d[prop] * 9/5 ) + 32 ).toFixed(2)); if (d['temperature_unit'] == "F"){ rows = rows + ''+i18n.t('smart_stats.'+prop)+''+temperature_f+'°F'; @@ -48,6 +55,8 @@ var drive_health = " "+i18n.t('unknown')+"" } else if (d['overall_health'] == "FAILED!"){ var drive_health = " "+i18n.t('failing')+"" + // Update the tab badge + $('#smart_stats-cnt').html(i18n.t('failing')); } else { var drive_health = d['overall_health'] } } else { rows = rows + ''+i18n.t('smart_stats.'+prop)+''+d[prop]+'';