Skip to content

Commit fcda14f

Browse files
Merge #6837: feat(stats): add support for IPv6, introduce functional tests, deprecate -statsport
e607836 docs: add release notes (Kittywhiskers Van Gogh) b50c7f2 stats: add functional test for statsd reporting (Kittywhiskers Van Gogh) 592aa04 stats: add support for URLs in `statshost`, deprecate `statsport` (Kittywhiskers Van Gogh) 3640071 stats: extend connection support to IPv6 (Kittywhiskers Van Gogh) 131d536 stats: use `bilingual_str` for RawSender error messages (Kittywhiskers Van Gogh) fced9f6 stats: add stricter validation for arguments (Kittywhiskers Van Gogh) 8d7e74f stats: bubble errors up, halt init if stats client reports errors (Kittywhiskers Van Gogh) 1508f4a stats: use string_view for fixed values (Kittywhiskers Van Gogh) 9e9ee35 stats: move implementation to source file, define interface (Kittywhiskers Van Gogh) Pull request description: ## Additional Information * Support for URLs have been introduced for `statshost` and can be formatted as `udp://127.0.0.1:8125`. As `statshost` can also include the port, `statsport` has been deprecated and will be removed in a future release of Dash Core. * Startup validation errors are no longer ignored and trigger an `InitError()`. Connection failure will **not** halt startup but lookup failure **will**, this is because `LookupHost()` is used to identify if the connection is IPv4/IPv6. ## Breaking Changes See release notes. ## Checklist - [x] I have performed a self-review of my own code - [x] I have commented my code, particularly in hard-to-understand areas - [x] I have added or updated relevant unit/integration/functional/e2e tests - [x] I have made corresponding changes to the documentation - [x] I have assigned this pull request to a milestone _(for repository code-owners and collaborators only)_ ACKs for top commit: UdjinM6: utACK e607836 Tree-SHA512: 69d0ed16bc0a23cc960dc9b550919677dd59dcff9fb5145ae1da9f99146b0e65d300044d9628f72ac058fab68b321b1dd04dfd3e80773bfcce72efd85a77d660
2 parents 8e11b9a + e607836 commit fcda14f

File tree

10 files changed

+367
-117
lines changed

10 files changed

+367
-117
lines changed

doc/release-notes-6837.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Statistics
2+
----------
3+
4+
- IPv6 hosts are now supported by the StatsD client.
5+
6+
- `-statshost` now accepts URLs to allow specifying the protocol, host and port in one argument.
7+
8+
- Specifying invalid values will no longer result in silent disablement of the StatsD client and will now cause errors
9+
at startup.
10+
11+
### Deprecations
12+
13+
- `-statsport` has been deprecated and ports are now specified using the new URL syntax supported by `-statshost`.
14+
`-statsport` will be removed in a future release.
15+
16+
- If both `-statsport` and `-statshost` with a URL specifying a port is supplied, the `-statsport` value will be
17+
ignored.

src/bench/checkblock.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
#include <streams.h>
1212
#include <validation.h>
1313

14+
#include <memory>
15+
1416
// These are the two major time-sinks which happen after we have fully received
1517
// a block off the wire, but before we can relay the block on to peers using
1618
// compact block relay.
@@ -38,8 +40,8 @@ static void DeserializeAndCheckBlockTest(benchmark::Bench& bench)
3840
ArgsManager bench_args;
3941
const auto chainParams = CreateChainParams(bench_args, CBaseChainParams::MAIN);
4042
// CheckBlock calls g_stats_client internally, we aren't using a testing setup
41-
// so we need to do this manually.
42-
::g_stats_client = InitStatsClient(bench_args);
43+
// so we need to do this manually. We can use the stub interface for this.
44+
::g_stats_client = std::make_unique<StatsdClient>();
4345

