Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 59fa4867 authored by Akilesh Kailash's avatar Akilesh Kailash
Browse files

libsnapshot_cow: Support multi-block compression



This patch supports compression for bigger block size.

3 bits [57-59] in the COW Operation "source_info_" field is used to store
the compression factor. Supported compression factors are power of 2
viz: 4k, 8k, 16k, 32k, 64k, 128k, 256k.

Only REPLACE operations will have the bigger block size support for now.
This can be extended to other operations later.

The write path in EmitBlocks() has the core logic wherein consecutive
sequence of REPLACE ops are compressed based on the compression factor
settings. Thus, for a 64k compression factor, there will be just one
COW operation which encodes all the 16 operation and the entire 64k
block is compressed in one shot.

NOTE: There is no read I/O path support in this patch. Subsequent patch
will have the read support.

Performance data (with read I/O path support in subsequent patch):

go/variable-block-vabc-perf covers detail performance runs
on Pixel 6 for full and incremental OTA.

TL;DR:

Performance of a full OTA (All numbers are compared against 4k block
size)
=======================================
Snapshot-size:

~10-11% decrease in snapshot-size (disk-space) for zstd with 256k block
size.

~8% decrease in snapshot-size (disk-space) for lz4

Install time:

~13% decrease in OTA install time for zstd with 256k block size.

Snapshot-merge:

~50% decrease in snapshot-merge time with 256k block size for zstd

Post OTA boot-time:

~10.5 decrease in boot time for 64k block size for zstd

In-memory footprint for COW operations:

~80% decrease in memory footprint for 256k block size. (58MB -> 9.2MB)

============================================

For more improvements, further tuning of zstd/lz4 is
required primariy the compression levels, zstd compression window,
performance of gz with compression levels.

Bug: 319309466

Test: cow_api test covering all the supported block sizes for v3 writer.

On Pixel 6:

=======================================
COW Writer V3:

for OTA in full, incremental OTA
   for block_size in 4k, 16k, 32k, 64k, 128k, 256k
       for compression_algo in lz4, zstd, gz, none
          install OTA, reboot, verify merge
=======================================
COW Writer V2:

for OTA in full, incremental OTA
   for block_size in 4k
      for compression_algo in lz4, zstd, gz, none
          install OTA, reboot, verity merge

=====================================
Change-Id: I96201f1609582aa9d44d8085852e284b0c4a426d
Signed-off-by: default avatarAkilesh Kailash <akailash@google.com>
parent 13cb6f7b
Loading
Loading
Loading
Loading
+34 −12
Original line number Diff line number Diff line
@@ -108,7 +108,7 @@ cc_library_static {
    ],
    srcs: [":libsnapshot_sources"],
    static_libs: [
        "libfs_mgr_binder"
        "libfs_mgr_binder",
    ],
}

@@ -128,7 +128,7 @@ cc_library {
    static_libs: [
        "libc++fs",
        "libsnapshot_cow",
    ]
    ],
}

