Refactor VM config to support different CPU topologies
This is preliminary work to support crosvm's --host-cpu-topology (WIP),
which will make it possible to mirror host's CPU topology in the guest.
As a first step, we refactor AVF's system API to stop accepting number
of vCPUs as an argument, but instead only expose two topology configs:
1 vCPU (default) and matching the host's CPU topology.
For the time being, the latter results in crosvm started with `--cpu
<nproc>`.
Bug: 266664564
Test: atest -p packages/modules/Virtualization:avf-presubmit
Change-Id: I03a37be0b68b93dc0fa6e84fd51ca3bdefbe6dde
diff --git a/javalib/api/system-current.txt b/javalib/api/system-current.txt
index b455c85..1ba479f 100644
--- a/javalib/api/system-current.txt
+++ b/javalib/api/system-current.txt
@@ -57,15 +57,17 @@
public final class VirtualMachineConfig {
method @Nullable public String getApkPath();
- method @NonNull public int getDebugLevel();
+ method public int getCpuTopology();
+ method public int getDebugLevel();
method @IntRange(from=0) public long getEncryptedStorageBytes();
method @IntRange(from=0) public long getMemoryBytes();
- method @IntRange(from=1) public int getNumCpus();
method @Nullable public String getPayloadBinaryName();
method public boolean isCompatibleWith(@NonNull android.system.virtualmachine.VirtualMachineConfig);
method public boolean isEncryptedStorageEnabled();
method public boolean isProtectedVm();
method public boolean isVmOutputCaptured();
+ field public static final int CPU_TOPOLOGY_MATCH_HOST = 1; // 0x1
+ field public static final int CPU_TOPOLOGY_ONE_CPU = 0; // 0x0
field public static final int DEBUG_LEVEL_FULL = 1; // 0x1
field public static final int DEBUG_LEVEL_NONE = 0; // 0x0
}
@@ -74,10 +76,10 @@
ctor public VirtualMachineConfig.Builder(@NonNull android.content.Context);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig build();
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setApkPath(@NonNull String);
+ method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setCpuTopology(int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setDebugLevel(int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setEncryptedStorageBytes(@IntRange(from=1) long);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setMemoryBytes(@IntRange(from=1) long);
- method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setNumCpus(@IntRange(from=1) int);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setPayloadBinaryName(@NonNull String);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setProtectedVm(boolean);
method @NonNull public android.system.virtualmachine.VirtualMachineConfig.Builder setVmOutputCaptured(boolean);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index cb9bad0..5842d01 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -60,7 +60,7 @@
private static final String[] EMPTY_STRING_ARRAY = {};
// These define the schema of the config file persisted on disk.
- private static final int VERSION = 5;
+ private static final int VERSION = 6;
private static final String KEY_VERSION = "version";
private static final String KEY_PACKAGENAME = "packageName";
private static final String KEY_APKPATH = "apkPath";
@@ -70,6 +70,7 @@
private static final String KEY_PROTECTED_VM = "protectedVm";
private static final String KEY_MEMORY_BYTES = "memoryBytes";
private static final String KEY_NUM_CPUS = "numCpus";
+ private static final String KEY_CPU_TOPOLOGY = "cpuTopology";
private static final String KEY_ENCRYPTED_STORAGE_BYTES = "encryptedStorageBytes";
private static final String KEY_VM_OUTPUT_CAPTURED = "vmOutputCaptured";
@@ -97,6 +98,33 @@
*/
@SystemApi public static final int DEBUG_LEVEL_FULL = 1;
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(
+ prefix = "CPU_TOPOLOGY_",
+ value = {
+ CPU_TOPOLOGY_ONE_CPU,
+ CPU_TOPOLOGY_MATCH_HOST,
+ })
+ public @interface CpuTopology {}
+
+ /**
+ * Run VM with 1 vCPU. This is the default option, usually the fastest to boot and consuming the
+ * least amount of resources. Typically the best option for small or ephemeral workloads.
+ *
+ * @hide
+ */
+ @SystemApi public static final int CPU_TOPOLOGY_ONE_CPU = 0;
+
+ /**
+ * Run VM with vCPU topology matching the physical CPU topology of the host. Usually takes
+ * longer to boot and cosumes more resources compared to a single vCPU. Typically a good option
+ * for long-running workloads that benefit from parallel execution.
+ *
+ * @hide
+ */
+ @SystemApi public static final int CPU_TOPOLOGY_MATCH_HOST = 1;
+
/** Name of a package whose primary APK contains the VM payload. */
@Nullable private final String mPackageName;
@@ -116,10 +144,8 @@
*/
private final long mMemoryBytes;
- /**
- * Number of vCPUs in the VM. Defaults to 1 when not specified.
- */
- private final int mNumCpus;
+ /** CPU topology configuration of the VM. */
+ @CpuTopology private final int mCpuTopology;
/**
* Path within the APK to the payload config file that defines software aspects of the VM.
@@ -143,7 +169,7 @@
@DebugLevel int debugLevel,
boolean protectedVm,
long memoryBytes,
- int numCpus,
+ @CpuTopology int cpuTopology,
long encryptedStorageBytes,
boolean vmOutputCaptured) {
// This is only called from Builder.build(); the builder handles parameter validation.
@@ -154,7 +180,7 @@
mDebugLevel = debugLevel;
mProtectedVm = protectedVm;
mMemoryBytes = memoryBytes;
- mNumCpus = numCpus;
+ mCpuTopology = cpuTopology;
mEncryptedStorageBytes = encryptedStorageBytes;
mVmOutputCaptured = vmOutputCaptured;
}
@@ -225,7 +251,7 @@
if (memoryBytes != 0) {
builder.setMemoryBytes(memoryBytes);
}
- builder.setNumCpus(b.getInt(KEY_NUM_CPUS));
+ builder.setCpuTopology(b.getInt(KEY_CPU_TOPOLOGY));
long encryptedStorageBytes = b.getLong(KEY_ENCRYPTED_STORAGE_BYTES);
if (encryptedStorageBytes != 0) {
builder.setEncryptedStorageBytes(encryptedStorageBytes);
@@ -258,7 +284,7 @@
b.putString(KEY_PAYLOADBINARYNAME, mPayloadBinaryName);
b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
- b.putInt(KEY_NUM_CPUS, mNumCpus);
+ b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology);
if (mMemoryBytes > 0) {
b.putLong(KEY_MEMORY_BYTES, mMemoryBytes);
}
@@ -312,7 +338,6 @@
* @hide
*/
@SystemApi
- @NonNull
@DebugLevel
public int getDebugLevel() {
return mDebugLevel;
@@ -341,14 +366,14 @@
}
/**
- * Returns the number of vCPUs that the VM will have.
+ * Returns the CPU topology configuration of the VM.
*
* @hide
*/
@SystemApi
- @IntRange(from = 1)
- public int getNumCpus() {
- return mNumCpus;
+ @CpuTopology
+ public int getCpuTopology() {
+ return mCpuTopology;
}
/**
@@ -455,7 +480,14 @@
}
vsConfig.protectedVm = mProtectedVm;
vsConfig.memoryMib = bytesToMebiBytes(mMemoryBytes);
- vsConfig.numCpus = mNumCpus;
+ switch (mCpuTopology) {
+ case CPU_TOPOLOGY_MATCH_HOST:
+ vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.MATCH_HOST;
+ break;
+ default:
+ vsConfig.cpuTopology = android.system.virtualizationservice.CpuTopology.ONE_CPU;
+ break;
+ }
// Don't allow apps to set task profiles ... at least for now.
vsConfig.taskProfiles = EMPTY_STRING_ARRAY;
return vsConfig;
@@ -486,7 +518,7 @@
private boolean mProtectedVm;
private boolean mProtectedVmSet;
private long mMemoryBytes;
- private int mNumCpus = 1;
+ @CpuTopology private int mCpuTopology = CPU_TOPOLOGY_ONE_CPU;
private long mEncryptedStorageBytes;
private boolean mVmOutputCaptured = false;
@@ -555,7 +587,7 @@
mDebugLevel,
mProtectedVm,
mMemoryBytes,
- mNumCpus,
+ mCpuTopology,
mEncryptedStorageBytes,
mVmOutputCaptured);
}
@@ -687,25 +719,21 @@
}
/**
- * Sets the number of vCPUs in the VM. Defaults to 1. Cannot be more than the number of real
- * CPUs (as returned by {@link Runtime#availableProcessors}).
+ * Sets the CPU topology configuration of the VM. Defaults to {@link #CPU_TOPOLOGY_ONE_CPU}.
+ *
+ * <p>This determines how many virtual CPUs will be created, and their performance and
+ * scheduling characteristics, such as affinity masks. Topology also has an effect on memory
+ * usage as each vCPU requires additional memory to keep its state.
*
* @hide
*/
@SystemApi
@NonNull
- public Builder setNumCpus(@IntRange(from = 1) int numCpus) {
- int availableCpus = Runtime.getRuntime().availableProcessors();
- if (numCpus < 1 || numCpus > availableCpus) {
- throw new IllegalArgumentException(
- "Number of vCPUs ("
- + numCpus
- + ") is out of "
- + "range [1, "
- + availableCpus
- + "]");
+ public Builder setCpuTopology(@CpuTopology int cpuTopology) {
+ if (cpuTopology != CPU_TOPOLOGY_ONE_CPU && cpuTopology != CPU_TOPOLOGY_MATCH_HOST) {
+ throw new IllegalArgumentException("Invalid cpuTopology: " + cpuTopology);
}
- mNumCpus = numCpus;
+ mCpuTopology = cpuTopology;
return this;
}