diff --git a/whitebox_tempest_plugin/services/clients.py b/whitebox_tempest_plugin/services/clients.py index 2024afb9..c51a312e 100644 --- a/whitebox_tempest_plugin/services/clients.py +++ b/whitebox_tempest_plugin/services/clients.py @@ -68,3 +68,54 @@ class NovaConfigClient(SSHClient): config = configparser.ConfigParser() config.readfp(StringIO(self._read_nova_conf())) return config.get(section, option) + + +class NUMAClient(SSHClient): + """A client to get host NUMA information. `numactl` needs to be installed + in the environment or container(s). + """ + + def get_host_topology(self): + """Returns the host topology as a dict. + + :return nodes: A dict of CPUs in each host NUMA node, keyed by host + node number, for example: {0: [1, 2], + 1: [3, 4]} + """ + nodes = {} + numactl = self.execute('numactl -H', sudo=True) + for line in StringIO(numactl).readlines(): + if 'node' in line and 'cpus' in line: + cpus = [int(cpu) for cpu in line.split(':')[1].split()] + node = int(line.split()[1]) + nodes[node] = cpus + return nodes + + def get_num_cpus(self): + nodes = self.get_host_topology() + return sum([len(cpus) for cpus in nodes.values()]) + + def get_pagesize(self): + proc_meminfo = self.execute('cat /proc/meminfo') + for line in StringIO(proc_meminfo).readlines(): + if line.startswith('Hugepagesize'): + return int(line.split(':')[1].split()[0]) + + def get_hugepages(self): + """Returns a nested dict of number of total and free pages, keyed by + NUMA node. For example: + + {0: {'total': 2000, 'free': 2000}, + 1: {'total': 2000, 'free': 0}} + """ + pages = {} + for node in self.get_host_topology(): + meminfo = self.execute( + 'cat /sys/devices/system/node/node%d/meminfo' % node) + for line in StringIO(meminfo).readlines(): + if 'HugePages_Total' in line: + total = int(line.split(':')[1].lstrip()) + if 'HugePages_Free' in line: + free = int(line.split(':')[1].lstrip()) + pages[node] = {'total': total, 'free': free} + return pages diff --git a/whitebox_tempest_plugin/tests/test_clients.py b/whitebox_tempest_plugin/tests/test_clients.py index dd1dd838..f0458fa1 100644 --- a/whitebox_tempest_plugin/tests/test_clients.py +++ b/whitebox_tempest_plugin/tests/test_clients.py @@ -74,3 +74,170 @@ class ConfigClientTestCase(base.WhiteboxPluginTestCase): return_value=fake_config): self.assertEqual(config_client.getopt('default', 'fake-key'), 'fake-value') + + +class NUMAClientTestCase(base.WhiteboxPluginTestCase): + + fake_numactl_output = textwrap.dedent(""" + available: 2 nodes (0-1) + node 0 cpus: 0 1 2 + node 0 size: 7793 MB + node 0 free: 1640 MB + node 1 cpus: 3 4 + node 1 size: 7875 MB + node 1 free: 4059 MB + node distances: + node 0 1 + 0: 10 20 + 1: 20 10""") + + fake_proc_meminfo = textwrap.dedent(""" + MemTotal: 16035320 kB + MemFree: 481884 kB + MemAvailable: 2489036 kB + Buffers: 4128 kB + Cached: 2246912 kB + SwapCached: 572 kB + Active: 5348564 kB + Inactive: 1375316 kB + Active(anon): 4165752 kB + Inactive(anon): 347028 kB + Active(file): 1182812 kB + Inactive(file): 1028288 kB + Unevictable: 112816 kB + Mlocked: 112816 kB + SwapTotal: 629756 kB + SwapFree: 625648 kB + Dirty: 68 kB + Writeback: 0 kB + AnonPages: 4429328 kB + Mapped: 329536 kB + Shmem: 4052 kB + KReclaimable: 165572 kB + Slab: 350468 kB + SReclaimable: 165572 kB + SUnreclaim: 184896 kB + KernelStack: 13616 kB + PageTables: 26564 kB + NFS_Unstable: 0 kB + Bounce: 0 kB + WritebackTmp: 0 kB + CommitLimit: 4551416 kB + Committed_AS: 10445028 kB + VmallocTotal: 34359738367 kB + VmallocUsed: 0 kB + VmallocChunk: 0 kB + Percpu: 6600 kB + HardwareCorrupted: 0 kB + AnonHugePages: 0 kB + ShmemHugePages: 0 kB + ShmemPmdMapped: 0 kB + CmaTotal: 0 kB + CmaFree: 0 kB + HugePages_Total: 4000 + HugePages_Free: 4000 + HugePages_Rsvd: 0 + HugePages_Surp: 0 + Hugepagesize: 2048 kB + Hugetlb: 8192000 kB + DirectMap4k: 391032 kB + DirectMap2M: 10749952 kB + DirectMap1G: 5242880 kB""") + + fake_node0_meminfo = textwrap.dedent(""" + Node 0 MemTotal: 7971444 kB + Node 0 MemFree: 138612 kB + Node 0 MemUsed: 7832832 kB + Node 0 Active: 2832660 kB + Node 0 Inactive: 586824 kB + Node 0 Active(anon): 2538412 kB + Node 0 Inactive(anon): 326172 kB + Node 0 Active(file): 294248 kB + Node 0 Inactive(file): 260652 kB + Node 0 Unevictable: 105392 kB + Node 0 Mlocked: 105392 kB + Node 0 Dirty: 136 kB + Node 0 Writeback: 0 kB + Node 0 FilePages: 589864 kB + Node 0 Mapped: 126096 kB + Node 0 AnonPages: 2855072 kB + Node 0 Shmem: 2024 kB + Node 0 KernelStack: 3720 kB + Node 0 PageTables: 13308 kB + Node 0 NFS_Unstable: 0 kB + Node 0 Bounce: 0 kB + Node 0 WritebackTmp: 0 kB + Node 0 KReclaimable: 54788 kB + Node 0 Slab: 142752 kB + Node 0 SReclaimable: 54788 kB + Node 0 SUnreclaim: 87964 kB + Node 0 AnonHugePages: 0 kB + Node 0 ShmemHugePages: 0 kB + Node 0 ShmemPmdMapped: 0 kB + Node 0 HugePages_Total: 4000 + Node 0 HugePages_Free: 3000 + Node 0 HugePages_Surp: 0""") + + fake_node1_meminfo = textwrap.dedent(""" + Node 1 MemTotal: 8063876 kB + Node 1 MemFree: 323536 kB + Node 1 MemUsed: 7740340 kB + Node 1 Active: 2520684 kB + Node 1 Inactive: 795228 kB + Node 1 Active(anon): 1632468 kB + Node 1 Inactive(anon): 20852 kB + Node 1 Active(file): 888216 kB + Node 1 Inactive(file): 774376 kB + Node 1 Unevictable: 7424 kB + Node 1 Mlocked: 7424 kB + Node 1 Dirty: 308 kB + Node 1 Writeback: 0 kB + Node 1 FilePages: 1668208 kB + Node 1 Mapped: 206436 kB + Node 1 AnonPages: 1579356 kB + Node 1 Shmem: 2052 kB + Node 1 KernelStack: 9960 kB + Node 1 PageTables: 13452 kB + Node 1 NFS_Unstable: 0 kB + Node 1 Bounce: 0 kB + Node 1 WritebackTmp: 0 kB + Node 1 KReclaimable: 112860 kB + Node 1 Slab: 210308 kB + Node 1 SReclaimable: 112860 kB + Node 1 SUnreclaim: 97448 kB + Node 1 AnonHugePages: 0 kB + Node 1 ShmemHugePages: 0 kB + Node 1 ShmemPmdMapped: 0 kB + Node 1 HugePages_Total: 2000 + Node 1 HugePages_Free: 1000 + Node 1 HugePages_Surp: 0""") + + def test_get_hugepages(self): + numa_client = clients.NUMAClient('fake-host') + with mock.patch.object(numa_client, 'execute', + side_effect=(self.fake_numactl_output, + self.fake_node0_meminfo, + self.fake_node1_meminfo)): + self.assertEqual(numa_client.get_hugepages(), + {0: {'total': 4000, 'free': 3000}, + 1: {'total': 2000, 'free': 1000}}) + + def test_get_pagesize(self): + numa_client = clients.NUMAClient('fake-host') + with mock.patch.object(numa_client, 'execute', + return_value=self.fake_proc_meminfo): + self.assertEqual(numa_client.get_pagesize(), 2048) + + def test_get_host_topology(self): + numa_client = clients.NUMAClient('fake-host') + with mock.patch.object(numa_client, 'execute', + return_value=self.fake_numactl_output): + self.assertEqual(numa_client.get_host_topology(), + {0: [0, 1, 2], + 1: [3, 4]}) + + def test_get_num_cpus(self): + numa_client = clients.NUMAClient('fake-host') + with mock.patch.object(numa_client, 'execute', + return_value=self.fake_numactl_output): + self.assertEqual(5, numa_client.get_num_cpus())