cc_library_static {
@@ -204,6 +204,10 @@ cc_library_static {
        "libsnapshot_cow/writer_v2.cpp",
        "libsnapshot_cow/writer_v3.cpp",
    ],

    header_libs: [
        "libstorage_literals_headers",
    ],
    export_include_dirs: ["include"],
    host_supported: true,
    recovery_available: true,
@@ -243,7 +247,10 @@ cc_library_static {

cc_defaults {
    name: "libsnapshot_test_defaults",
    defaults: ["libsnapshot_defaults", "libsnapshot_cow_defaults"],
    defaults: [
        "libsnapshot_defaults",
        "libsnapshot_cow_defaults",
    ],
    srcs: [
        "partition_cow_creator_test.cpp",
        "snapshot_metadata_updater_test.cpp",
@@ -283,10 +290,13 @@ cc_defaults {

cc_test {
    name: "vts_libsnapshot_test",
    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
    defaults: [
        "libsnapshot_test_defaults",
        "libsnapshot_hal_deps",
    ],
    test_suites: [
        "vts",
        "device-tests"
        "device-tests",
    ],
    test_options: {
        min_shipping_api_level: 30,
@@ -295,12 +305,15 @@ cc_test {

cc_test {
    name: "vab_legacy_tests",
    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
    defaults: [
        "libsnapshot_test_defaults",
        "libsnapshot_hal_deps",
    ],
    cppflags: [
        "-DLIBSNAPSHOT_TEST_VAB_LEGACY",
    ],
    test_suites: [
        "device-tests"
        "device-tests",
    ],
    test_options: {
        // Legacy VAB launched in Android R.
@@ -310,12 +323,15 @@ cc_test {

cc_test {
    name: "vabc_legacy_tests",
    defaults: ["libsnapshot_test_defaults", "libsnapshot_hal_deps"],
    defaults: [
        "libsnapshot_test_defaults",
        "libsnapshot_hal_deps",
    ],
    cppflags: [
        "-DLIBSNAPSHOT_TEST_VABC_LEGACY",
    ],
    test_suites: [
        "device-tests"
        "device-tests",
    ],
    test_options: {
        // Legacy VABC launched in Android S.
@@ -343,7 +359,10 @@ cc_test {

cc_binary {
    name: "snapshotctl",
    defaults: ["libsnapshot_cow_defaults", "libsnapshot_hal_deps"],
    defaults: [
        "libsnapshot_cow_defaults",
        "libsnapshot_hal_deps",
    ],
    srcs: [
        "snapshotctl.cpp",
    ],
@@ -412,8 +431,11 @@ cc_test {
        "libgtest",
        "libsnapshot_cow",
    ],
    header_libs: [
        "libstorage_literals_headers",
    ],
    test_suites: [
        "device-tests"
        "device-tests",
    ],
    test_options: {
        min_shipping_api_level: 30,
+35 −2
Original line number Diff line number Diff line
@@ -201,6 +201,12 @@ static constexpr uint64_t kCowOpSourceInfoDataMask = (1ULL << 48) - 1;
static constexpr uint64_t kCowOpSourceInfoTypeBit = 60;
static constexpr uint64_t kCowOpSourceInfoTypeNumBits = 4;
static constexpr uint64_t kCowOpSourceInfoTypeMask = (1ULL << kCowOpSourceInfoTypeNumBits) - 1;

static constexpr uint64_t kCowOpSourceInfoCompressionBit = 57;
static constexpr uint64_t kCowOpSourceInfoCompressionNumBits = 3;
static constexpr uint64_t kCowOpSourceInfoCompressionMask =
        ((1ULL << kCowOpSourceInfoCompressionNumBits) - 1);

// The on disk format of cow (currently ==  CowOperation)
struct CowOperationV3 {
    // If this operation reads from the data section of the COW, this contains
@@ -211,8 +217,8 @@ struct CowOperationV3 {
    uint32_t new_block;

    // source_info with have the following layout
    // |---4 bits ---| ---12 bits---| --- 48 bits ---|
    // |--- type --- | -- unused -- | --- source --- |
    // |--- 4 bits -- | --------- 3 bits ------ | --- 9 bits --- | --- 48 bits ---|
    // |--- type ---  | -- compression factor --| --- unused --- | --- source --- |
    //
    // The value of |source| depends on the operation code.
    //
@@ -225,6 +231,17 @@ struct CowOperationV3 {
    // For ops other than Label:
    //  Bits 47-62 are reserved and must be zero.
    // A block is compressed if it’s data is < block_sz
    //
    // Bits [57-59] represents the compression factor.
    //
    //       Compression - factor
    // ==========================
    // 000 -  4k
    // 001 -  8k
    // 010 -  16k
    // ...
    // 110 -  256k
    //
    uint64_t source_info_;
    constexpr uint64_t source() const { return source_info_ & kCowOpSourceInfoDataMask; }
    constexpr void set_source(uint64_t source) {
@@ -245,6 +262,20 @@ struct CowOperationV3 {
        source_info_ |= (static_cast<uint64_t>(type) & kCowOpSourceInfoTypeMask)
                        << kCowOpSourceInfoTypeBit;
    }
    constexpr void set_compression_bits(uint8_t compression_factor) {
        // Clear the 3 bits from bit 57 - [57-59]
        source_info_ &= ~(kCowOpSourceInfoCompressionMask << kCowOpSourceInfoCompressionBit);
        // Set the actual compression factor
        source_info_ |=
                (static_cast<uint64_t>(compression_factor) & kCowOpSourceInfoCompressionMask)
                << kCowOpSourceInfoCompressionBit;
    }
    constexpr uint8_t compression_bits() const {
        // Grab the 3 bits from [57-59]
        const auto compression_factor =
                (source_info_ >> kCowOpSourceInfoCompressionBit) & kCowOpSourceInfoCompressionMask;
        return static_cast<uint8_t>(compression_factor);
    }
} __attribute__((packed));

// Ensure that getters/setters added to CowOperationV3 does not increases size
@@ -326,5 +357,7 @@ bool IsOrderedOp(const CowOperation& op);
// Convert compression name to internal value.
std::optional<CowCompressionAlgorithm> CompressionAlgorithmFromString(std::string_view name);

// Return block size used for compression
size_t CowOpCompressionSize(const CowOperation* op, size_t block_size);
}  // namespace snapshot
}  // namespace android
+6 −4
Original line number Diff line number Diff line
@@ -119,9 +119,9 @@ class ICowWriter {

class CompressWorker {
  public:
    CompressWorker(std::unique_ptr<ICompressor>&& compressor, uint32_t block_size);
    CompressWorker(std::unique_ptr<ICompressor>&& compressor);
    bool RunThread();
    void EnqueueCompressBlocks(const void* buffer, size_t num_blocks);
    void EnqueueCompressBlocks(const void* buffer, size_t block_size, size_t num_blocks);
    bool GetCompressedBuffers(std::vector<std::basic_string<uint8_t>>* compressed_buf);
    void Finalize();
    static uint32_t GetDefaultCompressionLevel(CowCompressionAlgorithm compression);
@@ -134,20 +134,22 @@ class CompressWorker {
    struct CompressWork {
        const void* buffer;
        size_t num_blocks;
        size_t block_size;
        bool compression_status = false;
        std::vector<std::basic_string<uint8_t>> compressed_data;
    };

    std::unique_ptr<ICompressor> compressor_;
    uint32_t block_size_;

    std::queue<CompressWork> work_queue_;
    std::queue<CompressWork> compressed_queue_;
    std::mutex lock_;
    std::condition_variable cv_;
    bool stopped_ = false;
    size_t total_submitted_ = 0;
    size_t total_processed_ = 0;

    bool CompressBlocks(const void* buffer, size_t num_blocks,
    bool CompressBlocks(const void* buffer, size_t num_blocks, size_t block_size,
                        std::vector<std::basic_string<uint8_t>>* compressed_data);
};

+18 −18
Original line number Diff line number Diff line
@@ -208,9 +208,9 @@ class ZstdCompressor final : public ICompressor {
    std::unique_ptr<ZSTD_CCtx, decltype(&ZSTD_freeCCtx)> zstd_context_;
};

bool CompressWorker::CompressBlocks(const void* buffer, size_t num_blocks,
bool CompressWorker::CompressBlocks(const void* buffer, size_t num_blocks, size_t block_size,
                                    std::vector<std::basic_string<uint8_t>>* compressed_data) {
    return CompressBlocks(compressor_.get(), block_size_, buffer, num_blocks, compressed_data);
    return CompressBlocks(compressor_.get(), block_size, buffer, num_blocks, compressed_data);
}

bool CompressWorker::CompressBlocks(ICompressor* compressor, size_t block_size, const void* buffer,
@@ -254,7 +254,8 @@ bool CompressWorker::RunThread() {
        }

        // Compress blocks
        bool ret = CompressBlocks(blocks.buffer, blocks.num_blocks, &blocks.compressed_data);
        bool ret = CompressBlocks(blocks.buffer, blocks.num_blocks, blocks.block_size,
                                  &blocks.compressed_data);
        blocks.compression_status = ret;
        {
            std::lock_guard<std::mutex> lock(lock_);
@@ -273,35 +274,31 @@ bool CompressWorker::RunThread() {
    return true;
}

void CompressWorker::EnqueueCompressBlocks(const void* buffer, size_t num_blocks) {
void CompressWorker::EnqueueCompressBlocks(const void* buffer, size_t block_size,
                                           size_t num_blocks) {
    {
        std::lock_guard<std::mutex> lock(lock_);

        CompressWork blocks = {};
        blocks.buffer = buffer;
        blocks.block_size = block_size;
        blocks.num_blocks = num_blocks;
        work_queue_.push(std::move(blocks));
        total_submitted_ += 1;
    }
    cv_.notify_all();
}

bool CompressWorker::GetCompressedBuffers(std::vector<std::basic_string<uint8_t>>* compressed_buf) {
    {
    while (true) {
        std::unique_lock<std::mutex> lock(lock_);
        while (compressed_queue_.empty() && !stopped_) {
        while ((total_submitted_ != total_processed_) && compressed_queue_.empty() && !stopped_) {
            cv_.wait(lock);
        }

        if (stopped_) {
            return true;
        }
    }

    {
        std::lock_guard<std::mutex> lock(lock_);
        while (compressed_queue_.size() > 0) {
            CompressWork blocks = std::move(compressed_queue_.front());
            compressed_queue_.pop();
            total_processed_ += 1;

            if (blocks.compression_status) {
                compressed_buf->insert(compressed_buf->end(),
@@ -312,10 +309,13 @@ bool CompressWorker::GetCompressedBuffers(std::vector<std::basic_string<uint8_t>
                return false;
            }
        }
    }

        if ((total_submitted_ == total_processed_) || stopped_) {
            total_submitted_ = 0;
            total_processed_ = 0;
            return true;
        }
    }
}

std::unique_ptr<ICompressor> ICompressor::Brotli(uint32_t compression_level,
                                                 const int32_t block_size) {
@@ -344,8 +344,8 @@ void CompressWorker::Finalize() {
    cv_.notify_all();
}

CompressWorker::CompressWorker(std::unique_ptr<ICompressor>&& compressor, uint32_t block_size)
    : compressor_(std::move(compressor)), block_size_(block_size) {}
CompressWorker::CompressWorker(std::unique_ptr<ICompressor>&& compressor)
    : compressor_(std::move(compressor)) {}

}  // namespace snapshot
}  // namespace android
+7 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <libsnapshot/cow_format.h>
#include <storage_literals/storage_literals.h>
#include "writer_v2.h"
#include "writer_v3.h"

@@ -28,6 +29,7 @@ namespace android {
namespace snapshot {

using android::base::unique_fd;
using namespace android::storage_literals;

std::ostream& EmitCowTypeString(std::ostream& os, CowOperationType cow_type) {
    switch (cow_type) {
@@ -174,5 +176,10 @@ std::unique_ptr<ICowWriter> CreateCowEstimator(uint32_t version, const CowOption
    return CreateCowWriter(version, options, unique_fd{-1}, std::nullopt);
}

size_t CowOpCompressionSize(const CowOperation* op, size_t block_size) {
    uint8_t compression_bits = op->compression_bits();
    return (block_size << compression_bits);
}

}  // namespace snapshot
}  // namespace android
Loading