Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions source/extensions/dynamic_modules/abi/abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -13657,6 +13657,115 @@ bool envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout(
char* name_buffer, size_t name_buffer_capacity, size_t* name_size, char* value_buffer,
size_t value_buffer_capacity, size_t* value_size);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_extracted_name writes the
* tag-extracted name of a counter at the given index. The tag-extracted name is the stat name with
* the tag values removed (for example "cluster.foo.bar" with a "cluster_name" tag extracted becomes
* "cluster.bar"), so a module can reconstruct the same name and labels that Envoy's built-in stat
* formatters produce. Pair this with the tag callbacks below.
*
* @param snapshot_envoy_ptr is the opaque snapshot handle.
* @param index is the index of the counter (0-based).
* @param name_buffer is the module-owned buffer that receives the tag-extracted name. No null
* terminator is written. May be null only if name_buffer_capacity is 0.
* @param name_buffer_capacity is the capacity of name_buffer in bytes.
* @param name_size is set to the full length of the name, with the same truncation contract as the
* name callbacks above. Must not be null.
* @return true if the index is valid, false otherwise. When false, no outputs are written.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_extracted_name(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
char* name_buffer, size_t name_buffer_capacity, size_t* name_size);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_count returns the number of
* tags on the counter at the given index.
*
* @param snapshot_envoy_ptr is the opaque snapshot handle.
* @param index is the index of the counter (0-based).
* @param tag_count is set to the number of tags on the counter. Must not be null.
* @return true if the index is valid, false otherwise. When false, tag_count is not written.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_count(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
size_t* tag_count);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag writes the name and value of a
* single tag on the counter at the given index into module-provided buffers.
*
* @param snapshot_envoy_ptr is the opaque snapshot handle.
* @param index is the index of the counter (0-based).
* @param tag_index is the index of the tag (0-based, less than the count reported by
* envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_count).
* @param name_buffer is the module-owned buffer that receives the tag name. No null terminator is
* written. May be null only if name_buffer_capacity is 0.
* @param name_buffer_capacity is the capacity of name_buffer in bytes.
* @param name_size is set to the full length of the tag name, with the same truncation contract as
* the name callbacks above. Must not be null.
* @param value_buffer is the module-owned buffer that receives the tag value. No null terminator is
* written. May be null only if value_buffer_capacity is 0.
* @param value_buffer_capacity is the capacity of value_buffer in bytes.
* @param value_size is set to the full length of the tag value, with the same truncation contract.
* Must not be null.
* @return true if both index and tag_index are valid, false otherwise. When false, no outputs are
* written.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
size_t tag_index, char* name_buffer, size_t name_buffer_capacity, size_t* name_size,
char* value_buffer, size_t value_buffer_capacity, size_t* value_size);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_extracted_name is the gauge
* counterpart of the counter tag-extracted-name callback above.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_extracted_name(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
char* name_buffer, size_t name_buffer_capacity, size_t* name_size);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_count is the gauge counterpart of
* the counter tag-count callback above.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_count(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
size_t* tag_count);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag is the gauge counterpart of the
* counter tag callback above.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
size_t tag_index, char* name_buffer, size_t name_buffer_capacity, size_t* name_size,
char* value_buffer, size_t value_buffer_capacity, size_t* value_size);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_extracted_name is the text
* readout counterpart of the counter tag-extracted-name callback above.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_extracted_name(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
char* name_buffer, size_t name_buffer_capacity, size_t* name_size);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_count is the text readout
* counterpart of the counter tag-count callback above.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_count(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
size_t* tag_count);

/**
* envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag is the text readout
* counterpart of the counter tag callback above.
*/
bool envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr snapshot_envoy_ptr, size_t index,
size_t tag_index, char* name_buffer, size_t name_buffer_capacity, size_t* name_size,
char* value_buffer, size_t value_buffer_capacity, size_t* value_size);

