forked from sonic-net/sonic-platform-common
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathssd_generic.py
More file actions
220 lines (184 loc) · 7.49 KB
/
Copy pathssd_generic.py
File metadata and controls
220 lines (184 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
#
# ssd_generic.py
#
# Generic implementation of the SSD health API
# SSD models supported:
# - InnoDisk
# - StorFly
# - Virtium
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"
# Set Vendor Specific IDs
INNODISK_HEALTH_ID = 169
INNODISK_TEMPERATURE_ID = 194
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:
vendor = self._parse_vendor()
if vendor:
self.fetch_vendor_ssd_info(diskdev, vendor)
self.parse_vendor_ssd_info(vendor)
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(), universal_newlines=True, 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 _parse_vendor(self):
model_short = self.model.split()[0]
if model_short in self.vendor_ssd_utility:
return model_short
elif self.model.startswith('VSF'):
return 'Virtium'
else:
return None
def fetch_generic_ssd_info(self, diskdev):
self.ssd_info = self._execute_shell(self.vendor_ssd_utility["Generic"]["utility"].format(diskdev))
# Health and temperature values may be overwritten with vendor specific data
def parse_generic_ssd_info(self):
if "nvme" in self.dev:
self.model = self._parse_re('Model Number:\s*(.+?)\n', self.ssd_info)
health_raw = self._parse_re('Percentage Used\s*(.+?)\n', self.ssd_info)
if health_raw == NOT_AVAILABLE:
self.health = NOT_AVAILABLE
else:
health_raw = health_raw.split()[-1]
self.health = 100 - float(health_raw.strip('%'))
temp_raw = self._parse_re('Temperature\s*(.+?)\n', self.ssd_info)
if temp_raw == NOT_AVAILABLE:
self.temperature = NOT_AVAILABLE
else:
temp_raw = temp_raw.split()[-2]
self.temperature = float(temp_raw)
else:
self.model = self._parse_re('Device Model:\s*(.+?)\n', self.ssd_info)
health_raw = self._parse_re('Remaining_Lifetime_Perc\s*(.+?)\n', self.ssd_info)
if health_raw == NOT_AVAILABLE:
self.health = NOT_AVAILABLE
else:
self.health = health_raw.split()[-1]
temp_raw = self._parse_re('Temperature_Celsius\s*(.+?)\n', self.ssd_info)
if temp_raw == NOT_AVAILABLE:
self.temperature = NOT_AVAILABLE
else:
self.temperature = temp_raw.split()[-6]
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):
if self.vendor_ssd_info:
self.health = self._parse_re('Health:\s*(.+?)%', self.vendor_ssd_info)
self.temperature = self._parse_re('Temperature\s*\[\s*(.+?)\]', self.vendor_ssd_info)
if self.health == NOT_AVAILABLE:
health_raw = self.parse_id_number(INNODISK_HEALTH_ID)
if health_raw == NOT_AVAILABLE:
self.health = NOT_AVAILABLE
else:
self.health = health_raw.split()[-1]
if self.temperature == NOT_AVAILABLE:
temp_raw = self.parse_id_number(INNODISK_TEMPERATURE_ID)
if temp_raw == NOT_AVAILABLE:
self.temperature = NOT_AVAILABLE
else:
self.temperature = temp_raw.split()[-6]
def parse_virtium_info(self):
if self.vendor_ssd_info:
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)
if nand_endurance != NOT_AVAILABLE and avg_erase_count != NOT_AVAILABLE:
try:
self.health = 100 - (float(avg_erase_count) * 100 / float(nand_endurance))
except (ValueError, ZeroDivisionError):
pass
else:
try:
self.health = float(self._parse_re('Remaining_Life_Left\s*\d*\s*\d*\s*(\d+?)\s+', self.vendor_ssd_info))
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
def parse_id_number(self, id):
return self._parse_re('{}\s*(.+?)\n'.format(id), self.ssd_info)