Skip to content

Commit 177c3f6

Browse files
committed
performance test draft prototype
1 parent e667c8c commit 177c3f6

File tree

6 files changed

+436
-0
lines changed

6 files changed

+436
-0
lines changed

CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,13 @@ if (LEGACY_BUILD)
341341
add_sdks()
342342
include(tests)
343343

344+
#Performance tests (under tests/performance_tests)
345+
option(BUILD_PERFORMANCE_TESTS "Build AWS C++ performance tests" OFF)
346+
if(BUILD_PERFORMANCE_TESTS)
347+
add_subdirectory(tests/performance_tests)
348+
endif()
349+
350+
344351
# for user friendly cmake usage
345352
include(setup_cmake_find_module)
346353

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
cmake_minimum_required(VERSION 3.13)
2+
3+
include(FetchContent)
4+
5+
FetchContent_Declare(
6+
nlohmann_json
7+
URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz
8+
)
9+
FetchContent_MakeAvailable(nlohmann_json)
10+
11+
add_project(performance_tests
12+
"A suite of simple AWS C++ SDK performance tests"
13+
aws-cpp-sdk-core
14+
aws-cpp-sdk-s3
15+
)
16+
17+
add_library(json_metrics STATIC
18+
service/JsonReportingMetrics.cpp
19+
service/JsonReportingMetrics.h
20+
)
21+
22+
target_include_directories(json_metrics PUBLIC
23+
"${PROJECT_SOURCE_DIR}/aws-cpp-sdk-core/include"
24+
)
25+
26+
target_include_directories(json_metrics PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/service)
27+
target_link_libraries(json_metrics
28+
PUBLIC
29+
aws-cpp-sdk-core
30+
nlohmann_json::nlohmann_json
31+
)
32+
33+
# PutObject Performance Test
34+
add_executable(s3_put_object_perf
35+
service/S3PutObject.cpp
36+
)
37+
set_compiler_flags(s3_put_object_perf)
38+
set_compiler_warnings(s3_put_object_perf)
39+
target_link_libraries(s3_put_object_perf
40+
PRIVATE
41+
${PROJECT_LIBS}
42+
json_metrics
43+
)
44+
45+
# GetObject Performance Test
46+
add_executable(s3_get_object_perf
47+
service/S3GetObject.cpp
48+
)
49+
set_compiler_flags(s3_get_object_perf)
50+
set_compiler_warnings(s3_get_object_perf)
51+
target_link_libraries(s3_get_object_perf
52+
PRIVATE
53+
${PROJECT_LIBS}
54+
json_metrics
55+
)
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#include "JsonReportingMetrics.h"
2+
#include <aws/core/utils/json/JsonSerializer.h>
3+
#include <aws/core/utils/StringUtils.h>
4+
#include <aws/core/utils/DateTime.h>
5+
#include <fstream>
6+
7+
JsonReportingMetrics::JsonReportingMetrics() {}
8+
9+
JsonReportingMetrics::~JsonReportingMetrics() {
10+
DumpJson();
11+
}
12+
13+
JsonReportingMetricsFactory::~JsonReportingMetricsFactory() {}
14+
15+
// And the factory method
16+
Aws::UniquePtr<Aws::Monitoring::MonitoringInterface> JsonReportingMetricsFactory::CreateMonitoringInstance() const {
17+
return Aws::MakeUnique<JsonReportingMetrics>("JsonReportingMetrics");
18+
}
19+
20+
void JsonReportingMetrics::AddMetric(const Aws::String& serviceName, const Aws::String& requestName,
21+
const Aws::Monitoring::CoreMetricsCollection& metricsFromCore, bool success) const {
22+
std::lock_guard<std::mutex> lock(m_mutex);
23+
24+
double durationMs = 0.0;
25+
Aws::String latencyKey = Aws::Monitoring::GetHttpClientMetricNameByType(Aws::Monitoring::HttpClientMetricsType::RequestLatency);
26+
27+
auto it = metricsFromCore.httpClientMetrics.find(latencyKey);
28+
if (it != metricsFromCore.httpClientMetrics.end()) {
29+
durationMs = static_cast<double>(it->second);
30+
}
31+
32+
RequestMetric metric;
33+
metric.name = Aws::Utils::StringUtils::ToLower(serviceName.c_str()) +
34+
Aws::String(".") +
35+
Aws::Utils::StringUtils::ToLower(requestName.c_str());
36+
metric.description = Aws::String("Time to complete ") + metric.name + Aws::String(" operation");
37+
metric.unit = "Milliseconds";
38+
metric.date = Aws::Utils::DateTime::CurrentTimeMillis() / 1000;
39+
metric.measurements.push_back(durationMs);
40+
41+
metric.dimensions.push_back(std::make_pair(Aws::String("Service"), serviceName));
42+
metric.dimensions.push_back(std::make_pair(Aws::String("Operation"), requestName));
43+
metric.publishToCloudWatch = true;
44+
45+
m_metrics.push_back(metric);
46+
}
47+
48+
void JsonReportingMetrics::AggregateMetrics() const {
49+
// Create finalized metrics from the aggregated data
50+
for (const auto& pair : m_aggregatedMetrics) {
51+
RequestMetric metric;
52+
metric.name = pair.first;
53+
metric.description = Aws::String("Time to complete ") + pair.first + Aws::String(" operation");
54+
metric.unit = "Milliseconds";
55+
metric.date = Aws::Utils::DateTime::CurrentTimeMillis() / 1000;
56+
metric.measurements = pair.second;
57+
metric.publishToCloudWatch = true;
58+
59+
// Add dimensions (e.g., AWS service name)
60+
size_t dotPos = pair.first.find('.');
61+
if (dotPos != Aws::String::npos) {
62+
Aws::String service = pair.first.substr(0, dotPos);
63+
metric.dimensions.push_back(std::make_pair(Aws::String("Service"), service));
64+
}
65+
66+
m_metrics.push_back(metric);
67+
}
68+
}
69+
70+
// Interface Overrides
71+
void* JsonReportingMetrics::OnRequestStarted(const Aws::String&, const Aws::String&,
72+
const std::shared_ptr<const Aws::Http::HttpRequest>&) const {
73+
return nullptr;
74+
}
75+
76+
void JsonReportingMetrics::OnRequestSucceeded(const Aws::String& serviceName, const Aws::String& requestName,
77+
const std::shared_ptr<const Aws::Http::HttpRequest>&,
78+
const Aws::Client::HttpResponseOutcome& outcome,
79+
const Aws::Monitoring::CoreMetricsCollection& metricsFromCore, void*) const {
80+
AddMetric(serviceName, requestName, metricsFromCore, outcome.IsSuccess());
81+
}
82+
83+
void JsonReportingMetrics::OnRequestFailed(const Aws::String& serviceName, const Aws::String& requestName,
84+
const std::shared_ptr<const Aws::Http::HttpRequest>&,
85+
const Aws::Client::HttpResponseOutcome& outcome,
86+
const Aws::Monitoring::CoreMetricsCollection& metricsFromCore, void*) const {
87+
AddMetric(serviceName, requestName, metricsFromCore, outcome.IsSuccess());
88+
}
89+
90+
void JsonReportingMetrics::OnFinish(const Aws::String&, const Aws::String&, const std::shared_ptr<const Aws::Http::HttpRequest>&,
91+
void*) const {
92+
}
93+
94+
void JsonReportingMetrics::OnRequestRetry(const Aws::String&, const Aws::String&, const std::shared_ptr<const Aws::Http::HttpRequest>&,
95+
void*) const {
96+
}
97+
98+
void JsonReportingMetrics::DumpJson() const {
99+
std::lock_guard<std::mutex> lock(m_mutex);
100+
101+
if (m_metrics.empty()) {
102+
return;
103+
}
104+
105+
Aws::Utils::Json::JsonValue root;
106+
107+
// Add required top-level fields
108+
root.WithString("productId", "AWS SDK for C++");
109+
root.WithString("sdkVersion", "1.0.0");
110+
root.WithString("commitId", "unknown"); // Consider getting this from CI/CD env variable
111+
112+
Aws::Utils::Array<Aws::Utils::Json::JsonValue> results(m_metrics.size());
113+
114+
for (size_t i = 0; i < m_metrics.size(); ++i) {
115+
Aws::Utils::Json::JsonValue metric;
116+
metric.WithString("name", m_metrics[i].name);
117+
metric.WithString("description", m_metrics[i].description);
118+
metric.WithString("unit", m_metrics[i].unit);
119+
metric.WithInt64("date", m_metrics[i].date);
120+
121+
// Add dimensions if present
122+
if (!m_metrics[i].dimensions.empty()) {
123+
Aws::Utils::Array<Aws::Utils::Json::JsonValue> dimensionsArray(m_metrics[i].dimensions.size());
124+
for (size_t j = 0; j < m_metrics[i].dimensions.size(); ++j) {
125+
Aws::Utils::Json::JsonValue dimension;
126+
dimension.WithString("name", m_metrics[i].dimensions[j].first);
127+
dimension.WithString("value", m_metrics[i].dimensions[j].second);
128+
dimensionsArray[j] = std::move(dimension);
129+
}
130+
metric.WithArray("dimensions", std::move(dimensionsArray));
131+
}
132+
133+
// Create a JSON array for measurements
134+
Aws::Utils::Array<Aws::Utils::Json::JsonValue> measurementsArray(m_metrics[i].measurements.size());
135+
for (size_t j = 0; j < m_metrics[i].measurements.size(); ++j) {
136+
Aws::Utils::Json::JsonValue measurementValue;
137+
measurementValue.AsDouble(m_metrics[i].measurements[j]);
138+
measurementsArray[j] = std::move(measurementValue);
139+
}
140+
metric.WithArray("measurements", std::move(measurementsArray));
141+
142+
results[i] = std::move(metric);
143+
}
144+
root.WithArray("results", std::move(results));
145+
146+
// Write to stdout so the performance test runner can capture it
147+
std::cout << root.View().WriteReadable() << std::endl;
148+
149+
// Save to file
150+
std::ofstream outFile("perf-results.json");
151+
if (outFile.is_open()) {
152+
outFile << root.View().WriteReadable();
153+
}
154+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#pragma once
2+
3+
#include <memory>
4+
#include <mutex>
5+
#include <string>
6+
#include <vector>
7+
8+
#include <aws/core/utils/memory/AWSMemory.h>
9+
#include <aws/core/utils/memory/stl/AWSString.h>
10+
#include <aws/core/utils/memory/stl/AWSVector.h>
11+
#include <aws/core/utils/memory/stl/AWSMap.h>
12+
13+
#include <aws/core/monitoring/MonitoringFactory.h>
14+
#include <aws/core/monitoring/MonitoringInterface.h>
15+
16+
#include <aws/core/client/AWSClient.h>
17+
#include <aws/core/http/HttpRequest.h>
18+
#include <aws/core/monitoring/CoreMetrics.h>
19+
20+
struct RequestMetric {
21+
Aws::String name;
22+
Aws::String description;
23+
Aws::String unit;
24+
int64_t date;
25+
Aws::Vector<double> measurements;
26+
Aws::Vector<std::pair<Aws::String, Aws::String>> dimensions;
27+
bool publishToCloudWatch;
28+
};
29+
30+
class JsonReportingMetrics : public Aws::Monitoring::MonitoringInterface {
31+
public:
32+
JsonReportingMetrics();
33+
~JsonReportingMetrics() override;
34+
35+
void* OnRequestStarted(const Aws::String& serviceName, const Aws::String& requestName,
36+
const std::shared_ptr<const Aws::Http::HttpRequest>& request) const override;
37+
38+
void OnRequestSucceeded(const Aws::String& serviceName, const Aws::String& requestName,
39+
const std::shared_ptr<const Aws::Http::HttpRequest>& request, const Aws::Client::HttpResponseOutcome& outcome,
40+
const Aws::Monitoring::CoreMetricsCollection& metricsFromCore, void* context) const override;
41+
42+
void OnRequestFailed(const Aws::String& serviceName, const Aws::String& requestName,
43+
const std::shared_ptr<const Aws::Http::HttpRequest>& request, const Aws::Client::HttpResponseOutcome& outcome,
44+
const Aws::Monitoring::CoreMetricsCollection& metricsFromCore, void* context) const override;
45+
46+
void OnRequestRetry(const Aws::String& serviceName, const Aws::String& requestName,
47+
const std::shared_ptr<const Aws::Http::HttpRequest>& request, void* context) const override;
48+
49+
void OnFinish(const Aws::String& serviceName, const Aws::String& requestName,
50+
const std::shared_ptr<const Aws::Http::HttpRequest>& request, void* context) const override;
51+
52+
private:
53+
void AddMetric(const Aws::String& serviceName, const Aws::String& requestName,
54+
const Aws::Monitoring::CoreMetricsCollection& metricsFromCore, bool success) const;
55+
void DumpJson() const;
56+
57+
void AggregateMetrics() const;
58+
59+
mutable std::mutex m_mutex;
60+
mutable Aws::Vector<RequestMetric> m_metrics;
61+
mutable Aws::Map<Aws::String, Aws::Vector<double>> m_aggregatedMetrics;
62+
};
63+
64+
class JsonReportingMetricsFactory : public Aws::Monitoring::MonitoringFactory {
65+
public:
66+
JsonReportingMetricsFactory() {}
67+
~JsonReportingMetricsFactory() override;
68+
Aws::UniquePtr<Aws::Monitoring::MonitoringInterface> CreateMonitoringInstance() const override;
69+
};
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/**
2+
* Simple perf test: create bucket -> put 64 KiB -> get it once -> cleanup
3+
*/
4+
#include <aws/core/Aws.h>
5+
#include <aws/core/client/ClientConfiguration.h>
6+
#include <aws/core/utils/StringUtils.h>
7+
#include <aws/core/utils/UUID.h>
8+
#include <aws/core/utils/memory/stl/AWSStringStream.h>
9+
#include <aws/s3/S3Client.h>
10+
#include <aws/s3/model/CreateBucketRequest.h>
11+
#include <aws/s3/model/DeleteBucketRequest.h>
12+
#include <aws/s3/model/DeleteObjectRequest.h>
13+
#include <aws/s3/model/GetObjectRequest.h>
14+
#include <aws/s3/model/PutObjectRequest.h>
15+
16+
#include <iostream>
17+
18+
#include "JsonReportingMetrics.h"
19+
20+
int main(int argc, char** argv) {
21+
Aws::SDKOptions options;
22+
23+
options.monitoringOptions.customizedMonitoringFactory_create_fn.push_back(
24+
[]() { return Aws::MakeUnique<JsonReportingMetricsFactory>("jsonFactory"); });
25+
26+
Aws::InitAPI(options);
27+
{
28+
Aws::Client::ClientConfiguration cfg;
29+
if (argc > 1) cfg.region = argv[1];
30+
31+
cfg.enableHttpClientTrace = true;
32+
33+
Aws::S3::S3Client s3(cfg);
34+
35+
// 1) bucket name
36+
Aws::String raw = static_cast<Aws::String>(Aws::Utils::UUID::RandomUUID());
37+
Aws::String id = Aws::Utils::StringUtils::ToLower(raw.c_str()).substr(10);
38+
Aws::String bucket = Aws::String("get-bucket-benchmark-") + id;
39+
40+
// 2) create bucket
41+
Aws::S3::Model::CreateBucketRequest cbr;
42+
cbr.WithBucket(bucket);
43+
if (!s3.CreateBucket(cbr).IsSuccess()) {
44+
std::cerr << "[ERROR] CreateBucket\n";
45+
return 1;
46+
}
47+
48+
// 3) put a 64KiB test object
49+
std::string payload(64 * 1024, 'x');
50+
auto stream = Aws::MakeShared<Aws::StringStream>("PerfStream");
51+
*stream << payload;
52+
53+
Aws::S3::Model::PutObjectRequest por;
54+
por.WithBucket(bucket).WithKey("test-object").SetBody(stream);
55+
if (!s3.PutObject(por).IsSuccess()) {
56+
std::cerr << "[ERROR] PutObject\n";
57+
return 1;
58+
}
59+
std::cout << "Uploaded 64 KiB to " << bucket << "/test-object\n";
60+
61+
// 4) get object
62+
Aws::S3::Model::GetObjectRequest gor;
63+
gor.WithBucket(bucket).WithKey("test-object");
64+
65+
auto goOutcome = s3.GetObject(gor);
66+
if (!goOutcome.IsSuccess()) {
67+
std::cerr << "[ERROR] GetObject: " << goOutcome.GetError().GetMessage() << "\n";
68+
return 1;
69+
}
70+
std::cout << "Downloaded " << goOutcome.GetResult().GetContentLength() << " bytes\n";
71+
72+
// 5) cleanup
73+
s3.DeleteObject(Aws::S3::Model::DeleteObjectRequest().WithBucket(bucket).WithKey("test-object"));
74+
s3.DeleteBucket(Aws::S3::Model::DeleteBucketRequest().WithBucket(bucket));
75+
std::cout << "Cleaned up\n";
76+
}
77+
Aws::ShutdownAPI(options);
78+
return 0;
79+
}

0 commit comments

Comments
 (0)