File: //proc/thread-self/root/opt/node_api/wearout.hook
#!/usr/bin/python3
import os
import re
import json
def zabbix_drive_info():
out=[]
drives = os.popen('/etc/zabbix/scripts/smartctl-disks-discovery.pl').read()
dict = json.loads(drives)
for i in dict['data']:
data = {
'DISKSN': i['{#DISKSN}'],
'DISKNAME': i['{#DISKNAME}'],
'DISKMODEL': i['{#DISKMODEL}'],
'DISKTYPE': i['{#DISKTYPE}'],
'DISKCMD': i['{#DISKCMD}']
}
out.append(data)
return out
def remove_zero(num):
if int(num[0]) == 0:
num = num[1:]
if int(num[0]) == 0:
num = num[1:]
return num
def smart(drives, raid):
smartinfo={}
for i in drives:
info = {}
if 'megaraid' not in i['DISKCMD']:
data = os.popen('smartctl -a ' + i['DISKNAME'] + ' 2>/dev/null').read().splitlines()
info['name'] = i['DISKNAME']
else:
cmd = str(i['DISKCMD']).split(',')
with open('/proc/scsi/sg/device_strs') as device_strs:
lines = device_strs.read().splitlines()
for position, item in enumerate(lines):
if re.search('AVAGO', item, re.I | re.U):
raid_position = position
if re.search('LSI', item, re.I | re.U):
raid_position = position
if re.search('DELL', item, re.I | re.U):
raid_position = position
break
data = os.popen('smartctl -a -d sat+megaraid,' + str(cmd[-1]) + ' -T permissive /dev/sg' + str(raid_position) + ' 2>/dev/null').read().splitlines()
info['name'] = str(cmd[-1])
for x in data:
# ssd / hdd
if re.search('Wear_Leveling_Count', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['wearout'] = remove_zero(line[3])
if re.search('Media_Wearout_Indicator', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['wearout'] = remove_zero(line[3])
if re.search('SSD_Life_Left', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['wearout'] = line[9]
if re.search('Percent_Lifetime_Used', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['wearout'] = line[9]
if re.search('Drive_Life_Remaining%', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['wearout'] = line[9]
if re.search('Reallocated_Sector_Ct', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['error'] = line[10]
info['remains'] = int(remove_zero(line[4])) - int(remove_zero(line[6])) if line[6] != '---' else 'not supported'
if re.search('Runtime_Bad_Block', x) and info.get('error') is None:
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['error'] = line[9]
if re.search('Retired_Block_Count', x) and info.get('error') is None:
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['error'] = line[10]
if re.search('User Capacity', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['size'] = line[4][1:] + ' ' + line[5][:-1]
if re.search('Power_On_Hours', x):
res = re.sub(r"\s+", " ", x.strip())
poh = res.split(' ')
if 'h' in poh[9]:
d = poh[9].split('h')
info['poh'] = d[0]
else:
info['poh'] = poh[9]
if re.search('Total_LBAs_Written', x):
res = re.sub(r"\s+", " ", x.strip())
Total_LBAs_Written = res.split(' ')
info['tlw'] = Total_LBAs_Written[9]
if re.search('Lifetime_Writes_GiB', x):
res = re.sub(r"\s+", " ", x.strip())
Total_LBAs_Written = res.split(' ')
tlw = int(Total_LBAs_Written[9]) * 1024 * 1024 * 1024 / 512
info['tlw'] = tlw
if re.search('FAILING_NOW', x):
info['failing'] = 'FAILING_NOW'
#Micron and Crucial
if re.search('202 Unknown_SSD_Attribute', x) and info.get('wearout') is None:
res = re.sub(r"\s+", " ", x.strip())
line = res.split(' ')
info['wearout'] = remove_zero(line[3])
if re.search('246 Unknown_Attribute', x) and info.get('tlw') is None:
res = re.sub(r"\s+", " ", x.strip())
Total_LBAs_Written = res.split(' ')
info['tlw'] = Total_LBAs_Written[9]
if re.search('Percent_Lifetime_Remain', x):
res = re.sub(r"\s+", " ", x.strip())
line = res.split(' ')
info['wearout'] = remove_zero(line[3])
if re.search('Total_Host_Sector_Write', x):
res = re.sub(r"\s+", " ", x.strip())
Total_LBAs_Written = res.split(' ')
info['tlw'] = Total_LBAs_Written[9]
#nmve
if re.search('Total NVM Capacity', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['size'] = line[4][1:] + ' ' + line[5][:-1]
if re.search('Namespace 1 Size/Capacity', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['size'] = line[4][1:] + ' ' + line[5][:-1]
if re.search('Available Spare:', x):
res = re.sub(r"\s+", " ", x)
line = res.split(' ')
info['wearout'] = line[2].replace('%', '')
if re.search('Critical Warning:', x):
res = re.sub(r"\s+", " ", x.strip())
line = res.split(' ')
info['error'] = line[-1][-1]
if re.search('Power On Hours:', x):
res = re.sub(r"\s+", " ", x.strip())
poh = res.split(' ')
info['poh'] = poh[3].replace(',', '').replace('\xe2\x80\xaf', '')
if re.search('Data Units Written:', x):
res = re.sub(r"\s+", " ", x.strip())
tlw = res.split(' ')
info['tlw'] = str(int(tlw[3].replace(',', '').replace('\xe2\x80\xaf', '')) * 1000)
if info.get("wearout") == None:
info['wearout'] = 'not supported'
if info.get("error") == None:
info['error'] = 'not supported'
if info.get("remains") == None:
info['remains'] = 'not supported'
if info.get("tlw") == None:
info['tlw'] = 'not supported'
if info.get("poh") == None:
info['poh'] = 'not supported'
if info.get("failing") == None:
info['failing'] = 'not found'
info['model'] = i['DISKMODEL']
if 'sg' in str(i['DISKNAME']) and raid and 'LSI Logical Volume' not in info['model'] and 'AVAGO' not in info['model'] or info['name'].isdigit():
info['raid'] = raid[0]
else:
info['raid'] = '-'
if int(i['DISKTYPE']) == 0:
info['type'] = int(i['DISKTYPE'])
if os.path.exists('/sys/block/' + i['DISKNAME'].replace('/dev/', '') + '/queue/hw_sector_size') is True:
info['sector'] = os.popen('cat /sys/block/' + i['DISKNAME'].replace('/dev/', '') + '/queue/hw_sector_size').read().replace('\n', '')
else:
for x in data:
if re.search('Sector Size', x):
res = re.sub(r"\s+", " ", x.strip())
sector = res.split(' ')
info['sector'] = sector[2]
elif int(i['DISKTYPE']) == 1:
if 'nvme' in str(i['DISKNAME']):
info['type'] = 2
if os.path.exists('/sys/block/' + i['DISKNAME'].replace('/dev/', '') + 'n1/queue/hw_sector_size') is True:
info['sector'] = os.popen('cat /sys/block/' + i['DISKNAME'].replace('/dev/', '') + 'n1/queue/hw_sector_size').read().replace('\n', '')
else:
for x in data:
if re.search('Sector Size', x):
res = re.sub(r"\s+", " ", x.strip())
sector = res.split(' ')
info['sector'] = sector[2]
else:
info['type'] = int(i['DISKTYPE'])
if os.path.exists('/sys/block/' + i['DISKNAME'].replace('/dev/', '') + '/queue/hw_sector_size') is True:
info['sector'] = os.popen('cat /sys/block/' + i['DISKNAME'].replace('/dev/', '') + '/queue/hw_sector_size').read().replace('\n', '')
else:
for x in data:
if re.search('Sector Size', x):
res = re.sub(r"\s+", " ", x.strip())
sector = res.split(' ')
info['sector'] = sector[2]
if info.get("sector") == None:
info['sector'] = '512'
smartinfo[i['DISKSN']] = info
return smartinfo
def main():
raid = []
lspci = os.popen('lspci').read().splitlines()
for line in lspci:
if re.search('[Aa]daptec', line):
raid.append('Adaptec')
if re.search('LSI SAS', line):
raid.append('LSI SAS')
if re.search('MegaRAID', line):
raid.append('MegaRAID')
drives = zabbix_drive_info()
out = smart(drives, raid)
print(json.dumps(out, indent=4))
if __name__=="__main__":
main()