blob: 1d269a233dac845d70cb968cb8f274a6c32d07b9 [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <vector>
#include "blobfs-compression.h"
#include "src/lib/chunked-compression/chunked-compressor.h"
#include "src/lib/chunked-compression/status.h"
namespace blobfs_compress {
namespace {
using ::chunked_compression::ChunkedCompressor;
using ::chunked_compression::CompressionParams;
using ::chunked_compression::ToZxStatus;
constexpr const char kAnsiUpLine[] = "\33[A";
constexpr const char kAnsiClearLine[] = "\33[2K\r";
} // namespace
// ProgressWriter writes live a progress indicator to stdout. Updates are written in-place
// (using ANSI control codes to rewrite the current line).
class ProgressWriter {
public:
explicit ProgressWriter(int refresh_hz = 60) : refresh_hz_(refresh_hz) {
last_report_ = std::chrono::steady_clock::time_point::min();
printf("\n");
}
void Update(const char* fmt, ...) {
auto now = std::chrono::steady_clock::now();
if (now < last_report_ + refresh_duration()) {
return;
}
last_report_ = now;
printf("%s%s", kAnsiUpLine, kAnsiClearLine);
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
fflush(stdout);
}
void Final(const char* fmt, ...) {
printf("%s%s", kAnsiUpLine, kAnsiClearLine);
va_list args;
va_start(args, fmt);
vprintf(fmt, args);
va_end(args);
fflush(stdout);
}
std::chrono::steady_clock::duration refresh_duration() const {
return std::chrono::seconds(1) / refresh_hz_;
}
private:
std::chrono::steady_clock::time_point last_report_;
int refresh_hz_;
};
// Returns 0 if the compression runs successfully; otherwise non-zero values.
// This method reads |src_sz| from |src|, compresses it using the compression
// |params|, and then writes the compressed bytes to |dest_write_buf| and the
// compressed size to |out_compressed_size|.
//
// |dest_write_buf| can be nullptr if wanting the final compressed size only.
// However, even if |dest_write_buf| is set to nullptr, there will still be
// temporary RAM consumption for storing compressed data due to current internal
// compression API design.
zx_status_t BlobfsCompress(const uint8_t* src,
const size_t src_sz,
uint8_t* dest_write_buf,
size_t* out_compressed_size,
CompressionParams params) {
ChunkedCompressor compressor(params);
ProgressWriter progress;
compressor.SetProgressCallback([&](size_t bytes_read,
size_t bytes_total,
size_t bytes_written) {
progress.Update("%2.0f%% (%lu bytes written)\n",
static_cast<double>(bytes_read)
/ static_cast<double>(bytes_total) * 100,
bytes_written);
});
size_t compressed_size;
size_t output_limit = params.ComputeOutputSizeLimit(src_sz);
std::vector<uint8_t> output_buffer;
// The caller does not need the compressed data. However, the compressor
// still requires a write buffer to store the compressed output.
if (dest_write_buf == nullptr) {
output_buffer.resize(output_limit);
dest_write_buf = output_buffer.data();
}
const auto compression_status = compressor.Compress(src,
src_sz,
dest_write_buf,
output_limit,
&compressed_size);
if (compression_status != chunked_compression::kStatusOk) {
return ToZxStatus(compression_status);
}
double saving_ratio = static_cast<double>(src_sz - compressed_size);
if (src_sz) {
saving_ratio /= static_cast<double>(src_sz);
} else {
saving_ratio = 0;
}
progress.Final("Wrote %lu bytes (%.2f%% space saved).\n",
compressed_size,
saving_ratio * 100);
*out_compressed_size = compressed_size;
return ZX_OK;
}
} // namespace blobfs_compress