Skip to content

Commit

Permalink
Merge 2396ecc into 08f8c1d
Browse files Browse the repository at this point in the history
  • Loading branch information
john-science committed Apr 22, 2024
2 parents 08f8c1d + 2396ecc commit a351393
Show file tree
Hide file tree
Showing 4 changed files with 235 additions and 19 deletions.
169 changes: 152 additions & 17 deletions armi/bookkeeping/report/reportingUtils.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def _writeCaseInformation(o, cs):
(Operator_ArmiCodebase, context.ROOT),
(Operator_WorkingDirectory, os.getcwd()),
(Operator_PythonInterperter, sys.version),
(Operator_MasterMachine, os.environ.get("COMPUTERNAME", "?")),
(Operator_MasterMachine, getNodeName()),
(Operator_NumProcessors, context.MPI_SIZE),
(Operator_Date, context.START_TIME),
]
Expand Down Expand Up @@ -191,16 +191,9 @@ def _writeMachineInformation():
nodeMappingData.append(
(uniqueName, numProcessors, ", ".join(matchingProcs))
)
# If this is on Windows: run sys info on each unique node too
if "win" in sys.platform:
sysInfoCmd = (
'systeminfo | findstr /B /C:"OS Name" /B /C:"OS Version" /B '
'/C:"Processor" && systeminfo | findstr /E /C:"Mhz"'
)
out = subprocess.run(
sysInfoCmd, capture_output=True, text=True, shell=True
)
sysInfo += out.stdout

sysInfo += getSystemInfo()

runLog.header("=========== Machine Information ===========")
runLog.info(
tabulate.tabulate(
Expand All @@ -209,6 +202,7 @@ def _writeMachineInformation():
tablefmt="armi",
)
)

if sysInfo:
runLog.header("=========== System Information ===========")
runLog.info(sysInfo)
Expand Down Expand Up @@ -241,6 +235,148 @@ def _writeReactorCycleInformation(o, cs):
_writeReactorCycleInformation(o, cs)


def getNodeName():
"""Get the name of this comput node.
First, look in context.py. Then try various Linux tools. Then try Windows commands.
Returns
-------
str
Compute node name.
"""
hostNames = [
context.MPI_NODENAME,
context.MPI_NODENAMES[0],
subprocess.run("hostname", capture_output=True, text=True, shell=True).stdout,
subprocess.run("uname -n", capture_output=True, text=True, shell=True).stdout,
os.environ.get("COMPUTERNAME", context.LOCAL),
]
for nodeName in hostNames:
if nodeName and nodeName != context.LOCAL:
return nodeName

return context.LOCAL


def _getSystemInfoWindows():
"""Get system information, assuming the system is Windows.
Returns
-------
str
Basic system information: OS name, OS version, basic processor information
Examples
--------
Example results:
OS Name: Microsoft Windows 10 Enterprise
OS Version: 10.0.19041 N/A Build 19041
Processor(s): 1 Processor(s) Installed.
[01]: Intel64 Family 6 Model 142 Stepping 12 GenuineIntel ~801 Mhz
"""
cmd = (
'systeminfo | findstr /B /C:"OS Name" /B /C:"OS Version" /B '
'/C:"Processor" && systeminfo | findstr /E /C:"Mhz"'
)
return subprocess.run(cmd, capture_output=True, text=True, shell=True).stdout


def _getSystemInfoLinux():
"""Get system information, assuming the system is Linux.
This method uses multiple, redundant variations on common Linux command utilities to get the
information necessary. While it is not possible to guarantee what programs or files will be
available on "all Linux operating system", this collection of tools is widely supported and
should provide a reasonably broad-distribution coverage.
Returns
-------
str
Basic system information: OS name, OS version, basic processor information
Examples
--------
Example results:
OS Info: Ubuntu 22.04.3 LTS
Processor(s):
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 126
model name : Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
...
"""
# get OS name / version
linuxOsCommands = [
'cat /etc/os-release | grep "^PRETTY_NAME=" | cut -d = -f 2',
"uname -a",
"lsb_release -d | cut -d : -f 2",
'hostnamectl | grep "Operating System" | cut -d : -f 2',
]
osInfo = ""
for cmd in linuxOsCommands:
osInfo = subprocess.run(
cmd, capture_output=True, text=True, shell=True
).stdout.strip()
if osInfo:
break

if not osInfo:
runLog.warning("Linux OS information not found.")
return ""

# get processor information
linuxProcCommands = ["cat /proc/cpuinfo", "lscpu", "lshw -class CPU"]
procInfo = ""
for cmd in linuxProcCommands:
procInfo = subprocess.run(
cmd, capture_output=True, text=True, shell=True
).stdout
if procInfo:
break

if not procInfo:
runLog.warning("Linux processor information not found.")
return ""

# build output string
out = "OS Info: "
out += osInfo.strip()
out += "\nProcessor(s):\n "
out += procInfo.strip().replace("\n", "\n ")
out += "\n"

return out


def getSystemInfo():
"""Get system information, assuming the system is Windows or Linux.
Notes
-----
The format of the system information will be different on Windows vs Linux.
Returns
-------
str
Basic system information: OS name, OS version, basic processor information
"""
# Get basic system information (on Windows and Linux)
if "win" in sys.platform:
return _getSystemInfoWindows()
elif "linux" in sys.platform:
return _getSystemInfoLinux()
else:
runLog.warning(
f"Cannot get system information for {sys.platform} because ARMI only "
+ "supports Linux and Windows."
)
return ""


