Modernize CountedObject infrastructure

This commit is contained in:
Alphonse Mousse
2026-01-13 10:42:33 +01:00
parent db6e457851
commit b3f96aade0
7 changed files with 140 additions and 168 deletions

View File

@@ -1,75 +1,31 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#define RIPPLE_BASICS_COUNTEDOBJECT_H_INCLUDED
#include <xrpl/beast/type_name.h>
#include <atomic>
#include <cstddef>
#include <iterator>
#include <string>
#include <utility>
#include <vector>
namespace ripple {
/** Manages all counted object types. */
class CountedObjects
{
public:
static CountedObjects&
getInstance() noexcept;
using Entry = std::pair<std::string, int>;
using List = std::vector<Entry>;
List
getCounts(int minimumThreshold) const;
public:
/** Implementation for @ref CountedObject.
@internal
*/
class Counter
{
public:
Counter(std::string name) noexcept : name_(std::move(name)), count_(0)
{
// Insert ourselves at the front of the lock-free linked list
CountedObjects& instance = CountedObjects::getInstance();
Counter* head;
do
{
head = instance.m_head.load();
next_ = head;
} while (instance.m_head.exchange(this) != head);
++instance.m_count;
}
~Counter() noexcept = default;
Counter(std::string name) noexcept;
int
increment() noexcept
{
return ++count_;
auto const newCount = ++count_;
if (auto maxCount = maxCount_.load(); newCount > maxCount)
maxCount_.compare_exchange_strong(maxCount, newCount);
return newCount;
}
int
@@ -79,78 +35,136 @@ public:
}
int
getCount() const noexcept
count() const noexcept
{
return count_.load();
}
Counter*
getNext() const noexcept
int
max() const noexcept
{
return next_;
return std::max(count_.load(), maxCount_.load());
}
std::string const&
getName() const noexcept
name() const noexcept
{
return name_;
}
private:
std::string const name_;
std::atomic<int> count_;
friend class CountedObjects;
Counter* next_;
std::atomic<std::uint32_t> count_ = 0;
std::atomic<std::uint32_t> maxCount_ = 0;
std::string const name_;
};
private:
CountedObjects() noexcept;
~CountedObjects() noexcept = default;
class Iterator
{
public:
using value_type = Counter const;
using reference = value_type&;
using pointer = value_type*;
using difference_type = std::ptrdiff_t;
using iterator_category = std::forward_iterator_tag;
explicit Iterator(Counter* c = nullptr) noexcept : current_(c)
{
}
reference
operator*() const noexcept
{
return *current_;
}
pointer
operator->() const noexcept
{
return current_;
}
Iterator&
operator++() noexcept
{
current_ = current_->next_;
return *this;
}
Iterator
operator++(int) noexcept
{
auto tmp = *this;
++*this;
return tmp;
}
bool
operator==(Iterator const&) const noexcept = default;
private:
Counter* current_;
};
constexpr CountedObjects() noexcept = default;
auto
begin() const noexcept
{
return Iterator{head_.load()};
}
auto
end() const noexcept
{
return Iterator{};
}
private:
std::atomic<int> m_count;
std::atomic<Counter*> m_head;
friend class Counter;
std::atomic<Counter*> head_ = nullptr;
};
inline constinit CountedObjects countedObjects;
inline CountedObjects::Counter::Counter(std::string name) noexcept
: name_(std::move(name))
{
do
next_ = countedObjects.head_.load();
while (!countedObjects.head_.compare_exchange_weak(next_, this));
}
//------------------------------------------------------------------------------
/** Tracks the number of instances of an object.
Derived classes have their instances counted automatically. This is used
for reporting purposes.
@ingroup ripple_basics
*/
template <class Object>
class CountedObject
{
private:
static auto&
getCounter() noexcept
{
static CountedObjects::Counter c{beast::type_name<Object>()};
return c;
}
static CountedObjects::Counter counter_;
public:
CountedObject() noexcept
{
getCounter().increment();
counter_.increment();
}
CountedObject(CountedObject const&) noexcept
{
getCounter().increment();
counter_.increment();
}
CountedObject&
operator=(CountedObject const&) noexcept = default;
~CountedObject() noexcept
{
getCounter().decrement();
counter_.decrement();
}
};
// Instantiation of the static CountedObject<T>::counter_
template <class Object>
CountedObjects::Counter CountedObject<Object>::counter_{
beast::type_name<Object>()};
} // namespace ripple
#endif

View File

@@ -464,6 +464,7 @@ JSS(max_ledger); // in/out: LedgerCleaner
JSS(max_queue_size); // out: TxQ
JSS(max_spend_drops); // out: AccountInfo
JSS(max_spend_drops_total); // out: AccountInfo
JSS(maximum);
JSS(mean); // out: get_aggregate_price
JSS(median); // out: get_aggregate_price
JSS(median_fee); // out: TxQ

View File

