-
Notifications
You must be signed in to change notification settings - Fork 191
Add SSD Health API and generic implementation #47
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# | ||
# ssd_base.py | ||
# | ||
# Base class for implementing common SSD health features | ||
# | ||
|
||
|
||
class SsdBase(object): | ||
""" | ||
Base class for interfacing with a SSD | ||
""" | ||
def __init__(self, diskdev): | ||
""" | ||
Constructor | ||
|
||
Args: | ||
diskdev: Linux device name to get parameters for | ||
""" | ||
pass | ||
|
||
def get_health(self): | ||
""" | ||
Retrieves current disk health in percentages | ||
|
||
Returns: | ||
A float number of current ssd health | ||
e.g. 83.5 | ||
""" | ||
raise NotImplementedError | ||
|
||
def get_temperature(self): | ||
""" | ||
Retrieves current disk temperature in Celsius | ||
|
||
Returns: | ||
A float number of current temperature in Celsius | ||
e.g. 40.1 | ||
""" | ||
raise NotImplementedError | ||
|
||
def get_model(self): | ||
""" | ||
Retrieves model for the given disk device | ||
|
||
Returns: | ||
A string holding disk model as provided by the manufacturer | ||
""" | ||
raise NotImplementedError | ||
|
||
def get_firmware(self): | ||
""" | ||
Retrieves firmware version for the given disk device | ||
|
||
Returns: | ||
A string holding disk firmware version as provided by the manufacturer | ||
""" | ||
raise NotImplementedError | ||
|
||
def get_serial(self): | ||
""" | ||
Retrieves serial number for the given disk device | ||
|
||
Returns: | ||
A string holding disk serial number as provided by the manufacturer | ||
""" | ||
raise NotImplementedError | ||
|
||
def get_vendor_output(self): | ||
""" | ||
Retrieves vendor specific data for the given disk device | ||
|
||
Returns: | ||
A string holding some vendor specific disk information | ||
""" | ||
raise NotImplementedError |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
# | ||
# ssd_generic.py | ||
# | ||
# Generic implementation of the SSD health API | ||
# SSD models supported: | ||
# - InnoDisk | ||
# - StorFly | ||
# - Virtium | ||
|
||
try: | ||
import exceptions # Python 2 | ||
except ImportError: | ||
import builtins as exceptions # Python 3 | ||
try: | ||
import re | ||
import subprocess | ||
from .ssd_base import SsdBase | ||
except ImportError as e: | ||
raise ImportError (str(e) + "- required module not found") | ||
|
||
SMARTCTL = "smartctl {} -a" | ||
INNODISK = "iSmart -d {}" | ||
VIRTIUM = "SmartCmd -m {}" | ||
|
||
NOT_AVAILABLE = "N/A" | ||
|
||
|
||
class SsdUtil(SsdBase): | ||
""" | ||
Generic implementation of the SSD health API | ||
""" | ||
model = NOT_AVAILABLE | ||
serial = NOT_AVAILABLE | ||
firmware = NOT_AVAILABLE | ||
temperature = NOT_AVAILABLE | ||
health = NOT_AVAILABLE | ||
ssd_info = NOT_AVAILABLE | ||
vendor_ssd_info = NOT_AVAILABLE | ||
|
||
def __init__(self, diskdev): | ||
self.vendor_ssd_utility = { | ||
"Generic" : { "utility" : SMARTCTL, "parser" : self.parse_generic_ssd_info }, | ||
"InnoDisk" : { "utility" : INNODISK, "parser" : self.parse_innodisk_info }, | ||
"M.2" : { "utility" : INNODISK, "parser" : self.parse_innodisk_info }, | ||
"StorFly" : { "utility" : VIRTIUM, "parser" : self.parse_virtium_info }, | ||
"Virtium" : { "utility" : VIRTIUM, "parser" : self.parse_virtium_info } | ||
} | ||
|
||
self.dev = diskdev | ||
# Generic part | ||
self.fetch_generic_ssd_info(diskdev) | ||
self.parse_generic_ssd_info() | ||
|
||
# Known vendor part | ||
if self.model: | ||
model_short = self.model.split()[0] | ||
if self.vendor_ssd_utility.has_key(model_short): | ||
self.fetch_vendor_ssd_info(diskdev, model_short) | ||
self.parse_vendor_ssd_info(model_short) | ||
else: | ||
# No handler registered for this disk model | ||
pass | ||
else: | ||
# Failed to get disk model | ||
self.model = "Unknown" | ||
|
||
def _execute_shell(self, cmd): | ||
process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE) | ||
output, error = process.communicate() | ||
return output | ||
|
||
def _parse_re(self, pattern, buffer): | ||
res_list = re.findall(pattern, buffer) | ||
return res_list[0] if res_list else NOT_AVAILABLE | ||
|
||
def fetch_generic_ssd_info(self, diskdev): | ||
self.ssd_info = self._execute_shell(self.vendor_ssd_utility["Generic"]["utility"].format(diskdev)) | ||
|
||
def parse_generic_ssd_info(self): | ||
self.model = self._parse_re('Device Model:\s*(.+?)\n', self.ssd_info) | ||
self.serial = self._parse_re('Serial Number:\s*(.+?)\n', self.ssd_info) | ||
self.firmware = self._parse_re('Firmware Version:\s*(.+?)\n', self.ssd_info) | ||
|
||
def parse_innodisk_info(self): | ||
self.health = self._parse_re('Health:\s*(.+?)%', self.vendor_ssd_info) | ||
self.temperature = self._parse_re('Temperature\s*\[\s*(.+?)\]', self.vendor_ssd_info) | ||
|
||
def parse_virtium_info(self): | ||
self.temperature = self._parse_re('Temperature_Celsius\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) | ||
nand_endurance = self._parse_re('NAND_Endurance\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) | ||
avg_erase_count = self._parse_re('Average_Erase_Count\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info) | ||
try: | ||
self.health = 100 - (float(avg_erase_count) * 100 / float(nand_endurance)) | ||
except ValueError: | ||
pass | ||
|
||
def fetch_vendor_ssd_info(self, diskdev, model): | ||
self.vendor_ssd_info = self._execute_shell(self.vendor_ssd_utility[model]["utility"].format(diskdev)) | ||
|
||
def parse_vendor_ssd_info(self, model): | ||
self.vendor_ssd_utility[model]["parser"]() | ||
|
||
def get_health(self): | ||
""" | ||
Retrieves current disk health in percentages | ||
|
||
Returns: | ||
A float number of current ssd health | ||
e.g. 83.5 | ||
""" | ||
return self.health | ||
|
||
def get_temperature(self): | ||
""" | ||
Retrieves current disk temperature in Celsius | ||
|
||
Returns: | ||
A float number of current temperature in Celsius | ||
e.g. 40.1 | ||
""" | ||
return self.temperature | ||
|
||
def get_model(self): | ||
""" | ||
Retrieves model for the given disk device | ||
|
||
Returns: | ||
A string holding disk model as provided by the manufacturer | ||
""" | ||
return self.model | ||
|
||
def get_firmware(self): | ||
""" | ||
Retrieves firmware version for the given disk device | ||
|
||
Returns: | ||
A string holding disk firmware version as provided by the manufacturer | ||
""" | ||
return self.firmware | ||
|
||
def get_serial(self): | ||
""" | ||
Retrieves serial number for the given disk device | ||
|
||
Returns: | ||
A string holding disk serial number as provided by the manufacturer | ||
""" | ||
return self.serial | ||
|
||
def get_vendor_output(self): | ||
""" | ||
Retrieves vendor specific data for the given disk device | ||
|
||
Returns: | ||
A string holding some vendor specific disk information | ||
""" | ||
return self.vendor_ssd_info | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Except the attributes you list. It's better to add "capacity" "P/E cycle" "Bad block" "Remaining time" . There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the attributes you suggest are probably specific to InnoDisk SSDs Someday we can add daemon to the pmon which will periodically query current disk health and raise alarm once it reaches some threshold. |
Uh oh!
There was an error while loading. Please reload this page.