numba_sysinfo.py 26.1 KB
Newer Older
dugupeiwen's avatar
dugupeiwen committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
import json
import locale
import multiprocessing
import os
import platform
import textwrap
import sys
from contextlib import redirect_stdout
from datetime import datetime
from io import StringIO
from subprocess import check_output, PIPE, CalledProcessError
import numpy as np
import llvmlite.binding as llvmbind
from llvmlite import __version__ as llvmlite_version
from numba import cuda as cu, __version__ as version_number
from numba.cuda import cudadrv
from numba.cuda.cudadrv.driver import driver as cudriver
from numba.cuda.cudadrv.runtime import runtime as curuntime
from numba.core import config

_psutil_import = False
try:
    import psutil
except ImportError:
    pass
else:
    _psutil_import = True

__all__ = ['get_sysinfo', 'display_sysinfo']

# Keys of a `sysinfo` dictionary

# Time info
_start, _start_utc, _runtime = 'Start', 'Start UTC', 'Runtime'
_numba_version = 'Numba Version'
# Hardware info
_machine = 'Machine'
_cpu_name, _cpu_count = 'CPU Name', 'CPU Count'
_cpus_allowed, _cpus_list = 'CPUs Allowed', 'List CPUs Allowed'
_cpu_features = 'CPU Features'
_cfs_quota, _cfs_period = 'CFS Quota', 'CFS Period',
_cfs_restrict = 'CFS Restriction'
_mem_total, _mem_available = 'Mem Total', 'Mem Available'
# OS info
_platform_name, _platform_release = 'Platform Name', 'Platform Release'
_os_name, _os_version = 'OS Name', 'OS Version'
_os_spec_version = 'OS Specific Version'
_libc_version = 'Libc Version'
# Python info
_python_comp = 'Python Compiler'
_python_impl = 'Python Implementation'
_python_version = 'Python Version'
_python_locale = 'Python Locale'
# LLVM info
_llvmlite_version = 'llvmlite Version'
_llvm_version = 'LLVM Version'
# CUDA info
_cu_dev_init = 'CUDA Device Init'
_cu_drv_ver = 'CUDA Driver Version'
_cu_rt_ver = 'CUDA Runtime Version'
_cu_nvidia_bindings = 'NVIDIA CUDA Bindings'
_cu_nvidia_bindings_used = 'NVIDIA CUDA Bindings In Use'
_cu_detect_out, _cu_lib_test = 'CUDA Detect Output', 'CUDA Lib Test'
_cu_mvc_available = 'NVIDIA CUDA Minor Version Compatibility Available'
_cu_mvc_needed = 'NVIDIA CUDA Minor Version Compatibility Needed'
_cu_mvc_in_use = 'NVIDIA CUDA Minor Version Compatibility In Use'
# NumPy info
_numpy_version = 'NumPy Version'
_numpy_supported_simd_features = 'NumPy Supported SIMD features'
_numpy_supported_simd_dispatch = 'NumPy Supported SIMD dispatch'
_numpy_supported_simd_baseline = 'NumPy Supported SIMD baseline'
_numpy_AVX512_SKX_detected = 'NumPy AVX512_SKX detected'
# SVML info
_svml_state, _svml_loaded = 'SVML State', 'SVML Lib Loaded'
_llvm_svml_patched = 'LLVM SVML Patched'
_svml_operational = 'SVML Operational'
# Threading layer info
_tbb_thread, _tbb_error = 'TBB Threading', 'TBB Threading Error'
_openmp_thread, _openmp_error = 'OpenMP Threading', 'OpenMP Threading Error'
_openmp_vendor = 'OpenMP vendor'
_wkq_thread, _wkq_error = 'Workqueue Threading', 'Workqueue Threading Error'
# Numba info
_numba_env_vars = 'Numba Env Vars'
# Conda info
_conda_build_ver, _conda_env_ver = 'Conda Build', 'Conda Env'
_conda_platform, _conda_python_ver = 'Conda Platform', 'Conda Python Version'
_conda_root_writable = 'Conda Root Writable'
# Packages info
_inst_pkg = 'Installed Packages'
# Psutil info
_psutil = 'Psutil Available'
# Errors and warnings
_errors = 'Errors'
_warnings = 'Warnings'