def getInterfaceStackSummary(o):
data = []
for ii, i in enumerate(o.interfaces, start=1):
Expand Down Expand Up @@ -390,8 +526,7 @@ def _makeBOLAssemblyMassSummary(massSum):
line += "{0:<25.3f}".format(s[val])
str_.append("{0:12s}{1}".format(val, line))

# print blocks in this assembly
# up to 10
# print blocks in this assembly up to 10
for i in range(10):
line = " " * 12
for s in massSum:
Expand All @@ -401,6 +536,7 @@ def _makeBOLAssemblyMassSummary(massSum):
line += " " * 25
if re.search(r"\S", line): # \S matches any non-whitespace character.
str_.append(line)

return "\n".join(str_)


Expand All @@ -425,10 +561,10 @@ def writeCycleSummary(core):
Parameters
----------
core: armi.reactor.reactors.Core
core: armi.reactor.reactors.Core
cs: armi.settings.caseSettings.Settings
"""
# would io be worth considering for this?
# Would io be worth considering for this?
cycle = core.r.p.cycle
str_ = []
runLog.important("Cycle {0} Summary:".format(cycle))
Expand All @@ -446,7 +582,6 @@ def setNeutronBalancesReport(core):
Parameters
----------
core : armi.reactor.reactors.Core
"""
if not core.getFirstBlock().p.rateCap:
runLog.warning(
Expand Down Expand Up @@ -642,7 +777,7 @@ def makeCoreDesignReport(core, cs):
Parameters
----------
core: armi.reactor.reactors.Core
core: armi.reactor.reactors.Core
cs: armi.settings.caseSettings.Settings
"""
coreDesignTable = report.data.Table(
Expand Down
79 changes: 79 additions & 0 deletions armi/bookkeeping/report/tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@
"""Really basic tests of the report Utils."""
import logging
import os
import subprocess
import unittest
from unittest.mock import patch

from armi import runLog, settings
from armi.bookkeeping import report
from armi.bookkeeping.report import data, reportInterface
from armi.bookkeeping.report.reportingUtils import (
_getSystemInfoLinux,
_getSystemInfoWindows,
getNodeName,
getSystemInfo,
makeBlockDesignReport,
setNeutronBalancesReport,
summarizePinDesign,
Expand All @@ -35,6 +41,79 @@
from armi.utils.directoryChangers import TemporaryDirectoryChanger


class _MockReturnResult:
"""Mocking the subprocess.run() return object."""

def __init__(self, stdout):
self.stdout = stdout


class TestReportingUtils(unittest.TestCase):
def test_getSystemInfoLinux(self):
"""Test _getSystemInfoLinux() on any operating system, by mocking the system calls."""
osInfo = '"Ubuntu 22.04.3 LTS"'
procInfo = """processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 126
model name : Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
...
"""
correctResult = """OS Info: "Ubuntu 22.04.3 LTS"
Processor(s):
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 126
model name : Intel(R) Core(TM) i5-1035G1 CPU @ 1.00GHz
..."""

def __mockSubprocessRun(*args, **kwargs):
if "os-release" in args[0]:
return _MockReturnResult(osInfo)
else:
return _MockReturnResult(procInfo)

with patch.object(subprocess, "run", side_effect=__mockSubprocessRun):
out = _getSystemInfoLinux()
self.assertEqual(out.strip(), correctResult)

@patch("subprocess.run")
def test_getSystemInfoWindows(self, mockSubprocess):
"""Test _getSystemInfoWindows() on any operating system, by mocking the system call."""
windowsResult = """OS Name: Microsoft Windows 10 Enterprise
OS Version: 10.0.19041 N/A Build 19041
Processor(s): 1 Processor(s) Installed.
[01]: Intel64 Family 6 Model 142 Stepping 12 GenuineIntel ~801 Mhz"""

mockSubprocess.return_value = _MockReturnResult(windowsResult)

out = _getSystemInfoWindows()
self.assertEqual(out, windowsResult)

def test_getSystemInfo(self):
"""Basic sanity check of getSystemInfo() running in the wild.
This test should pass if it is run on Window or mainstream Linux distros. But we expect this
to fail if the test is run on some other OS.
"""
out = getSystemInfo()
substrings = ["OS ", "Processor(s):"]
for sstr in substrings:
self.assertIn(sstr, out)

self.assertGreater(len(out), sum(len(sstr) + 5 for sstr in substrings))

def test_getNodeName(self):
"""Test that the getNodeName() method returns a non-empty string.
It is hard to know what string SHOULD be return here, and it would depend on how the OS is
set up on your machine or cluster. But this simple test needs to pass as-is on Windows
and Linux.
"""
self.assertGreater(len(getNodeName()), 0)


class TestReport(unittest.TestCase):
def setUp(self):
self.test_group = data.Table(settings.Settings(), "banana")
Expand Down
5 changes: 3 additions & 2 deletions armi/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ def setMode(cls, mode):
# MPI_SIZE is the total number of CPUs
MPI_RANK = 0
MPI_SIZE = 1
MPI_NODENAME = "local"
MPI_NODENAMES = ["local"]
LOCAL = "local"
MPI_NODENAME = LOCAL
MPI_NODENAMES = [LOCAL]


try:
Expand Down
1 change: 1 addition & 0 deletions doc/release/0.3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Release Date: TBD
New Features
------------
#. Conserve mass by component in assembly.setBlockMesh(). (`PR#1665 <https://github.com/terrapower/armi/pull/1665>`_)
#. System information is now also logged on Linux. (`PR#1689 <https://github.com/terrapower/armi/pull/1689>`_)
#. TBD

API Changes
Expand Down

0 comments on commit a351393

Please sign in to comment.