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