# Error and warning log
_error_log = []
_warning_log = []


def get_os_spec_info(os_name):
    # Linux man page for `/proc`:
    # http://man7.org/linux/man-pages/man5/proc.5.html

    # Windows documentation for `wmic OS`:
    # https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-operatingsystem

    # MacOS man page for `sysctl`:
    # https://www.unix.com/man-page/osx/3/sysctl/
    # MacOS man page for `vm_stat`:
    # https://www.unix.com/man-page/osx/1/vm_stat/

    class CmdBufferOut(tuple):
        buffer_output_flag = True

    class CmdReadFile(tuple):
        read_file_flag = True

    shell_params = {
        'Linux': {
            'cmd': (
                CmdReadFile(('/sys/fs/cgroup/cpuacct/cpu.cfs_quota_us',)),
                CmdReadFile(('/sys/fs/cgroup/cpuacct/cpu.cfs_period_us',)),
            ),
            'cmd_optional': (
                CmdReadFile(('/proc/meminfo',)),
                CmdReadFile(('/proc/self/status',)),
            ),
            'kwds': {
                # output string fragment -> result dict key
                'MemTotal:': _mem_total,
                'MemAvailable:': _mem_available,
                'Cpus_allowed:': _cpus_allowed,
                'Cpus_allowed_list:': _cpus_list,
                '/sys/fs/cgroup/cpuacct/cpu.cfs_quota_us': _cfs_quota,
                '/sys/fs/cgroup/cpuacct/cpu.cfs_period_us': _cfs_period,
            },
        },
        'Windows': {
            'cmd': (),
            'cmd_optional': (
                CmdBufferOut(('wmic', 'OS', 'get', 'TotalVirtualMemorySize')),
                CmdBufferOut(('wmic', 'OS', 'get', 'FreeVirtualMemory')),
            ),
            'kwds': {
                # output string fragment -> result dict key
                'TotalVirtualMemorySize': _mem_total,
                'FreeVirtualMemory': _mem_available,
            },
        },
        'Darwin': {
            'cmd': (),
            'cmd_optional': (
                ('sysctl', 'hw.memsize'),
                ('vm_stat'),
            ),
            'kwds': {
                # output string fragment -> result dict key
                'hw.memsize:': _mem_total,
                'free:': _mem_available,
            },
            'units': {
                _mem_total: 1,  # Size is given in bytes.
                _mem_available: 4096,  # Size is given in 4kB pages.
            },
        },
    }

    os_spec_info = {}
    params = shell_params.get(os_name, {})
    cmd_selected = params.get('cmd', ())

    if _psutil_import:
        vm = psutil.virtual_memory()
        os_spec_info.update({
            _mem_total: vm.total,
            _mem_available: vm.available,
        })
        p = psutil.Process()
        cpus_allowed = p.cpu_affinity() if hasattr(p, 'cpu_affinity') else []
        if cpus_allowed:
            os_spec_info[_cpus_allowed] = len(cpus_allowed)
            os_spec_info[_cpus_list] = ' '.join(str(n) for n in cpus_allowed)

    else:
        _warning_log.append(
            "Warning (psutil): psutil cannot be imported. "
            "For more accuracy, consider installing it.")
        # Fallback to internal heuristics
        cmd_selected += params.get('cmd_optional', ())

    # Assuming the shell cmd returns a unique (k, v) pair per line
    # or a unique (k, v) pair spread over several lines:
    # Gather output in a list of strings containing a keyword and some value.
    output = []
    for cmd in cmd_selected:
        if hasattr(cmd, 'read_file_flag'):
            # Open file within Python
            if os.path.exists(cmd[0]):
                try:
                    with open(cmd[0], 'r') as f:
                        out = f.readlines()
                        if out:
                            out[0] = ' '.join((cmd[0], out[0]))
                            output.extend(out)
                except OSError as e:
                    _error_log.append(f'Error (file read): {e}')
                    continue
            else:
                _warning_log.append('Warning (no file): {}'.format(cmd[0]))
                continue
        else:
            # Spawn a subprocess
            try:
                out = check_output(cmd, stderr=PIPE)
            except (OSError, CalledProcessError) as e:
                _error_log.append(f'Error (subprocess): {e}')
                continue
            if hasattr(cmd, 'buffer_output_flag'):
                out = b' '.join(line for line in out.splitlines()) + b'\n'
            output.extend(out.decode().splitlines())

    # Extract (k, output) pairs by searching for keywords in output
    kwds = params.get('kwds', {})
    for line in output:
        match = kwds.keys() & line.split()
        if match and len(match) == 1:
            k = kwds[match.pop()]
            os_spec_info[k] = line
        elif len(match) > 1:
            print(f'Ambiguous output: {line}')

    # Try to extract something meaningful from output string
    def format():
        # CFS restrictions
        split = os_spec_info.get(_cfs_quota, '').split()
        if split:
            os_spec_info[_cfs_quota] = float(split[-1])
        split = os_spec_info.get(_cfs_period, '').split()
        if split:
            os_spec_info[_cfs_period] = float(split[-1])
        if os_spec_info.get(_cfs_quota, -1) != -1:
            cfs_quota = os_spec_info.get(_cfs_quota, '')
            cfs_period = os_spec_info.get(_cfs_period, '')
            runtime_amount = cfs_quota / cfs_period
            os_spec_info[_cfs_restrict] = runtime_amount

    def format_optional():
        # Memory
        units = {_mem_total: 1024, _mem_available: 1024}
        units.update(params.get('units', {}))
        for k in (_mem_total, _mem_available):
            digits = ''.join(d for d in os_spec_info.get(k, '') if d.isdigit())
            os_spec_info[k] = int(digits or 0) * units[k]
        # Accessible CPUs
        split = os_spec_info.get(_cpus_allowed, '').split()
        if split:
            n = split[-1]
            n = n.split(',')[-1]
            os_spec_info[_cpus_allowed] = str(bin(int(n or 0, 16))).count('1')
        split = os_spec_info.get(_cpus_list, '').split()
        if split:
            os_spec_info[_cpus_list] = split[-1]

    try:
        format()
        if not _psutil_import:
            format_optional()
    except Exception as e:
        _error_log.append(f'Error (format shell output): {e}')

    # Call OS specific functions
    os_specific_funcs = {
        'Linux': {
            _libc_version: lambda: ' '.join(platform.libc_ver())
        },
        'Windows': {
            _os_spec_version: lambda: ' '.join(
                s for s in platform.win32_ver()),
        },
        'Darwin': {
            _os_spec_version: lambda: ''.join(
                i or ' ' for s in tuple(platform.mac_ver()) for i in s),
        },
    }
    key_func = os_specific_funcs.get(os_name, {})
    os_spec_info.update({k: f() for k, f in key_func.items()})
    return os_spec_info


