Skip to content

Commit 9fde229

Browse files
committed
fix up test
1 parent e369628 commit 9fde229

File tree

2 files changed

+58
-38
lines changed

2 files changed

+58
-38
lines changed

include/cpp-statsd-client/StatsdClient.hpp

Lines changed: 39 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,39 @@
44
#include <cpp-statsd-client/UDPSender.hpp>
55
#include <cstdint>
66
#include <cstdio>
7+
#include <functional>
78
#include <iomanip>
89
#include <memory>
910
#include <random>
1011
#include <sstream>
1112
#include <string>
13+
#include <utility>
1214
#include <vector>
1315

1416
namespace Statsd {
1517

18+
namespace detail {
19+
inline std::string sanitizePrefix(std::string prefix) {
20+
// For convenience we provide the dot when generating the stat message
21+
if (!prefix.empty() && prefix.back() == '.') {
22+
prefix.pop_back();
23+
}
24+
return prefix;
25+
}
26+
27+
inline float rand(unsigned int seed) {
28+
static thread_local std::mt19937 twister(seed);
29+
static thread_local std::uniform_real_distribution<float> dist(0.f, 1.f);
30+
return dist(twister);
31+
}
32+
33+
// All supported metric types
34+
constexpr char METRIC_TYPE_COUNT[] = "c";
35+
constexpr char METRIC_TYPE_GAUGE[] = "g";
36+
constexpr char METRIC_TYPE_TIMING[] = "ms";
37+
constexpr char METRIC_TYPE_SET[] = "s";
38+
} // namespace detail
39+
1640
/*!
1741
*
1842
* Statsd client
@@ -27,8 +51,8 @@ namespace Statsd {
2751
* nor prepend one to the key
2852
*
2953
* The sampling frequency is specified per call and uses a
30-
* random number generator to determine whether or not the stat
31-
* will be recorded this time or not.
54+
* random number generator (optionally user-specified) to
55+
* determine whether the stat will be recorded this time or not.
3256
*
3357
* The top level configuration includes 2 optional parameters
3458
* that determine how the stats are delivered to statsd. These
@@ -57,6 +81,11 @@ namespace Statsd {
5781
*/
5882
class StatsdClient {
5983
public:
84+
85+
//! A functor that returns a value between 0 and 1 used
86+
//! to determine whether a given message is sampled.
87+
using FrequencyFunc = std::function<float()>;
88+
6089
//!@name Constructor and destructor, non-copyable
6190
//!@{
6291

@@ -67,7 +96,8 @@ class StatsdClient {
6796
const uint64_t batchsize = 0,
6897
const uint64_t sendInterval = 1000,
6998
const int gaugePrecision = 4,
70-
unsigned int seed = std::random_device()()) noexcept;
99+
FrequencyFunc frequencyFunc =
100+
std::bind(detail::rand, std::random_device()())) noexcept;
71101

72102
StatsdClient(const StatsdClient&) = delete;
73103
StatsdClient& operator=(const StatsdClient&) = delete;
@@ -142,7 +172,6 @@ class StatsdClient {
142172

143173
//!@}
144174

145-
private:
146175
//! The prefix to be used for metrics
147176
std::string m_prefix;
148177

@@ -151,41 +180,21 @@ class StatsdClient {
151180

152181
//! Fixed floating point precision of gauges
153182
int m_gaugePrecision;
154-
};
155-
156-
namespace detail {
157-
inline std::string sanitizePrefix(std::string prefix) {
158-
// For convenience we provide the dot when generating the stat message
159-
if (!prefix.empty() && prefix.back() == '.') {
160-
prefix.pop_back();
161-
}
162-
return prefix;
163-
}
164183

165-
inline std::mt19937& rng(unsigned int seed = 0) {
166-
static thread_local std::mt19937 twister(seed);
167-
return twister;
168-
}
169-
170-
// All supported metric types
171-
constexpr char METRIC_TYPE_COUNT[] = "c";
172-
constexpr char METRIC_TYPE_GAUGE[] = "g";
173-
constexpr char METRIC_TYPE_TIMING[] = "ms";
174-
constexpr char METRIC_TYPE_SET[] = "s";
175-
} // namespace detail
184+
//! The function used to determine whether a message is sampled
185+
FrequencyFunc m_frequencyFunc;
186+
};
176187

177188
inline StatsdClient::StatsdClient(const std::string& host,
178189
const uint16_t port,
179190
const std::string& prefix,
180191
const uint64_t batchsize,
181192
const uint64_t sendInterval,
182193
const int gaugePrecision,
183-
const unsigned int seed) noexcept
194+
FrequencyFunc frequencyFunc) noexcept
184195
: m_prefix(detail::sanitizePrefix(prefix)),
185196
m_sender(new UDPSender{host, port, batchsize, sendInterval}),
186-
m_gaugePrecision(gaugePrecision) {
187-
// Initialize the random generator to be used for sampling
188-
detail::rng(seed);
197+
m_gaugePrecision(gaugePrecision), m_frequencyFunc(std::move(frequencyFunc)) {
189198
}
190199

191200
inline const std::string& StatsdClient::errorMessage() const noexcept {
@@ -260,7 +269,7 @@ inline void StatsdClient::send(const std::string& key,
260269
const bool isFrequencyOne = std::fabs(frequency - 1.0f) < epsilon;
261270
const bool isFrequencyZero = std::fabs(frequency) < epsilon;
262271
if (isFrequencyZero ||
263-
(!isFrequencyOne && (frequency < std::uniform_real_distribution<float>(0.f, 1.f)(detail::rng())))) {
272+
(!isFrequencyOne && (frequency < m_frequencyFunc()))) {
264273
return;
265274
}
266275

tests/testStatsdClient.cpp

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,15 +58,19 @@ void testSendRecv(uint64_t batchSize, uint64_t sendInterval) {
5858
std::vector<std::string> messages, expected;
5959
std::thread server(mock, std::ref(mock_server), std::ref(messages));
6060

61+
std::mt19937 twister(std::random_device{}());
62+
std::uniform_real_distribution<float> dist(0.f, 1.f);
63+
auto rand = [&]()->float{return dist(twister);};
64+
6165
// Set a new config that has the client send messages to a proper address that can be resolved
62-
StatsdClient client("localhost", 8125, "sendRecv.", batchSize, sendInterval, 3, 19); // this seed works for the testing below
66+
StatsdClient client("localhost", 8125, "sendRecv.", batchSize, sendInterval, 3, rand);
6367
throwOnError(client);
6468

6569
// TODO: I forget if we need to wait for the server to be ready here before sending the first stats
6670
// is there a race condition where the client sending before the server binds would drop that clients message
6771

6872
for (int i = 0; i < 3; ++i) {
69-
// Increment "coco"
73+
// Increment "coco"
7074
client.increment("coco");
7175
throwOnError(client);
7276
expected.emplace_back("sendRecv.coco:1|c");
@@ -77,6 +81,7 @@ void testSendRecv(uint64_t batchSize, uint64_t sendInterval) {
7781
expected.emplace_back("sendRecv.kiki:-1|c");
7882

7983
// Adjusts "toto" by +2
84+
twister.seed(19); // this seed gets a hit on the first call
8085
client.count("toto", 2, 0.1f);
8186
throwOnError(client);
8287
expected.emplace_back("sendRecv.toto:2|c|@0.10");
@@ -96,6 +101,7 @@ void testSendRecv(uint64_t batchSize, uint64_t sendInterval) {
96101
expected.emplace_back("sendRecv.titifloat:-123.457|g");
97102

98103
// Record a timing of 2ms for "myTiming"
104+
twister.seed(19);
99105
client.timing("myTiming", 2, 0.1f);
100106
throwOnError(client);
101107
expected.emplace_back("sendRecv.myTiming:2|ms|@0.10");
@@ -134,13 +140,18 @@ void testSendRecv(uint64_t batchSize, uint64_t sendInterval) {
134140

135141
// Make sure we get the exactly correct output
136142
if (messages != expected) {
137-
std::cerr << "Unexpected stats received by server, got:" << std::endl;
138-
for (const auto& message : messages) {
139-
std::cerr << message << std::endl;
143+
for (size_t i = 0; i < expected.size(); ++i) {
144+
if (i >= messages.size()) {
145+
std::cerr << "Missing messages at index " << i << ": expected '" << expected[i] << "'" << std::endl;
146+
break;
147+
}
148+
if (messages[i] != expected[i]) {
149+
std::cerr << "Mismatch at index " << i << ": expected '" << expected[i] << "', got '" << messages[i] << "'" << std::endl;
150+
break;
151+
}
140152
}
141-
std::cerr << std::endl << "But we expected:" << std::endl;
142-
for (const auto& message : expected) {
143-
std::cerr << message << std::endl;
153+
if (messages.size() > expected.size()) {
154+
std::cerr << "Got more messages than expected, got " << messages.size() << ", expected " << expected.size() << std::endl;
144155
}
145156
throw std::runtime_error("Unexpected stats");
146157
}

0 commit comments

Comments
 (0)