diff --git a/include/cpp-statsd-client/StatsdClient.hpp b/include/cpp-statsd-client/StatsdClient.hpp index 4c3909e..2eec825 100644 --- a/include/cpp-statsd-client/StatsdClient.hpp +++ b/include/cpp-statsd-client/StatsdClient.hpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace Statsd { @@ -59,28 +60,57 @@ class StatsdClient { const std::string& errorMessage() const noexcept; //! Increments the key, at a given frequency rate - void increment(const std::string& key, float frequency = 1.0f) const noexcept; + void increment(const std::string& key, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; //! Increments the key, at a given frequency rate - void decrement(const std::string& key, float frequency = 1.0f) const noexcept; + void decrement(const std::string& key, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; //! Adjusts the specified key by a given delta, at a given frequency rate - void count(const std::string& key, const int delta, float frequency = 1.0f) const noexcept; + void count(const std::string& key, + const int delta, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; //! Records a gauge for the key, with a given value, at a given frequency rate - void gauge(const std::string& key, const unsigned int value, float frequency = 1.0f) const noexcept; + void gauge(const std::string& key, + const unsigned int value, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; //! Records a timing for a key, at a given frequency - void timing(const std::string& key, const unsigned int ms, float frequency = 1.0f) const noexcept; + void timing(const std::string& key, + const unsigned int ms, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; - //! Send a value for a key, according to its type, at a given frequency - void send(const std::string& key, const int value, const std::string& type, float frequency = 1.0f) const noexcept; + //! Records a count of unique occurrences for a key, at a given frequency + void set(const std::string& key, + const unsigned int sum, + float frequency = 1.0f, + const std::vector& tags = {}) const noexcept; //! Seed the RNG that controls sampling void seed(unsigned int seed = std::random_device()()) noexcept; //!@} +private: + // @name Private methods + // @{ + + //! Send a value for a key, according to its type, at a given frequency + void send(const std::string& key, + const int value, + const char* type, + float frequency, + const std::vector& tags) const noexcept; + + //!@} + private: //! The prefix to be used for metrics std::string m_prefix; @@ -103,6 +133,12 @@ std::string sanitizePrefix(std::string prefix) { } return prefix; } + +// All supported metric types +constexpr char METRIC_TYPE_COUNT[] = "c"; +constexpr char METRIC_TYPE_GUAGE[] = "g"; +constexpr char METRIC_TYPE_TIMING[] = "ms"; +constexpr char METRIC_TYPE_SET[] = "s"; } // namespace detail inline StatsdClient::StatsdClient(const std::string& host, @@ -128,32 +164,51 @@ inline const std::string& StatsdClient::errorMessage() const noexcept { return m_sender->errorMessage(); } -inline void StatsdClient::decrement(const std::string& key, float frequency) const noexcept { - return count(key, -1, frequency); +inline void StatsdClient::decrement(const std::string& key, + float frequency, + const std::vector& tags) const noexcept { + return count(key, -1, frequency, tags); } -inline void StatsdClient::increment(const std::string& key, float frequency) const noexcept { - return count(key, 1, frequency); +inline void StatsdClient::increment(const std::string& key, + float frequency, + const std::vector& tags) const noexcept { + return count(key, 1, frequency, tags); } -inline void StatsdClient::count(const std::string& key, const int delta, float frequency) const noexcept { - return send(key, delta, "c", frequency); +inline void StatsdClient::count(const std::string& key, + const int delta, + float frequency, + const std::vector& tags) const noexcept { + return send(key, delta, detail::METRIC_TYPE_COUNT, frequency, tags); } inline void StatsdClient::gauge(const std::string& key, const unsigned int value, - const float frequency) const noexcept { - return send(key, value, "g", frequency); + const float frequency, + const std::vector& tags) const noexcept { + return send(key, value, detail::METRIC_TYPE_GUAGE, frequency, tags); +} + +inline void StatsdClient::timing(const std::string& key, + const unsigned int ms, + float frequency, + const std::vector& tags) const noexcept { + return send(key, ms, detail::METRIC_TYPE_TIMING, frequency, tags); } -inline void StatsdClient::timing(const std::string& key, const unsigned int ms, float frequency) const noexcept { - return send(key, ms, "ms", frequency); +inline void StatsdClient::set(const std::string& key, + const unsigned int sum, + float frequency, + const std::vector& tags) const noexcept { + return send(key, sum, detail::METRIC_TYPE_SET, frequency, tags); } inline void StatsdClient::send(const std::string& key, const int value, - const std::string& type, - float frequency) const noexcept { + const char* type, + float frequency, + const std::vector& tags) const noexcept { // Bail if we can't send anything anyway if (!m_sender->initialized()) { return; @@ -189,6 +244,15 @@ inline void StatsdClient::send(const std::string& key, m_buffer.append(std::to_string(static_cast(frequency * 100))); } + if (!tags.empty()) { + m_buffer.append("|#"); + for (const auto& tag : tags) { + m_buffer.append(tag); + m_buffer.push_back(','); + } + m_buffer.pop_back(); + } + // Send the message via the UDP sender m_sender->send(m_buffer); } diff --git a/tests/testStatsdClient.cpp b/tests/testStatsdClient.cpp index 0e40518..5842e5f 100644 --- a/tests/testStatsdClient.cpp +++ b/tests/testStatsdClient.cpp @@ -48,24 +48,24 @@ void testReconfigure() { StatsdServer server; StatsdClient client("localhost", 8125, "first."); - client.send("foo", 1, "c", 1.f); + client.increment("foo"); if (server.receive() != "first.foo:1|c") { throw std::runtime_error("Incorrect stat received"); } client.setConfig("localhost", 8125, "second"); - client.send("bar", 1, "c", 1.f); + client.increment("bar"); if (server.receive() != "second.bar:1|c") { throw std::runtime_error("Incorrect stat received"); } client.setConfig("localhost", 8125, ""); - client.send("third.baz", 1, "c", 1.f); + client.increment("third.baz"); if (server.receive() != "third.baz:1|c") { throw std::runtime_error("Incorrect stat received"); } - client.send("", 1, "c", 1.f); + client.increment(""); if (server.receive() != ":1|c") { throw std::runtime_error("Incorrect stat received"); } @@ -118,14 +118,24 @@ void testSendRecv(uint64_t batchSize) { throwOnError(client); expected.emplace_back("sendRecv.myTiming:2|ms|@0.10"); - // Send a metric explicitly - client.send("tutu", 241, "c", 2.0f); + // Send a set with 1227 total uniques + client.set("tutu", 1227, 2.0f); throwOnError(client); - expected.emplace_back("sendRecv.tutu:241|c"); + expected.emplace_back("sendRecv.tutu:1227|s"); + + // Gauge but with tags + client.gauge("dr.rösti.grabe", 333, 1.f, {"liegt", "im", "weste"}); + throwOnError(client); + expected.emplace_back("sendRecv.dr.rösti.grabe:333|g|#liegt,im,weste"); + + // All the things + client.count("foo", -42, .9f, {"bar", "baz"}); + throwOnError(client); + expected.emplace_back("sendRecv.foo:-42|c|@0.90|#bar,baz"); } // Signal the mock server we are done - client.send("DONE", 0, "ms"); + client.timing("DONE", 0); // Wait for the server to stop server.join();