Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion confidence-resolver/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ The crate does NOT use `thiserror`. It has a custom error system:
The build script compiles protos from `protos/`. Beyond standard prost generation:
- When `json` feature is enabled, `pbjson-build` generates additional `.serde.rs` files for each module
- Google well-known types are re-exported from `pbjson_types` (with `json`) or `prost_types` (without)
- It also generates `telemetry_config.rs` by scanning proto annotations for histogram configs and emitting `HistogramConfig` trait impls
- It also generates `telemetry_config.rs` with constants derived from proto descriptors (e.g. `REASON_COUNT` from the `ResolveReason` enum)

## Key Types

Expand Down
104 changes: 2 additions & 102 deletions confidence-resolver/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::fmt::Write as _;
use std::io::Result;
use std::path::PathBuf;

use prost_reflect::{DescriptorPool, Value};
use prost_reflect::DescriptorPool;

fn main() -> Result<()> {
// Suppress all clippy lints in generated proto code
Expand Down Expand Up @@ -110,79 +110,14 @@ fn main() -> Result<()> {
Ok(())
}

/// Scan proto descriptors for messages annotated with `(confidence.api.histogram)`
/// and generate `HistogramConfig` trait impls for the corresponding prost types.
/// Generate telemetry constants from proto descriptors.
fn generate_telemetry_config(descriptor_path: &std::path::Path) -> Result<()> {
let descriptor_bytes = std::fs::read(descriptor_path)?;
let pool = DescriptorPool::decode(descriptor_bytes.as_ref())
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;

let histogram_ext = pool
.get_extension_by_name("confidence.api.histogram")
.expect("confidence.api.histogram extension not found in descriptor pool");

let mut code = String::new();

for msg in pool.all_messages() {
let opts = msg.options();
if !opts.has_extension(&histogram_ext) {
continue;
}
let val = opts.get_extension(&histogram_ext);

let histogram_msg = match val.as_ref() {
Value::Message(m) => m,
_ => continue,
};

let min_value = histogram_msg
.get_field_by_name("min_value")
.and_then(|v| v.as_i32())
.unwrap_or(0);
let max_value = histogram_msg
.get_field_by_name("max_value")
.and_then(|v| v.as_i32())
.unwrap_or(0);
let bucket_count = histogram_msg
.get_field_by_name("bucket_count")
.and_then(|v| v.as_i32())
.unwrap_or(0);
let unit = histogram_msg
.get_field_by_name("unit")
.and_then(|v| v.as_str().map(|s| s.to_owned()))
.unwrap_or_default();

assert!(
min_value > 0,
"histogram min_value must be positive, got {min_value} on {}",
msg.full_name()
);
assert!(
max_value > min_value,
"histogram max_value must be greater than min_value, got max={max_value} min={min_value} on {}",
msg.full_name()
);
assert!(
bucket_count >= 3,
"histogram bucket_count must be >= 3, got {bucket_count} on {}",
msg.full_name()
);

let rust_type = proto_name_to_rust_type(msg.full_name());

writeln!(
code,
"impl crate::telemetry::HistogramConfig for crate::proto::{rust_type} {{"
)
.unwrap();
writeln!(code, " const MIN_VALUE: u32 = {min_value};").unwrap();
writeln!(code, " const MAX_VALUE: u32 = {max_value};").unwrap();
writeln!(code, " const BUCKET_COUNT: usize = {bucket_count};").unwrap();
writeln!(code, " const UNIT: &'static str = \"{unit}\";").unwrap();
writeln!(code, "}}").unwrap();
writeln!(code).unwrap();
}

// Generate REASON_COUNT from the ResolveReason enum
let reason_enum = pool
.get_enum_by_name("confidence.flags.resolver.v1.ResolveReason")
Expand Down Expand Up @@ -213,38 +148,3 @@ fn generate_telemetry_config(descriptor_path: &std::path::Path) -> Result<()> {

Ok(())
}

/// Convert a fully-qualified proto name like
/// `confidence.flags.resolver.v1.TelemetryData.ResolveLatency`
/// into a Rust module path like
/// `confidence::flags::resolver::v1::telemetry_data::ResolveLatency`.
fn proto_name_to_rust_type(full_name: &str) -> String {
let parts: Vec<&str> = full_name.split('.').collect();
let mut result = String::new();
for (i, part) in parts.iter().enumerate() {
if i > 0 {
result.push_str("::");
}
// In prost, parent message names become snake_case modules,
// but the final type name stays PascalCase. We also need to handle
// package segments (all lowercase) vs message names (PascalCase).
if i < parts.len() - 1 && part.chars().next().is_some_and(|c| c.is_uppercase()) {
// This is a parent message name — prost generates a module for it in snake_case
result.push_str(&to_snake_case(part));
} else {
result.push_str(part);
}
}
result
}

fn to_snake_case(s: &str) -> String {
let mut result = String::new();
for (i, ch) in s.chars().enumerate() {
if ch.is_uppercase() && i > 0 {
result.push('_');
}
result.push(ch.to_ascii_lowercase());
}
result
}
11 changes: 0 additions & 11 deletions confidence-resolver/protos/confidence/api/annotations.proto
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,3 @@ extend google.protobuf.FieldOptions {
ValidationSpec validation = 4324223;
}

// Histogram configuration for metrics collection
message HistogramSpec {
int32 min_value = 1;
int32 max_value = 2;
int32 bucket_count = 3;
string unit = 4;
}

extend google.protobuf.MessageOptions {
optional HistogramSpec histogram = 4399232;
}
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,6 @@ message TelemetryData {
string resolver_version = 8;

message ResolveLatency {
option (confidence.api.histogram) = {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This was cute, but isn't really needed any longer cause we resample the histograms if client/server have different settings. Removing it deprecates quite a lot of code in builder.rs

min_value: 1
max_value: 1000000
bucket_count: 100
unit: "microseconds"
};

// Delta sum of observed values since the last flush.
uint32 sum = 1;
uint32 count = 2;
Expand Down
Loading
Loading