blob: fa2eb19c4d1221c7f3eeda13aeb54028f1eee6ad [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <benchmark/benchmark.h>
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <initializer_list>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <fmt/core.h>
#include "filesystem/GlutenS3FileSystem.h"
#include "velox/common/config/Config.h"
#include "velox/common/file/File.h"
#include "velox/common/memory/Memory.h"
#include "velox/connectors/hive/storage_adapters/s3fs/S3Config.h"
#include "velox/connectors/hive/storage_adapters/s3fs/S3FileSystem.h"
namespace gluten {
namespace {
namespace velox = facebook::velox;
namespace filesystems = facebook::velox::filesystems;
namespace memory = facebook::velox::memory;
constexpr std::string_view kDefaultLogLocation{"/tmp/gluten-s3-benchmark"};
constexpr std::string_view kDefaultMinPartSize{"5MB"};
std::optional<std::string> getEnv(std::string_view name) {
const auto* value = std::getenv(std::string(name).c_str());
if (value == nullptr || value[0] == '\0') {
return std::nullopt;
}
return value;
}
std::string getEnvOrDefault(std::string_view name, std::string_view fallback) {
return getEnv(name).value_or(std::string(fallback));
}
std::optional<std::string> firstEnv(std::initializer_list<std::string_view> names) {
for (const auto name : names) {
auto value = getEnv(name);
if (value.has_value()) {
return value;
}
}
return std::nullopt;
}
bool startsWith(std::string_view value, std::string_view prefix) {
return value.substr(0, prefix.size()) == prefix;
}
bool contains(std::string_view value, std::string_view pattern) {
return value.find(pattern) != std::string_view::npos;
}
std::string boolString(bool value) {
return value ? "true" : "false";
}
bool defaultPathStyleAccess(const std::optional<std::string>& endpoint) {
if (!endpoint.has_value()) {
return false;
}
return contains(endpoint.value(), "localhost") || contains(endpoint.value(), "127.0.0.1") ||
!contains(endpoint.value(), "amazonaws.com");
}
std::optional<std::string> inferAwsRegion(const std::optional<std::string>& endpoint) {
if (!endpoint.has_value()) {
return std::nullopt;
}
const auto& value = endpoint.value();
const auto s3Prefix = value.find("s3.");
const auto awsSuffix = value.find(".amazonaws.com");
if (s3Prefix == std::string::npos || awsSuffix == std::string::npos || awsSuffix <= s3Prefix + 3) {
return std::nullopt;
}
return value.substr(s3Prefix + 3, awsSuffix - s3Prefix - 3);
}
std::string s3ConfigKey(filesystems::S3Config::Keys key) {
return filesystems::S3Config::baseConfigKey(key);
}
class S3AsyncUploadBenchmark {
public:
S3AsyncUploadBenchmark() {
bucket_ = firstEnv({"GLUTEN_S3_BENCH_BUCKET", "S3_BUCKET"}).value_or("");
VELOX_USER_CHECK(!bucket_.empty(), "Set GLUTEN_S3_BENCH_BUCKET or S3_BUCKET before running this benchmark.");
objectPrefix_ = getEnvOrDefault("GLUTEN_S3_BENCH_PREFIX", "gluten-s3-benchmark");
minPartSize_ = getEnvOrDefault("GLUTEN_S3_BENCH_MIN_PART_SIZE", kDefaultMinPartSize);
maxConcurrentUploadNum_ = getEnvOrDefault("GLUTEN_S3_BENCH_MAX_CONCURRENCY", "4");
uploadThreads_ = getEnvOrDefault("GLUTEN_S3_BENCH_UPLOAD_THREADS", "16");
runId_ = fmt::format("{}", std::chrono::steady_clock::now().time_since_epoch().count());
filesystems::initializeS3(
getEnvOrDefault("GLUTEN_S3_BENCH_LOG_LEVEL", "FATAL"),
getEnv("GLUTEN_S3_BENCH_LOG_LOCATION").value_or(std::string(kDefaultLogLocation)));
}
~S3AsyncUploadBenchmark() {
filesystems::finalizeS3();
}
void run(::benchmark::State& state, std::string_view name, bool enableUploadPartAsync, int32_t sizeMiB) {
state.PauseTiming();
const auto sequence = sequence_.fetch_add(1);
const auto key = fmt::format("{}/{}_{}MiB_{}_{}.bin", objectPrefix_, name, sizeMiB, runId_, sequence);
const auto s3File = fmt::format("s3://{}/{}", bucket_, key);
auto config = createConfig(enableUploadPartAsync);
GlutenS3FileSystem s3fs(bucket_, config);
std::string data(1024 * 1024, 'a');
state.ResumeTiming();
auto pool = memory::memoryManager()->addLeafPool(fmt::format("S3AsyncUploadBenchmark-{}", sequence));
filesystems::FileOptions options;
options.pool = pool.get();
auto writeFile = s3fs.openFileForWrite(s3File, options);
for (int32_t i = 0; i < sizeMiB; ++i) {
writeFile->append(data);
}
writeFile->close();
::benchmark::DoNotOptimize(writeFile->size());
VELOX_CHECK_EQ(writeFile->size(), static_cast<uint64_t>(sizeMiB) * 1024 * 1024);
}
private:
std::shared_ptr<const velox::config::ConfigBase> createConfig(bool enableUploadPartAsync) const {
std::unordered_map<std::string, std::string> values;
const auto endpoint = firstEnv({"GLUTEN_S3_BENCH_ENDPOINT", "AWS_ENDPOINT"});
if (endpoint.has_value()) {
values[s3ConfigKey(filesystems::S3Config::Keys::kEndpoint)] = endpoint.value();
}
if (auto accessKey = firstEnv({"GLUTEN_S3_BENCH_ACCESS_KEY", "AWS_ACCESS_KEY_ID"})) {
values[s3ConfigKey(filesystems::S3Config::Keys::kAccessKey)] = accessKey.value();
}
if (auto secretKey = firstEnv({"GLUTEN_S3_BENCH_SECRET_KEY", "AWS_SECRET_ACCESS_KEY"})) {
values[s3ConfigKey(filesystems::S3Config::Keys::kSecretKey)] = secretKey.value();
}
auto region = firstEnv({"GLUTEN_S3_BENCH_REGION", "AWS_REGION", "AWS_DEFAULT_REGION"});
if (!region.has_value()) {
region = inferAwsRegion(endpoint);
}
if (region.has_value()) {
values[s3ConfigKey(filesystems::S3Config::Keys::kEndpointRegion)] = region.value();
}
values[s3ConfigKey(filesystems::S3Config::Keys::kPathStyleAccess)] =
getEnv("GLUTEN_S3_BENCH_PATH_STYLE_ACCESS").value_or(boolString(defaultPathStyleAccess(endpoint)));
values[s3ConfigKey(filesystems::S3Config::Keys::kSSLEnabled)] =
getEnv("GLUTEN_S3_BENCH_SSL_ENABLED")
.value_or(boolString(!endpoint.has_value() || !startsWith(endpoint.value(), "http://")));
values[s3ConfigKey(filesystems::S3Config::Keys::kMultipartMinPartSize)] = minPartSize_;
values["hive.s3.part-upload-async"] = enableUploadPartAsync ? "true" : "false";
values["hive.s3.max-concurrent-upload-num"] = maxConcurrentUploadNum_;
values["hive.s3.upload-threads"] = uploadThreads_;
return std::make_shared<velox::config::ConfigBase>(std::move(values));
}
std::string bucket_;
std::string objectPrefix_;
std::string minPartSize_;
std::string maxConcurrentUploadNum_;
std::string uploadThreads_;
std::string runId_;
std::atomic<uint64_t> sequence_{0};
};
std::unique_ptr<S3AsyncUploadBenchmark> s3Benchmark;
void runS3AsyncUploadBenchmark(::benchmark::State& state, bool enableUploadPartAsync, int32_t sizeMiB) {
const auto name = enableUploadPartAsync ? "async_upload" : "sync_upload";
for (auto _ : state) {
s3Benchmark->run(state, name, enableUploadPartAsync, sizeMiB);
}
state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * sizeMiB * 1024 * 1024);
}
#define REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(size) \
BENCHMARK_CAPTURE(runS3AsyncUploadBenchmark, sync_upload_##size##M, false, size)->Unit(::benchmark::kMillisecond); \
BENCHMARK_CAPTURE(runS3AsyncUploadBenchmark, async_upload_##size##M, true, size)->Unit(::benchmark::kMillisecond)
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(4);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(8);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(16);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(32);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(64);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(128);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(256);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(512);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(1024);
REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS(2048);
#undef REGISTER_S3_ASYNC_UPLOAD_BENCHMARKS
} // namespace
void initializeS3AsyncUploadBenchmark() {
s3Benchmark = std::make_unique<S3AsyncUploadBenchmark>();
}
void finalizeS3AsyncUploadBenchmark() {
s3Benchmark.reset();
}
} // namespace gluten
int main(int argc, char** argv) {
facebook::velox::memory::MemoryManager::initialize(facebook::velox::memory::MemoryManager::Options{});
gluten::initializeS3AsyncUploadBenchmark();
::benchmark::Initialize(&argc, argv);
::benchmark::RunSpecifiedBenchmarks();
::benchmark::Shutdown();
gluten::finalizeS3AsyncUploadBenchmark();
return 0;
}