|
| 1 | +/* |
| 2 | + * Copyright (c) Meta Platforms, Inc. and affiliates. |
| 3 | + * |
| 4 | + * This source code is licensed under the MIT license found in the |
| 5 | + * LICENSE file in the root directory of this source tree. |
| 6 | + */ |
| 7 | + |
| 8 | +#include <chrono> |
| 9 | +#include <memory> |
| 10 | + |
| 11 | +#include <gtest/gtest.h> |
| 12 | + |
| 13 | +#include <react/renderer/telemetry/SurfaceTelemetry.h> |
| 14 | +#include <react/renderer/telemetry/TransactionTelemetry.h> |
| 15 | +#include <react/utils/Telemetry.h> |
| 16 | + |
| 17 | +using namespace facebook::react; |
| 18 | + |
| 19 | +namespace { |
| 20 | + |
| 21 | +using namespace std::chrono_literals; |
| 22 | + |
| 23 | +/* |
| 24 | + * Builds a fully-completed `TransactionTelemetry` with deterministic phase |
| 25 | + * durations by driving a lambda-backed clock whose time we advance explicitly |
| 26 | + * between each `will`/`did` pair. `SurfaceTelemetry::incorporate` reads every |
| 27 | + * phase's start/end time, so all four phases (diff, layout, commit, mount) must |
| 28 | + * be signaled or the telemetry getters would assert. |
| 29 | + */ |
| 30 | +TransactionTelemetry makeCompletedTelemetry( |
| 31 | + std::chrono::milliseconds diff, |
| 32 | + std::chrono::milliseconds layout, |
| 33 | + std::chrono::milliseconds commit, |
| 34 | + std::chrono::milliseconds mount, |
| 35 | + std::chrono::milliseconds textMeasurePerCall, |
| 36 | + int numberOfTextMeasurements, |
| 37 | + int revisionNumber) { |
| 38 | + auto now = std::make_shared<TelemetryTimePoint>(); |
| 39 | + auto telemetry = TransactionTelemetry{[now]() { return *now; }}; |
| 40 | + |
| 41 | + telemetry.willDiff(); |
| 42 | + *now += diff; |
| 43 | + telemetry.didDiff(); |
| 44 | + |
| 45 | + telemetry.willLayout(); |
| 46 | + *now += layout; |
| 47 | + telemetry.didLayout(); |
| 48 | + |
| 49 | + for (int i = 0; i < numberOfTextMeasurements; ++i) { |
| 50 | + telemetry.willMeasureText(); |
| 51 | + *now += textMeasurePerCall; |
| 52 | + telemetry.didMeasureText(); |
| 53 | + } |
| 54 | + |
| 55 | + telemetry.willCommit(); |
| 56 | + *now += commit; |
| 57 | + telemetry.didCommit(); |
| 58 | + |
| 59 | + telemetry.willMount(); |
| 60 | + *now += mount; |
| 61 | + telemetry.didMount(); |
| 62 | + |
| 63 | + telemetry.setRevisionNumber(revisionNumber); |
| 64 | + return telemetry; |
| 65 | +} |
| 66 | + |
| 67 | +} // namespace |
| 68 | + |
| 69 | +TEST(SurfaceTelemetryTest, incorporateAggregatesSingleTransaction) { |
| 70 | + auto telemetry = makeCompletedTelemetry( |
| 71 | + /* diff */ 10ms, |
| 72 | + /* layout */ 20ms, |
| 73 | + /* commit */ 30ms, |
| 74 | + /* mount */ 40ms, |
| 75 | + /* textMeasurePerCall */ 5ms, |
| 76 | + /* numberOfTextMeasurements */ 3, |
| 77 | + /* revisionNumber */ 7); |
| 78 | + |
| 79 | + auto surface = SurfaceTelemetry{}; |
| 80 | + surface.incorporate(telemetry, /* numberOfMutations */ 11); |
| 81 | + |
| 82 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getDiffTime()), 10); |
| 83 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getLayoutTime()), 20); |
| 84 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getCommitTime()), 30); |
| 85 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getMountTime()), 40); |
| 86 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getTextMeasureTime()), 15); |
| 87 | + |
| 88 | + EXPECT_EQ(surface.getNumberOfTransactions(), 1); |
| 89 | + EXPECT_EQ(surface.getNumberOfMutations(), 11); |
| 90 | + EXPECT_EQ(surface.getNumberOfTextMeasurements(), 3); |
| 91 | + EXPECT_EQ(surface.getLastRevisionNumber(), 7); |
| 92 | + EXPECT_EQ(surface.getRecentTransactionTelemetries().size(), 1); |
| 93 | +} |
| 94 | + |
| 95 | +TEST(SurfaceTelemetryTest, incorporateAccumulatesAcrossTransactions) { |
| 96 | + auto first = makeCompletedTelemetry( |
| 97 | + /* diff */ 10ms, |
| 98 | + /* layout */ 20ms, |
| 99 | + /* commit */ 30ms, |
| 100 | + /* mount */ 40ms, |
| 101 | + /* textMeasurePerCall */ 5ms, |
| 102 | + /* numberOfTextMeasurements */ 2, |
| 103 | + /* revisionNumber */ 3); |
| 104 | + auto second = makeCompletedTelemetry( |
| 105 | + /* diff */ 1ms, |
| 106 | + /* layout */ 2ms, |
| 107 | + /* commit */ 3ms, |
| 108 | + /* mount */ 4ms, |
| 109 | + /* textMeasurePerCall */ 7ms, |
| 110 | + /* numberOfTextMeasurements */ 1, |
| 111 | + /* revisionNumber */ 9); |
| 112 | + |
| 113 | + auto surface = SurfaceTelemetry{}; |
| 114 | + surface.incorporate(first, /* numberOfMutations */ 4); |
| 115 | + surface.incorporate(second, /* numberOfMutations */ 6); |
| 116 | + |
| 117 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getDiffTime()), 11); |
| 118 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getLayoutTime()), 22); |
| 119 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getCommitTime()), 33); |
| 120 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getMountTime()), 44); |
| 121 | + EXPECT_EQ(telemetryDurationToMilliseconds(surface.getTextMeasureTime()), 17); |
| 122 | + |
| 123 | + EXPECT_EQ(surface.getNumberOfTransactions(), 2); |
| 124 | + EXPECT_EQ(surface.getNumberOfMutations(), 10); |
| 125 | + EXPECT_EQ(surface.getNumberOfTextMeasurements(), 3); |
| 126 | + // The last revision number must reflect the most recently incorporated |
| 127 | + // transaction, not an aggregate. |
| 128 | + EXPECT_EQ(surface.getLastRevisionNumber(), 9); |
| 129 | + EXPECT_EQ(surface.getRecentTransactionTelemetries().size(), 2); |
| 130 | +} |
| 131 | + |
| 132 | +TEST(SurfaceTelemetryTest, recentTransactionTelemetriesRetainAllAtCapBoundary) { |
| 133 | + const auto cap = |
| 134 | + static_cast<int>(SurfaceTelemetry::kMaxNumberOfRecordedCommitTelemetries); |
| 135 | + |
| 136 | + auto surface = SurfaceTelemetry{}; |
| 137 | + for (int revision = 1; revision <= cap; ++revision) { |
| 138 | + auto telemetry = makeCompletedTelemetry( |
| 139 | + 1ms, 1ms, 1ms, 1ms, 0ms, /* numberOfTextMeasurements */ 0, revision); |
| 140 | + surface.incorporate(telemetry, /* numberOfMutations */ 1); |
| 141 | + } |
| 142 | + |
| 143 | + auto recent = surface.getRecentTransactionTelemetries(); |
| 144 | + // Exactly `cap` transactions fit without any eviction. |
| 145 | + EXPECT_EQ(recent.size(), static_cast<size_t>(cap)); |
| 146 | + EXPECT_EQ(recent.front().getRevisionNumber(), 1); |
| 147 | + EXPECT_EQ(recent.back().getRevisionNumber(), cap); |
| 148 | +} |
| 149 | + |
| 150 | +TEST(SurfaceTelemetryTest, recentTransactionTelemetriesEvictOldestBeyondCap) { |
| 151 | + const auto cap = |
| 152 | + static_cast<int>(SurfaceTelemetry::kMaxNumberOfRecordedCommitTelemetries); |
| 153 | + const int total = cap + 4; |
| 154 | + |
| 155 | + auto surface = SurfaceTelemetry{}; |
| 156 | + for (int revision = 1; revision <= total; ++revision) { |
| 157 | + auto telemetry = makeCompletedTelemetry( |
| 158 | + 1ms, 1ms, 1ms, 1ms, 0ms, /* numberOfTextMeasurements */ 0, revision); |
| 159 | + surface.incorporate(telemetry, /* numberOfMutations */ 1); |
| 160 | + } |
| 161 | + |
| 162 | + auto recent = surface.getRecentTransactionTelemetries(); |
| 163 | + // The buffer is capped, but the running transaction counter is not. |
| 164 | + EXPECT_EQ(recent.size(), static_cast<size_t>(cap)); |
| 165 | + EXPECT_EQ(surface.getNumberOfTransactions(), total); |
| 166 | + // The oldest `total - cap` transactions are evicted in FIFO order, so the |
| 167 | + // retained window is [total - cap + 1, total]. |
| 168 | + EXPECT_EQ(recent.front().getRevisionNumber(), total - cap + 1); |
| 169 | + EXPECT_EQ(recent.back().getRevisionNumber(), total); |
| 170 | +} |
0 commit comments