Skip to content

Commit 29bb77a

Browse files
committed
fixes
Signed-off-by: Greg Greenway <[email protected]>
1 parent ce0bfb8 commit 29bb77a

File tree

7 files changed

+127
-12
lines changed

7 files changed

+127
-12
lines changed

api/envoy/extensions/access_loggers/stats/v3/stats.proto

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/acc
1616
option (udpa.annotations.file_status).package_version_status = ACTIVE;
1717

1818
// [#protodoc-title: Stats logger]
19-
// Configuration for an access logger that emits customEnvoy stats according to its
19+
// Configuration for an access logger that emits custom Envoy stats according to its
2020
// configuration. The stats can have tags and values derived from
2121
// :ref:`command operators <config_access_log_command_operators>`.
2222
// [#extension: envoy.access_loggers.stats]
@@ -91,7 +91,7 @@ message Config {
9191
google.protobuf.UInt64Value value_fixed = 3 [(validate.rules).uint64 = {gt: 0}];
9292
}
9393

94-
// The stat prefix for all stats in this logger.
94+
// The stat prefix for the generated stats.
9595
string stat_prefix = 1 [(validate.rules).string = {min_len: 1}];
9696

9797
// The histograms this logger will emit.

source/extensions/access_loggers/stats/stats.cc

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ StatsAccessLog::DynamicTag::DynamicTag(
9292
Stats::StatNamePool& pool, const std::vector<Formatter::CommandParserPtr>& commands)
9393
: name_(pool.add(tag_cfg.name())),
9494
value_formatter_(THROW_OR_RETURN_VALUE(
95-
Formatter::FormatterImpl::create(tag_cfg.value_format(), false, commands),
95+
Formatter::FormatterImpl::create(tag_cfg.value_format(), true, commands),
9696
Formatter::FormatterPtr)) {}
9797