@@ -1,58 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <xrpl/basics/CountedObject.h>
#include <algorithm>
#include <type_traits>
namespace ripple {
CountedObjects&
CountedObjects::getInstance() noexcept
{
static CountedObjects instance;
return instance;
}
CountedObjects::CountedObjects() noexcept : m_count(0), m_head(nullptr)
{
}
CountedObjects::List
CountedObjects::getCounts(int minimumThreshold) const
{
List counts;
// When other operations are concurrent, the count
// might be temporarily less than the actual count.
counts.reserve(m_count.load());
for (auto* ctr = m_head.load(); ctr != nullptr; ctr = ctr->getNext())
{
if (ctr->getCount() >= minimumThreshold)
counts.emplace_back(ctr->getName(), ctr->getCount());
}
std::sort(counts.begin(), counts.end());
return counts;
}
} // namespace ripple

View File

@@ -293,11 +293,11 @@ struct Regression_test : public beast::unit_test::suite
return digest.as_uint256();
}();
auto const mapCounts = [&](CountedObjects::List const& list) {
auto const mapCounts = [&]() {
std::map<std::string, int> result;
for (auto const& e : list)
for (auto const& e : countedObjects)
{
result[e.first] = e.second;
result[e.name()] = e.count();
}
return result;
@@ -308,14 +308,12 @@ struct Regression_test : public beast::unit_test::suite
{
auto& cache = env.app().cachedSLEs();
cache.del(*digest, false);
auto const beforeCounts =
mapCounts(CountedObjects::getInstance().getCounts(0));
auto const beforeCounts = mapCounts();
env(check::cash(alice, bob_index, check::DeliverMin(XRP(100))),
ter(tecNO_ENTRY));
auto const afterCounts =
mapCounts(CountedObjects::getInstance().getCounts(0));
auto const afterCounts = mapCounts();
using namespace std::string_literals;
BEAST_EXPECT(

View File

@@ -61,17 +61,26 @@ class GetCounts_test : public beast::unit_test::suite
env.close();
}
auto getCountedObjects = [](int minimumCount) {
std::vector<std::pair<std::string, int>> result;
for (auto const& c : countedObjects)
if (c.count() >= minimumCount)
result.emplace_back(c.name(), c.count());
return result;
};
{
// check counts, default params
result = env.rpc("get_counts")[jss::result];
BEAST_EXPECT(result[jss::status] == "success");
// compare with values reported by CountedObjects
auto const& objectCounts =
CountedObjects::getInstance().getCounts(10);
for (auto const& it : objectCounts)
for (auto const& it : getCountedObjects(10))
{
BEAST_EXPECTS(result.isMember(it.first), it.first);
BEAST_EXPECTS(result[it.first].asInt() == it.second, it.first);
BEAST_EXPECTS(
result[it.first][jss::current].asInt() == it.second,
it.first);
}
BEAST_EXPECT(!result.isMember(jss::local_txs));
}
@@ -81,14 +90,12 @@ class GetCounts_test : public beast::unit_test::suite
// that only STObject and NodeObject are reported
result = env.rpc("get_counts", "100")[jss::result];
BEAST_EXPECT(result[jss::status] == "success");
// compare with values reported by CountedObjects
auto const& objectCounts =
CountedObjects::getInstance().getCounts(100);
for (auto const& it : objectCounts)
for (auto const& it : getCountedObjects(100))
{
BEAST_EXPECTS(result.isMember(it.first), it.first);
BEAST_EXPECTS(result[it.first].asInt() == it.second, it.first);
BEAST_EXPECTS(
result[it.first][jss::current].asInt() == it.second,
it.first);
}
BEAST_EXPECT(!result.isMember("Transaction"));
BEAST_EXPECT(!result.isMember("STTx"));

View File

@@ -408,8 +408,6 @@ private:
getDebugCounters()
{
DebugCounters counters;
ObjectCountMap objectCounts =
CountedObjects::getInstance().getCounts(1);
// Database metrics if applicable
if (app_.config().useTxTables())
@@ -456,7 +454,15 @@ private:
counters.nodeFetchHitCount = app_.getNodeStore().getFetchHitCount();
counters.nodeFetchSize = app_.getNodeStore().getFetchSize();
return {counters, objectCounts};
return {counters, []() {
std::vector<std::pair<std::string, int>> result;
for (auto const& c : countedObjects)
if (c.count())
result.emplace_back(c.name(), c.count());
return result;
}()};
}
uint32_t

View File

@@ -62,13 +62,17 @@ textTime(
Json::Value
getCountsJson(Application& app, int minObjectCount)
{
auto objectCounts = CountedObjects::getInstance().getCounts(minObjectCount);
Json::Value ret(Json::objectValue);
for (auto const& [k, v] : objectCounts)
for (auto const& c : countedObjects)
{
ret[k] = v;
if (c.count() >= minObjectCount)
{
Json::Value obj(Json::objectValue);
obj[jss::current] = c.count();
obj[jss::maximum] = c.max();
ret[c.name()] = std::move(obj);
}
}
if (app.config().useTxTables())