Skip to content

Commit a8d0c5f

Browse files
committed
Shared metric object for bucket list snapshots
1 parent a8bed8e commit a8d0c5f

5 files changed

Lines changed: 106 additions & 43 deletions

File tree

src/bucket/BucketListSnapshot.cpp

Lines changed: 48 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -53,27 +53,44 @@ BucketListSnapshotData<BucketT>::BucketListSnapshotData(
5353
{
5454
}
5555

56+
//
57+
// BucketSnapshotMetrics
58+
//
59+
60+
template <class BucketT>
61+
BucketSnapshotMetrics<BucketT>::BucketSnapshotMetrics(MetricsRegistry& metrics)
62+
: mPointTimers([&metrics]() {
63+
UnorderedMap<LedgerEntryType, std::reference_wrapper<SimpleTimer>>
64+
timers;
65+
for (auto t : xdr::xdr_traits<LedgerEntryType>::enum_values())
66+
{
67+
auto const& label = xdr::xdr_traits<LedgerEntryType>::enum_name(
68+
static_cast<LedgerEntryType>(t));
69+
auto& metric = metrics.NewSimpleTimer(
70+
{BucketT::METRIC_STRING, label}, std::chrono::microseconds{1});
71+
timers.emplace(static_cast<LedgerEntryType>(t), metric);
72+
}
73+
return timers;
74+
}())
75+
, mBulkLoadMeter(
76+
metrics.NewMeter({BucketT::METRIC_STRING, "query", "loads"}, "query"))
77+
{
78+
}
79+
5680
//
5781
// SearchableBucketListSnapshot
5882
//
5983

6084
template <class BucketT>
6185
SearchableBucketListSnapshot<BucketT>::SearchableBucketListSnapshot(
6286
MetricsRegistry& metrics,
87+
std::shared_ptr<BucketSnapshotMetrics<BucketT> const> snapshotMetrics,
6388
std::shared_ptr<BucketListSnapshotData<BucketT> const> data)
6489
: mData(std::move(data))
6590
, mMetrics(metrics)
66-
, mBulkLoadMeter(
67-
metrics.NewMeter({BucketT::METRIC_STRING, "query", "loads"}, "query"))
91+
, mSnapshotMetrics(std::move(snapshotMetrics))
6892
{
69-
for (auto t : xdr::xdr_traits<LedgerEntryType>::enum_values())
70-
{
71-
auto const& label = xdr::xdr_traits<LedgerEntryType>::enum_name(
72-
static_cast<LedgerEntryType>(t));
73-
auto& metric = metrics.NewSimpleTimer({BucketT::METRIC_STRING, label},
74-
std::chrono::microseconds{1});
75-
mPointTimers.emplace(static_cast<LedgerEntryType>(t), metric);
76-
}
93+
releaseAssert(mSnapshotMetrics);
7794
}
7895

7996
template <class BucketT>
@@ -82,9 +99,8 @@ SearchableBucketListSnapshot<BucketT>::SearchableBucketListSnapshot(
8299
: mData(other.mData)
83100
// mStreams intentionally left empty — each copy gets its own stream cache
84101
, mMetrics(other.mMetrics)
85-
, mPointTimers(other.mPointTimers)
102+
, mSnapshotMetrics(other.mSnapshotMetrics)
86103
, mBulkTimers(other.mBulkTimers)
87-
, mBulkLoadMeter(other.mBulkLoadMeter)
88104
{
89105
}
90106

@@ -98,12 +114,11 @@ SearchableBucketListSnapshot<BucketT>::operator=(
98114
mData = other.mData;
99115
mStreams.clear();
100116
mMetrics = other.mMetrics;
101-
mPointTimers = other.mPointTimers;
117+
mSnapshotMetrics = other.mSnapshotMetrics;
102118
mBulkTimers = other.mBulkTimers;
103-
mBulkLoadMeter = other.mBulkLoadMeter;
104119
#ifdef BUILD_TESTS
105120
// Reset thread ownership so the copy can be claimed by another thread.
106-
mThreadId = std::thread::id{};
121+
mThreadId.store(std::thread::id{});
107122
#endif
108123
}
109124
return *this;
@@ -119,14 +134,13 @@ void
119134
SearchableBucketListSnapshot<BucketT>::threadInvariant() const
120135
{
121136
#ifdef BUILD_TESTS
122-
auto current = std::this_thread::get_id();
123-
if (mThreadId == std::thread::id{})
124-
{
125-
mThreadId = current;
126-
}
127-
else
137+
auto const current = std::this_thread::get_id();
138+
std::thread::id unclaimed{};
139+
// Atomically claim ownership on first use, so any concurrent claimant sees
140+
// the CAS fail with `unclaimed` set to the owner's id and asserts.
141+
if (!mThreadId.compare_exchange_strong(unclaimed, current))
128142
{
129-
releaseAssert(mThreadId == current);
143+
releaseAssert(unclaimed == current);
130144
}
131145
#endif
132146
}
@@ -336,8 +350,8 @@ SearchableBucketListSnapshot<BucketT>::load(LedgerKey const& k) const
336350
releaseAssert(mData);
337351
threadInvariant();
338352

339-
auto timerIter = mPointTimers.find(k.type());
340-
releaseAssert(timerIter != mPointTimers.end());
353+
auto timerIter = mSnapshotMetrics->mPointTimers.find(k.type());
354+
releaseAssert(timerIter != mSnapshotMetrics->mPointTimers.end());
341355
auto timer = timerIter->second.get().TimeScope();
342356

343357
std::shared_ptr<typename BucketT::LoadT const> result{};
@@ -375,7 +389,7 @@ SearchableBucketListSnapshot<BucketT>::getBulkLoadTimer(
375389
threadInvariant();
376390
if (numEntries != 0)
377391
{
378-
mBulkLoadMeter.get().Mark(numEntries);
392+
mSnapshotMetrics->mBulkLoadMeter.get().Mark(numEntries);
379393
}
380394

381395
auto iter = mBulkTimers.find(label);
@@ -402,8 +416,10 @@ SearchableBucketListSnapshot<BucketT>::getSnapshotData() const
402416

403417
SearchableLiveBucketListSnapshot::SearchableLiveBucketListSnapshot(
404418
MetricsRegistry& metrics,
419+
std::shared_ptr<BucketSnapshotMetrics<LiveBucket> const> snapshotMetrics,
405420
std::shared_ptr<BucketListSnapshotData<LiveBucket> const> data)
406-
: SearchableBucketListSnapshot<LiveBucket>(metrics, std::move(data))
421+
: SearchableBucketListSnapshot<LiveBucket>(
422+
metrics, std::move(snapshotMetrics), std::move(data))
407423
{
408424
}
409425

@@ -843,8 +859,11 @@ SearchableLiveBucketListSnapshot::scanForEvictionInBucket(
843859

844860
SearchableHotArchiveBucketListSnapshot::SearchableHotArchiveBucketListSnapshot(
845861
MetricsRegistry& metrics,
862+
std::shared_ptr<BucketSnapshotMetrics<HotArchiveBucket> const>
863+
snapshotMetrics,
846864
std::shared_ptr<BucketListSnapshotData<HotArchiveBucket> const> data)
847-
: SearchableBucketListSnapshot<HotArchiveBucket>(metrics, std::move(data))
865+
: SearchableBucketListSnapshot<HotArchiveBucket>(
866+
metrics, std::move(snapshotMetrics), std::move(data))
848867
{
849868
}
850869

@@ -879,6 +898,8 @@ SearchableHotArchiveBucketListSnapshot::scanAllEntries(
879898
// Explicit template instantiations
880899
template struct BucketListSnapshotData<LiveBucket>;
881900
template struct BucketListSnapshotData<HotArchiveBucket>;
901+
template struct BucketSnapshotMetrics<LiveBucket>;
902+
template struct BucketSnapshotMetrics<HotArchiveBucket>;
882903
template class SearchableBucketListSnapshot<LiveBucket>;
883904
template class SearchableBucketListSnapshot<HotArchiveBucket>;
884905

src/bucket/BucketListSnapshot.h

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "util/XDRStream.h"
1515
#include "xdr/Stellar-ledger-entries.h"
1616

17+
#include <atomic>
1718
#include <functional>
1819
#include <list>
1920
#include <memory>
@@ -65,6 +66,24 @@ template <class BucketT> struct BucketListSnapshotData
6566
explicit BucketListSnapshotData(BucketListBase<BucketT> const& bl);
6667
};
6768

69+
// Pre-resolved metric references for snapshot queries. Resolving a metric
70+
// takes the global MetricsRegistry lock, so metrics are resolved once and
71+
// shared across snapshots rather than re-resolved in every
72+
// SearchableBucketListSnapshot constructor.
73+
template <class BucketT> struct BucketSnapshotMetrics
74+
{
75+
BUCKET_TYPE_ASSERT(BucketT);
76+
77+
// Tracks load times for each LedgerEntryType. We use
78+
// SimpleTimer since medida Timer overhead is too expensive for point
79+
// loads.
80+
UnorderedMap<LedgerEntryType, std::reference_wrapper<SimpleTimer>> const
81+
mPointTimers;
82+
std::reference_wrapper<medida::Meter> const mBulkLoadMeter;
83+
84+
explicit BucketSnapshotMetrics(MetricsRegistry& metrics);
85+
};
86+
6887
// SearchableBucketListSnapshot provides BucketList lookup functionality.
6988
// Each snapshot maintains its own stream cache for file I/O and a pointer to
7089
// immutable snapshot data (ledger header, list of referenced buckets, etc).
@@ -96,7 +115,7 @@ template <class BucketT> class SearchableBucketListSnapshot
96115
// Used by threadInvariant() to assert that a single snapshot is not queried
97116
// concurrently from multiple threads. Reset on copy so each copy can be
98117
// claimed by a different thread.
99-
mutable std::thread::id mThreadId{};
118+
mutable std::atomic<std::thread::id> mThreadId{};
100119
#endif
101120

102121
// Bucket loads are not thread safe and a single snapshot instance should
@@ -106,16 +125,15 @@ template <class BucketT> class SearchableBucketListSnapshot
106125

107126
std::reference_wrapper<MetricsRegistry> mMetrics;
108127

109-
// Tracks load times for each LedgerEntryType. We use
110-
// SimpleTimer since medida Timer overhead is too expensive for point loads.
111-
UnorderedMap<LedgerEntryType, std::reference_wrapper<SimpleTimer>>
112-
mPointTimers;
128+
// Pre-resolved point load timers and bulk load meter, shared across
129+
// snapshots (see BucketSnapshotMetrics).
130+
std::shared_ptr<BucketSnapshotMetrics<BucketT> const> mSnapshotMetrics;
113131

114132
// Bulk load timers take significantly longer, so the timer overhead is
115-
// comparatively negligible.
133+
// comparatively negligible. Resolved lazily per label, so kept
134+
// per-instance rather than in BucketSnapshotMetrics.
116135
mutable UnorderedMap<std::string, std::reference_wrapper<medida::Timer>>
117136
mBulkTimers;
118-
std::reference_wrapper<medida::Meter> mBulkLoadMeter;
119137

120138
// Returns (lazily-constructed) file stream for bucket file. Note
121139
// this might be in some random position left over from a previous read --
@@ -156,6 +174,7 @@ template <class BucketT> class SearchableBucketListSnapshot
156174

157175
SearchableBucketListSnapshot(
158176
MetricsRegistry& metrics,
177+
std::shared_ptr<BucketSnapshotMetrics<BucketT> const> snapshotMetrics,
159178
std::shared_ptr<BucketListSnapshotData<BucketT> const> data);
160179

161180
public:
@@ -190,6 +209,8 @@ class SearchableLiveBucketListSnapshot
190209
{
191210
SearchableLiveBucketListSnapshot(
192211
MetricsRegistry& metrics,
212+
std::shared_ptr<BucketSnapshotMetrics<LiveBucket> const>
213+
snapshotMetrics,
193214
std::shared_ptr<BucketListSnapshotData<LiveBucket> const> data);
194215

195216
Loop scanForEvictionInBucket(
@@ -230,6 +251,8 @@ class SearchableHotArchiveBucketListSnapshot
230251
{
231252
SearchableHotArchiveBucketListSnapshot(
232253
MetricsRegistry& metrics,
254+
std::shared_ptr<BucketSnapshotMetrics<HotArchiveBucket> const>
255+
snapshotMetrics,
233256
std::shared_ptr<BucketListSnapshotData<HotArchiveBucket> const> data);
234257

235258
public:
@@ -246,6 +269,8 @@ class SearchableHotArchiveBucketListSnapshot
246269

247270
extern template struct BucketListSnapshotData<LiveBucket>;
248271
extern template struct BucketListSnapshotData<HotArchiveBucket>;
272+
extern template struct BucketSnapshotMetrics<LiveBucket>;
273+
extern template struct BucketSnapshotMetrics<HotArchiveBucket>;
249274
extern template class SearchableBucketListSnapshot<LiveBucket>;
250275
extern template class SearchableBucketListSnapshot<HotArchiveBucket>;
251276

src/ledger/ImmutableLedgerView.cpp

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,12 +225,16 @@ ImmutableLedgerData::checkInvariant() const
225225
ImmutableLedgerData::ImmutableLedgerData(
226226
LiveBucketList const& liveBL, HotArchiveBucketList const& hotArchiveBL,
227227
LedgerHeaderHistoryEntry const& lcl, HistoryArchiveState const& has,
228-
std::optional<SorobanNetworkConfig> sorobanConfig)
228+
std::optional<SorobanNetworkConfig> sorobanConfig, MetricsRegistry& metrics)
229229
: mLiveBucketData(
230230
std::make_shared<BucketListSnapshotData<LiveBucket>>(liveBL))
231231
, mHotArchiveBucketData(
232232
std::make_shared<BucketListSnapshotData<HotArchiveBucket>>(
233233
hotArchiveBL))
234+
, mLiveSnapshotMetrics(
235+
std::make_shared<BucketSnapshotMetrics<LiveBucket>>(metrics))
236+
, mHotArchiveSnapshotMetrics(
237+
std::make_shared<BucketSnapshotMetrics<HotArchiveBucket>>(metrics))
234238
, mSorobanConfig(std::move(sorobanConfig))
235239
, mLastClosedLedgerHeader(lcl)
236240
, mLastClosedHistoryArchiveState(has)
@@ -275,19 +279,22 @@ ImmutableLedgerData::createAndMaybeLoadConfig(
275279
// Bootstrap: build a lightweight temporary state just to load config
276280
// from the current live bucket list.
277281
auto tempState = std::make_shared<ImmutableLedgerData>(
278-
liveBL, hotArchiveBL, lcl, has, /*sorobanConfig*/ std::nullopt);
282+
liveBL, hotArchiveBL, lcl, has, /*sorobanConfig*/ std::nullopt,
283+
metrics);
279284
ImmutableLedgerView tempView(tempState, metrics);
280285
sorobanConfig = SorobanNetworkConfig::loadFromLedger(tempView);
281286
}
282-
return std::make_shared<ImmutableLedgerData>(liveBL, hotArchiveBL, lcl, has,
283-
std::move(sorobanConfig));
287+
return std::make_shared<ImmutableLedgerData>(
288+
liveBL, hotArchiveBL, lcl, has, std::move(sorobanConfig), metrics);
284289
}
285290

286291
ImmutableLedgerView::ImmutableLedgerView(ImmutableLedgerDataPtr state,
287292
MetricsRegistry& metrics)
288293
: mState(state)
289-
, mLiveSnapshot(metrics, state->mLiveBucketData)
290-
, mHotArchiveSnapshot(metrics, state->mHotArchiveBucketData)
294+
, mLiveSnapshot(metrics, state->mLiveSnapshotMetrics,
295+
state->mLiveBucketData)
296+
, mHotArchiveSnapshot(metrics, state->mHotArchiveSnapshotMetrics,
297+
state->mHotArchiveBucketData)
291298
, mMetrics(metrics)
292299
{
293300
}

src/ledger/ImmutableLedgerView.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,6 +279,15 @@ class ImmutableLedgerData : public NonMovableOrCopyable
279279
std::shared_ptr<BucketListSnapshotData<HotArchiveBucket> const> const
280280
mHotArchiveBucketData;
281281

282+
// Pre-resolved metric references shared by all views over this state.
283+
// Resolving metrics takes the global registry lock, so they are resolved
284+
// once here instead of in every view construction, which is on the
285+
// per-transaction hot path.
286+
std::shared_ptr<BucketSnapshotMetrics<LiveBucket> const> const
287+
mLiveSnapshotMetrics;
288+
std::shared_ptr<BucketSnapshotMetrics<HotArchiveBucket> const> const
289+
mHotArchiveSnapshotMetrics;
290+
282291
std::optional<SorobanNetworkConfig const> const mSorobanConfig;
283292
LedgerHeaderHistoryEntry const mLastClosedLedgerHeader;
284293
HistoryArchiveState const mLastClosedHistoryArchiveState;
@@ -295,7 +304,8 @@ class ImmutableLedgerData : public NonMovableOrCopyable
295304
HotArchiveBucketList const& hotArchiveBL,
296305
LedgerHeaderHistoryEntry const& lcl,
297306
HistoryArchiveState const& has,
298-
std::optional<SorobanNetworkConfig> sorobanConfig);
307+
std::optional<SorobanNetworkConfig> sorobanConfig,
308+
MetricsRegistry& metrics);
299309

300310
// Factory: constructs a ImmutableLedgerData, auto-loading the
301311
// SorobanNetworkConfig from the bucket list when the protocol requires it.

src/ledger/LedgerManagerImpl.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ LedgerManagerImpl::LedgerManagerImpl(Application& app)
360360

361361
auto initialState = std::make_shared<ImmutableLedgerData>(
362362
bm.getLiveBucketList(), bm.getHotArchiveBucketList(), emptyLcl,
363-
emptyHas, /*sorobanConfig*/ std::nullopt);
363+
emptyHas, /*sorobanConfig*/ std::nullopt, mApp.getMetrics());
364364

365365
mApplyState.setLedgerState(initialState);
366366
{
@@ -2135,7 +2135,7 @@ LedgerManagerImpl::buildLedgerState(
21352135
// Caller already loaded config (e.g. from LTX during ledger close)
21362136
return std::make_shared<ImmutableLedgerData>(
21372137
bm.getLiveBucketList(), bm.getHotArchiveBucketList(), lcl, has,
2138-
std::move(sorobanConfig));
2138+
std::move(sorobanConfig), mApp.getMetrics());
21392139
}
21402140

21412141
// Auto-load SorobanNetworkConfig from the BucketList

0 commit comments

Comments
 (0)