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."""
13 # Standard binary prefix
16 # systemd mount point for huge pages
17 HUGE_MOUNT = "/dev/hugepages"
21 '''Format memory size in kB into conventional format'''
22 logk = int(log2(kb) / 10)
23 suffix = BINARY_PREFIX[logk]
25 return '{}{}b'.format(int(kb / unit), suffix)
29 '''Convert memory size with suffix to kB'''
30 match = re.match(r'(\d+)([' + BINARY_PREFIX + r']?)$', arg.upper())
32 sys.exit('{} is not a valid size'.format(arg))
33 num = float(match.group(1))
34 suffix = match.group(2)
36 return int(num / 1024)
37 idx = BINARY_PREFIX.find(suffix)
38 return int(num * (2**(idx * 10)))
42 '''Test if NUMA is necessary on this system'''
43 return os.path.exists('/sys/devices/system/node')
46 def 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)
53 def get_hugepages(path):
54 '''Read number of reserved pages'''
55 with open(path + '/nr_hugepages') as nr_hugepages:
56 return int(nr_hugepages.read())
60 def set_hugepages(path, reqpages):
61 '''Write the number of reserved huge pages'''
62 filename = path + '/nr_hugepages'
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))
77 def 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):
85 for hdir in os.listdir(path):
86 pages = get_hugepages(path + '/' + hdir)
88 kb = int(hdir[10:-2]) # slice out of hugepages-NNNkB
89 print('{:<4} {:<5} {:<6} {}'.format(node, pages,
91 fmt_memsize(pages * kb)))
94 def 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)
101 kb = int(hdir[10:-2])
102 print('{:<5} {:<6} {}'.format(pages, fmt_memsize(kb),
103 fmt_memsize(pages * kb)))
107 '''Show existing huge page settings'''
111 show_non_numa_pages()
115 '''Clear all existing huge page mappings'''
118 '/sys/devices/system/node/node*/hugepages/hugepages-*')
120 dirs = glob.glob('/sys/kernel/mm/hugepages/hugepages-*')
123 set_hugepages(path, 0)
126 def default_pagesize():
127 '''Get default huge page size from /proc/meminfo'''
128 with open('/proc/meminfo') as meminfo:
130 if line.startswith('Hugepagesize:'):
131 return int(line.split()[1])
135 def set_numa_pages(pages, hugepgsz, node=None):
136 '''Set huge page reservation on Numa system'''
138 nodes = ['/sys/devices/system/node/node{}/hugepages'.format(node)]
140 nodes = glob.glob('/sys/devices/system/node/node*/hugepages')
142 for node_path in nodes:
143 huge_path = '{}/hugepages-{}kB'.format(node_path, hugepgsz)
144 set_hugepages(huge_path, pages)
147 def 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)
153 def 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)
158 set_non_numa_pages(pages, hugepgsz)
161 def get_mountpoints():
162 '''Get list of where hugepage filesystem is mounted'''
164 with open('/proc/mounts') as mounts:
166 fields = line.split()
167 if fields[2] != 'hugetlbfs':
169 mounted.append(fields[1])
173 def mount_huge(pagesize, mountpoint):
174 '''Mount the huge TLB file system'''
175 if mountpoint in get_mountpoints():
176 print(mountpoint, "already mounted")
178 cmd = "mount -t hugetlbfs"
180 cmd += ' -o pagesize={}'.format(pagesize * 1024)
181 cmd += ' nodev ' + mountpoint
185 def umount_huge(mountpoint):
186 '''Unmount the huge TLB file system (if mounted)'''
187 if mountpoint in get_mountpoints():
188 os.system("umount " + mountpoint)
192 '''Show where huge page filesystem is mounted'''
193 mounted = get_mountpoints()
195 print("Hugepages mounted on", *mounted)
197 print("Hugepages not mounted")
201 '''Process the command line arguments and setup huge pages'''
202 parser = argparse.ArgumentParser(
203 formatter_class=argparse.RawDescriptionHelpFormatter,
204 description="Setup huge pages",
208 To display current huge page settings:
211 To a complete setup of with 2 Gigabyte of 1G huge pages:
212 %(prog)s -p 1G --setup 2G
218 help="print the current huge page configuration")
220 '--clear', '-c', action='store_true', help="clear existing huge pages")
225 help='mount the huge page filesystem')
230 help='unmount the system huge page directory')
232 '--node', '-n', help='select numa node to reserve pages on')
237 help='choose huge page size to use')
242 help='reserve huge pages. Size is in bytes with K, M, or G suffix')
246 help='setup huge pages by doing clear, unmount, reserve and mount')
247 args = parser.parse_args()
252 args.reserve = args.setup
256 pagesize_kb = get_memsize(args.pagesize)
258 pagesize_kb = default_pagesize()
260 sys.exit("Invalid page size: {}kB".format(pagesize_kb))
265 umount_huge(HUGE_MOUNT)
268 reserve_kb = get_memsize(args.reserve)
269 if reserve_kb % pagesize_kb != 0:
271 'Huge reservation {}kB is not a multiple of page size {}kB'.
272 format(reserve_kb, pagesize_kb))
274 int(reserve_kb / pagesize_kb), pagesize_kb, node=args.node)
276 mount_huge(pagesize_kb, HUGE_MOUNT)
283 if __name__ == "__main__":