HEX
Server: LiteSpeed
System: Linux pl-rocket-da1.hostsila.org 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: etorby24eu (1588)
PHP: 8.3.23
Disabled: exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: //proc/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()