File: SdkTestTransferStats_test.cpp

package info (click to toggle)
megacmd 2.5.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 32,592 kB
  • sloc: cpp: 326,437; ansic: 34,524; python: 4,630; java: 3,965; sh: 2,869; objc: 2,459; makefile: 197; xml: 113
file content (330 lines) | stat: -rw-r--r-- 14,001 bytes parent folder | download | duplicates (2)
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{});
}