9898
std::pair<Stats::StatNameTagVector, std::vector<Stats::StatNameDynamicStorage>>
@@ -143,7 +143,12 @@ absl::optional<uint64_t> getFormatValue(const Formatter::FormatterProvider& form
143143

144144
void StatsAccessLog::emitLog(const Formatter::Context& context,
145145
const StreamInfo::StreamInfo& stream_info) {
146-
for (auto& histogram : histograms_) {
146+
emitLogConst(context, stream_info);
147+
}
148+
149+
void StatsAccessLog::emitLogConst(const Formatter::Context& context,
150+
const StreamInfo::StreamInfo& stream_info) const {
151+
for (const auto& histogram : histograms_) {
147152
absl::optional<uint64_t> computed_value_opt =
148153
getFormatValue(*histogram.value_formatter_, context, stream_info,
149154
histogram.unit_ == Stats::Histogram::Unit::Percent);
@@ -159,7 +164,7 @@ void StatsAccessLog::emitLog(const Formatter::Context& context,
159164
histogram_stat.recordValue(value);
160165
}
161166

162-
for (auto& counter : counters_) {
167+
for (const auto& counter : counters_) {
163168
uint64_t value;
164169
if (counter.value_formatter_ != nullptr) {
165170
absl::optional<uint64_t> computed_value_opt =

source/extensions/access_loggers/stats/stats.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,11 @@ class StatsAccessLog : public Common::ImplBase {
2222
void emitLog(const Formatter::Context& context,
2323
const StreamInfo::StreamInfo& stream_info) override;
2424

25+
// `emitLog` is called concurrently from different works. Move all the logic into a const function
26+
// to ensure there are no data races in mutation of class members.
27+
void emitLogConst(const Formatter::Context& context,
28+
const StreamInfo::StreamInfo& stream_info) const;
29+
2530
class DynamicTag {
2631
public:
2732
DynamicTag(const envoy::extensions::access_loggers::stats::v3::Config::Tag& tag_cfg,

test/extensions/access_loggers/stats/integration_test.cc

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,53 @@ TEST_P(StatsAccessLogIntegrationTest, Basic) {
8282
TestUtility::readSampleSum(test_server_->server().dispatcher(), *histogram)));
8383
}
8484

85+
// Trigger simultaneous logs on multiple workers to trigger TSAN errors if present.
86+
TEST_P(StatsAccessLogIntegrationTest, Concurrency) {
87+
concurrency_ = 2;
88+
const std::string config_yaml = R"EOF(
89+
name: envoy.access_loggers.stats
90+
typed_config:
91+
"@type": type.googleapis.com/envoy.extensions.access_loggers.stats.v3.Config
92+
stat_prefix: test_stat_prefix
93+
counters:
94+
- stat:
95+
name: formatcounter
96+
value_format: '%RESPONSE_CODE%'
97+
histograms:
98+
- stat:
99+
name: testhistogram
100+
tags:
101+
- name: tag
102+
value_format: '%REQUEST_HEADER(tag-value)%'
103+
value_format: '%REQUEST_HEADER(histogram-value)%'
104+
105+
)EOF";
106+
107+
init(config_yaml);
108+
109+
const Http::TestRequestHeaderMapImpl request_headers{
110+
{":method", "GET"}, {":authority", "envoyproxy.io"}, {":path", "/test/long/url"},
111+
{":scheme", "http"}, {"tag-value", "mytagvalue"}, {"counter-value", "7"},
112+
{"histogram-value", "2"},
113+
};
114+
115+
std::vector<std::thread> threads;
116+
for (uint32_t i = 0; i < 10; i++) {
117+
threads.emplace_back([&]() {
118+
for (uint32_t requests = 0; requests < 10; requests++) {
119+
BufferingStreamDecoderPtr response = IntegrationUtil::makeSingleRequest(
120+
lookupPort("http"), "GET", "/", "", downstream_protocol_, version_, "envoyproxy.io");
121+
ASSERT_TRUE(response->complete());
122+
EXPECT_EQ("200", response->headers().getStatusValue());
123+
}
124+
});
125+
}
126+
127+
for (auto& t : threads) {
128+
t.join();
129+
}
130+
}
131+
85132
TEST_P(StatsAccessLogIntegrationTest, PercentHistogram) {
86133
const std::string config_yaml = R"EOF(
87134
name: envoy.access_loggers.stats

test/extensions/access_loggers/stats/stats_test.cc

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,11 @@ class StatsAccessLoggerTest : public testing::Test {
4343
}
4444

4545
void initialize(const envoy::extensions::access_loggers::stats::v3::Config& config) {
46-
// ON_CALL(context_, statsScope()).WillByDefault(testing::ReturnRef(root_scope_));
4746
ON_CALL(context_, statsScope()).WillByDefault(testing::ReturnRef(store_.mockScope()));
4847
EXPECT_CALL(store_.mockScope(), createScope_(_))
4948
.WillOnce(Invoke([this](const std::string& name) {
5049
Stats::StatNameDynamicStorage storage(name, context_.store_.symbolTable());
51-
scope_ = std::make_shared<Stats::MockScope>(storage.statName(), store_);
50+
scope_ = std::make_shared<NiceMock<Stats::MockScope>>(storage.statName(), store_);
5251
return scope_;
5352
}));
5453

@@ -217,6 +216,24 @@ TEST_F(StatsAccessLoggerTest, NumberStringValueFormatted) {
217216
logger_->log(formatter_context_, stream_info_);
218217
}
219218

219+
TEST_F(StatsAccessLoggerTest, CounterValueFixed) {
220+
const std::string yaml = R"EOF(
221+
stat_prefix: test_stat_prefix
222+
counters:
223+
- stat:
224+
name: counter
225+
value_fixed: 42
226+
)EOF";
227+
228+
initialize(yaml);
229+
230+
absl::optional<std::string> a_number{"42"};
231+
EXPECT_CALL(stream_info_, responseCodeDetails()).WillRepeatedly(testing::ReturnRef(a_number));
232+
EXPECT_CALL(store_, counter(_));
233+
EXPECT_CALL(store_.counter_, add(42));
234+
logger_->log(formatter_context_, stream_info_);
235+
}
236+
220237
// Histogram values are in the range 0-1.0, so ensure that fractional values work.
221238
TEST_F(StatsAccessLoggerTest, HistogramPercent) {
222239
const std::string yaml = R"EOF(
@@ -248,6 +265,38 @@ TEST_F(StatsAccessLoggerTest, HistogramPercent) {
248265
logger_->log(formatter_context_, stream_info_);
249266
}
250267

268+
// Test that a tag formatter that doesn't have a value becomes an empty string.
269+
TEST_F(StatsAccessLoggerTest, EmptyTagFormatter) {
270+
const std::string yaml = R"EOF(
271+
stat_prefix: test_stat_prefix
272+
counters:
273+
- stat:
274+
name: counter
275+
tags:
276+
- name: tag
277+
value_format: '%RESPONSE_CODE_DETAILS%:%RESPONSE_CODE%'
278+
value_fixed: 1
279+
)EOF";
280+
281+
initialize(yaml);
282+
283+
absl::optional<std::string> nullopt{absl::nullopt};
284+
EXPECT_CALL(stream_info_, responseCodeDetails()).WillRepeatedly(testing::ReturnRef(nullopt));
285+
EXPECT_CALL(stream_info_, responseCode())
286+
.WillRepeatedly(testing::Return(absl::optional<uint32_t>{200}));
287+
EXPECT_CALL(*scope_, counterFromStatNameWithTags(_, _))
288+
.WillOnce(
289+
testing::Invoke([this](const Stats::StatName& name,
290+
Stats::StatNameTagVectorOptConstRef tags) -> Stats::Counter& {
291+
EXPECT_EQ("counter", scope_->symbolTable().toString(name));
292+
EXPECT_EQ(1, tags->get().size());
293+
EXPECT_EQ(":200", scope_->symbolTable().toString(tags->get().front().second));
294+
295+
return scope_->counterFromStatNameWithTags_(name, tags);
296+
}));
297+
logger_->log(formatter_context_, stream_info_);
298+
}
299+
251300
} // namespace StatsAccessLog
252301
} // namespace AccessLoggers
253302
} // namespace Extensions

test/mocks/stats/mocks.cc

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,16 @@ MockSinkPredicates::MockSinkPredicates() = default;
8282
MockSinkPredicates::~MockSinkPredicates() = default;
8383

8484
MockScope::MockScope(StatName prefix, MockStore& store)
85-
: TestUtil::TestScope(prefix, store), mock_store_(store) {}
85+
: TestUtil::TestScope(prefix, store), mock_store_(store) {
86+
ON_CALL(*this, counterFromStatNameWithTags(_, _))
87+
.WillByDefault(
88+
Invoke([this](const StatName& name, StatNameTagVectorOptConstRef tags) -> Counter& {
89+
return counterFromStatNameWithTags_(name, tags);
90+
}));
91+
}
8692

87-
Counter& MockScope::counterFromStatNameWithTags(const StatName& name,
88-
StatNameTagVectorOptConstRef) {
93+
Counter& MockScope::counterFromStatNameWithTags_(const StatName& name,
94+
StatNameTagVectorOptConstRef) {
8995
// We always just respond with the mocked counter, so the tags don't matter.
9096
return mock_store_.counter(symbolTable().toString(name));
9197
}
@@ -121,7 +127,7 @@ MockStore::MockStore() {
121127
MockStore::~MockStore() = default;
122128

123129
ScopeSharedPtr MockStore::makeScope(StatName prefix) {
124-
return std::make_shared<MockScope>(prefix, *this);
130+
return std::make_shared<NiceMock<MockScope>>(prefix, *this);
125131
}
126132

127133
MockIsolatedStatsStore::MockIsolatedStatsStore() = default;

test/mocks/stats/mocks.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,10 @@ class MockScope : public TestUtil::TestScope {
312312
// Override the lowest level of stat creation based on StatName to redirect
313313
// back to the old string-based mechanisms still on the MockStore object
314314
// to allow tests to inject EXPECT_CALL hooks for those.
315-
Counter& counterFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef) override;
315+
MOCK_METHOD(Counter&, counterFromStatNameWithTags,
316+
(const StatName&, StatNameTagVectorOptConstRef));
317+
Counter& counterFromStatNameWithTags_(const StatName& name, StatNameTagVectorOptConstRef);
318+
316319
Gauge& gaugeFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef,
317320
Gauge::ImportMode import_mode) override;
318321
Histogram& histogramFromStatNameWithTags(const StatName& name, StatNameTagVectorOptConstRef,

0 commit comments

Comments
 (0)