mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Feat] (Plugin, P11 B1): the broker hands the helper the core the server reserves for the session's apply thread and the helper execs the program through taskset on every other core (--keep-affinity opts out), so a client the scheduler would put there is not starved
This commit is contained in:
@@ -49,7 +49,10 @@ public final class ExternalClientBroker extends BroadcastReceiver {
|
||||
public static final String KEY_VERSION = "version";
|
||||
public static final int PROTOCOL_VERSION = 1;
|
||||
|
||||
/** The broker's answer: int version, then the control and aux descriptors. */
|
||||
/**
|
||||
* The broker's answer: int version, the control and aux descriptors, then long applyCore - the cpu
|
||||
* mask the server's `auto` policy reserves for this session's apply thread (0 = none).
|
||||
*/
|
||||
public static final int REPLY_FDS = IBinder.FIRST_CALL_TRANSACTION;
|
||||
/** The broker's refusal: String code, String message. */
|
||||
public static final int REPLY_REFUSED = IBinder.FIRST_CALL_TRANSACTION + 1;
|
||||
@@ -166,9 +169,12 @@ public final class ExternalClientBroker extends BroadcastReceiver {
|
||||
data.writeInt(PROTOCOL_VERSION);
|
||||
control.writeToParcel(data, 0);
|
||||
aux.writeToParcel(data, 0);
|
||||
// The core the server reserves for this session's apply thread (0 = none): the helper keeps
|
||||
// the client off it. Appended after the descriptors; a reader that stops before it is fine.
|
||||
data.writeLong(target.applyCore);
|
||||
boolean delivered = callback.transact(REPLY_FDS, data, null, IBinder.FLAG_ONEWAY);
|
||||
Log.i(TAG, "brokered a connection pair to " + target.endpoint + " for a client (delivered="
|
||||
+ delivered + ")");
|
||||
+ delivered + ", apply core 0x" + Long.toHexString(target.applyCore) + ")");
|
||||
} catch (Exception error) {
|
||||
Log.e(TAG, "could not hand the connection pair to the client", error);
|
||||
} finally {
|
||||
|
||||
@@ -44,8 +44,10 @@ import java.util.Map;
|
||||
* Two connected sockets: it clears their close-on-exec flag and {@code execve}s the program with
|
||||
* {@code MOBILEGL_TRANSPORT=spawn}, {@code MOBILEGL_IPC_CONTROL=fd:<control>,<aux>},
|
||||
* {@code MOBILEGL_IPC_DATA=shm} and {@code MOBILEGL_IPC_TOKEN} (the session's Hello carries it too).
|
||||
* A refusal: it prints the broker's name for it and exits with that refusal's code. No answer at
|
||||
* all within the timeout: it says so and exits {@value #EXIT_NO_ANSWER}.
|
||||
* When the broker names a cpu core the server reserves for the session's apply thread, the program is
|
||||
* exec'd through {@code taskset} on every other core (see {@link #clientMask}; {@code --keep-affinity}
|
||||
* opts out). A refusal: it prints the broker's name for it and exits with that refusal's code. No
|
||||
* answer at all within the timeout: it says so and exits {@value #EXIT_NO_ANSWER}.
|
||||
*/
|
||||
public final class ExternalClientHelper {
|
||||
private static final String TAG = "MobileGLHelper";
|
||||
@@ -64,8 +66,26 @@ public final class ExternalClientHelper {
|
||||
public static final int EXIT_EXEC_FAILED = 73;
|
||||
|
||||
private static final long DEFAULT_TIMEOUT_MS = 10000;
|
||||
private static final String TASKSET = "/system/bin/taskset";
|
||||
private static final Object answered = new Object();
|
||||
private static boolean done = false;
|
||||
private static volatile boolean keepAffinity = false;
|
||||
|
||||
/**
|
||||
* Every cpu this device counts except the server's reserved apply core, or 0 (no mask) when none is
|
||||
* reserved or nothing would be left. Why: the server pins a dialled-in shared-segment client's
|
||||
* apply thread to that core and spins there, and a client the scheduler also puts there (an
|
||||
* adb-shell one on the Redmi) is starved: rd12 27 fps on shared memory against 51 over tcp;
|
||||
* kept off it, the client takes the other prime core (CONTRACT-P11 B1). The cost: the whole program
|
||||
* loses that one core. --keep-affinity opts out.
|
||||
*/
|
||||
private static long clientMask(long applyCore) {
|
||||
if (applyCore == 0) return 0;
|
||||
int count = Math.min(64, Math.max(1, Runtime.getRuntime().availableProcessors()));
|
||||
long online = count >= 64 ? ~0L : (1L << count) - 1;
|
||||
long mask = online & ~applyCore;
|
||||
return mask == 0 || mask == online ? 0 : mask;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
Looper.prepareMainLooper();
|
||||
@@ -80,6 +100,8 @@ public final class ExternalClientHelper {
|
||||
break;
|
||||
} else if ("--server".equals(arg) && i + 1 < args.length) {
|
||||
serverPackage = args[++i];
|
||||
} else if ("--keep-affinity".equals(arg)) {
|
||||
keepAffinity = true;
|
||||
} else if ("--timeout-ms".equals(arg) && i + 1 < args.length) {
|
||||
try {
|
||||
timeoutMs = Long.parseLong(args[++i]);
|
||||
@@ -124,11 +146,16 @@ public final class ExternalClientHelper {
|
||||
int version = data.readInt();
|
||||
ParcelFileDescriptor control = ParcelFileDescriptor.CREATOR.createFromParcel(data);
|
||||
ParcelFileDescriptor aux = ParcelFileDescriptor.CREATOR.createFromParcel(data);
|
||||
long applyCore = data.dataAvail() >= 8 ? data.readLong() : 0;
|
||||
int controlFd = inheritable(control);
|
||||
int auxFd = inheritable(aux);
|
||||
Log.i(TAG, "broker v" + version + " handed over fd:" + controlFd + "," + auxFd + "; exec "
|
||||
+ programPath);
|
||||
exec(programPath, argv, controlFd, auxFd, presented);
|
||||
long mask = keepAffinity ? 0 : clientMask(applyCore);
|
||||
say("broker v" + version + " handed over fd:" + controlFd + "," + auxFd
|
||||
+ (mask != 0 ? "; the server's apply core 0x" + Long.toHexString(applyCore)
|
||||
+ " reserved, the program runs on cpu mask 0x" + Long.toHexString(mask)
|
||||
: "; no cpu mask applied")
|
||||
+ "; exec " + programPath);
|
||||
exec(programPath, argv, controlFd, auxFd, presented, mask);
|
||||
} catch (Throwable error) {
|
||||
say("could not exec " + programPath + ": " + error);
|
||||
finish(EXIT_EXEC_FAILED);
|
||||
@@ -195,7 +222,7 @@ public final class ExternalClientHelper {
|
||||
System.err.println(PREFIX + problem);
|
||||
System.err.println("usage: MOBILEGL_IPC_TOKEN=<token> CLASSPATH=<server APK> app_process / "
|
||||
+ ExternalClientHelper.class.getName()
|
||||
+ " [--server <package>] [--timeout-ms <n>] [--] <program> [args...]");
|
||||
+ " [--server <package>] [--timeout-ms <n>] [--keep-affinity] [--] <program> [args...]");
|
||||
System.exit(EXIT_USAGE);
|
||||
}
|
||||
|
||||
@@ -240,7 +267,8 @@ public final class ExternalClientHelper {
|
||||
return pfd.detachFd();
|
||||
}
|
||||
|
||||
private static void exec(String path, List<String> argv, int control, int aux, String token) throws Exception {
|
||||
private static void exec(String path, List<String> argv, int control, int aux, String token, long cpuMask)
|
||||
throws Exception {
|
||||
Map<String, String> env = new LinkedHashMap<>();
|
||||
String[] inherited = Os.environ();
|
||||
if (inherited != null) {
|
||||
@@ -257,7 +285,18 @@ public final class ExternalClientHelper {
|
||||
env.put("MOBILEGL_IPC_TOKEN", token);
|
||||
List<String> envp = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : env.entrySet()) envp.add(entry.getKey() + "=" + entry.getValue());
|
||||
Os.execve(path, argv.toArray(new String[0]), envp.toArray(new String[0]));
|
||||
if (cpuMask != 0 && new File(TASKSET).canExecute()) {
|
||||
// No sched_setaffinity from Java: toybox taskset sets the mask and execvp's the program,
|
||||
// which inherits it (and the two descriptors, whose close-on-exec flag is already clear).
|
||||
List<String> wrapped = new ArrayList<>();
|
||||
wrapped.add(TASKSET);
|
||||
wrapped.add(Long.toHexString(cpuMask));
|
||||
wrapped.add(path);
|
||||
wrapped.addAll(argv.subList(1, argv.size()));
|
||||
Os.execve(TASKSET, wrapped.toArray(new String[0]), envp.toArray(new String[0]));
|
||||
} else {
|
||||
Os.execve(path, argv.toArray(new String[0]), envp.toArray(new String[0]));
|
||||
}
|
||||
throw new IllegalStateException("execve returned");
|
||||
}
|
||||
|
||||
|
||||
@@ -40,14 +40,34 @@ public final class MobileGLServerService extends Service {
|
||||
final String endpoint;
|
||||
final String token;
|
||||
final Process process;
|
||||
// The core the server's `auto` policy gives a dialled-in shared-segment client's apply thread
|
||||
// (ServerEnvironment.reservedApplyCore); the broker hands it to the helper, which keeps the
|
||||
// client off it. 0 = none reserved.
|
||||
final long applyCore;
|
||||
|
||||
BrokerTarget(String endpoint, String token, Process process) {
|
||||
BrokerTarget(String endpoint, String token, Process process, long applyCore) {
|
||||
this.endpoint = endpoint;
|
||||
this.token = token;
|
||||
this.process = process;
|
||||
this.applyCore = applyCore;
|
||||
}
|
||||
}
|
||||
|
||||
/** cpuinfo_max_freq of every cpu this process can count (0 where unreadable). */
|
||||
private static long[] cpuMaxFrequenciesKHz() {
|
||||
int count = Math.min(64, Math.max(1, Runtime.getRuntime().availableProcessors()));
|
||||
long[] frequencies = new long[count];
|
||||
for (int cpu = 0; cpu < count; cpu++) {
|
||||
File file = new File("/sys/devices/system/cpu/cpu" + cpu + "/cpufreq/cpuinfo_max_freq");
|
||||
try (BufferedReader reader = new BufferedReader(new java.io.FileReader(file))) {
|
||||
frequencies[cpu] = Long.parseLong(reader.readLine().trim());
|
||||
} catch (Exception unreadable) {
|
||||
frequencies[cpu] = 0;
|
||||
}
|
||||
}
|
||||
return frequencies;
|
||||
}
|
||||
|
||||
// Set when a supervisor the broker can reach is running, cleared when it stops. The broker runs
|
||||
// in this same process (:mglsrv), so a broadcast that finds null here finds no server.
|
||||
private static volatile BrokerTarget brokerTarget;
|
||||
@@ -135,6 +155,10 @@ public final class MobileGLServerService extends Service {
|
||||
if (endpoint == null) endpoint = "tcp://127.0.0.1:40613";
|
||||
final String envExtra = intent == null ? null : intent.getStringExtra("env");
|
||||
final String token = intent == null ? null : intent.getStringExtra("token");
|
||||
java.util.Map<String, String> requested = new java.util.HashMap<>();
|
||||
ServerEnvironment.apply(requested, envExtra);
|
||||
final long applyCore = ServerEnvironment.reservedApplyCore(cpuMaxFrequenciesKHz(),
|
||||
requested.get("MOBILEGL_IPC_SERVER_AFFINITY"));
|
||||
try {
|
||||
supervisor = startSupervisor(endpoint, envExtra, token, "mgl-supervisor-log", startId, true);
|
||||
// P11 B1: the broker's target. A unix endpoint is reachable by this app as it is (a
|
||||
@@ -145,14 +169,16 @@ public final class MobileGLServerService extends Service {
|
||||
new java.security.SecureRandom().nextLong() & 0xffffffffL);
|
||||
brokerSupervisor = startSupervisor(privateName, envExtra, token, "mgl-broker-supervisor-log",
|
||||
startId, false);
|
||||
brokerTarget = new BrokerTarget(privateName, token, brokerSupervisor);
|
||||
brokerTarget = new BrokerTarget(privateName, token, brokerSupervisor, applyCore);
|
||||
Log.i(TAG, "same-device external clients: broker listener " + privateName
|
||||
+ " beside the TCP-only supervisor on " + endpoint);
|
||||
+ " beside the TCP-only supervisor on " + endpoint + "; apply core reserved for them 0x"
|
||||
+ Long.toHexString(applyCore));
|
||||
} else {
|
||||
String unix = endpoint.startsWith("@") || endpoint.startsWith("/") ? endpoint
|
||||
: new File(getFilesDir(), endpoint).getAbsolutePath();
|
||||
brokerTarget = new BrokerTarget(unix, token, supervisor);
|
||||
Log.i(TAG, "same-device external clients: the broker connects to " + unix);
|
||||
brokerTarget = new BrokerTarget(unix, token, supervisor, applyCore);
|
||||
Log.i(TAG, "same-device external clients: the broker connects to " + unix
|
||||
+ "; apply core reserved for them 0x" + Long.toHexString(applyCore));
|
||||
}
|
||||
} catch (Exception error) {
|
||||
Log.e(TAG, "cannot start TCP supervisor", error);
|
||||
|
||||
@@ -461,7 +461,8 @@ public final class ServerControlActivity extends Activity {
|
||||
text.append(launchCommand(usable ? token : "<token>")).append("\n\n")
|
||||
.append("Broker: ").append(getPackageName()).append('/').append(ExternalClientBroker.class.getName())
|
||||
.append("\nThe token travels in the environment (MOBILEGL_IPC_TOKEN, or MOBILEGL_IPC_TOKEN_FILE=<file>)")
|
||||
.append(", never on the command line. The program runs over shared memory; the server keeps")
|
||||
.append(", never on the command line. The program runs over shared memory, off the one cpu core")
|
||||
.append(" the server's render thread takes (--keep-affinity to opt out); the server keeps")
|
||||
.append(" running as a foreground service. Battery: set both this app and the client's app to")
|
||||
.append(" \"No restrictions\".");
|
||||
launchCommandView.setText(text.toString());
|
||||
|
||||
@@ -81,6 +81,34 @@ public final class ServerEnvironment {
|
||||
return edits;
|
||||
}
|
||||
|
||||
/**
|
||||
* P11 B1 (CONTRACT-P11 B1): the core the server's `auto` policy reserves for the apply thread of a
|
||||
* dialled-in shared-segment client - MG_Remote/Server/ApplyThreadPolicy.h ReservedApplyCore over
|
||||
* ServerLoop.cpp's DetectBigCoreMask, mirrored here so the broker can tell the helper which core to
|
||||
* keep the client off. {@code maxFreqKHz[i]} is cpu i's cpuinfo_max_freq (0 = unreadable, which
|
||||
* like the native probe gives no answer). A cpu is big when its ceiling is within 15% of the peak;
|
||||
* the reserved core is the LOWEST big one, and only when the big set is a strict subset of at least
|
||||
* two. 0 = none. {@code affinityOverride} is the server's MOBILEGL_IPC_SERVER_AFFINITY: anything
|
||||
* but unset / empty / {@code auto} is an operator's own placement, and then nothing is reserved.
|
||||
*/
|
||||
public static long reservedApplyCore(long[] maxFreqKHz, String affinityOverride) {
|
||||
if (affinityOverride != null && !affinityOverride.isEmpty() && !affinityOverride.equals("auto")) return 0;
|
||||
int count = Math.min(64, maxFreqKHz.length);
|
||||
long peak = 0;
|
||||
for (int cpu = 0; cpu < count; cpu++) {
|
||||
if (maxFreqKHz[cpu] <= 0) return 0;
|
||||
peak = Math.max(peak, maxFreqKHz[cpu]);
|
||||
}
|
||||
if (count == 0 || peak == 0) return 0;
|
||||
long big = 0;
|
||||
for (int cpu = 0; cpu < count; cpu++) {
|
||||
if (maxFreqKHz[cpu] >= peak * 85 / 100) big |= 1L << cpu;
|
||||
}
|
||||
long online = count >= 64 ? ~0L : (1L << count) - 1;
|
||||
if ((big & online) == online || Long.bitCount(big) < 2) return 0;
|
||||
return Long.lowestOneBit(big);
|
||||
}
|
||||
|
||||
/**
|
||||
* Aspect-fit (letterbox / pillarbox): the largest {@code bufferWidth:bufferHeight} rectangle
|
||||
* inside a {@code containerWidth x containerHeight} view, as {width, height}. {-1, -1} (fill the
|
||||
|
||||
Reference in New Issue
Block a user