/**
* envoy_dynamic_module_callback_stat_sink_config_define_gauge creates a gauge with the given name
* that the module can update later via envoy_dynamic_module_callback_stat_sink_config_set_gauge.
Expand Down
72 changes: 72 additions & 0 deletions source/extensions/dynamic_modules/abi_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3821,6 +3821,78 @@ __attribute__((weak)) bool envoy_dynamic_module_callback_stat_sink_snapshot_get_
return false;
}

__attribute__((weak)) bool
envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_extracted_name(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, char*, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_extracted_name: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool
envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_count(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_count: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, size_t, char*, size_t, size_t*,
char*, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool
envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_extracted_name(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, char*, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_extracted_name: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_count(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_count: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, size_t, char*, size_t, size_t*,
char*, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool
envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_extracted_name(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, char*, size_t, size_t*) {
IS_ENVOY_BUG(
"envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_extracted_name: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool
envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_count(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_count: "
"not implemented in this context");
return false;
}

__attribute__((weak)) bool envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag(
envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr, size_t, size_t, char*, size_t, size_t*,
char*, size_t, size_t*) {
IS_ENVOY_BUG("envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag: "
"not implemented in this context");
return false;
}

__attribute__((weak)) envoy_dynamic_module_type_metrics_result
envoy_dynamic_module_callback_stat_sink_config_define_gauge(
envoy_dynamic_module_type_stat_sink_config_envoy_ptr, envoy_dynamic_module_type_module_buffer,
Expand Down
183 changes: 183 additions & 0 deletions source/extensions/dynamic_modules/sdk/rust/src/lib_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6736,6 +6736,12 @@ const STUB_COUNTERS: [(&str, u64, u64); 2] = [("counter_0", 10, 5), ("counter_1"
const STUB_GAUGES: [(&str, u64); 1] = [("gauge_0", 42)];
const STUB_TEXT_READOUTS: [(&str, &str); 1] = [("text_0", "value_0")];

// Tag-extracted names and tags, indexed to match STUB_COUNTERS. counter_0 carries two tags,
// counter_1 carries none, exercising both the empty and multi-tag paths.
const STUB_COUNTER_TAG_EXTRACTED_NAMES: [&str; 2] = ["cluster.rq_total", "counter_1"];
const STUB_COUNTER_TAGS: [&[(&str, &str)]; 2] =
[&[("envoy.cluster_name", "foo"), ("envoy.response_code", "200")], &[]];

// Counts stub_write invocations on the calling thread so tests can assert the grow-and-retry
// behavior (each ABI getter call writes its name once, plus a value for text readouts). It is
// thread-local so it stays correct even if tests run in parallel.
Expand Down Expand Up @@ -6800,6 +6806,155 @@ pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_counter(
true
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_extracted_name(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
index: usize,
name_buffer: *mut std::ffi::c_char,
name_buffer_capacity: usize,
name_size: *mut usize,
) -> bool {
if index >= STUB_COUNTER_TAG_EXTRACTED_NAMES.len() {
return false;
}
unsafe {
stub_write(
STUB_COUNTER_TAG_EXTRACTED_NAMES[index],
name_buffer,
name_buffer_capacity,
name_size,
);
}
true
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag_count(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
index: usize,
tag_count: *mut usize,
) -> bool {
if index >= STUB_COUNTER_TAGS.len() {
return false;
}
unsafe {
*tag_count = STUB_COUNTER_TAGS[index].len();
}
true
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_counter_tag(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
index: usize,
tag_index: usize,
name_buffer: *mut std::ffi::c_char,
name_buffer_capacity: usize,
name_size: *mut usize,
value_buffer: *mut std::ffi::c_char,
value_buffer_capacity: usize,
value_size: *mut usize,
) -> bool {
if index >= STUB_COUNTER_TAGS.len() || tag_index >= STUB_COUNTER_TAGS[index].len() {
return false;
}
let (name, value) = STUB_COUNTER_TAGS[index][tag_index];
unsafe {
stub_write(name, name_buffer, name_buffer_capacity, name_size);
stub_write(value, value_buffer, value_buffer_capacity, value_size);
}
true
}

// Gauge and text-readout tag callbacks share the counter tag code paths, so the harness stubs
// them as empty (no tags) purely to satisfy linkage; the counter stubs above carry the tag data
// the tests exercise.
#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_extracted_name(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
index: usize,
name_buffer: *mut std::ffi::c_char,
name_buffer_capacity: usize,
name_size: *mut usize,
) -> bool {
if index >= STUB_GAUGES.len() {
return false;
}
unsafe { stub_write(STUB_GAUGES[index].0, name_buffer, name_buffer_capacity, name_size) };
true
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag_count(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
index: usize,
tag_count: *mut usize,
) -> bool {
if index >= STUB_GAUGES.len() {
return false;
}
unsafe { *tag_count = 0 };
true
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_tag(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
_index: usize,
_tag_index: usize,
_name_buffer: *mut std::ffi::c_char,
_name_buffer_capacity: usize,
_name_size: *mut usize,
_value_buffer: *mut std::ffi::c_char,
_value_buffer_capacity: usize,
_value_size: *mut usize,
) -> bool {
false
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_extracted_name(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
index: usize,
name_buffer: *mut std::ffi::c_char,
name_buffer_capacity: usize,
name_size: *mut usize,
) -> bool {
if index >= STUB_TEXT_READOUTS.len() {
return false;
}
unsafe { stub_write(STUB_TEXT_READOUTS[index].0, name_buffer, name_buffer_capacity, name_size) };
true
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag_count(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
index: usize,
tag_count: *mut usize,
) -> bool {
if index >= STUB_TEXT_READOUTS.len() {
return false;
}
unsafe { *tag_count = 0 };
true
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_text_readout_tag(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
_index: usize,
_tag_index: usize,
_name_buffer: *mut std::ffi::c_char,
_name_buffer_capacity: usize,
_name_size: *mut usize,
_value_buffer: *mut std::ffi::c_char,
_value_buffer_capacity: usize,
_value_size: *mut usize,
) -> bool {
false
}

#[no_mangle]
pub extern "C" fn envoy_dynamic_module_callback_stat_sink_snapshot_get_gauge_count(
_snapshot: abi::envoy_dynamic_module_type_stat_sink_snapshot_envoy_ptr,
Expand Down Expand Up @@ -6958,6 +7113,34 @@ fn test_metric_snapshot_reads_all_entry_types() {
assert_eq!(text_value.as_slice(), b"value_0");
}

#[test]
fn test_metric_snapshot_reads_tags() {
let mut dummy = 0u8;
let snapshot = stats_sink::MetricSnapshot::new(&mut dummy as *mut _ as *mut std::ffi::c_void);

let mut name = Vec::new();
let mut value = Vec::new();

// counter_0: tag-extracted name plus two tags.
assert!(snapshot.counter_tag_extracted_name(0, &mut name));
assert_eq!(name.as_slice(), b"cluster.rq_total");
assert_eq!(snapshot.counter_tag_count(0), Some(2));
assert!(snapshot.counter_tag(0, 0, &mut name, &mut value));
assert_eq!(name.as_slice(), b"envoy.cluster_name");
assert_eq!(value.as_slice(), b"foo");
assert!(snapshot.counter_tag(0, 1, &mut name, &mut value));
assert_eq!(name.as_slice(), b"envoy.response_code");
assert_eq!(value.as_slice(), b"200");

// counter_1: no tags.
assert_eq!(snapshot.counter_tag_count(1), Some(0));

// Out-of-range metric and tag indices return None/false.
assert_eq!(snapshot.counter_tag_count(2), None);
assert!(!snapshot.counter_tag(0, 2, &mut name, &mut value));
assert!(!snapshot.counter_tag(2, 0, &mut name, &mut value));
}

#[test]
fn test_metric_snapshot_reuses_buffer_without_reallocating() {
let mut dummy = 0u8;
Expand Down
Loading
Loading