stats: native DogStatsD support#2158
Conversation
Add new stats sink which supports tagged metrics with DogStatsD format. At the first moment, the sink only supports UDP output. Tested with stastd-exporter v0.5.0 and Prometheus v2.0.0. Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
source/common/stats/dog_statsd.cc
Outdated
| Writer::Writer(Network::Address::InstanceConstSharedPtr address) : Statsd::Writer(address) {} | ||
|
|
||
| void Writer::writeCounter(const std::string& name, const std::string& tag_str, uint64_t increment) { | ||
| std::string message(fmt::format("envoy.{}:{}|c{}\n", name, increment, tag_str)); |
There was a problem hiding this comment.
At a high-level, wondering if you could comment on the tradeoff between doing this as a separate sink vs. making this a configurable option on the existing statsd sink? It seems the formats are fairly similar, with regular `statsd a subset. Don't mind a separate sink if it makes more sense.
There was a problem hiding this comment.
Right, it's another option to implement this. In my opinion, pros of a separate sink: dedicated sink confiuration brings future extensibility on configuration around tagging. Then, pros of exteding existing statsd sink: compact code base and configuration.
I think we have to think which is more user-friendly idea not from point of code-base. In my current opinion, since the original issue #2024 starts from a new sink with dedicated configuration, the new sink seems more human natural idea. What do you think?
For code-base or design points, I'm welcome to any advices and will make improvements.
There was a problem hiding this comment.
@taiki45 I think having a dedicated configuration makes sense from the user perspective. I think the main question is whether to have a shared base class for the implementation (or just one implementation with some functionality variable built in). I don't really have that strong of an opinion either way as the code is pretty self contained.
There was a problem hiding this comment.
Aha, now I got the question. In this case, for code parspective, I agree to change current implementation because inheritting from concrete class (Envoy::Stats::Statsd::UdpStatsdSink class here) slightly smells bad for me.
I'm thinking about using and extending existing Envoy::Stats::Statsd::UdpStatsdSink instead of having a shared base class. Any advices?
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
|
hmm... |
|
@taiki45 sometimes CI is just broken. You need to merge master anyway so just push a new commit and it will probably fix itself. |
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
|
OK, I've resolved the conflicts and pushed the merge commit. |
|
Looks like |
htuch
left a comment
There was a problem hiding this comment.
Refactored approach looks good.
source/common/stats/statsd.cc
Outdated
| if (i == 0) { | ||
| tagStr.append(fmt::format("{}:{}", tag.name_, tag.value_)); | ||
| } else { | ||
| tagStr.append(fmt::format(",{}:{}", tag.name_, tag.value_)); |
There was a problem hiding this comment.
I'd recommend using https://github.com/envoyproxy/envoy/blob/master/source/common/common/utility.h#L140 here to simplify.
There was a problem hiding this comment.
Good. be26f9f
Also, I'm curious about better ways to join std::vector<Tag>& tags with "," into single string, joining each tag with ":". I did some googling but I can't found the way...
source/common/stats/statsd.cc
Outdated
| return {}; | ||
| } | ||
|
|
||
| std::string tagStr = "|#"; |
There was a problem hiding this comment.
Snake rather than camel case for locals, i.e. tag_str.
source/common/stats/statsd.cc
Outdated
| } | ||
|
|
||
| const std::string buildTagStr(const std::vector<Tag>& tags) { | ||
| if (tags.empty()) { |
There was a problem hiding this comment.
Should this be use_tag_ || tags.empty() to preserve existing behavior?
source/common/stats/statsd.h
Outdated
|
|
||
| ThreadLocal::SlotPtr tls_; | ||
| Network::Address::InstanceConstSharedPtr server_address_; | ||
| bool useTag_; |
There was a problem hiding this comment.
Snake rather than camel for member variables, i.e. use_tag_.
| ON_CALL(counter, tagExtractedName()).WillByDefault(ReturnRef(name)); | ||
| ON_CALL(counter, tags()).WillByDefault(ReturnRef(tags)); | ||
|
|
||
| sink.flushCounter(counter, 1); |
There was a problem hiding this comment.
I don't see the actual expected statsd raw output in any of these tests? Shouldn't we have something like this?
I think this is an issue with the existing tests; there's no validation that we're actually building the correct string.
There was a problem hiding this comment.
Yes, agree to add the validation test. I'll try it in this PR.
There was a problem hiding this comment.
Thanks, this would be a nice improvement to the existing tests.
There was a problem hiding this comment.
It was a little bit difficult for me, but I tried with changing Writer's responsibility.
How about this one: e3487df
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Also use `const` for bool. Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
For envoyproxy/envoy#2158. Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
source/common/stats/statsd.cc
Outdated
| void Writer::writeCounter(const std::string& name, uint64_t increment) { | ||
| std::string message(fmt::format("envoy.{}:{}|c", name, increment)); | ||
| void Writer::writeCounter(const std::string& name, const std::string& tag_str, uint64_t increment) { | ||
| std::string message(fmt::format("envoy.{}:{}|c{}\n", name, increment, tag_str)); |
There was a problem hiding this comment.
Should we avoid the \n for regular statsd? i.e. only add it in buildTagStr() if use_tag_?
There was a problem hiding this comment.
Nice point. Actually we don't need trailing newline, so just dropped it. b664048
source/common/stats/statsd.cc
Outdated
| for (const Tag& tag : tags) { | ||
| if (i == 0) { | ||
| std::vector<std::string> v = {tag.name_, tag.value_}; | ||
| tag_str.append(StringUtil::join(v, ":")); |
There was a problem hiding this comment.
The idea with join is you do something like:
std::vector<std::string> tag_strs;
for (const Tag& tag : tags) {
tag_strs.emplace_back(tag._name_ + ":" + tag._value);
}
return "|#" + StringUtil::join(tag_strs, ",");
This is probably less performant than what you have, but more concise. Since this is only at statsd flush time, it's probably fine; if we hit scaling issues then we can micro-optimize.
There was a problem hiding this comment.
Since this is only at statsd flush time, it's probably fine
Agreed. Refine with 3c38668.
| ON_CALL(counter, tagExtractedName()).WillByDefault(ReturnRef(name)); | ||
| ON_CALL(counter, tags()).WillByDefault(ReturnRef(tags)); | ||
|
|
||
| sink.flushCounter(counter, 1); |
There was a problem hiding this comment.
Thanks, this would be a nice improvement to the existing tests.
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Remove process of building stats string from Writer and it now only holds a fd. Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
mattklein123
left a comment
There was a problem hiding this comment.
Generally looks good. A bunch of nits and a little extra test coverage needed. Thank you!
source/common/stats/statsd.cc
Outdated
|
|
||
| void UdpStatsdSink::flushCounter(const Counter& counter, uint64_t delta) { | ||
| tls_->getTyped<Writer>().writeCounter(counter.name(), delta); | ||
| std::string message( |
There was a problem hiding this comment.
nit: const here and similar places, or just kill local variable and do inline as var not needed.
source/common/stats/statsd.cc
Outdated
|
|
||
| const std::string UdpStatsdSink::buildTagStr(const std::vector<Tag>& tags) { | ||
| if (!use_tag_ || tags.empty()) { | ||
| return {}; |
There was a problem hiding this comment.
nit: I would probably use "" here as that is more idiomatic.
source/common/stats/statsd.cc
Outdated
| return {}; | ||
| } | ||
|
|
||
| std::vector<std::string> tag_strs; |
There was a problem hiding this comment.
nit: s/tag_strs/tag_strings
source/common/stats/statsd.h
Outdated
| void writeGauge(const std::string& name, uint64_t value); | ||
| void writeTimer(const std::string& name, const std::chrono::milliseconds& ms); | ||
| // For testing. | ||
| Writer() : fd_(-1){}; |
There was a problem hiding this comment.
nit: del ';' at end of line
source/common/stats/statsd.h
Outdated
| UdpStatsdSink(ThreadLocal::SlotAllocator& tls, Network::Address::InstanceConstSharedPtr address, | ||
| const bool use_tag); | ||
| // For testing. | ||
| UdpStatsdSink(ThreadLocal::SlotAllocator& tls, std::shared_ptr<Writer> w, const bool use_tag) |
test/common/stats/udp_statsd_test.cc
Outdated
|
|
||
| class MockWriter : public Writer { | ||
| public: | ||
| MOCK_METHOD1(write, void(const std::string& m)); |
test/common/stats/udp_statsd_test.cc
Outdated
| EXPECT_NE(fd, -1); | ||
|
|
||
| // Check that fd has not changed. | ||
| std::vector<Tag> tags; |
There was a problem hiding this comment.
I would recommend moving this into the base mock class, and then putting the ON_CALL WillByDefault into the constructor, so that it can be use by others later.
test/common/stats/udp_statsd_test.cc
Outdated
| ON_CALL(counter, tags()).WillByDefault(ReturnRef(tags)); | ||
| EXPECT_CALL(*std::dynamic_pointer_cast<NiceMock<MockWriter>>(writer_ptr), | ||
| write("envoy.test_counter:1|c")); | ||
| ; |
There was a problem hiding this comment.
nit: remove ';' on lines by itself in this file.
| namespace Server { | ||
| namespace Configuration { | ||
|
|
||
| Stats::SinkPtr DogStatsdSinkFactory::createStatsSink(const Protobuf::Message& config, |
There was a problem hiding this comment.
Please make sure that the actual registration system has coverage. You could either add this to one of the v2 integration test configurations or add dedicated config loading tests here: https://github.com/envoyproxy/envoy/blob/master/test/server/config/stats/config_test.cc. Note that you can check your coverage in CI by opening up the CI coverage build and looking at the build artifacts in CircleCI.
There was a problem hiding this comment.
Done fc4efda
Note that you can check your coverage in CI by opening up the CI coverage build and looking at the build artifacts in CircleCI.
I just didn't know, thanks!
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
|
Thank you all too! |
* update envoy to latest Signed-off-by: Lizan Zhou <lizan@tetrate.io> * update envoy with latest build fixes (envoyproxy#2147) * fix build Signed-off-by: Lizan Zhou <lizan@tetrate.io> * fix build Signed-off-by: Lizan Zhou <lizan@tetrate.io> * fix formatting * fix status match Signed-off-by: Lizan Zhou <lizan@tetrate.io>
For envoyproxy/envoy#2158. Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Description:
Add new stats sink which support tagged metrics with DogStatsD format.
This feature is configurable by specifying
envoy.dog_statsdin Bootstrap.stats_sinks.Config and documentation changes are here: envoyproxy/data-plane-api#325.
Original issue is #2024.
Risk Level:
Medium?: New features that are not enabled by default.
Testing:
unit test, and manual testing with statsd_exporter which supports DogStatsD extension and Prometheus.
Docs Changes:
envoyproxy/data-plane-api#325
Release Notes:
Added, but I'm not sure about format. I referred https://github.com/envoyproxy/data-plane-api/blob/master/docs/root/intro/version_history.rst.
Fixes #2024
API Changes:
envoyproxy/data-plane-api#325
Review Note:
I'm not a fluent English speaker. Please feel free to point out any bit of difference, thanks.