linuxfabrik-lib 2.2.1__tar.gz → 2.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {linuxfabrik_lib-2.2.1/linuxfabrik_lib.egg-info → linuxfabrik_lib-2.4.0}/PKG-INFO +1 -1
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/args.py +12 -1
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/base.py +15 -8
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/distro.py +3 -3
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/dmidecode.py +129 -44
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/endoflifedate.py +585 -217
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0/linuxfabrik_lib.egg-info}/PKG-INFO +1 -1
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/pyproject.toml +1 -1
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/redfish.py +122 -29
- linuxfabrik_lib-2.4.0/rocket.py +462 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/shell.py +36 -23
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/time.py +21 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/txt.py +8 -8
- linuxfabrik_lib-2.2.1/rocket.py +0 -149
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/LICENSE.txt +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/README.md +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/__init__.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/cache.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/db_mysql.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/db_sqlite.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/disk.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/feedparser.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/globals.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/grassfish.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/huawei.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/human.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/icinga.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/infomaniak.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/jitsi.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/keycloak.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/lftest.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/librenms.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/linuxfabrik_lib.egg-info/SOURCES.txt +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/linuxfabrik_lib.egg-info/dependency_links.txt +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/linuxfabrik_lib.egg-info/requires.txt +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/linuxfabrik_lib.egg-info/top_level.txt +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/net.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/nodebb.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/powershell.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/psutil.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/qts.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/setup.cfg +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/smb.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/uptimerobot.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/url.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/veeam.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/version.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/wildfly.py +0 -0
- {linuxfabrik_lib-2.2.1 → linuxfabrik_lib-2.4.0}/winrm.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: linuxfabrik-lib
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.0
|
|
4
4
|
Summary: Python libraries used in various Linuxfabrik projects, including the 'Linuxfabrik Monitoring Plugins' project.
|
|
5
5
|
Author-email: "Linuxfabrik GmbH, Zurich, Switzerland" <info@linuxfabrik.ch>
|
|
6
6
|
Project-URL: Homepage, https://github.com/Linuxfabrik/lib
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
|
|
15
|
-
__version__ = '
|
|
15
|
+
__version__ = '2025091501'
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
HELP_TEXTS = {
|
|
@@ -25,6 +25,17 @@ HELP_TEXTS = {
|
|
|
25
25
|
'`(?: ... )*` is a non-capturing group that matches any sequence of characters '
|
|
26
26
|
'that satisfy the condition inside it, zero or more times.'
|
|
27
27
|
),
|
|
28
|
+
'--stratum': (
|
|
29
|
+
'Warns if the determined stratum of the time server is greater than or equal to this '
|
|
30
|
+
'value. '
|
|
31
|
+
'Stratum 1 indicates a computer with a locally attached reference clock. A computer that '
|
|
32
|
+
'is synchronised to a stratum 1 computer is at stratum 2. A computer that is synchronised '
|
|
33
|
+
'to a stratum 2 computer is at stratum 3, and so on.'
|
|
34
|
+
),
|
|
35
|
+
'--verbose': (
|
|
36
|
+
'Makes this plugin verbose during the operation. Useful for debugging and seeing '
|
|
37
|
+
'what\'s going on under the hood.'
|
|
38
|
+
),
|
|
28
39
|
}
|
|
29
40
|
|
|
30
41
|
# Predefined sets for checking units and methods
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
14
|
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
|
|
15
|
-
__version__ = '
|
|
15
|
+
__version__ = '2025070401'
|
|
16
16
|
|
|
17
17
|
import collections
|
|
18
18
|
import numbers
|
|
@@ -269,6 +269,7 @@ def get_table(data, cols, header=None, strip=True, sort_by_key=None, sort_order_
|
|
|
269
269
|
"""
|
|
270
270
|
if not data:
|
|
271
271
|
return ''
|
|
272
|
+
data = data.copy() # data has been passed by-reference - kick the reference
|
|
272
273
|
|
|
273
274
|
if sort_by_key:
|
|
274
275
|
data = sorted(data, key=operator.itemgetter(sort_by_key), reverse=sort_order_reverse)
|
|
@@ -393,7 +394,7 @@ def guess_type(v, consumer='python'):
|
|
|
393
394
|
return str(v) if consumer == 'python' else 'text'
|
|
394
395
|
|
|
395
396
|
|
|
396
|
-
def is_empty_list(
|
|
397
|
+
def is_empty_list(lst):
|
|
397
398
|
"""
|
|
398
399
|
Check if a list only contains either empty elements or whitespace.
|
|
399
400
|
|
|
@@ -479,7 +480,8 @@ def match_range(value, spec):
|
|
|
479
480
|
|
|
480
481
|
### Returns
|
|
481
482
|
- **bool**:
|
|
482
|
-
- True if `value` is inside the bounds for a non-inverted `spec`, or outside the bounds for an
|
|
483
|
+
- True if `value` is inside the bounds for a non-inverted `spec`, or outside the bounds for an
|
|
484
|
+
inverted `spec`.
|
|
483
485
|
- Otherwise, False.
|
|
484
486
|
|
|
485
487
|
### Example
|
|
@@ -626,7 +628,8 @@ def oao(msg, state=STATE_OK, perfdata='', always_ok=False):
|
|
|
626
628
|
"""
|
|
627
629
|
msg = txt.sanitize_sensitive_data(msg.strip()).replace('|', '!')
|
|
628
630
|
if always_ok and msg:
|
|
629
|
-
|
|
631
|
+
# Instead of splitlines(), we just split('\n', 1), so only first line is touched.
|
|
632
|
+
parts = msg.split('\n', 1)
|
|
630
633
|
parts[0] += ' (always ok)'
|
|
631
634
|
msg = '\n'.join(parts)
|
|
632
635
|
print(f'{msg}|{perfdata.strip()}' if perfdata else msg)
|
|
@@ -683,12 +686,16 @@ def sort(array, reverse=True, sort_by_key=False):
|
|
|
683
686
|
If the input is not a dictionary, the original input is returned unmodified.
|
|
684
687
|
|
|
685
688
|
### Parameters
|
|
686
|
-
- **array** (`dict` or `any`): The dictionary to be sorted. If not a dictionary, the input is
|
|
687
|
-
|
|
688
|
-
- **
|
|
689
|
+
- **array** (`dict` or `any`): The dictionary to be sorted. If not a dictionary, the input is
|
|
690
|
+
returned as is.
|
|
691
|
+
- **reverse** (`bool`, optional): If True, sort in descending order; if False, ascending.
|
|
692
|
+
Defaults to True.
|
|
693
|
+
- **sort_by_key** (`bool`, optional): If True, sort by dictionary keys; if False, by values.
|
|
694
|
+
Defaults to False.
|
|
689
695
|
|
|
690
696
|
### Returns
|
|
691
|
-
- **list** or **any**: A list of sorted (key, value) tuples if a dictionary is provided,
|
|
697
|
+
- **list** or **any**: A list of sorted (key, value) tuples if a dictionary is provided,
|
|
698
|
+
otherwise the original input.
|
|
692
699
|
|
|
693
700
|
### Example
|
|
694
701
|
>>> sort({'a': 2, 'b': 1})
|
|
@@ -17,7 +17,7 @@ Source Code is taken, converted, shortened and modified from:
|
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
19
|
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
|
|
20
|
-
__version__ = '
|
|
20
|
+
__version__ = '2025061001'
|
|
21
21
|
|
|
22
22
|
|
|
23
23
|
import os
|
|
@@ -51,7 +51,7 @@ SEARCH_STRING = {
|
|
|
51
51
|
|
|
52
52
|
OS_FAMILY_MAP = {
|
|
53
53
|
'RedHat': ['RedHat', 'RHEL', 'CentOS', 'Scientific', 'OracleLinux', 'Fedora', 'AlmaLinux', 'Rocky'],
|
|
54
|
-
'Debian': ['Debian', 'Debian GNU/Linux', 'Ubuntu', 'Raspbian', 'Pop!_OS', 'Kali', 'Parrot', 'Devuan', 'Deepin', 'Mint'],
|
|
54
|
+
'Debian': ['Debian', 'Debian GNU/Linux', 'Ubuntu', 'Raspbian', 'Pop!_OS', 'Kali', 'Parrot', 'Devuan', 'Devuan GNU/Linux', 'Deepin', 'Mint'],
|
|
55
55
|
'Suse': ['SUSE', 'openSUSE', 'SLES', 'SLED'],
|
|
56
56
|
'Archlinux': ['Archlinux', 'Manjaro', 'Antergos'],
|
|
57
57
|
'Gentoo': ['Gentoo', 'Funtoo'],
|
|
@@ -400,7 +400,7 @@ def get_distribution_facts():
|
|
|
400
400
|
"""
|
|
401
401
|
facts = _process_dist_files()
|
|
402
402
|
|
|
403
|
-
distro = facts.get('
|
|
403
|
+
distro = facts.get('distribution_file_variety', 'distribution')
|
|
404
404
|
facts['os_family'] = _map_os_family(distro) # returns 'RedHat', for example
|
|
405
405
|
|
|
406
406
|
cmd = '. /etc/os-release && echo "$NAME $VERSION"'
|
|
@@ -14,7 +14,7 @@ Copied and refactored from py-dmidecode (https://github.com/zaibon/py-dmidecode)
|
|
|
14
14
|
"""
|
|
15
15
|
|
|
16
16
|
__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
|
|
17
|
-
__version__ = '
|
|
17
|
+
__version__ = '2025090901'
|
|
18
18
|
|
|
19
19
|
import re
|
|
20
20
|
import subprocess
|
|
@@ -219,49 +219,86 @@ def cpu_type(dmi):
|
|
|
219
219
|
|
|
220
220
|
def dmidecode_parse(output):
|
|
221
221
|
"""
|
|
222
|
-
Parse
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
222
|
+
Parse `dmidecode` output into a dict, collapsing near-duplicates in an admin-friendly way.
|
|
223
|
+
|
|
224
|
+
Type-aware dedupe rules:
|
|
225
|
+
- Type 4 (Processor Information): ignore per-thread/core/socket noise fields; merge;
|
|
226
|
+
add dedup_count/dedup_sockets
|
|
227
|
+
- Type 17 (Memory Device): drop unpopulated; ignore slot labels; merge;
|
|
228
|
+
add dedup_count/dedup_slots
|
|
229
|
+
- Other types: generic dedupe (exact content after normalization)
|
|
230
|
+
|
|
231
|
+
Returns:
|
|
232
|
+
{ dmi_handle_tuple: parsed_record_dict, ... }
|
|
233
|
+
where parsed_record_dict includes keys: dminame, dmisize, dmitype, parsed fields,
|
|
234
|
+
and possibly:
|
|
235
|
+
- dedup_count (int >= 1)
|
|
236
|
+
- dedup_sockets / dedup_slots (sorted list of labels encountered)
|
|
237
|
+
- dedup_handles (list of original DMI handle strings that were merged)
|
|
238
|
+
"""
|
|
239
|
+
data = {}
|
|
240
|
+
seen = {} # fp -> (first_handle, aggregated_record)
|
|
241
|
+
|
|
242
|
+
# --- helpers -------------------------------------------------------------
|
|
243
|
+
def _normalize(s):
|
|
244
|
+
if s is None:
|
|
245
|
+
return ''
|
|
246
|
+
s = str(s).strip()
|
|
247
|
+
# treat common "unknown" variants as empty so they don't block dedupe
|
|
248
|
+
if s.lower() in {'unknown', 'not specified', 'not provided', 'n/a'}:
|
|
249
|
+
return ''
|
|
250
|
+
# collapse whitespace
|
|
251
|
+
return ' '.join(s.split())
|
|
252
|
+
|
|
253
|
+
def _lower(s):
|
|
254
|
+
return _normalize(s).lower()
|
|
255
|
+
|
|
256
|
+
def _drop_unpopulated_type17(rec):
|
|
257
|
+
size = _lower(rec.get('Size'))
|
|
258
|
+
if not size:
|
|
259
|
+
return True
|
|
260
|
+
if 'no module installed' in size:
|
|
261
|
+
return True
|
|
262
|
+
# Sometimes vendors encode 0-sized entries
|
|
263
|
+
if size.startswith('0 ') or size == '0':
|
|
264
|
+
return True
|
|
265
|
+
return False
|
|
240
266
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
'
|
|
246
|
-
'
|
|
247
|
-
'
|
|
248
|
-
'H': 'D\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0\t\t0'
|
|
267
|
+
# Fields to ignore by DMI type when constructing fingerprints (order-independent)
|
|
268
|
+
IGNORE_BY_TYPE = {
|
|
269
|
+
4: { # Processor Information
|
|
270
|
+
'Socket Designation', 'ID',
|
|
271
|
+
'L1 Cache Handle', 'L2 Cache Handle', 'L3 Cache Handle',
|
|
272
|
+
'Serial Number', 'Asset Tag', 'Part Number',
|
|
273
|
+
'Core Count', 'Core Enabled', # often bogus or per-core
|
|
249
274
|
},
|
|
250
|
-
|
|
251
|
-
'
|
|
252
|
-
'
|
|
253
|
-
'
|
|
254
|
-
'
|
|
255
|
-
'Version': '1.7.1',
|
|
256
|
-
'Release Date': '12/06/2024',
|
|
257
|
-
'ROM Size': '64 MB',
|
|
258
|
-
...,
|
|
275
|
+
17: { # Memory Device
|
|
276
|
+
'Locator', 'Bank Locator', 'Device Locator',
|
|
277
|
+
'Memory Array Mapped Address Handle', 'Mem Array Error Info Handle',
|
|
278
|
+
'Total Width', 'Data Width', # width can vary by board reporting; not essential
|
|
279
|
+
'Serial Number', # sometimes blank; can differ even for identical sticks
|
|
259
280
|
},
|
|
260
|
-
...
|
|
261
281
|
}
|
|
262
|
-
"""
|
|
263
|
-
data = {}
|
|
264
282
|
|
|
283
|
+
def _fingerprint(rec):
|
|
284
|
+
"""Build a stable, type-aware fingerprint for dedupe."""
|
|
285
|
+
dtype = int(rec.get('dmitype', -1))
|
|
286
|
+
ignore = IGNORE_BY_TYPE.get(dtype, set())
|
|
287
|
+
base = (_normalize(rec.get('dminame', '')), dtype)
|
|
288
|
+
|
|
289
|
+
# normalize all fields except ignored + meta
|
|
290
|
+
items = []
|
|
291
|
+
for k in sorted(rec.keys()):
|
|
292
|
+
if k in ('dminame', 'dmitype', 'dmisize'):
|
|
293
|
+
continue
|
|
294
|
+
if k in ignore:
|
|
295
|
+
continue
|
|
296
|
+
v = rec[k]
|
|
297
|
+
# Multi-line blocks were joined with tabs; normalize them
|
|
298
|
+
items.append((k, _normalize(v)))
|
|
299
|
+
return base + tuple(items)
|
|
300
|
+
|
|
301
|
+
# --- parse loop ----------------------------------------------------------
|
|
265
302
|
for record in output.split('\n\n'):
|
|
266
303
|
record_element = record.splitlines()
|
|
267
304
|
if len(record_element) < 3:
|
|
@@ -271,8 +308,8 @@ def dmidecode_parse(output):
|
|
|
271
308
|
if not handle_data:
|
|
272
309
|
continue
|
|
273
310
|
|
|
274
|
-
dmi_handle = handle_data[0]
|
|
275
|
-
|
|
311
|
+
dmi_handle = handle_data[0] # ('0x0004','4','42')
|
|
312
|
+
current = {
|
|
276
313
|
'dminame': record_element[1],
|
|
277
314
|
'dmisize': int(dmi_handle[2]),
|
|
278
315
|
'dmitype': int(dmi_handle[1]),
|
|
@@ -282,11 +319,11 @@ def dmidecode_parse(output):
|
|
|
282
319
|
in_block_list = []
|
|
283
320
|
|
|
284
321
|
for line in record_element[2:]:
|
|
285
|
-
if in_block_element:
|
|
322
|
+
if in_block_element is not None:
|
|
286
323
|
in_block_data = IN_BLOCK_RE.findall(line)
|
|
287
324
|
if in_block_data:
|
|
288
325
|
in_block_list.append(in_block_data[0][0])
|
|
289
|
-
|
|
326
|
+
current[in_block_element] = '\t\t'.join(in_block_list)
|
|
290
327
|
continue
|
|
291
328
|
else:
|
|
292
329
|
in_block_element = None
|
|
@@ -295,7 +332,7 @@ def dmidecode_parse(output):
|
|
|
295
332
|
record_data = RECORD_RE.findall(line)
|
|
296
333
|
if record_data:
|
|
297
334
|
key, value = record_data[0]
|
|
298
|
-
|
|
335
|
+
current[key] = value
|
|
299
336
|
continue
|
|
300
337
|
|
|
301
338
|
record_data2 = RECORD2_RE.findall(line)
|
|
@@ -303,6 +340,54 @@ def dmidecode_parse(output):
|
|
|
303
340
|
in_block_element = record_data2[0][0]
|
|
304
341
|
in_block_list = []
|
|
305
342
|
|
|
343
|
+
# Type-specific filters (drop obviously irrelevant entries)
|
|
344
|
+
dtype = int(current.get('dmitype', -1))
|
|
345
|
+
if dtype == 4:
|
|
346
|
+
# keep only populated/enabled when reported
|
|
347
|
+
status = _lower(current.get('Status'))
|
|
348
|
+
if status and not ('populated' in status and 'enabled' in status):
|
|
349
|
+
continue
|
|
350
|
+
if dtype == 17:
|
|
351
|
+
if _drop_unpopulated_type17(current):
|
|
352
|
+
continue
|
|
353
|
+
|
|
354
|
+
# Build type-aware fingerprint and aggregate
|
|
355
|
+
fp = _fingerprint(current)
|
|
356
|
+
if fp not in seen:
|
|
357
|
+
# first occurrence becomes the representative
|
|
358
|
+
# attach dedupe metadata containers up-front (lazy-friendly)
|
|
359
|
+
rep = dict(current)
|
|
360
|
+
rep['dedup_count'] = 1
|
|
361
|
+
rep['dedup_handles'] = [dmi_handle[0]]
|
|
362
|
+
# capture socket/slot labels if present for admin visibility
|
|
363
|
+
if dtype == 4 and 'Socket Designation' in current:
|
|
364
|
+
rep['dedup_sockets'] = [current['Socket Designation']]
|
|
365
|
+
if dtype == 17:
|
|
366
|
+
labels = []
|
|
367
|
+
for k in ('Locator', 'Device Locator', 'Bank Locator'):
|
|
368
|
+
if current.get(k):
|
|
369
|
+
labels.append(current[k])
|
|
370
|
+
if labels:
|
|
371
|
+
rep['dedup_slots'] = sorted({*labels})
|
|
372
|
+
seen[fp] = (dmi_handle, rep)
|
|
373
|
+
data[dmi_handle] = rep
|
|
374
|
+
else:
|
|
375
|
+
first_handle, rep = seen[fp]
|
|
376
|
+
rep['dedup_count'] = int(rep.get('dedup_count', 1)) + 1
|
|
377
|
+
rep['dedup_handles'].append(dmi_handle[0])
|
|
378
|
+
# enrich socket/slot lists
|
|
379
|
+
if dtype == 4 and current.get('Socket Designation'):
|
|
380
|
+
sockets = set(rep.get('dedup_sockets', []))
|
|
381
|
+
sockets.add(current['Socket Designation'])
|
|
382
|
+
rep['dedup_sockets'] = sorted(sockets)
|
|
383
|
+
if dtype == 17:
|
|
384
|
+
slots = set(rep.get('dedup_slots', []))
|
|
385
|
+
for k in ('Locator', 'Device Locator', 'Bank Locator'):
|
|
386
|
+
if current.get(k):
|
|
387
|
+
slots.add(current[k])
|
|
388
|
+
if slots:
|
|
389
|
+
rep['dedup_slots'] = sorted(slots)
|
|
390
|
+
|
|
306
391
|
return data
|
|
307
392
|
|
|
308
393
|
|