1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
|
/**
* @file SdkTestTransferStats_test.cpp
* @brief This file defines some tests for testing transfer stats (uploads & downloads).
*/
#include "sdk_test_utils.h"
#include "SdkTest_test.h"
#include <numeric>
namespace
{
/**
* @brief Calculate specific metrics values that can be expected.
*
* @param transferType The type of transfer: PUT for uploads, GET for downloads.
* @param sizes A vector with the file sizes.
* @param raidedTransferRatio The ratio of raided files per transfer.
*
* @return A stats::TransferStats::Metrics object with the transfer type, median size,
* contraharmonic mean and raided transfer ratio.
*/
stats::TransferStats::Metrics calculateExpectedMetrics(const direction_t transferType,
const std::vector<m_off_t>& sizes,
const double raidedTransferRatio = 0.0)
{
stats::TransferStats::Metrics metrics;
// Assign the transfer type (PUT or GET).
EXPECT_TRUE(transferType == PUT || transferType == GET);
metrics.mTransferType = transferType;
// Assign number of transfers.
metrics.mNumTransfers = sizes.size();
// Calculate the median size.
auto sortedSizes = sizes;
std::sort(sortedSizes.begin(), sortedSizes.end());
metrics.mMedianSize = stats::calculateMedian(sortedSizes);
// Calculate the contraharmonic mean (sizes weighted by their own sizes).
metrics.mContraharmonicMeanSize = stats::calculateWeightedAverage(sizes, sizes);
// Set RAID transfer ratio.
metrics.mRaidedTransferRatio = raidedTransferRatio;
return metrics;
}
/**
* @brief Compare the expected Metrics with the metrics obtained from the TransferStatsManager.
*
* For medianSpeed, weightedAverageSpeed, maxSpeed, avgLatency, and failedRequestRatio,
* we perform some light checks, as those are not fully predictable.
*
* @param expected The expected values for TransferStats::Metrics.
* @param actual The TransferStats::Metrics object retrieved from the
* MegaClient::TransferStatsManager.
*
*/
void compareMetrics(const stats::TransferStats::Metrics& expected,
const stats::TransferStats::Metrics& actual)
{
if (expected.mNumTransfers != actual.mNumTransfers)
{
LOG_warn << "Expected number of transfers (" << expected.mNumTransfers
<< ") does not match with actual value (" << actual.mNumTransfers
<< "). Skipping comparison";
return;
}
EXPECT_EQ(expected.mTransferType, actual.mTransferType);
EXPECT_EQ(expected.mMedianSize, actual.mMedianSize);
EXPECT_EQ(expected.mContraharmonicMeanSize, actual.mContraharmonicMeanSize);
EXPECT_GT(actual.mMedianSpeed, 0);
EXPECT_GE(actual.mWeightedAverageSpeed, actual.mMedianSpeed);
EXPECT_GE(actual.mMaxSpeed, actual.mMedianSpeed);
EXPECT_GT(actual.mAvgLatency, 0);
EXPECT_LT(actual.mAvgLatency, 150000);
EXPECT_GE(actual.mFailedRequestRatio, 0.0);
EXPECT_LE(actual.mFailedRequestRatio, 1.1);
EXPECT_EQ(expected.mRaidedTransferRatio, actual.mRaidedTransferRatio);
};
} // namespace
/**
* @class SdkTestTransferStats
* @brief Fixture for test suite to test Transfer Stats.
*
*/
class SdkTestTransferStats: public SdkTest
{
public:
/**
* @brief Extra time from transfer request completion to the TransferSlot constructor, where the
* transfer stats are collected.
*/
static constexpr std::chrono::milliseconds TIME_FROM_TRANSFER_COMPLETE_TO_STATS_COLLECTION{
1500};
/**
* @brief Wrapper to upload files with the necessary parameters.
*
* @param rootNode The ROOTNODE of the Cloud.
* @param uploadFileName The name of the file.
* @param content The contents of the file.
*
* @return A pointer to the MegaNode object created from the Cloud.
*/
MegaNode* uploadFileForStats(MegaNode* const rootNode,
const std::string_view uploadFileName,
const std::string_view content)
{
MegaHandle fileHandle = 0;
sdk_test::LocalTempFile testTempFile(uploadFileName.data(), content);
EXPECT_EQ(MegaError::API_OK,
doStartUpload(0,
&fileHandle,
uploadFileName.data(),
rootNode,
nullptr,
::mega::MegaApi::INVALID_CUSTOM_MOD_TIME,
nullptr,
true,
false,
nullptr))
<< "Cannot upload " << uploadFileName;
std::this_thread::sleep_for(TIME_FROM_TRANSFER_COMPLETE_TO_STATS_COLLECTION);
return megaApi[0]->getNodeByHandle(fileHandle);
}
/**
* @brief Wrapper to download files with the necessary parameters.
*
* @param node The cloud node with the file info to download.
* @param downloadFileName The name of the file.
*/
void downloadFileForStats(MegaNode* const node, const std::string_view downloadFileName)
{
ASSERT_EQ(
MegaError::API_OK,
doStartDownload(0,
node,
downloadFileName.data(),
nullptr /*customName*/,
nullptr /*appData*/,
false /*startFirst*/,
nullptr /*cancelToken*/,
MegaTransfer::COLLISION_CHECK_FINGERPRINT /*collisionCheck*/,
MegaTransfer::COLLISION_RESOLUTION_NEW_WITH_N /* collisionResolution */,
false /* undelete */))
<< "Cannot download " << downloadFileName;
std::this_thread::sleep_for(TIME_FROM_TRANSFER_COMPLETE_TO_STATS_COLLECTION);
};
};
/**
* @test SdkTestTransferStats
*
* Upload and download regular files and a CloudRAID file,
* collect Transfer Metrics and check expected results.
*
* Note: We don't compare upload metrics before to ensure
* that upload and downloads metrics are separated and
* were not mixed up by the TransferStatsManager.
*
* 1. UPLOAD AND DOWNLOAD TWO FILES TO COLLECT TRANSFER STATS.
* 1.1 Upload files.
* 1.2 Download both files.
* 2. COLLECT AND COMPARE UPLOADS AND DOWNLOADS METRICS.
* 2.1 Define sizes of uploaded and regular downloaded files.
* 2.2 Collect metrics.
* 2.3 Define expected metrics for uploads and compare results.
* 2.4 Define expected metrics for the regular downloads and compare results.
* 3. CHECK DOWNLOAD TRANSFER STATS INCLUDING A NEW CLOUDRAID FILE.
* 3.1 Download a CloudRAID file.
* 3.2 Define expected metrics after RAID download.
* 3.3 Collect metrics for downloads including the CloudRAID file and compare results.
*/
TEST_F(SdkTestTransferStats, SdkTestTransferStats)
{
LOG_info << "___TEST SdkTestTransferStats";
ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1));
// Make sure our clients are working with pro plans.
auto accountRestorer = scopedToPro(*megaApi[0]);
ASSERT_EQ(result(accountRestorer), API_OK);
std::unique_ptr<MegaNode> rootNode(megaApi[0]->getRootNode());
ASSERT_TRUE(rootNode);
// 1. UPLOAD AND DOWNLOAD TWO FILES TO COLLECT TRANSFER STATS.
// 1.1 Upload files.
constexpr std::string_view file1content = "Current content 1";
std::unique_ptr<MegaNode> testFileNode1(
uploadFileForStats(rootNode.get(), "test1.txt", file1content));
ASSERT_TRUE(testFileNode1);
constexpr std::string_view file2content = "Current content 2 - longer";
std::unique_ptr<MegaNode> testFileNode2(
uploadFileForStats(rootNode.get(), "test2.txt", file2content));
ASSERT_TRUE(testFileNode2);
// 1.2. Download both files.
downloadFileForStats(testFileNode1.get(), DOTSLASH "downfile1.txt");
downloadFileForStats(testFileNode2.get(), DOTSLASH "downfile2.txt");
// 2. COLLECT AND COMPARE UPLOADS AND DOWNLOADS METRICS.
// 2.1 Define sizes of uploaded and regular downloaded files.
constexpr m_off_t file1size = file1content.size(); // size = 17 bytes
constexpr m_off_t file2size = file2content.size(); // size = 26 bytes
const std::vector<m_off_t> regularFileSizes = {file1size, file2size};
const stats::TransferStats::UncollectedTransfersCounters unCollectedDataExpectations{
regularFileSizes.size(),
std::accumulate(regularFileSizes.begin(), regularFileSizes.end(), m_off_t{0})};
// Give enough time for the TransferSlot constructor to be called, when the stats are collected.
std::this_thread::sleep_for(std::chrono::seconds(2));
// 2.2 Collect metrics.
auto* const client{megaApi[0]->getClient()};
LOG_debug << "[SdkTest::SdkTestTransferStats] collectAndPrintMetrics for UPLOADS";
ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(PUT),
unCollectedDataExpectations);
const stats::TransferStats::Metrics uploadMetrics =
client->mTransferStatsManager.collectAndPrintMetrics(PUT);
LOG_debug << "[SdkTest::SdkTestTransferStats] collectAndPrintMetrics for DOWNLOADS";
ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(GET),
unCollectedDataExpectations);
const stats::TransferStats::Metrics downloadMetrics1 =
client->mTransferStatsManager.collectAndPrintMetrics(GET);
// 2.3 Define expected metrics for uploads and compare results.
const stats::TransferStats::Metrics expectedUploadMetrics =
calculateExpectedMetrics(PUT, regularFileSizes);
compareMetrics(expectedUploadMetrics, uploadMetrics);
// 2.4 Define expected metrics for the regular downloads and compare results.
const stats::TransferStats::Metrics expectedDownloadMetrics1 =
calculateExpectedMetrics(GET, regularFileSizes);
compareMetrics(expectedDownloadMetrics1, downloadMetrics1);
// 3. CHECK DOWNLOAD TRANSFER STATS INCLUDING A NEW CLOUDRAID FILE.
// 3.1 Download a CloudRAID file.
{
// https://mega.app/file/JzckQJ6L#X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA
std::string url100MB =
"/#!JzckQJ6L!X_p0u26-HOTenAG0rATFhKdxYx-rOV1U6YHYhnz2nsA";
const auto importHandle =
importPublicLink(0, MegaClient::getMegaURL() + url100MB, rootNode.get());
std::unique_ptr<MegaNode> nimported{megaApi[0]->getNodeByHandle(importHandle)};
constexpr std::string_view downloadFileName3{DOTSLASH "downfile3.cloudraided.sdktest"};
deleteFile(downloadFileName3.data());
downloadFileForStats(nimported.get(), DOTSLASH "downfile3.cloudraided.sdktest");
deleteFile(downloadFileName3.data());
}
// 3.2 Define expected metrics after RAID download.
constexpr m_off_t raidFileSize = 100 * 1024 * 1024; // 100MB
const std::vector<m_off_t> allFileSizes = {file1size, file2size, raidFileSize};
const stats::TransferStats::Metrics expectedDownloadMetrics2 =
calculateExpectedMetrics(GET,
allFileSizes,
0.33); // 1 out of 3 is RAID, with 2 decimal precision
// 3.3 Collect metrics for downloads including the CloudRAID file and compare results.
LOG_debug << "[SdkTest::SdkTestTransferStats] collectAndPrintMetrics for DOWNLOADS after "
"CLOUDRAID download";
ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(GET),
(stats::TransferStats::UncollectedTransfersCounters{size_t{1}, raidFileSize}));
const stats::TransferStats::Metrics downloadMetrics2 =
client->mTransferStatsManager.collectAndPrintMetrics(GET);
compareMetrics(expectedDownloadMetrics2, downloadMetrics2);
}
/**
* @test SdkTestTransferStatsLogging
*
* Tests that TransferStats::collectAndPrintMetrics() have been called automatically after
* TransferStatsManager::NUM_ENTRIES_FOR_LOGGING transfers.
*
* 1. Uploads NUM_ENTRIES_FOR_LOGGING-1 regular files
* 2. Checks that the uncollectedAndPrinted transfer data is equal to the accumulated data for those
* NUM_ENTRIES_FOR_LOGGING-1 transfers.
* 3. Uploads 1 extra file.
* 4. Checks that the uncollectedAndPrinted transfer data values are now zero.
*/
TEST_F(SdkTestTransferStats, SdkTestTransferStatsLogging)
{
static const auto logPre = getLogPrefix();
LOG_info << "___TEST " << logPre;
ASSERT_NO_FATAL_FAILURE(getAccountsForTest(1));
const auto rootNode = std::unique_ptr<MegaNode>(megaApi[0]->getRootNode());
ASSERT_TRUE(rootNode);
constexpr std::string_view fileName{"test1.txt"};
constexpr std::string_view baseContent{"Current content "};
constexpr auto numUploads{stats::TransferStatsManager::NUM_ENTRIES_FOR_LOGGING - 1};
size_t totalExpectedBytes = 0;
for (const auto i: range(numUploads))
{
const auto fileContent = std::string(baseContent) + std::to_string(i);
totalExpectedBytes += fileContent.size();
LOG_debug << logPre << "Upload file " << (i + 1);
ASSERT_TRUE(
std::unique_ptr<MegaNode>(uploadFileForStats(rootNode.get(), fileName, fileContent)));
}
auto* const client = megaApi[0]->getClient();
ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(PUT),
(stats::TransferStats::UncollectedTransfersCounters{
numUploads,
static_cast<m_off_t>(totalExpectedBytes)}));
LOG_debug << logPre << "Upload last file (" << (numUploads + 1) << ")";
ASSERT_TRUE(
std::unique_ptr<MegaNode>(uploadFileForStats(rootNode.get(), fileName, baseContent)));
ASSERT_EQ(client->mTransferStatsManager.getUncollectedAndPrintedTransferData(PUT),
stats::TransferStats::UncollectedTransfersCounters{});
}
|