parser module
This commit is contained in:
parent
417276b88b
commit
f7b149f926
|
@ -6,15 +6,19 @@ from contextlib import suppress
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from enum import Enum, unique
|
from enum import Enum, unique
|
||||||
from fractions import Fraction
|
from fractions import Fraction
|
||||||
from subprocess import CalledProcessError, PIPE, run
|
from subprocess import PIPE, CalledProcessError, run
|
||||||
from typing import Iterator, List, Optional, Tuple, Type, TypeVar
|
from typing import Iterator, List, Optional, Tuple, Type, TypeVar
|
||||||
from warnings import catch_warnings, filterwarnings
|
from warnings import catch_warnings, filterwarnings
|
||||||
|
|
||||||
import dateutil.parser
|
import dateutil.parser
|
||||||
import pySMART
|
import pySMART
|
||||||
from ereuse_utils import cmd, getter as g, text
|
from ereuse_utils import cmd
|
||||||
from ereuse_utils.nested_lookup import get_nested_dicts_with_key_containing_value, \
|
from ereuse_utils import getter as g
|
||||||
get_nested_dicts_with_key_value
|
from ereuse_utils import text
|
||||||
|
from ereuse_utils.nested_lookup import (
|
||||||
|
get_nested_dicts_with_key_containing_value,
|
||||||
|
get_nested_dicts_with_key_value,
|
||||||
|
)
|
||||||
from numpy import hypot
|
from numpy import hypot
|
||||||
|
|
||||||
from ereuse_devicehub.parser import base2, unit, utils
|
from ereuse_devicehub.parser import base2, unit, utils
|
||||||
|
@ -37,11 +41,13 @@ class Device(Dumpeable):
|
||||||
|
|
||||||
def from_lshw(self, lshw_node: dict):
|
def from_lshw(self, lshw_node: dict):
|
||||||
self.manufacturer = g.dict(lshw_node, 'vendor', default=None, type=str)
|
self.manufacturer = g.dict(lshw_node, 'vendor', default=None, type=str)
|
||||||
self.model = g.dict(lshw_node,
|
self.model = g.dict(
|
||||||
|
lshw_node,
|
||||||
'product',
|
'product',
|
||||||
remove={self.manufacturer} if self.manufacturer else set(),
|
remove={self.manufacturer} if self.manufacturer else set(),
|
||||||
default=None,
|
default=None,
|
||||||
type=str)
|
type=str,
|
||||||
|
)
|
||||||
self.serial_number = g.dict(lshw_node, 'serial', default=None, type=str)
|
self.serial_number = g.dict(lshw_node, 'serial', default=None, type=str)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
@ -64,13 +70,16 @@ class Processor(Component):
|
||||||
# We want only the physical cpu's, not the logic ones
|
# We want only the physical cpu's, not the logic ones
|
||||||
# In some cases we may get empty cpu nodes, we can detect them because
|
# In some cases we may get empty cpu nodes, we can detect them because
|
||||||
# all regular cpus have at least a description (Intel Core i5...)
|
# all regular cpus have at least a description (Intel Core i5...)
|
||||||
return (cls(node) for node in nodes if
|
return (
|
||||||
'logical' not in node['id']
|
cls(node)
|
||||||
|
for node in nodes
|
||||||
|
if 'logical' not in node['id']
|
||||||
and node.get('description', '').lower() != 'co-processor'
|
and node.get('description', '').lower() != 'co-processor'
|
||||||
and not node.get('disabled')
|
and not node.get('disabled')
|
||||||
and 'co-processor' not in node.get('model', '').lower()
|
and 'co-processor' not in node.get('model', '').lower()
|
||||||
and 'co-processor' not in node.get('description', '').lower()
|
and 'co-processor' not in node.get('description', '').lower()
|
||||||
and 'width' in node)
|
and 'width' in node
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, node: dict) -> None:
|
def __init__(self, node: dict) -> None:
|
||||||
super().__init__(node)
|
super().__init__(node)
|
||||||
|
@ -89,7 +98,6 @@ class Processor(Component):
|
||||||
|
|
||||||
assert not hasattr(self, 'cores') or 1 <= self.cores <= 16
|
assert not hasattr(self, 'cores') or 1 <= self.cores <= 16
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def processor_brand_generation(model: str):
|
def processor_brand_generation(model: str):
|
||||||
"""Generates the ``brand`` and ``generation`` fields for the given model.
|
"""Generates the ``brand`` and ``generation`` fields for the given model.
|
||||||
|
@ -148,7 +156,9 @@ class RamModule(Component):
|
||||||
memories = get_nested_dicts_with_key_value(lshw, 'class', 'memory')
|
memories = get_nested_dicts_with_key_value(lshw, 'class', 'memory')
|
||||||
TYPES = {'ddr', 'sdram', 'sodimm'}
|
TYPES = {'ddr', 'sdram', 'sodimm'}
|
||||||
for memory in memories:
|
for memory in memories:
|
||||||
physical_ram = any(t in memory.get('description', '').lower() for t in TYPES)
|
physical_ram = any(
|
||||||
|
t in memory.get('description', '').lower() for t in TYPES
|
||||||
|
)
|
||||||
not_empty = 'size' in memory
|
not_empty = 'size' in memory
|
||||||
if physical_ram and not_empty:
|
if physical_ram and not_empty:
|
||||||
yield cls(memory)
|
yield cls(memory)
|
||||||
|
@ -183,14 +193,17 @@ class DataStorage(Component):
|
||||||
|
|
||||||
usb_disks = list() # List of disks that are plugged in an USB host
|
usb_disks = list() # List of disks that are plugged in an USB host
|
||||||
for usb in get_nested_dicts_with_key_containing_value(lshw, 'id', 'usbhost'):
|
for usb in get_nested_dicts_with_key_containing_value(lshw, 'id', 'usbhost'):
|
||||||
usb_disks.extend(get_nested_dicts_with_key_containing_value(usb, 'id', 'disk'))
|
usb_disks.extend(
|
||||||
|
get_nested_dicts_with_key_containing_value(usb, 'id', 'disk')
|
||||||
|
)
|
||||||
|
|
||||||
for disk in (n for n in disks if n not in usb_disks):
|
for disk in (n for n in disks if n not in usb_disks):
|
||||||
# We can get nodes that are not truly disks as they don't have size
|
# We can get nodes that are not truly disks as they don't have size
|
||||||
if 'size' in disk:
|
if 'size' in disk:
|
||||||
interface = DataStorage.get_interface(disk)
|
interface = DataStorage.get_interface(disk)
|
||||||
removable = interface == 'usb' or \
|
removable = interface == 'usb' or disk.get('capabilities', {}).get(
|
||||||
disk.get('capabilities', {}).get('removable', False)
|
'removable', False
|
||||||
|
)
|
||||||
if not removable:
|
if not removable:
|
||||||
yield cls(disk, interface)
|
yield cls(disk, interface)
|
||||||
|
|
||||||
|
@ -210,7 +223,9 @@ class DataStorage(Component):
|
||||||
super().__init__(node)
|
super().__init__(node)
|
||||||
self.from_lshw(node)
|
self.from_lshw(node)
|
||||||
self.size = unit.Quantity(node['size'], node.get('units', 'B')).to('MB').m
|
self.size = unit.Quantity(node['size'], node.get('units', 'B')).to('MB').m
|
||||||
self.interface = self.DataStorageInterface(interface.upper()) if interface else None
|
self.interface = (
|
||||||
|
self.DataStorageInterface(interface.upper()) if interface else None
|
||||||
|
)
|
||||||
self._logical_name = node['logicalname']
|
self._logical_name = node['logicalname']
|
||||||
self.variant = node['version']
|
self.variant = node['version']
|
||||||
|
|
||||||
|
@ -225,22 +240,29 @@ class DataStorage(Component):
|
||||||
self.serial_number = self.serial_number or smart.serial
|
self.serial_number = self.serial_number or smart.serial
|
||||||
self.model = self.model or smart.model
|
self.model = self.model or smart.model
|
||||||
|
|
||||||
assert 1.0 < self.size < 1000000000000000.0, \
|
assert 1.0 < self.size < 1000000000000000.0, 'Invalid HDD size {}'.format(
|
||||||
'Invalid HDD size {}'.format(self.size)
|
self.size
|
||||||
|
)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return '{} {} {} with {} MB'.format(super().__str__(), self.interface, self.type,
|
return '{} {} {} with {} MB'.format(
|
||||||
self.size)
|
super().__str__(), self.interface, self.type, self.size
|
||||||
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_interface(node: dict):
|
def get_interface(node: dict):
|
||||||
interface = run('udevadm info '
|
interface = run(
|
||||||
|
'udevadm info '
|
||||||
'--query=all '
|
'--query=all '
|
||||||
'--name={} | '
|
'--name={} | '
|
||||||
'grep '
|
'grep '
|
||||||
'ID_BUS | '
|
'ID_BUS | '
|
||||||
'cut -c 11-'.format(node['logicalname']),
|
'cut -c 11-'.format(node['logicalname']),
|
||||||
check=True, universal_newlines=True, shell=True, stdout=PIPE).stdout
|
check=True,
|
||||||
|
universal_newlines=True,
|
||||||
|
shell=True,
|
||||||
|
stdout=PIPE,
|
||||||
|
).stdout
|
||||||
# todo not sure if ``interface != usb`` is needed
|
# todo not sure if ``interface != usb`` is needed
|
||||||
return interface.strip()
|
return interface.strip()
|
||||||
|
|
||||||
|
@ -260,13 +282,17 @@ class GraphicCard(Component):
|
||||||
def _memory(bus_info):
|
def _memory(bus_info):
|
||||||
"""The size of the memory of the gpu."""
|
"""The size of the memory of the gpu."""
|
||||||
try:
|
try:
|
||||||
lines = cmd.run('lspci',
|
lines = cmd.run(
|
||||||
|
'lspci',
|
||||||
'-v -s {bus} | ',
|
'-v -s {bus} | ',
|
||||||
'grep \'prefetchable\' | ',
|
'grep \'prefetchable\' | ',
|
||||||
'grep -v \'non-prefetchable\' | ',
|
'grep -v \'non-prefetchable\' | ',
|
||||||
'egrep -o \'[0-9]{{1,3}}[KMGT]+\''.format(bus=bus_info),
|
'egrep -o \'[0-9]{{1,3}}[KMGT]+\''.format(bus=bus_info),
|
||||||
shell=True).stdout.splitlines()
|
shell=True,
|
||||||
return max((base2.Quantity(value).to('MiB') for value in lines), default=None)
|
).stdout.splitlines()
|
||||||
|
return max(
|
||||||
|
(base2.Quantity(value).to('MiB') for value in lines), default=None
|
||||||
|
)
|
||||||
except subprocess.CalledProcessError:
|
except subprocess.CalledProcessError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -281,23 +307,29 @@ class Motherboard(Component):
|
||||||
def new(cls, lshw, hwinfo, **kwargs) -> C:
|
def new(cls, lshw, hwinfo, **kwargs) -> C:
|
||||||
node = next(get_nested_dicts_with_key_value(lshw, 'description', 'Motherboard'))
|
node = next(get_nested_dicts_with_key_value(lshw, 'description', 'Motherboard'))
|
||||||
bios_node = next(get_nested_dicts_with_key_value(lshw, 'id', 'firmware'))
|
bios_node = next(get_nested_dicts_with_key_value(lshw, 'id', 'firmware'))
|
||||||
memory_array = next(g.indents(hwinfo, 'Physical Memory Array', indent=' '), None)
|
memory_array = next(
|
||||||
|
g.indents(hwinfo, 'Physical Memory Array', indent=' '), None
|
||||||
|
)
|
||||||
return cls(node, bios_node, memory_array)
|
return cls(node, bios_node, memory_array)
|
||||||
|
|
||||||
def __init__(self, node: dict, bios_node: dict, memory_array: Optional[List[str]]) -> None:
|
def __init__(
|
||||||
|
self, node: dict, bios_node: dict, memory_array: Optional[List[str]]
|
||||||
|
) -> None:
|
||||||
super().__init__(node)
|
super().__init__(node)
|
||||||
self.from_lshw(node)
|
self.from_lshw(node)
|
||||||
self.usb = self.num_interfaces(node, 'usb')
|
self.usb = self.num_interfaces(node, 'usb')
|
||||||
self.firewire = self.num_interfaces(node, 'firewire')
|
self.firewire = self.num_interfaces(node, 'firewire')
|
||||||
self.serial = self.num_interfaces(node, 'serial')
|
self.serial = self.num_interfaces(node, 'serial')
|
||||||
self.pcmcia = self.num_interfaces(node, 'pcmcia')
|
self.pcmcia = self.num_interfaces(node, 'pcmcia')
|
||||||
self.slots = int(run('dmidecode -t 17 | '
|
self.slots = int(
|
||||||
'grep -o BANK | '
|
run(
|
||||||
'wc -l',
|
'dmidecode -t 17 | ' 'grep -o BANK | ' 'wc -l',
|
||||||
check=True,
|
check=True,
|
||||||
universal_newlines=True,
|
universal_newlines=True,
|
||||||
shell=True,
|
shell=True,
|
||||||
stdout=PIPE).stdout)
|
stdout=PIPE,
|
||||||
|
).stdout
|
||||||
|
)
|
||||||
self.bios_date = dateutil.parser.parse(bios_node['date'])
|
self.bios_date = dateutil.parser.parse(bios_node['date'])
|
||||||
self.version = bios_node['version']
|
self.version = bios_node['version']
|
||||||
self.ram_slots = self.ram_max_size = None
|
self.ram_slots = self.ram_max_size = None
|
||||||
|
@ -311,8 +343,11 @@ class Motherboard(Component):
|
||||||
def num_interfaces(node: dict, interface: str) -> int:
|
def num_interfaces(node: dict, interface: str) -> int:
|
||||||
interfaces = get_nested_dicts_with_key_containing_value(node, 'id', interface)
|
interfaces = get_nested_dicts_with_key_containing_value(node, 'id', interface)
|
||||||
if interface == 'usb':
|
if interface == 'usb':
|
||||||
interfaces = (c for c in interfaces
|
interfaces = (
|
||||||
if 'usbhost' not in c['id'] and 'usb' not in c['businfo'])
|
c
|
||||||
|
for c in interfaces
|
||||||
|
if 'usbhost' not in c['id'] and 'usb' not in c['businfo']
|
||||||
|
)
|
||||||
return len(tuple(interfaces))
|
return len(tuple(interfaces))
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
@ -341,14 +376,17 @@ class NetworkAdapter(Component):
|
||||||
# and to parse it
|
# and to parse it
|
||||||
# https://www.redhat.com/archives/redhat-list/2010-October/msg00066.html
|
# https://www.redhat.com/archives/redhat-list/2010-October/msg00066.html
|
||||||
# workbench-live includes proprietary firmwares
|
# workbench-live includes proprietary firmwares
|
||||||
self.serial_number = self.serial_number or utils.get_hw_addr(node['logicalname'])
|
self.serial_number = self.serial_number or utils.get_hw_addr(
|
||||||
|
node['logicalname']
|
||||||
|
)
|
||||||
|
|
||||||
self.variant = node.get('version', None)
|
self.variant = node.get('version', None)
|
||||||
self.wireless = bool(node.get('configuration', {}).get('wireless', False))
|
self.wireless = bool(node.get('configuration', {}).get('wireless', False))
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return '{} {} {}'.format(super().__str__(), self.speed,
|
return '{} {} {}'.format(
|
||||||
'wireless' if self.wireless else 'ethernet')
|
super().__str__(), self.speed, 'wireless' if self.wireless else 'ethernet'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SoundCard(Component):
|
class SoundCard(Component):
|
||||||
|
@ -387,18 +425,25 @@ class Display(Component):
|
||||||
self.resolution_width, self.resolution_height = text.numbers(
|
self.resolution_width, self.resolution_height = text.numbers(
|
||||||
g.kv(timings, 'Resolution')
|
g.kv(timings, 'Resolution')
|
||||||
)
|
)
|
||||||
x, y = (unit.Quantity(v, 'millimeter').to('inch') for v in
|
x, y = (
|
||||||
text.numbers(g.kv(node, 'Size')))
|
unit.Quantity(v, 'millimeter').to('inch')
|
||||||
|
for v in text.numbers(g.kv(node, 'Size'))
|
||||||
|
)
|
||||||
self.size = float(hypot(x, y).m)
|
self.size = float(hypot(x, y).m)
|
||||||
self.technology = next((t for t in self.TECHS if t in node[0]), None)
|
self.technology = next((t for t in self.TECHS if t in node[0]), None)
|
||||||
d = '{} {} 0'.format(g.kv(node, 'Year of Manufacture'), g.kv(node, 'Week of Manufacture'))
|
d = '{} {} 0'.format(
|
||||||
|
g.kv(node, 'Year of Manufacture'), g.kv(node, 'Week of Manufacture')
|
||||||
|
)
|
||||||
# We assume it has been produced the first day of such week
|
# We assume it has been produced the first day of such week
|
||||||
self.production_date = datetime.strptime(d, '%Y %W %w')
|
self.production_date = datetime.strptime(d, '%Y %W %w')
|
||||||
self._aspect_ratio = Fraction(self.resolution_width, self.resolution_height)
|
self._aspect_ratio = Fraction(self.resolution_width, self.resolution_height)
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return '{0} {1.resolution_width}x{1.resolution_height} {1.size} inches {2}'.format(
|
return (
|
||||||
super().__str__(), self, self._aspect_ratio)
|
'{0} {1.resolution_width}x{1.resolution_height} {1.size} inches {2}'.format(
|
||||||
|
super().__str__(), self, self._aspect_ratio
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class Battery(Component):
|
class Battery(Component):
|
||||||
|
@ -407,6 +452,7 @@ class Battery(Component):
|
||||||
the Linux Kernel convention, from
|
the Linux Kernel convention, from
|
||||||
https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power.
|
https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-class-power.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
LiIon = 'Li-ion'
|
LiIon = 'Li-ion'
|
||||||
NiCd = 'NiCd'
|
NiCd = 'NiCd'
|
||||||
NiMH = 'NiMH'
|
NiMH = 'NiMH'
|
||||||
|
@ -419,9 +465,9 @@ class Battery(Component):
|
||||||
@classmethod
|
@classmethod
|
||||||
def new(cls, lshw, hwinfo, **kwargs) -> Iterator[C]:
|
def new(cls, lshw, hwinfo, **kwargs) -> Iterator[C]:
|
||||||
try:
|
try:
|
||||||
uevent = cmd \
|
uevent = cmd.run(
|
||||||
.run('cat', '/sys/class/power_supply/BAT*/uevent', shell=True) \
|
'cat', '/sys/class/power_supply/BAT*/uevent', shell=True
|
||||||
.stdout.splitlines()
|
).stdout.splitlines()
|
||||||
except CalledProcessError:
|
except CalledProcessError:
|
||||||
return
|
return
|
||||||
yield cls(uevent)
|
yield cls(uevent)
|
||||||
|
@ -429,17 +475,21 @@ class Battery(Component):
|
||||||
def __init__(self, node: List[str]) -> None:
|
def __init__(self, node: List[str]) -> None:
|
||||||
super().__init__(node)
|
super().__init__(node)
|
||||||
try:
|
try:
|
||||||
self.serial_number = g.kv(node, self.PRE + 'SERIAL_NUMBER', sep='=', type=str)
|
self.serial_number = g.kv(
|
||||||
|
node, self.PRE + 'SERIAL_NUMBER', sep='=', type=str
|
||||||
|
)
|
||||||
self.manufacturer = g.kv(node, self.PRE + 'MANUFACTURER', sep='=')
|
self.manufacturer = g.kv(node, self.PRE + 'MANUFACTURER', sep='=')
|
||||||
self.model = g.kv(node, self.PRE + 'MODEL_NAME', sep='=')
|
self.model = g.kv(node, self.PRE + 'MODEL_NAME', sep='=')
|
||||||
self.size = g.kv(node, self.PRE + 'CHARGE_FULL_DESIGN', sep='=', default=0)
|
self.size = g.kv(node, self.PRE + 'CHARGE_FULL_DESIGN', sep='=', default=0)
|
||||||
if self.size is not None:
|
if self.size is not None:
|
||||||
self.size = self.size // 1000
|
self.size = self.size // 1000
|
||||||
self.technology = g.kv(node, self.PRE + 'TECHNOLOGY', sep='=', type=self.Technology)
|
self.technology = g.kv(
|
||||||
|
node, self.PRE + 'TECHNOLOGY', sep='=', type=self.Technology
|
||||||
|
)
|
||||||
measure = MeasureBattery(
|
measure = MeasureBattery(
|
||||||
size=g.kv(node, self.PRE + 'CHARGE_FULL', sep='='),
|
size=g.kv(node, self.PRE + 'CHARGE_FULL', sep='='),
|
||||||
voltage=g.kv(node, self.PRE + 'VOLTAGE_NOW', sep='='),
|
voltage=g.kv(node, self.PRE + 'VOLTAGE_NOW', sep='='),
|
||||||
cycle_count=g.kv(node, self.PRE + 'CYCLE_COUNT', sep='=')
|
cycle_count=g.kv(node, self.PRE + 'CYCLE_COUNT', sep='='),
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
measure.size = measure.size.m
|
measure.size = measure.size.m
|
||||||
|
@ -447,28 +497,51 @@ class Battery(Component):
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
pass
|
||||||
self.actions.add(measure)
|
self.actions.add(measure)
|
||||||
self._wear = round(1 - measure.size / self.size, 2) \
|
self._wear = (
|
||||||
if self.size and measure.size else None
|
round(1 - measure.size / self.size, 2)
|
||||||
|
if self.size and measure.size
|
||||||
|
else None
|
||||||
|
)
|
||||||
self._node = node
|
self._node = node
|
||||||
except NoBatteryInfo:
|
except NoBatteryInfo:
|
||||||
self._node = None
|
self._node = None
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
try:
|
try:
|
||||||
return '{0} {1.technology}. Size: {1.size} Wear: {1._wear:%}'.format(super().__str__(),
|
return '{0} {1.technology}. Size: {1.size} Wear: {1._wear:%}'.format(
|
||||||
self)
|
super().__str__(), self
|
||||||
|
)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
return 'There is not currently battery information'
|
return 'There is not currently battery information'
|
||||||
|
|
||||||
|
|
||||||
class Computer(Device):
|
class Computer(Device):
|
||||||
CHASSIS_TYPE = {
|
CHASSIS_TYPE = {
|
||||||
'Desktop': {'desktop', 'low-profile', 'tower', 'docking', 'all-in-one', 'pizzabox',
|
'Desktop': {
|
||||||
'mini-tower', 'space-saving', 'lunchbox', 'mini', 'stick'},
|
'desktop',
|
||||||
'Laptop': {'portable', 'laptop', 'convertible', 'tablet', 'detachable', 'notebook',
|
'low-profile',
|
||||||
'handheld', 'sub-notebook'},
|
'tower',
|
||||||
|
'docking',
|
||||||
|
'all-in-one',
|
||||||
|
'pizzabox',
|
||||||
|
'mini-tower',
|
||||||
|
'space-saving',
|
||||||
|
'lunchbox',
|
||||||
|
'mini',
|
||||||
|
'stick',
|
||||||
|
},
|
||||||
|
'Laptop': {
|
||||||
|
'portable',
|
||||||
|
'laptop',
|
||||||
|
'convertible',
|
||||||
|
'tablet',
|
||||||
|
'detachable',
|
||||||
|
'notebook',
|
||||||
|
'handheld',
|
||||||
|
'sub-notebook',
|
||||||
|
},
|
||||||
'Server': {'server'},
|
'Server': {'server'},
|
||||||
'Computer': {'_virtual'}
|
'Computer': {'_virtual'},
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
A translation dictionary whose keys are Devicehub types and values
|
A translation dictionary whose keys are Devicehub types and values
|
||||||
|
@ -489,7 +562,7 @@ class Computer(Device):
|
||||||
'Convertible': {'convertible'},
|
'Convertible': {'convertible'},
|
||||||
'Detachable': {'detachable'},
|
'Detachable': {'detachable'},
|
||||||
'Tablet': {'tablet'},
|
'Tablet': {'tablet'},
|
||||||
'Virtual': {'_virtual'}
|
'Virtual': {'_virtual'},
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
A conversion table from DMI's chassis type value Devicehub
|
A conversion table from DMI's chassis type value Devicehub
|
||||||
|
@ -503,9 +576,13 @@ class Computer(Device):
|
||||||
def __init__(self, node: dict) -> None:
|
def __init__(self, node: dict) -> None:
|
||||||
super().__init__(node)
|
super().__init__(node)
|
||||||
self.from_lshw(node)
|
self.from_lshw(node)
|
||||||
chassis = node['configuration'].get('chassis', '_virtual')
|
chassis = node.get('configuration', {}).get('chassis', '_virtual')
|
||||||
self.type = next(t for t, values in self.CHASSIS_TYPE.items() if chassis in values)
|
self.type = next(
|
||||||
self.chassis = next(t for t, values in self.CHASSIS_DH.items() if chassis in values)
|
t for t, values in self.CHASSIS_TYPE.items() if chassis in values
|
||||||
|
)
|
||||||
|
self.chassis = next(
|
||||||
|
t for t, values in self.CHASSIS_DH.items() if chassis in values
|
||||||
|
)
|
||||||
self.sku = g.dict(node, ('configuration', 'sku'), default=None, type=str)
|
self.sku = g.dict(node, ('configuration', 'sku'), default=None, type=str)
|
||||||
self.version = g.dict(node, 'version', default=None, type=str)
|
self.version = g.dict(node, 'version', default=None, type=str)
|
||||||
self._ram = None
|
self._ram = None
|
||||||
|
@ -529,7 +606,9 @@ class Computer(Device):
|
||||||
components.extend(Component.new(lshw=lshw, hwinfo=hwinfo))
|
components.extend(Component.new(lshw=lshw, hwinfo=hwinfo))
|
||||||
components.append(Motherboard.new(lshw, hwinfo))
|
components.append(Motherboard.new(lshw, hwinfo))
|
||||||
|
|
||||||
computer._ram = sum(ram.size for ram in components if isinstance(ram, RamModule))
|
computer._ram = sum(
|
||||||
|
ram.size for ram in components if isinstance(ram, RamModule)
|
||||||
|
)
|
||||||
return computer, components
|
return computer, components
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|
|
@ -0,0 +1,361 @@
|
||||||
|
import json
|
||||||
|
|
||||||
|
from dmidecode import DMIParse
|
||||||
|
|
||||||
|
from ereuse_devicehub.parser.computer import (
|
||||||
|
Display,
|
||||||
|
GraphicCard,
|
||||||
|
NetworkAdapter,
|
||||||
|
SoundCard,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ParseSnapshot:
|
||||||
|
def __init__(self, snapshot, default="n/a"):
|
||||||
|
self.default = default
|
||||||
|
self.dmidecode_raw = snapshot["data"]["demidecode"]
|
||||||
|
self.smart_raw = snapshot["data"]["smart"]
|
||||||
|
self.hwinfo_raw = snapshot["data"]["hwinfo"]
|
||||||
|
self.device = {"actions": []}
|
||||||
|
self.components = []
|
||||||
|
|
||||||
|
self.dmi = DMIParse(self.dmidecode_raw)
|
||||||
|
self.smart = self.loads(self.smart_raw)
|
||||||
|
self.hwinfo = self.parse_hwinfo()
|
||||||
|
|
||||||
|
self.set_basic_datas()
|
||||||
|
self.computer = {
|
||||||
|
"device": self.device,
|
||||||
|
"software": "Workbench",
|
||||||
|
"components": self.components(),
|
||||||
|
"uuid": snapshot['uuid'],
|
||||||
|
"type": snapshot['type'],
|
||||||
|
"version": snapshot["version"],
|
||||||
|
"endTime": snapshot["endTime"],
|
||||||
|
"elapsed": 0,
|
||||||
|
"closed": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
def set_basic_datas(self):
|
||||||
|
self.device['manufacturer'] = self.dmi.manufacturer()
|
||||||
|
self.device['model'] = self.dmi.model()
|
||||||
|
self.device['serialNumber'] = self.dmi.serial_number()
|
||||||
|
self.device['type'] = self.get_type()
|
||||||
|
self.device['sku'] = self.get_sku()
|
||||||
|
self.device['version'] = self.get_version()
|
||||||
|
self.device['uuid'] = self.get_uuid()
|
||||||
|
|
||||||
|
def set_components(self):
|
||||||
|
self.get_cpu()
|
||||||
|
self.get_ram()
|
||||||
|
self.get_mother_board()
|
||||||
|
|
||||||
|
def get_cpu(self):
|
||||||
|
# TODO @cayop generation, brand and address not exist in dmidecode
|
||||||
|
for cpu in self.dmi.get('Processor'):
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "Processor",
|
||||||
|
"speed": cpu.get('Max Speed'),
|
||||||
|
"cores": int(cpu.get('Core Count', 1)),
|
||||||
|
"model": cpu.get('Version'),
|
||||||
|
"threads": int(cpu.get('Thread Count', 1)),
|
||||||
|
"manufacturer": cpu.get('Manufacturer'),
|
||||||
|
"serialNumber": cpu.get('Serial Number'),
|
||||||
|
"generation": cpu.get('Generation'),
|
||||||
|
"brand": cpu.get('Brand'),
|
||||||
|
"address": cpu.get('Address'),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_ram(self):
|
||||||
|
# TODO @cayop format and model not exist in dmidecode
|
||||||
|
for ram in self.dmi.get("Memory Device"):
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "RamModule",
|
||||||
|
"size": self.get_ram_size(ram),
|
||||||
|
"speed": self.get_ram_speed(ram),
|
||||||
|
"manufacturer": ram.get("Manufacturer", self.default),
|
||||||
|
"serialNumber": ram.get("Serial Number", self.default),
|
||||||
|
"interface": ram.get("Type", self.default),
|
||||||
|
"format": ram.get("Format", self.default), # "DIMM",
|
||||||
|
"model": ram.get(
|
||||||
|
"Model", self.default
|
||||||
|
), # "48594D503131325336344350362D53362020",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_mother_board(self):
|
||||||
|
# TODO @cayop model, not exist in dmidecode
|
||||||
|
for moder_board in self.dmi.get("Baseboard"):
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "Motherboard",
|
||||||
|
"version": moder_board.get("Version"),
|
||||||
|
"serialNumber": moder_board.get("Serial Number"),
|
||||||
|
"manufacturer": moder_board.get("Manufacturer"),
|
||||||
|
"ramSlots": self.get_ram_slots(),
|
||||||
|
"ramMaxSize": self.get_max_ram_size(),
|
||||||
|
"slots": len(self.dmi.get("Number Of Devices")),
|
||||||
|
"biosDate": self.get_bios_date(),
|
||||||
|
"firewire": self.get_firmware(),
|
||||||
|
"model": moder_board.get("Product Name"), # ??
|
||||||
|
"pcmcia": self.get_pcmcia_num(), # ??
|
||||||
|
"serial": self.get_serial_num(), # ??
|
||||||
|
"usb": self.get_usb_num(),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_usb_num(self):
|
||||||
|
return len(
|
||||||
|
[u for u in self.get("Port Connector") if u.get("Port Type") == "USB"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_serial_num(self):
|
||||||
|
return len(
|
||||||
|
[u for u in self.get("Port Connector") if u.get("Port Type") == "SERIAL"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_pcmcia_num(self):
|
||||||
|
return len(
|
||||||
|
[u for u in self.get("Port Connector") if u.get("Port Type") == "PCMCIA"]
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_bios_date(self):
|
||||||
|
return self.get("BIOS")[0].get("Release Date", self.default)
|
||||||
|
|
||||||
|
def get_firmware(self):
|
||||||
|
return self.get("BIOS")[0].get("Firmware Revision", self.default)
|
||||||
|
|
||||||
|
def get_max_ram_size(self):
|
||||||
|
size = self.dmi.get("Physical Memory Array")
|
||||||
|
if size:
|
||||||
|
size = size.get("Maximum Capacity")
|
||||||
|
|
||||||
|
return size.split(" GB")[0] if size else self.default
|
||||||
|
|
||||||
|
def get_ram_slots(self):
|
||||||
|
slots = self.dmi.get("Physical Memory Array")
|
||||||
|
if slots:
|
||||||
|
slots = slots.get("Number Of Devices")
|
||||||
|
return int(slots) if slots else self.default
|
||||||
|
|
||||||
|
def get_ram_size(self, ram):
|
||||||
|
size = ram.get("Size")
|
||||||
|
return size.split(" MB")[0] if size else self.default
|
||||||
|
|
||||||
|
def get_ram_speed(self, ram):
|
||||||
|
size = ram.get("Speed")
|
||||||
|
return size.split(" MT/s")[0] if size else self.default
|
||||||
|
|
||||||
|
def get_sku(self):
|
||||||
|
return self.get("System")[0].get("SKU Number", self.default)
|
||||||
|
|
||||||
|
def get_version(self):
|
||||||
|
return self.get("System")[0].get("Version", self.default)
|
||||||
|
|
||||||
|
def get_uuid(self):
|
||||||
|
return self.get("System")[0].get("UUID", self.default)
|
||||||
|
|
||||||
|
def get_chassis(self):
|
||||||
|
return self.get("Chassis")[0].get("Type", self.default)
|
||||||
|
|
||||||
|
def get_type(self):
|
||||||
|
chassis_type = self.get_chassis()
|
||||||
|
return self.translation_to_devicehub(chassis_type)
|
||||||
|
|
||||||
|
def translation_to_devicehub(self, original_type):
|
||||||
|
lower_type = original_type.lower()
|
||||||
|
CHASSIS_TYPE = {
|
||||||
|
'Desktop': [
|
||||||
|
'desktop',
|
||||||
|
'low-profile',
|
||||||
|
'tower',
|
||||||
|
'docking',
|
||||||
|
'all-in-one',
|
||||||
|
'pizzabox',
|
||||||
|
'mini-tower',
|
||||||
|
'space-saving',
|
||||||
|
'lunchbox',
|
||||||
|
'mini',
|
||||||
|
'stick',
|
||||||
|
],
|
||||||
|
'Laptop': [
|
||||||
|
'portable',
|
||||||
|
'laptop',
|
||||||
|
'convertible',
|
||||||
|
'tablet',
|
||||||
|
'detachable',
|
||||||
|
'notebook',
|
||||||
|
'handheld',
|
||||||
|
'sub-notebook',
|
||||||
|
],
|
||||||
|
'Server': ['server'],
|
||||||
|
'Computer': ['_virtual'],
|
||||||
|
}
|
||||||
|
for k, v in CHASSIS_TYPE.items():
|
||||||
|
if lower_type in v:
|
||||||
|
return k
|
||||||
|
return self.default
|
||||||
|
|
||||||
|
def get_data_storage(self):
|
||||||
|
|
||||||
|
for sm in self.smart:
|
||||||
|
model = sm.get('model_name')
|
||||||
|
manufacturer = None
|
||||||
|
if len(model.split(" ")) == 2:
|
||||||
|
manufacturer, model = model.split(" ")
|
||||||
|
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": self.get_data_storage_type(sm),
|
||||||
|
"model": model,
|
||||||
|
"manufacturer": manufacturer,
|
||||||
|
"serialNumber": sm.get('serial_number'),
|
||||||
|
"size": self.get_data_storage_size(sm),
|
||||||
|
"variant": sm.get("firmware_version"),
|
||||||
|
"interface": self.get_data_storage_interface(sm),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_data_storage_type(self, x):
|
||||||
|
# TODO @cayop add more SSDS types
|
||||||
|
SSDS = ["nvme"]
|
||||||
|
SSD = 'SolidStateDrive'
|
||||||
|
HDD = 'HardDrive'
|
||||||
|
type_dev = x.get('device', {}).get('type')
|
||||||
|
return SSD if type_dev in SSDS else HDD
|
||||||
|
|
||||||
|
def get_data_storage_interface(self, x):
|
||||||
|
return x.get('device', {}).get('protocol', 'ATA')
|
||||||
|
|
||||||
|
def get_data_storage_size(self, x):
|
||||||
|
type_dev = x.get('device', {}).get('type')
|
||||||
|
total_capacity = "{type}_total_capacity".format(type=type_dev)
|
||||||
|
# convert bytes to Mb
|
||||||
|
return x.get(total_capacity) / 1024**2
|
||||||
|
|
||||||
|
def get_networks(self):
|
||||||
|
addr = []
|
||||||
|
for line in self.hwinfo:
|
||||||
|
for y in line:
|
||||||
|
if "Permanent HW Address:" in y:
|
||||||
|
mac = y.split(" Permanent HW Address: ")[1]
|
||||||
|
addr.extend(mac)
|
||||||
|
|
||||||
|
return addr
|
||||||
|
|
||||||
|
def parse_hwinfo(self):
|
||||||
|
hw_blocks = self.hwinfo_raw.split("\n\n")
|
||||||
|
return [x.split("\n") for x in hw_blocks]
|
||||||
|
|
||||||
|
def loads(self, x):
|
||||||
|
if isinstance(x, dict) or isinstance(x, list):
|
||||||
|
return x
|
||||||
|
return json.loads(x)
|
||||||
|
|
||||||
|
|
||||||
|
class LsHw:
|
||||||
|
def __init__(self, dmi, jshw, hwinfo, default="n/a"):
|
||||||
|
self.default = default
|
||||||
|
self.hw = self.loads(jshw)
|
||||||
|
self.hwinfo = hwinfo.splitlines()
|
||||||
|
self.childrens = self.hw.get('children', [])
|
||||||
|
self.dmi = dmi
|
||||||
|
self.components = dmi.components
|
||||||
|
self.device = dmi.device
|
||||||
|
self.add_components()
|
||||||
|
|
||||||
|
def add_components(self):
|
||||||
|
self.get_cpu_addr()
|
||||||
|
self.get_networks()
|
||||||
|
self.get_display()
|
||||||
|
self.get_sound_card()
|
||||||
|
self.get_graphic_card()
|
||||||
|
|
||||||
|
def get_cpu_addr(self):
|
||||||
|
for cpu in self.components:
|
||||||
|
if not cpu['type'] == "Processor":
|
||||||
|
continue
|
||||||
|
|
||||||
|
cpu["address"] = self.hw.get("width")
|
||||||
|
|
||||||
|
def get_networks(self):
|
||||||
|
networks = NetworkAdapter.new(self.lshw, self.hwinfo)
|
||||||
|
|
||||||
|
for x in networks:
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "NetworkAdapter",
|
||||||
|
"serialNumber": x.serial_number,
|
||||||
|
"speed": x.speed,
|
||||||
|
"model": x.model,
|
||||||
|
"manufacturer": x.manufacturer,
|
||||||
|
"variant": x.variant,
|
||||||
|
"wireless": x.wireless,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_display(self):
|
||||||
|
if not self.device['type'] == 'Laptop':
|
||||||
|
return
|
||||||
|
|
||||||
|
displays = Display.new(self.lshw, self.hwinfo)
|
||||||
|
|
||||||
|
for x in displays:
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "Display",
|
||||||
|
"model": x.model,
|
||||||
|
"manufacturer": x.manufacturer,
|
||||||
|
"serialNumber": x.serial_number,
|
||||||
|
"resolutionWidth": x.resolution_width,
|
||||||
|
"resolutionHeight": x.resolution_height,
|
||||||
|
"refreshRate": x.refresh_rate,
|
||||||
|
"technology": x.technology,
|
||||||
|
"productionDate": x.production_date,
|
||||||
|
"size": x.size,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_sound_card(self):
|
||||||
|
soundcards = SoundCard.new(self.lshw, self.hwinfo)
|
||||||
|
|
||||||
|
for x in soundcards:
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "SoundCard",
|
||||||
|
"model": x.model,
|
||||||
|
"manufacturer": x.manufacturer,
|
||||||
|
"serialNumber": x.serial_number,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_graphic_card(self):
|
||||||
|
# TODO @cayop memory get info from lspci on fly
|
||||||
|
graphicards = GraphicCard.new(self.lshw, self.hwinfo)
|
||||||
|
|
||||||
|
for x in graphicards:
|
||||||
|
self.components.append(
|
||||||
|
{
|
||||||
|
"actions": [],
|
||||||
|
"type": "GraphicCard",
|
||||||
|
"model": x.model,
|
||||||
|
"manufacturer": x.manufacturer,
|
||||||
|
"serialNumber": x.serial_number,
|
||||||
|
"memory": x.memory,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def loads(jshw):
|
||||||
|
if isinstance(jshw, dict):
|
||||||
|
return jshw
|
||||||
|
return json.loads(jshw)
|
Reference in New Issue