def get_sysinfo():

    # Gather the information that shouldn't raise exceptions
    sys_info = {
        _start: datetime.now(),
        _start_utc: datetime.utcnow(),
        _machine: platform.machine(),
        _cpu_name: llvmbind.get_host_cpu_name(),
        _cpu_count: multiprocessing.cpu_count(),
        _platform_name: platform.platform(aliased=True),
        _platform_release: platform.release(),
        _os_name: platform.system(),
        _os_version: platform.version(),
        _python_comp: platform.python_compiler(),
        _python_impl: platform.python_implementation(),
        _python_version: platform.python_version(),
        _numba_env_vars: {k: v for (k, v) in os.environ.items()
                          if k.startswith('NUMBA_')},
        _numba_version: version_number,
        _llvm_version: '.'.join(str(i) for i in llvmbind.llvm_version_info),
        _llvmlite_version: llvmlite_version,
        _psutil: _psutil_import,
    }

    # CPU features
    try:
        feature_map = llvmbind.get_host_cpu_features()
    except RuntimeError as e:
        _error_log.append(f'Error (CPU features): {e}')
    else:
        features = sorted([key for key, value in feature_map.items() if value])
        sys_info[_cpu_features] = ' '.join(features)

    # Python locale
    # On MacOSX, getdefaultlocale can raise. Check again if Py > 3.7.5
    try:
        # If $LANG is unset, getdefaultlocale() can return (None, None), make
        # sure we can encode this as strings by casting explicitly.
        sys_info[_python_locale] = '.'.join([str(i) for i in
                                             locale.getdefaultlocale()])
    except Exception as e:
        _error_log.append(f'Error (locale): {e}')

    # CUDA information
    try:
        cu.list_devices()[0]  # will a device initialise?
    except Exception as e:
        sys_info[_cu_dev_init] = False
        msg_not_found = "CUDA driver library cannot be found"
        msg_disabled_by_user = "CUDA is disabled"
        msg_end = " or no CUDA enabled devices are present."
        msg_generic_problem = "CUDA device initialisation problem."
        msg = getattr(e, 'msg', None)
        if msg is not None:
            if msg_not_found in msg:
                err_msg = msg_not_found + msg_end
            elif msg_disabled_by_user in msg:
                err_msg = msg_disabled_by_user + msg_end
            else:
                err_msg = msg_generic_problem + " Message:" + msg
        else:
            err_msg = msg_generic_problem + " " + str(e)
        # Best effort error report
        _warning_log.append("Warning (cuda): %s\nException class: %s" %
                            (err_msg, str(type(e))))
    else:
        try:
            sys_info[_cu_dev_init] = True

            output = StringIO()
            with redirect_stdout(output):
                cu.detect()
            sys_info[_cu_detect_out] = output.getvalue()
            output.close()

            cu_drv_ver = cudriver.get_version()
            cu_rt_ver = curuntime.get_version()
            sys_info[_cu_drv_ver] = '%s.%s' % cu_drv_ver
            sys_info[_cu_rt_ver] = '%s.%s' % cu_rt_ver

            output = StringIO()
            with redirect_stdout(output):
                cudadrv.libs.test()
            sys_info[_cu_lib_test] = output.getvalue()
            output.close()

            try:
                from cuda import cuda  # noqa: F401
                nvidia_bindings_available = True
            except ImportError:
                nvidia_bindings_available = False
            sys_info[_cu_nvidia_bindings] = nvidia_bindings_available

            nv_binding_used = bool(cudadrv.driver.USE_NV_BINDING)
            sys_info[_cu_nvidia_bindings_used] = nv_binding_used

            try:
                from ptxcompiler import compile_ptx  # noqa: F401
                from cubinlinker import CubinLinker  # noqa: F401
                sys_info[_cu_mvc_available] = True
            except ImportError:
                sys_info[_cu_mvc_available] = False

            sys_info[_cu_mvc_needed] = cu_rt_ver > cu_drv_ver
            sys_info[_cu_mvc_in_use] = bool(
                config.CUDA_ENABLE_MINOR_VERSION_COMPATIBILITY)
        except Exception as e:
            _warning_log.append(
                "Warning (cuda): Probing CUDA failed "
                "(device and driver present, runtime problem?)\n"
                f"(cuda) {type(e)}: {e}")

    # NumPy information
    sys_info[_numpy_version] = np.version.full_version
    try:
        # NOTE: These consts were added in NumPy 1.20
        from numpy.core._multiarray_umath import (__cpu_features__,
                                                  __cpu_dispatch__,
                                                  __cpu_baseline__,)
    except ImportError:
        sys_info[_numpy_AVX512_SKX_detected] = False
    else:
        feat_filtered = [k for k, v in __cpu_features__.items() if v]
        sys_info[_numpy_supported_simd_features] = feat_filtered
        sys_info[_numpy_supported_simd_dispatch] = __cpu_dispatch__
        sys_info[_numpy_supported_simd_baseline] = __cpu_baseline__
        sys_info[_numpy_AVX512_SKX_detected] = \
            __cpu_features__.get("AVX512_SKX", False)

    # SVML information
    # Replicate some SVML detection logic from numba.__init__ here.
    # If SVML load fails in numba.__init__ the splitting of the logic
    # here will help diagnosing the underlying issue.
    svml_lib_loaded = True
    try:
        if sys.platform.startswith('linux'):
            llvmbind.load_library_permanently("libsvml.so")
        elif sys.platform.startswith('darwin'):
            llvmbind.load_library_permanently("libsvml.dylib")
        elif sys.platform.startswith('win'):
            llvmbind.load_library_permanently("svml_dispmd")
        else:
            svml_lib_loaded = False
    except Exception:
        svml_lib_loaded = False
    func = getattr(llvmbind.targets, "has_svml", None)
    sys_info[_llvm_svml_patched] = func() if func else False
    sys_info[_svml_state] = config.USING_SVML
    sys_info[_svml_loaded] = svml_lib_loaded
    sys_info[_svml_operational] = all((
        sys_info[_svml_state],
        sys_info[_svml_loaded],
        sys_info[_llvm_svml_patched],
    ))

    # Check which threading backends are available.
    def parse_error(e, backend):
        # parses a linux based error message, this is to provide feedback
        # and hide user paths etc
        try:
            path, problem, symbol = [x.strip() for x in e.msg.split(':')]
            extn_dso = os.path.split(path)[1]
            if backend in extn_dso:
                return "%s: %s" % (problem, symbol)
        except Exception:
            pass
        return "Unknown import problem."

    try:
        # check import is ok, this means the DSO linkage is working
        from numba.np.ufunc import tbbpool  # NOQA
        # check that the version is compatible, this is a check performed at
        # runtime (well, compile time), it will also ImportError if there's
        # a problem.
        from numba.np.ufunc.parallel import _check_tbb_version_compatible
        _check_tbb_version_compatible()
        sys_info[_tbb_thread] = True
    except ImportError as e:
        # might be a missing symbol due to e.g. tbb libraries missing
        sys_info[_tbb_thread] = False
        sys_info[_tbb_error] = parse_error(e, 'tbbpool')

    try:
        from numba.np.ufunc import omppool
        sys_info[_openmp_thread] = True
        sys_info[_openmp_vendor] = omppool.openmp_vendor
    except ImportError as e:
        sys_info[_openmp_thread] = False
        sys_info[_openmp_error] = parse_error(e, 'omppool')

    try:
        from numba.np.ufunc import workqueue  # NOQA
        sys_info[_wkq_thread] = True
    except ImportError as e:
        sys_info[_wkq_thread] = True
        sys_info[_wkq_error] = parse_error(e, 'workqueue')

    # Look for conda and installed packages information
    cmd = ('conda', 'info', '--json')
    try:
        conda_out = check_output(cmd)
    except Exception as e:
        _warning_log.append(f'Warning: Conda not available.\n Error was {e}\n')
        # Conda is not available, try pip list to list installed packages
        cmd = (sys.executable, '-m', 'pip', 'list')
        try:
            reqs = check_output(cmd)
        except Exception as e:
            _error_log.append(f'Error (pip): {e}')
        else:
            sys_info[_inst_pkg] = reqs.decode().splitlines()

    else:
        jsond = json.loads(conda_out.decode())
        keys = {
            'conda_build_version': _conda_build_ver,
            'conda_env_version': _conda_env_ver,
            'platform': _conda_platform,
            'python_version': _conda_python_ver,
            'root_writable': _conda_root_writable,
        }
        for conda_k, sysinfo_k in keys.items():
            sys_info[sysinfo_k] = jsond.get(conda_k, 'N/A')

        # Get info about packages in current environment
        cmd = ('conda', 'list')
        try:
            conda_out = check_output(cmd)
        except CalledProcessError as e:
            _error_log.append(f'Error (conda): {e}')
        else:
            data = conda_out.decode().splitlines()
            sys_info[_inst_pkg] = [l for l in data if not l.startswith('#')]

    sys_info.update(get_os_spec_info(sys_info[_os_name]))
    sys_info[_errors] = _error_log
    sys_info[_warnings] = _warning_log
    sys_info[_runtime] = (datetime.now() - sys_info[_start]).total_seconds()
    return sys_info


