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 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, reqpages): 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(reqpages)) 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 gotpages = get_hugepages(path) 72 if gotpages != reqpages: 73 sys.exit('Unable to set pages ({} instead of {} in {}).'.format( 74 gotpages, reqpages, filename)) 75 76 77def show_numa_pages(): 78 '''Show huge page reservations on Numa system''' 79 print('Node Pages Size Total') 80 for numa_path in glob.glob('/sys/devices/system/node/node*'): 81 node = numa_path[29:] # slice after /sys/devices/system/node/node 82 path = numa_path + '/hugepages' 83 if not os.path.exists(path): 84 continue 85 for hdir in os.listdir(path): 86 pages = get_hugepages(path + '/' + hdir) 87 if pages > 0: 88 kb = int(hdir[10:-2]) # slice out of hugepages-NNNkB 89 print('{:<4} {:<5} {:<6} {}'.format(node, pages, 90 fmt_memsize(kb), 91 fmt_memsize(pages * kb))) 92 93 94def show_non_numa_pages(): 95 '''Show huge page reservations on non Numa system''' 96 print('Pages Size Total') 97 path = '/sys/kernel/mm/hugepages' 98 for hdir in os.listdir(path): 99 pages = get_hugepages(path + '/' + hdir) 100 if pages > 0: 101 kb = int(hdir[10:-2]) 102 print('{:<5} {:<6} {}'.format(pages, fmt_memsize(kb), 103 fmt_memsize(pages * kb))) 104 105 106def show_pages(): 107 '''Show existing huge page settings''' 108 if is_numa(): 109 show_numa_pages() 110 else: 111 show_non_numa_pages() 112 113 114def clear_pages(): 115 '''Clear all existing huge page mappings''' 116 if is_numa(): 117 dirs = glob.glob( 118 '/sys/devices/system/node/node*/hugepages/hugepages-*') 119 else: 120 dirs = glob.glob('/sys/kernel/mm/hugepages/hugepages-*') 121 122 for path in dirs: 123 set_hugepages(path, 0) 124 125 126def default_pagesize(): 127 '''Get default huge page size from /proc/meminfo''' 128 with open('/proc/meminfo') as meminfo: 129 for line in meminfo: 130 if line.startswith('Hugepagesize:'): 131 return int(line.split()[1]) 132 return None 133 134 135def set_numa_pages(pages, hugepgsz, node=None): 136 '''Set huge page reservation on Numa system''' 137 if node: 138 nodes = ['/sys/devices/system/node/node{}/hugepages'.format(node)] 139 else: 140 nodes = glob.glob('/sys/devices/system/node/node*/hugepages') 141 142 for node_path in nodes: 143 huge_path = '{}/hugepages-{}kB'.format(node_path, hugepgsz) 144 set_hugepages(huge_path, pages) 145 146 147def set_non_numa_pages(pages, hugepgsz): 148 '''Set huge page reservation on non Numa system''' 149 path = '/sys/kernel/mm/hugepages/hugepages-{}kB'.format(hugepgsz) 150 set_hugepages(path, pages) 151 152 153def reserve_pages(pages, hugepgsz, node=None): 154 '''Set the number of huge pages to be reserved''' 155 if node or is_numa(): 156 set_numa_pages(pages, hugepgsz, node=node) 157 else: 158 set_non_numa_pages(pages, hugepgsz) 159 160 161def get_mountpoints(): 162 '''Get list of where hugepage filesystem is mounted''' 163 mounted = [] 164 with open('/proc/mounts') as mounts: 165 for line in mounts: 166 fields = line.split() 167 if fields[2] != 'hugetlbfs': 168 continue 169 mounted.append(fields[1]) 170 return mounted 171 172 173def mount_huge(pagesize, mountpoint): 174 '''Mount the huge TLB file system''' 175 if mountpoint in get_mountpoints(): 176 print(mountpoint, "already mounted") 177 return 178 cmd = "mount -t hugetlbfs" 179 if pagesize: 180 cmd += ' -o pagesize={}'.format(pagesize * 1024) 181 cmd += ' nodev ' + mountpoint 182 os.system(cmd) 183 184 185def umount_huge(mountpoint): 186 '''Unmount the huge TLB file system (if mounted)''' 187 if mountpoint in get_mountpoints(): 188 os.system("umount " + mountpoint) 189 190 191def show_mount(): 192 '''Show where huge page filesystem is mounted''' 193 mounted = get_mountpoints() 194 if mounted: 195 print("Hugepages mounted on", *mounted) 196 else: 197 print("Hugepages not mounted") 198 199 200def main(): 201 '''Process the command line arguments and setup huge pages''' 202 parser = argparse.ArgumentParser( 203 formatter_class=argparse.RawDescriptionHelpFormatter, 204 description="Setup huge pages", 205 epilog=""" 206Examples: 207 208To display current huge page settings: 209 %(prog)s -s 210 211To a complete setup of with 2 Gigabyte of 1G huge pages: 212 %(prog)s -p 1G --setup 2G 213""") 214 parser.add_argument( 215 '--show', 216 '-s', 217 action='store_true', 218 help="print the current huge page configuration") 219 parser.add_argument( 220 '--clear', '-c', action='store_true', help="clear existing huge pages") 221 parser.add_argument( 222 '--mount', 223 '-m', 224 action='store_true', 225 help='mount the huge page filesystem') 226 parser.add_argument( 227 '--unmount', 228 '-u', 229 action='store_true', 230 help='unmount the system huge page directory') 231 parser.add_argument( 232 '--node', '-n', help='select numa node to reserve pages on') 233 parser.add_argument( 234 '--pagesize', 235 '-p', 236 metavar='SIZE', 237 help='choose huge page size to use') 238 parser.add_argument( 239 '--reserve', 240 '-r', 241 metavar='SIZE', 242 help='reserve huge pages. Size is in bytes with K, M, or G suffix') 243 parser.add_argument( 244 '--setup', 245 metavar='SIZE', 246 help='setup huge pages by doing clear, unmount, reserve and mount') 247 args = parser.parse_args() 248 249 if args.setup: 250 args.clear = True 251 args.unmount = True 252 args.reserve = args.setup 253 args.mount = True 254 255 if args.pagesize: 256 pagesize_kb = get_memsize(args.pagesize) 257 else: 258 pagesize_kb = default_pagesize() 259 if not pagesize_kb: 260 sys.exit("Invalid page size: {}kB".format(pagesize_kb)) 261 262 if args.clear: 263 clear_pages() 264 if args.unmount: 265 umount_huge(HUGE_MOUNT) 266 267 if args.reserve: 268 reserve_kb = get_memsize(args.reserve) 269 if reserve_kb % pagesize_kb != 0: 270 sys.exit( 271 'Huge reservation {}kB is not a multiple of page size {}kB'. 272 format(reserve_kb, pagesize_kb)) 273 reserve_pages( 274 int(reserve_kb / pagesize_kb), pagesize_kb, node=args.node) 275 if args.mount: 276 mount_huge(pagesize_kb, HUGE_MOUNT) 277 if args.show: 278 show_pages() 279 print() 280 show_mount() 281 282 283if __name__ == "__main__": 284 main() 285