blob: 3559073c66dce15de1b32927b01e804e3c535eb4 [file] [log] [blame]
Zachary Andersond71ba562021-07-29 23:43:49 -07001#!/usr/bin/env python3
2# Copyright 2019 The Fuchsia Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6# This script computes the number of concurrent jobs that can run in the
7# build as a function of the machine. It accepts a set of key value pairs
8# given by repeated --memory-per-job arguments. For example:
9#
10# $ get_concurrent_jobs.py --memory-per-job dart=1GB
11#
12# The result is a json map printed to stdout that gives the number of
13# concurrent jobs allowed of each kind. For example:
14#
15# {"dart": 8}
16#
17# Some memory can be held out of the calculation with the --reserve-memory flag.
18
19import argparse
20import ctypes
21import json
22import multiprocessing
23import os
24import re
25import subprocess
26import sys
27
28UNITS = {'B': 1, 'KB': 2**10, 'MB': 2**20, 'GB': 2**30, 'TB': 2**40}
29
30
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070031# pylint: disable=line-too-long
Zachary Andersond71ba562021-07-29 23:43:49 -070032# See https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex
33# and https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070034# pylint: enable=line-too-long
Zachary Andersond71ba562021-07-29 23:43:49 -070035class MEMORYSTATUSEX(ctypes.Structure):
36 _fields_ = [
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070037 ('dwLength', ctypes.c_ulong),
38 ('dwMemoryLoad', ctypes.c_ulong),
39 ('ullTotalPhys', ctypes.c_ulonglong),
40 ('ullAvailPhys', ctypes.c_ulonglong),
41 ('ullTotalPageFile', ctypes.c_ulonglong),
42 ('ullAvailPageFile', ctypes.c_ulonglong),
43 ('ullTotalVirtual', ctypes.c_ulonglong),
44 ('ullAvailVirtual', ctypes.c_ulonglong),
45 ('sullAvailExtendedVirtual', ctypes.c_ulonglong),
Zachary Andersond71ba562021-07-29 23:43:49 -070046 ]
47
48
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070049def get_total_memory():
Zachary Andersond71ba562021-07-29 23:43:49 -070050 if sys.platform in ('win32', 'cygwin'):
51 stat = MEMORYSTATUSEX(dwLength=ctypes.sizeof(MEMORYSTATUSEX))
52 success = ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
53 return stat.ullTotalPhys if success else 0
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070054 if sys.platform.startswith('linux'):
55 if os.path.exists('/proc/meminfo'):
56 with open('/proc/meminfo') as meminfo:
Zachary Andersond71ba562021-07-29 23:43:49 -070057 memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB')
58 for line in meminfo:
59 match = memtotal_re.match(line)
60 if match:
61 return float(match.group(1)) * 2**10
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070062 if sys.platform == 'darwin':
Zachary Andersond71ba562021-07-29 23:43:49 -070063 try:
64 return int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070065 except: # pylint: disable=bare-except
Zachary Andersond71ba562021-07-29 23:43:49 -070066 return 0
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070067 return 0
Zachary Andersond71ba562021-07-29 23:43:49 -070068
69
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070070def parse_size(string):
Zachary Andersond71ba562021-07-29 23:43:49 -070071 i = next(i for (i, c) in enumerate(string) if not c.isdigit())
72 number = string[:i].strip()
73 unit = string[i:].strip()
74 return int(float(number) * UNITS[unit])
75
76
77class ParseSizeAction(argparse.Action):
78
79 def __call__(self, parser, args, values, option_string=None):
80 sizes = getattr(args, self.dest, [])
81 for value in values:
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070082 (k, val) = value.split('=', 1)
83 sizes.append((k, parse_size(val)))
Zachary Andersond71ba562021-07-29 23:43:49 -070084 setattr(args, self.dest, sizes)
85
86
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070087def main():
Zachary Anderson9e0316d2022-06-03 13:00:14 -070088 parser = argparse.ArgumentParser()
89 parser.add_argument(
Zachary Andersond71ba562021-07-29 23:43:49 -070090 '--memory-per-job',
91 action=ParseSizeAction,
92 default=[],
93 nargs='*',
94 help='Key value pairings (dart=1GB) giving an estimate of the amount of '
Zachary Anderson9e0316d2022-06-03 13:00:14 -070095 'memory needed for the class of job.'
96 )
97 parser.add_argument(
Zachary Andersond71ba562021-07-29 23:43:49 -070098 '--reserve-memory',
Zachary Anderson2f0b7f92022-06-04 15:21:18 -070099 type=parse_size,
Zachary Andersond71ba562021-07-29 23:43:49 -0700100 default=0,
Zachary Anderson9e0316d2022-06-03 13:00:14 -0700101 help='The amount of memory to be held out of the amount for jobs to use.'
102 )
103 args = parser.parse_args()
Zachary Andersond71ba562021-07-29 23:43:49 -0700104
Zachary Anderson2f0b7f92022-06-04 15:21:18 -0700105 total_memory = get_total_memory()
Zachary Andersond71ba562021-07-29 23:43:49 -0700106
Zachary Anderson9e0316d2022-06-03 13:00:14 -0700107 # Ensure the total memory used in the calculation below is at least 0
108 mem_total_bytes = max(0, total_memory - args.reserve_memory)
Zachary Andersond71ba562021-07-29 23:43:49 -0700109
Zachary Anderson9e0316d2022-06-03 13:00:14 -0700110 # Ensure the number of cpus used in the calculation below is at least 1
111 try:
112 cpu_cap = multiprocessing.cpu_count()
Zachary Anderson2f0b7f92022-06-04 15:21:18 -0700113 except: # pylint: disable=bare-except
Zachary Anderson9e0316d2022-06-03 13:00:14 -0700114 cpu_cap = 1
Zachary Andersond71ba562021-07-29 23:43:49 -0700115
Zachary Anderson9e0316d2022-06-03 13:00:14 -0700116 concurrent_jobs = {}
117 for job, memory_per_job in args.memory_per_job:
118 # Calculate the number of jobs that will fit in memory. Ensure the
119 # value is at least 1.
120 num_concurrent_jobs = int(max(1, mem_total_bytes / memory_per_job))
121 # Cap the number of jobs by the number of cpus available.
122 concurrent_jobs[job] = min(num_concurrent_jobs, cpu_cap)
Zachary Andersond71ba562021-07-29 23:43:49 -0700123
Zachary Anderson9e0316d2022-06-03 13:00:14 -0700124 print(json.dumps(concurrent_jobs))
Zachary Andersond71ba562021-07-29 23:43:49 -0700125
Zachary Anderson9e0316d2022-06-03 13:00:14 -0700126 return 0
Zachary Andersond71ba562021-07-29 23:43:49 -0700127
128
129if __name__ == '__main__':
Zachary Anderson2f0b7f92022-06-04 15:21:18 -0700130 sys.exit(main())