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