usertools: show valid hugepage sizes if wrong request
[dpdk.git] / usertools / dpdk-hugepages.py
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
6 import argparse
7 import glob
8 import os
9 import re
10 import sys
11 from math import log2
12
13 # Standard binary prefix
14 BINARY_PREFIX = "KMG"
15
16 # systemd mount point for huge pages
17 HUGE_MOUNT = "/dev/hugepages"
18
19
20 def 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
28 def 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
41 def is_numa():
42     '''Test if NUMA is necessary on this system'''
43     return os.path.exists('/sys/devices/system/node')
44
45
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)
51
52
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())
57     return 0
58
59
60 def 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
75 def 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
92 def 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
104 def 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
112 def 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
124 def 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
133 def 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
145 def 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
151 def 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
159 def 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
171 def 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
183 def 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
189 def 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
198 def 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="""
204 Examples:
205
206 To display current huge page settings:
207     %(prog)s -s
208
209 To 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
279 if __name__ == "__main__":
280     main()