yams/tests/benchmarks/ipc_streaming_bench.cpp
2025-10-07 11:18:35 -04:00

267 lines
9.5 KiB
C++

#include "../common/benchmark_tracker.h"
#include "benchmark_base.h"
#include <chrono>
#include <filesystem>
#include <numeric>
#include <random>
#include <string>
#include <string_view>
#include <vector>
#include <yams/daemon/ipc/ipc_protocol.h>
#include <yams/daemon/ipc/message_framing.h>
namespace yams::benchmark {
namespace {
using yams::daemon::MessageFramer;
using yams::test::BenchmarkTracker;
std::string makeSnippet(size_t length, char seed) {
std::string out(length, seed);
for (size_t i = 0; i < length; ++i) {
out[i] = static_cast<char>(seed + static_cast<int>(i % 23));
}
return out;
}
daemon::SearchResult makeResult(size_t index, size_t snippetBytes) {
daemon::SearchResult result;
result.id = "doc-" + std::to_string(index);
result.path = "/bench/doc-" + std::to_string(index) + ".txt";
result.title = "Benchmark Doc " + std::to_string(index);
result.snippet = makeSnippet(snippetBytes, static_cast<char>('a' + (index % 13)));
result.score = 1.0 / static_cast<double>(index + 1);
result.metadata = {{"generator", "ipc_stream"}, {"bucket", std::to_string(index % 5)}};
return result;
}
daemon::Message makeMessage(uint64_t requestId, const daemon::Response& response) {
daemon::Message msg;
msg.version = daemon::PROTOCOL_VERSION;
msg.requestId = requestId;
msg.timestamp = std::chrono::steady_clock::now();
msg.expectsStreamingResponse = true;
msg.payload = response;
return msg;
}
} // namespace
class StreamingFramerBenchmark : public BenchmarkBase {
public:
StreamingFramerBenchmark(std::string name, size_t resultsPerChunk, size_t chunkCount,
size_t snippetBytes, const Config& config = Config())
: BenchmarkBase(std::move(name), config), resultsPerChunk_(resultsPerChunk),
chunkCount_(chunkCount), snippetBytes_(snippetBytes) {
buildPayloads();
}
protected:
size_t runIteration() override {
const uint64_t requestId = requestCounter_++;
std::size_t operations = 0;
// Header frame
frameBuffer_.clear();
frameBuffer_.reserve(reserveBytes_);
auto headerMessage = makeMessage(requestId, headerResponse_);
auto headerResult = framer_.frame_message_header_into(headerMessage, frameBuffer_);
if (!headerResult) {
throw std::runtime_error("Failed to frame streaming header: " +
headerResult.error().message);
}
bytesAccumulated_ += frameBuffer_.size();
operations++;
// Chunk frames
for (size_t i = 0; i < chunkResponses_.size(); ++i) {
frameBuffer_.clear();
auto chunkMessage = makeMessage(requestId, chunkResponses_[i]);
bool last = (i + 1 == chunkResponses_.size());
auto chunkResult = framer_.frame_message_chunk_into(chunkMessage, frameBuffer_, last);
if (!chunkResult) {
throw std::runtime_error("Failed to frame streaming chunk: " +
chunkResult.error().message);
}
bytesAccumulated_ += frameBuffer_.size();
operations++;
}
iterations_++;
return operations;
}
void collectCustomMetrics(std::map<std::string, double>& metrics) override {
if (iterations_ == 0)
return;
metrics["bytes_per_iteration"] =
static_cast<double>(bytesAccumulated_) / static_cast<double>(iterations_);
metrics["chunks_per_iteration"] = static_cast<double>(chunkCount_);
metrics["results_per_chunk"] = static_cast<double>(resultsPerChunk_);
}
private:
void buildPayloads() {
const size_t totalResults = resultsPerChunk_ * chunkCount_;
daemon::SearchResponse headerPayload;
headerPayload.totalCount = totalResults;
headerPayload.elapsed = std::chrono::milliseconds(12);
headerResponse_ =
daemon::Response{std::in_place_type<daemon::SearchResponse>, headerPayload};
chunkResponses_.clear();
chunkResponses_.reserve(chunkCount_);
size_t resultIndex = 0;
for (size_t chunk = 0; chunk < chunkCount_; ++chunk) {
daemon::SearchResponse chunkPayload;
chunkPayload.totalCount = totalResults;
chunkPayload.elapsed = std::chrono::milliseconds(20 + static_cast<int>(chunk));
chunkPayload.results.reserve(resultsPerChunk_);
for (size_t i = 0; i < resultsPerChunk_; ++i) {
chunkPayload.results.push_back(makeResult(resultIndex++, snippetBytes_));
}
daemon::Response chunkResp{std::in_place_type<daemon::SearchResponse>, chunkPayload};
chunkResponses_.push_back(std::move(chunkResp));
}
reserveBytes_ = MessageFramer::HEADER_SIZE + resultsPerChunk_ * (snippetBytes_ + 256);
frameBuffer_.reserve(reserveBytes_);
}
MessageFramer framer_;
std::vector<uint8_t> frameBuffer_;
daemon::Response headerResponse_;
std::vector<daemon::Response> chunkResponses_;
size_t resultsPerChunk_;
size_t chunkCount_;
size_t snippetBytes_;
size_t reserveBytes_ = MessageFramer::HEADER_SIZE * 2;
size_t iterations_ = 0;
uint64_t requestCounter_ = 1;
size_t bytesAccumulated_ = 0;
};
class UnaryFramerBenchmark : public BenchmarkBase {
public:
explicit UnaryFramerBenchmark(std::string name, size_t payloadBytes,
const Config& config = Config())
: BenchmarkBase(std::move(name), config), payloadBytes_(payloadBytes) {
buildPayload();
}
protected:
size_t runIteration() override {
frameBuffer_.clear();
frameBuffer_.reserve(reserveBytes_);
daemon::Message msg = makeMessage(requestId_++, response_);
auto result = framer_.frame_message_into(msg, frameBuffer_);
if (!result) {
throw std::runtime_error("Failed to frame unary message: " + result.error().message);
}
bytesAccumulated_ += frameBuffer_.size();
iterations_++;
return 1;
}
void collectCustomMetrics(std::map<std::string, double>& metrics) override {
if (iterations_ == 0)
return;
metrics["bytes_per_frame"] =
static_cast<double>(bytesAccumulated_) / static_cast<double>(iterations_);
}
private:
void buildPayload() {
daemon::SuccessResponse success;
success.message = std::string(payloadBytes_, 's');
response_ = daemon::Response{std::in_place_type<daemon::SuccessResponse>, success};
reserveBytes_ = MessageFramer::HEADER_SIZE + payloadBytes_ + 64;
frameBuffer_.reserve(reserveBytes_);
}
MessageFramer framer_;
std::vector<uint8_t> frameBuffer_;
daemon::Response response_;
size_t payloadBytes_;
size_t reserveBytes_ = MessageFramer::HEADER_SIZE * 2;
uint64_t requestId_ = 1000;
size_t iterations_ = 0;
size_t bytesAccumulated_ = 0;
};
} // namespace yams::benchmark
using yams::benchmark::BenchmarkBase;
using yams::benchmark::StreamingFramerBenchmark;
using yams::benchmark::UnaryFramerBenchmark;
using yams::test::BenchmarkTracker;
int main(int argc, char** argv) {
BenchmarkBase::Config config;
config.verbose = true;
config.benchmark_iterations = 8;
config.track_memory = false; // /proc/self/status not available on all platforms
for (int i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (arg == "--quiet") {
config.verbose = false;
} else if (arg == "--iterations" && i + 1 < argc) {
config.benchmark_iterations = std::stoi(argv[++i]);
} else if (arg == "--output" && i + 1 < argc) {
config.output_file = argv[++i];
}
}
std::cout << "YAMS IPC Streaming Benchmarks\n";
std::cout << "====================================\n\n";
std::filesystem::path outDir = "bench_results";
std::error_code ec;
std::filesystem::create_directories(outDir, ec);
if (ec) {
std::cerr << "WARNING: unable to create bench_results directory: " << ec.message()
<< std::endl;
}
if (config.output_file.empty()) {
config.output_file = (outDir / "ipc_stream_benchmarks.json").string();
}
BenchmarkTracker tracker(outDir / "ipc_stream_benchmarks.json");
std::vector<std::unique_ptr<BenchmarkBase>> benchmarks;
benchmarks.push_back(std::make_unique<StreamingFramerBenchmark>("StreamingFramer_32x10_256B",
32, 10, 256, config));
benchmarks.push_back(std::make_unique<StreamingFramerBenchmark>("StreamingFramer_64x6_512B", 64,
6, 512, config));
benchmarks.push_back(
std::make_unique<UnaryFramerBenchmark>("UnaryFramer_Success_8KB", 8 * 1024, config));
for (auto& bench : benchmarks) {
auto result = bench->run();
BenchmarkTracker::BenchmarkResult trackerResult;
trackerResult.name = result.name;
trackerResult.value = result.duration_ms;
trackerResult.unit = "ms";
trackerResult.timestamp = std::chrono::system_clock::now();
trackerResult.metrics = result.custom_metrics;
tracker.recordResult(trackerResult);
}
tracker.generateReport(outDir / "ipc_stream_benchmark_report.json");
tracker.generateMarkdownReport(outDir / "ipc_stream_benchmark_report.md");
std::cout << "\n====================================\n";
std::cout << "Benchmark complete. Reports generated.\n";
return 0;
}