Skip to content

Commit 839fcbb

Browse files
committed
dynamic_modules: test stats sink tag callbacks end to end and fix a doc comment
Signed-off-by: Basundhara Chakrabarty <basundhara17061996@gmail.com>
1 parent ba2cee4 commit 839fcbb

5 files changed

Lines changed: 175 additions & 42 deletions

File tree

source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs

Lines changed: 87 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -6742,6 +6742,14 @@ const STUB_COUNTER_TAG_EXTRACTED_NAMES: [&str; 2] = ["cluster.rq_total", "counte
67426742
const STUB_COUNTER_TAGS: [&[(&str, &str)]; 2] =
67436743
[&[("envoy.cluster_name", "foo"), ("envoy.response_code", "200")], &[]];
67446744

6745+
// Tag data for the single stub gauge and text readout, matching STUB_GAUGES / STUB_TEXT_READOUTS.
6746+
// Both carry one tag so the gauge and text-readout tag wrappers are exercised distinctly from the
6747+
// counter ones, guarding against a wrapper wired to the wrong ABI callback.
6748+
const STUB_GAUGE_TAG_EXTRACTED_NAMES: [&str; 1] = ["cluster.cx_active"];
6749+
const STUB_GAUGE_TAGS: [&[(&str, &str)]; 1] = [&[("envoy.cluster_name", "bar")]];
6750+
const STUB_TEXT_READOUT_TAG_EXTRACTED_NAMES: [&str; 1] = ["control_plane.identifier"];
6751+
const STUB_TEXT_READOUT_TAGS: [&[(&str, &str)]; 1] = [&[("envoy.control_plane", "xds")]];
6752+
67456753
// Counts stub_write invocations on the calling thread so tests can assert the grow-and-retry
67466754
// behavior (each ABI getter call writes its name once, plus a value for text readouts). It is
67476755
// thread-local so it stays correct even if tests run in parallel.
@@ -6866,9 +6874,8 @@ pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_t
68666874
true
68676875
}
68686876

