Whole file

workhorsy/py-cpuinfo

The author described this change as Fixed Bug #63: Include py-cpuinfo version in output. It counts as a record because the check below fails on the code as it stood at 1edbcb0aa and passes on f6e2ad387, with nothing else changed between the two runs.

Fix saved2017-04-07
Sharing licenceMIT · LICENSE
Change size+1638 1635

What the code was meant to do, written into the code itself as a save note

Fixed Bug #63: Include py-cpuinfo version in output

The change

2525 # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
2626 # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
2727
28-
29-import os, sys
30-import re
31-import time
32-import platform
33-import multiprocessing
34-import ctypes
35-import pickle
36-import base64
37-import subprocess
38-
39-try:
40- import _winreg as winreg
41-except ImportError as err:
42- try:
43- import winreg
44- except ImportError as err:
45- pass
46-
47-PY2 = sys.version_info[0] == 2
48-
49-
50-class DataSource(object):
51- bits = platform.architecture()[0]
52- cpu_count = multiprocessing.cpu_count()
53- is_windows = platform.system().lower() == 'windows'
54- raw_arch_string = platform.machine()
55- can_cpuid = True
56-
57- @staticmethod
58- def has_proc_cpuinfo():
59- return os.path.exists('/proc/cpuinfo')
60-
61- @staticmethod
62- def has_dmesg():
63- return len(program_paths('dmesg')) > 0
64-
65- @staticmethod
66- def has_var_run_dmesg_boot():
67- return os.path.exists('/var/run/dmesg.boot')
68-
69- @staticmethod
70- def has_cpufreq_info():
71- return len(program_paths('cpufreq-info')) > 0
72-
73- @staticmethod
74- def has_sestatus():
75- return len(program_paths('sestatus')) > 0
76-
77- @staticmethod
78- def has_sysctl():
79- return len(program_paths('sysctl')) > 0
80-
81- @staticmethod
82- def has_isainfo():
83- return len(program_paths('isainfo')) > 0
84-
85- @staticmethod
86- def has_kstat():
87- return len(program_paths('kstat')) > 0
88-
89- @staticmethod
90- def has_sysinfo():
91- return len(program_paths('sysinfo')) > 0
92-
93- @staticmethod
94- def has_lscpu():
95- return len(program_paths('lscpu')) > 0
96-
97- @staticmethod
98- def cat_proc_cpuinfo():
99- return run_and_get_stdout(['cat', '/proc/cpuinfo'])
100-
101- @staticmethod
102- def cpufreq_info():
103- return run_and_get_stdout(['cpufreq-info'])
104-
105- @staticmethod
106- def sestatus_allow_execheap():
107- return run_and_get_stdout(['sestatus', '-b'], ['grep', '-i', '"allow_execheap"'])[1].strip().lower().endswith('on')
108-
109- @staticmethod
110- def sestatus_allow_execmem():
111- return run_and_get_stdout(['sestatus', '-b'], ['grep', '-i', '"allow_execmem"'])[1].strip().lower().endswith('on')
112-
113- @staticmethod
114- def dmesg_a():
115- return run_and_get_stdout(['dmesg', '-a'])
116-
117- @staticmethod
118- def cat_var_run_dmesg_boot():
119- return run_and_get_stdout(['cat', '/var/run/dmesg.boot'])
120-
121- @staticmethod
122- def sysctl_machdep_cpu_hw_cpufrequency():
123- return run_and_get_stdout(['sysctl', 'machdep.cpu', 'hw.cpufrequency'])
124-
125- @staticmethod
126- def isainfo_vb():
127- return run_and_get_stdout(['isainfo', '-vb'])
128-
129- @staticmethod
130- def kstat_m_cpu_info():
131- return run_and_get_stdout(['kstat', '-m', 'cpu_info'])
132-
133- @staticmethod
134- def sysinfo_cpu():
135- return run_and_get_stdout(['sysinfo', '-cpu'])
136-
137- @staticmethod
138- def lscpu():
139- return run_and_get_stdout(['lscpu'])
140-
141- @staticmethod
142- def winreg_processor_brand():
143- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0")
144- processor_brand = winreg.QueryValueEx(key, "ProcessorNameString")[0]
145- winreg.CloseKey(key)
146- return processor_brand
147-
148- @staticmethod
149- def winreg_vendor_id():
150- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0")
151- vendor_id = winreg.QueryValueEx(key, "VendorIdentifier")[0]
152- winreg.CloseKey(key)
153- return vendor_id
154-
155- @staticmethod
156- def winreg_raw_arch_string():
157- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"SYSTEM\CurrentControlSet\Control\Session Manager\Environment")
158- raw_arch_string = winreg.QueryValueEx(key, "PROCESSOR_ARCHITECTURE")[0]
159- winreg.CloseKey(key)
160- return raw_arch_string
161-
162- @staticmethod
163- def winreg_hz_actual():
164- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0")
165- hz_actual = winreg.QueryValueEx(key, "~Mhz")[0]
166- winreg.CloseKey(key)
167- hz_actual = to_hz_string(hz_actual)
168- return hz_actual
169-
170- @staticmethod
171- def winreg_feature_bits():
172- key = winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, r"Hardware\Description\System\CentralProcessor\0")
173- feature_bits = winreg.QueryValueEx(key, "FeatureSet")[0]
174- winreg.CloseKey(key)
175- return feature_bits
176-
177-def obj_to_b64(thing):
178- a = thing
179- b = pickle.dumps(a)
180- c = base64.b64encode(b)
181- d = c.decode('utf8')
182- return d
183-
184-def b64_to_obj(thing):
185- a = base64.b64decode(thing)
186- b = pickle.loads(a)
187- return b
188-
189-def run_and_get_stdout(command, pipe_command=None):
190- if not pipe_command:
191- p1 = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
192- output = p1.communicate()[0]
193- if not PY2:
194- output = output.decode(encoding='UTF-8')
195- return p1.returncode, output
196- else:
197- p1 = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
198- p2 = subprocess.Popen(pipe_command, stdin=p1.stdout, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
199- p1.stdout.close()
200- output = p2.communicate()[0]
201- if not PY2:
202- output = output.decode(encoding='UTF-8')
203- return p2.returncode, output
204-
205-
206-def program_paths(program_name):
207- paths = []
208- exts = filter(None, os.environ.get('PATHEXT', '').split(os.pathsep))
209- path = os.environ['PATH']
210- for p in os.environ['PATH'].split(os.pathsep):
211- p = os.path.join(p, program_name)
212- if os.access(p, os.X_OK):
213- paths.append(p)
214- for e in exts:
215- pext = p + e
216- if os.access(pext, os.X_OK):
217- paths.append(pext)
218- return paths
219-
220-def _get_field_actual(cant_be_number, raw_string, field_names):
221- for line in raw_string.splitlines():
222- for field_name in field_names:
223- field_name = field_name.lower()
224- if ':' in line:
225- left, right = line.split(':', 1)
226- left = left.strip().lower()
227- right = right.strip()
228- if left == field_name and len(right) > 0:
229- if cant_be_number:
230- if not right.isdigit():
231- return right
232- else:
233- return right
234-
235- return None
236-
237-def _get_field(cant_be_number, raw_string, convert_to, default_value, *field_names):
238- retval = _get_field_actual(cant_be_number, raw_string, field_names)
239-
240- # Convert the return value
241- if retval and convert_to:
242- try:
243- retval = convert_to(retval)
244- except:
245- retval = default_value
246-
247- # Return the default if there is no return value
248- if retval is None:
249- retval = default_value
250-
251- return retval
252-
253-def _get_hz_string_from_brand(processor_brand):
254- # Just return 0 if the processor brand does not have the Hz
255- if not 'hz' in processor_brand.lower():
256- return (1, '0.0')
257-
258- hz_brand = processor_brand.lower()
259- scale = 1
260-
261- if hz_brand.endswith('mhz'):
262- scale = 6
263- elif hz_brand.endswith('ghz'):
264- scale = 9
265- if '@' in hz_brand:
266- hz_brand = hz_brand.split('@')[1]
267- else:
268- hz_brand = hz_brand.rsplit(None, 1)[1]
269-
270- hz_brand = hz_brand.rstrip('mhz').rstrip('ghz').strip()
271- hz_brand = to_hz_string(hz_brand)
272-
273- return (scale, hz_brand)
274-
275-def to_friendly_hz(ticks, scale):
276- # Get the raw Hz as a string
277- left, right = to_raw_hz(ticks, scale)
278- ticks = '{0}.{1}'.format(left, right)
279-
280- # Get the location of the dot, and remove said dot
281- dot_index = ticks.index('.')
282- ticks = ticks.replace('.', '')
283-
284- # Get the Hz symbol and scale
285- symbol = "Hz"
286- scale = 0
287- if dot_index > 9:
288- symbol = "GHz"
289- scale = 9
290- elif dot_index > 6:
291- symbol = "MHz"
292- scale = 6
293- elif dot_index > 3:
294- symbol = "KHz"
295- scale = 3
296-
297- # Get the Hz with the dot at the new scaled point
298- ticks = '{0}.{1}'.format(ticks[:-scale-1], ticks[-scale-1:])
299-
300- # Format the ticks to have 4 numbers after the decimal
301- # and remove any superfluous zeroes.
302- ticks = '{0:.4f} {1}'.format(float(ticks), symbol)
303- ticks = ticks.rstrip('0')
304-
305- return ticks
306-
307-def to_raw_hz(ticks, scale):
308- # Scale the numbers
309- ticks = ticks.lstrip('0')
310- old_index = ticks.index('.')
311- ticks = ticks.replace('.', '')
312- ticks = ticks.ljust(scale + old_index+1, '0')
313- new_index = old_index + scale
314- ticks = '{0}.{1}'.format(ticks[:new_index], ticks[new_index:])
315- left, right = ticks.split('.')
316- left, right = int(left), int(right)
317- return (left, right)
318-
319-def to_hz_string(ticks):
320- # Convert to string
321- ticks = '{0}'.format(ticks)
322-
323- # Add decimal if missing
324- if '.' not in ticks:
325- ticks = '{0}.0'.format(ticks)
326-
327- # Remove trailing zeros
328- ticks = ticks.rstrip('0')
329-
330- # Add one trailing zero for empty right side
331- if ticks.endswith('.'):
332- ticks = '{0}0'.format(ticks)
333-
334- return ticks
335-
336-def _parse_cpu_string(cpu_string):
337- # Get location of fields at end of string
338- fields_index = cpu_string.find('(', cpu_string.find('@'))
339- #print(fields_index)
340-
341- # Processor Brand
342- processor_brand = cpu_string
343- if fields_index != -1:
344- processor_brand = cpu_string[0 : fields_index].strip()
345- #print('processor_brand: ', processor_brand)
346-
347- fields = None
348- if fields_index != -1:
349- fields = cpu_string[fields_index : ]
350- #print('fields: ', fields)
351-
352- # Hz
353- scale, hz_brand = _get_hz_string_from_brand(processor_brand)
354-
355- # Various fields
356- vendor_id, stepping, model, family = (None, None, None, None)
357- if fields:
358- try:
359- fields = fields.rsplit('(', 1)[1].split(')')[0].split(',')
360- fields = [f.strip().lower() for f in fields]
361- fields = [f.split(':') for f in fields]
362- fields = [{f[0].strip() : f[1].strip()} for f in fields]
363- #print('fields: ', fields)
364- for field in fields:
365- name = list(field.keys())[0]
366- value = list(field.values())[0]
367- #print('name:{0}, value:{1}'.format(name, value))
368- if name == 'origin':
369- vendor_id = value.strip('"')
370- elif name == 'stepping':
371- stepping = int(value.lstrip('0x'), 16)
372- elif name == 'model':
373- model = int(value.lstrip('0x'), 16)
374- elif name in ['fam', 'family']:
375- family = int(value.lstrip('0x'), 16)
376- except:
377- #raise
378- pass
379-
380- return (processor_brand, hz_brand, scale, vendor_id, stepping, model, family)
381-
382-def _parse_dmesg_output(output):
383- try:
384- # Get all the dmesg lines that might contain a CPU string
385- lines = output.split(' CPU0:')[1:] + \
386- output.split(' CPU1:')[1:] + \
387- output.split(' CPU:')[1:] + \
388- output.split('\nCPU0:')[1:] + \
389- output.split('\nCPU1:')[1:] + \
390- output.split('\nCPU:')[1:]
391- lines = [l.split('\n')[0].strip() for l in lines]
392-
393- # Convert the lines to CPU strings
394- cpu_strings = [_parse_cpu_string(l) for l in lines]
395-
396- # Find the CPU string that has the most fields
397- best_string = None
398- highest_count = 0
399- for cpu_string in cpu_strings:
400- count = sum([n is not None for n in cpu_string])
401- if count > highest_count:
402- highest_count = count
403- best_string = cpu_string
404-
405- # If no CPU string was found, return {}
406- if not best_string:
407- return {}
408-
409- processor_brand, hz_actual, scale, vendor_id, stepping, model, family = best_string
410-
411- # Origin
412- if ' Origin=' in output:
413- fields = output[output.find(' Origin=') : ].split('\n')[0]
414- fields = fields.strip().split()
415- fields = [n.strip().split('=') for n in fields]
416- fields = [{n[0].strip().lower() : n[1].strip()} for n in fields]
417- #print('fields: ', fields)
418-
419- for field in fields:
420- name = list(field.keys())[0]
421- value = list(field.values())[0]
422- #print('name:{0}, value:{1}'.format(name, value))
423- if name == 'origin':
424- vendor_id = value.strip('"')
2879 further changed lines not shown

The check that tells the two apart

failpass·tests/test_free_bsd_11_x86_64.py::TestFreeBSD_11_X86_64::test_returns

Check file tests/test_free_bsd_11_x86_64.py, taken without changes from the fix and copied onto the older code, so the exact same check runs against both versions.

Origin and history

The code before it1edbcb0aac3c777eaaf035bd448258886c2ecf37
Broken version dated2017-04-07
Modulecpuinfo.cpuinfo
Units changedget_cpu_info, main
Fingerprinted302abfd9c53094
Checked2026-08-18 by goldset/0.1

Every field above is generated by our program. None of it is written by hand.

Other bugs found in workhorsy/py-cpuinfo