Skip to content

Add s3 service performance tests #3470

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions tests/performance-tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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
46 changes: 46 additions & 0 deletions tests/performance-tests/include/performance-tests/Utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#pragma once

#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/UUID.h>
#include <aws/core/utils/memory/stl/AWSString.h>

#include <algorithm>
#include <cstddef>
#include <cstdlib>

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#pragma once

#include <aws/core/utils/memory/AWSMemory.h>
#include <aws/core/utils/memory/stl/AWSString.h>
#include <aws/s3/S3Client.h>
#include <performance-tests/PerformanceTestBase.h>

#include <cstddef>

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<Aws::S3::S3Client> m_s3;
Aws::String m_bucketName;
};

} // namespace S3
} // namespace Services
} // namespace PerformanceTest
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#pragma once

#include <performance-tests/services/s3/S3PerformanceTest.h>

#include <array>
#include <set>

namespace PerformanceTest {
namespace Services {
namespace S3 {
namespace TestConfig {
const std::set<const char*> TestOperations = {"PutObject", "GetObject"};

const std::array<TestCase, 6> 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
119 changes: 119 additions & 0 deletions tests/performance-tests/src/services/s3/S3PerformanceTest.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/core/client/ClientConfiguration.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/logging/LogMacros.h>
#include <aws/core/utils/memory/AWSMemory.h>
#include <aws/core/utils/memory/stl/AWSAllocator.h>
#include <aws/core/utils/memory/stl/AWSString.h>
#include <aws/core/utils/memory/stl/AWSStringStream.h>
#include <aws/s3/S3Client.h>
#include <aws/s3/model/BucketInfo.h>
#include <aws/s3/model/BucketType.h>
#include <aws/s3/model/CreateBucketConfiguration.h>
#include <aws/s3/model/CreateBucketRequest.h>
#include <aws/s3/model/DataRedundancy.h>
#include <aws/s3/model/DeleteBucketRequest.h>
#include <aws/s3/model/DeleteObjectRequest.h>
#include <aws/s3/model/GetObjectRequest.h>
#include <aws/s3/model/LocationType.h>
#include <aws/s3/model/PutObjectRequest.h>
#include <performance-tests/Utils.h>
#include <performance-tests/services/s3/S3PerformanceTest.h>

#include <cstring>

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<Aws::S3::S3Client>("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<Aws::StringStream>("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());
}
}
60 changes: 60 additions & 0 deletions tests/performance-tests/src/services/s3/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

#include <aws/core/Aws.h>
#include <aws/core/Version.h>
#include <aws/core/monitoring/MonitoringFactory.h>
#include <aws/core/utils/StringUtils.h>
#include <aws/core/utils/memory/AWSMemory.h>
#include <aws/core/utils/memory/stl/AWSSet.h>
#include <aws/core/utils/memory/stl/AWSString.h>
#include <performance-tests/reporting/JsonReportingMetrics.h>
#include <performance-tests/services/s3/S3PerformanceTest.h>
#include <performance-tests/services/s3/S3TestConfig.h>

#include <cxxopts.hpp>
#include <string>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why std::string? maybe a unused import


int main(int argc, char** argv) {
cxxopts::Options options("s3-perf-test", "S3 Performance Test");
options.add_options()("r,region", "AWS region", cxxopts::value<std::string>()->default_value("us-east-1"))(
"a,az-id", "Availability zone ID", cxxopts::value<std::string>()->default_value("use1-az4"))(
"i,iterations", "Number of iterations", cxxopts::value<int>()->default_value("10"))(
"c,commit-id", "Commit ID", cxxopts::value<std::string>()->default_value("unknown"));

auto const result = options.parse(argc, argv);

Aws::String const region = Aws::Utils::StringUtils::to_string(result["region"].as<std::string>());
Aws::String const availabilityZoneId = Aws::Utils::StringUtils::to_string(result["az-id"].as<std::string>());
Aws::String const commitId = Aws::Utils::StringUtils::to_string(result["commit-id"].as<std::string>());
int const iterations = result["iterations"].as<int>();

Aws::SDKOptions sdkOptions;
Aws::String const versionStr = Aws::Version::GetVersionString();

sdkOptions.monitoringOptions.customizedMonitoringFactory_create_fn = {[&]() -> Aws::UniquePtr<Aws::Monitoring::MonitoringFactory> {
Aws::Set<Aws::String> operations;
for (const auto& operation : PerformanceTest::Services::S3::TestConfig::TestOperations) {
operations.insert(operation);
}
return Aws::MakeUnique<PerformanceTest::Reporting::JsonReportingMetricsFactory>(
"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<PerformanceTest::Services::S3::S3PerformanceTest>("S3PerformanceTest", region, config,
availabilityZoneId, iterations);
performanceTest->Setup();
performanceTest->Run();
performanceTest->TearDown();
}
}

Aws::ShutdownAPI(sdkOptions);
return 0;
}