From ad7be3651520b0615c8a7f6a0d445b17f62bd802 Mon Sep 17 00:00:00 2001 From: kai lin Date: Thu, 7 Aug 2025 14:04:52 -0400 Subject: [PATCH] feat: add gzip compression tracking to User-Agent metrics - Add test to verify 'L' metric appears in User-Agent when gzip compression is used - Enable compression in test setup and mock client - Validate business metrics tracking for request compression feature update logic for compression unit test --- cmake/sdksCommon.cmake | 2 +- .../include/aws/core/client/UserAgent.h | 1 + .../source/client/AWSClient.cpp | 3 + .../source/client/UserAgent.cpp | 1 + .../CMakeLists.txt | 33 ++++++ .../CloudWatchUnitTest.cpp | 107 ++++++++++++++++++ 6 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 tests/aws-cpp-sdk-monitoring-unit-tests/CMakeLists.txt create mode 100644 tests/aws-cpp-sdk-monitoring-unit-tests/CloudWatchUnitTest.cpp diff --git a/cmake/sdksCommon.cmake b/cmake/sdksCommon.cmake index 229940fc7f6..4d66f46c147 100644 --- a/cmake/sdksCommon.cmake +++ b/cmake/sdksCommon.cmake @@ -101,7 +101,7 @@ list(APPEND SDK_TEST_PROJECT_LIST "kinesis:tests/aws-cpp-sdk-kinesis-integration list(APPEND SDK_TEST_PROJECT_LIST "lambda:tests/aws-cpp-sdk-lambda-integration-tests") list(APPEND SDK_TEST_PROJECT_LIST "logs:tests/aws-cpp-sdk-logs-integration-tests,tests/aws-cpp-sdk-logs-unit-tests") list(APPEND SDK_TEST_PROJECT_LIST "mediastore-data:tests/aws-cpp-sdk-mediastore-data-integration-tests") -list(APPEND SDK_TEST_PROJECT_LIST "monitoring:tests/aws-cpp-sdk-monitoring-integration-tests") +list(APPEND SDK_TEST_PROJECT_LIST "monitoring:tests/aws-cpp-sdk-monitoring-integration-tests,tests/aws-cpp-sdk-monitoring-unit-tests") list(APPEND SDK_TEST_PROJECT_LIST "rds:tests/aws-cpp-sdk-rds-integration-tests") list(APPEND SDK_TEST_PROJECT_LIST "redshift:tests/aws-cpp-sdk-redshift-integration-tests") list(APPEND SDK_TEST_PROJECT_LIST "s3:tests/aws-cpp-sdk-s3-integration-tests") diff --git a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h index 891693f3330..b3cc7740af5 100644 --- a/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h +++ b/src/aws-cpp-sdk-core/include/aws/core/client/UserAgent.h @@ -31,6 +31,7 @@ enum class UserAgentFeature { ACCOUNT_ID_MODE_DISABLED, ACCOUNT_ID_MODE_REQUIRED, RESOLVED_ACCOUNT_ID, + GZIP_REQUEST_COMPRESSION, }; class AWS_CORE_API UserAgent { diff --git a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp index 18bdfd60e47..e91e84dbd22 100644 --- a/src/aws-cpp-sdk-core/source/client/AWSClient.cpp +++ b/src/aws-cpp-sdk-core/source/client/AWSClient.cpp @@ -910,6 +910,9 @@ void AWSClient::BuildHttpRequest(const Aws::AmazonWebServiceRequest& request, co if (compressOutcome.IsSuccess()) { Aws::String compressionAlgorithmId = Aws::Client::GetCompressionAlgorithmId(selectedCompressionAlgorithm); AppendHeaderValueToRequest(httpRequest, CONTENT_ENCODING_HEADER, compressionAlgorithmId); + if (selectedCompressionAlgorithm == Aws::Client::CompressionAlgorithm::GZIP) { + request.AddUserAgentFeature(Aws::Client::UserAgentFeature::GZIP_REQUEST_COMPRESSION); + } AddContentBodyToRequest( httpRequest, compressOutcome.GetResult(), request.ShouldComputeContentMd5(), diff --git a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp index 75dabcecafa..5528392f8ee 100644 --- a/src/aws-cpp-sdk-core/source/client/UserAgent.cpp +++ b/src/aws-cpp-sdk-core/source/client/UserAgent.cpp @@ -41,6 +41,7 @@ const std::pair BUSINESS_METRIC_MAPPING[] = { {UserAgentFeature::ACCOUNT_ID_MODE_DISABLED , "Q"}, {UserAgentFeature::ACCOUNT_ID_MODE_REQUIRED, "R"}, {UserAgentFeature::RESOLVED_ACCOUNT_ID, "T"}, + {UserAgentFeature::GZIP_REQUEST_COMPRESSION, "L"}, }; Aws::String BusinessMetricForFeature(UserAgentFeature feature) { diff --git a/tests/aws-cpp-sdk-monitoring-unit-tests/CMakeLists.txt b/tests/aws-cpp-sdk-monitoring-unit-tests/CMakeLists.txt new file mode 100644 index 00000000000..9321f827db5 --- /dev/null +++ b/tests/aws-cpp-sdk-monitoring-unit-tests/CMakeLists.txt @@ -0,0 +1,33 @@ +add_project(aws-cpp-sdk-monitoring-unit-tests + "Unit Tests for the CloudWatch Monitoring Client" + aws-cpp-sdk-monitoring + testing-resources + aws_test_main + aws-cpp-sdk-core) + +add_definitions(-DRESOURCES_DIR="${CMAKE_CURRENT_SOURCE_DIR}/resources") + +if(MSVC AND BUILD_SHARED_LIBS) + add_definitions(-DGTEST_LINKED_AS_SHARED_LIBRARY=1) +endif() + +enable_testing() + +if(PLATFORM_ANDROID AND BUILD_SHARED_LIBS) + add_library(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/CloudWatchUnitTest.cpp) +else() + add_executable(${PROJECT_NAME} ${CMAKE_CURRENT_SOURCE_DIR}/CloudWatchUnitTest.cpp) +endif() + +set_compiler_flags(${PROJECT_NAME}) +set_compiler_warnings(${PROJECT_NAME}) + +target_link_libraries(${PROJECT_NAME} ${PROJECT_LIBS}) + +if(MSVC AND BUILD_SHARED_LIBS) + set_target_properties(${PROJECT_NAME} PROPERTIES LINK_FLAGS "/DELAYLOAD:aws-cpp-sdk-monitoring.dll /DELAYLOAD:aws-cpp-sdk-core.dll") + target_link_libraries(${PROJECT_NAME} delayimp.lib) +endif() + +include(GoogleTest) +gtest_add_tests(TARGET ${PROJECT_NAME}) \ No newline at end of file diff --git a/tests/aws-cpp-sdk-monitoring-unit-tests/CloudWatchUnitTest.cpp b/tests/aws-cpp-sdk-monitoring-unit-tests/CloudWatchUnitTest.cpp new file mode 100644 index 00000000000..b777912a82d --- /dev/null +++ b/tests/aws-cpp-sdk-monitoring-unit-tests/CloudWatchUnitTest.cpp @@ -0,0 +1,107 @@ +/** + * 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 + +using namespace Aws::Auth; +using namespace Aws::Http; +using namespace Aws::Client; +using namespace Aws::CloudWatch; +using namespace Aws::CloudWatch::Model; + +const char* LOG_TAG = "CloudWatchTest"; + +class CloudWatchTest : public Aws::Testing::AwsCppSdkGTestSuite { + protected: + std::shared_ptr m_mockHttpClient; + std::shared_ptr m_mockHttpClientFactory; + + void SetUp() { + m_mockHttpClient = Aws::MakeShared(LOG_TAG); + m_mockHttpClientFactory = Aws::MakeShared(LOG_TAG); + m_mockHttpClientFactory->SetClient(m_mockHttpClient); + SetHttpClientFactory(m_mockHttpClientFactory); + } + + void TearDown() { + m_mockHttpClient->Reset(); + m_mockHttpClient = nullptr; + m_mockHttpClientFactory = nullptr; + Aws::Http::CleanupHttp(); + Aws::Http::InitHttp(); + } +}; + +TEST_F(CloudWatchTest, TestUserAgentCompressionTracking) { +#ifndef ENABLED_ZLIB_REQUEST_COMPRESSION + GTEST_SKIP() << "ZLIB compression not available in this build"; +#endif + // Setup mock response with proper CloudWatch XML + std::shared_ptr requestTmp = + CreateHttpRequest(Aws::Http::URI("dummy"), Aws::Http::HttpMethod::HTTP_POST, Aws::Utils::Stream::DefaultResponseStreamFactoryMethod); + auto successResponse = Aws::MakeShared(LOG_TAG, requestTmp); + successResponse->SetResponseCode(HttpResponseCode::OK); + successResponse->GetResponseBody() << "test-request-id"; + m_mockHttpClient->AddResponseToReturn(successResponse); + + // Create client with compression enabled + Aws::Client::ClientConfigurationInitValues cfgInit; + cfgInit.shouldDisableIMDS = true; + Aws::Client::ClientConfiguration clientConfig(cfgInit); + clientConfig.region = Aws::Region::US_EAST_1; + clientConfig.requestCompressionConfig.useRequestCompression = UseRequestCompression::ENABLE; + clientConfig.requestCompressionConfig.requestMinCompressionSizeBytes = 10240; + + AWSCredentials credentials{"mock", "credentials"}; + CloudWatchClient client(credentials, clientConfig); + + // Create request with large payload to trigger compression + PutMetricDataRequest request; + request.SetNamespace("TestNamespace"); + + // Add large metric data to trigger compression + MetricDatum metricDatum; + metricDatum.SetMetricName("TestMetric"); + metricDatum.SetValue(123.45); + metricDatum.SetTimestamp(Aws::Utils::DateTime::Now()); + + // Create large metadata to exceed compression threshold + Aws::String largeValue(20000, 'A'); // 20KB value to exceed compression threshold + Dimension dimension; + dimension.SetName("LargeDimension"); + dimension.SetValue(largeValue); + metricDatum.AddDimensions(dimension); + + request.AddMetricData(metricDatum); + + auto outcome = client.PutMetricData(request); + AWS_ASSERT_SUCCESS(outcome); + + // Verify User-Agent contains compression metric + auto lastRequest = m_mockHttpClient->GetMostRecentHttpRequest(); + EXPECT_TRUE(lastRequest.HasUserAgent()); + const auto& userAgent = lastRequest.GetUserAgent(); + EXPECT_TRUE(!userAgent.empty()); + + const auto userAgentParsed = Aws::Utils::StringUtils::Split(userAgent, ' '); + + // Check for gzip compression business metric (L) in user agent + auto businessMetrics = std::find_if(userAgentParsed.begin(), userAgentParsed.end(), + [](const Aws::String& value) { return value.find("m/") != Aws::String::npos && value.find("L") != Aws::String::npos; }); + + EXPECT_TRUE(businessMetrics != userAgentParsed.end()); +} \ No newline at end of file