Index: gft_hwcomp.py |
diff --git a/gft_hwcomp.py b/gft_hwcomp.py |
index ff2b8e0e36b5eeb191b070fc02b65b184adcbd42..e83a80c21fa9414938c51fb547397f8e4b4eb25e 100755 |
--- a/gft_hwcomp.py |
+++ b/gft_hwcomp.py |
@@ -9,6 +9,7 @@ import os |
import pprint |
import re |
import sys |
+import threading |
import flashrom_util |
import gft_common |
@@ -19,18 +20,7 @@ from gft_common import DebugMsg, VerboseMsg, WarningMsg, ErrorMsg, ErrorDie |
def Memorize(f): |
- """ Decorator for functions that need memorization.""" |
- memorize_data = {} |
- def memorize_call(*args): |
- index = repr(args) |
- if index in memorize_data: |
- value = memorize_data[index] |
- # DebugMsg('Memorize: using cached value for: %s %s' % (repr(f), index)) |
- return value |
- value = f(*args) |
- memorize_data[index] = value |
- return value |
- return memorize_call |
+ return gft_common.ThreadSafe(gft_common.Memorize(f)) |
class HardwareComponents(object): |
@@ -39,7 +29,7 @@ class HardwareComponents(object): |
# Function names in this class are used for reflection, so please don't change |
# the function names even if they are not comliant to coding style guide. |
- version = 5 |
+ version = 6 |
# We divide all component IDs (cids) into 5 categories: |
# - enumerable: able to get the results by running specific commands; |
@@ -88,6 +78,13 @@ class HardwareComponents(object): |
'data_recovery_url', |
] |
+ # list of cids that should not be fetched asynchronously. |
+ _non_async_cids = [ |
+ # Reading EC will become very slow and cause inaccurate results if we try to |
+ # probe components that also fires EC command at the same time. |
+ 'part_id_ec_flash_chip', |
+ ] |
+ |
# _not_test_cids and _to_be_tested_cids will be re-created for each match. |
_not_test_cids = [] |
_to_be_tested_cids = [] |
@@ -135,6 +132,40 @@ class HardwareComponents(object): |
return loaded |
@Memorize |
+ def is_legacy_device_record(self, record): |
+ """ Returns if a matching record looks like a legacy device. """ |
+ # Current format: [0-9a-f]{4}:[0-9a-f]{4} |
+ return True if re.match('[0-9a-f]{4}:[0-9a-f]{4}', record) else False |
+ |
+ @Memorize |
+ def _get_legacy_device_list(self): |
+ # pci: cat /proc/bus/pci/devices | cut -f 2 # 0.004s < lspci=0.012s |
+ device_list = [] |
+ pci_device_file = '/proc/bus/pci/devices' |
+ if os.path.exists(pci_device_file): |
+ with open(pci_device_file) as handle: |
+ pci_list = [data.split('\t', 2)[1] |
+ for data in handle.readlines()] |
+ device_list += ['%s:%s' % (entry[:4], entry[4:]) |
+ for entry in pci_list] |
+ else: |
+ DebugMsg('Failed to read %s. Execute lspci.' % pci_device_list) |
+ pci_list = [entry.split()[2:4] |
+ for entry in |
+ gft_common.SystemOutput('lspci -n -mm').splitlines()] |
+ device_list += ['%s:%s' % (vendor.strip('"'), product.strip('"')) |
+ for (vendor, product) in pci_list] |
+ # usb: realpath(/sys/bus/usb/devices/*:*)/../id* # 0.05s < lspci=0.078s |
+ usb_devs = glob.glob('/sys/bus/usb/devices/*:*') |
+ for dev in usb_devs: |
+ path = os.path.join(os.path.realpath(dev), '..') |
+ device_list += ['%s:%s' % |
+ (gft_common.ReadOneLine(os.path.join(path, 'idVendor')), |
+ gft_common.ReadOneLine(os.path.join(path, 'idProduct')))] |
+ DebugMsg('Legacy device list: ' + ', '.join(device_list)) |
+ return device_list |
+ |
+ @Memorize |
def _get_all_connection_info(self): |
""" Probes available connectivity and device information """ |
connection_info = { |
@@ -633,9 +664,12 @@ class HardwareComponents(object): |
approved[cid] = '*' |
return approved |
- def get_all_enumerable_components(self): |
+ def get_all_enumerable_components(self, async): |
results = {} |
- for cid in self._enumerable_cids: |
+ thread_pools = [] |
+ |
+ def fetch_enumerable_component(cid): |
+ """ Fetch an enumerable component and update the results """ |
if self._verbose: |
sys.stdout.flush() |
sys.stderr.write('<Fetching property %s>\n' % cid) |
@@ -643,18 +677,52 @@ class HardwareComponents(object): |
if not isinstance(components, list): |
components = [components] |
results[cid] = components |
+ |
+ class FetchThread(threading.Thread): |
+ """ Thread object for parallel enumerating """ |
+ def __init__(self, cid): |
+ threading.Thread.__init__(self) |
+ self.cid = cid |
+ |
+ def run(self): |
+ fetch_enumerable_component(self.cid) |
+ |
+ for cid in self._enumerable_cids: |
+ if async and cid not in self._non_async_cids: |
+ thread_pools.append(FetchThread(cid)) |
+ else: |
+ fetch_enumerable_component(cid) |
+ |
+ # Complete the threads |
+ for thread in thread_pools: |
+ thread.start() |
+ for thread in thread_pools: |
+ thread.join() |
return results |
def check_enumerable_component(self, cid, exact_values, approved_values): |
if '*' in approved_values: |
return |
- for value in exact_values: |
- if value not in approved_values: |
- if cid in self._failures: |
- self._failures[cid].append(value) |
- else: |
- self._failures[cid] = [value] |
+ unmatched = [value for value in exact_values |
+ if value not in approved_values] |
+ if not unmatched: |
+ return |
+ |
+ # there's some error, let's try to match them in legacy list |
+ legacy_approved = filter(self.is_legacy_device_record, approved_values) |
+ if set(legacy_approved) == set(approved_values): |
+ DebugMsg('Start legacy search for cid: ' + cid) |
+ # safe for legacy match |
+ # TODO(hungte) prefetch this list in async batch process. |
+ legacy_list = self._get_legacy_device_list() |
+ matched = list(set(legacy_list).intersection(set(approved_values))) |
+ if matched: |
+ DebugMsg('Changed detected list: %s->%s' % (self._system[cid], matched)) |
+ self._system[cid] = matched |
+ return |
+ # update with remaining error. |
+ self._failures[cid] = unmatched |
@Memorize |
def verify_probable_component(self, cid, approved_values): |
@@ -677,12 +745,12 @@ class HardwareComponents(object): |
def pformat(self, obj): |
return '\n' + self._pp.pformat(obj) + '\n' |
- def initialize(self, force=False): |
+ def initialize(self, force=False, async=False): |
if self._initialized and not force: |
return |
# probe current system components |
DebugMsg('Starting to detect system components...') |
- self._enumerable_system = self.get_all_enumerable_components() |
+ self._enumerable_system = self.get_all_enumerable_components(async) |
self._initialized = True |
def match_current_system(self, filename, ignored_cids=[]): |
@@ -721,15 +789,33 @@ class HardwareComponents(object): |
# Console main entry |
@gft_common.GFTConsole |
def _main(self_path, args): |
- if not args: |
- print 'Usage: %s components_db_files...\n' % self_path |
- sys.exit(1) |
- components = HardwareComponents(verbose=True) |
- print 'Starting to probe system...' |
- components.initialize() |
- print 'Starting to match system...' |
+ do_async = True |
+ # preprocess args |
+ compdb_list = [] |
for arg in args: |
+ if arg == '--sync': |
+ do_async = False |
+ elif arg == '--async': |
+ do_async = True |
+ elif not os.path.exists(arg): |
+ print 'ERROR: unknown parameter: ' + arg |
+ print 'Usage: %s [--sync|--async] [components_db_files...]\n' % self_path |
+ sys.exit(1) |
+ else: |
+ compdb_list.append(arg) |
+ |
+ components = HardwareComponents(verbose=True) |
+ print 'Starting to detect%s...' % (' asynchrounously' if do_async else '') |
+ components.initialize(async=do_async) |
+ |
+ if not compdb_list: |
+ print 'Enumerable properties:' |
+ print components.pformat(components._enumerable_system) |
+ sys.exit(0) |
+ |
+ print 'Starting to match system...' |
+ for arg in compdb_list: |
(matched, failures) = components.match_current_system(arg) |
print 'Probed (%s):' % arg |
print components.pformat(matched) |