6869-
// Gauge and text-readout tag callbacks share the counter tag code paths, so the harness stubs
6870-
// them as empty (no tags) purely to satisfy linkage; the counter stubs above carry the tag data
6871-
// the tests exercise.
6877+
// The gauge and text-readout tag callbacks share the counter tag code paths, but each stub serves
6878+
// its own canned data so the corresponding SDK wrappers are exercised distinctly.
68726879
#[no_mangle]
68736880
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_extracted_name(
68746881
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
@@ -6877,10 +6884,17 @@ pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag
68776884
name_buffer_capacity: usize,
68786885
name_size: *mut usize,
68796886
) -> bool {
6880-
if index >= STUB_GAUGES.len() {
6887+
if index >= STUB_GAUGE_TAG_EXTRACTED_NAMES.len() {
68816888
return false;
68826889
}
6883-
unsafe { stub_write(STUB_GAUGES[index].0, name_buffer, name_buffer_capacity, name_size) };
6890+
unsafe {
6891+
stub_write(
6892+
STUB_GAUGE_TAG_EXTRACTED_NAMES[index],
6893+
name_buffer,
6894+
name_buffer_capacity,
6895+
name_size,
6896+
);
6897+
}
68846898
true
68856899
}
68866900

@@ -6890,26 +6904,34 @@ pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag
68906904
index: usize,
68916905
tag_count: *mut usize,
68926906
) -> bool {
6893-
if index >= STUB_GAUGES.len() {
6907+
if index >= STUB_GAUGE_TAGS.len() {
68946908
return false;
68956909
}
6896-
unsafe { *tag_count = 0 };
6910+
unsafe { *tag_count = STUB_GAUGE_TAGS[index].len() };
68976911
true
68986912
}
68996913

69006914
#[no_mangle]
69016915
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag(
69026916
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
6903-
_index: usize,
6904-
_tag_index: usize,
6905-
_name_buffer: *mut std::ffi::c_char,
6906-
_name_buffer_capacity: usize,
6907-
_name_size: *mut usize,
6908-
_value_buffer: *mut std::ffi::c_char,
6909-
_value_buffer_capacity: usize,
6910-
_value_size: *mut usize,
6917+
index: usize,
6918+
tag_index: usize,
6919+
name_buffer: *mut std::ffi::c_char,
6920+
name_buffer_capacity: usize,
6921+
name_size: *mut usize,
6922+
value_buffer: *mut std::ffi::c_char,
6923+
value_buffer_capacity: usize,
6924+
value_size: *mut usize,
69116925
) -> bool {
6912-
false
6926+
if index >= STUB_GAUGE_TAGS.len() || tag_index >= STUB_GAUGE_TAGS[index].len() {
6927+
return false;
6928+
}
6929+
let (name, value) = STUB_GAUGE_TAGS[index][tag_index];
6930+
unsafe {
6931+
stub_write(name, name_buffer, name_buffer_capacity, name_size);
6932+
stub_write(value, value_buffer, value_buffer_capacity, value_size);
6933+
}
6934+
true
69136935
}
69146936

69156937
#[no_mangle]
@@ -6920,10 +6942,17 @@ pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_text_read
69206942
name_buffer_capacity: usize,
69216943
name_size: *mut usize,
69226944
) -> bool {
6923-
if index >= STUB_TEXT_READOUTS.len() {
6945+
if index >= STUB_TEXT_READOUT_TAG_EXTRACTED_NAMES.len() {
69246946
return false;
69256947
}
6926-
unsafe { stub_write(STUB_TEXT_READOUTS[index].0, name_buffer, name_buffer_capacity, name_size) };
6948+
unsafe {
6949+
stub_write(
6950+
STUB_TEXT_READOUT_TAG_EXTRACTED_NAMES[index],
6951+
name_buffer,
6952+
name_buffer_capacity,
6953+
name_size,
6954+
);
6955+
}
69276956
true
69286957
}
69296958

@@ -6933,26 +6962,34 @@ pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_text_read
69336962
index: usize,
69346963
tag_count: *mut usize,
69356964
) -> bool {
6936-
if index >= STUB_TEXT_READOUTS.len() {
6965+
if index >= STUB_TEXT_READOUT_TAGS.len() {
69376966
return false;
69386967
}
6939-
unsafe { *tag_count = 0 };
6968+
unsafe { *tag_count = STUB_TEXT_READOUT_TAGS[index].len() };
69406969
true
69416970
}
69426971

69436972
#[no_mangle]
69446973
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag(
69456974
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
6946-
_index: usize,
6947-
_tag_index: usize,
6948-
_name_buffer: *mut std::ffi::c_char,
6949-
_name_buffer_capacity: usize,
6950-
_name_size: *mut usize,
6951-
_value_buffer: *mut std::ffi::c_char,
6952-
_value_buffer_capacity: usize,
6953-
_value_size: *mut usize,
6975+
index: usize,
6976+
tag_index: usize,
6977+
name_buffer: *mut std::ffi::c_char,
6978+
name_buffer_capacity: usize,
6979+
name_size: *mut usize,
6980+
value_buffer: *mut std::ffi::c_char,
6981+
value_buffer_capacity: usize,
6982+
value_size: *mut usize,
69546983
) -> bool {
6955-
false
6984+
if index >= STUB_TEXT_READOUT_TAGS.len() || tag_index >= STUB_TEXT_READOUT_TAGS[index].len() {
6985+
return false;
6986+
}
6987+
let (name, value) = STUB_TEXT_READOUT_TAGS[index][tag_index];
6988+
unsafe {
6989+
stub_write(name, name_buffer, name_buffer_capacity, name_size);
6990+
stub_write(value, value_buffer, value_buffer_capacity, value_size);
6991+
}
6992+
true
69566993
}
69576994

69586995
#[no_mangle]
@@ -7139,6 +7176,27 @@ fn test_metric_snapshot_reads_tags() {
71397176
assert_eq!(snapshot.counter_tag_count(2), None);
71407177
assert!(!snapshot.counter_tag(0, 2, &mut name, &mut value));
71417178
assert!(!snapshot.counter_tag(2, 0, &mut name, &mut value));
7179+
7180+
// gauge_0: one tag. Exercises the gauge tag wrappers, which are distinct entry points from the
7181+
// counter ones.
7182+
assert!(snapshot.gauge_tag_extracted_name(0, &mut name));
7183+
assert_eq!(name.as_slice(), b"cluster.cx_active");
7184+
assert_eq!(snapshot.gauge_tag_count(0), Some(1));
7185+
assert!(snapshot.gauge_tag(0, 0, &mut name, &mut value));
7186+
assert_eq!(name.as_slice(), b"envoy.cluster_name");
7187+
assert_eq!(value.as_slice(), b"bar");
7188+
assert_eq!(snapshot.gauge_tag_count(1), None);
7189+
assert!(!snapshot.gauge_tag(0, 1, &mut name, &mut value));
7190+
7191+
// text_0: one tag. Exercises the text-readout tag wrappers.
7192+
assert!(snapshot.text_readout_tag_extracted_name(0, &mut name));
7193+
assert_eq!(name.as_slice(), b"control_plane.identifier");
7194+
assert_eq!(snapshot.text_readout_tag_count(0), Some(1));
7195+
assert!(snapshot.text_readout_tag(0, 0, &mut name, &mut value));
7196+
assert_eq!(name.as_slice(), b"envoy.control_plane");
7197+
assert_eq!(value.as_slice(), b"xds");
7198+
assert_eq!(snapshot.text_readout_tag_count(1), None);
7199+
assert!(!snapshot.text_readout_tag(0, 1, &mut name, &mut value));
71427200
}
71437201

71447202
#[test]

source/extensions/dynamic_modules/sdk/rust/src/stats_sink.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ impl<'a> MetricSnapshot<'a> {
120120
}
121121

122122
/// Reads the tag-extracted name of the counter at `index` into `name` (the stat name with tag
123-
/// values removed). Returns `None` and leaves `name` unchanged when the index is out of range.
123+
/// values removed). Returns `false` and leaves `name` unchanged when the index is out of range.
124124
pub fn counter_tag_extracted_name(&self, index: usize, name: &mut Vec<u8>) -> bool {
125125
fill_buffer(name, |ptr, capacity, size| unsafe {
126126
abi::envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_extracted_name(

test/extensions/dynamic_modules/stat_sink/abi_impl_test.cc

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,8 +262,8 @@ TEST_F(DynamicModuleStatsSinkAbiTest, GetGaugeTagExtractedNameAndTags) {
262262
EXPECT_EQ("cluster.cx_active", written(name_buffer, name_size, sizeof(name_buffer)));
263263

264264
size_t tag_count = 0;
265-
EXPECT_TRUE(envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_count(
266-
snapshotHandle(), 0, &tag_count));
265+
EXPECT_TRUE(envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_count(snapshotHandle(),
266+
0, &tag_count));
267267
EXPECT_EQ(1u, tag_count);
268268

269269
char tag_name[256];
@@ -277,6 +277,36 @@ TEST_F(DynamicModuleStatsSinkAbiTest, GetGaugeTagExtractedNameAndTags) {
277277
EXPECT_EQ("bar", written(tag_value, tag_value_size, sizeof(tag_value)));
278278
}
279279

280+
// The text-readout tag callbacks reach the third snapshot collection; a spot check confirms the
281+
// wiring, mirroring the gauge check above.
282+
TEST_F(DynamicModuleStatsSinkAbiTest, GetTextReadoutTagExtractedNameAndTags) {
283+
t0_.name_ = "control_plane.foo.identifier";
284+
t0_.setTagExtractedName("control_plane.identifier");
285+
t0_.setTags({{"envoy.control_plane", "foo"}});
286+
snapshot_.text_readouts_.push_back(t0_);
287+
288+
char name_buffer[256];
289+
size_t name_size = 0;
290+
EXPECT_TRUE(envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_extracted_name(
291+
snapshotHandle(), 0, name_buffer, sizeof(name_buffer), &name_size));
292+
EXPECT_EQ("control_plane.identifier", written(name_buffer, name_size, sizeof(name_buffer)));
293+
294+
size_t tag_count = 0;
295+
EXPECT_TRUE(envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_count(
296+
snapshotHandle(), 0, &tag_count));
297+
EXPECT_EQ(1u, tag_count);
298+
299+
char tag_name[256];
300+
char tag_value[256];
301+
size_t tag_name_size = 0;
302+
size_t tag_value_size = 0;
303+
ASSERT_TRUE(envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag(
304+
snapshotHandle(), 0, 0, tag_name, sizeof(tag_name), &tag_name_size, tag_value,
305+
sizeof(tag_value), &tag_value_size));
306+
EXPECT_EQ("envoy.control_plane", written(tag_name, tag_name_size, sizeof(tag_name)));
307+
EXPECT_EQ("foo", written(tag_value, tag_value_size, sizeof(tag_value)));
308+
}
309+
280310
// =============================================================================
281311
// Gauge callbacks
282312
// =============================================================================

test/extensions/dynamic_modules/stat_sink/integration_test.cc

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,23 @@ INSTANTIATE_TEST_SUITE_P(
8888
TEST_P(DynamicModulesStatsSinkIntegrationTest, BasicFlush) {
8989
// The "found gauge server.uptime" marker proves the module decoded a gauge name through the
9090
// buffer-based snapshot API.
91-
EXPECT_LOG_CONTAINS_ALL_OF(Envoy::ExpectedLogMessages({
92-
{"info", "stat sink integration test: config_new called"},
93-
{"info", "stat sink integration test: flush called"},
94-
{"info", "stat sink integration test: found gauge server.uptime"},
95-
}),
96-
{
97-
addStatSinkAndInitialize();
98-
timeSystem().realSleepDoNotUseWithoutScrutiny(
99-
std::chrono::milliseconds(500));
100-
});
91+
Envoy::ExpectedLogMessages expected{
92+
{"info", "stat sink integration test: config_new called"},
93+
{"info", "stat sink integration test: flush called"},
94+
{"info", "stat sink integration test: found gauge server.uptime"},
95+
};
96+
if (language() == "rust") {
97+
// Only the Rust SDK exposes the tag callbacks. The "reconstructed tagged gauge" marker proves
98+
// the module read the tag-extracted name and the "envoy.cluster_name" tag of the always-present
99+
// cluster.membership_total gauge and rebuilt the dimensional name a Prometheus-style sink would
100+
// emit.
101+
expected.push_back({"info", "stat sink integration test: reconstructed tagged gauge "
102+
"cluster.membership_total envoy.cluster_name=cluster_0"});
103+
}
104+
EXPECT_LOG_CONTAINS_ALL_OF(expected, {
105+
addStatSinkAndInitialize();
106+
timeSystem().realSleepDoNotUseWithoutScrutiny(std::chrono::milliseconds(500));
107+
});
101108
}
102109

103110
TEST_P(DynamicModulesStatsSinkIntegrationTest, FlushAfterTraffic) {

test/extensions/dynamic_modules/test_data/rust/stat_sink_integration_test.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,23 +63,61 @@ impl StatSink for TestStatSink {
6363
// pattern the API enables (for example writing each name to a socket).
6464
let mut name = Vec::new();
6565
let mut value = Vec::new();
66+
let mut tag_name = Vec::new();
67+
let mut tag_value = Vec::new();
68+
// Exercise the counter tag callbacks against every counter in the live snapshot. Every tag
69+
// index the count reports must resolve; a mismatch means the count and the per-tag reader
70+
// disagree.
71+
let mut counter_tags_consistent = true;
6672
for index in 0..snapshot.counter_count() {
6773
let _ = snapshot.counter(index, &mut name);
74+
if !snapshot.counter_tag_extracted_name(index, &mut name) {
75+
counter_tags_consistent = false;
76+
continue;
77+
}
78+
let Some(tag_count) = snapshot.counter_tag_count(index) else {
79+
counter_tags_consistent = false;
80+
continue;
81+
};
82+
for tag_index in 0..tag_count {
83+
if !snapshot.counter_tag(index, tag_index, &mut tag_name, &mut tag_value) {
84+
counter_tags_consistent = false;
85+
}
86+
}
6887
}
6988
// Decode every gauge name and look for the always-present "server.uptime" gauge, which proves a
7089
// name round-trips byte-for-byte through the buffer API end to end.
7190
let mut found_uptime = false;
91+
// Reconstruct the dimensional name of a tagged gauge from its tag-extracted name and tags, the
92+
// way a Prometheus-style sink would. "cluster.cluster_0.membership_total" carries a single
93+
// "envoy.cluster_name" tag whose value is the cluster name, so the tag callbacks must yield
94+
// tag-extracted name "cluster.membership_total" and tag ("envoy.cluster_name", "cluster_0").
95+
let mut found_tagged_gauge = false;
7296
for index in 0..snapshot.gauge_count() {
7397
if snapshot.gauge(index, &mut name).is_some() && name.as_slice() == b"server.uptime" {
7498
found_uptime = true;
7599
}
100+
if snapshot.gauge_tag_extracted_name(index, &mut name)
101+
&& name.as_slice() == b"cluster.membership_total"
102+
&& snapshot.gauge_tag_count(index) == Some(1)
103+
&& snapshot.gauge_tag(index, 0, &mut tag_name, &mut tag_value)
104+
&& tag_name.as_slice() == b"envoy.cluster_name"
105+
&& tag_value.as_slice() == b"cluster_0"
106+
{
107+
found_tagged_gauge = true;
108+
}
76109
}
77110
for index in 0..snapshot.text_readout_count() {
78111
let _ = snapshot.text_readout(index, &mut name, &mut value);
79112
}
80113
if found_uptime {
81114
envoy_log_info!("stat sink integration test: found gauge server.uptime");
82115
}
116+
// A tagged gauge reconstructed from the tag callbacks, and every counter's tag count agreed
117+
// with its per-tag reads: the tag ABI round-trips end to end against a live snapshot.
118+
if found_tagged_gauge && counter_tags_consistent {
119+
envoy_log_info!("stat sink integration test: reconstructed tagged gauge cluster.membership_total envoy.cluster_name=cluster_0");
120+
}
83121
envoy_log_info!(
84122
"stat sink integration test: flush called counters={} gauges={}",
85123
snapshot.counter_count(),

0 commit comments

Comments
 (0)