4446
bench.unit("block").run([&] {
4547
CBlock block; // Note that CBlock caches its checked state, so we need to recreate it here

src/init.cpp

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -783,7 +783,7 @@ void SetupServerArgs(ArgsManager& argsman)
783783
argsman.AddArg("-statsbatchsize=<bytes>", strprintf("Specify the size of each batch of stats messages (default: %d)", DEFAULT_STATSD_BATCH_SIZE), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
784784
argsman.AddArg("-statsduration=<ms>", strprintf("Specify the number of milliseconds between stats messages (default: %d)", DEFAULT_STATSD_DURATION), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
785785
argsman.AddArg("-statshost=<ip>", strprintf("Specify statsd host (default: %s)", DEFAULT_STATSD_HOST), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
786-
argsman.AddArg("-statsport=<port>", strprintf("Specify statsd port (default: %u)", DEFAULT_STATSD_PORT), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
786+
hidden_args.emplace_back("-statsport");
787787
argsman.AddArg("-statsperiod=<seconds>", strprintf("Specify the number of seconds between periodic measurements (default: %d)", DEFAULT_STATSD_PERIOD), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
788788
argsman.AddArg("-statsprefix=<string>", strprintf("Specify an optional string prepended to every stats key (default: %s)", DEFAULT_STATSD_PREFIX), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
789789
argsman.AddArg("-statssuffix=<string>", strprintf("Specify an optional string appended to every stats key (default: %s)", DEFAULT_STATSD_SUFFIX), ArgsManager::ALLOW_ANY, OptionsCategory::STATSD);
@@ -1584,11 +1584,6 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
15841584
fDiscover = args.GetBoolArg("-discover", true);
15851585
const bool ignores_incoming_txs{args.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)};
15861586

1587-
// We need to initialize g_stats_client early as currently, g_stats_client is called
1588-
// regardless of whether transmitting stats are desirable or not and if
1589-
// g_stats_client isn't present when that attempt is made, the client will crash.
1590-
::g_stats_client = InitStatsClient(args);
1591-
15921587
{
15931588

15941589
// Read asmap file if configured
@@ -1631,6 +1626,17 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info)
16311626
return InitError(strprintf(_("Invalid -socketevents ('%s') specified. Only these modes are supported: %s"), sem_str, GetSupportedSocketEventsStr()));
16321627
}
16331628

1629+
// We need to initialize g_stats_client early as currently, g_stats_client is called
1630+
// regardless of whether transmitting stats are desirable or not and if
1631+
// g_stats_client isn't present when that attempt is made, the client will crash.
1632+
{
1633+
auto stats_client = StatsdClient::make(args);
1634+
if (!stats_client) {
1635+
return InitError(_("Cannot init Statsd client") + Untranslated(" (") + util::ErrorString(stats_client) + Untranslated(")"));
1636+
}
1637+
::g_stats_client = std::move(*stats_client);
1638+
}
1639+
16341640
assert(!node.banman);
16351641
node.banman = std::make_unique<BanMan>(gArgs.GetDataDirNet() / "banlist", &uiInterface, args.GetIntArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
16361642
assert(!node.connman);

src/stats/client.cpp

Lines changed: 139 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,27 @@
77
#include <stats/client.h>
88

99
#include <logging.h>
10+
#include <random.h>
1011
#include <stats/rawsender.h>
12+
#include <sync.h>
13+
#include <util/check.h>
14+
#include <util/strencodings.h>
1115
#include <util/system.h>
16+
#include <util/translation.h>
1217

18+
#include <algorithm>
1319
#include <cmath>
20+
#include <limits>
1421
#include <random>
1522

1623
namespace {
1724
/** Threshold below which a value is considered effectively zero */
1825
static constexpr float EPSILON{0.0001f};
26+
/** Delimiter segmenting scheme from the rest of the URL */
27+
static constexpr std::string_view URL_SCHEME_DELIMITER{"://"};
28+
29+
/** Default port used to connect to a Statsd server */
30+
static constexpr uint16_t DEFAULT_STATSD_PORT{8125};
1931

2032
/** Delimiter segmenting two fully formed Statsd messages */
2133
static constexpr char STATSD_MSG_DELIMITER{'\n'};
@@ -27,12 +39,120 @@ static constexpr char STATSD_METRIC_COUNT[]{"c"};
2739
static constexpr char STATSD_METRIC_GAUGE[]{"g"};
2840
/** Characters used to denote Statsd message type as timing */
2941
static constexpr char STATSD_METRIC_TIMING[]{"ms"};
42+
43+
class StatsdClientImpl final : public StatsdClient
44+
{
45+
public:
46+
explicit StatsdClientImpl(const std::string& host, uint16_t port, uint64_t batch_size, uint64_t interval_ms,
47+
const std::string& prefix, const std::string& suffix, std::optional<bilingual_str>& error);
48+
~StatsdClientImpl() = default;
49+
50+
public:
51+
bool dec(std::string_view key, float sample_rate) override { return count(key, -1, sample_rate); }
52+
bool inc(std::string_view key, float sample_rate) override { return count(key, 1, sample_rate); }
53+
bool count(std::string_view key, int64_t delta, float sample_rate) override { return _send(key, delta, STATSD_METRIC_COUNT, sample_rate); }
54+
bool gauge(std::string_view key, int64_t value, float sample_rate) override { return _send(key, value, STATSD_METRIC_GAUGE, sample_rate); }
55+
bool gaugeDouble(std::string_view key, double value, float sample_rate) override { return _send(key, value, STATSD_METRIC_GAUGE, sample_rate); }
56+
bool timing(std::string_view key, uint64_t ms, float sample_rate) override { return _send(key, ms, STATSD_METRIC_TIMING, sample_rate); }
57+
58+
bool send(std::string_view key, double value, std::string_view type, float sample_rate) override { return _send(key, value, type, sample_rate); }
59+
bool send(std::string_view key, int32_t value, std::string_view type, float sample_rate) override { return _send(key, value, type, sample_rate); }
60+
bool send(std::string_view key, int64_t value, std::string_view type, float sample_rate) override { return _send(key, value, type, sample_rate); }
61+
bool send(std::string_view key, uint32_t value, std::string_view type, float sample_rate) override { return _send(key, value, type, sample_rate); }
62+
bool send(std::string_view key, uint64_t value, std::string_view type, float sample_rate) override { return _send(key, value, type, sample_rate); }
63+
64+
bool active() const override { return m_sender != nullptr; }
65+
66+
private:
67+
template <typename T1>
68+
inline bool _send(std::string_view key, T1 value, std::string_view type, float sample_rate);
69+
70+
private:
71+
/* Mutex to protect PRNG */
72+
mutable Mutex cs;
73+
/* PRNG used to dice-roll messages that are 0 < f < 1 */
74+
mutable FastRandomContext insecure_rand GUARDED_BY(cs);
75+
76+
/* Broadcasts messages crafted by StatsdClient */
77+
std::unique_ptr<RawSender> m_sender{nullptr};
78+
79+
/* Phrase prepended to keys */
80+
const std::string m_prefix{};
81+
/* Phrase appended to keys */
82+
const std::string m_suffix{};
83+
};
3084
} // anonymous namespace
3185

3286
std::unique_ptr<StatsdClient> g_stats_client;
3387

34-
std::unique_ptr<StatsdClient> InitStatsClient(const ArgsManager& args)
88+
util::Result<std::unique_ptr<StatsdClient>> StatsdClient::make(const ArgsManager& args)
3589
{
90+
auto host = args.GetArg("-statshost", DEFAULT_STATSD_HOST);
91+
if (host.empty()) {
92+
LogPrintf("Transmitting stats are disabled, will not init Statsd client\n");
93+
return std::make_unique<StatsdClient>();
94+
}
95+
96+
const int64_t batch_size = args.GetIntArg("-statsbatchsize", DEFAULT_STATSD_BATCH_SIZE);
97+
if (batch_size < 0) {
98+
return util::Error{_("-statsbatchsize cannot be configured with a negative value.")};
99+
}
100+
101+
const int64_t interval_ms = args.GetIntArg("-statsduration", DEFAULT_STATSD_DURATION);
102+
if (interval_ms < 0) {
103+
return util::Error{_("-statsduration cannot be configured with a negative value.")};
104+
}
105+
106+
auto port_arg = args.GetIntArg("-statsport", DEFAULT_STATSD_PORT);
107+
if (args.IsArgSet("-statsport")) {
108+
// Port range validation if -statsport is specified.
109+
if (port_arg < 1 || port_arg > std::numeric_limits<uint16_t>::max()) {
110+
return util::Error{strprintf(_("Port must be between %d and %d, supplied %d"), 1,
111+
std::numeric_limits<uint16_t>::max(), port_arg)};
112+
}
113+
}
114+
uint16_t port = static_cast<uint16_t>(port_arg);
115+
116+
// Could be a URL, try to parse it.
117+
const size_t scheme_idx{host.find(URL_SCHEME_DELIMITER)};
118+
if (scheme_idx != std::string::npos) {
119+
// Parse the scheme and trim it out of the URL if we succeed
120+
if (scheme_idx == 0) {
121+
return util::Error{_("No text before the scheme delimiter, malformed URL")};
122+
}
123+
std::string scheme{ToLower(host.substr(/*pos=*/0, scheme_idx))};
124+
if (scheme != "udp") {
125+
return util::Error{_("Unsupported URL scheme, must begin with udp://")};
126+
}
127+
host = host.substr(scheme_idx + URL_SCHEME_DELIMITER.length());
128+
129+
// Strip trailing slashes and parse the port
130+
const size_t colon_idx{host.rfind(':')};
131+
if (colon_idx != std::string::npos) {
132+
// Remove all forward slashes found after the port delimiter (colon)
133+
host = std::string(
134+
host.begin(), host.end() - [&colon_idx, &host]() {
135+
const size_t slash_idx{host.find('/', /*pos=*/colon_idx + 1)};
136+
return slash_idx != std::string::npos ? host.length() - slash_idx : 0;
137+
}());
138+
uint16_t port_url{0};
139+
SplitHostPort(host, port_url, host);
140+
if (port_url != 0) {
141+
if (args.IsArgSet("-statsport")) {
142+
LogPrintf("%s: Supplied URL with port, ignoring -statsport\n", __func__);
143+
}
144+
port = port_url;
145+
}
146+
} else {
147+
// There was no port specified, remove everything after the first forward slash
148+
host = host.substr(/*pos=*/0, host.find("/"));
149+
}
150+
151+
if (host.empty()) {
152+
return util::Error{_("No host specified, malformed URL")};
153+
}
154+
}
155+
36156
auto sanitize_string = [](std::string string) {
37157
// Remove key delimiters from the front and back as they're added back in
38158
// the constructor
@@ -43,72 +163,40 @@ std::unique_ptr<StatsdClient> InitStatsClient(const ArgsManager& args)
43163
return string;
44164
};
45165

46-
return std::make_unique<StatsdClient>(args.GetArg("-statshost", DEFAULT_STATSD_HOST),
47-
args.GetIntArg("-statsport", DEFAULT_STATSD_PORT),
48-
args.GetIntArg("-statsbatchsize", DEFAULT_STATSD_BATCH_SIZE),
49-
args.GetIntArg("-statsduration", DEFAULT_STATSD_DURATION),
50-
sanitize_string(args.GetArg("-statsprefix", DEFAULT_STATSD_PREFIX)),
51-
sanitize_string(args.GetArg("-statssuffix", DEFAULT_STATSD_SUFFIX)));
166+
std::optional<bilingual_str> error_opt;
167+
auto statsd_ptr = std::make_unique<StatsdClientImpl>(
168+
host, port, batch_size, interval_ms,
169+
sanitize_string(args.GetArg("-statsprefix", DEFAULT_STATSD_PREFIX)),
170+
sanitize_string(args.GetArg("-statssuffix", DEFAULT_STATSD_SUFFIX)), error_opt);
171+
if (error_opt.has_value()) {
172+
statsd_ptr.reset();
173+
return util::Error{error_opt.value()};
174+
}
175+
return {std::move(statsd_ptr)};
52176
}
53177

54-
StatsdClient::StatsdClient(const std::string& host, uint16_t port, uint64_t batch_size, uint64_t interval_ms,
55-
const std::string& prefix, const std::string& suffix) :
178+
StatsdClientImpl::StatsdClientImpl(const std::string& host, uint16_t port, uint64_t batch_size, uint64_t interval_ms,
179+
const std::string& prefix, const std::string& suffix,
180+
std::optional<bilingual_str>& error) :
56181
m_prefix{[prefix]() { return !prefix.empty() ? prefix + STATSD_NS_DELIMITER : prefix; }()},
57182
m_suffix{[suffix]() { return !suffix.empty() ? STATSD_NS_DELIMITER + suffix : suffix; }()}
58183
{
59-
if (host.empty()) {
60-
LogPrintf("Transmitting stats are disabled, will not init StatsdClient\n");
61-
return;
62-
}
63-
64-
std::optional<std::string> error_opt;
65184
m_sender = std::make_unique<RawSender>(host, port,
66185
std::make_pair(batch_size, static_cast<uint8_t>(STATSD_MSG_DELIMITER)),
67-
interval_ms, error_opt);
68-
if (error_opt.has_value()) {
69-
LogPrintf("ERROR: %s, cannot initialize StatsdClient.\n", error_opt.value());
186+
interval_ms, error);
187+
if (error.has_value()) {
70188
m_sender.reset();
71189
return;
72190
}
73191

74192
LogPrintf("StatsdClient initialized to transmit stats to %s:%d\n", host, port);
75193
}
76194

77-
StatsdClient::~StatsdClient() {}
78-
79-
bool StatsdClient::dec(const std::string& key, float sample_rate) { return count(key, -1, sample_rate); }
80-
81-
bool StatsdClient::inc(const std::string& key, float sample_rate) { return count(key, 1, sample_rate); }
82-
83-
bool StatsdClient::count(const std::string& key, int64_t delta, float sample_rate)
84-
{
85-
return send(key, delta, STATSD_METRIC_COUNT, sample_rate);
86-
}
87-
88-
bool StatsdClient::gauge(const std::string& key, int64_t value, float sample_rate)
89-
{
90-
return send(key, value, STATSD_METRIC_GAUGE, sample_rate);
91-
}
92-
93-
bool StatsdClient::gaugeDouble(const std::string& key, double value, float sample_rate)
94-
{
95-
return send(key, value, STATSD_METRIC_GAUGE, sample_rate);
96-
}
97-
98-
bool StatsdClient::timing(const std::string& key, uint64_t ms, float sample_rate)
99-
{
100-
return send(key, ms, STATSD_METRIC_TIMING, sample_rate);
101-
}
102-
103195
template <typename T1>
104-
bool StatsdClient::send(const std::string& key, T1 value, const std::string& type, float sample_rate)
196+
inline bool StatsdClientImpl::_send(std::string_view key, T1 value, std::string_view type, float sample_rate)
105197
{
106198
static_assert(std::is_arithmetic<T1>::value, "Must specialize to an arithmetic type");
107199

108-
if (!m_sender) {
109-
return false;
110-
}
111-
112200
// Determine if we should send the message at all but claim that we did even if we don't
113201
sample_rate = std::clamp(sample_rate, 0.f, 1.f);
114202
bool always_send = std::fabs(sample_rate - 1.f) < EPSILON;
@@ -125,16 +213,10 @@ bool StatsdClient::send(const std::string& key, T1 value, const std::string& typ
125213
}
126214

127215
// Send it and report an error if we encounter one
128-
if (auto error_opt = m_sender->Send(msg); error_opt.has_value()) {
129-
LogPrintf("ERROR: %s.\n", error_opt.value());
216+
if (auto error_opt = Assert(m_sender)->Send(msg); error_opt.has_value()) {
217+
LogPrintf("ERROR: %s.\n", error_opt->original);
130218
return false;
131219
}
132220

133221
return true;
134222
}
135-
136-
template bool StatsdClient::send(const std::string& key, double value, const std::string& type, float sample_rate);
137-
template bool StatsdClient::send(const std::string& key, int32_t value, const std::string& type, float sample_rate);
138-
template bool StatsdClient::send(const std::string& key, int64_t value, const std::string& type, float sample_rate);
139-
template bool StatsdClient::send(const std::string& key, uint32_t value, const std::string& type, float sample_rate);
140-
template bool StatsdClient::send(const std::string& key, uint64_t value, const std::string& type, float sample_rate);

0 commit comments

Comments
 (0)