xref: /dpdk/usertools/dpdk-hugepages.py (revision 3bb3ebb51b789d4ecb417cbdb1dce5c7211f6f18)
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 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
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, 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
75def 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
92def 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
104def 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
112def 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
124def 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
133def 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
145def 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
151def 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
159def 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
171def 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
183def 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
189def 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
198def 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="""
204Examples:
205
206To display current huge page settings:
207    %(prog)s -s
208
209To 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
279if __name__ == "__main__":
280    main()
281