2 # Copyright 2014 The Chromium Authors
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
6 # This script computs the number of concurrent links we want to run in the build
7 # as a function of machine spec. It's based on GetDefaultConcurrentLinks in GYP.
10 import multiprocessing
16 sys.path.insert(1, os.path.join(os.path.dirname(__file__), '..'))
20 def _GetTotalMemoryInBytes():
21 if sys.platform in ('win32', 'cygwin'):
24 class MEMORYSTATUSEX(ctypes.Structure):
26 ("dwLength", ctypes.c_ulong),
27 ("dwMemoryLoad", ctypes.c_ulong),
28 ("ullTotalPhys", ctypes.c_ulonglong),
29 ("ullAvailPhys", ctypes.c_ulonglong),
30 ("ullTotalPageFile", ctypes.c_ulonglong),
31 ("ullAvailPageFile", ctypes.c_ulonglong),
32 ("ullTotalVirtual", ctypes.c_ulonglong),
33 ("ullAvailVirtual", ctypes.c_ulonglong),
34 ("sullAvailExtendedVirtual", ctypes.c_ulonglong),
37 stat = MEMORYSTATUSEX(dwLength=ctypes.sizeof(MEMORYSTATUSEX))
38 ctypes.windll.kernel32.GlobalMemoryStatusEx(ctypes.byref(stat))
39 return stat.ullTotalPhys
40 elif sys.platform.startswith('linux'):
41 if os.path.exists("/proc/meminfo"):
42 with open("/proc/meminfo") as meminfo:
43 memtotal_re = re.compile(r'^MemTotal:\s*(\d*)\s*kB')
45 match = memtotal_re.match(line)
48 return float(match.group(1)) * 2**10
49 elif sys.platform == 'darwin':
51 return int(subprocess.check_output(['sysctl', '-n', 'hw.memsize']))
54 # TODO(scottmg): Implement this for other platforms.
58 def _GetDefaultConcurrentLinks(per_link_gb, reserve_gb, thin_lto_type,
59 secondary_per_link_gb, override_ram_in_gb):
62 'per_link_gb={} reserve_gb={} secondary_per_link_gb={}'.format(
63 per_link_gb, reserve_gb, secondary_per_link_gb))
64 if override_ram_in_gb:
65 mem_total_gb = override_ram_in_gb
67 mem_total_gb = float(_GetTotalMemoryInBytes()) / 2**30
68 adjusted_mem_total_gb = max(0, mem_total_gb - reserve_gb)
70 # Ensure that there is at least as many links allocated for the secondary as
71 # there is for the primary. The secondary link usually uses fewer gbs.
73 max(1, adjusted_mem_total_gb / (per_link_gb + secondary_per_link_gb)))
76 cpu_count = multiprocessing.cpu_count()
80 # A local LTO links saturate all cores, but only for some amount of the link.
81 # Goma LTO runs LTO codegen on goma, only run one of these tasks at once.
83 if thin_lto_type is not None:
84 if thin_lto_type == 'goma':
87 assert thin_lto_type == 'local'
88 cpu_cap = min(cpu_count, 6)
91 'cpu_count={} cpu_cap={} mem_total_gb={:.1f}GiB adjusted_mem_total_gb={:.1f}GiB'
92 .format(cpu_count, cpu_cap, mem_total_gb, adjusted_mem_total_gb))
94 num_links = min(mem_cap, cpu_cap)
95 if num_links == cpu_cap:
96 if cpu_cap == cpu_count:
99 reason = 'cpu_cap (thinlto)'
103 # static link see too many open files if we have many concurrent links.
104 # ref: http://b/233068481
109 explanation.append('concurrent_links={} (reason: {})'.format(
112 # Use remaining RAM for a secondary pool if needed.
113 if secondary_per_link_gb:
114 mem_remaining = adjusted_mem_total_gb - num_links * per_link_gb
115 secondary_size = int(max(0, mem_remaining / secondary_per_link_gb))
116 if secondary_size > cpu_count:
117 secondary_size = cpu_count
120 reason = 'mem_remaining={:.1f}GiB'.format(mem_remaining)
121 explanation.append('secondary_size={} (reason: {})'.format(
122 secondary_size, reason))
126 return num_links, secondary_size, explanation
130 parser = argparse.ArgumentParser()
131 parser.add_argument('--mem_per_link_gb', type=int, default=8)
132 parser.add_argument('--reserve_mem_gb', type=int, default=0)
133 parser.add_argument('--secondary_mem_per_link', type=int, default=0)
134 parser.add_argument('--override-ram-in-gb-for-testing', type=float, default=0)
135 parser.add_argument('--thin-lto')
136 options = parser.parse_args()
138 primary_pool_size, secondary_pool_size, explanation = (
139 _GetDefaultConcurrentLinks(options.mem_per_link_gb,
140 options.reserve_mem_gb, options.thin_lto,
141 options.secondary_mem_per_link,
142 options.override_ram_in_gb_for_testing))
143 if options.override_ram_in_gb_for_testing:
144 print('primary={} secondary={} explanation={}'.format(
145 primary_pool_size, secondary_pool_size, explanation))
148 gn_helpers.ToGNString({
149 'primary_pool_size': primary_pool_size,
150 'secondary_pool_size': secondary_pool_size,
151 'explanation': explanation,
156 if __name__ == '__main__':