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, user, group): 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 if user: 182 cmd += ' -o uid=' + user 183 if group: 184 cmd += ' -o gid=' + group 185 cmd += ' nodev ' + mountpoint 186 os.system(cmd) 187 188 189def umount_huge(mountpoint): 190 '''Unmount the huge TLB file system (if mounted)''' 191 if mountpoint in get_mountpoints(): 192 os.system("umount " + mountpoint) 193 194 195def show_mount(): 196 '''Show where huge page filesystem is mounted''' 197 mounted = get_mountpoints() 198 if mounted: 199 print("Hugepages mounted on", *mounted) 200 else: 201 print("Hugepages not mounted") 202 203 204def main(): 205 '''Process the command line arguments and setup huge pages''' 206 parser = argparse.ArgumentParser( 207 formatter_class=argparse.RawDescriptionHelpFormatter, 208 description="Setup huge pages", 209 epilog=""" 210Examples: 211 212To display current huge page settings: 213 %(prog)s -s 214 215To a complete setup of with 2 Gigabyte of 1G huge pages: 216 %(prog)s -p 1G --setup 2G 217""") 218 parser.add_argument( 219 '--show', 220 '-s', 221 action='store_true', 222 help="print the current huge page configuration") 223 parser.add_argument( 224 '--clear', '-c', action='store_true', help="clear existing huge pages") 225 parser.add_argument( 226 '--mount', 227 '-m', 228 action='store_true', 229 help='mount the huge page filesystem') 230 parser.add_argument( 231 '--unmount', 232 '-u', 233 action='store_true', 234 help='unmount the system huge page directory') 235 parser.add_argument( 236 '--directory', 237 '-d', 238 metavar='DIR', 239 default=HUGE_MOUNT, 240 help='mount point') 241 parser.add_argument( 242 '--user', 243 '-U', 244 metavar='UID', 245 help='set the mounted directory owner user') 246 parser.add_argument( 247 '--group', 248 '-G', 249 metavar='GID', 250 help='set the mounted directory owner group') 251 parser.add_argument( 252 '--node', '-n', help='select numa node to reserve pages on') 253 parser.add_argument( 254 '--pagesize', 255 '-p', 256 metavar='SIZE', 257 help='choose huge page size to use') 258 parser.add_argument( 259 '--reserve', 260 '-r', 261 metavar='SIZE', 262 help='reserve huge pages. Size is in bytes with K, M, or G suffix') 263 parser.add_argument( 264 '--setup', 265 metavar='SIZE', 266 help='setup huge pages by doing clear, unmount, reserve and mount') 267 args = parser.parse_args() 268 269 if args.setup: 270 args.clear = True 271 args.unmount = True 272 args.reserve = args.setup 273 args.mount = True 274 275 if args.pagesize: 276 pagesize_kb = get_memsize(args.pagesize) 277 else: 278 pagesize_kb = default_pagesize() 279 if not pagesize_kb: 280 sys.exit("Invalid page size: {}kB".format(pagesize_kb)) 281 282 if args.clear: 283 clear_pages() 284 if args.unmount: 285 umount_huge(args.directory) 286 287 if args.reserve: 288 reserve_kb = get_memsize(args.reserve) 289 if reserve_kb % pagesize_kb != 0: 290 sys.exit( 291 'Huge reservation {}kB is not a multiple of page size {}kB'. 292 format(reserve_kb, pagesize_kb)) 293 reserve_pages( 294 int(reserve_kb / pagesize_kb), pagesize_kb, node=args.node) 295 if args.mount: 296 mount_huge(pagesize_kb, args.directory, args.user, args.group) 297 if args.show: 298 show_pages() 299 print() 300 show_mount() 301 302 303if __name__ == "__main__": 304 main() 305