diff --git a/tests/performance-tests/CMakeLists.txt b/tests/performance-tests/CMakeLists.txt new file mode 100644 index 00000000000..e91f4a02411 --- /dev/null +++ b/tests/performance-tests/CMakeLists.txt @@ -0,0 +1,34 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# SPDX-License-Identifier: Apache-2.0. +# + +add_project(performance-tests + "A suite of AWS C++ SDK performance tests" + aws-cpp-sdk-core + aws-cpp-sdk-s3 + aws-cpp-sdk-dynamodb +) + +include(FetchContent) +FetchContent_Declare( + cxxopts + GIT_REPOSITORY https://github.com/jarro2783/cxxopts.git + GIT_TAG v3.1.1 +) +FetchContent_MakeAvailable(cxxopts) + +function(add_service_test SERVICE SDK_LIB PERF_TEST_FILE) + add_executable(${SERVICE}-performance-test + src/services/${SERVICE}/main.cpp + src/reporting/JsonReportingMetrics.cpp + src/services/${SERVICE}/${PERF_TEST_FILE} + ) + set_compiler_flags(${SERVICE}-performance-test) + set_compiler_warnings(${SERVICE}-performance-test) + target_include_directories(${SERVICE}-performance-test PRIVATE include) + target_link_libraries(${SERVICE}-performance-test PRIVATE aws-cpp-sdk-core ${SDK_LIB} cxxopts::cxxopts) + target_compile_options(${SERVICE}-performance-test PRIVATE -std=c++17 -fexceptions) +endfunction() + +add_service_test(s3 aws-cpp-sdk-s3 S3PerformanceTest.cpp) +add_service_test(dynamodb aws-cpp-sdk-dynamodb DynamoDBPerformanceTest.cpp) diff --git a/tests/performance-tests/include/performance-tests/PerformanceTestBase.h b/tests/performance-tests/include/performance-tests/PerformanceTestBase.h new file mode 100644 index 00000000000..ef4fb45dbf5 --- /dev/null +++ b/tests/performance-tests/include/performance-tests/PerformanceTestBase.h @@ -0,0 +1,33 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +namespace PerformanceTest { + +/** + * Base class for all performance tests. + */ +class PerformanceTestBase { + public: + virtual ~PerformanceTestBase() = default; + + /** + * Initialize resources for the test. + */ + virtual void Setup() = 0; + + /** + * Run the performance test operations. + */ + virtual void Run() = 0; + + /** + * Clean up resources created during setup. + */ + virtual void TearDown() = 0; +}; + +} // namespace PerformanceTest \ No newline at end of file diff --git a/tests/performance-tests/include/performance-tests/Utils.h b/tests/performance-tests/include/performance-tests/Utils.h new file mode 100644 index 00000000000..eac2f4ab7e8 --- /dev/null +++ b/tests/performance-tests/include/performance-tests/Utils.h @@ -0,0 +1,46 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include +#include +#include + +#include +#include +#include + +namespace PerformanceTest { +namespace Utils { +/** + * Generate a random string of specified length. + * @param length The desired length of the string + * @return A random string containing lowercase letters and digits + */ +static inline Aws::String RandomString(size_t length) { + auto randchar = []() -> char { + const char charset[] = + "0123456789" + "abcdefghijklmnopqrstuvwxyz"; + const size_t max_index = (sizeof(charset) - 1); + return charset[rand() % max_index]; + }; + Aws::String str(length, 0); + std::generate_n(str.begin(), length, randchar); + return str; +} + +/** + * Generate a unique identifier using UUID. + * @return A 10-character lowercase UUID substring + */ +static inline Aws::String GenerateUniqueId() { + Aws::String const rawUUID = Aws::Utils::UUID::RandomUUID(); + return Aws::Utils::StringUtils::ToLower(rawUUID.c_str()).substr(0, 10); +} + +} // namespace Utils +} // namespace PerformanceTest \ No newline at end of file diff --git a/tests/performance-tests/include/performance-tests/services/s3/S3PerformanceTest.h b/tests/performance-tests/include/performance-tests/services/s3/S3PerformanceTest.h new file mode 100644 index 00000000000..2d39f19bf19 --- /dev/null +++ b/tests/performance-tests/include/performance-tests/services/s3/S3PerformanceTest.h @@ -0,0 +1,50 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include +#include +#include +#include + +#include + +namespace PerformanceTest { +namespace Services { +namespace S3 { +/** + * Configuration for S3 performance test cases. + */ +struct TestCase { + const char* sizeLabel; + size_t sizeBytes; + const char* bucketTypeLabel; +}; + +/** + * S3 performance test implementation. + * Tests PutObject and GetObject operations with different payload sizes and bucket types. + */ +class S3PerformanceTest : public PerformanceTestBase { + public: + S3PerformanceTest(const Aws::String& region, const TestCase& config, const Aws::String& availabilityZoneId, int iterations = 3); + + void Setup() override; + void TearDown() override; + void Run() override; + + private: + const TestCase& m_config; + const Aws::String m_region; + const Aws::String m_availabilityZoneId; + const int m_iterations; + Aws::UniquePtr m_s3; + Aws::String m_bucketName; +}; + +} // namespace S3 +} // namespace Services +} // namespace PerformanceTest \ No newline at end of file diff --git a/tests/performance-tests/include/performance-tests/services/s3/S3TestConfig.h b/tests/performance-tests/include/performance-tests/services/s3/S3TestConfig.h new file mode 100644 index 00000000000..24fc8c56a3c --- /dev/null +++ b/tests/performance-tests/include/performance-tests/services/s3/S3TestConfig.h @@ -0,0 +1,30 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#pragma once + +#include + +#include +#include + +namespace PerformanceTest { +namespace Services { +namespace S3 { +namespace TestConfig { +const std::set TestOperations = {"PutObject", "GetObject"}; + +const std::array TestMatrix = {{{"8KB", 8 * 1024, "s3-standard"}, + {"64KB", 64 * 1024, "s3-standard"}, + {"1MB", 1024 * 1024, "s3-standard"}, + {"8KB", 8 * 1024, "s3-express"}, + {"64KB", 64 * 1024, "s3-express"}, + {"1MB", 1024 * 1024, "s3-express"}}}; + +const char* OutputFilename = "s3-performance-test-results.json"; +} // namespace TestConfig +} // namespace S3 +} // namespace Services +} // namespace PerformanceTest \ No newline at end of file diff --git a/tests/performance-tests/src/services/s3/S3PerformanceTest.cpp b/tests/performance-tests/src/services/s3/S3PerformanceTest.cpp new file mode 100644 index 00000000000..68a5d9b84da --- /dev/null +++ b/tests/performance-tests/src/services/s3/S3PerformanceTest.cpp @@ -0,0 +1,119 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +PerformanceTest::Services::S3::S3PerformanceTest::S3PerformanceTest(const Aws::String& region, const TestCase& config, + const Aws::String& availabilityZoneId, int iterations) + : m_config(config), m_region(region), m_availabilityZoneId(availabilityZoneId), m_iterations(iterations) {} + +void PerformanceTest::Services::S3::S3PerformanceTest::Setup() { + Aws::Client::ClientConfiguration cfg; + cfg.region = m_region; + m_s3 = Aws::MakeUnique("S3PerformanceTest", cfg); + + Aws::S3::Model::CreateBucketRequest cbr; + Aws::String const bucketId = PerformanceTest::Utils::GenerateUniqueId(); + + if (strcmp(m_config.bucketTypeLabel, "s3-express") == 0) { + m_bucketName = "perf-express-" + bucketId + "--" + m_availabilityZoneId + "--x-s3"; + cbr.SetBucket(m_bucketName); + Aws::S3::Model::CreateBucketConfiguration bucketConfig; + bucketConfig.SetLocation( + Aws::S3::Model::LocationInfo().WithType(Aws::S3::Model::LocationType::AvailabilityZone).WithName(m_availabilityZoneId)); + + bucketConfig.SetBucket(Aws::S3::Model::BucketInfo() + .WithType(Aws::S3::Model::BucketType::Directory) + .WithDataRedundancy(Aws::S3::Model::DataRedundancy::SingleAvailabilityZone)); + + cbr.SetCreateBucketConfiguration(bucketConfig); + } else { + m_bucketName = "perf-standard-" + bucketId; + cbr.SetBucket(m_bucketName); + } + + auto createOutcome = m_s3->CreateBucket(cbr); + if (!createOutcome.IsSuccess()) { + AWS_LOG_ERROR("PerformanceTest", ("S3:CreateBucket failed: " + createOutcome.GetError().GetMessage()).c_str()); + m_bucketName.clear(); + } +} + +void PerformanceTest::Services::S3::S3PerformanceTest::Run() { + if (m_bucketName.empty()) { + AWS_LOG_ERROR("PerformanceTest", "S3:Run - Bucket setup failed, skipping test"); + return; + } + + const auto randomPayload = PerformanceTest::Utils::RandomString(m_config.sizeBytes); + + // Run PutObject multiple times + for (int i = 0; i < m_iterations; i++) { + auto stream = Aws::MakeShared("PerfStream"); + *stream << randomPayload; + + Aws::S3::Model::PutObjectRequest por; + por.WithBucket(m_bucketName).WithKey("test-object-" + Aws::Utils::StringUtils::to_string(i)).SetBody(stream); + por.SetAdditionalCustomHeaderValue("test-dimension-size", m_config.sizeLabel); + por.SetAdditionalCustomHeaderValue("test-dimension-bucket-type", m_config.bucketTypeLabel); + auto putOutcome = m_s3->PutObject(por); + if (!putOutcome.IsSuccess()) { + AWS_LOG_ERROR("PerformanceTest", ("S3:PutObject failed: " + putOutcome.GetError().GetMessage()).c_str()); + } + } + + // Run GetObject multiple times + for (int i = 0; i < m_iterations; i++) { + Aws::S3::Model::GetObjectRequest gor; + gor.WithBucket(m_bucketName).WithKey("test-object-" + Aws::Utils::StringUtils::to_string(i)); + gor.SetAdditionalCustomHeaderValue("test-dimension-size", m_config.sizeLabel); + gor.SetAdditionalCustomHeaderValue("test-dimension-bucket-type", m_config.bucketTypeLabel); + auto getOutcome = m_s3->GetObject(gor); + if (!getOutcome.IsSuccess()) { + AWS_LOG_ERROR("PerformanceTest", ("S3:GetObject failed: " + getOutcome.GetError().GetMessage()).c_str()); + } + } +} + +void PerformanceTest::Services::S3::S3PerformanceTest::TearDown() { + if (m_bucketName.empty()) { + AWS_LOG_ERROR("PerformanceTest", "S3:TearDown - No bucket to clean up, setup likely failed"); + return; + } + + for (int i = 0; i < m_iterations; i++) { + auto deleteObjectOutcome = m_s3->DeleteObject( + Aws::S3::Model::DeleteObjectRequest().WithBucket(m_bucketName).WithKey("test-object-" + Aws::Utils::StringUtils::to_string(i))); + if (!deleteObjectOutcome.IsSuccess()) { + AWS_LOG_ERROR("PerformanceTest", ("S3:DeleteObject failed: " + deleteObjectOutcome.GetError().GetMessage()).c_str()); + } + } + + auto deleteBucketOutcome = m_s3->DeleteBucket(Aws::S3::Model::DeleteBucketRequest().WithBucket(m_bucketName)); + if (!deleteBucketOutcome.IsSuccess()) { + AWS_LOG_ERROR("PerformanceTest", ("S3:DeleteBucket failed: " + deleteBucketOutcome.GetError().GetMessage()).c_str()); + } +} \ No newline at end of file diff --git a/tests/performance-tests/src/services/s3/main.cpp b/tests/performance-tests/src/services/s3/main.cpp new file mode 100644 index 00000000000..0be30cd4999 --- /dev/null +++ b/tests/performance-tests/src/services/s3/main.cpp @@ -0,0 +1,60 @@ +/** + * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. + * SPDX-License-Identifier: Apache-2.0. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +int main(int argc, char** argv) { + cxxopts::Options options("s3-perf-test", "S3 Performance Test"); + options.add_options()("r,region", "AWS region", cxxopts::value()->default_value("us-east-1"))( + "a,az-id", "Availability zone ID", cxxopts::value()->default_value("use1-az4"))( + "i,iterations", "Number of iterations", cxxopts::value()->default_value("10"))( + "c,commit-id", "Commit ID", cxxopts::value()->default_value("unknown")); + + auto const result = options.parse(argc, argv); + + Aws::String const region = Aws::Utils::StringUtils::to_string(result["region"].as()); + Aws::String const availabilityZoneId = Aws::Utils::StringUtils::to_string(result["az-id"].as()); + Aws::String const commitId = Aws::Utils::StringUtils::to_string(result["commit-id"].as()); + int const iterations = result["iterations"].as(); + + Aws::SDKOptions sdkOptions; + Aws::String const versionStr = Aws::Version::GetVersionString(); + + sdkOptions.monitoringOptions.customizedMonitoringFactory_create_fn = {[&]() -> Aws::UniquePtr { + Aws::Set operations; + for (const auto& operation : PerformanceTest::Services::S3::TestConfig::TestOperations) { + operations.insert(operation); + } + return Aws::MakeUnique( + "JsonReportingMetricsFactory", operations, "cpp1", versionStr, commitId, PerformanceTest::Services::S3::TestConfig::OutputFilename); + }}; + + Aws::InitAPI(sdkOptions); + + { + for (const auto& config : PerformanceTest::Services::S3::TestConfig::TestMatrix) { + auto performanceTest = Aws::MakeUnique("S3PerformanceTest", region, config, + availabilityZoneId, iterations); + performanceTest->Setup(); + performanceTest->Run(); + performanceTest->TearDown(); + } + } + + Aws::ShutdownAPI(sdkOptions); + return 0; +} \ No newline at end of file