def display_sysinfo(info=None, sep_pos=45):
    class DisplayMap(dict):
        display_map_flag = True

    class DisplaySeq(tuple):
        display_seq_flag = True

    class DisplaySeqMaps(tuple):
        display_seqmaps_flag = True

    if info is None:
        info = get_sysinfo()

    fmt = f'%-{sep_pos}s : %-s'
    MB = 1024**2
    template = (
        ("-" * 80,),
        ("__Time Stamp__",),
        ("Report started (local time)", info.get(_start, '?')),
        ("UTC start time", info.get(_start_utc, '?')),
        ("Running time (s)", info.get(_runtime, '?')),
        ("",),
        ("__Hardware Information__",),
        ("Machine", info.get(_machine, '?')),
        ("CPU Name", info.get(_cpu_name, '?')),
        ("CPU Count", info.get(_cpu_count, '?')),
        ("Number of accessible CPUs", info.get(_cpus_allowed, '?')),
        ("List of accessible CPUs cores", info.get(_cpus_list, '?')),
        ("CFS Restrictions (CPUs worth of runtime)",
            info.get(_cfs_restrict, 'None')),
        ("",),
        ("CPU Features", '\n'.join(
            ' ' * (sep_pos + 3) + l if i else l
            for i, l in enumerate(
                textwrap.wrap(
                    info.get(_cpu_features, '?'),
                    width=79 - sep_pos
                )
            )
        )),
        ("",),
        ("Memory Total (MB)", info.get(_mem_total, 0) // MB or '?'),
        ("Memory Available (MB)"
            if info.get(_os_name, '') != 'Darwin' or info.get(_psutil, False)
            else "Free Memory (MB)", info.get(_mem_available, 0) // MB or '?'),
        ("",),
        ("__OS Information__",),
        ("Platform Name", info.get(_platform_name, '?')),
        ("Platform Release", info.get(_platform_release, '?')),
        ("OS Name", info.get(_os_name, '?')),
        ("OS Version", info.get(_os_version, '?')),
        ("OS Specific Version", info.get(_os_spec_version, '?')),
        ("Libc Version", info.get(_libc_version, '?')),
        ("",),
        ("__Python Information__",),
        DisplayMap({k: v for k, v in info.items() if k.startswith('Python')}),
        ("",),
        ("__Numba Toolchain Versions__",),
        ("Numba Version", info.get(_numba_version, '?')),
        ("llvmlite Version", info.get(_llvmlite_version, '?')),
        ("",),
        ("__LLVM Information__",),
        ("LLVM Version", info.get(_llvm_version, '?')),
        ("",),
        ("__CUDA Information__",),
        ("CUDA Device Initialized", info.get(_cu_dev_init, '?')),
        ("CUDA Driver Version", info.get(_cu_drv_ver, '?')),
        ("CUDA Runtime Version", info.get(_cu_rt_ver, '?')),
        ("CUDA NVIDIA Bindings Available", info.get(_cu_nvidia_bindings, '?')),
        ("CUDA NVIDIA Bindings In Use",
         info.get(_cu_nvidia_bindings_used, '?')),
        ("CUDA Minor Version Compatibility Available",
         info.get(_cu_mvc_available, '?')),
        ("CUDA Minor Version Compatibility Needed",
         info.get(_cu_mvc_needed, '?')),
        ("CUDA Minor Version Compatibility In Use",
         info.get(_cu_mvc_in_use, '?')),
        ("CUDA Detect Output:",),
        (info.get(_cu_detect_out, "None"),),
        ("CUDA Libraries Test Output:",),
        (info.get(_cu_lib_test, "None"),),
        ("",),
        ("__NumPy Information__",),
        ("NumPy Version", info.get(_numpy_version, '?')),
        ("NumPy Supported SIMD features",
         DisplaySeq(info.get(_numpy_supported_simd_features, [])
                    or ('None found.',))),
        ("NumPy Supported SIMD dispatch",
         DisplaySeq(info.get(_numpy_supported_simd_dispatch, [])
                    or ('None found.',))),
        ("NumPy Supported SIMD baseline",
         DisplaySeq(info.get(_numpy_supported_simd_baseline, [])
                    or ('None found.',))),
        ("NumPy AVX512_SKX support detected",
         info.get(_numpy_AVX512_SKX_detected, '?')),
        ("",),
        ("__SVML Information__",),
        ("SVML State, config.USING_SVML", info.get(_svml_state, '?')),
        ("SVML Library Loaded", info.get(_svml_loaded, '?')),
        ("llvmlite Using SVML Patched LLVM", info.get(_llvm_svml_patched, '?')),
        ("SVML Operational", info.get(_svml_operational, '?')),
        ("",),
        ("__Threading Layer Information__",),
        ("TBB Threading Layer Available", info.get(_tbb_thread, '?')),
        ("+-->TBB imported successfully." if info.get(_tbb_thread, '?')
            else f"+--> Disabled due to {info.get(_tbb_error, '?')}",),
        ("OpenMP Threading Layer Available", info.get(_openmp_thread, '?')),
        (f"+-->Vendor: {info.get(_openmp_vendor, '?')}"
            if info.get(_openmp_thread, False)
            else f"+--> Disabled due to {info.get(_openmp_error, '?')}",),
        ("Workqueue Threading Layer Available", info.get(_wkq_thread, '?')),
        ("+-->Workqueue imported successfully." if info.get(_wkq_thread, False)
            else f"+--> Disabled due to {info.get(_wkq_error, '?')}",),
        ("",),
        ("__Numba Environment Variable Information__",),
        (DisplayMap(info.get(_numba_env_vars, {})) or ('None found.',)),
        ("",),
        ("__Conda Information__",),
        (DisplayMap({k: v for k, v in info.items()
                     if k.startswith('Conda')}) or ("Conda not available.",)),
        ("",),
        ("__Installed Packages__",),
        DisplaySeq(info.get(_inst_pkg, ("Couldn't retrieve packages info.",))),
        ("",),
        ("__Error log__" if info.get(_errors, [])
            else "No errors reported.",),
        DisplaySeq(info.get(_errors, [])),
        ("",),
        ("__Warning log__" if info.get(_warnings, [])
            else "No warnings reported.",),
        DisplaySeq(info.get(_warnings, [])),
        ("-" * 80,),
        ("If requested, please copy and paste the information between\n"
         "the dashed (----) lines, or from a given specific section as\n"
         "appropriate.\n\n"
         "=============================================================\n"
         "IMPORTANT: Please ensure that you are happy with sharing the\n"
         "contents of the information present, any information that you\n"
         "wish to keep private you should remove before sharing.\n"
         "=============================================================\n",),
    )
    for t in template:
        if hasattr(t, 'display_seq_flag'):
            print(*t, sep='\n')
        elif hasattr(t, 'display_map_flag'):
            print(*tuple(fmt % (k, v) for (k, v) in t.items()), sep='\n')
        elif hasattr(t, 'display_seqmaps_flag'):
            for d in t:
                print(*tuple(fmt % ('\t' + k, v) for (k, v) in d.items()),
                      sep='\n', end='\n')
        elif len(t) == 2:
            print(fmt % t)
        else:
            print(*t)


if __name__ == '__main__':
    display_sysinfo()