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