1#! /usr/bin/env python3 2# SPDX-License-Identifier: BSD-3-Clause 3# Copyright (c) 2020 Microsoft Corporation 4"""Script to query and setup huge pages for DPDK applications.""" 5 6import argparse 7import glob 8import os 9import re 10import sys 11from math import log2 12 13# Standard binary prefix 14BINARY_PREFIX = "KMG" 15 16# systemd mount point for huge pages 17HUGE_MOUNT = "/dev/hugepages" 18 19 20def fmt_memsize(kb): 21 '''Format memory size in kB into conventional format''' 22 logk = int(log2(kb) / 10) 23 suffix = BINARY_PREFIX[logk] 24 unit = 2**(logk * 10) 25 return '{}{}b'.format(int(kb / unit), suffix) 26 27 28def get_memsize(arg): 29 '''Convert memory size with suffix to kB''' 30 match = re.match(r'(\d+)([' + BINARY_PREFIX + r']?)$', arg.upper()) 31 if match is None: 32 sys.exit('{} is not a valid page size'.format(arg)) 33 num = float(match.group(1)) 34 suffix = match.group(2) 35 if suffix == "": 36 return int(num / 1024) 37 idx = BINARY_PREFIX.find(suffix) 38 return int(num * (2**(idx * 10))) 39 40 41def is_numa(): 42 '''Test if NUMA is necessary on this system''' 43 return os.path.exists('/sys/devices/system/node') 44 45 46def get_valid_page_sizes(path): 47 '''Extract valid hugepage sizes''' 48 dir = os.path.dirname(path) 49 pg_sizes = (d.split("-")[1] for d in os.listdir(dir)) 50 return " ".join(pg_sizes) 51 52 53def get_hugepages(path): 54 '''Read number of reserved pages''' 55 with open(path + '/nr_hugepages') as nr_hugepages: 56 return int(nr_hugepages.read()) 57 return 0 58 59 60def set_hugepages(path, pages): 61 '''Write the number of reserved huge pages''' 62 filename = path + '/nr_hugepages' 63 try: 64 with open(filename, 'w') as nr_hugepages: 65 nr_hugepages.write('{}\n'.format(pages)) 66 except PermissionError: 67 sys.exit('Permission denied: need to be root!') 68 except FileNotFoundError: 69 sys.exit("Invalid page size. Valid page sizes: {}".format( 70 get_valid_page_sizes(path))) 71 if get_hugepages(path) != pages: 72 sys.exit('Unable to reserve required pages.') 73 74 75def show_numa_pages(): 76 '''Show huge page reservations on Numa system''' 77 print('Node Pages Size Total') 78 for numa_path in glob.glob('/sys/devices/system/node/node*'): 79 node = numa_path[29:] # slice after /sys/devices/system/node/node 80 path = numa_path + '/hugepages' 81 if not os.path.exists(path): 82 continue 83 for hdir in os.listdir(path): 84 pages = get_hugepages(path + '/' + hdir) 85 if pages > 0: 86 kb = int(hdir[10:-2]) # slice out of hugepages-NNNkB 87 print('{:<4} {:<5} {:<6} {}'.format(node, pages, 88 fmt_memsize(kb), 89 fmt_memsize(pages * kb))) 90 91 92def show_non_numa_pages(): 93 '''Show huge page reservations on non Numa system''' 94 print('Pages Size Total') 95 path = '/sys/kernel/mm/hugepages' 96 for hdir in os.listdir(path): 97 pages = get_hugepages(path + '/' + hdir) 98 if pages > 0: 99 kb = int(hdir[10:-2]) 100 print('{:<5} {:<6} {}'.format(pages, fmt_memsize(kb), 101 fmt_memsize(pages * kb))) 102 103 104def show_pages(): 105 '''Show existing huge page settings''' 106 if is_numa(): 107 show_numa_pages() 108 else: 109 show_non_numa_pages() 110 111 112def clear_pages(): 113 '''Clear all existing huge page mappings''' 114 if is_numa(): 115 dirs = glob.glob( 116 '/sys/devices/system/node/node*/hugepages/hugepages-*') 117 else: 118 dirs = glob.glob('/sys/kernel/mm/hugepages/hugepages-*') 119 120 for path in dirs: 121 set_hugepages(path, 0) 122 123 124def default_pagesize(): 125 '''Get default huge page size from /proc/meminfo''' 126 with open('/proc/meminfo') as meminfo: 127 for line in meminfo: 128 if line.startswith('Hugepagesize:'): 129 return int(line.split()[1]) 130 return None 131 132 133def set_numa_pages(pages, hugepgsz, node=None): 134 '''Set huge page reservation on Numa system''' 135 if node: 136 nodes = ['/sys/devices/system/node/node{}/hugepages'.format(node)] 137 else: 138 nodes = glob.glob('/sys/devices/system/node/node*/hugepages') 139 140 for node_path in nodes: 141 huge_path = '{}/hugepages-{}kB'.format(node_path, hugepgsz) 142 set_hugepages(huge_path, pages) 143 144 145def set_non_numa_pages(pages, hugepgsz): 146 '''Set huge page reservation on non Numa system''' 147 path = '/sys/kernel/mm/hugepages/hugepages-{}kB'.format(hugepgsz) 148 set_hugepages(path, pages) 149 150 151def reserve_pages(pages, hugepgsz, node=None): 152 '''Set the number of huge pages to be reserved''' 153 if node or is_numa(): 154 set_numa_pages(pages, hugepgsz, node=node) 155 else: 156 set_non_numa_pages(pages, hugepgsz) 157 158 159def get_mountpoints(): 160 '''Get list of where hugepage filesystem is mounted''' 161 mounted = [] 162 with open('/proc/mounts') as mounts: 163 for line in mounts: 164 fields = line.split() 165 if fields[2] != 'hugetlbfs': 166 continue 167 mounted.append(fields[1]) 168 return mounted 169 170 171def mount_huge(pagesize, mountpoint): 172 '''Mount the huge TLB file system''' 173 if mountpoint in get_mountpoints(): 174 print(mountpoint, "already mounted") 175 return 176 cmd = "mount -t hugetlbfs" 177 if pagesize: 178 cmd += ' -o pagesize={}'.format(pagesize * 1024) 179 cmd += ' nodev ' + mountpoint 180 os.system(cmd) 181 182 183def umount_huge(mountpoint): 184 '''Unmount the huge TLB file system (if mounted)''' 185 if mountpoint in get_mountpoints(): 186 os.system("umount " + mountpoint) 187 188 189def show_mount(): 190 '''Show where huge page filesystem is mounted''' 191 mounted = get_mountpoints() 192 if mounted: 193 print("Hugepages mounted on", *mounted) 194 else: 195 print("Hugepages not mounted") 196 197 198def main(): 199 '''Process the command line arguments and setup huge pages''' 200 parser = argparse.ArgumentParser( 201 formatter_class=argparse.RawDescriptionHelpFormatter, 202 description="Setup huge pages", 203 epilog=""" 204Examples: 205 206To display current huge page settings: 207 %(prog)s -s 208 209To a complete setup of with 2 Gigabyte of 1G huge pages: 210 %(prog)s -p 1G --setup 2G 211""") 212 parser.add_argument( 213 '--show', 214 '-s', 215 action='store_true', 216 help="print the current huge page configuration") 217 parser.add_argument( 218 '--clear', '-c', action='store_true', help="clear existing huge pages") 219 parser.add_argument( 220 '--mount', 221 '-m', 222 action='store_true', 223 help='mount the huge page filesystem') 224 parser.add_argument( 225 '--unmount', 226 '-u', 227 action='store_true', 228 help='unmount the system huge page directory') 229 parser.add_argument( 230 '--node', '-n', help='select numa node to reserve pages on') 231 parser.add_argument( 232 '--pagesize', 233 '-p', 234 metavar='SIZE', 235 help='choose huge page size to use') 236 parser.add_argument( 237 '--reserve', 238 '-r', 239 metavar='SIZE', 240 help='reserve huge pages. Size is in bytes with K, M, or G suffix') 241 parser.add_argument( 242 '--setup', 243 metavar='SIZE', 244 help='setup huge pages by doing clear, unmount, reserve and mount') 245 args = parser.parse_args() 246 247 if args.setup: 248 args.clear = True 249 args.unmount = True 250 args.reserve = args.setup 251 args.mount = True 252 253 if args.pagesize: 254 pagesize_kb = get_memsize(args.pagesize) 255 else: 256 pagesize_kb = default_pagesize() 257 258 if args.clear: 259 clear_pages() 260 if args.unmount: 261 umount_huge(HUGE_MOUNT) 262 263 if args.reserve: 264 reserve_kb = get_memsize(args.reserve) 265 if reserve_kb % pagesize_kb != 0: 266 sys.exit( 267 'Huge reservation {}kB is not a multiple of page size {}kB'. 268 format(reserve_kb, pagesize_kb)) 269 reserve_pages( 270 int(reserve_kb / pagesize_kb), pagesize_kb, node=args.node) 271 if args.mount: 272 mount_huge(pagesize_kb, HUGE_MOUNT) 273 if args.show: 274 show_pages() 275 print() 276 show_mount() 277 278 279if __name__ == "__main__": 280 main() 281