Skip to content

Commit d498724

Browse files
goffrieConvex, Inc.
authored and
Convex, Inc.
committed
Instrument gRPC method handlers using their method name (#35682)
GitOrigin-RevId: baaaa9a8cdd70ce127d93ad1915981e93627b332
1 parent 423f914 commit d498724

File tree

9 files changed

+126
-10
lines changed

9 files changed

+126
-10
lines changed

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/common/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ openidconnect = { workspace = true }
5252
packed_value = { path = "../packed_value" }
5353
parking_lot = { workspace = true }
5454
pb = { path = "../pb" }
55+
pb_extras = { path = "../pb_extras" }
5556
pin-project = { workspace = true }
5657
prometheus = { workspace = true }
5758
prometheus-hyper = { workspace = true }

crates/common/src/grpc/mod.rs

Lines changed: 30 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
use std::{
22
convert::Infallible,
33
net::SocketAddr,
4+
sync::Arc,
45
task::{
56
Context,
67
Poll,
78
},
89
};
910

11+
use fnv::FnvHashMap;
1012
use futures::Future;
13+
use http::Request;
1114
use pb::error_metadata::ErrorMetadataStatusExt;
15+
use pb_extras::ReflectionService;
1216
use sentry::integrations::tower as sentry_tower;
1317
use tokio::net::TcpSocket;
1418
use tokio_metrics::Instrumented;
@@ -35,8 +39,11 @@ use crate::{
3539
runtime::TaskManager,
3640
};
3741

42+
// maps the full route `/service.Service/Method` to just `Method`
43+
type KnownMethods = FnvHashMap<String, &'static str>;
3844
pub struct ConvexGrpcService {
3945
routes: Routes,
46+
known_methods: KnownMethods,
4047
health_reporter: HealthReporter,
4148
service_names: Vec<&'static str>,
4249
}
@@ -47,6 +54,7 @@ impl ConvexGrpcService {
4754
let routes = Routes::new(health_service);
4855
Self {
4956
routes,
57+
known_methods: FnvHashMap::default(),
5058
health_reporter,
5159
service_names: Vec::new(),
5260
}
@@ -58,7 +66,7 @@ impl ConvexGrpcService {
5866
http::Request<tonic::body::BoxBody>,
5967
Response = http::Response<tonic::body::BoxBody>,
6068
Error = Infallible,
61-
> + NamedService
69+
> + ReflectionService
6270
+ Clone
6371
+ Send
6472
+ 'static,
@@ -69,15 +77,20 @@ impl ConvexGrpcService {
6977
// line with all names when we start serving.
7078
let service_name = <S as NamedService>::NAME;
7179
self.service_names.push(service_name);
80+
for method_name in S::METHODS {
81+
self.known_methods
82+
.insert(format!("/{service_name}/{method_name}"), method_name);
83+
}
7284
self
7385
}
7486

7587
pub async fn serve<F>(mut self, addr: SocketAddr, shutdown: F) -> anyhow::Result<()>
7688
where
7789
F: Future<Output = ()>,
7890
{
91+
let known_methods = Arc::new(self.known_methods);
7992
let convex_layers = ServiceBuilder::new()
80-
.layer_fn(TokioInstrumentationService::new)
93+
.layer_fn(move |s| TokioInstrumentationService::new(known_methods.clone(), s))
8194
.layer(sentry_tower::NewSentryLayer::new_from_top())
8295
.layer(sentry_tower::SentryHttpLayer::with_transaction());
8396

@@ -118,18 +131,22 @@ pub fn handle_response<T>(response: Result<Response<T>, Status>) -> anyhow::Resu
118131

119132
#[derive(Clone)]
120133
struct TokioInstrumentationService<S> {
134+
known_methods: Arc<KnownMethods>,
121135
inner: S,
122136
}
123137

124138
impl<S> TokioInstrumentationService<S> {
125-
fn new(inner: S) -> Self {
126-
Self { inner }
139+
fn new(known_methods: Arc<KnownMethods>, inner: S) -> Self {
140+
Self {
141+
known_methods,
142+
inner,
143+
}
127144
}
128145
}
129146

130-
impl<S, Request> Service<Request> for TokioInstrumentationService<S>
147+
impl<S, T> Service<Request<T>> for TokioInstrumentationService<S>
131148
where
132-
S: Service<Request>,
149+
S: Service<Request<T>>,
133150
{
134151
type Error = S::Error;
135152
type Future = Instrumented<S::Future>;
@@ -139,7 +156,12 @@ where
139156
self.inner.poll_ready(cx)
140157
}
141158

142-
fn call(&mut self, req: Request) -> Self::Future {
143-
TaskManager::instrument("grpc_handler", self.inner.call(req))
159+
fn call(&mut self, req: Request<T>) -> Self::Future {
160+
let name = self
161+
.known_methods
162+
.get(req.uri().path())
163+
.copied()
164+
.unwrap_or("grpc_handler");
165+
TaskManager::instrument(name, self.inner.call(req))
144166
}
145167
}

crates/pb/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ anyhow = { workspace = true }
1212
convex_sync_types = { path = "../convex/sync_types" }
1313
errors = { path = "../errors" }
1414
http = { workspace = true }
15+
pb_extras = { path = "../pb_extras" }
1516
prost = { workspace = true }
1617
prost-reflect = { workspace = true }
1718
prost-types = { workspace = true }

crates/pb/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ pub mod usage {
4040
include!(concat!(env!("OUT_DIR"), "/usage.rs"));
4141
}
4242

43+
include!(concat!(env!("OUT_DIR"), "/_extras.rs"));
4344
use std::sync::LazyLock;
4445

4546
use prost_reflect::DescriptorPool;

crates/pb_build/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ doctest = false
99

1010
[dependencies]
1111
cfg-if = { workspace = true }
12+
prost = { workspace = true }
1213
tonic-build = { workspace = true }
1314

1415
[package.metadata.cargo-machete]

crates/pb_build/src/lib.rs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use std::{
22
ffi::OsStr,
3+
fmt::Write as _,
34
fs,
45
io::Result,
56
path::{
@@ -8,6 +9,9 @@ use std::{
89
},
910
};
1011

12+
use prost::Message;
13+
use tonic_build::FileDescriptorSet;
14+
1115
cfg_if::cfg_if! {
1216
if #[cfg(target_os = "macos")] {
1317
const PROTOC_BINARY_NAME: &str = "protoc-macos-universal";
@@ -76,8 +80,8 @@ pub fn pb_build(features: Vec<&'static str>, mut extra_includes: Vec<&'static st
7680
let mut includes = vec!["protos/"];
7781
includes.append(&mut extra_includes);
7882

79-
let mut builder =
80-
tonic_build::configure().file_descriptor_set_path(out_dir.join("descriptors.bin"));
83+
let descriptor_set_path = out_dir.join("descriptors.bin");
84+
let mut builder = tonic_build::configure().file_descriptor_set_path(&descriptor_set_path);
8185
for (proto_path, rust_path) in external_paths {
8286
builder = builder.extern_path(proto_path, rust_path);
8387
}
@@ -99,6 +103,47 @@ pub fn pb_build(features: Vec<&'static str>, mut extra_includes: Vec<&'static st
99103
}
100104
mods.sort();
101105

106+
// Read back the file descriptor set to codegen `ReflectionService` impls.
107+
let file_descriptor_set = FileDescriptorSet::decode(&std::fs::read(&descriptor_set_path)?[..])?;
108+
let mut extras = String::new();
109+
for file in file_descriptor_set.file {
110+
let Some(package_name) = file.package else {
111+
continue;
112+
};
113+
if !packages.contains(&package_name) {
114+
continue;
115+
}
116+
for service in file.service {
117+
let Some(service_name) = service.name else {
118+
continue;
119+
};
120+
let lower_name = naive_snake_case(&service_name);
121+
let server_mod = format!("crate::{package_name}::{lower_name}_server");
122+
write!(
123+
&mut extras,
124+
r#"impl<T> pb_extras::ReflectionService for {server_mod}::{service_name}Server<T> {{
125+
const METHODS: &[&str] = &[
126+
"#
127+
)
128+
.unwrap();
129+
for method in service.method {
130+
let Some(method_name) = method.name else {
131+
continue;
132+
};
133+
writeln!(&mut extras, " {method_name:?},").unwrap();
134+
}
135+
write!(
136+
&mut extras,
137+
r#" ];
138+
}}
139+
"#
140+
)
141+
.unwrap();
142+
}
143+
}
144+
145+
std::fs::write(out_dir.join("_extras.rs"), extras)?;
146+
102147
// Now let's build the lib.rs file.
103148
let mut lib_file_contents = String::new();
104149
lib_file_contents.push_str("// @generated - do not modify. Modify build.rs instead.\n");
@@ -118,6 +163,7 @@ pub fn pb_build(features: Vec<&'static str>, mut extra_includes: Vec<&'static st
118163

119164
lib_file_contents.push_str(
120165
r#"
166+
include!(concat!(env!("OUT_DIR"), "/_extras.rs"));
121167
use std::sync::LazyLock;
122168
123169
use prost_reflect::DescriptorPool;
@@ -135,3 +181,20 @@ pub static DESCRIPTOR_POOL: LazyLock<DescriptorPool> =
135181

136182
Ok(())
137183
}
184+
185+
// copied from `tonic-build`
186+
fn naive_snake_case(name: &str) -> String {
187+
let mut s = String::new();
188+
let mut it = name.chars().peekable();
189+
190+
while let Some(x) = it.next() {
191+
s.push(x.to_ascii_lowercase());
192+
if let Some(y) = it.peek() {
193+
if y.is_uppercase() {
194+
s.push('_');
195+
}
196+
}
197+
}
198+
199+
s
200+
}

crates/pb_extras/Cargo.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[package]
2+
name = "pb_extras"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "LicenseRef-FSL-1.1-Apache-2.0"
6+
7+
[dependencies]
8+
tonic = { workspace = true }
9+
10+
[lints]
11+
workspace = true

crates/pb_extras/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
use tonic::server::NamedService;
2+
3+
pub trait ReflectionService: NamedService {
4+
/// The list of methods supported by this service, e.g. "ExecuteVectorQuery"
5+
const METHODS: &[&str];
6+
}

0 commit comments

Comments
 (0)