Add unit test for cow compressor performance Adding test to measure performance differences between cow compression algorithms + levels. This gives us an easy way to test performance between the tunables without having to run an OTA every time. Ultimately we want this to be separate from cow_api_test so it would be nice to have this be it's own binary. Can add some tests for decompression + compressing from a part of an actual img file too. Test: m cow_benchmark Change-Id: Iba92ae3c0b2ad4ff6f842556b701b223d7d37823
diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h index 97974c4..cf65615 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_compress.h
@@ -17,7 +17,6 @@ #pragma once #include <memory> -#include <string_view> #include "libsnapshot/cow_format.h" namespace android {
diff --git a/fs_mgr/libsnapshot/tools/Android.bp b/fs_mgr/libsnapshot/tools/Android.bp new file mode 100644 index 0000000..cfa0cef --- /dev/null +++ b/fs_mgr/libsnapshot/tools/Android.bp
@@ -0,0 +1,22 @@ + +cc_binary { + name: "cow_benchmark", + host_supported: true, + defaults: [ + "fs_mgr_defaults", + "libsnapshot_cow_defaults", + ], + + srcs: ["cow_benchmark.cpp"], + + static_libs: [ + "libsnapshot_cow", + ], + + shared_libs: [ + "libbase", + "liblog", + ], + + cflags: ["-Werror"], +}
diff --git a/fs_mgr/libsnapshot/tools/cow_benchmark.cpp b/fs_mgr/libsnapshot/tools/cow_benchmark.cpp new file mode 100644 index 0000000..da2b879 --- /dev/null +++ b/fs_mgr/libsnapshot/tools/cow_benchmark.cpp
@@ -0,0 +1,188 @@ + +#include <memory> + +#include <array> +#include <iostream> +#include <random> + +#include <libsnapshot/cow_compress.h> +#include <libsnapshot/cow_format.h> + +static const uint32_t BLOCK_SZ = 4096; +static const uint32_t SEED_NUMBER = 10; + +namespace android { +namespace snapshot { + +static std::string CompressionToString(CowCompression& compression) { + std::string output; + switch (compression.algorithm) { + case kCowCompressBrotli: + output.append("brotli"); + break; + case kCowCompressGz: + output.append("gz"); + break; + case kCowCompressLz4: + output.append("lz4"); + break; + case kCowCompressZstd: + output.append("zstd"); + break; + case kCowCompressNone: + return "No Compression"; + } + output.append(" " + std::to_string(compression.compression_level)); + return output; +} + +void OneShotCompressionTest() { + std::cout << "\n-------One Shot Compressor Perf Analysis-------\n"; + + std::vector<CowCompression> compression_list = { + {kCowCompressLz4, 0}, {kCowCompressBrotli, 1}, {kCowCompressBrotli, 3}, + {kCowCompressBrotli, 11}, {kCowCompressZstd, 3}, {kCowCompressZstd, 6}, + {kCowCompressZstd, 9}, {kCowCompressZstd, 22}, {kCowCompressGz, 1}, + {kCowCompressGz, 3}, {kCowCompressGz, 6}, {kCowCompressGz, 9}}; + std::vector<std::unique_ptr<ICompressor>> compressors; + for (auto i : compression_list) { + compressors.emplace_back(ICompressor::Create(i, BLOCK_SZ)); + } + + // Allocate a buffer of size 8 blocks. + std::array<char, 32768> buffer; + + // Generate a random 4k buffer of characters + std::default_random_engine gen(SEED_NUMBER); + std::uniform_int_distribution<int> distribution(0, 10); + for (int i = 0; i < buffer.size(); i++) { + buffer[i] = static_cast<char>(distribution(gen)); + } + + std::vector<std::pair<double, std::string>> latencies; + std::vector<std::pair<double, std::string>> ratios; + + for (size_t i = 0; i < compressors.size(); i++) { + const auto start = std::chrono::steady_clock::now(); + std::basic_string<uint8_t> compressed_data = + compressors[i]->Compress(buffer.data(), buffer.size()); + const auto end = std::chrono::steady_clock::now(); + const auto latency = + std::chrono::duration_cast<std::chrono::nanoseconds>(end - start) / 1000.0; + const double compression_ratio = + static_cast<uint16_t>(compressed_data.size()) * 1.00 / buffer.size(); + + std::cout << "Metrics for " << CompressionToString(compression_list[i]) << ": latency -> " + << latency.count() << "ms " + << " compression ratio ->" << compression_ratio << " \n"; + + latencies.emplace_back( + std::make_pair(latency.count(), CompressionToString(compression_list[i]))); + ratios.emplace_back( + std::make_pair(compression_ratio, CompressionToString(compression_list[i]))); + } + + int best_speed = 0; + int best_ratio = 0; + + for (size_t i = 1; i < latencies.size(); i++) { + if (latencies[i].first < latencies[best_speed].first) { + best_speed = i; + } + if (ratios[i].first < ratios[best_ratio].first) { + best_ratio = i; + } + } + + std::cout << "BEST SPEED: " << latencies[best_speed].first << "ms " + << latencies[best_speed].second << "\n"; + std::cout << "BEST RATIO: " << ratios[best_ratio].first << " " << ratios[best_ratio].second + << "\n"; +} + +void IncrementalCompressionTest() { + std::cout << "\n-------Incremental Compressor Perf Analysis-------\n"; + + std::vector<CowCompression> compression_list = { + {kCowCompressLz4, 0}, {kCowCompressBrotli, 1}, {kCowCompressBrotli, 3}, + {kCowCompressBrotli, 11}, {kCowCompressZstd, 3}, {kCowCompressZstd, 6}, + {kCowCompressZstd, 9}, {kCowCompressZstd, 22}, {kCowCompressGz, 1}, + {kCowCompressGz, 3}, {kCowCompressGz, 6}, {kCowCompressGz, 9}}; + std::vector<std::unique_ptr<ICompressor>> compressors; + for (auto i : compression_list) { + compressors.emplace_back(ICompressor::Create(i, BLOCK_SZ)); + } + + // Allocate a buffer of size 8 blocks. + std::array<char, 32768> buffer; + + // Generate a random 4k buffer of characters + std::default_random_engine gen(SEED_NUMBER); + std::uniform_int_distribution<int> distribution(0, 10); + for (int i = 0; i < buffer.size(); i++) { + buffer[i] = static_cast<char>(distribution(gen)); + } + + std::vector<std::pair<double, std::string>> latencies; + std::vector<std::pair<double, std::string>> ratios; + + for (size_t i = 0; i < compressors.size(); i++) { + std::vector<std::basic_string<uint8_t>> compressed_data_vec; + int num_blocks = buffer.size() / BLOCK_SZ; + const uint8_t* iter = reinterpret_cast<const uint8_t*>(buffer.data()); + + const auto start = std::chrono::steady_clock::now(); + while (num_blocks > 0) { + std::basic_string<uint8_t> compressed_data = compressors[i]->Compress(iter, BLOCK_SZ); + compressed_data_vec.emplace_back(compressed_data); + num_blocks--; + iter += BLOCK_SZ; + } + + const auto end = std::chrono::steady_clock::now(); + const auto latency = + std::chrono::duration_cast<std::chrono::nanoseconds>(end - start) / 1000.0; + + size_t size = 0; + for (auto& i : compressed_data_vec) { + size += i.size(); + } + const double compression_ratio = size * 1.00 / buffer.size(); + + std::cout << "Metrics for " << CompressionToString(compression_list[i]) << ": latency -> " + << latency.count() << "ms " + << " compression ratio ->" << compression_ratio << " \n"; + + latencies.emplace_back( + std::make_pair(latency.count(), CompressionToString(compression_list[i]))); + ratios.emplace_back( + std::make_pair(compression_ratio, CompressionToString(compression_list[i]))); + } + + int best_speed = 0; + int best_ratio = 0; + + for (size_t i = 1; i < latencies.size(); i++) { + if (latencies[i].first < latencies[best_speed].first) { + best_speed = i; + } + if (ratios[i].first < ratios[best_ratio].first) { + best_ratio = i; + } + } + + std::cout << "BEST SPEED: " << latencies[best_speed].first << "ms " + << latencies[best_speed].second << "\n"; + std::cout << "BEST RATIO: " << ratios[best_ratio].first << " " << ratios[best_ratio].second + << "\n"; +} + +} // namespace snapshot +} // namespace android + +int main() { + android::snapshot::OneShotCompressionTest(); + android::snapshot::IncrementalCompressionTest(); + + return 0; +} \ No newline at end of file