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.
Projectworkhorsy/py-cpuinfo
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
| 25 | 25 | # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE | |
| 26 | 26 | # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
| 27 | 27 | ||
| 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
fail→pass·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
- 2019-05-09Fixed issue with CPUID tests having wrong mock Hz.
- 2017-04-19_get_cpu_info_from_ibm_pa_features
- 2017-04-10_get_cpu_info_from_cpufreq_info
- 2017-04-05_get_cpu_info_from_sysctl