xref: /llvm-project/libcxx/utils/libcxx/test/android.py (revision 52dc4918ca8b874ddd4e4fcad873a66ecc5b6953)
1*d173ce4aSRyan Prichard# ===----------------------------------------------------------------------===##
2*d173ce4aSRyan Prichard#
3*d173ce4aSRyan Prichard# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4*d173ce4aSRyan Prichard# See https://llvm.org/LICENSE.txt for license information.
5*d173ce4aSRyan Prichard# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6*d173ce4aSRyan Prichard#
7*d173ce4aSRyan Prichard# ===----------------------------------------------------------------------===##
8*d173ce4aSRyan Prichard
9*d173ce4aSRyan Prichardimport re
10*d173ce4aSRyan Prichardimport select
11*d173ce4aSRyan Prichardimport socket
12*d173ce4aSRyan Prichardimport subprocess
13*d173ce4aSRyan Prichardimport tempfile
14*d173ce4aSRyan Prichardimport threading
15*d173ce4aSRyan Prichardfrom typing import List
16*d173ce4aSRyan Prichard
17*d173ce4aSRyan Prichard
18*d173ce4aSRyan Pricharddef _get_cpu_count() -> int:
19*d173ce4aSRyan Prichard    # Determine the number of cores by listing a /sys directory. Older devices
20*d173ce4aSRyan Prichard    # lack `nproc`. Even if a static toybox binary is pushed to the device, it may
21*d173ce4aSRyan Prichard    # return an incorrect value. (e.g. On a Nexus 7 running Android 5.0, toybox
22*d173ce4aSRyan Prichard    # nproc returns 1 even though the device has 4 CPUs.)
23*d173ce4aSRyan Prichard    job = subprocess.run(["adb", "shell", "ls /sys/devices/system/cpu"],
24*d173ce4aSRyan Prichard                         encoding="utf8", check=False,
25*d173ce4aSRyan Prichard                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
26*d173ce4aSRyan Prichard    if job.returncode == 1:
27*d173ce4aSRyan Prichard        # Maybe adb is missing, maybe ANDROID_SERIAL needs to be defined, maybe the
28*d173ce4aSRyan Prichard        # /sys subdir isn't there. Most errors will be handled later, just use one
29*d173ce4aSRyan Prichard        # job. (N.B. The adb command still succeeds even if ls fails on older
30*d173ce4aSRyan Prichard        # devices that lack the shell_v2 adb feature.)
31*d173ce4aSRyan Prichard        return 1
32*d173ce4aSRyan Prichard    # Make sure there are no CR characters in the output. Pre-shell_v2, the adb
33*d173ce4aSRyan Prichard    # stdout comes from a master pty so newlines are CRLF-delimited. On Windows,
34*d173ce4aSRyan Prichard    # LF might also get expanded to CRLF.
35*d173ce4aSRyan Prichard    cpu_listing = job.stdout.replace('\r', '\n')
36*d173ce4aSRyan Prichard
37*d173ce4aSRyan Prichard    # Count lines that match "cpu${DIGITS}".
38*d173ce4aSRyan Prichard    result = len([line for line in cpu_listing.splitlines()
39*d173ce4aSRyan Prichard                  if re.match(r'cpu(\d)+$', line)])
40*d173ce4aSRyan Prichard
41*d173ce4aSRyan Prichard    # Restrict the result to something reasonable.
42*d173ce4aSRyan Prichard    if result < 1:
43*d173ce4aSRyan Prichard        result = 1
44*d173ce4aSRyan Prichard    if result > 1024:
45*d173ce4aSRyan Prichard        result = 1024
46*d173ce4aSRyan Prichard
47*d173ce4aSRyan Prichard    return result
48*d173ce4aSRyan Prichard
49*d173ce4aSRyan Prichard
50*d173ce4aSRyan Pricharddef _job_limit_socket_thread(temp_dir: tempfile.TemporaryDirectory,
51*d173ce4aSRyan Prichard                             server: socket.socket, job_count: int) -> None:
52*d173ce4aSRyan Prichard    """Service the job limit server socket, accepting only as many connections
53*d173ce4aSRyan Prichard    as there should be concurrent jobs.
54*d173ce4aSRyan Prichard    """
55*d173ce4aSRyan Prichard    clients: List[socket.socket] = []
56*d173ce4aSRyan Prichard    while True:
57*d173ce4aSRyan Prichard        rlist = list(clients)
58*d173ce4aSRyan Prichard        if len(clients) < job_count:
59*d173ce4aSRyan Prichard            rlist.append(server)
60*d173ce4aSRyan Prichard        rlist, _, _ = select.select(rlist, [], [])
61*d173ce4aSRyan Prichard        for sock in rlist:
62*d173ce4aSRyan Prichard            if sock == server:
63*d173ce4aSRyan Prichard                new_client, _ = server.accept()
64*d173ce4aSRyan Prichard                new_client.send(b"x")
65*d173ce4aSRyan Prichard                clients.append(new_client)
66*d173ce4aSRyan Prichard            else:
67*d173ce4aSRyan Prichard                sock.close()
68*d173ce4aSRyan Prichard                clients.remove(sock)
69*d173ce4aSRyan Prichard
70*d173ce4aSRyan Prichard
71*d173ce4aSRyan Pricharddef adb_job_limit_socket() -> str:
72*d173ce4aSRyan Prichard    """An Android device can frequently have many fewer cores than the host
73*d173ce4aSRyan Prichard    (e.g. 4 versus 128). We want to exploit all the device cores without
74*d173ce4aSRyan Prichard    overburdening it.
75*d173ce4aSRyan Prichard
76*d173ce4aSRyan Prichard    Create a Unix domain socket that only allows as many connections as CPUs on
77*d173ce4aSRyan Prichard    the Android device.
78*d173ce4aSRyan Prichard    """
79*d173ce4aSRyan Prichard
80*d173ce4aSRyan Prichard    # Create the job limit server socket.
81*d173ce4aSRyan Prichard    temp_dir = tempfile.TemporaryDirectory(prefix="libcxx_")
82*d173ce4aSRyan Prichard    sock_addr = temp_dir.name + "/adb_job.sock"
83*d173ce4aSRyan Prichard    server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
84*d173ce4aSRyan Prichard    server.bind(sock_addr)
85*d173ce4aSRyan Prichard    server.listen(1)
86*d173ce4aSRyan Prichard
87*d173ce4aSRyan Prichard    # Spawn a thread to service the socket. As a daemon thread, its existence
88*d173ce4aSRyan Prichard    # won't prevent interpreter shutdown. The temp dir will still be removed on
89*d173ce4aSRyan Prichard    # shutdown.
90*d173ce4aSRyan Prichard    cpu_count = _get_cpu_count()
91*d173ce4aSRyan Prichard    threading.Thread(target=_job_limit_socket_thread,
92*d173ce4aSRyan Prichard                     args=(temp_dir, server, cpu_count),
93*d173ce4aSRyan Prichard                     daemon=True).start()
94*d173ce4aSRyan Prichard
95*d173ce4aSRyan Prichard    return sock_addr
96