|
75 | 75 | #include <Tracy.hpp> |
76 | 76 |
|
77 | 77 | #include "LedgerManagerImpl.h" |
| 78 | +#include <algorithm> |
78 | 79 | #include <chrono> |
79 | 80 | #include <future> |
| 81 | +#include <limits> |
80 | 82 | #include <memory> |
81 | 83 | #include <optional> |
82 | 84 | #include <regex> |
@@ -228,6 +230,32 @@ LedgerManagerImpl::LedgerApplyMetrics::LedgerApplyMetrics( |
228 | 230 | { |
229 | 231 | } |
230 | 232 |
|
| 233 | +#ifdef BUILD_TESTS |
| 234 | +// For simulations of e2e latency, we use these metrics instead of the V1 |
| 235 | +// self-delay metric for a few reasons. First, the self-delay metric doesn't |
| 236 | +// exist on the overlay-v2 branch. Second, it is hard to unify what the |
| 237 | +// self-delay window is measuring between v1 and v2 (since it measures time from |
| 238 | +// mempool addition to removal). Third, the self-delay metric is a slightly |
| 239 | +// different window from the e2e number we want to report (we want the time from |
| 240 | +// tx sub to meta emission, but the existing metric, for example, includes an |
| 241 | +// extra delay for a postOnMainThread when background apply is enabled). Fourth, |
| 242 | +// as of writing, the metric is a SimpleTimer, so it is lacking most of the |
| 243 | +// percentile data we want, but even if we switch it to be a full Timer, the |
| 244 | +// 30-second medida windowing makes interpretation more difficult. |
| 245 | +LedgerManagerImpl::TxLatencyMetrics::TxLatencyMetrics(MetricsRegistry& registry) |
| 246 | + : mTxsSubmitted(registry.NewCounter({"loadgen", "tx-latency", "submitted"})) |
| 247 | + , mTxsExternalized( |
| 248 | + registry.NewCounter({"loadgen", "tx-latency", "externalized"})) |
| 249 | + , mRunMin(registry.NewCounter({"loadgen", "tx-latency-run", "min-ms"})) |
| 250 | + , mRunMax(registry.NewCounter({"loadgen", "tx-latency-run", "max-ms"})) |
| 251 | + , mRunMean(registry.NewCounter({"loadgen", "tx-latency-run", "mean-ms"})) |
| 252 | + , mRunP50(registry.NewCounter({"loadgen", "tx-latency-run", "p50-ms"})) |
| 253 | + , mRunP75(registry.NewCounter({"loadgen", "tx-latency-run", "p75-ms"})) |
| 254 | + , mRunP99(registry.NewCounter({"loadgen", "tx-latency-run", "p99-ms"})) |
| 255 | +{ |
| 256 | +} |
| 257 | +#endif |
| 258 | + |
231 | 259 | LedgerManagerImpl::ApplyState::ApplyState(Application& app) |
232 | 260 | : mMetrics(app.getMetrics()) |
233 | 261 | , mAppConnector(app.getAppConnector()) |
@@ -351,6 +379,9 @@ LedgerManagerImpl::LedgerManagerImpl(Application& app) |
351 | 379 | , mCatchupDuration( |
352 | 380 | app.getMetrics().NewTimer({"ledger", "catchup", "duration"})) |
353 | 381 | , mState(LM_BOOTING_STATE) |
| 382 | +#ifdef BUILD_TESTS |
| 383 | + , mTxLatencyMetrics(app.getMetrics()) |
| 384 | +#endif |
354 | 385 | { |
355 | 386 | // At this point, we haven't called assumeState yet, so the BucketLists are |
356 | 387 | // empty. We will create an "empty" snapshot that is not null, but |
@@ -1307,6 +1338,143 @@ LedgerManagerImpl::emitNextMeta() |
1307 | 1338 | mNextMetaToEmit.reset(); |
1308 | 1339 | } |
1309 | 1340 |
|
| 1341 | +#ifdef BUILD_TESTS |
| 1342 | +void |
| 1343 | +LedgerManagerImpl::recordTxSubmission(Hash const& contentsHash) |
| 1344 | +{ |
| 1345 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_E2E_LATENCY_FOR_TESTING) |
| 1346 | + { |
| 1347 | + return; |
| 1348 | + } |
| 1349 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1350 | + if (mTxLatencyMetrics.mTxSubmitTimes |
| 1351 | + .try_emplace(contentsHash, mApp.getClock().now()) |
| 1352 | + .second) |
| 1353 | + { |
| 1354 | + mTxLatencyMetrics.mTxsSubmitted.inc(); |
| 1355 | + } |
| 1356 | + else |
| 1357 | + { |
| 1358 | + CLOG_WARNING(Ledger, "Duplicate tx submission recorded for hash {}.", |
| 1359 | + binToHex(contentsHash)); |
| 1360 | + } |
| 1361 | +} |
| 1362 | + |
| 1363 | +void |
| 1364 | +LedgerManagerImpl::recordTxMetaEmissionLatency(LedgerCloseMeta const& lcm) |
| 1365 | +{ |
| 1366 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_E2E_LATENCY_FOR_TESTING) |
| 1367 | + { |
| 1368 | + return; |
| 1369 | + } |
| 1370 | + VirtualClock::time_point const emitTime = mApp.getClock().now(); |
| 1371 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1372 | + auto probe = |
| 1373 | + [&](auto const& txProcessing) REQUIRES(mTxLatencyMetrics.mMutex) { |
| 1374 | + for (auto const& txp : txProcessing) |
| 1375 | + { |
| 1376 | + if (auto submitted = mTxLatencyMetrics.mTxSubmitTimes.find( |
| 1377 | + txp.result.transactionHash); |
| 1378 | + submitted != mTxLatencyMetrics.mTxSubmitTimes.end()) |
| 1379 | + { |
| 1380 | + auto const latency = emitTime - submitted->second; |
| 1381 | + mTxLatencyMetrics.mTxsExternalized.inc(); |
| 1382 | + int64_t const ms = |
| 1383 | + std::chrono::duration_cast<std::chrono::milliseconds>( |
| 1384 | + latency) |
| 1385 | + .count(); |
| 1386 | + mTxLatencyMetrics.mSamples.push_back(std::clamp<int64_t>( |
| 1387 | + ms, 0, std::numeric_limits<uint32_t>::max())); |
| 1388 | + mTxLatencyMetrics.mTxSubmitTimes.erase(submitted); |
| 1389 | + } |
| 1390 | + } |
| 1391 | + }; |
| 1392 | + switch (lcm.v()) |
| 1393 | + { |
| 1394 | + case 0: |
| 1395 | + probe(lcm.v0().txProcessing); |
| 1396 | + break; |
| 1397 | + case 1: |
| 1398 | + probe(lcm.v1().txProcessing); |
| 1399 | + break; |
| 1400 | + case 2: |
| 1401 | + probe(lcm.v2().txProcessing); |
| 1402 | + break; |
| 1403 | + default: |
| 1404 | + releaseAssert(false); |
| 1405 | + } |
| 1406 | +} |
| 1407 | + |
| 1408 | +void |
| 1409 | +LedgerManagerImpl::beginTxLatencyMeasurement(uint32_t expectedTxCount) |
| 1410 | +{ |
| 1411 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_E2E_LATENCY_FOR_TESTING) |
| 1412 | + { |
| 1413 | + return; |
| 1414 | + } |
| 1415 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1416 | + if (!mTxLatencyMetrics.mTxSubmitTimes.empty()) |
| 1417 | + { |
| 1418 | + CLOG_WARNING(Ledger, |
| 1419 | + "Starting tx latency measurement with {} unmatched " |
| 1420 | + "submissions from prior run", |
| 1421 | + mTxLatencyMetrics.mTxSubmitTimes.size()); |
| 1422 | + mTxLatencyMetrics.mTxSubmitTimes.clear(); |
| 1423 | + } |
| 1424 | + mTxLatencyMetrics.mSamples.clear(); |
| 1425 | + mTxLatencyMetrics.mSamples.reserve(expectedTxCount); |
| 1426 | + |
| 1427 | + // Clear the per-run latency metrics. |
| 1428 | + mTxLatencyMetrics.mRunMin.clear(); |
| 1429 | + mTxLatencyMetrics.mRunMax.clear(); |
| 1430 | + mTxLatencyMetrics.mRunMean.clear(); |
| 1431 | + mTxLatencyMetrics.mRunP50.clear(); |
| 1432 | + mTxLatencyMetrics.mRunP75.clear(); |
| 1433 | + mTxLatencyMetrics.mRunP99.clear(); |
| 1434 | +} |
| 1435 | + |
| 1436 | +void |
| 1437 | +LedgerManagerImpl::finalizeTxLatencyMeasurement() |
| 1438 | +{ |
| 1439 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_E2E_LATENCY_FOR_TESTING) |
| 1440 | + { |
| 1441 | + return; |
| 1442 | + } |
| 1443 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1444 | + auto& samples = mTxLatencyMetrics.mSamples; |
| 1445 | + if (samples.empty()) |
| 1446 | + { |
| 1447 | + CLOG_WARNING(Ledger, |
| 1448 | + "Finalizing tx latency measurement with no samples"); |
| 1449 | + return; |
| 1450 | + } |
| 1451 | + std::sort(samples.begin(), samples.end()); |
| 1452 | + size_t const n = samples.size(); |
| 1453 | + |
| 1454 | + auto percentile = [&](size_t p) |
| 1455 | + REQUIRES(mTxLatencyMetrics.mMutex) -> uint32_t { |
| 1456 | + // Calculate ceiling of rank |
| 1457 | + size_t rank = (p * n + 99) / 100; |
| 1458 | + rank = std::clamp<size_t>(rank, 1, n); |
| 1459 | + return samples[rank - 1]; |
| 1460 | + }; |
| 1461 | + |
| 1462 | + uint64_t sum = 0; |
| 1463 | + for (uint32_t s : samples) |
| 1464 | + { |
| 1465 | + sum += s; |
| 1466 | + } |
| 1467 | + |
| 1468 | + mTxLatencyMetrics.mRunMin.set_count(samples.front()); |
| 1469 | + mTxLatencyMetrics.mRunMax.set_count(samples.back()); |
| 1470 | + // Mean rounded to the nearest millisecond. |
| 1471 | + mTxLatencyMetrics.mRunMean.set_count((sum + n / 2) / n); |
| 1472 | + mTxLatencyMetrics.mRunP50.set_count(percentile(50)); |
| 1473 | + mTxLatencyMetrics.mRunP75.set_count(percentile(75)); |
| 1474 | + mTxLatencyMetrics.mRunP99.set_count(percentile(99)); |
| 1475 | +} |
| 1476 | +#endif |
| 1477 | + |
1310 | 1478 | namespace |
1311 | 1479 | { |
1312 | 1480 | #ifdef BUILD_TESTS |
@@ -1782,6 +1950,7 @@ LedgerManagerImpl::applyLedger(LedgerCloseData const& ledgerData, |
1782 | 1950 | appliedLedgerState->getLastClosedLedgerHeader(); |
1783 | 1951 | // Copy this before we move it into mNextMetaToEmit below |
1784 | 1952 | mLastLedgerCloseMeta = *ledgerCloseMeta; |
| 1953 | + recordTxMetaEmissionLatency(ledgerCloseMeta->getXDR()); |
1785 | 1954 | } |
1786 | 1955 | #endif |
1787 | 1956 |
|
|
0 commit comments