Skip to content

stats: native DogStatsD support#2158

Merged
mattklein123 merged 28 commits intoenvoyproxy:masterfrom
taiki45:dog-statsd
Dec 11, 2017
Merged

stats: native DogStatsD support#2158
mattklein123 merged 28 commits intoenvoyproxy:masterfrom
taiki45:dog-statsd

Conversation

@taiki45
Copy link
Copy Markdown
Member

@taiki45 taiki45 commented Dec 5, 2017

Description:
Add new stats sink which support tagged metrics with DogStatsD format.
This feature is configurable by specifying envoy.dog_statsd in 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.

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>
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));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@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.

Copy link
Copy Markdown
Member Author

@taiki45 taiki45 Dec 5, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure that SGTM.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5937ac4.

Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
@taiki45
Copy link
Copy Markdown
Member Author

taiki45 commented Dec 6, 2017

hmm... continuous-integration/travis-ci seems not working? It's waiting forever...

@mattklein123 mattklein123 reopened this Dec 6, 2017
@mattklein123
Copy link
Copy Markdown
Member

@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>
@taiki45
Copy link
Copy Markdown
Member Author

taiki45 commented Dec 6, 2017

OK, I've resolved the conflicts and pushed the merge commit.

@htuch
Copy link
Copy Markdown
Member

htuch commented Dec 6, 2017

Looks like //test/integration:integration_test is timing out on OS X, but I'd hazard a guess that this is not your PR and is general flake, I will kick it again.

Copy link
Copy Markdown
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored approach looks good.

if (i == 0) {
tagStr.append(fmt::format("{}:{}", tag.name_, tag.value_));
} else {
tagStr.append(fmt::format(",{}:{}", tag.name_, tag.value_));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

@taiki45 taiki45 Dec 7, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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...

return {};
}

std::string tagStr = "|#";
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snake rather than camel case for locals, i.e. tag_str.

}

const std::string buildTagStr(const std::vector<Tag>& tags) {
if (tags.empty()) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be use_tag_ || tags.empty() to preserve existing behavior?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. 80560ce


ThreadLocal::SlotPtr tls_;
Network::Address::InstanceConstSharedPtr server_address_;
bool useTag_;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Snake rather than camel for member variables, i.e. use_tag_.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, this can be const bool.

ON_CALL(counter, tagExtractedName()).WillByDefault(ReturnRef(name));
ON_CALL(counter, tags()).WillByDefault(ReturnRef(tags));

sink.flushCounter(counter, 1);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, agree to add the validation test. I'll try it in this PR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, this would be a nice improvement to the existing tests.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
htuch pushed a commit to envoyproxy/data-plane-api that referenced this pull request Dec 7, 2017
For envoyproxy/envoy#2158.

Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
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));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we avoid the \n for regular statsd? i.e. only add it in buildTagStr() if use_tag_?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice point. Actually we don't need trailing newline, so just dropped it. b664048

for (const Tag& tag : tags) {
if (i == 0) {
std::vector<std::string> v = {tag.name_, tag.value_};
tag_str.append(StringUtil::join(v, ":"));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally looks good. A bunch of nits and a little extra test coverage needed. Thank you!


void UdpStatsdSink::flushCounter(const Counter& counter, uint64_t delta) {
tls_->getTyped<Writer>().writeCounter(counter.name(), delta);
std::string message(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: const here and similar places, or just kill local variable and do inline as var not needed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, use const d3fb9ef.


const std::string UdpStatsdSink::buildTagStr(const std::vector<Tag>& tags) {
if (!use_tag_ || tags.empty()) {
return {};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I would probably use "" here as that is more idiomatic.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed: 0109362

return {};
}

std::vector<std::string> tag_strs;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/tag_strs/tag_strings

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){};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: del ';' at end of line

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)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/w/writer


class MockWriter : public Writer {
public:
MOCK_METHOD1(write, void(const std::string& m));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: s/m/message

EXPECT_NE(fd, -1);

// Check that fd has not changed.
std::vector<Tag> tags;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, how about this one? 85dab7f

ON_CALL(counter, tags()).WillByDefault(ReturnRef(tags));
EXPECT_CALL(*std::dynamic_pointer_cast<NiceMock<MockWriter>>(writer_ptr),
write("envoy.test_counter:1|c"));
;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove ';' on lines by itself in this file.

namespace Server {
namespace Configuration {

Stats::SinkPtr DogStatsdSinkFactory::createStatsSink(const Protobuf::Message& config,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks!

@mattklein123 mattklein123 merged commit 112f6c6 into envoyproxy:master Dec 11, 2017
@taiki45
Copy link
Copy Markdown
Member Author

taiki45 commented Dec 12, 2017

Thank you all too!

@taiki45 taiki45 deleted the dog-statsd branch December 12, 2017 05:07
Shikugawa pushed a commit to Shikugawa/envoy that referenced this pull request Mar 28, 2020
* 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>
Elite1015 pushed a commit to Elite1015/data-plane-api that referenced this pull request Feb 23, 2025
For envoyproxy/envoy#2158.

Signed-off-by: Taiki Ono <taiks.4559@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants