xrpld
Loading...
Searching...
No Matches
StatsDCollector.cpp
1#include <xrpl/beast/insight/StatsDCollector.h>
2
3#include <xrpl/beast/core/List.h>
4#include <xrpl/beast/insight/CounterImpl.h>
5#include <xrpl/beast/insight/EventImpl.h>
6#include <xrpl/beast/insight/GaugeImpl.h>
7#include <xrpl/beast/insight/Hook.h>
8#include <xrpl/beast/insight/HookImpl.h>
9#include <xrpl/beast/insight/MeterImpl.h>
10#include <xrpl/beast/net/IPEndpoint.h>
11#include <xrpl/beast/utility/Journal.h>
12#include <xrpl/beast/utility/instrumentation.h>
13
14#include <boost/asio/basic_waitable_timer.hpp>
15#include <boost/asio/bind_executor.hpp>
16#include <boost/asio/buffer.hpp>
17#include <boost/asio/dispatch.hpp>
18#include <boost/asio/error.hpp>
19#include <boost/asio/executor_work_guard.hpp>
20#include <boost/asio/io_context.hpp>
21#include <boost/asio/ip/udp.hpp>
22#include <boost/asio/strand.hpp>
23#include <boost/system/detail/error_code.hpp>
24#include <boost/system/system_error.hpp>
25
26#include <chrono>
27#include <cstddef>
28#include <deque>
29#include <functional>
30#include <limits>
31#include <memory>
32#include <mutex>
33#include <optional>
34#include <sstream>
35#include <string>
36#include <thread>
37#include <utility>
38#include <vector>
39
40#ifndef BEAST_STATSDCOLLECTOR_TRACING_ENABLED
41#define BEAST_STATSDCOLLECTOR_TRACING_ENABLED 0
42#endif
43
44namespace beast::insight {
45
46namespace detail {
47
49
50//------------------------------------------------------------------------------
51
52class StatsDMetricBase : public List<StatsDMetricBase>::Node
53{
54public:
55 virtual void
56 doProcess() = 0;
57 virtual ~StatsDMetricBase() = default;
58 StatsDMetricBase() = default;
61 operator=(StatsDMetricBase const&) = delete;
62};
63
64//------------------------------------------------------------------------------
65
67{
68public:
70
71 ~StatsDHookImpl() override;
72
73 void
74 doProcess() override;
75
77 operator=(StatsDHookImpl const&) = delete;
78
79private:
82};
83
84//------------------------------------------------------------------------------
85
87{
88public:
90
91 ~StatsDCounterImpl() override;
92
93 void
94 increment(CounterImpl::value_type amount) override;
95
96 void
97 flush();
98 void
100 void
101 doProcess() override;
102
104 operator=(StatsDCounterImpl const&) = delete;
105
106private:
110 bool dirty_{false};
111};
112
113//------------------------------------------------------------------------------
114
116{
117public:
119
120 ~StatsDEventImpl() override = default;
121
122 void
123 notify(EventImpl::value_type const& value) override;
124
125 void
126 doNotify(EventImpl::value_type const& value);
127 void
129
130private:
133
136};
137
138//------------------------------------------------------------------------------
139
141{
142public:
144
145 ~StatsDGaugeImpl() override;
146
147 void
148 set(GaugeImpl::value_type value) override;
149 void
150 increment(GaugeImpl::difference_type amount) override;
151
152 void
153 flush();
154 void
156 void
158 void
159 doProcess() override;
160
162 operator=(StatsDGaugeImpl const&) = delete;
163
164private:
169 bool dirty_{false};
170};
171
172//------------------------------------------------------------------------------
173
175{
176public:
178
179 ~StatsDMeterImpl() override;
180
181 void
182 increment(MeterImpl::value_type amount) override;
183
184 void
185 flush();
186 void
188 void
189 doProcess() override;
190
192 operator=(StatsDMeterImpl const&) = delete;
193
194private:
198 bool dirty_{false};
199};
200
201//------------------------------------------------------------------------------
202
204 public std::enable_shared_from_this<StatsDCollectorImp>
205{
206private:
207 static constexpr auto kMaxPacketSize = 1472;
208
212 boost::asio::io_context ioContext_;
214 boost::asio::strand<boost::asio::io_context::executor_type> strand_;
215 boost::asio::basic_waitable_timer<std::chrono::steady_clock> timer_;
216 boost::asio::ip::udp::socket socket_;
220
221 // Must come last for order of init
223
224 static boost::asio::ip::udp::endpoint
226 {
227 return boost::asio::ip::udp::endpoint(ep.address(), ep.port());
228 }
229
230public:
232 : journal_(journal)
233 , address_(std::move(address))
234 , prefix_(std::move(prefix))
235 , work_(boost::asio::make_work_guard(ioContext_))
236 , strand_(boost::asio::make_strand(ioContext_))
240 {
241 }
242
244 {
245 try
246 {
247 timer_.cancel();
248 }
249 catch (boost::system::system_error const&) // NOLINT(bugprone-empty-catch)
250 {
251 // ignored
252 }
253
254 work_.reset();
255 thread_.join();
256 }
257
258 Hook
259 makeHook(HookImpl::HandlerType const& handler) override
260 {
262 }
263
264 Counter
265 makeCounter(std::string const& name) override
266 {
268 }
269
270 Event
271 makeEvent(std::string const& name) override
272 {
274 }
275
276 Gauge
277 makeGauge(std::string const& name) override
278 {
280 }
281
282 Meter
283 makeMeter(std::string const& name) override
284 {
286 }
287
288 //--------------------------------------------------------------------------
289
290 void
292 {
294 metrics_.pushBack(metric);
295 }
296
297 void
299 {
301 metrics_.erase(metrics_.iteratorTo(metric));
302 }
303
304 //--------------------------------------------------------------------------
305
306 boost::asio::io_context&
308 {
309 return ioContext_;
310 }
311
312 std::string const&
313 prefix() const
314 {
315 return prefix_;
316 }
317
318 void
320 {
321 data_.emplace_back(buffer);
322 }
323
324 void
326 {
327 boost::asio::dispatch(
328 ioContext_, boost::asio::bind_executor(strand_, [this, buffer = std::move(buffer)] {
329 doPostBuffer(buffer);
330 }));
331 }
332
333 // The keepAlive parameter makes sure the buffers sent to
334 // boost::asio::async_send do not go away until the call is finished
335 void
338 boost::system::error_code ec,
340 {
341 if (ec == boost::asio::error::operation_aborted)
342 return;
343
344 if (ec)
345 {
346 if (auto stream = journal_.error())
347 stream << "async_send failed: " << ec.message();
348 return;
349 }
350 }
351
352 static void
354 {
355 (void)buffers;
356#if BEAST_STATSDCOLLECTOR_TRACING_ENABLED
357 for (auto const& buffer : buffers)
358 {
359 std::string const s(buffer.data(), boost::asio::buffer_size(buffer));
360 std::cerr << s;
361 }
362 std::cerr << '\n';
363#endif
364 }
365
366 // Send what we have
367 void
369 {
370 if (data_.empty())
371 return;
372
373 // Break up the array of strings into blocks
374 // that each fit into one UDP packet.
375 //
377 buffers.reserve(data_.size());
378 std::size_t size(0);
379
380 auto keepAlive = std::make_shared<std::deque<std::string>>(std::move(data_));
381 data_.clear();
382
383 for (auto const& s : *keepAlive)
384 {
385 std::size_t const length(s.size());
386 XRPL_ASSERT(
387 !s.empty(),
388 "beast::insight::detail::StatsDCollectorImp::sendBuffers : "
389 "non-empty payload");
390 if (!buffers.empty() && (size + length) > kMaxPacketSize)
391 {
392 log(buffers);
393 socket_.async_send(
394 buffers,
395 [this, keepAlive](
396 boost::system::error_code const& ec, std::size_t bytesTransferred) {
397 onSend(keepAlive, ec, bytesTransferred);
398 });
399 buffers.clear();
400 size = 0;
401 }
402
403 buffers.emplace_back(&s[0], length);
404 size += length;
405 }
406
407 if (!buffers.empty())
408 {
409 log(buffers);
410 socket_.async_send(
411 buffers,
412 [this, keepAlive](
413 boost::system::error_code const& ec, std::size_t bytesTransferred) {
414 onSend(keepAlive, ec, bytesTransferred);
415 });
416 }
417 }
418
419 void
421 {
422 using namespace std::chrono_literals;
423 timer_.expires_after(1s);
424 timer_.async_wait([this](boost::system::error_code const& ec) { onTimer(ec); });
425 }
426
427 void
428 onTimer(boost::system::error_code ec)
429 {
430 if (ec == boost::asio::error::operation_aborted)
431 return;
432
433 if (ec)
434 {
435 if (auto stream = journal_.error())
436 stream << "onTimer failed: " << ec.message();
437 return;
438 }
439
441
442 for (auto& m : metrics_)
443 m.doProcess();
444
445 sendBuffers();
446
447 setTimer();
448 }
449
450 void
452 {
453 boost::system::error_code ec;
454
455 if (socket_.connect(toEndpoint(address_), ec))
456 {
457 if (auto stream = journal_.error())
458 stream << "Connect failed: " << ec.message();
459 return;
460 }
461
462 setTimer();
463
464 ioContext_.run();
465
466 // NOLINTNEXTLINE(bugprone-unused-return-value)
467 socket_.shutdown(boost::asio::ip::udp::socket::shutdown_send, ec);
468
469 socket_.close();
470
471 ioContext_.poll();
472 }
473};
474
475//------------------------------------------------------------------------------
476
478 : impl_(std::move(impl)), handler_(std::move(handler))
479{
480 impl_->add(*this);
481}
482
484{
485 impl_->remove(*this);
486}
487
488void
493
494//------------------------------------------------------------------------------
495
497 : impl_(std::move(impl)), name_(std::move(name))
498{
499 impl_->add(*this);
500}
501
503{
504 impl_->remove(*this);
505}
506
507void
509{
510 boost::asio::dispatch(
511 impl_->getIoContext(),
513 self->doIncrement(amount);
514 });
515}
516
517void
519{
520 if (dirty_)
521 {
522 dirty_ = false;
524 ss << impl_->prefix() << "." << name_ << ":" << value_ << "|c"
525 << "\n";
526 value_ = 0;
527 impl_->postBuffer(ss.str());
528 }
529}
530
531void
533{
534 value_ += amount;
535 dirty_ = true;
536}
537
538void
543
544//------------------------------------------------------------------------------
545
550
551void
553{
554 boost::asio::dispatch(
555 impl_->getIoContext(),
557 self->doNotify(value);
558 });
559}
560
561void
563{
565 ss << impl_->prefix() << "." << name_ << ":" << value.count() << "|ms"
566 << "\n";
567 impl_->postBuffer(ss.str());
568}
569
570//------------------------------------------------------------------------------
571
573 : impl_(std::move(impl)), name_(std::move(name))
574{
575 impl_->add(*this);
576}
577
579{
580 impl_->remove(*this);
581}
582
583void
585{
586 boost::asio::dispatch(
587 impl_->getIoContext(),
589 self->doSet(value);
590 });
591}
592
593void
595{
596 boost::asio::dispatch(
597 impl_->getIoContext(),
599 self->doIncrement(amount);
600 });
601}
602
603void
605{
606 if (dirty_)
607 {
608 dirty_ = false;
610 ss << impl_->prefix() << "." << name_ << ":" << value_ << "|g"
611 << "\n";
612 impl_->postBuffer(ss.str());
613 }
614}
615
616void
618{
619 value_ = value;
620
621 if (value_ != lastValue_)
622 {
624 dirty_ = true;
625 }
626}
627
628void
630{
632
633 if (amount > 0)
634 {
635 auto const d = static_cast<GaugeImpl::value_type>(amount);
638 : d;
639 }
640 else if (amount < 0)
641 {
642 auto const d = static_cast<GaugeImpl::value_type>(-amount);
643 value = (d >= value) ? 0 : value - d;
644 }
645
646 doSet(value);
647}
648
649void
654
655//------------------------------------------------------------------------------
656
658 : impl_(std::move(impl)), name_(std::move(name))
659{
660 impl_->add(*this);
661}
662
664{
665 impl_->remove(*this);
666}
667
668void
670{
671 boost::asio::dispatch(
672 impl_->getIoContext(),
674 self->doIncrement(amount);
675 });
676}
677
678void
680{
681 if (dirty_)
682 {
683 dirty_ = false;
685 ss << impl_->prefix() << "." << name_ << ":" << value_ << "|m"
686 << "\n";
687 value_ = 0;
688 impl_->postBuffer(ss.str());
689 }
690}
691
692void
694{
695 value_ += amount;
696 dirty_ = true;
697}
698
699void
704
705} // namespace detail
706
707//------------------------------------------------------------------------------
708
710StatsDCollector::make(ip::Endpoint const& address, std::string const& prefix, Journal journal)
711{
712 return std::make_shared<detail::StatsDCollectorImp>(address, prefix, journal);
713}
714
715} // namespace beast::insight
A generic endpoint for log messages.
Definition Journal.h:44
Intrusive doubly linked list.
Definition List.h:258
A metric for measuring an integral value.
Definition Counter.h:20
std::chrono::milliseconds value_type
Definition EventImpl.h:13
A metric for reporting event timing.
Definition Event.h:22
std::uint64_t value_type
Definition GaugeImpl.h:13
std::int64_t difference_type
Definition GaugeImpl.h:14
A metric for measuring an integral value.
Definition Gauge.h:21
std::function< void(void)> HandlerType
Definition HookImpl.h:11
A reference to a handler for performing polled collection.
Definition Hook.h:14
std::uint64_t value_type
Definition MeterImpl.h:13
A metric for measuring an integral value.
Definition Meter.h:19
static std::shared_ptr< StatsDCollector > make(ip::Endpoint const &address, std::string const &prefix, Journal journal)
Create a StatsD collector.
void doPostBuffer(std::string const &buffer)
Gauge makeGauge(std::string const &name) override
Create a gauge with the specified name.
void onTimer(boost::system::error_code ec)
static boost::asio::ip::udp::endpoint toEndpoint(ip::Endpoint const &ep)
Hook makeHook(HookImpl::HandlerType const &handler) override
Counter makeCounter(std::string const &name) override
Create a counter with the specified name.
Meter makeMeter(std::string const &name) override
Create a meter with the specified name.
Event makeEvent(std::string const &name) override
Create an event with the specified name.
void onSend(std::shared_ptr< std::deque< std::string > >, boost::system::error_code ec, std::size_t)
boost::asio::basic_waitable_timer< std::chrono::steady_clock > timer_
boost::asio::strand< boost::asio::io_context::executor_type > strand_
StatsDCollectorImp(ip::Endpoint address, std::string prefix, Journal journal)
static void log(std::vector< boost::asio::const_buffer > const &buffers)
std::optional< boost::asio::executor_work_guard< boost::asio::io_context::executor_type > > work_
StatsDCounterImpl & operator=(StatsDCounterImpl const &)=delete
void doIncrement(CounterImpl::value_type amount)
void increment(CounterImpl::value_type amount) override
StatsDCounterImpl(std::string name, std::shared_ptr< StatsDCollectorImp > impl)
std::shared_ptr< StatsDCollectorImp > impl_
void notify(EventImpl::value_type const &value) override
StatsDEventImpl(std::string name, std::shared_ptr< StatsDCollectorImp > impl)
StatsDEventImpl & operator=(StatsDEventImpl const &)
void doNotify(EventImpl::value_type const &value)
std::shared_ptr< StatsDCollectorImp > impl_
void increment(GaugeImpl::difference_type amount) override
void set(GaugeImpl::value_type value) override
void doSet(GaugeImpl::value_type value)
std::shared_ptr< StatsDCollectorImp > impl_
void doIncrement(GaugeImpl::difference_type amount)
StatsDGaugeImpl & operator=(StatsDGaugeImpl const &)=delete
StatsDGaugeImpl(std::string name, std::shared_ptr< StatsDCollectorImp > impl)
StatsDHookImpl(HandlerType handler, std::shared_ptr< StatsDCollectorImp > impl)
std::shared_ptr< StatsDCollectorImp > impl_
StatsDHookImpl & operator=(StatsDHookImpl const &)=delete
void doIncrement(MeterImpl::value_type amount)
StatsDMeterImpl & operator=(StatsDMeterImpl const &)=delete
StatsDMeterImpl(std::string name, std::shared_ptr< StatsDCollectorImp > impl)
void increment(MeterImpl::value_type amount) override
std::shared_ptr< StatsDCollectorImp > impl_
StatsDMetricBase & operator=(StatsDMetricBase const &)=delete
StatsDMetricBase(StatsDMetricBase const &)=delete
A version-independent IP address and port combination.
Definition IPEndpoint.h:24
Port port() const
Returns the port number on the endpoint.
Definition IPEndpoint.h:56
Address const & address() const
Returns the address portion of this endpoint.
Definition IPEndpoint.h:74
T clear(T... args)
T emplace_back(T... args)
T empty(T... args)
T make_shared(T... args)
T max(T... args)
STL namespace.
T static_pointer_cast(T... args)
T reserve(T... args)
T str(T... args)