Added macOS support for CpuRam() object.

This commit is contained in:
2Shirt 2019-10-23 20:33:41 -07:00
parent 07120b7dc4
commit 70248ef0b5
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C

View file

@ -3,6 +3,8 @@
import logging import logging
import pathlib import pathlib
import platform
import plistlib
import re import re
from collections import OrderedDict from collections import OrderedDict
@ -13,6 +15,16 @@ from wk.std import bytes_to_string, color_string, string_to_bytes
# STATIC VARIABLES # STATIC VARIABLES
KEY_NVME = 'nvme_smart_health_information_log' KEY_NVME = 'nvme_smart_health_information_log'
KEY_SMART = 'ata_smart_attributes' KEY_SMART = 'ata_smart_attributes'
KNOWN_RAM_VENDOR_IDS = {
# https://github.com/hewigovens/hewigovens.github.com/wiki/Memory-vendor-code
'0x014F': 'Transcend',
'0x2C00': 'Micron',
'0x802C': 'Micron',
'0x80AD': 'Hynix',
'0x80CE': 'Samsung',
'0xAD00': 'Hynix',
'0xCE00': 'Samsung',
}
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
REGEX_POWER_ON_TIME = re.compile( REGEX_POWER_ON_TIME = re.compile(
r'^(\d+)([Hh].*|\s+\(\d+\s+\d+\s+\d+\).*)' r'^(\d+)([Hh].*|\s+\(\d+\s+\d+\s+\d+\).*)'
@ -22,57 +34,58 @@ REGEX_POWER_ON_TIME = re.compile(
class CpuRam(): class CpuRam():
"""Object for tracking CPU & RAM specific data.""" """Object for tracking CPU & RAM specific data."""
def __init__(self): def __init__(self):
self.lscpu = {} self.description = 'Unknown'
self.details = {}
self.ram_total = 'Unknown'
self.ram_dimms = []
self.tests = OrderedDict() self.tests = OrderedDict()
# Update details
self.get_cpu_details() self.get_cpu_details()
self.get_ram_details() self.get_ram_details()
self.name = self.lscpu.get('Model name', 'Unknown CPU')
self.description = self.name
def get_cpu_details(self): def get_cpu_details(self):
"""Get CPU details from lscpu.""" """Get CPU details using OS specific methods."""
cmd = ['lscpu', '--json'] if platform.system() == 'Darwin':
json_data = get_json_from_command(cmd) cmd = 'sysctl -n machdep.cpu.brand_string'.split()
for line in json_data.get('lscpu', [{}]): proc = run_program(cmd, check=False)
_field = line.get('field', '').replace(':', '') self.description = re.sub(r'\s+', ' ', proc.stdout.strip())
_data = line.get('data', '') elif platform.system() == 'Linux':
if not (_field or _data): cmd = ['lscpu', '--json']
# Skip json_data = get_json_from_command(cmd)
continue for line in json_data.get('lscpu', [{}]):
self.lscpu[_field] = _data _field = line.get('field', '').replace(':', '')
_data = line.get('data', '')
if not (_field or _data):
# Skip
continue
self.details[_field] = _data
self.description = self.details.get('Model name', '')
# Replace empty description
if not self.description:
self.description = 'Unknown CPU'
def get_ram_details(self): def get_ram_details(self):
"""Get RAM details from dmidecode.""" """Get RAM details using OS specific methods."""
cmd = ['sudo', 'dmidecode', '--type', 'memory'] if platform.system() == 'Darwin':
manufacturer = 'UNKNOWN' dimm_list = get_ram_list_macos()
elif platform.system() == 'Linux':
dimm_list = get_ram_list_linux()
details = {'Total': 0} details = {'Total': 0}
size = 0 for dimm_details in dimm_list:
size, manufacturer = dimm_details
# Get DMI data if size <= 0:
proc = run_program(cmd) # Skip empty DIMMs
dmi_data = proc.stdout.splitlines() continue
description = f'{bytes_to_string(size)} {manufacturer}'
# Parse data details['Total'] += size
for line in dmi_data: if description in details:
line = line.strip() details[description] += 1
if line == 'Memory Device': else:
# Reset vars details[description] = 1
manufacturer = 'UNKNOWN'
size = 0
elif line.startswith('Size:'):
size = line.replace('Size: ', '')
size = string_to_bytes(size, assume_binary=True)
elif line.startswith('Manufacturer:'):
manufacturer = line.replace('Manufacturer: ', '')
if size <= 0:
# Skip non-populated slots
continue
description = f'{bytes_to_string(size)} {manufacturer}'
details['Total'] += size
if description in details:
details[description] += 1
else:
details[description] = 1
# Save details # Save details
self.ram_total = bytes_to_string(details.pop('Total', 0)) self.ram_total = bytes_to_string(details.pop('Total', 0))
@ -84,7 +97,7 @@ class CpuRam():
"""Generate CPU report with data from all tests.""" """Generate CPU report with data from all tests."""
report = [] report = []
report.append(color_string('Device', 'BLUE')) report.append(color_string('Device', 'BLUE'))
report.append(f' {self.name}') report.append(f' {self.description}')
# Include RAM details # Include RAM details
report.append(color_string('RAM', 'BLUE')) report.append(color_string('RAM', 'BLUE'))
@ -101,7 +114,7 @@ class Disk():
"""Object for tracking disk specific data.""" """Object for tracking disk specific data."""
def __init__(self, path): def __init__(self, path):
self.attributes = {} self.attributes = {}
self.description = 'UNKNOWN' self.description = 'Unknown'
self.lsblk = {} self.lsblk = {}
self.nvme_smart_notes = {} self.nvme_smart_notes = {}
self.path = pathlib.Path(path).resolve() self.path = pathlib.Path(path).resolve()
@ -155,7 +168,7 @@ class Disk():
# Set description # Set description
self.description = '{size_str} ({tran}) {model} {serial}'.format( self.description = '{size_str} ({tran}) {model} {serial}'.format(
size_str = bytes_to_string(self.lsblk['size'], use_binary=False), size_str=bytes_to_string(self.lsblk['size'], use_binary=False),
**self.lsblk, **self.lsblk,
) )
@ -207,9 +220,9 @@ class Disk():
# Ignoring invalid attribute # Ignoring invalid attribute
LOG.error('Invalid SMART attribute: %s', attribute) LOG.error('Invalid SMART attribute: %s', attribute)
continue continue
name = str(attribute.get('name', 'UNKNOWN')).replace('_', ' ').title() name = str(attribute.get('name', 'Unknown')).replace('_', ' ').title()
raw = int(attribute.get('raw', {}).get('value', -1)) raw = int(attribute.get('raw', {}).get('value', -1))
raw_str = attribute.get('raw', {}).get('string', 'UNKNOWN') raw_str = attribute.get('raw', {}).get('string', 'Unknown')
# Fix power-on time # Fix power-on time
match = REGEX_POWER_ON_TIME.match(raw_str) match = REGEX_POWER_ON_TIME.match(raw_str)
@ -221,5 +234,69 @@ class Disk():
'name': name, 'raw': raw, 'raw_str': raw_str} 'name': name, 'raw': raw, 'raw_str': raw_str}
# Functions
def get_ram_list_linux():
"""Get RAM list using dmidecode."""
cmd = ['sudo', 'dmidecode', '--type', 'memory']
dimm_list = []
manufacturer = 'Unknown'
size = 0
# Get DMI data
proc = run_program(cmd)
dmi_data = proc.stdout.splitlines()
# Parse data
for line in dmi_data:
line = line.strip()
if line == 'Memory Device':
# Reset vars
manufacturer = 'Unknown'
size = 0
elif line.startswith('Size:'):
size = line.replace('Size: ', '')
size = string_to_bytes(size, assume_binary=True)
elif line.startswith('Manufacturer:'):
manufacturer = line.replace('Manufacturer: ', '')
dimm_list.append([size, manufacturer])
# Save details
return dimm_list
def get_ram_list_macos():
"""Get RAM list using system_profiler."""
dimm_list = []
# Get and parse plist data
cmd = [
'system_profiler',
'-xml',
'SPMemoryDataType',
]
proc = run_program(cmd, check=False, encoding=None, errors=None)
try:
plist_data = plistlib.loads(proc.stdout)
except (TypeError, ValueError):
# Ignore and return an empty list
return dimm_list
# Check DIMM data
dimm_details = plist_data[0].get('_items', [{}])[0].get('_items', [])
for dimm in dimm_details:
manufacturer = dimm.get('dimm_manufacturer', None)
manufacturer = KNOWN_RAM_VENDOR_IDS.get(manufacturer, 'Unknown')
size = dimm.get('dimm_size', '0 GB')
try:
size = string_to_bytes(size, assume_binary=True)
except ValueError:
# Empty DIMM?
LOG.error('Invalid DIMM size: %s', size)
continue
dimm_list.append([size, manufacturer])
# Save details
return dimm_list
if __name__ == '__main__': if __name__ == '__main__':
print("This file is not meant to be called directly.") print("This file is not meant to be called directly.")