1import ctypes
2import json
3import locale
4import multiprocessing
5import os
6import platform
7import textwrap
8import sys
9from contextlib import redirect_stdout
10from datetime import datetime
11from io import StringIO
12from subprocess import check_output, PIPE, CalledProcessError
13import llvmlite.binding as llvmbind
14from numba import cuda as cu
15from numba import roc
16from numba.roc.hlc import hlc, libhlc
17from numba.cuda import cudadrv
18from numba.cuda.cudadrv.driver import driver as cudriver
19from numba.core import config
20
21_psutil_import = False
22try:
23    import psutil
24except ImportError:
25    pass
26else:
27    _psutil_import = True
28
29__all__ = ['get_sysinfo', 'display_sysinfo']
30
31# Keys of a `sysinfo` dictionary
32
33# Time info
34_start, _start_utc, _runtime = 'Start', 'Start UTC', 'Runtime'
35# Hardware info
36_machine = 'Machine'
37_cpu_name, _cpu_count = 'CPU Name', 'CPU Count'
38_cpus_allowed, _cpus_list = 'CPUs Allowed', 'List CPUs Allowed'
39_cpu_features = 'CPU Features'
40_cfs_quota, _cfs_period = 'CFS Quota', 'CFS Period',
41_cfs_restrict = 'CFS Restriction'
42_mem_total, _mem_available = 'Mem Total', 'Mem Available'
43# OS info
44_platform_name, _platform_release = 'Platform Name', 'Platform Release'
45_os_name, _os_version = 'OS Name', 'OS Version'
46_os_spec_version = 'OS Specific Version'
47_libc_version = 'Libc Version'
48# Python info
49_python_comp = 'Python Compiler'
50_python_impl = 'Python Implementation'
51_python_version = 'Python Version'
52_python_locale = 'Python Locale'
53# LLVM info
54_llvm_version = 'LLVM Version'
55# CUDA info
56_cu_dev_init = 'CUDA Device Init'
57_cu_drv_ver = 'CUDA Driver Version'
58_cu_detect_out, _cu_lib_test = 'CUDA Detect Output', 'CUDA Lib Test'
59# ROC information
60_roc_available, _roc_toolchains = 'ROC Available', 'ROC Toolchains'
61_hsa_agents_count, _hsa_agents = 'HSA Agents Count', 'HSA Agents'
62_hsa_gpus_count, _hsa_gpus = 'HSA Discrete GPUs Count', 'HSA Discrete GPUs'
63# SVML info
64_svml_state, _svml_loaded = 'SVML State', 'SVML Lib Loaded'
65_llvm_svml_patched = 'LLVM SVML Patched'
66_svml_operational = 'SVML Operational'
67# Threading layer info
68_tbb_thread, _tbb_error = 'TBB Threading', 'TBB Threading Error'
69_openmp_thread, _openmp_error = 'OpenMP Threading', 'OpenMP Threading Error'
70_openmp_vendor = 'OpenMP vendor'
71_wkq_thread, _wkq_error = 'Workqueue Threading', 'Workqueue Threading Error'
72# Numba info
73_numba_env_vars = 'Numba Env Vars'
74# Conda info
75_conda_build_ver, _conda_env_ver = 'Conda Build', 'Conda Env'
76_conda_platform, _conda_python_ver = 'Conda Platform', 'Conda Python Version'
77_conda_root_writable = 'Conda Root Writable'
78# Packages info
79_inst_pkg = 'Installed Packages'
80# Psutil info
81_psutil = 'Psutil Available'
82# Errors and warnings
83_errors = 'Errors'
84_warnings = 'Warnings'
85
86# Error and warning log
87_error_log = []
88_warning_log = []
89
90
91def get_os_spec_info(os_name):
92    # Linux man page for `/proc`:
93    # http://man7.org/linux/man-pages/man5/proc.5.html
94
95    # Windows documentation for `wmic OS`:
96    # https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/cim-operatingsystem
97
98    # MacOS man page for `sysctl`:
99    # https://www.unix.com/man-page/osx/3/sysctl/
100    # MacOS man page for `vm_stat`:
101    # https://www.unix.com/man-page/osx/1/vm_stat/
102
103    class CmdBufferOut(tuple):
104        buffer_output_flag = True
105
106    class CmdReadFile(tuple):
107        read_file_flag = True
108
109    shell_params = {
110        'Linux': {
111            'cmd': (
112                CmdReadFile(('/sys/fs/cgroup/cpuacct/cpu.cfs_quota_us',)),
113                CmdReadFile(('/sys/fs/cgroup/cpuacct/cpu.cfs_period_us',)),
114            ),
115            'cmd_optional': (
116                CmdReadFile(('/proc/meminfo',)),
117                CmdReadFile(('/proc/self/status',)),
118            ),
119            'kwds': {
120                # output string fragment -> result dict key
121                'MemTotal:': _mem_total,
122                'MemAvailable:': _mem_available,
123                'Cpus_allowed:': _cpus_allowed,
124                'Cpus_allowed_list:': _cpus_list,
125                '/sys/fs/cgroup/cpuacct/cpu.cfs_quota_us': _cfs_quota,
126                '/sys/fs/cgroup/cpuacct/cpu.cfs_period_us': _cfs_period,
127            },
128        },
129        'Windows': {
130            'cmd': (),
131            'cmd_optional': (
132                CmdBufferOut(('wmic', 'OS', 'get', 'TotalVirtualMemorySize')),
133                CmdBufferOut(('wmic', 'OS', 'get', 'FreeVirtualMemory')),
134            ),
135            'kwds': {
136                # output string fragment -> result dict key
137                'TotalVirtualMemorySize': _mem_total,
138                'FreeVirtualMemory': _mem_available,
139            },
140        },
141        'Darwin': {
142            'cmd': (),
143            'cmd_optional': (
144                ('sysctl', 'hw.memsize'),
145                ('vm_stat'),
146            ),
147            'kwds': {
148                # output string fragment -> result dict key
149                'hw.memsize:': _mem_total,
150                'free:': _mem_available,
151            },
152            'units': {
153                _mem_total: 1,  # Size is given in bytes.
154                _mem_available: 4096,  # Size is given in 4kB pages.
155            },
156        },
157    }
158
159    os_spec_info = {}
160    params = shell_params.get(os_name, {})
161    cmd_selected = params.get('cmd', ())
162
163    if _psutil_import:
164        vm = psutil.virtual_memory()
165        os_spec_info.update({
166            _mem_total: vm.total,
167            _mem_available: vm.available,
168        })
169        p = psutil.Process()
170        cpus_allowed = p.cpu_affinity() if hasattr(p, 'cpu_affinity') else []
171        if cpus_allowed:
172            os_spec_info[_cpus_allowed] = len(cpus_allowed)
173            os_spec_info[_cpus_list] = ' '.join(str(n) for n in cpus_allowed)
174
175    else:
176        _warning_log.append(
177            "Warning (psutil): psutil cannot be imported. "
178            "For more accuracy, consider installing it.")
179        # Fallback to internal heuristics
180        cmd_selected += params.get('cmd_optional', ())
181
182    # Assuming the shell cmd returns a unique (k, v) pair per line
183    # or a unique (k, v) pair spread over several lines:
184    # Gather output in a list of strings containing a keyword and some value.
185    output = []
186    for cmd in cmd_selected:
187        if hasattr(cmd, 'read_file_flag'):
188            # Open file within Python
189            if os.path.exists(cmd[0]):
190                try:
191                    with open(cmd[0], 'r') as f:
192                        out = f.readlines()
193                        if out:
194                            out[0] = ' '.join((cmd[0], out[0]))
195                            output.extend(out)
196                except OSError as e:
197                    _error_log.append(f'Error (file read): {e}')
198                    continue
199            else:
200                _warning_log.append('Warning (no file): {}'.format(cmd[0]))
201                continue
202        else:
203            # Spawn a subprocess
204            try:
205                out = check_output(cmd, stderr=PIPE)
206            except (OSError, CalledProcessError) as e:
207                _error_log.append(f'Error (subprocess): {e}')
208                continue
209            if hasattr(cmd, 'buffer_output_flag'):
210                out = b' '.join(line for line in out.splitlines()) + b'\n'
211            output.extend(out.decode().splitlines())
212
213    # Extract (k, output) pairs by searching for keywords in output
214    kwds = params.get('kwds', {})
215    for line in output:
216        match = kwds.keys() & line.split()
217        if match and len(match) == 1:
218            k = kwds[match.pop()]
219            os_spec_info[k] = line
220        elif len(match) > 1:
221            print(f'Ambiguous output: {line}')
222
223    # Try to extract something meaningful from output string
224    def format():
225        # CFS restrictions
226        split = os_spec_info.get(_cfs_quota, '').split()
227        if split:
228            os_spec_info[_cfs_quota] = float(split[-1])
229        split = os_spec_info.get(_cfs_period, '').split()
230        if split:
231            os_spec_info[_cfs_period] = float(split[-1])
232        if os_spec_info.get(_cfs_quota, -1) != -1:
233            cfs_quota = os_spec_info.get(_cfs_quota, '')
234            cfs_period = os_spec_info.get(_cfs_period, '')
235            runtime_amount = cfs_quota / cfs_period
236            os_spec_info[_cfs_restrict] = runtime_amount
237
238    def format_optional():
239        # Memory
240        units = {_mem_total: 1024, _mem_available: 1024}
241        units.update(params.get('units', {}))
242        for k in (_mem_total, _mem_available):
243            digits = ''.join(d for d in os_spec_info.get(k, '') if d.isdigit())
244            os_spec_info[k] = int(digits or 0) * units[k]
245        # Accessible CPUs
246        split = os_spec_info.get(_cpus_allowed, '').split()
247        if split:
248            n = split[-1]
249            n = n.split(',')[-1]
250            os_spec_info[_cpus_allowed] = str(bin(int(n or 0, 16))).count('1')
251        split = os_spec_info.get(_cpus_list, '').split()
252        if split:
253            os_spec_info[_cpus_list] = split[-1]
254
255    try:
256        format()
257        if not _psutil_import:
258            format_optional()
259    except Exception as e:
260        _error_log.append(f'Error (format shell output): {e}')
261
262    # Call OS specific functions
263    os_specific_funcs = {
264        'Linux': {
265            _libc_version: lambda: ' '.join(platform.libc_ver())
266        },
267        'Windows': {
268            _os_spec_version: lambda: ' '.join(
269                s for s in platform.win32_ver()),
270        },
271        'Darwin': {
272            _os_spec_version: lambda: ''.join(
273                i or ' ' for s in tuple(platform.mac_ver()) for i in s),
274        },
275    }
276    key_func = os_specific_funcs.get(os_name, {})
277    os_spec_info.update({k: f() for k, f in key_func.items()})
278    return os_spec_info
279
280
281def get_sysinfo():
282
283    # Gather the information that shouldn't raise exceptions
284    sys_info = {
285        _start: datetime.now(),
286        _start_utc: datetime.utcnow(),
287        _machine: platform.machine(),
288        _cpu_name: llvmbind.get_host_cpu_name(),
289        _cpu_count: multiprocessing.cpu_count(),
290        _platform_name: platform.platform(aliased=True),
291        _platform_release: platform.release(),
292        _os_name: platform.system(),
293        _os_version: platform.version(),
294        _python_comp: platform.python_compiler(),
295        _python_impl: platform.python_implementation(),
296        _python_version: platform.python_version(),
297        _numba_env_vars: {k: v for (k, v) in os.environ.items()
298                          if k.startswith('NUMBA_')},
299        _llvm_version: '.'.join(str(i) for i in llvmbind.llvm_version_info),
300        _roc_available: roc.is_available(),
301        _psutil: _psutil_import,
302    }
303
304    # CPU features
305    try:
306        feature_map = llvmbind.get_host_cpu_features()
307    except RuntimeError as e:
308        _error_log.append(f'Error (CPU features): {e}')
309    else:
310        features = sorted([key for key, value in feature_map.items() if value])
311        sys_info[_cpu_features] = ' '.join(features)
312
313    # Python locale
314    # On MacOSX, getdefaultlocale can raise. Check again if Py > 3.7.5
315    try:
316        # If $LANG is unset, getdefaultlocale() can return (None, None), make
317        # sure we can encode this as strings by casting explicitly.
318        sys_info[_python_locale] = '.'.join([str(i) for i in
319                                             locale.getdefaultlocale()])
320    except Exception as e:
321        _error_log.append(f'Error (locale): {e}')
322
323    # CUDA information
324    try:
325        cu.list_devices()[0]  # will a device initialise?
326    except Exception as e:
327        sys_info[_cu_dev_init] = False
328        msg_not_found = "CUDA driver library cannot be found"
329        msg_disabled_by_user = "CUDA is disabled"
330        msg_end = " or no CUDA enabled devices are present."
331        msg_generic_problem = "CUDA device intialisation problem."
332        msg = getattr(e, 'msg', None)
333        if msg is not None:
334            if msg_not_found in msg:
335                err_msg = msg_not_found + msg_end
336            elif msg_disabled_by_user in msg:
337                err_msg = msg_disabled_by_user + msg_end
338            else:
339                err_msg = msg_generic_problem + " Message:" + msg
340        else:
341            err_msg = msg_generic_problem + " " + str(e)
342        # Best effort error report
343        _warning_log.append("Warning (cuda): %s\nException class: %s" %
344                            (err_msg, str(type(e))))
345    else:
346        try:
347            sys_info[_cu_dev_init] = True
348
349            output = StringIO()
350            with redirect_stdout(output):
351                cu.detect()
352            sys_info[_cu_detect_out] = output.getvalue()
353            output.close()
354
355            dv = ctypes.c_int(0)
356            cudriver.cuDriverGetVersion(ctypes.byref(dv))
357            sys_info[_cu_drv_ver] = dv.value
358
359            output = StringIO()
360            with redirect_stdout(output):
361                cudadrv.libs.test(sys.platform, print_paths=False)
362            sys_info[_cu_lib_test] = output.getvalue()
363            output.close()
364        except Exception as e:
365            _warning_log.append(
366                "Warning (cuda): Probing CUDA failed "
367                "(device and driver present, runtime problem?)\n"
368                f"(cuda) {type(e)}: {e}")
369
370    # ROC information
371    # If no ROC try and report why
372    if not sys_info[_roc_available]:
373        from numba.roc.hsadrv.driver import hsa
374        try:
375            hsa.is_available
376        except Exception as e:
377            msg = str(e)
378        else:
379            msg = 'No ROC toolchains found.'
380        _warning_log.append(f"Warning (roc): Error initialising ROC: {msg}")
381
382    toolchains = []
383    try:
384        libhlc.HLC()
385        toolchains.append('librocmlite library')
386    except Exception:
387        pass
388    try:
389        cmd = hlc.CmdLine().check_tooling()
390        toolchains.append('ROC command line tools')
391    except Exception:
392        pass
393    sys_info[_roc_toolchains] = toolchains
394
395    try:
396        # ROC might not be available due to lack of tool chain, but HSA
397        # agents may be listed
398        from numba.roc.hsadrv.driver import hsa, dgpu_count
399
400        def decode(x):
401            return x.decode('utf-8') if isinstance(x, bytes) else x
402
403        sys_info[_hsa_agents_count] = len(hsa.agents)
404        agents = []
405        for i, agent in enumerate(hsa.agents):
406            agents.append({
407                'Agent id': i,
408                'Vendor': decode(agent.vendor_name),
409                'Name': decode(agent.name),
410                'Type': agent.device,
411            })
412        sys_info[_hsa_agents] = agents
413
414        _dgpus = []
415        for a in hsa.agents:
416            if a.is_component and a.device == 'GPU':
417                _dgpus.append(decode(a.name))
418        sys_info[_hsa_gpus_count] = dgpu_count()
419        sys_info[_hsa_gpus] = ', '.join(_dgpus)
420    except Exception as e:
421        _warning_log.append(
422            "Warning (roc): No HSA Agents found, "
423            f"encountered exception when searching: {e}")
424
425    # SVML information
426    # Replicate some SVML detection logic from numba.__init__ here.
427    # If SVML load fails in numba.__init__ the splitting of the logic
428    # here will help diagnosing the underlying issue.
429    svml_lib_loaded = True
430    try:
431        if sys.platform.startswith('linux'):
432            llvmbind.load_library_permanently("libsvml.so")
433        elif sys.platform.startswith('darwin'):
434            llvmbind.load_library_permanently("libsvml.dylib")
435        elif sys.platform.startswith('win'):
436            llvmbind.load_library_permanently("svml_dispmd")
437        else:
438            svml_lib_loaded = False
439    except Exception:
440        svml_lib_loaded = False
441    func = getattr(llvmbind.targets, "has_svml", None)
442    sys_info[_llvm_svml_patched] = func() if func else False
443    sys_info[_svml_state] = config.USING_SVML
444    sys_info[_svml_loaded] = svml_lib_loaded
445    sys_info[_svml_operational] = all((
446        sys_info[_svml_state],
447        sys_info[_svml_loaded],
448        sys_info[_llvm_svml_patched],
449    ))
450
451    # Check which threading backends are available.
452    def parse_error(e, backend):
453        # parses a linux based error message, this is to provide feedback
454        # and hide user paths etc
455        try:
456            path, problem, symbol = [x.strip() for x in e.msg.split(':')]
457            extn_dso = os.path.split(path)[1]
458            if backend in extn_dso:
459                return "%s: %s" % (problem, symbol)
460        except Exception:
461            pass
462        return "Unknown import problem."
463
464    try:
465        from numba.np.ufunc import tbbpool  # NOQA
466        sys_info[_tbb_thread] = True
467    except ImportError as e:
468        # might be a missing symbol due to e.g. tbb libraries missing
469        sys_info[_tbb_thread] = False
470        sys_info[_tbb_error] = parse_error(e, 'tbbpool')
471
472    try:
473        from numba.np.ufunc import omppool
474        sys_info[_openmp_thread] = True
475        sys_info[_openmp_vendor] = omppool.openmp_vendor
476    except ImportError as e:
477        sys_info[_openmp_thread] = False
478        sys_info[_openmp_error] = parse_error(e, 'omppool')
479
480    try:
481        from numba.np.ufunc import workqueue  # NOQA
482        sys_info[_wkq_thread] = True
483    except ImportError as e:
484        sys_info[_wkq_thread] = True
485        sys_info[_wkq_error] = parse_error(e, 'workqueue')
486
487    # Look for conda and installed packages information
488    cmd = ('conda', 'info', '--json')
489    try:
490        conda_out = check_output(cmd)
491    except Exception as e:
492        _warning_log.append(f'Warning: Conda not available.\n Error was {e}\n')
493        # Conda is not available, try pip list to list installed packages
494        cmd = (sys.executable, '-m', 'pip', 'list')
495        try:
496            reqs = check_output(cmd)
497        except Exception as e:
498            _error_log.append(f'Error (pip): {e}')
499        else:
500            sys_info[_inst_pkg] = reqs.decode().splitlines()
501
502    else:
503        jsond = json.loads(conda_out.decode())
504        keys = {
505            'conda_build_version': _conda_build_ver,
506            'conda_env_version': _conda_env_ver,
507            'platform': _conda_platform,
508            'python_version': _conda_python_ver,
509            'root_writable': _conda_root_writable,
510        }
511        for conda_k, sysinfo_k in keys.items():
512            sys_info[sysinfo_k] = jsond.get(conda_k, 'N/A')
513
514        # Get info about packages in current environment
515        cmd = ('conda', 'list')
516        try:
517            conda_out = check_output(cmd)
518        except CalledProcessError as e:
519            _error_log.append(f'Error (conda): {e}')
520        else:
521            data = conda_out.decode().splitlines()
522            sys_info[_inst_pkg] = [l for l in data if not l.startswith('#')]
523
524    sys_info.update(get_os_spec_info(sys_info[_os_name]))
525    sys_info[_errors] = _error_log
526    sys_info[_warnings] = _warning_log
527    sys_info[_runtime] = (datetime.now() - sys_info[_start]).total_seconds()
528    return sys_info
529
530
531def display_sysinfo(info=None, sep_pos=45):
532    class DisplayMap(dict):
533        display_map_flag = True
534
535    class DisplaySeq(tuple):
536        display_seq_flag = True
537
538    class DisplaySeqMaps(tuple):
539        display_seqmaps_flag = True
540
541    if info is None:
542        info = get_sysinfo()
543
544    fmt = f'%-{sep_pos}s : %-s'
545    MB = 1024**2
546    template = (
547        ("-" * 80,),
548        ("__Time Stamp__",),
549        ("Report started (local time)", info.get(_start, '?')),
550        ("UTC start time", info.get(_start_utc, '?')),
551        ("Running time (s)", info.get(_runtime, '?')),
552        ("",),
553        ("__Hardware Information__",),
554        ("Machine", info.get(_machine, '?')),
555        ("CPU Name", info.get(_cpu_name, '?')),
556        ("CPU Count", info.get(_cpu_count, '?')),
557        ("Number of accessible CPUs", info.get(_cpus_allowed, '?')),
558        ("List of accessible CPUs cores", info.get(_cpus_list, '?')),
559        ("CFS Restrictions (CPUs worth of runtime)",
560            info.get(_cfs_restrict, 'None')),
561        ("",),
562        ("CPU Features", '\n'.join(
563            ' ' * (sep_pos + 3) + l if i else l
564            for i, l in enumerate(
565                textwrap.wrap(
566                    info.get(_cpu_features, '?'),
567                    width=79 - sep_pos
568                )
569            )
570        )),
571        ("",),
572        ("Memory Total (MB)", info.get(_mem_total, 0) // MB or '?'),
573        ("Memory Available (MB)"
574            if info.get(_os_name, '') != 'Darwin' or info.get(_psutil, False)
575            else "Free Memory (MB)", info.get(_mem_available, 0) // MB or '?'),
576        ("",),
577        ("__OS Information__",),
578        ("Platform Name", info.get(_platform_name, '?')),
579        ("Platform Release", info.get(_platform_release, '?')),
580        ("OS Name", info.get(_os_name, '?')),
581        ("OS Version", info.get(_os_version, '?')),
582        ("OS Specific Version", info.get(_os_spec_version, '?')),
583        ("Libc Version", info.get(_libc_version, '?')),
584        ("",),
585        ("__Python Information__",),
586        DisplayMap({k: v for k, v in info.items() if k.startswith('Python')}),
587        ("",),
588        ("__LLVM Information__",),
589        ("LLVM Version", info.get(_llvm_version, '?')),
590        ("",),
591        ("__CUDA Information__",),
592        ("CUDA Device Initialized", info.get(_cu_dev_init, '?')),
593        ("CUDA Driver Version", info.get(_cu_drv_ver, '?')),
594        ("CUDA Detect Output:",),
595        (info.get(_cu_detect_out, "None"),),
596        ("CUDA Librairies Test Output:",),
597        (info.get(_cu_lib_test, "None"),),
598        ("",),
599        ("__ROC information__",),
600        ("ROC Available", info.get(_roc_available, '?')),
601        ("ROC Toolchains", info.get(_roc_toolchains, []) or 'None'),
602        ("HSA Agents Count", info.get(_hsa_agents_count, 0)),
603        ("HSA Agents:",),
604        (DisplaySeqMaps(info.get(_hsa_agents, {})) or ('None',)),
605        ('HSA Discrete GPUs Count', info.get(_hsa_gpus_count, 0)),
606        ('HSA Discrete GPUs', info.get(_hsa_gpus, 'None')),
607        ("",),
608        ("__SVML Information__",),
609        ("SVML State, config.USING_SVML", info.get(_svml_state, '?')),
610        ("SVML Library Loaded", info.get(_svml_loaded, '?')),
611        ("llvmlite Using SVML Patched LLVM", info.get(_llvm_svml_patched, '?')),
612        ("SVML Operational", info.get(_svml_operational, '?')),
613        ("",),
614        ("__Threading Layer Information__",),
615        ("TBB Threading Layer Available", info.get(_tbb_thread, '?')),
616        ("+-->TBB imported successfully." if info.get(_tbb_thread, '?')
617            else f"+--> Disabled due to {info.get(_tbb_error, '?')}",),
618        ("OpenMP Threading Layer Available", info.get(_openmp_thread, '?')),
619        (f"+-->Vendor: {info.get(_openmp_vendor, '?')}"
620            if info.get(_openmp_thread, False)
621            else f"+--> Disabled due to {info.get(_openmp_error, '?')}",),
622        ("Workqueue Threading Layer Available", info.get(_wkq_thread, '?')),
623        ("+-->Workqueue imported successfully." if info.get(_wkq_thread, False)
624            else f"+--> Disabled due to {info.get(_wkq_error, '?')}",),
625        ("",),
626        ("__Numba Environment Variable Information__",),
627        (DisplayMap(info.get(_numba_env_vars, {})) or ('None found.',)),
628        ("",),
629        ("__Conda Information__",),
630        (DisplayMap({k: v for k, v in info.items()
631                     if k.startswith('Conda')}) or ("Conda not available.",)),
632        ("",),
633        ("__Installed Packages__",),
634        DisplaySeq(info.get(_inst_pkg, ("Couldn't retrieve packages info.",))),
635        ("",),
636        ("__Error log__" if info.get(_errors, [])
637            else "No errors reported.",),
638        DisplaySeq(info.get(_errors, [])),
639        ("",),
640        ("__Warning log__" if info.get(_warnings, [])
641            else "No warnings reported.",),
642        DisplaySeq(info.get(_warnings, [])),
643        ("-" * 80,),
644        ("If requested, please copy and paste the information between\n"
645         "the dashed (----) lines, or from a given specific section as\n"
646         "appropriate.\n\n"
647         "=============================================================\n"
648         "IMPORTANT: Please ensure that you are happy with sharing the\n"
649         "contents of the information present, any information that you\n"
650         "wish to keep private you should remove before sharing.\n"
651         "=============================================================\n",),
652    )
653    for t in template:
654        if hasattr(t, 'display_seq_flag'):
655            print(*t, sep='\n')
656        elif hasattr(t, 'display_map_flag'):
657            print(*tuple(fmt % (k, v) for (k, v) in t.items()), sep='\n')
658        elif hasattr(t, 'display_seqmaps_flag'):
659            for d in t:
660                print(*tuple(fmt % ('\t' + k, v) for (k, v) in d.items()),
661                      sep='\n', end='\n')
662        elif len(t) == 2:
663            print(fmt % t)
664        else:
665            print(*t)
666
667
668if __name__ == '__main__':
669    display_sysinfo()
670