From 53abe6077dc3a13702c626d0196481e725bdbda0 Mon Sep 17 00:00:00 2001 From: Billy Lau Date: Mon, 25 Apr 2022 16:29:26 -0500 Subject: [PATCH] Reuse a same buffer per session instead of reallocating each time. Previously, a fixed size buffer was allocated each time a call to `computeSha256DigestForLargeFile` was made. This causes undesirable performance issues. This change allocates a buffer per apex/module-update session instead. To fix this, that method is amended to take in a buffer passed in by the caller instead. To aid in the usage of this API, a new method called `createLargeFileBuffer` is introduced to help callers allocate a pre-determined buffer size to be passed into the SHA computation method based on whether the device is low RAM device or not. Subsequent RSS benchmarking shows that this helps to significantly lower the RSS high watermark (HWM) for the `system_server` process into just noise. Bug: 229137737 Test: Manual Change-Id: I6d2d17353f999bd6cfb937cd5ae6e6925f1353ac --- core/java/android/util/PackageUtils.java | 44 ++++++++++++------- .../server/BinaryTransparencyService.java | 9 ++-- 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java index c5ab82dbf0c51..11481209284a5 100644 --- a/core/java/android/util/PackageUtils.java +++ b/core/java/android/util/PackageUtils.java @@ -171,39 +171,53 @@ public final class PackageUtils { } /** - * @see #computeSha256DigestForLargeFile(String, String) + * Creates a fixed size buffer based on whether the device is low ram or not. This is to be used + * with the {@link #computeSha256DigestForLargeFile(String, byte[])} and + * {@link #computeSha256DigestForLargeFile(String, byte[], String)} methods. + * @return a byte array of size {@link #LOW_RAM_BUFFER_SIZE_BYTES} if the device is a low RAM + * device, otherwise a byte array of size {@link #HIGH_RAM_BUFFER_SIZE_BYTES} */ - public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath) { - return computeSha256DigestForLargeFile(filePath, null); + public static @NonNull byte[] createLargeFileBuffer() { + int bufferSize = ActivityManager.isLowRamDeviceStatic() + ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES; + return new byte[bufferSize]; } /** - * Computes the SHA256 digest of large files. - * @param filePath The path to which the file's content is to be hashed. - * @param separator Separator between each pair of characters, such as colon, or null to omit. - * @return The digest or null if an error occurs. + * @see #computeSha256DigestForLargeFile(String, byte[], String) */ public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, - @Nullable String separator) { + @NonNull byte[] fileBuffer) { + return computeSha256DigestForLargeFile(filePath, fileBuffer, null); + } + + /** + * Computes the SHA256 digest of large files. This is typically useful for large APEXs. + * @param filePath The path to which the file's content is to be hashed. + * @param fileBuffer A buffer to read file's content into memory. It is strongly recommended to + * make use of the {@link #createLargeFileBuffer()} method to create this + * buffer. + * @param separator Separator between each pair of characters, such as colon, or null to omit. + * @return The SHA256 digest or null if an error occurs. + */ + public static @Nullable String computeSha256DigestForLargeFile(@NonNull String filePath, + @NonNull byte[] fileBuffer, @Nullable String separator) { MessageDigest messageDigest; try { messageDigest = MessageDigest.getInstance("SHA256"); messageDigest.reset(); } catch (NoSuchAlgorithmException e) { - // this shouldn't happen! + // this really shouldn't happen! return null; } - boolean isLowRamDevice = ActivityManager.isLowRamDeviceStatic(); - int bufferSize = isLowRamDevice ? LOW_RAM_BUFFER_SIZE_BYTES : HIGH_RAM_BUFFER_SIZE_BYTES; - File f = new File(filePath); try { - DigestInputStream digestStream = new DigestInputStream(new FileInputStream(f), + DigestInputStream digestInputStream = new DigestInputStream(new FileInputStream(f), messageDigest); - byte[] buffer = new byte[bufferSize]; - while (digestStream.read(buffer) != -1); + while (digestInputStream.read(fileBuffer) != -1); } catch (IOException e) { + e.printStackTrace(); return null; } diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java index 877ee8218ea69..fa52ac905a433 100644 --- a/services/core/java/com/android/server/BinaryTransparencyService.java +++ b/services/core/java/com/android/server/BinaryTransparencyService.java @@ -500,6 +500,7 @@ public class BinaryTransparencyService extends SystemService { // ones appearing out of the blue. Thus, we're going to only go through our cache to check // for changes, rather than freshly invoking `getInstalledPackages()` and // `getInstalledModules()` + byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer(); for (Map.Entry entry : mBinaryLastUpdateTimes.entrySet()) { String packageName = entry.getKey(); try { @@ -513,7 +514,7 @@ public class BinaryTransparencyService extends SystemService { // compute the digest for the updated package String sha256digest = PackageUtils.computeSha256DigestForLargeFile( - packageInfo.applicationInfo.sourceDir); + packageInfo.applicationInfo.sourceDir, largeFileBuffer); if (sha256digest == null) { Slog.e(TAG, "Failed to compute SHA256sum for file at " + packageInfo.applicationInfo.sourceDir); @@ -545,11 +546,13 @@ public class BinaryTransparencyService extends SystemService { // In general, we care about all APEXs, *and* all Modules, which may include some APKs. // First, we deal with all installed APEXs. + byte[] largeFileBuffer = PackageUtils.createLargeFileBuffer(); for (PackageInfo packageInfo : getInstalledApexs()) { ApplicationInfo appInfo = packageInfo.applicationInfo; // compute SHA256 for these APEXs - String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir); + String sha256digest = PackageUtils.computeSha256DigestForLargeFile(appInfo.sourceDir, + largeFileBuffer); if (sha256digest == null) { Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", packageInfo.packageName)); @@ -585,7 +588,7 @@ public class BinaryTransparencyService extends SystemService { // compute SHA256 digest for these modules String sha256digest = PackageUtils.computeSha256DigestForLargeFile( - appInfo.sourceDir); + appInfo.sourceDir, largeFileBuffer); if (sha256digest == null) { Slog.e(TAG, String.format("Failed to compute SHA256 digest for %s", packageName));