diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 444a20501..3c4ff9adf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,7 +3,7 @@ name: CI on: pull_request: push: - branches: + branches: - master - v0.* schedule: @@ -33,7 +33,7 @@ jobs: matrix: include: - host: ubuntu-latest - profile: + profile: suffix: name: Linux-Stable${{ matrix.suffix }} runs-on: ${{ matrix.host }} @@ -57,7 +57,7 @@ jobs: matrix: include: - host: ubuntu-latest - profile: + profile: suffix: name: Linux-Stable-openssl${{ matrix.suffix }} runs-on: ${{ matrix.host }} @@ -124,12 +124,12 @@ jobs: - uses: actions/checkout@v2 - run: choco install -y llvm - run: Import-Module $env:ChocolateyInstall\helpers\chocolateyProfile.psm1 - - run: go version ; cargo version ; cmake --version + - run: go version ; cargo version ; cmake --version - run: cargo xtask submodule - run: cargo build - run: cargo test --all - run: cargo test --features "nightly" - + Pre-Release: name: Pre-Release runs-on: ubuntu-latest diff --git a/Cargo.toml b/Cargo.toml index 1afe73be2..493bb5a26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -50,6 +50,7 @@ boringssl = ["grpcio-sys/boringssl", "_secure"] openssl = ["_secure", "grpcio-sys/openssl"] openssl-vendored = ["_secure", "grpcio-sys/openssl-vendored"] no-omit-frame-pointer = ["grpcio-sys/no-omit-frame-pointer"] +internals = ["grpcio-sys/internals"] [badges] travis-ci = { repository = "tikv/grpc-rs" } diff --git a/grpc-sys/Cargo.toml b/grpc-sys/Cargo.toml index 975ae4eb3..23251ece5 100644 --- a/grpc-sys/Cargo.toml +++ b/grpc-sys/Cargo.toml @@ -64,8 +64,9 @@ openssl = ["_secure"] openssl-vendored = ["openssl", "openssl-sys"] no-omit-frame-pointer = [] # A hidden feature that is used to force regenerating bindings. -_gen-bindings = ["bindgen"] +_gen-bindings = ["bindgen", "internals"] _list-package = [] +internals = [] [target.'cfg(not(all(any(target_os = "linux", target_os = "macos"), any(target_arch = "x86_64", target_arch = "aarch64"))))'.build-dependencies] bindgen = "0.59.0" diff --git a/grpc-sys/bindings/binding_internals.rs b/grpc-sys/bindings/binding_internals.rs new file mode 100644 index 000000000..a2daf80d7 --- /dev/null +++ b/grpc-sys/bindings/binding_internals.rs @@ -0,0 +1,78 @@ +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct grpcwrap_stats { + _unused: [u8; 0], +} +#[repr(u32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum grpcwrap_stats_counter { + ClientCallsCreated = 0, + ServerCallsCreated = 1, + ClientChannelsCreated = 2, + ClientSubchannelsCreated = 3, + ServerChannelsCreated = 4, + InsecureConnectionsCreated = 5, + SyscallWrite = 6, + SyscallRead = 7, + TcpReadAlloc8k = 8, + TcpReadAlloc64k = 9, + Http2SettingsWrites = 10, + Http2PingsSent = 11, + Http2WritesBegun = 12, + Http2TransportStalls = 13, + Http2StreamStalls = 14, + CqPluckCreates = 15, + CqNextCreates = 16, + CqCallbackCreates = 17, + COUNTER_COUNT = 18, +} +#[repr(u32)] +#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)] +pub enum grpcwrap_stats_histogram { + CallInitialSize = 0, + TcpWriteSize = 1, + TcpWriteIovSize = 2, + TcpReadSize = 3, + TcpReadOffer = 4, + TcpReadOfferIovSize = 5, + Http2SendMessageSize = 6, + Http2MetadataSize = 7, + HISTOGRAM_COUNT = 8, +} +extern "C" { + pub fn grpcwrap_stats_collect() -> *mut grpcwrap_stats; +} +extern "C" { + pub fn grpcwrap_stats_free(stats: *mut grpcwrap_stats); +} +extern "C" { + pub fn grpcwrap_stats_get_counter( + stats: *const grpcwrap_stats, + which: grpcwrap_stats_counter, + ) -> u64; +} +extern "C" { + pub fn grpcwrap_stats_counter_name(which: grpcwrap_stats_counter) -> grpc_slice; +} +extern "C" { + pub fn grpcwrap_stats_counter_doc(which: grpcwrap_stats_counter) -> grpc_slice; +} +extern "C" { + pub fn grpcwrap_stats_get_histogram_percentile( + stats: *const grpcwrap_stats, + which: grpcwrap_stats_histogram, + percentile: f64, + ) -> f64; +} +extern "C" { + pub fn grpcwrap_stats_get_histogram_count( + stats: *const grpcwrap_stats, + which: grpcwrap_stats_histogram, + ) -> f64; +} +extern "C" { + pub fn grpcwrap_stats_histogram_name(which: grpcwrap_stats_histogram) -> grpc_slice; +} +extern "C" { + pub fn grpcwrap_stats_histogram_doc(which: grpcwrap_stats_histogram) -> grpc_slice; +} diff --git a/grpc-sys/build.rs b/grpc-sys/build.rs index cd3a3acc8..bd3ad3081 100644 --- a/grpc-sys/build.rs +++ b/grpc-sys/build.rs @@ -252,7 +252,12 @@ fn build_grpc(cc: &mut cc::Build, library: &str) { if !cfg!(feature = "_list-package") { config.build_target(library); } - config.uses_cxx11().build() + + config.define("CMAKE_CXX_STANDARD", "14"); + config.define("CMAKE_CXX_STANDARD_REQUIRED", "ON"); + // Use -std=c++yy rather than -std=gnu++yy. + config.define("CMAKE_CXX_EXTENSIONS", "OFF"); + config.build() }; let lib_suffix = if target.contains("msvc") { @@ -294,6 +299,10 @@ fn build_grpc(cc: &mut cc::Build, library: &str) { } cc.include("grpc/include"); + if cfg!(feature = "internals") { + cc.include("grpc"); + cc.include("grpc/third_party/abseil-cpp"); + } } fn figure_ssl_path(build_dir: &str) { @@ -365,6 +374,34 @@ fn get_env(name: &str) -> Option { } } +enum Binding { + GrpcWrap { + #[allow(dead_code)] + src: &'static str, + dest: &'static str, + env: &'static str, + }, + GrpcWrapInternals { + #[allow(dead_code)] + src: &'static str, + dest: &'static str, + env: &'static str, + }, +} + +impl Binding { + fn env(&self) -> &str { + match self { + Self::GrpcWrap { env, .. } | Self::GrpcWrapInternals { env, .. } => env, + } + } + fn dest(&self) -> &str { + match self { + Self::GrpcWrap { dest, .. } | Self::GrpcWrapInternals { dest, .. } => dest, + } + } +} + // Generate the bindings to grpc C-core. // Try to disable the generation of platform-related bindings. #[cfg(any( @@ -374,7 +411,7 @@ fn get_env(name: &str) -> Option { any(target_arch = "x86_64", target_arch = "aarch64") )) ))] -fn bindgen_grpc(file_path: &Path) { +fn bindgen_grpc(binding: Binding, dest_path: &Path) { // create a config to generate binding file let mut config = bindgen::Builder::default(); if cfg!(feature = "_secure") { @@ -410,24 +447,16 @@ fn bindgen_grpc(file_path: &Path) { println!("cargo:rerun-if-env-changed=TEST_BIND"); let gen_tests = env::var("TEST_BIND").map_or(false, |s| s == "1"); - let cfg = config - .header("grpc_wrap.cc") + let mut cfg = config .clang_arg("-xc++") .clang_arg("-I./grpc/include") - .clang_arg("-std=c++11") + .clang_arg("-std=c++14") .rustfmt_bindings(true) .impl_debug(true) .size_t_is_usize(true) .disable_header_comment() - .allowlist_function(r"\bgrpc_.*") - .allowlist_function(r"\bgpr_.*") .allowlist_function(r"\bgrpcwrap_.*") - .allowlist_var(r"\bGRPC_.*") - .allowlist_type(r"\bgrpc_.*") - .allowlist_type(r"\bgpr_.*") .allowlist_type(r"\bgrpcwrap_.*") - .allowlist_type(r"\bcensus_context.*") - .allowlist_type(r"\bverify_peer_options.*") // Block all system headers. .blocklist_file(r"^/.*") .blocklist_function(r"\bgpr_mu_.*") @@ -441,10 +470,33 @@ fn bindgen_grpc(file_path: &Path) { .default_enum_style(bindgen::EnumVariation::Rust { non_exhaustive: false, }); + match binding { + // Generate grpc_wrap.cc bindings. + Binding::GrpcWrap { src, .. } => { + cfg = cfg + .header(src) + .allowlist_function(r"\bgrpc_.*") + .allowlist_function(r"\bgpr_.*") + .allowlist_var(r"\bGRPC_.*") + .allowlist_type(r"\bgrpc_.*") + .allowlist_type(r"\bgpr_.*") + .allowlist_type(r"\bcensus_context.*") + .allowlist_type(r"\bverify_peer_options.*"); + } + // Generate grpc_wrap_internals.cc bindings. + Binding::GrpcWrapInternals { src, .. } => { + cfg = cfg + .header(src) + .clang_arg("-I./grpc") + .clang_arg("-I./grpc/third_party/abseil-cpp") + .blocklist_function(r"\bgrpc_.*") + .blocklist_type(r"\bgrpc_.*"); + } + } println!("running {}", cfg.command_line_flags().join(" ")); cfg.generate() .expect("Unable to generate grpc bindings") - .write_to_file(file_path) + .write_to_file(dest_path) .expect("Couldn't write bindings!"); } @@ -452,54 +504,73 @@ fn bindgen_grpc(file_path: &Path) { // need to be updated by default unless the _gen-bindings feature is specified. // Other platforms use bindgen to generate the bindings every time. fn config_binding_path() { - let target = env::var("TARGET").unwrap(); - let file_path: PathBuf = match target.as_str() { - "x86_64-unknown-linux-gnu" - | "x86_64-unknown-linux-musl" - | "aarch64-unknown-linux-musl" - | "aarch64-unknown-linux-gnu" - | "x86_64-apple-darwin" - | "aarch64-apple-darwin" => { - // Cargo treats nonexistent files changed, so we only emit the rerun-if-changed - // directive when we expect the target-specific pre-generated binding file to be - // present. - println!("cargo:rerun-if-changed=bindings/bindings.rs"); - - PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) - .join("bindings") - .join("bindings.rs") + let config_binding = |binding: Binding| { + let target = env::var("TARGET").unwrap(); + let dest_path = match target.as_str() { + "x86_64-unknown-linux-gnu" + | "x86_64-unknown-linux-musl" + | "aarch64-unknown-linux-musl" + | "aarch64-unknown-linux-gnu" + | "x86_64-apple-darwin" + | "aarch64-apple-darwin" => { + // Cargo treats nonexistent files changed, so we only emit the rerun-if-changed + // directive when we expect the target-specific pre-generated binding file to be + // present. + println!("cargo:rerun-if-changed=bindings/{}", binding.dest()); + + PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("bindings") + .join(binding.dest()) + } + _ => { + PathBuf::from(env::var("OUT_DIR").unwrap()).join(format!("grpc-{}", binding.dest())) + } + }; + println!( + "cargo:rustc-env={}={}", + binding.env(), + dest_path.to_str().unwrap() + ); + #[cfg(any( + feature = "_gen-bindings", + not(all( + any(target_os = "linux", target_os = "macos"), + any(target_arch = "x86_64", target_arch = "aarch64") + )) + ))] + { + // On some system (like Windows), stack size of main thread may + // be too small. + let f = dest_path.clone(); + std::thread::Builder::new() + .stack_size(8 * 1024 * 1024) + .name("bindgen_grpc".to_string()) + .spawn(move || { + bindgen_grpc(binding, &f); + }) + .unwrap() + .join() + .unwrap(); } - _ => PathBuf::from(env::var("OUT_DIR").unwrap()).join("grpc-bindings.rs"), }; - #[cfg(any( - feature = "_gen-bindings", - not(all( - any(target_os = "linux", target_os = "macos"), - any(target_arch = "x86_64", target_arch = "aarch64") - )) - ))] - { - // On some system (like Windows), stack size of main thread may - // be too small. - let f = file_path.clone(); - std::thread::Builder::new() - .stack_size(8 * 1024 * 1024) - .name("bindgen_grpc".to_string()) - .spawn(move || bindgen_grpc(&f)) - .unwrap() - .join() - .unwrap(); + config_binding(Binding::GrpcWrap { + src: "grpc_wrap.cc", + dest: "bindings.rs", + env: "BINDING_WRAP_PATH", + }); + if cfg!(feature = "internals") { + config_binding(Binding::GrpcWrapInternals { + src: "grpc_wrap_internals.cc", + dest: "binding_internals.rs", + env: "BINDING_WRAP_INTERNAL_PATH", + }); } - - println!( - "cargo:rustc-env=BINDING_PATH={}", - file_path.to_str().unwrap() - ); } fn main() { println!("cargo:rerun-if-changed=grpc_wrap.cc"); + println!("cargo:rerun-if-changed=grpc_wrap_internals.cc"); println!("cargo:rerun-if-changed=grpc"); // create a builder to compile grpc_wrap.cc @@ -517,7 +588,9 @@ fn main() { cc.define("_WIN32_WINNT", Some("0x600")); } - if get_env("GRPCIO_SYS_USE_PKG_CONFIG").map_or(false, |s| s == "1") { + if !cfg!(feature = "internals") + && get_env("GRPCIO_SYS_USE_PKG_CONFIG").map_or(false, |s| s == "1") + { // Print cargo metadata. let lib_core = probe_library(library, true); for inc_path in lib_core.include_paths { @@ -528,10 +601,23 @@ fn main() { } cc.cpp(true); - if !cfg!(target_env = "msvc") { - cc.flag("-std=c++11"); + if cfg!(target_env = "msvc") { + cc.flag("-std:c++14"); + } else { + cc.flag("-std=c++14"); } cc.file("grpc_wrap.cc"); + if cfg!(feature = "internals") { + if cfg!(target_env = "msvc") { + // grpc_wrap_internals.cc includes implicitly which uses + // C++ exception handler. Specify -EHsc to use unwind semantics. + cc.flag("-EHsc"); + // Disable warnings from included files on windows. + cc.flag("-external:W0"); + cc.flag("-external:anglebrackets"); + } + cc.file("grpc_wrap_internals.cc"); + } cc.warnings_into_errors(true); cc.compile("libgrpc_wrap.a"); diff --git a/grpc-sys/grpc_wrap_internals.cc b/grpc-sys/grpc_wrap_internals.cc new file mode 100644 index 000000000..002f90a03 --- /dev/null +++ b/grpc-sys/grpc_wrap_internals.cc @@ -0,0 +1,123 @@ +// Copyright 2023 TiKV Project Authors. Licensed under Apache-2.0. + +#include +#include + +#include + +#ifdef GPR_WINDOWS +#define GPR_EXPORT extern "C" __declspec(dllexport) +#define GPR_CALLTYPE __cdecl +#endif + +#ifndef GPR_EXPORT +#define GPR_EXPORT extern "C" +#endif + +#ifndef GPR_CALLTYPE +#define GPR_CALLTYPE +#endif + +/** gRPC stats. + See: grpc/src/core/lib/debug/stats_data.yaml */ +typedef struct grpcwrap_stats grpcwrap_stats; + +enum grpcwrap_stats_counter { + ClientCallsCreated, + ServerCallsCreated, + ClientChannelsCreated, + ClientSubchannelsCreated, + ServerChannelsCreated, + InsecureConnectionsCreated, + SyscallWrite, + SyscallRead, + TcpReadAlloc8k, + TcpReadAlloc64k, + Http2SettingsWrites, + Http2PingsSent, + Http2WritesBegun, + Http2TransportStalls, + Http2StreamStalls, + CqPluckCreates, + CqNextCreates, + CqCallbackCreates, + COUNTER_COUNT +}; +// Just make sure they have the same number of counters. +static_assert(static_cast(grpcwrap_stats_counter::COUNTER_COUNT) == + static_cast(grpc_core::GlobalStats::Counter::COUNT), + "Counter count must be the same"); + +enum grpcwrap_stats_histogram { + CallInitialSize, + TcpWriteSize, + TcpWriteIovSize, + TcpReadSize, + TcpReadOffer, + TcpReadOfferIovSize, + Http2SendMessageSize, + Http2MetadataSize, + HISTOGRAM_COUNT +}; +// Just make sure they have the same number of histograms. +static_assert(static_cast(grpcwrap_stats_histogram::HISTOGRAM_COUNT) == + static_cast(grpc_core::GlobalStats::Histogram::COUNT), + "Histogram count must be the same"); + +GPR_EXPORT grpcwrap_stats* GPR_CALLTYPE grpcwrap_stats_collect() { + return (grpcwrap_stats*)grpc_core::global_stats().Collect().release(); +} + +GPR_EXPORT void GPR_CALLTYPE grpcwrap_stats_free(grpcwrap_stats* stats) { + auto s = (grpc_core::GlobalStats*)stats; + delete s; +} + +GPR_EXPORT uint64_t GPR_CALLTYPE grpcwrap_stats_get_counter( + const grpcwrap_stats* stats, grpcwrap_stats_counter which) { + auto s = (const grpc_core::GlobalStats*)stats; + return s->counters[which]; +} + +GPR_EXPORT grpc_slice GPR_CALLTYPE +grpcwrap_stats_counter_name(grpcwrap_stats_counter which) { + auto name = grpc_core::GlobalStats::counter_name[which]; + auto slice = grpc_slice_from_static_buffer(name.data(), name.size()); + return slice; +} + +GPR_EXPORT grpc_slice GPR_CALLTYPE +grpcwrap_stats_counter_doc(grpcwrap_stats_counter which) { + auto doc = grpc_core::GlobalStats::counter_doc[which]; + auto slice = grpc_slice_from_static_buffer(doc.data(), doc.size()); + return slice; +} + +GPR_EXPORT double GPR_CALLTYPE grpcwrap_stats_get_histogram_percentile( + const grpcwrap_stats* stats, grpcwrap_stats_histogram which, + double percentile) { + auto s = (const grpc_core::GlobalStats*)stats; + return s->histogram(static_cast(which)) + .Percentile(percentile); +} + +GPR_EXPORT double GPR_CALLTYPE grpcwrap_stats_get_histogram_count( + const grpcwrap_stats* stats, grpcwrap_stats_histogram which) { + auto s = (const grpc_core::GlobalStats*)stats; + return s->histogram(static_cast(which)) + .Count(); +} + +GPR_EXPORT grpc_slice GPR_CALLTYPE +grpcwrap_stats_histogram_name(grpcwrap_stats_histogram which) { + auto name = grpc_core::GlobalStats::histogram_name[which]; + auto slice = grpc_slice_from_static_buffer(name.data(), name.size()); + return slice; +} + +GPR_EXPORT grpc_slice GPR_CALLTYPE +grpcwrap_stats_histogram_doc(grpcwrap_stats_histogram which) { + auto doc = grpc_core::GlobalStats::histogram_doc[which]; + auto slice = grpc_slice_from_static_buffer(doc.data(), doc.size()); + return slice; +} diff --git a/grpc-sys/src/lib.rs b/grpc-sys/src/lib.rs index dc0699c06..d6bc53fc4 100644 --- a/grpc-sys/src/lib.rs +++ b/grpc-sys/src/lib.rs @@ -5,7 +5,9 @@ #![allow(non_upper_case_globals)] #[allow(clippy::all)] mod bindings { - include!(env!("BINDING_PATH")); + include!(env!("BINDING_WRAP_PATH")); + #[cfg(feature = "internals")] + include!(env!("BINDING_WRAP_INTERNAL_PATH")); } mod grpc_wrap; diff --git a/src/buf.rs b/src/buf.rs index 7b5f4d31f..a6df16885 100644 --- a/src/buf.rs +++ b/src/buf.rs @@ -64,6 +64,11 @@ impl GrpcSlice { GrpcSlice::from_static_slice(s.as_bytes()) } + /// Creates a slice from grpc_slice. + pub fn from_raw(slice: grpc_slice) -> GrpcSlice { + GrpcSlice(slice) + } + /// Checks whether the slice stores bytes inline. pub fn is_inline(&self) -> bool { self.0.refcount.is_null() diff --git a/src/lib.rs b/src/lib.rs index 652467025..f6d02e05d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -43,6 +43,8 @@ mod metadata; mod quota; mod security; mod server; +#[cfg(feature = "internals")] +pub mod stats; mod task; pub use crate::buf::GrpcSlice; diff --git a/src/stats.rs b/src/stats.rs new file mode 100644 index 000000000..df20e93dd --- /dev/null +++ b/src/stats.rs @@ -0,0 +1,201 @@ +// Copyright 2023 TiKV Project Authors. Licensed under Apache-2.0. + +use std::{ + fmt::{self, Debug, Display}, + str::{self, FromStr}, +}; + +use grpcio_sys::*; + +use crate::buf::GrpcSlice; + +unsafe fn slice_to_string(slice: grpc_slice) -> String { + let s = GrpcSlice::from_raw(slice); + String::from_str(str::from_utf8_unchecked(s.as_slice())).unwrap() +} + +macro_rules! stats_item { + ( + $(#[$item_attr:meta])* + $item:ident($inner:ident); + $name_func:ident; + $doc_func:ident; + $( + $(#[$konst_attr:meta])* ($num:path, $konst:ident); + )+ + ) => { + $(#[$item_attr])* + pub struct $item($inner); + + impl $item { + $( + $(#[$konst_attr])* + pub const $konst: $item = $item($num); + )+ + /// All kinds of this stat. + pub const ALL: &[$item] = &[ $( $item::$konst, )+ ]; + + // Returns name of this stat. + pub fn name(&self) -> String { + unsafe { + let slice = $name_func(self.0); + slice_to_string(slice) + } + } + + // Returns doc of this stat. + pub fn doc(&self) -> String { + unsafe { + let slice = $doc_func(self.0); + slice_to_string(slice) + } + } + } + + impl Display for $item { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + Debug::fmt(self, f) + } + } + + impl Debug for $item { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", self.name(), + ) + } + } + } +} + +stats_item! { + /// gRPC stats counter. + #[derive(PartialEq, Eq, Clone, Copy, Hash)] + Counter(grpcwrap_stats_counter); + grpcwrap_stats_counter_name; + grpcwrap_stats_counter_doc; + /// Number of client side calls created by this process + (grpcwrap_stats_counter::ClientCallsCreated, CLIENT_CALLS_CREATED); + /// Number of server side calls created by this process + (grpcwrap_stats_counter::ServerCallsCreated, SERVER_CALLS_CREATED); + /// Number of client channels created + (grpcwrap_stats_counter::ClientChannelsCreated, CLIENT_CHANNELS_CREATED); + /// Number of client subchannels created + (grpcwrap_stats_counter::ClientSubchannelsCreated, CLIENT_SUBCHANNELS_CREATED); + /// Number of server channels created + (grpcwrap_stats_counter::ServerChannelsCreated, SERVER_CHANNELS_CREATED); + /// Number of insecure connections created + (grpcwrap_stats_counter::InsecureConnectionsCreated, INSECURE_CONNECTIONS_CREATED); + /// Number of write syscalls (or equivalent - eg sendmsg) made by this process + (grpcwrap_stats_counter::SyscallWrite, SYSCALL_WRITE); + /// Number of read syscalls (or equivalent - eg recvmsg) made by this process + (grpcwrap_stats_counter::SyscallRead, SYSCALL_READ); + /// Number of 8k allocations by the TCP subsystem for reading + (grpcwrap_stats_counter::TcpReadAlloc8k, TCP_READ_ALLOC_8K); + /// Number of 64k allocations by the TCP subsystem for reading + (grpcwrap_stats_counter::TcpReadAlloc64k, TCP_READ_ALLOC_64K); + /// Number of settings frames sent + (grpcwrap_stats_counter::Http2SettingsWrites, HTTP2_SETTINGS_WRITES); + /// Number of HTTP2 pings sent by process + (grpcwrap_stats_counter::Http2PingsSent, HTTP_2PINGS_SENT); + /// Number of HTTP2 writes initiated + (grpcwrap_stats_counter::Http2WritesBegun, HTTP2_WRITES_BEGUN); + /// Number of times sending was completely stalled by the transport flow control window + (grpcwrap_stats_counter::Http2TransportStalls, HTTP2_TRANSPORT_STALLS); + /// Number of times sending was completely stalled by the stream flow control window + (grpcwrap_stats_counter::Http2StreamStalls, HTTP2_STREAM_STALLS); + /// Number of completion queues created for cq_pluck (indicates sync api usage) + (grpcwrap_stats_counter::CqPluckCreates, CQ_PLUCK_CREATES); + /// Number of completion queues created for cq_next (indicates cq async api usage) + (grpcwrap_stats_counter::CqNextCreates, CQ_NEXT_CREATES); + /// Number of completion queues created for cq_callback (indicates callback api usage) + (grpcwrap_stats_counter::CqCallbackCreates, CQ_CALLBACK_CREATES); +} + +stats_item! { + /// gRPC stats histogram. + #[derive(PartialEq, Eq, Clone, Copy)] + Histogram(grpcwrap_stats_histogram); + grpcwrap_stats_histogram_name; + grpcwrap_stats_histogram_doc; + /// Initial size of the grpc_call arena created at call start + (grpcwrap_stats_histogram::CallInitialSize, CALL_INITIAL_SIZE); + /// Number of bytes offered to each syscall_write + (grpcwrap_stats_histogram::TcpWriteSize, TCP_WRITE_SIZE); + /// Number of byte segments offered to each syscall_write + (grpcwrap_stats_histogram::TcpWriteIovSize, TCP_WRITE_IOV_SIZE); + /// Number of bytes received by each syscall_read + (grpcwrap_stats_histogram::TcpReadSize, TCP_READ_SIZE); + /// Number of bytes offered to each syscall_read + (grpcwrap_stats_histogram::TcpReadOffer, TCP_READ_OFFER); + /// Number of byte segments offered to each syscall_read + (grpcwrap_stats_histogram::TcpReadOfferIovSize, TCP_READ_OFFER_IOV_SIZE); + /// Size of messages received by HTTP2 transport + (grpcwrap_stats_histogram::Http2SendMessageSize, HTTP2_SEND_MESSAGE_SIZE); + /// Number of bytes consumed by metadata, according to HPACK accounting rules + (grpcwrap_stats_histogram::Http2MetadataSize, HTTP2_METADATA_SIZE); +} + +pub struct Stats { + stats: *mut grpcwrap_stats, +} + +impl Drop for Stats { + fn drop(&mut self) { + unsafe { + grpcwrap_stats_free(self.stats); + } + } +} + +impl Stats { + pub fn collect() -> Stats { + let stats = unsafe { grpcwrap_stats_collect() }; + Stats { stats } + } + + pub fn counter(&self, which: Counter) -> u64 { + unsafe { grpcwrap_stats_get_counter(self.stats, which.0) } + } + + pub fn histogram_percentile(&self, which: Histogram, percentile: f64) -> f64 { + unsafe { grpcwrap_stats_get_histogram_percentile(self.stats, which.0, percentile) } + } + + pub fn histogram_count(&self, which: Histogram) -> f64 { + unsafe { grpcwrap_stats_get_histogram_count(self.stats, which.0) } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_name_doc() { + for i in Counter::ALL { + let _ = i.name(); + let _ = i.doc(); + } + for i in Histogram::ALL { + let _ = i.name(); + let _ = i.doc(); + } + } + + #[test] + fn test_counter() { + let stats = Stats::collect(); + for i in Counter::ALL { + let _ = stats.counter(*i); + } + } + + #[test] + fn test_histogram() { + let stats = Stats::collect(); + for i in Histogram::ALL { + let _ = stats.histogram_count(*i); + let _ = stats.histogram_percentile(*i, 0.99); + } + } +} diff --git a/tests-and-examples/Cargo.toml b/tests-and-examples/Cargo.toml index 51b0dd970..2c687827d 100644 --- a/tests-and-examples/Cargo.toml +++ b/tests-and-examples/Cargo.toml @@ -21,7 +21,7 @@ protobuf = { version = "2.22", optional = true } prost = { version = "0.11", optional = true } bytes = { version = "1.0", optional = true } log = "0.4" -grpcio = { path = "..", default-features = false, features = ["boringssl"] } +grpcio = { path = "..", default-features = false, features = ["boringssl", "internals"] } grpcio-health = { path = "../health", default-features = false } [dev-dependencies] diff --git a/tests-and-examples/tests/cases/misc.rs b/tests-and-examples/tests/cases/misc.rs index ff37e0c6e..1043755a0 100644 --- a/tests-and-examples/tests/cases/misc.rs +++ b/tests-and-examples/tests/cases/misc.rs @@ -420,3 +420,42 @@ fn test_channelz() { res ); } + +#[test] +fn test_stats() { + let env = Arc::new(Environment::new(2)); + // Start a server and delay the process of grpc server. + let service = create_greeter(PeerService); + let mut server = ServerBuilder::new(env.clone()) + .register_service(service) + .build() + .unwrap(); + let port = server + .add_listening_port("127.0.0.1:0", ServerCredentials::insecure()) + .unwrap(); + server.start(); + let ch = ChannelBuilder::new(env).connect(&format!("127.0.0.1:{port}")); + let client = GreeterClient::new(ch); + let req = HelloRequest::default(); + client.say_hello(&req).unwrap(); + let stats = stats::Stats::collect(); + assert_ne!(stats.counter(stats::Counter::CLIENT_CALLS_CREATED), 0); + assert_ne!(stats.counter(stats::Counter::CLIENT_CHANNELS_CREATED), 0); + assert_ne!(stats.counter(stats::Counter::SERVER_CHANNELS_CREATED), 0); + assert_ne!( + stats.histogram_count(stats::Histogram::CALL_INITIAL_SIZE), + 0.0 + ); + assert_ne!( + stats.histogram_percentile(stats::Histogram::CALL_INITIAL_SIZE, 0.8), + 0.0 + ); + assert_ne!( + stats.histogram_count(stats::Histogram::HTTP2_SEND_MESSAGE_SIZE), + 0.0 + ); + assert_ne!( + stats.histogram_percentile(stats::Histogram::HTTP2_SEND_MESSAGE_SIZE, 0.8), + 0.0 + ); +} diff --git a/xtask/src/main.rs b/xtask/src/main.rs index e1aab16c0..b9cbfd945 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -98,15 +98,21 @@ fn submodule() { } fn clang_lint() { - exec(cmd("clang-tidy").args(&[ - "grpc-sys/grpc_wrap.cc", - "--", - "-Igrpc-sys/grpc/include", - "-x", - "c++", - "-std=c++11", - ])); - exec(cmd("clang-format").args(&["-i", "grpc-sys/grpc_wrap.cc"])); + fn lint(file: &str) { + exec(cmd("clang-tidy").args(&[ + file, + "--", + "-Igrpc-sys/grpc/include", + "-Igrpc-sys/grpc", + "-Igrpc-sys/grpc/third_party/abseil-cpp", + "-x", + "c++", + "-std=c++14", + ])); + exec(cmd("clang-format").args(&["-i", file])); + } + lint("grpc-sys/grpc_wrap.cc"); + lint("grpc-sys/grpc_wrap_internals.cc"); } const PROTOS: &[(&str, &[&str], &str, &str)] = &[