Enhance image optimization with compression and CPU affinity support#36
Merged
Conversation
- Fix qemu-img convert: positional args must follow all options (was breaking -c placement) - Add ImageUtils.hasCompressedClusters() using qemu-img check's real compressed-clusters count - Implement applyKeepCompress() to preserve source compression algorithm during optimization - New "Keep compression when optimizing" setting (disabled by default) with UI controls - Add i18n strings (English, Simplified Chinese, Traditional Chinese)
- Add compression selector (visible only for QCOW2 format) - Allow QCOW2→QCOW2 conversion to support re-compression/decompression - Handle in-place re-compress via temp file path to avoid read lock collision
- New CpuUtils utility to enumerate cores and identify frequency tiers - Add CPU affinity settings UI with multi-core selection dialog - Bind disk operations (qemu-img, clone) to selected cores via taskset - Support automatic big-core filtering on heterogeneous CPU layouts - Reserve KEY_CROSVM_CPU_AFFINITY for future VM affinity support
There was a problem hiding this comment.
Pull request overview
This PR expands disk operation capabilities by adding CPU affinity selection (for qemu-img / disk tasks) and improving qcow2 compression handling during convert/optimize flows, including preserving existing compression when requested.
Changes:
- Added host CPU topology utilities (
CpuUtils) and a new Settings UI flow to pick CPU cores for disk operations (persisted as a preference and applied viataskset). - Added user-facing disk compression selection in the disk format/convert dialog and propagated the chosen algorithm into command generation.
- Improved “optimize” behavior to optionally preserve existing qcow2 compression by detecting compressed clusters and reapplying the source algorithm.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| app/src/main/res/values/strings.xml | Adds new Settings strings for compression preservation and CPU affinity. |
| app/src/main/res/values-zh-rTW/strings.xml | Traditional Chinese translations for new Settings strings. |
| app/src/main/res/values-zh-rCN/strings.xml | Simplified Chinese translations for new Settings strings. |
| app/src/main/res/layout/fragment_main_settings.xml | Adds UI rows for “keep compression on optimize” and CPU affinity selection. |
| app/src/main/res/layout/dialog_disk_set_format.xml | Adds compression chooser row to disk format dialog layout. |
| app/src/main/java/cn/classfun/droidvm/ui/main/settings/MainSettingsFragment.java | Persists new preferences; implements CPU affinity selection dialog and summary updates. |
| app/src/main/java/cn/classfun/droidvm/ui/disk/operation/ImageCommandGenerate.java | Applies CPU affinity via taskset and refines qemu-img convert argument ordering/compress flags. |
| app/src/main/java/cn/classfun/droidvm/ui/disk/operation/DiskOperationActivity.java | Propagates affinity + keep-compress behavior into task preparation before command generation. |
| app/src/main/java/cn/classfun/droidvm/ui/disk/action/DiskSetFormatDialog.java | Adds compression selection logic and allows same-format “recompress” conversions for qcow2. |
| app/src/main/java/cn/classfun/droidvm/ui/disk/action/DiskActionDialog.java | Ensures optimize tasks opt into compression preservation. |
| app/src/main/java/cn/classfun/droidvm/lib/utils/ImageUtils.java | Adds compressed-cluster detection via qemu-img check --output=json. |
| app/src/main/java/cn/classfun/droidvm/lib/utils/CpuUtils.java | New CPU core enumeration/tiering + CSV-to-mask and display helpers for affinity. |
| app/src/main/java/cn/classfun/droidvm/daemon/vm/backend/CrosvmBackendInstance.java | Adds TODO notes for future VM process affinity binding using taskset. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+141
to
+154
| @NonNull | ||
| private static List<Integer> parseCsv(@NonNull String csv) { | ||
| var out = new ArrayList<Integer>(); | ||
| if (csv.isEmpty()) return out; | ||
| for (var part : csv.split(",")) { | ||
| part = part.trim(); | ||
| if (part.isEmpty()) continue; | ||
| try { | ||
| out.add(Integer.parseInt(part)); | ||
| } catch (NumberFormatException ignored) { | ||
| } | ||
| } | ||
| return out; | ||
| } |
Comment on lines
+229
to
+232
| /** | ||
| * qemu-img CPU affinity as a taskset -c core list (e.g. "4,5,6,7"). | ||
| * Empty string means no binding (qemu-img may use every core). | ||
| */ |
Comment on lines
+40
to
+43
| /** | ||
| * Restrict the worker (qemu-img, or pv for clone) to the given host cores. | ||
| * Value is a taskset -c list such as "4,5,6,7"; empty means no binding. | ||
| */ |
| <string name="settings_vm_clear_logs_before_start_title">Clear logs before start</string> | ||
| <string name="settings_vm_clear_logs_before_start_summary">Clear all console logs for the VM before it starts</string> | ||
| <string name="settings_vm_keep_compress_on_optimize_title">Keep compression when optimizing</string> | ||
| <string name="settings_vm_keep_compress_on_optimize_summary">Preserve a disk\'s existing compression when optimizing instead of decompressing it; off rewrites the disk uncompressed</string> |
BigfootACA
requested changes
Jul 6, 2026
Comment on lines
+138
to
+142
| // TODO(cpu-affinity): to bind the VM to selected host cores, prepend a | ||
| // system-taskset wrapper here (mirroring prepareStraceArguments): | ||
| // "taskset -a <hexMask>", with the mask from CpuUtils.coresCsvToHexMask | ||
| // reading a future MainSettingsFragment.KEY_CROSVM_CPU_AFFINITY setting. | ||
| // Note toybox taskset takes a hex mask (no "0x"), not a -c core list. |
Comment on lines
+96
to
+99
| public static String formatFreq(long khz) { | ||
| if (khz <= 0) return ""; | ||
| return String.format(Locale.US, "%.2f GHz", khz / 1_000_000.0); | ||
| } |
Comment on lines
+210
to
+213
| @NonNull | ||
| private static String fmt(@NonNull String f, Object... args) { | ||
| return String.format(Locale.US, f, args); | ||
| } |
| try { | ||
| raw = FileUtils.shellReadFile(path); | ||
| } catch (Exception rootFailed) { | ||
| Log.d(TAG, "freq read failed: " + path); |
Comment on lines
+42
to
+57
| public static boolean hasCompressedClusters(String path) { | ||
| try { | ||
| var result = runListQuiet( | ||
| getPrebuiltBinaryPath("qemu-img"), | ||
| "check", "--output=json", path); | ||
| var out = result.getOutString(); | ||
| // qemu-img check still writes its JSON report on non-zero exits | ||
| // (leaks/corruptions), so parse whatever it produced rather than | ||
| // gating on isSuccess(); a missing/garbage body throws -> false. | ||
| if (out == null || out.isEmpty()) | ||
| return false; | ||
| return new JSONObject(out).optLong("compressed-clusters", 0) > 0; | ||
| } catch (Exception e) { | ||
| return false; | ||
| } | ||
| } |
Member
There was a problem hiding this comment.
拆分一个getImageCheck函数,参考getImageInfo
Comment on lines
+371
to
+372
| private static java.util.Set<Integer> parseCsvToSet(@NonNull String csv) { | ||
| var set = new java.util.HashSet<Integer>(); |
Member
There was a problem hiding this comment.
应为Set<Integer>和HashSet<Integer>,添加import
Comment on lines
+69
to
+73
| public static final String KEY_VM_KEEP_COMPRESS_ON_OPTIMIZE = "vm_keep_compress_on_optimize"; | ||
| public static final String KEY_QEMU_IMG_CPU_AFFINITY = "qemu_img_cpu_affinity"; | ||
| // Reserved for a future crosvm/qemu VM affinity setting; not read or | ||
| // written yet, and intentionally has no UI in this release. | ||
| // public static final String KEY_CROSVM_CPU_AFFINITY = "crosvm_cpu_affinity"; |
- CrosvmBackendInstance: drop the no-op cpu-affinity TODO comment - CpuUtils: reuse SizeUtils.findFloatUnit for frequency formatting and StringUtils.fmt instead of a local fmt helper - ImageUtils: split out getImageCheck() mirroring getImageInfo() - MainSettingsFragment: import Set/HashSet, drop commented-out reserved key
findFloatUnit uses binary 1024 steps, which underreports clock speeds (2.60 GHz shown as 2.42 GHz). Frequencies are SI/decimal, so pick the Hz/kHz/MHz/GHz tier by dividing by 1000.
BigfootACA
approved these changes
Jul 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This pull request adds comprehensive support for CPU affinity and disk compression handling in disk operations and the VM backend. It introduces a new utility class for host CPU topology and affinity mask generation, enables user-facing selection of disk compression algorithms during format/convert actions, and ensures that disk optimization preserves compression where appropriate. The changes also lay the groundwork for binding VM processes to specific CPU cores.
CPU Affinity and Host Topology Utilities
CpuUtilsclass to enumerate host CPU cores, group them by frequency tiers, and generate CPU masks for affinity binding.CrosvmBackendInstance.javato include a TODO for prepending atasksetwrapper to bind VMs to selected host cores, using affinity masks fromCpuUtils.ImageCommandGenerateto support setting a CPU affinity mask for disk operations.Disk Compression Handling
DiskSetFormatDialog) to allow users to select a compression algorithm (deflate/zstd/none) when converting or creating qcow2 disks, with UI visibility logic based on format support [1] [2] [3] [4] [5] [6].Disk Compression Detection and Preservation
ImageUtils.hasCompressedClustersto reliably detect if a disk image actually contains compressed clusters, usingqemu-img check.Integration and Refactoring
These changes collectively improve performance, user control, and reliability for disk and VM operations by leveraging CPU topology and smarter disk compression management.