|
74 | 74 | #include <Tracy.hpp> |
75 | 75 |
|
76 | 76 | #include "LedgerManagerImpl.h" |
| 77 | +#include <algorithm> |
77 | 78 | #include <chrono> |
| 79 | +#include <limits> |
78 | 80 | #include <memory> |
79 | 81 | #include <optional> |
80 | 82 | #include <regex> |
@@ -226,6 +228,22 @@ LedgerManagerImpl::LedgerApplyMetrics::LedgerApplyMetrics( |
226 | 228 | { |
227 | 229 | } |
228 | 230 |
|
| 231 | +#ifdef BUILD_TESTS |
| 232 | +LedgerManagerImpl::TxLatencyMetrics::TxLatencyMetrics(MetricsRegistry& registry) |
| 233 | + : mTxsSubmitted(registry.NewCounter({"loadgen", "tx-latency", "submitted"})) |
| 234 | + , mTxsExternalized( |
| 235 | + registry.NewCounter({"loadgen", "tx-latency", "externalized"})) |
| 236 | + , mLatencyTimer(registry.NewTimer({"loadgen", "tx-latency", "duration"})) |
| 237 | + , mRunMin(registry.NewCounter({"loadgen", "tx-latency-run", "min-ms"})) |
| 238 | + , mRunMax(registry.NewCounter({"loadgen", "tx-latency-run", "max-ms"})) |
| 239 | + , mRunMean(registry.NewCounter({"loadgen", "tx-latency-run", "mean-ms"})) |
| 240 | + , mRunP50(registry.NewCounter({"loadgen", "tx-latency-run", "p50-ms"})) |
| 241 | + , mRunP75(registry.NewCounter({"loadgen", "tx-latency-run", "p75-ms"})) |
| 242 | + , mRunP99(registry.NewCounter({"loadgen", "tx-latency-run", "p99-ms"})) |
| 243 | +{ |
| 244 | +} |
| 245 | +#endif |
| 246 | + |
229 | 247 | LedgerManagerImpl::ApplyState::ApplyState(Application& app) |
230 | 248 | : mMetrics(app.getMetrics()) |
231 | 249 | , mAppConnector(app.getAppConnector()) |
@@ -349,6 +367,9 @@ LedgerManagerImpl::LedgerManagerImpl(Application& app) |
349 | 367 | , mCatchupDuration( |
350 | 368 | app.getMetrics().NewTimer({"ledger", "catchup", "duration"})) |
351 | 369 | , mState(LM_BOOTING_STATE) |
| 370 | +#ifdef BUILD_TESTS |
| 371 | + , mTxLatencyMetrics(app.getMetrics()) |
| 372 | +#endif |
352 | 373 | { |
353 | 374 | // At this point, we haven't called assumeState yet, so the BucketLists are |
354 | 375 | // empty. We will create an "empty" snapshot that is not null, but |
@@ -1308,6 +1329,142 @@ LedgerManagerImpl::emitNextMeta() |
1308 | 1329 | mNextMetaToEmit.reset(); |
1309 | 1330 | } |
1310 | 1331 |
|
| 1332 | +#ifdef BUILD_TESTS |
| 1333 | +void |
| 1334 | +LedgerManagerImpl::recordTxSubmission(Hash const& contentsHash) |
| 1335 | +{ |
| 1336 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_LATENCY_FOR_TESTING) |
| 1337 | + { |
| 1338 | + return; |
| 1339 | + } |
| 1340 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1341 | + if (mTxLatencyMetrics.mTxSubmitTimes |
| 1342 | + .try_emplace(contentsHash, mApp.getClock().now()) |
| 1343 | + .second) |
| 1344 | + { |
| 1345 | + mTxLatencyMetrics.mTxsSubmitted.inc(); |
| 1346 | + } |
| 1347 | + else |
| 1348 | + { |
| 1349 | + CLOG_WARNING(Ledger, "Duplicate tx submission recorded for hash {}.", |
| 1350 | + binToHex(contentsHash)); |
| 1351 | + } |
| 1352 | +} |
| 1353 | + |
| 1354 | +void |
| 1355 | +LedgerManagerImpl::recordTxMetaEmissionLatency(LedgerCloseMeta const& lcm) |
| 1356 | +{ |
| 1357 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_LATENCY_FOR_TESTING) |
| 1358 | + { |
| 1359 | + return; |
| 1360 | + } |
| 1361 | + VirtualClock::time_point const emitTime = mApp.getClock().now(); |
| 1362 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1363 | + auto probe = [&](auto const& txProcessing) { |
| 1364 | + for (auto const& txp : txProcessing) |
| 1365 | + { |
| 1366 | + if (auto submitted = mTxLatencyMetrics.mTxSubmitTimes.find( |
| 1367 | + txp.result.transactionHash); |
| 1368 | + submitted != mTxLatencyMetrics.mTxSubmitTimes.end()) |
| 1369 | + { |
| 1370 | + auto const latency = emitTime - submitted->second; |
| 1371 | + mTxLatencyMetrics.mTxsExternalized.inc(); |
| 1372 | + mTxLatencyMetrics.mLatencyTimer.Update(latency); |
| 1373 | + int64_t const ms = |
| 1374 | + std::chrono::duration_cast<std::chrono::milliseconds>( |
| 1375 | + latency) |
| 1376 | + .count(); |
| 1377 | + mTxLatencyMetrics.mSamples.push_back(std::clamp<int64_t>( |
| 1378 | + ms, 0, std::numeric_limits<uint32_t>::max())); |
| 1379 | + mTxLatencyMetrics.mTxSubmitTimes.erase(submitted); |
| 1380 | + } |
| 1381 | + } |
| 1382 | + }; |
| 1383 | + switch (lcm.v()) |
| 1384 | + { |
| 1385 | + case 0: |
| 1386 | + probe(lcm.v0().txProcessing); |
| 1387 | + break; |
| 1388 | + case 1: |
| 1389 | + probe(lcm.v1().txProcessing); |
| 1390 | + break; |
| 1391 | + case 2: |
| 1392 | + probe(lcm.v2().txProcessing); |
| 1393 | + break; |
| 1394 | + default: |
| 1395 | + releaseAssert(false); |
| 1396 | + } |
| 1397 | +} |
| 1398 | + |
| 1399 | +void |
| 1400 | +LedgerManagerImpl::beginTxLatencyMeasurement(uint32_t expectedTxCount) |
| 1401 | +{ |
| 1402 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_LATENCY_FOR_TESTING) |
| 1403 | + { |
| 1404 | + return; |
| 1405 | + } |
| 1406 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1407 | + if (!mTxLatencyMetrics.mTxSubmitTimes.empty()) |
| 1408 | + { |
| 1409 | + CLOG_WARNING(Ledger, |
| 1410 | + "Starting tx latency measurement with {} unmatched " |
| 1411 | + "submissions from prior run", |
| 1412 | + mTxLatencyMetrics.mTxSubmitTimes.size()); |
| 1413 | + mTxLatencyMetrics.mTxSubmitTimes.clear(); |
| 1414 | + } |
| 1415 | + mTxLatencyMetrics.mSamples.clear(); |
| 1416 | + mTxLatencyMetrics.mSamples.reserve(expectedTxCount); |
| 1417 | + |
| 1418 | + // Clear the per-run latency metrics. |
| 1419 | + mTxLatencyMetrics.mRunMin.clear(); |
| 1420 | + mTxLatencyMetrics.mRunMax.clear(); |
| 1421 | + mTxLatencyMetrics.mRunMean.clear(); |
| 1422 | + mTxLatencyMetrics.mRunP50.clear(); |
| 1423 | + mTxLatencyMetrics.mRunP75.clear(); |
| 1424 | + mTxLatencyMetrics.mRunP99.clear(); |
| 1425 | +} |
| 1426 | + |
| 1427 | +void |
| 1428 | +LedgerManagerImpl::finalizeTxLatencyMeasurement() |
| 1429 | +{ |
| 1430 | + if (!mApp.getConfig().LOADGEN_MEASURE_TX_LATENCY_FOR_TESTING) |
| 1431 | + { |
| 1432 | + return; |
| 1433 | + } |
| 1434 | + MutexLocker guard(mTxLatencyMetrics.mMutex); |
| 1435 | + auto& samples = mTxLatencyMetrics.mSamples; |
| 1436 | + if (samples.empty()) |
| 1437 | + { |
| 1438 | + CLOG_WARNING(Ledger, |
| 1439 | + "Finalizing tx latency measurement with no samples"); |
| 1440 | + return; |
| 1441 | + } |
| 1442 | + std::sort(samples.begin(), samples.end()); |
| 1443 | + size_t const n = samples.size(); |
| 1444 | + |
| 1445 | + auto percentile = [&](size_t p) -> uint32_t { |
| 1446 | + // Calculate ceiling of rank |
| 1447 | + size_t rank = (p * n + 99) / 100; |
| 1448 | + rank = std::clamp<size_t>(rank, 1, n); |
| 1449 | + return samples[rank - 1]; |
| 1450 | + }; |
| 1451 | + |
| 1452 | + uint64_t sum = 0; |
| 1453 | + for (uint32_t s : samples) |
| 1454 | + { |
| 1455 | + sum += s; |
| 1456 | + } |
| 1457 | + |
| 1458 | + mTxLatencyMetrics.mRunMin.set_count(samples.front()); |
| 1459 | + mTxLatencyMetrics.mRunMax.set_count(samples.back()); |
| 1460 | + // Mean rounded to the nearest millisecond. |
| 1461 | + mTxLatencyMetrics.mRunMean.set_count((sum + n / 2) / n); |
| 1462 | + mTxLatencyMetrics.mRunP50.set_count(percentile(50)); |
| 1463 | + mTxLatencyMetrics.mRunP75.set_count(percentile(75)); |
| 1464 | + mTxLatencyMetrics.mRunP99.set_count(percentile(99)); |
| 1465 | +} |
| 1466 | +#endif |
| 1467 | + |
1311 | 1468 | namespace |
1312 | 1469 | { |
1313 | 1470 | #ifdef BUILD_TESTS |
@@ -1773,6 +1930,7 @@ LedgerManagerImpl::applyLedger(LedgerCloseData const& ledgerData, |
1773 | 1930 | appliedLedgerState->getLastClosedLedgerHeader(); |
1774 | 1931 | // Copy this before we move it into mNextMetaToEmit below |
1775 | 1932 | mLastLedgerCloseMeta = *ledgerCloseMeta; |
| 1933 | + recordTxMetaEmissionLatency(ledgerCloseMeta->getXDR()); |
1776 | 1934 | } |
1777 | 1935 | #endif |
1778 | 1936 |
|
|
0 commit comments